@beechcms/core 0.7.0 → 0.8.1

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 (370) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/antivirus/antivirus.interface.d.ts +12 -0
  3. package/dist/antivirus/antivirus.interface.d.ts.map +1 -0
  4. package/dist/antivirus/antivirus.interface.js +3 -0
  5. package/dist/antivirus/noop-antivirus.provider.d.ts +6 -0
  6. package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -0
  7. package/dist/antivirus/noop-antivirus.provider.js +8 -0
  8. package/dist/antivirus/virustotal-antivirus.provider.d.ts +8 -0
  9. package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -0
  10. package/dist/antivirus/virustotal-antivirus.provider.js +48 -0
  11. package/dist/automations-grammar-words.d.ts +2 -0
  12. package/dist/automations-grammar-words.d.ts.map +1 -0
  13. package/dist/automations-grammar-words.js +8 -0
  14. package/dist/automations.repository.interface.d.ts +19 -0
  15. package/dist/automations.repository.interface.d.ts.map +1 -0
  16. package/dist/automations.repository.interface.js +3 -0
  17. package/dist/automations.runner.interface.d.ts +10 -0
  18. package/dist/automations.runner.interface.d.ts.map +1 -0
  19. package/dist/automations.runner.interface.js +3 -0
  20. package/dist/automations.runner.stub.d.ts +5 -0
  21. package/dist/automations.runner.stub.d.ts.map +1 -0
  22. package/dist/automations.runner.stub.js +7 -0
  23. package/dist/automations.types.d.ts +75 -0
  24. package/dist/automations.types.d.ts.map +1 -0
  25. package/dist/automations.types.js +3 -0
  26. package/dist/clock.d.ts +25 -0
  27. package/dist/clock.d.ts.map +1 -0
  28. package/dist/clock.js +11 -0
  29. package/dist/common/canonical-json.d.ts +30 -0
  30. package/dist/common/canonical-json.d.ts.map +1 -0
  31. package/dist/common/canonical-json.js +89 -0
  32. package/dist/common/hooks.d.ts +3 -0
  33. package/dist/common/hooks.d.ts.map +1 -1
  34. package/dist/content/content.repository.d.ts +2 -2
  35. package/dist/content/content.repository.d.ts.map +1 -1
  36. package/dist/content/deletion-ledger.d.ts +39 -0
  37. package/dist/content/deletion-ledger.d.ts.map +1 -0
  38. package/dist/content/deletion-ledger.js +3 -0
  39. package/dist/content/richtext/richtext-render.d.ts.map +1 -1
  40. package/dist/content/richtext/richtext-render.js +14 -1
  41. package/dist/content/seed.repository.d.ts +22 -0
  42. package/dist/content/seed.repository.d.ts.map +1 -1
  43. package/dist/content-scan.repository.d.ts +9 -0
  44. package/dist/content-scan.repository.d.ts.map +1 -0
  45. package/dist/content-scan.repository.js +3 -0
  46. package/dist/content.repository.d.ts +247 -0
  47. package/dist/content.repository.d.ts.map +1 -0
  48. package/dist/content.repository.js +59 -0
  49. package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
  50. package/dist/dashboard-layout/seed-layout.js +13 -6
  51. package/dist/dashboard-layout.d.ts +154 -0
  52. package/dist/dashboard-layout.d.ts.map +1 -0
  53. package/dist/dashboard-layout.js +186 -0
  54. package/dist/dashboard-layout.repository.d.ts +18 -0
  55. package/dist/dashboard-layout.repository.d.ts.map +1 -0
  56. package/dist/dashboard-layout.repository.js +3 -0
  57. package/dist/dashboard-permissions.d.ts +3 -0
  58. package/dist/dashboard-permissions.d.ts.map +1 -0
  59. package/dist/dashboard-permissions.js +11 -0
  60. package/dist/dashboard-scopes.d.ts +12 -0
  61. package/dist/dashboard-scopes.d.ts.map +1 -0
  62. package/dist/dashboard-scopes.js +30 -0
  63. package/dist/define-seed.d.ts +3 -0
  64. package/dist/define-seed.d.ts.map +1 -0
  65. package/dist/define-seed.js +5 -0
  66. package/dist/demo-data.repository.d.ts +5 -0
  67. package/dist/demo-data.repository.d.ts.map +1 -0
  68. package/dist/demo-data.repository.js +3 -0
  69. package/dist/email.provider.d.ts +32 -0
  70. package/dist/email.provider.d.ts.map +1 -0
  71. package/dist/email.provider.js +4 -0
  72. package/dist/engine/ddl/ddl.d.ts +216 -0
  73. package/dist/engine/ddl/ddl.d.ts.map +1 -0
  74. package/dist/engine/ddl/ddl.js +574 -0
  75. package/dist/engine/ddl/schema-mutator.d.ts +26 -0
  76. package/dist/engine/ddl/schema-mutator.d.ts.map +1 -0
  77. package/dist/engine/ddl/schema-mutator.js +3 -0
  78. package/dist/engine/ddl/seed-ddl-destructive.d.ts +40 -0
  79. package/dist/engine/ddl/seed-ddl-destructive.d.ts.map +1 -0
  80. package/dist/engine/ddl/seed-ddl-destructive.js +149 -0
  81. package/dist/engine/ddl/seed-ddl.d.ts +43 -0
  82. package/dist/engine/ddl/seed-ddl.d.ts.map +1 -0
  83. package/dist/engine/ddl/seed-ddl.js +115 -0
  84. package/dist/engine/ddl.d.ts +15 -0
  85. package/dist/engine/ddl.d.ts.map +1 -1
  86. package/dist/engine/ddl.js +30 -1
  87. package/dist/engine/introspection/introspection.d.ts +101 -0
  88. package/dist/engine/introspection/introspection.d.ts.map +1 -0
  89. package/dist/engine/introspection/introspection.js +127 -0
  90. package/dist/engine/introspection/schema-fingerprint.d.ts +73 -0
  91. package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -0
  92. package/dist/engine/introspection/schema-fingerprint.js +97 -0
  93. package/dist/engine/introspection.d.ts +101 -0
  94. package/dist/engine/introspection.d.ts.map +1 -0
  95. package/dist/engine/introspection.js +127 -0
  96. package/dist/engine/localization/localization.d.ts +127 -0
  97. package/dist/engine/localization/localization.d.ts.map +1 -0
  98. package/dist/engine/localization/localization.js +212 -0
  99. package/dist/engine/localization.d.ts +127 -0
  100. package/dist/engine/localization.d.ts.map +1 -0
  101. package/dist/engine/localization.js +212 -0
  102. package/dist/engine/policies.d.ts.map +1 -1
  103. package/dist/engine/policies.js +5 -0
  104. package/dist/engine/privacy/policies.d.ts +58 -0
  105. package/dist/engine/privacy/policies.d.ts.map +1 -0
  106. package/dist/engine/privacy/policies.js +185 -0
  107. package/dist/engine/privacy/privacy.service.d.ts +65 -0
  108. package/dist/engine/privacy/privacy.service.d.ts.map +1 -0
  109. package/dist/engine/privacy/privacy.service.js +114 -0
  110. package/dist/engine/privacy.service.d.ts.map +1 -1
  111. package/dist/engine/query/query.d.ts +23 -0
  112. package/dist/engine/query/query.d.ts.map +1 -0
  113. package/dist/engine/query/query.js +301 -0
  114. package/dist/engine/query/relations.d.ts +28 -0
  115. package/dist/engine/query/relations.d.ts.map +1 -0
  116. package/dist/engine/query/relations.js +25 -0
  117. package/dist/engine/query/serialize.d.ts +22 -0
  118. package/dist/engine/query/serialize.d.ts.map +1 -0
  119. package/dist/engine/query/serialize.js +175 -0
  120. package/dist/engine/schema-fingerprint.d.ts +73 -0
  121. package/dist/engine/schema-fingerprint.d.ts.map +1 -0
  122. package/dist/engine/schema-fingerprint.js +97 -0
  123. package/dist/engine/seed-registry.d.ts.map +1 -1
  124. package/dist/engine/seed-registry.js +23 -4
  125. package/dist/engine/seed-types-generator.d.ts.map +1 -1
  126. package/dist/engine/seed-types-generator.js +8 -5
  127. package/dist/engine/seed-validation.d.ts +7 -0
  128. package/dist/engine/seed-validation.d.ts.map +1 -1
  129. package/dist/engine/seed-validation.js +59 -1
  130. package/dist/engine/seeds/define-seed.d.ts +3 -0
  131. package/dist/engine/seeds/define-seed.d.ts.map +1 -0
  132. package/dist/engine/seeds/define-seed.js +5 -0
  133. package/dist/engine/seeds/seed-registry.d.ts +71 -0
  134. package/dist/engine/seeds/seed-registry.d.ts.map +1 -0
  135. package/dist/engine/seeds/seed-registry.js +167 -0
  136. package/dist/engine/seeds/seed-types-generator.d.ts +25 -0
  137. package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -0
  138. package/dist/engine/seeds/seed-types-generator.js +117 -0
  139. package/dist/engine/seeds/seed-validation.d.ts +30 -0
  140. package/dist/engine/seeds/seed-validation.d.ts.map +1 -0
  141. package/dist/engine/seeds/seed-validation.js +364 -0
  142. package/dist/engine/seeds/seeds.d.ts +20 -0
  143. package/dist/engine/seeds/seeds.d.ts.map +1 -0
  144. package/dist/engine/seeds/seeds.js +21 -0
  145. package/dist/engine/types.d.ts +7 -0
  146. package/dist/engine/types.d.ts.map +1 -1
  147. package/dist/engine/validation/emptiness.d.ts +12 -0
  148. package/dist/engine/validation/emptiness.d.ts.map +1 -0
  149. package/dist/engine/validation/emptiness.js +33 -0
  150. package/dist/engine/validation/index.d.ts +1 -1
  151. package/dist/engine/validation/index.d.ts.map +1 -1
  152. package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
  153. package/dist/engine/validation/richtext-sanitizer.js +2 -1
  154. package/dist/engine.d.ts +144 -0
  155. package/dist/engine.d.ts.map +1 -0
  156. package/dist/engine.js +741 -0
  157. package/dist/file-types.d.ts +13 -0
  158. package/dist/file-types.d.ts.map +1 -0
  159. package/dist/file-types.js +85 -0
  160. package/dist/hooks.d.ts +29 -0
  161. package/dist/hooks.d.ts.map +1 -0
  162. package/dist/hooks.js +3 -0
  163. package/dist/id-generator.d.ts +26 -0
  164. package/dist/id-generator.d.ts.map +1 -0
  165. package/dist/id-generator.js +12 -0
  166. package/dist/idempotency.repository.d.ts +13 -0
  167. package/dist/idempotency.repository.d.ts.map +1 -0
  168. package/dist/idempotency.repository.js +3 -0
  169. package/dist/index.d.ts +16 -1
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +16 -1
  172. package/dist/kanban-position.repository.d.ts +19 -0
  173. package/dist/kanban-position.repository.d.ts.map +1 -0
  174. package/dist/kanban-position.repository.js +3 -0
  175. package/dist/kanban.d.ts +75 -0
  176. package/dist/kanban.d.ts.map +1 -0
  177. package/dist/kanban.js +82 -0
  178. package/dist/layout-permissions.d.ts +3 -0
  179. package/dist/layout-permissions.d.ts.map +1 -0
  180. package/dist/layout-permissions.js +11 -0
  181. package/dist/media/file-types.d.ts +103 -9
  182. package/dist/media/file-types.d.ts.map +1 -1
  183. package/dist/media/file-types.js +336 -52
  184. package/dist/media/image-transformer.d.ts +34 -0
  185. package/dist/media/image-transformer.d.ts.map +1 -0
  186. package/dist/media/image-transformer.js +3 -0
  187. package/dist/media/magic-bytes.d.ts +21 -0
  188. package/dist/media/magic-bytes.d.ts.map +1 -0
  189. package/dist/media/magic-bytes.js +66 -0
  190. package/dist/media/media-transform.d.ts +88 -0
  191. package/dist/media/media-transform.d.ts.map +1 -0
  192. package/dist/media/media-transform.js +193 -0
  193. package/dist/media.repository.d.ts +61 -0
  194. package/dist/media.repository.d.ts.map +1 -0
  195. package/dist/media.repository.js +3 -0
  196. package/dist/notifications/notification-service.d.ts +1 -1
  197. package/dist/oauth/authorization-code.repository.d.ts +33 -0
  198. package/dist/oauth/authorization-code.repository.d.ts.map +1 -0
  199. package/dist/oauth/authorization-code.repository.js +3 -0
  200. package/dist/oauth/client.repository.d.ts +17 -0
  201. package/dist/oauth/client.repository.d.ts.map +1 -0
  202. package/dist/oauth/client.repository.js +3 -0
  203. package/dist/oauth/consent.repository.d.ts +25 -0
  204. package/dist/oauth/consent.repository.d.ts.map +1 -0
  205. package/dist/oauth/consent.repository.js +3 -0
  206. package/dist/oauth/pkce.d.ts +21 -0
  207. package/dist/oauth/pkce.d.ts.map +1 -0
  208. package/dist/oauth/pkce.js +47 -0
  209. package/dist/oauth/role-guard.d.ts +32 -0
  210. package/dist/oauth/role-guard.d.ts.map +1 -0
  211. package/dist/oauth/role-guard.js +14 -0
  212. package/dist/oauth/scopes.d.ts +27 -0
  213. package/dist/oauth/scopes.d.ts.map +1 -0
  214. package/dist/oauth/scopes.js +41 -0
  215. package/dist/oauth/token.repository.d.ts +46 -0
  216. package/dist/oauth/token.repository.d.ts.map +1 -0
  217. package/dist/oauth/token.repository.js +3 -0
  218. package/dist/observability/activity-logger.d.ts +1 -1
  219. package/dist/observability/activity-logger.d.ts.map +1 -1
  220. package/dist/policies.d.ts +10 -0
  221. package/dist/policies.d.ts.map +1 -0
  222. package/dist/policies.js +33 -0
  223. package/dist/queue/queue.stub.d.ts.map +1 -1
  224. package/dist/queue/scheduler.stub.d.ts.map +1 -1
  225. package/dist/queue.interface.d.ts +39 -0
  226. package/dist/queue.interface.d.ts.map +1 -0
  227. package/dist/queue.interface.js +3 -0
  228. package/dist/queue.stub.d.ts +6 -0
  229. package/dist/queue.stub.d.ts.map +1 -0
  230. package/dist/queue.stub.js +8 -0
  231. package/dist/rate-limit/rate-limiter.d.ts +13 -5
  232. package/dist/rate-limit/rate-limiter.d.ts.map +1 -1
  233. package/dist/rate-limit/token-bucket-rate-limiter.d.ts +20 -0
  234. package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -0
  235. package/dist/rate-limit/token-bucket-rate-limiter.js +63 -0
  236. package/dist/rbac/evaluate.d.ts +57 -0
  237. package/dist/rbac/evaluate.d.ts.map +1 -0
  238. package/dist/rbac/evaluate.js +113 -0
  239. package/dist/rbac/invitation.repository.d.ts +66 -0
  240. package/dist/rbac/invitation.repository.d.ts.map +1 -0
  241. package/dist/rbac/invitation.repository.js +3 -0
  242. package/dist/rbac/permissions.d.ts +39 -0
  243. package/dist/rbac/permissions.d.ts.map +1 -0
  244. package/dist/rbac/permissions.js +43 -0
  245. package/dist/rbac/types.d.ts +118 -0
  246. package/dist/rbac/types.d.ts.map +1 -0
  247. package/dist/rbac/types.js +3 -0
  248. package/dist/relations.d.ts +28 -0
  249. package/dist/relations.d.ts.map +1 -0
  250. package/dist/relations.js +25 -0
  251. package/dist/richtext-render.d.ts +11 -0
  252. package/dist/richtext-render.d.ts.map +1 -0
  253. package/dist/richtext-render.js +87 -0
  254. package/dist/richtext.d.ts +11 -0
  255. package/dist/richtext.d.ts.map +1 -0
  256. package/dist/richtext.js +13 -0
  257. package/dist/scheduler.interface.d.ts +4 -0
  258. package/dist/scheduler.interface.d.ts.map +1 -0
  259. package/dist/scheduler.interface.js +3 -0
  260. package/dist/scheduler.stub.d.ts +5 -0
  261. package/dist/scheduler.stub.d.ts.map +1 -0
  262. package/dist/scheduler.stub.js +7 -0
  263. package/dist/schema/canonical.d.ts +15 -0
  264. package/dist/schema/canonical.d.ts.map +1 -0
  265. package/dist/schema/canonical.js +37 -0
  266. package/dist/schema/define.d.ts +42 -0
  267. package/dist/schema/define.d.ts.map +1 -0
  268. package/dist/schema/define.js +56 -0
  269. package/dist/schema/emit.d.ts +14 -0
  270. package/dist/schema/emit.d.ts.map +1 -0
  271. package/dist/schema/emit.js +43 -0
  272. package/dist/schema/index.d.ts +12 -0
  273. package/dist/schema/index.d.ts.map +1 -0
  274. package/dist/schema/index.js +13 -0
  275. package/dist/schema/manifest-seeds.d.ts +12 -0
  276. package/dist/schema/manifest-seeds.d.ts.map +1 -0
  277. package/dist/schema/manifest-seeds.js +48 -0
  278. package/dist/schema/manifest-validation.d.ts +13 -0
  279. package/dist/schema/manifest-validation.d.ts.map +1 -0
  280. package/dist/schema/manifest-validation.js +41 -0
  281. package/dist/schema/manifest.types.d.ts +54 -0
  282. package/dist/schema/manifest.types.d.ts.map +1 -0
  283. package/dist/schema/manifest.types.js +7 -0
  284. package/dist/schema-mutator.d.ts +26 -0
  285. package/dist/schema-mutator.d.ts.map +1 -0
  286. package/dist/schema-mutator.js +3 -0
  287. package/dist/search/vector-extractor.d.ts +11 -0
  288. package/dist/search/vector-extractor.d.ts.map +1 -0
  289. package/dist/search/vector-extractor.js +25 -0
  290. package/dist/search/vector.repository.d.ts +13 -0
  291. package/dist/search/vector.repository.d.ts.map +1 -0
  292. package/dist/search/vector.repository.js +3 -0
  293. package/dist/security/time-trap-token.repository.d.ts +9 -0
  294. package/dist/security/time-trap-token.repository.d.ts.map +1 -0
  295. package/dist/security/time-trap-token.repository.js +3 -0
  296. package/dist/security/time-trap.d.ts +7 -0
  297. package/dist/security/time-trap.d.ts.map +1 -0
  298. package/dist/security/time-trap.js +49 -0
  299. package/dist/seed-ddl-destructive.d.ts +40 -0
  300. package/dist/seed-ddl-destructive.d.ts.map +1 -0
  301. package/dist/seed-ddl-destructive.js +149 -0
  302. package/dist/seed-ddl.d.ts +40 -0
  303. package/dist/seed-ddl.d.ts.map +1 -0
  304. package/dist/seed-ddl.js +100 -0
  305. package/dist/seed-layout.d.ts +145 -0
  306. package/dist/seed-layout.d.ts.map +1 -0
  307. package/dist/seed-layout.js +192 -0
  308. package/dist/seed-layout.repository.d.ts +22 -0
  309. package/dist/seed-layout.repository.d.ts.map +1 -0
  310. package/dist/seed-layout.repository.js +3 -0
  311. package/dist/seed-registry.d.ts +71 -0
  312. package/dist/seed-registry.d.ts.map +1 -0
  313. package/dist/seed-registry.js +143 -0
  314. package/dist/seed-types-generator.d.ts +1 -1
  315. package/dist/seed-types-generator.d.ts.map +1 -1
  316. package/dist/seed-validation.d.ts +23 -0
  317. package/dist/seed-validation.d.ts.map +1 -0
  318. package/dist/seed-validation.js +224 -0
  319. package/dist/seed.repository.d.ts +37 -0
  320. package/dist/seed.repository.d.ts.map +1 -0
  321. package/dist/seed.repository.js +3 -0
  322. package/dist/seeds.d.ts +20 -0
  323. package/dist/seeds.d.ts.map +1 -0
  324. package/dist/seeds.js +21 -0
  325. package/dist/site-settings.repository.d.ts +16 -0
  326. package/dist/site-settings.repository.d.ts.map +1 -0
  327. package/dist/site-settings.repository.js +3 -0
  328. package/dist/slug-utils.d.ts +18 -0
  329. package/dist/slug-utils.d.ts.map +1 -0
  330. package/dist/slug-utils.js +32 -0
  331. package/dist/storage.d.ts +46 -0
  332. package/dist/storage.d.ts.map +1 -0
  333. package/dist/storage.js +3 -0
  334. package/dist/transfer/csv.d.ts +34 -0
  335. package/dist/transfer/csv.d.ts.map +1 -0
  336. package/dist/transfer/csv.js +135 -0
  337. package/dist/transfer/flat-seed.d.ts +13 -0
  338. package/dist/transfer/flat-seed.d.ts.map +1 -0
  339. package/dist/transfer/flat-seed.js +45 -0
  340. package/dist/transfer/index.d.ts +7 -0
  341. package/dist/transfer/index.d.ts.map +1 -0
  342. package/dist/transfer/index.js +8 -0
  343. package/dist/transfer/ndjson.d.ts +25 -0
  344. package/dist/transfer/ndjson.d.ts.map +1 -0
  345. package/dist/transfer/ndjson.js +62 -0
  346. package/dist/transfer/row-mapping.d.ts +36 -0
  347. package/dist/transfer/row-mapping.d.ts.map +1 -0
  348. package/dist/transfer/row-mapping.js +90 -0
  349. package/dist/transfer/transfer.constants.d.ts +20 -0
  350. package/dist/transfer/transfer.constants.d.ts.map +1 -0
  351. package/dist/transfer/transfer.constants.js +21 -0
  352. package/dist/transfer/transfer.types.d.ts +46 -0
  353. package/dist/transfer/transfer.types.d.ts.map +1 -0
  354. package/dist/transfer/transfer.types.js +6 -0
  355. package/dist/types.d.ts +243 -0
  356. package/dist/types.d.ts.map +1 -0
  357. package/dist/types.js +3 -0
  358. package/dist/validation.d.ts +50 -0
  359. package/dist/validation.d.ts.map +1 -0
  360. package/dist/validation.js +660 -0
  361. package/dist/webhook-crypto.d.ts +2 -0
  362. package/dist/webhook-crypto.d.ts.map +1 -1
  363. package/dist/webhook-crypto.js +2 -2
  364. package/dist/webhook-validation.d.ts +3 -0
  365. package/dist/webhook-validation.d.ts.map +1 -0
  366. package/dist/webhook-validation.js +6 -0
  367. package/dist/webhooks/webhook-crypto.d.ts +2 -0
  368. package/dist/webhooks/webhook-crypto.d.ts.map +1 -1
  369. package/dist/webhooks/webhook-crypto.js +2 -2
  370. package/package.json +23 -15
@@ -0,0 +1,149 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { junctionTableName, generateFtsTable, generateFtsTriggers } from './engine.js';
4
+ const BRANCH_SQL_TYPE = {
5
+ text: 'TEXT',
6
+ number: 'REAL',
7
+ boolean: 'INTEGER',
8
+ date: 'INTEGER',
9
+ json: 'TEXT',
10
+ richtext: 'TEXT',
11
+ file: 'TEXT',
12
+ tags: 'TEXT',
13
+ relation: 'TEXT',
14
+ repeater: 'TEXT',
15
+ };
16
+ /** Branch types that receive a B-tree index in generateIndexes (used by retype to drop/recreate). */
17
+ const INDEXABLE_TYPES = new Set(['text', 'number', 'date', 'boolean', 'relation']);
18
+ function searchBranches(seed) {
19
+ return seed.branches.filter(b => (b.type === 'text' || b.type === 'richtext') && b.policies?.search !== false);
20
+ }
21
+ function ftsTriggerDrops(slug) {
22
+ return [
23
+ `DROP TRIGGER IF EXISTS fts_${slug}_insert;`,
24
+ `DROP TRIGGER IF EXISTS fts_${slug}_update;`,
25
+ `DROP TRIGGER IF EXISTS fts_${slug}_delete;`,
26
+ `DROP TABLE IF EXISTS fts_${slug};`,
27
+ ];
28
+ }
29
+ /**
30
+ * Ordered DROP TABLE statements for a full hard-delete of a seed's tables.
31
+ * Order: junction drafts → junctions → FTS triggers → FTS table → drafts → main table.
32
+ * All statements use IF EXISTS (idempotent).
33
+ */
34
+ export function generateDropTable(seed) {
35
+ const stmts = [];
36
+ const { slug } = seed;
37
+ for (const branch of seed.branches) {
38
+ if (branch.type !== 'relation' || branch.multiple !== true)
39
+ continue;
40
+ const jt = junctionTableName(slug, branch.alias);
41
+ stmts.push(`DROP TABLE IF EXISTS ${jt}_drafts;`);
42
+ stmts.push(`DROP TABLE IF EXISTS ${jt};`);
43
+ }
44
+ stmts.push(...ftsTriggerDrops(slug));
45
+ stmts.push(`DROP TABLE IF EXISTS content_${slug}_drafts;`);
46
+ stmts.push(`DROP TABLE IF EXISTS content_${slug};`);
47
+ return stmts;
48
+ }
49
+ /**
50
+ * DROP COLUMN statements for a single branch (by alias).
51
+ * For multi-relation branches: drops the junction tables (no column on parent).
52
+ * For regular branches: drops from main table + drafts table (if allowDrafts).
53
+ * Does NOT include FTS handling — callers must prepend ftsTriggerDrops() and
54
+ * append planFtsRebuild() when the branch is searchable.
55
+ */
56
+ export function generateDropColumn(seed, alias) {
57
+ const stmts = [];
58
+ const { slug } = seed;
59
+ const branch = seed.branches.find(b => b.alias === alias);
60
+ if (!branch)
61
+ throw new Error(`Branch '${alias}' not found in seed '${slug}'`);
62
+ if (branch.type === 'relation' && branch.multiple === true) {
63
+ const jt = junctionTableName(slug, alias);
64
+ if (seed.allowDrafts)
65
+ stmts.push(`DROP TABLE IF EXISTS ${jt}_drafts;`);
66
+ stmts.push(`DROP TABLE IF EXISTS ${jt};`);
67
+ }
68
+ else {
69
+ stmts.push(`ALTER TABLE content_${slug} DROP COLUMN ${alias};`);
70
+ if (seed.allowDrafts)
71
+ stmts.push(`ALTER TABLE content_${slug}_drafts DROP COLUMN ${alias};`);
72
+ }
73
+ return stmts;
74
+ }
75
+ /**
76
+ * RENAME COLUMN statements for a branch alias change.
77
+ * Updates main table + drafts (if allowDrafts).
78
+ * Does NOT handle FTS — callers must rebuild FTS separately (SQLite auto-updates
79
+ * trigger bodies on RENAME but the FTS virtual-table columns keep the old name).
80
+ */
81
+ export function generateRenameColumn(seed, fromAlias, toAlias) {
82
+ const stmts = [];
83
+ const { slug } = seed;
84
+ stmts.push(`ALTER TABLE content_${slug} RENAME COLUMN ${fromAlias} TO ${toAlias};`);
85
+ if (seed.allowDrafts) {
86
+ stmts.push(`ALTER TABLE content_${slug}_drafts RENAME COLUMN ${fromAlias} TO ${toAlias};`);
87
+ }
88
+ return stmts;
89
+ }
90
+ /**
91
+ * Full FTS rebuild: drop triggers + FTS table, then recreate them, then backfill.
92
+ * Returns an empty array if the seed has no searchable branches.
93
+ * The drop steps use IF EXISTS so this is safe to call even if FTS was already dropped.
94
+ */
95
+ export function planFtsRebuild(seed) {
96
+ const rtBranches = searchBranches(seed);
97
+ if (rtBranches.length === 0)
98
+ return [];
99
+ const { slug } = seed;
100
+ const stmts = [...ftsTriggerDrops(slug)];
101
+ const ftsTable = generateFtsTable(seed);
102
+ if (ftsTable)
103
+ stmts.push(ftsTable);
104
+ stmts.push(...generateFtsTriggers(seed));
105
+ const cols = rtBranches.map(b => b.alias);
106
+ stmts.push(`INSERT INTO fts_${slug}(entry_id, ${cols.join(', ')}) SELECT id, ${cols.join(', ')} FROM content_${slug};`);
107
+ return stmts;
108
+ }
109
+ /**
110
+ * Generates statements to change a branch's SQL storage type using the
111
+ * add-new-column / copy / drop-old / rename approach.
112
+ * Includes FTS rebuild statements when the branch is (or becomes) searchable.
113
+ * `updatedSeed` must already reflect the new type on the renamed branch.
114
+ */
115
+ export function planRetypeColumn(seed, alias, newType, updatedSeed) {
116
+ const branch = seed.branches.find(b => b.alias === alias);
117
+ if (!branch)
118
+ throw new Error(`Branch '${alias}' not found in seed '${seed.slug}'`);
119
+ const { slug } = seed;
120
+ const newSqlType = BRANCH_SQL_TYPE[newType];
121
+ const tmpAlias = `${alias}_migrate`;
122
+ const stmts = [];
123
+ // Always drop FTS first to avoid "column referenced in trigger" errors on DROP COLUMN.
124
+ stmts.push(...ftsTriggerDrops(slug));
125
+ // Drop the branch index if it exists on the old column (required before DROP COLUMN).
126
+ if (INDEXABLE_TYPES.has(branch.type) && branch.policies?.filter !== false) {
127
+ stmts.push(`DROP INDEX IF EXISTS idx_${slug}_${alias};`);
128
+ }
129
+ // Main table: add migration column, copy data, drop old, rename.
130
+ stmts.push(`ALTER TABLE content_${slug} ADD COLUMN ${tmpAlias} ${newSqlType};`);
131
+ stmts.push(`UPDATE content_${slug} SET ${tmpAlias} = CAST(${alias} AS ${newSqlType});`);
132
+ stmts.push(`ALTER TABLE content_${slug} DROP COLUMN ${alias};`);
133
+ stmts.push(`ALTER TABLE content_${slug} RENAME COLUMN ${tmpAlias} TO ${alias};`);
134
+ // Recreate index for the renamed column if the new type warrants it.
135
+ if (INDEXABLE_TYPES.has(newType) && branch.policies?.filter !== false) {
136
+ stmts.push(`CREATE INDEX IF NOT EXISTS idx_${slug}_${alias} ON content_${slug}(${alias});`);
137
+ }
138
+ // Drafts table (no indexes to worry about).
139
+ if (seed.allowDrafts) {
140
+ stmts.push(`ALTER TABLE content_${slug}_drafts ADD COLUMN ${tmpAlias} ${newSqlType};`);
141
+ stmts.push(`UPDATE content_${slug}_drafts SET ${tmpAlias} = CAST(${alias} AS ${newSqlType});`);
142
+ stmts.push(`ALTER TABLE content_${slug}_drafts DROP COLUMN ${alias};`);
143
+ stmts.push(`ALTER TABLE content_${slug}_drafts RENAME COLUMN ${tmpAlias} TO ${alias};`);
144
+ }
145
+ // Rebuild FTS if the seed has searchable branches in the updated definition.
146
+ const rebuild = planFtsRebuild(updatedSeed);
147
+ stmts.push(...rebuild);
148
+ return { statements: stmts, ftsRebuildNeeded: rebuild.length > 0 };
149
+ }
@@ -0,0 +1,40 @@
1
+ import type { Seed } from './types.js';
2
+ /**
3
+ * Full create-from-scratch statement set for a seed. Mirrors the CLI's buildStatements.
4
+ * Order: parent table → indexes → draft table → FTS table → FTS triggers →
5
+ * per multi-relation: junction table → junction indexes → junction draft table.
6
+ * Callers that create several seeds must order seeds with sortSeedsByDependencies
7
+ * first so relation FK targets exist.
8
+ */
9
+ export declare function planCreateSeed(seed: Seed): string[];
10
+ export interface ExtendPlan {
11
+ statements: string[];
12
+ ftsRebuildNeeded: boolean;
13
+ }
14
+ /**
15
+ * Additive extension: given the columns that already exist on content_{slug}
16
+ * (from PRAGMA table_info, passed in by the caller), return ONLY the statements
17
+ * needed to add new branches — ADD COLUMN + indexes, plus junction tables for
18
+ * new multi-relation branches. Never drops or renames.
19
+ *
20
+ * FTS: SQLite cannot ALTER an fts5 table's columns. If a new text/richtext
21
+ * searchable branch was added, ftsRebuildNeeded=true signals the caller
22
+ * (sprint 03) to handle it — no DROP is emitted.
23
+ */
24
+ export declare function planExtendSeed(seed: Seed, existingColumns: Set<string>): ExtendPlan;
25
+ /**
26
+ * Destructive FTS rebuild (sprint 06). SQLite cannot ALTER an fts5 table's
27
+ * columns, so when a searchable branch is added, renamed, retyped, or dropped
28
+ * the only correct fix is to drop and recreate the `fts_{slug}` virtual table
29
+ * (and its insert/update/delete triggers), then backfill from `content_{slug}`.
30
+ *
31
+ * Returns an empty array when the seed has no searchable branches — in that
32
+ * case the caller should instead drop any leftover FTS table via
33
+ * generateDropTable's FTS statements. The returned statements are destructive
34
+ * and must run through ISchemaMutator.execDestructive, never execDdl.
35
+ *
36
+ * Order: drop triggers → drop fts table → recreate table → recreate triggers →
37
+ * backfill existing rows.
38
+ */
39
+ export declare function planFtsRebuild(seed: Seed): string[];
40
+ //# sourceMappingURL=seed-ddl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-ddl.d.ts","sourceRoot":"","sources":["../src/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,100 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { generateCreateTable, generateDraftTable, generateIndexes, generateFtsTable, generateFtsTriggers, generateAddColumn, generateJunctionTable, generateJunctionIndexes, generateJunctionDraftTable, indexableSearchBranches, } from './engine.js';
4
+ /**
5
+ * Full create-from-scratch statement set for a seed. Mirrors the CLI's buildStatements.
6
+ * Order: parent table → indexes → draft table → FTS table → FTS triggers →
7
+ * per multi-relation: junction table → junction indexes → junction draft table.
8
+ * Callers that create several seeds must order seeds with sortSeedsByDependencies
9
+ * first so relation FK targets exist.
10
+ */
11
+ export function planCreateSeed(seed) {
12
+ const stmts = [generateCreateTable(seed), ...generateIndexes(seed)];
13
+ const draft = generateDraftTable(seed);
14
+ if (draft)
15
+ stmts.push(draft);
16
+ const fts = generateFtsTable(seed);
17
+ if (fts)
18
+ stmts.push(fts, ...generateFtsTriggers(seed));
19
+ for (const branch of seed.branches) {
20
+ if (branch.type !== 'relation' || branch.multiple !== true)
21
+ continue;
22
+ stmts.push(generateJunctionTable(seed, branch), ...generateJunctionIndexes(seed, branch));
23
+ const dj = generateJunctionDraftTable(seed, branch);
24
+ if (dj)
25
+ stmts.push(dj);
26
+ }
27
+ return stmts;
28
+ }
29
+ /**
30
+ * Additive extension: given the columns that already exist on content_{slug}
31
+ * (from PRAGMA table_info, passed in by the caller), return ONLY the statements
32
+ * needed to add new branches — ADD COLUMN + indexes, plus junction tables for
33
+ * new multi-relation branches. Never drops or renames.
34
+ *
35
+ * FTS: SQLite cannot ALTER an fts5 table's columns. If a new text/richtext
36
+ * searchable branch was added, ftsRebuildNeeded=true signals the caller
37
+ * (sprint 03) to handle it — no DROP is emitted.
38
+ */
39
+ export function planExtendSeed(seed, existingColumns) {
40
+ const statements = [];
41
+ let ftsRebuildNeeded = false;
42
+ for (const branch of seed.branches) {
43
+ if (branch.type === 'relation' && branch.multiple === true) {
44
+ // junction table is CREATE IF NOT EXISTS — safe to re-emit
45
+ statements.push(generateJunctionTable(seed, branch), ...generateJunctionIndexes(seed, branch));
46
+ const dj = generateJunctionDraftTable(seed, branch);
47
+ if (dj)
48
+ statements.push(dj);
49
+ continue;
50
+ }
51
+ if (existingColumns.has(branch.alias))
52
+ continue;
53
+ statements.push(generateAddColumn(seed, branch));
54
+ if ((branch.type === 'text' || branch.type === 'richtext') && branch.policies?.search !== false) {
55
+ ftsRebuildNeeded = true;
56
+ }
57
+ }
58
+ // CREATE INDEX IF NOT EXISTS — idempotent, safe to re-run
59
+ statements.push(...generateIndexes(seed));
60
+ return { statements, ftsRebuildNeeded };
61
+ }
62
+ /**
63
+ * Destructive FTS rebuild (sprint 06). SQLite cannot ALTER an fts5 table's
64
+ * columns, so when a searchable branch is added, renamed, retyped, or dropped
65
+ * the only correct fix is to drop and recreate the `fts_{slug}` virtual table
66
+ * (and its insert/update/delete triggers), then backfill from `content_{slug}`.
67
+ *
68
+ * Returns an empty array when the seed has no searchable branches — in that
69
+ * case the caller should instead drop any leftover FTS table via
70
+ * generateDropTable's FTS statements. The returned statements are destructive
71
+ * and must run through ISchemaMutator.execDestructive, never execDdl.
72
+ *
73
+ * Order: drop triggers → drop fts table → recreate table → recreate triggers →
74
+ * backfill existing rows.
75
+ */
76
+ export function planFtsRebuild(seed) {
77
+ const branches = indexableSearchBranches(seed);
78
+ if (branches.length === 0)
79
+ return [];
80
+ const slug = seed.slug;
81
+ const ftsTable = `fts_${slug}`;
82
+ const contentTable = `content_${slug}`;
83
+ const cols = branches.map(b => b.alias);
84
+ const ftsTableDdl = generateFtsTable(seed);
85
+ const triggers = generateFtsTriggers(seed);
86
+ // generateFtsTable/Triggers return non-null here (branches.length > 0 checked).
87
+ if (!ftsTableDdl)
88
+ return [];
89
+ const ftsColList = ['entry_id', ...cols].join(', ');
90
+ const selectList = ['id', ...cols].join(', ');
91
+ return [
92
+ `DROP TRIGGER IF EXISTS fts_${slug}_insert;`,
93
+ `DROP TRIGGER IF EXISTS fts_${slug}_update;`,
94
+ `DROP TRIGGER IF EXISTS fts_${slug}_delete;`,
95
+ `DROP TABLE IF EXISTS ${ftsTable};`,
96
+ ftsTableDdl,
97
+ ...triggers,
98
+ `INSERT INTO ${ftsTable}(${ftsColList}) SELECT ${selectList} FROM ${contentTable};`,
99
+ ];
100
+ }
@@ -0,0 +1,145 @@
1
+ import { z } from 'zod';
2
+ import type { Seed, Branch } from './types.js';
3
+ export declare const kanbanViewConfigSchema: z.ZodObject<{
4
+ axisBranchId: z.ZodNullable<z.ZodString>;
5
+ sort: z.ZodNullable<z.ZodObject<{
6
+ branchId: z.ZodString;
7
+ dir: z.ZodEnum<{
8
+ ASC: "ASC";
9
+ DESC: "DESC";
10
+ }>;
11
+ }, z.core.$strip>>;
12
+ hiddenColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
13
+ collapsedColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
14
+ }, z.core.$strip>;
15
+ export type KanbanViewConfig = z.infer<typeof kanbanViewConfigSchema>;
16
+ export declare const seedViewConfigSchema: z.ZodObject<{
17
+ kanban: z.ZodOptional<z.ZodObject<{
18
+ axisBranchId: z.ZodNullable<z.ZodString>;
19
+ sort: z.ZodNullable<z.ZodObject<{
20
+ branchId: z.ZodString;
21
+ dir: z.ZodEnum<{
22
+ ASC: "ASC";
23
+ DESC: "DESC";
24
+ }>;
25
+ }, z.core.$strip>>;
26
+ hiddenColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
27
+ collapsedColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
28
+ }, z.core.$strip>>;
29
+ }, z.core.$loose>;
30
+ export type SeedViewConfig = z.infer<typeof seedViewConfigSchema>;
31
+ /** Reference to a Seed branch by its stable id (e.g. 'br_03').
32
+ * Aliases can be renamed — ids cannot. When the id does not match any
33
+ * branch on the Seed at read time, the field is silently stripped (auto-cleanup). */
34
+ export interface LayoutField {
35
+ branchId: string;
36
+ }
37
+ export interface LayoutColumn {
38
+ id: string;
39
+ fields: LayoutField[];
40
+ }
41
+ export interface LayoutSection {
42
+ id: string;
43
+ label?: string;
44
+ hideLabel?: boolean;
45
+ hideBorder?: boolean;
46
+ collapsible?: boolean;
47
+ /** Max 4 columns. Enforced by validator. */
48
+ columns: LayoutColumn[];
49
+ }
50
+ export interface LayoutTab {
51
+ id: string;
52
+ label: string;
53
+ sections: LayoutSection[];
54
+ }
55
+ export interface FormLayout {
56
+ /** Format version — bump when introducing breaking changes. */
57
+ version: 1;
58
+ tabs: LayoutTab[];
59
+ }
60
+ /** Branch types that must occupy a section alone, full width. */
61
+ export declare const FULL_WIDTH_BRANCH_TYPES: Set<import("./types.js").BranchType>;
62
+ /** Returns true when a branch is a gallery (file + multiple/asset-list). */
63
+ export declare function isGalleryBranch(branch: Branch): boolean;
64
+ /** Branch types currently unsupported in the Layout Builder. */
65
+ export declare const UNSUPPORTED_BRANCH_TYPES: Set<import("./types.js").BranchType>;
66
+ /** Aliases that are NEVER included in the editor form (handled by other UI). */
67
+ export declare const SYSTEM_ALIASES: Set<string>;
68
+ /** True when the branch is eligible to appear in the layout at all. */
69
+ export declare function isLayoutableBranch(branch: Branch): boolean;
70
+ /** True when the branch must occupy a dedicated full-width section. */
71
+ export declare function isFullWidthBranch(branch: Branch): boolean;
72
+ /** Branches considered SEO fields. Matches the existing rule in entry-editor.tsx. */
73
+ export declare function isSeoBranch(branch: Branch): boolean;
74
+ export declare const layoutFieldSchema: z.ZodObject<{
75
+ branchId: z.ZodString;
76
+ }, z.core.$strip>;
77
+ export declare const layoutColumnSchema: z.ZodObject<{
78
+ id: z.ZodString;
79
+ fields: z.ZodArray<z.ZodObject<{
80
+ branchId: z.ZodString;
81
+ }, z.core.$strip>>;
82
+ }, z.core.$strip>;
83
+ export declare const layoutSectionSchema: z.ZodObject<{
84
+ id: z.ZodString;
85
+ label: z.ZodOptional<z.ZodString>;
86
+ hideLabel: z.ZodOptional<z.ZodBoolean>;
87
+ hideBorder: z.ZodOptional<z.ZodBoolean>;
88
+ collapsible: z.ZodOptional<z.ZodBoolean>;
89
+ columns: z.ZodArray<z.ZodObject<{
90
+ id: z.ZodString;
91
+ fields: z.ZodArray<z.ZodObject<{
92
+ branchId: z.ZodString;
93
+ }, z.core.$strip>>;
94
+ }, z.core.$strip>>;
95
+ }, z.core.$strip>;
96
+ export declare const layoutTabSchema: z.ZodObject<{
97
+ id: z.ZodString;
98
+ label: z.ZodString;
99
+ sections: z.ZodArray<z.ZodObject<{
100
+ id: z.ZodString;
101
+ label: z.ZodOptional<z.ZodString>;
102
+ hideLabel: z.ZodOptional<z.ZodBoolean>;
103
+ hideBorder: z.ZodOptional<z.ZodBoolean>;
104
+ collapsible: z.ZodOptional<z.ZodBoolean>;
105
+ columns: z.ZodArray<z.ZodObject<{
106
+ id: z.ZodString;
107
+ fields: z.ZodArray<z.ZodObject<{
108
+ branchId: z.ZodString;
109
+ }, z.core.$strip>>;
110
+ }, z.core.$strip>>;
111
+ }, z.core.$strip>>;
112
+ }, z.core.$strip>;
113
+ export declare const formLayoutSchema: z.ZodObject<{
114
+ version: z.ZodLiteral<1>;
115
+ tabs: z.ZodArray<z.ZodObject<{
116
+ id: z.ZodString;
117
+ label: z.ZodString;
118
+ sections: z.ZodArray<z.ZodObject<{
119
+ id: z.ZodString;
120
+ label: z.ZodOptional<z.ZodString>;
121
+ hideLabel: z.ZodOptional<z.ZodBoolean>;
122
+ hideBorder: z.ZodOptional<z.ZodBoolean>;
123
+ collapsible: z.ZodOptional<z.ZodBoolean>;
124
+ columns: z.ZodArray<z.ZodObject<{
125
+ id: z.ZodString;
126
+ fields: z.ZodArray<z.ZodObject<{
127
+ branchId: z.ZodString;
128
+ }, z.core.$strip>>;
129
+ }, z.core.$strip>>;
130
+ }, z.core.$strip>>;
131
+ }, z.core.$strip>>;
132
+ }, z.core.$strip>;
133
+ export declare function generateDefaultLayout(seed: Seed, opts?: {
134
+ newId: () => string;
135
+ }): FormLayout;
136
+ export type ValidateLayoutResult = {
137
+ ok: true;
138
+ cleaned: FormLayout;
139
+ } | {
140
+ ok: false;
141
+ errors: string[];
142
+ cleaned: FormLayout;
143
+ };
144
+ export declare function validateLayoutAgainstSeed(layout: FormLayout, seed: Seed): ValidateLayoutResult;
145
+ //# sourceMappingURL=seed-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-layout.d.ts","sourceRoot":"","sources":["../src/seed-layout.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAO9C,eAAO,MAAM,sBAAsB;;;;;;;;;;;iBAKjC,CAAA;AACF,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA;AAErE,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;iBAEjB,CAAA;AAChB,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAA;AAMjE;;sFAEsF;AACtF,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,WAAW,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,4CAA4C;IAC5C,OAAO,EAAE,YAAY,EAAE,CAAA;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,aAAa,EAAE,CAAA;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,+DAA+D;IAC/D,OAAO,EAAE,CAAC,CAAA;IACV,IAAI,EAAE,SAAS,EAAE,CAAA;CAClB;AAMD,iEAAiE;AACjE,eAAO,MAAM,uBAAuB,sCAAwC,CAAA;AAE5E,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGvD;AAED,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,sCAAoC,CAAA;AAEzE,gFAAgF;AAChF,eAAO,MAAM,cAAc,aAEzB,CAAA;AAEF,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAK1D;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAMD,eAAO,MAAM,iBAAiB;;iBAAgE,CAAA;AAC9F,eAAO,MAAM,kBAAkB;;;;;iBAG7B,CAAA;AACF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;iBAO9B,CAAA;AACF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;iBAI1B,CAAA;AACF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;iBAG3B,CAAA;AAiDF,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,MAAM,CAAA;CAAE,GAC7B,UAAU,CAmBZ;AAMD,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,GACjC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAA;AAExD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,IAAI,GACT,oBAAoB,CAiEtB"}
@@ -0,0 +1,192 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { z } from 'zod';
4
+ import { findBranchById } from './seed-registry.js';
5
+ // ---------------------------------------------------------------------------
6
+ // View config — per-seed, per-view dashboard preferences (KB-S02)
7
+ // ---------------------------------------------------------------------------
8
+ export const kanbanViewConfigSchema = z.object({
9
+ axisBranchId: z.string().nullable(),
10
+ sort: z.object({ branchId: z.string(), dir: z.enum(['ASC', 'DESC']) }).nullable(),
11
+ hiddenColumnValues: z.array(z.string()).optional(),
12
+ collapsedColumnValues: z.array(z.string()).optional(),
13
+ });
14
+ export const seedViewConfigSchema = z.object({
15
+ kanban: kanbanViewConfigSchema.optional(),
16
+ }).passthrough();
17
+ // ---------------------------------------------------------------------------
18
+ // Branch-type rules used by both the default generator and the validator.
19
+ // ---------------------------------------------------------------------------
20
+ /** Branch types that must occupy a section alone, full width. */
21
+ export const FULL_WIDTH_BRANCH_TYPES = new Set(['richtext']);
22
+ /** Returns true when a branch is a gallery (file + multiple/asset-list). */
23
+ export function isGalleryBranch(branch) {
24
+ return branch.type === 'file'
25
+ && (branch.multiple === true || branch.format === 'asset-list');
26
+ }
27
+ /** Branch types currently unsupported in the Layout Builder. */
28
+ export const UNSUPPORTED_BRANCH_TYPES = new Set(['json']);
29
+ /** Aliases that are NEVER included in the editor form (handled by other UI). */
30
+ export const SYSTEM_ALIASES = new Set([
31
+ 'id', 'slug', 'status', 'created_at', 'updated_at',
32
+ ]);
33
+ /** True when the branch is eligible to appear in the layout at all. */
34
+ export function isLayoutableBranch(branch) {
35
+ if (SYSTEM_ALIASES.has(branch.alias))
36
+ return false;
37
+ if (branch.policies?.visibility === 'hidden')
38
+ return false;
39
+ if (UNSUPPORTED_BRANCH_TYPES.has(branch.type))
40
+ return false;
41
+ return true;
42
+ }
43
+ /** True when the branch must occupy a dedicated full-width section. */
44
+ export function isFullWidthBranch(branch) {
45
+ return FULL_WIDTH_BRANCH_TYPES.has(branch.type) || isGalleryBranch(branch);
46
+ }
47
+ /** Branches considered SEO fields. Matches the existing rule in entry-editor.tsx. */
48
+ export function isSeoBranch(branch) {
49
+ return branch.alias.startsWith('meta');
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // Zod schema for shape validation
53
+ // ---------------------------------------------------------------------------
54
+ export const layoutFieldSchema = z.object({ branchId: z.string().regex(/^br_[A-Za-z0-9]+$/) });
55
+ export const layoutColumnSchema = z.object({
56
+ id: z.string().min(1),
57
+ fields: z.array(layoutFieldSchema),
58
+ });
59
+ export const layoutSectionSchema = z.object({
60
+ id: z.string().min(1),
61
+ label: z.string().optional(),
62
+ hideLabel: z.boolean().optional(),
63
+ hideBorder: z.boolean().optional(),
64
+ collapsible: z.boolean().optional(),
65
+ columns: z.array(layoutColumnSchema).min(1).max(4),
66
+ });
67
+ export const layoutTabSchema = z.object({
68
+ id: z.string().min(1),
69
+ label: z.string().min(1).max(60),
70
+ sections: z.array(layoutSectionSchema).min(1),
71
+ });
72
+ export const formLayoutSchema = z.object({
73
+ version: z.literal(1),
74
+ tabs: z.array(layoutTabSchema).min(1).max(8),
75
+ });
76
+ // ---------------------------------------------------------------------------
77
+ // Default layout generator
78
+ // ---------------------------------------------------------------------------
79
+ const DEFAULT_COLUMNS_PER_SECTION = 3;
80
+ const defaultIdFactory = () => crypto.randomUUID();
81
+ function buildSectionsForBranches(branches, newId) {
82
+ const sections = [];
83
+ let currentColumns = [];
84
+ function flushCompact() {
85
+ if (currentColumns.length === 0)
86
+ return;
87
+ sections.push({ id: newId(), columns: currentColumns });
88
+ currentColumns = [];
89
+ }
90
+ for (const branch of branches) {
91
+ if (isFullWidthBranch(branch)) {
92
+ flushCompact();
93
+ sections.push({
94
+ id: newId(),
95
+ label: branch.label,
96
+ hideLabel: true,
97
+ columns: [{ id: newId(), fields: [{ branchId: branch.id }] }],
98
+ });
99
+ }
100
+ else {
101
+ currentColumns.push({ id: newId(), fields: [{ branchId: branch.id }] });
102
+ if (currentColumns.length >= DEFAULT_COLUMNS_PER_SECTION) {
103
+ flushCompact();
104
+ }
105
+ }
106
+ }
107
+ flushCompact();
108
+ if (sections.length === 0) {
109
+ sections.push({ id: newId(), columns: [{ id: newId(), fields: [] }] });
110
+ }
111
+ return sections;
112
+ }
113
+ export function generateDefaultLayout(seed, opts) {
114
+ const newId = opts?.newId ?? defaultIdFactory;
115
+ const layoutable = seed.branches.filter(isLayoutableBranch);
116
+ const seo = layoutable.filter(isSeoBranch);
117
+ const main = layoutable.filter((b) => !isSeoBranch(b));
118
+ const dataTab = {
119
+ id: newId(),
120
+ label: 'Data',
121
+ sections: buildSectionsForBranches(main, newId),
122
+ };
123
+ const seoTab = {
124
+ id: newId(),
125
+ label: 'SEO',
126
+ sections: buildSectionsForBranches(seo, newId),
127
+ };
128
+ return { version: 1, tabs: [dataTab, seoTab] };
129
+ }
130
+ export function validateLayoutAgainstSeed(layout, seed) {
131
+ // Gracefully handle undefined/null layout, or missing tabs (e.g. from {} placeholder)
132
+ if (!layout || !Array.isArray(layout.tabs)) {
133
+ const defaultLayout = generateDefaultLayout(seed);
134
+ return {
135
+ ok: false,
136
+ errors: ['Invalid layout structure: missing tabs'],
137
+ cleaned: defaultLayout,
138
+ };
139
+ }
140
+ const errors = [];
141
+ const seenBranchIds = new Set();
142
+ const cleanedTabs = (layout.tabs ?? []).map((tab) => ({
143
+ ...tab,
144
+ sections: (tab?.sections ?? []).map((section) => ({
145
+ ...section,
146
+ columns: (section?.columns ?? []).map((col) => ({
147
+ ...col,
148
+ fields: (col?.fields ?? []).filter((f) => {
149
+ if (!f || typeof f !== 'object' || !('branchId' in f))
150
+ return false;
151
+ const branch = findBranchById(seed, f.branchId);
152
+ if (!branch)
153
+ return false;
154
+ if (UNSUPPORTED_BRANCH_TYPES.has(branch.type) || SYSTEM_ALIASES.has(branch.alias))
155
+ return false;
156
+ return true;
157
+ }),
158
+ })),
159
+ })),
160
+ }));
161
+ // Duplicate and full-width constraint checks on cleaned layout
162
+ for (const tab of cleanedTabs) {
163
+ for (const section of tab.sections ?? []) {
164
+ const fieldsInSection = [];
165
+ for (const col of section.columns ?? []) {
166
+ for (const f of col.fields ?? []) {
167
+ const branch = findBranchById(seed, f.branchId);
168
+ if (!branch)
169
+ continue;
170
+ if (seenBranchIds.has(f.branchId)) {
171
+ errors.push(`Branch '${branch.alias}' (id=${f.branchId}) appears more than once in the layout.`);
172
+ }
173
+ else {
174
+ seenBranchIds.add(f.branchId);
175
+ fieldsInSection.push(branch);
176
+ }
177
+ }
178
+ }
179
+ // Full-width branches must occupy a dedicated section
180
+ const fullWidthInSection = fieldsInSection.filter(isFullWidthBranch);
181
+ if (fullWidthInSection.length > 0 && fieldsInSection.length > fullWidthInSection.length) {
182
+ for (const fw of fullWidthInSection) {
183
+ errors.push(`Branch '${fw.alias}' (type=${fw.type}) must occupy a dedicated section and cannot share it with other fields.`);
184
+ }
185
+ }
186
+ }
187
+ }
188
+ const cleaned = { ...layout, tabs: cleanedTabs };
189
+ if (errors.length > 0)
190
+ return { ok: false, errors, cleaned };
191
+ return { ok: true, cleaned };
192
+ }
@@ -0,0 +1,22 @@
1
+ import type { FormLayout, SeedViewConfig } from './seed-layout.js';
2
+ export interface SeedLayoutRecord {
3
+ slug: string;
4
+ layout: FormLayout;
5
+ updatedAt: number;
6
+ updatedBy: string;
7
+ }
8
+ export interface ISeedLayoutRepository {
9
+ /** Return the stored layout for a seed, or null if none was ever saved. */
10
+ get(slug: string): Promise<SeedLayoutRecord | null>;
11
+ /** Return all stored layouts, keyed by slug — used by GET /api/schema to enrich. */
12
+ getAllAsMap(): Promise<Map<string, FormLayout>>;
13
+ /** Upsert. `updatedBy` is the writer's user id. */
14
+ upsert(slug: string, layout: FormLayout, updatedBy: string): Promise<void>;
15
+ /** Remove the stored row — used by the "Reset" action. */
16
+ remove(slug: string): Promise<void>;
17
+ /** Return the per-view config blob for a seed, or null if none was stored. */
18
+ getViewConfig(slug: string): Promise<SeedViewConfig | null>;
19
+ /** Upsert the per-view config for a seed. */
20
+ setViewConfig(slug: string, config: SeedViewConfig, updatedBy: string): Promise<void>;
21
+ }
22
+ //# sourceMappingURL=seed-layout.repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-layout.repository.d.ts","sourceRoot":"","sources":["../src/seed-layout.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAElE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IACnD,oFAAoF;IACpF,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;IAC/C,mDAAmD;IACnD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1E,0DAA0D;IAC1D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,8EAA8E;IAC9E,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAA;IAC3D,6CAA6C;IAC7C,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACtF"}
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ export {};