@beechcms/core 0.8.0 → 0.9.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 (389) hide show
  1. package/dist/auth/account-email.d.ts +3 -0
  2. package/dist/auth/account-email.d.ts.map +1 -0
  3. package/dist/auth/account-email.js +9 -0
  4. package/dist/auth/password-reset-token.repository.d.ts +13 -2
  5. package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
  6. package/dist/auth/user.repository.d.ts +69 -4
  7. package/dist/auth/user.repository.d.ts.map +1 -1
  8. package/dist/automations-grammar-words.d.ts +2 -0
  9. package/dist/automations-grammar-words.d.ts.map +1 -0
  10. package/dist/automations-grammar-words.js +8 -0
  11. package/dist/automations.repository.interface.d.ts +19 -0
  12. package/dist/automations.repository.interface.d.ts.map +1 -0
  13. package/dist/automations.repository.interface.js +3 -0
  14. package/dist/automations.runner.interface.d.ts +10 -0
  15. package/dist/automations.runner.interface.d.ts.map +1 -0
  16. package/dist/automations.runner.interface.js +3 -0
  17. package/dist/automations.runner.stub.d.ts +5 -0
  18. package/dist/automations.runner.stub.d.ts.map +1 -0
  19. package/dist/automations.runner.stub.js +7 -0
  20. package/dist/automations.types.d.ts +75 -0
  21. package/dist/automations.types.d.ts.map +1 -0
  22. package/dist/automations.types.js +3 -0
  23. package/dist/clock.d.ts +25 -0
  24. package/dist/clock.d.ts.map +1 -0
  25. package/dist/clock.js +11 -0
  26. package/dist/common/canonical-json.d.ts +31 -0
  27. package/dist/common/canonical-json.d.ts.map +1 -0
  28. package/dist/common/canonical-json.js +91 -0
  29. package/dist/content/content-scan.repository.d.ts +1 -1
  30. package/dist/content/content.repository.d.ts +154 -3
  31. package/dist/content/content.repository.d.ts.map +1 -1
  32. package/dist/content/content.repository.js +62 -0
  33. package/dist/content/deletion-ledger.d.ts +39 -0
  34. package/dist/content/deletion-ledger.d.ts.map +1 -0
  35. package/dist/content/deletion-ledger.js +3 -0
  36. package/dist/content/seed-media-purge.repository.d.ts +34 -0
  37. package/dist/content/seed-media-purge.repository.d.ts.map +1 -0
  38. package/dist/content/seed-media-purge.repository.js +3 -0
  39. package/dist/content/seed.repository.d.ts +14 -0
  40. package/dist/content/seed.repository.d.ts.map +1 -1
  41. package/dist/content-scan.repository.d.ts +9 -0
  42. package/dist/content-scan.repository.d.ts.map +1 -0
  43. package/dist/content-scan.repository.js +3 -0
  44. package/dist/content.repository.d.ts +247 -0
  45. package/dist/content.repository.d.ts.map +1 -0
  46. package/dist/content.repository.js +59 -0
  47. package/dist/dashboard-layout/content-view.d.ts +757 -0
  48. package/dist/dashboard-layout/content-view.d.ts.map +1 -0
  49. package/dist/dashboard-layout/content-view.js +285 -0
  50. package/dist/dashboard-layout/content-view.repository.d.ts +39 -0
  51. package/dist/dashboard-layout/content-view.repository.d.ts.map +1 -0
  52. package/dist/dashboard-layout/content-view.repository.js +3 -0
  53. package/dist/dashboard-layout/dashboard-layout.d.ts +5 -0
  54. package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
  55. package/dist/dashboard-layout/dashboard-layout.js +17 -9
  56. package/dist/dashboard-layout/kanban/kanban.d.ts +32 -18
  57. package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -1
  58. package/dist/dashboard-layout/kanban/kanban.js +45 -21
  59. package/dist/dashboard-layout/seed-layout.d.ts +22 -31
  60. package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
  61. package/dist/dashboard-layout/seed-layout.js +93 -18
  62. package/dist/dashboard-layout/seed-layout.repository.d.ts +1 -5
  63. package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -1
  64. package/dist/dashboard-layout.d.ts +154 -0
  65. package/dist/dashboard-layout.d.ts.map +1 -0
  66. package/dist/dashboard-layout.js +186 -0
  67. package/dist/dashboard-layout.repository.d.ts +18 -0
  68. package/dist/dashboard-layout.repository.d.ts.map +1 -0
  69. package/dist/dashboard-layout.repository.js +3 -0
  70. package/dist/dashboard-permissions.d.ts +3 -0
  71. package/dist/dashboard-permissions.d.ts.map +1 -0
  72. package/dist/dashboard-permissions.js +11 -0
  73. package/dist/dashboard-scopes.d.ts +12 -0
  74. package/dist/dashboard-scopes.d.ts.map +1 -0
  75. package/dist/dashboard-scopes.js +30 -0
  76. package/dist/define-seed.d.ts +3 -0
  77. package/dist/define-seed.d.ts.map +1 -0
  78. package/dist/define-seed.js +5 -0
  79. package/dist/demo-data.repository.d.ts +5 -0
  80. package/dist/demo-data.repository.d.ts.map +1 -0
  81. package/dist/demo-data.repository.js +3 -0
  82. package/dist/email.provider.d.ts +32 -0
  83. package/dist/email.provider.d.ts.map +1 -0
  84. package/dist/email.provider.js +4 -0
  85. package/dist/engine/ddl/ddl.d.ts +247 -0
  86. package/dist/engine/ddl/ddl.d.ts.map +1 -0
  87. package/dist/engine/ddl/ddl.js +653 -0
  88. package/dist/engine/ddl/schema-mutator.d.ts +29 -0
  89. package/dist/engine/ddl/schema-mutator.d.ts.map +1 -0
  90. package/dist/engine/ddl/schema-mutator.js +3 -0
  91. package/dist/engine/ddl/seed-ddl-destructive.d.ts +40 -0
  92. package/dist/engine/ddl/seed-ddl-destructive.d.ts.map +1 -0
  93. package/dist/engine/ddl/seed-ddl-destructive.js +149 -0
  94. package/dist/engine/ddl/seed-ddl.d.ts +43 -0
  95. package/dist/engine/ddl/seed-ddl.d.ts.map +1 -0
  96. package/dist/engine/ddl/seed-ddl.js +115 -0
  97. package/dist/engine/ddl/seed-purge-guards.d.ts +7 -0
  98. package/dist/engine/ddl/seed-purge-guards.d.ts.map +1 -0
  99. package/dist/engine/ddl/seed-purge-guards.js +19 -0
  100. package/dist/engine/engine.d.ts +4 -3
  101. package/dist/engine/engine.d.ts.map +1 -1
  102. package/dist/engine/engine.js +4 -3
  103. package/dist/engine/introspection/introspection.d.ts +101 -0
  104. package/dist/engine/introspection/introspection.d.ts.map +1 -0
  105. package/dist/engine/introspection/introspection.js +127 -0
  106. package/dist/engine/introspection/relation-audit.d.ts +46 -0
  107. package/dist/engine/introspection/relation-audit.d.ts.map +1 -0
  108. package/dist/engine/introspection/relation-audit.js +103 -0
  109. package/dist/engine/introspection/schema-fingerprint.d.ts +74 -0
  110. package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -0
  111. package/dist/engine/introspection/schema-fingerprint.js +102 -0
  112. package/dist/engine/introspection.d.ts +101 -0
  113. package/dist/engine/introspection.d.ts.map +1 -0
  114. package/dist/engine/introspection.js +127 -0
  115. package/dist/engine/localization/localization.d.ts +127 -0
  116. package/dist/engine/localization/localization.d.ts.map +1 -0
  117. package/dist/engine/localization/localization.js +212 -0
  118. package/dist/engine/localization.d.ts +127 -0
  119. package/dist/engine/localization.d.ts.map +1 -0
  120. package/dist/engine/localization.js +212 -0
  121. package/dist/engine/privacy/policies.d.ts +58 -0
  122. package/dist/engine/privacy/policies.d.ts.map +1 -0
  123. package/dist/engine/privacy/policies.js +191 -0
  124. package/dist/engine/privacy/privacy.service.d.ts +78 -0
  125. package/dist/engine/privacy/privacy.service.d.ts.map +1 -0
  126. package/dist/engine/privacy/privacy.service.js +138 -0
  127. package/dist/engine/privacy.service.d.ts.map +1 -1
  128. package/dist/engine/query/active-clause.d.ts +19 -0
  129. package/dist/engine/query/active-clause.d.ts.map +1 -0
  130. package/dist/engine/query/active-clause.js +27 -0
  131. package/dist/engine/query/query.d.ts +36 -0
  132. package/dist/engine/query/query.d.ts.map +1 -0
  133. package/dist/engine/query/query.js +393 -0
  134. package/dist/engine/query/relations.d.ts +28 -0
  135. package/dist/engine/query/relations.d.ts.map +1 -0
  136. package/dist/engine/query/relations.js +25 -0
  137. package/dist/engine/query/serialize.d.ts +22 -0
  138. package/dist/engine/query/serialize.d.ts.map +1 -0
  139. package/dist/engine/query/serialize.js +175 -0
  140. package/dist/engine/schema-fingerprint.d.ts +73 -0
  141. package/dist/engine/schema-fingerprint.d.ts.map +1 -0
  142. package/dist/engine/schema-fingerprint.js +97 -0
  143. package/dist/engine/seed-registry.d.ts.map +1 -1
  144. package/dist/engine/seeds/candidate-ids.d.ts +14 -0
  145. package/dist/engine/seeds/candidate-ids.d.ts.map +1 -0
  146. package/dist/engine/seeds/candidate-ids.js +40 -0
  147. package/dist/engine/seeds/define-seed.d.ts +3 -0
  148. package/dist/engine/seeds/define-seed.d.ts.map +1 -0
  149. package/dist/engine/seeds/define-seed.js +5 -0
  150. package/dist/engine/seeds/seed-definition-diff.d.ts +38 -0
  151. package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -0
  152. package/dist/engine/seeds/seed-definition-diff.js +51 -0
  153. package/dist/engine/seeds/seed-registry.d.ts +88 -0
  154. package/dist/engine/seeds/seed-registry.d.ts.map +1 -0
  155. package/dist/engine/seeds/seed-registry.js +194 -0
  156. package/dist/engine/seeds/seed-types-generator.d.ts +25 -0
  157. package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -0
  158. package/dist/engine/seeds/seed-types-generator.js +116 -0
  159. package/dist/engine/seeds/seed-validation.d.ts +35 -0
  160. package/dist/engine/seeds/seed-validation.d.ts.map +1 -0
  161. package/dist/engine/seeds/seed-validation.js +384 -0
  162. package/dist/engine/seeds/seeds.d.ts +20 -0
  163. package/dist/engine/seeds/seeds.d.ts.map +1 -0
  164. package/dist/engine/seeds/seeds.js +21 -0
  165. package/dist/engine/seeds/tag-migration.d.ts +73 -0
  166. package/dist/engine/seeds/tag-migration.d.ts.map +1 -0
  167. package/dist/engine/seeds/tag-migration.js +212 -0
  168. package/dist/engine/seeds/tag-options.d.ts +15 -0
  169. package/dist/engine/seeds/tag-options.d.ts.map +1 -0
  170. package/dist/engine/seeds/tag-options.js +111 -0
  171. package/dist/engine/seeds/tag-value.d.ts +31 -0
  172. package/dist/engine/seeds/tag-value.d.ts.map +1 -0
  173. package/dist/engine/seeds/tag-value.js +51 -0
  174. package/dist/engine/types.d.ts +59 -7
  175. package/dist/engine/types.d.ts.map +1 -1
  176. package/dist/engine/validation/cache.d.ts.map +1 -1
  177. package/dist/engine/validation/cache.js +20 -4
  178. package/dist/engine/validation/emptiness.d.ts +12 -0
  179. package/dist/engine/validation/emptiness.d.ts.map +1 -0
  180. package/dist/engine/validation/emptiness.js +33 -0
  181. package/dist/engine/validation/index.d.ts +11 -0
  182. package/dist/engine/validation/index.d.ts.map +1 -1
  183. package/dist/engine/validation/index.js +29 -33
  184. package/dist/engine/validation/primitives.d.ts +12 -0
  185. package/dist/engine/validation/primitives.d.ts.map +1 -1
  186. package/dist/engine/validation/primitives.js +22 -0
  187. package/dist/engine/validation/richtext-sanitizer.d.ts +9 -1
  188. package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
  189. package/dist/engine/validation/richtext-sanitizer.js +36 -7
  190. package/dist/engine/validation/schema-builders.d.ts +16 -0
  191. package/dist/engine/validation/schema-builders.d.ts.map +1 -1
  192. package/dist/engine/validation/schema-builders.js +125 -18
  193. package/dist/engine.d.ts +144 -0
  194. package/dist/engine.d.ts.map +1 -0
  195. package/dist/engine.js +741 -0
  196. package/dist/file-types.d.ts +13 -0
  197. package/dist/file-types.d.ts.map +1 -0
  198. package/dist/file-types.js +85 -0
  199. package/dist/hooks.d.ts +29 -0
  200. package/dist/hooks.d.ts.map +1 -0
  201. package/dist/hooks.js +3 -0
  202. package/dist/id-generator.d.ts +26 -0
  203. package/dist/id-generator.d.ts.map +1 -0
  204. package/dist/id-generator.js +12 -0
  205. package/dist/idempotency.repository.d.ts +13 -0
  206. package/dist/idempotency.repository.d.ts.map +1 -0
  207. package/dist/idempotency.repository.js +3 -0
  208. package/dist/index.d.ts +34 -11
  209. package/dist/index.d.ts.map +1 -1
  210. package/dist/index.js +34 -11
  211. package/dist/kanban-position.repository.d.ts +19 -0
  212. package/dist/kanban-position.repository.d.ts.map +1 -0
  213. package/dist/kanban-position.repository.js +3 -0
  214. package/dist/kanban.d.ts +75 -0
  215. package/dist/kanban.d.ts.map +1 -0
  216. package/dist/kanban.js +82 -0
  217. package/dist/layout-permissions.d.ts +3 -0
  218. package/dist/layout-permissions.d.ts.map +1 -0
  219. package/dist/layout-permissions.js +11 -0
  220. package/dist/media/file-types.d.ts +12 -0
  221. package/dist/media/file-types.d.ts.map +1 -1
  222. package/dist/media/file-types.js +24 -4
  223. package/dist/media/image-transformer.d.ts +34 -0
  224. package/dist/media/image-transformer.d.ts.map +1 -0
  225. package/dist/media/image-transformer.js +3 -0
  226. package/dist/media/magic-bytes.d.ts.map +1 -1
  227. package/dist/media/magic-bytes.js +19 -9
  228. package/dist/media/media-transform.d.ts +92 -0
  229. package/dist/media/media-transform.d.ts.map +1 -0
  230. package/dist/media/media-transform.js +197 -0
  231. package/dist/media/media.repository.d.ts +5 -0
  232. package/dist/media/media.repository.d.ts.map +1 -1
  233. package/dist/media.repository.d.ts +61 -0
  234. package/dist/media.repository.d.ts.map +1 -0
  235. package/dist/media.repository.js +3 -0
  236. package/dist/notifications/notification.repository.d.ts +11 -2
  237. package/dist/notifications/notification.repository.d.ts.map +1 -1
  238. package/dist/oauth/authorization-code.repository.d.ts +10 -5
  239. package/dist/oauth/authorization-code.repository.d.ts.map +1 -1
  240. package/dist/oauth/role-guard.d.ts +30 -11
  241. package/dist/oauth/role-guard.d.ts.map +1 -1
  242. package/dist/oauth/role-guard.js +27 -5
  243. package/dist/oauth/token.repository.d.ts +2 -0
  244. package/dist/oauth/token.repository.d.ts.map +1 -1
  245. package/dist/policies.d.ts +10 -0
  246. package/dist/policies.d.ts.map +1 -0
  247. package/dist/policies.js +33 -0
  248. package/dist/queue/queue.interface.d.ts +9 -1
  249. package/dist/queue/queue.interface.d.ts.map +1 -1
  250. package/dist/queue/queue.stub.d.ts +1 -1
  251. package/dist/queue/queue.stub.d.ts.map +1 -1
  252. package/dist/queue/queue.stub.js +2 -3
  253. package/dist/queue.interface.d.ts +39 -0
  254. package/dist/queue.interface.d.ts.map +1 -0
  255. package/dist/queue.interface.js +3 -0
  256. package/dist/queue.stub.d.ts +6 -0
  257. package/dist/queue.stub.d.ts.map +1 -0
  258. package/dist/queue.stub.js +8 -0
  259. package/dist/rate-limit/token-bucket-rate-limiter.d.ts +7 -0
  260. package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
  261. package/dist/rate-limit/token-bucket-rate-limiter.js +66 -0
  262. package/dist/rbac/evaluate.d.ts +57 -0
  263. package/dist/rbac/evaluate.d.ts.map +1 -0
  264. package/dist/rbac/evaluate.js +113 -0
  265. package/dist/rbac/invitation.repository.d.ts +84 -0
  266. package/dist/rbac/invitation.repository.d.ts.map +1 -0
  267. package/dist/rbac/invitation.repository.js +3 -0
  268. package/dist/rbac/permissions.d.ts +39 -0
  269. package/dist/rbac/permissions.d.ts.map +1 -0
  270. package/dist/rbac/permissions.js +43 -0
  271. package/dist/rbac/types.d.ts +130 -0
  272. package/dist/rbac/types.d.ts.map +1 -0
  273. package/dist/rbac/types.js +4 -0
  274. package/dist/relations.d.ts +28 -0
  275. package/dist/relations.d.ts.map +1 -0
  276. package/dist/relations.js +25 -0
  277. package/dist/richtext-render.d.ts +11 -0
  278. package/dist/richtext-render.d.ts.map +1 -0
  279. package/dist/richtext-render.js +87 -0
  280. package/dist/richtext.d.ts +11 -0
  281. package/dist/richtext.d.ts.map +1 -0
  282. package/dist/richtext.js +13 -0
  283. package/dist/scheduler.interface.d.ts +4 -0
  284. package/dist/scheduler.interface.d.ts.map +1 -0
  285. package/dist/scheduler.interface.js +3 -0
  286. package/dist/scheduler.stub.d.ts +5 -0
  287. package/dist/scheduler.stub.d.ts.map +1 -0
  288. package/dist/scheduler.stub.js +7 -0
  289. package/dist/schema/canonical.d.ts +15 -0
  290. package/dist/schema/canonical.d.ts.map +1 -0
  291. package/dist/schema/canonical.js +37 -0
  292. package/dist/schema/define.d.ts +42 -0
  293. package/dist/schema/define.d.ts.map +1 -0
  294. package/dist/schema/define.js +56 -0
  295. package/dist/schema/emit.d.ts +14 -0
  296. package/dist/schema/emit.d.ts.map +1 -0
  297. package/dist/schema/emit.js +43 -0
  298. package/dist/schema/index.d.ts +12 -0
  299. package/dist/schema/index.d.ts.map +1 -0
  300. package/dist/schema/index.js +13 -0
  301. package/dist/schema/manifest-seeds.d.ts +12 -0
  302. package/dist/schema/manifest-seeds.d.ts.map +1 -0
  303. package/dist/schema/manifest-seeds.js +48 -0
  304. package/dist/schema/manifest-validation.d.ts +13 -0
  305. package/dist/schema/manifest-validation.d.ts.map +1 -0
  306. package/dist/schema/manifest-validation.js +41 -0
  307. package/dist/schema/manifest.types.d.ts +54 -0
  308. package/dist/schema/manifest.types.d.ts.map +1 -0
  309. package/dist/schema/manifest.types.js +7 -0
  310. package/dist/schema-mutator.d.ts +26 -0
  311. package/dist/schema-mutator.d.ts.map +1 -0
  312. package/dist/schema-mutator.js +3 -0
  313. package/dist/search/search.repository.d.ts +5 -4
  314. package/dist/search/search.repository.d.ts.map +1 -1
  315. package/dist/search/vector-extractor.d.ts.map +1 -1
  316. package/dist/search/vector-extractor.js +23 -2
  317. package/dist/search/vector.repository.d.ts +2 -1
  318. package/dist/search/vector.repository.d.ts.map +1 -1
  319. package/dist/security/time-trap-token.repository.d.ts +5 -3
  320. package/dist/security/time-trap-token.repository.d.ts.map +1 -1
  321. package/dist/seed-ddl-destructive.d.ts +40 -0
  322. package/dist/seed-ddl-destructive.d.ts.map +1 -0
  323. package/dist/seed-ddl-destructive.js +149 -0
  324. package/dist/seed-ddl.d.ts +40 -0
  325. package/dist/seed-ddl.d.ts.map +1 -0
  326. package/dist/seed-ddl.js +100 -0
  327. package/dist/seed-layout.d.ts +145 -0
  328. package/dist/seed-layout.d.ts.map +1 -0
  329. package/dist/seed-layout.js +192 -0
  330. package/dist/seed-layout.repository.d.ts +22 -0
  331. package/dist/seed-layout.repository.d.ts.map +1 -0
  332. package/dist/seed-layout.repository.js +3 -0
  333. package/dist/seed-registry.d.ts +71 -0
  334. package/dist/seed-registry.d.ts.map +1 -0
  335. package/dist/seed-registry.js +143 -0
  336. package/dist/seed-types-generator.d.ts +1 -1
  337. package/dist/seed-types-generator.d.ts.map +1 -1
  338. package/dist/seed-validation.d.ts +23 -0
  339. package/dist/seed-validation.d.ts.map +1 -0
  340. package/dist/seed-validation.js +224 -0
  341. package/dist/seed.repository.d.ts +37 -0
  342. package/dist/seed.repository.d.ts.map +1 -0
  343. package/dist/seed.repository.js +3 -0
  344. package/dist/seeds.d.ts +20 -0
  345. package/dist/seeds.d.ts.map +1 -0
  346. package/dist/seeds.js +21 -0
  347. package/dist/settings/site-settings.repository.d.ts +8 -0
  348. package/dist/settings/site-settings.repository.d.ts.map +1 -1
  349. package/dist/site-settings.repository.d.ts +16 -0
  350. package/dist/site-settings.repository.d.ts.map +1 -0
  351. package/dist/site-settings.repository.js +3 -0
  352. package/dist/slug-utils.d.ts +18 -0
  353. package/dist/slug-utils.d.ts.map +1 -0
  354. package/dist/slug-utils.js +32 -0
  355. package/dist/storage.d.ts +46 -0
  356. package/dist/storage.d.ts.map +1 -0
  357. package/dist/storage.js +3 -0
  358. package/dist/transfer/csv.d.ts +34 -0
  359. package/dist/transfer/csv.d.ts.map +1 -0
  360. package/dist/transfer/csv.js +135 -0
  361. package/dist/transfer/flat-seed.d.ts +13 -0
  362. package/dist/transfer/flat-seed.d.ts.map +1 -0
  363. package/dist/transfer/flat-seed.js +50 -0
  364. package/dist/transfer/index.d.ts +7 -0
  365. package/dist/transfer/index.d.ts.map +1 -0
  366. package/dist/transfer/index.js +8 -0
  367. package/dist/transfer/ndjson.d.ts +25 -0
  368. package/dist/transfer/ndjson.d.ts.map +1 -0
  369. package/dist/transfer/ndjson.js +62 -0
  370. package/dist/transfer/row-mapping.d.ts +36 -0
  371. package/dist/transfer/row-mapping.d.ts.map +1 -0
  372. package/dist/transfer/row-mapping.js +97 -0
  373. package/dist/transfer/transfer.constants.d.ts +20 -0
  374. package/dist/transfer/transfer.constants.d.ts.map +1 -0
  375. package/dist/transfer/transfer.constants.js +21 -0
  376. package/dist/transfer/transfer.types.d.ts +46 -0
  377. package/dist/transfer/transfer.types.d.ts.map +1 -0
  378. package/dist/transfer/transfer.types.js +6 -0
  379. package/dist/types.d.ts +243 -0
  380. package/dist/types.d.ts.map +1 -0
  381. package/dist/types.js +3 -0
  382. package/dist/validation.d.ts +50 -0
  383. package/dist/validation.d.ts.map +1 -0
  384. package/dist/validation.js +660 -0
  385. package/dist/webhook-validation.d.ts +3 -0
  386. package/dist/webhook-validation.d.ts.map +1 -0
  387. package/dist/webhook-validation.js +6 -0
  388. package/package.json +11 -6
  389. package/dist/.tsbuildinfo +0 -1
@@ -33,36 +33,6 @@ export declare const kanbanCardConfigSchema: z.ZodObject<{
33
33
  }, z.core.$strip>>>;
34
34
  }, z.core.$strip>;
35
35
  export type KanbanCardConfig = z.infer<typeof kanbanCardConfigSchema>;
36
- export declare const seedViewConfigSchema: z.ZodObject<{
37
- kanban: z.ZodOptional<z.ZodObject<{
38
- axisBranchId: z.ZodNullable<z.ZodString>;
39
- sort: z.ZodNullable<z.ZodObject<{
40
- branchId: z.ZodString;
41
- dir: z.ZodEnum<{
42
- ASC: "ASC";
43
- DESC: "DESC";
44
- }>;
45
- }, z.core.$strip>>;
46
- hiddenColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
47
- collapsedColumnValues: z.ZodOptional<z.ZodArray<z.ZodString>>;
48
- }, z.core.$strip>>;
49
- card: z.ZodOptional<z.ZodObject<{
50
- version: z.ZodLiteral<1>;
51
- media: z.ZodOptional<z.ZodNullable<z.ZodObject<{
52
- branchId: z.ZodString;
53
- }, z.core.$strip>>>;
54
- header: z.ZodOptional<z.ZodNullable<z.ZodObject<{
55
- branchId: z.ZodString;
56
- }, z.core.$strip>>>;
57
- subtitle: z.ZodOptional<z.ZodNullable<z.ZodObject<{
58
- branchId: z.ZodString;
59
- }, z.core.$strip>>>;
60
- metadata: z.ZodDefault<z.ZodArray<z.ZodObject<{
61
- branchId: z.ZodString;
62
- }, z.core.$strip>>>;
63
- }, z.core.$strip>>;
64
- }, z.core.$loose>;
65
- export type SeedViewConfig = z.infer<typeof seedViewConfigSchema>;
66
36
  /** Reference to a Seed branch by its stable id (e.g. 'br_03').
67
37
  * Aliases can be renamed — ids cannot. When the id does not match any
68
38
  * branch on the Seed at read time, the field is silently stripped (auto-cleanup). */
@@ -108,6 +78,12 @@ export declare function isFullWidthBranch(branch: Branch): boolean;
108
78
  export declare function isSeoBranch(branch: Branch): boolean;
109
79
  export declare const METADATA_SLOT_CAP = 6;
110
80
  export declare function isCardEligibleBranch(branch: Branch): boolean;
81
+ /** Gallery cover: a single-file branch that accepts images (a multi-file/asset-list branch has no single cover). */
82
+ export declare function isImageMediaBranch(branch: Branch): boolean;
83
+ export interface ValidateCardConfigOptions {
84
+ /** Gallery cards only render an image cover: a non-image `media` slot is dropped. */
85
+ mediaImageOnly?: boolean;
86
+ }
111
87
  export type ValidateCardConfigResult = {
112
88
  ok: true;
113
89
  cleaned: KanbanCardConfig;
@@ -116,7 +92,9 @@ export type ValidateCardConfigResult = {
116
92
  errors: string[];
117
93
  cleaned: KanbanCardConfig;
118
94
  };
119
- export declare function validateCardConfigAgainstSeed(config: KanbanCardConfig, seed: Seed): ValidateCardConfigResult;
95
+ export declare function validateCardConfigAgainstSeed(config: KanbanCardConfig, seed: Seed, options?: ValidateCardConfigOptions): ValidateCardConfigResult;
96
+ /** Max serialized size of a form layout. Keeps the `seed_layouts` row far below D1's per-row limit. */
97
+ export declare const MAX_LAYOUT_BYTES: number;
120
98
  export declare const layoutFieldSchema: z.ZodObject<{
121
99
  branchId: z.ZodString;
122
100
  }, z.core.$strip>;
@@ -176,6 +154,12 @@ export declare const formLayoutSchema: z.ZodObject<{
176
154
  }, z.core.$strip>>;
177
155
  }, z.core.$strip>>;
178
156
  }, z.core.$strip>;
157
+ /**
158
+ * Builds the default editor layout for a Seed with no custom `layout`: full-width branches
159
+ * (richtext, json, galleries) each get their own dedicated section, the remaining branches are
160
+ * packed three per section in seed order, and a single main non-gallery image file branch
161
+ * (`fileOptions.accept === 'image'`) leads the Data tab alone in its own full-width section.
162
+ */
179
163
  export declare function generateDefaultLayout(seed: Seed, opts?: {
180
164
  newId: () => string;
181
165
  }): FormLayout;
@@ -188,4 +172,11 @@ export type ValidateLayoutResult = {
188
172
  cleaned: FormLayout;
189
173
  };
190
174
  export declare function validateLayoutAgainstSeed(layout: FormLayout, seed: Seed): ValidateLayoutResult;
175
+ /**
176
+ * Makes a stored layout safe to serve after the Seed changed. A layout that breaks the
177
+ * full-width rules falls back to the generated default; otherwise layoutable branches the
178
+ * layout never placed (added to the Seed later) are appended as trailing sections of the
179
+ * first tab, so the editor can always render and fill them.
180
+ */
181
+ export declare function reconcileLayoutWithSeed(layout: FormLayout, seed: Seed): FormLayout;
191
182
  //# sourceMappingURL=seed-layout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seed-layout.d.ts","sourceRoot":"","sources":["../../src/dashboard-layout/seed-layout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seed-layout.d.ts","sourceRoot":"","sources":["../../src/dashboard-layout/seed-layout.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
3
  import { z } from 'zod';
4
- import { findBranchById } from '../engine/seed-registry.js';
4
+ import { findBranchById } from '../engine/seeds/seed-registry.js';
5
5
  // ---------------------------------------------------------------------------
6
- // View config — per-seed, per-view dashboard preferences (KB-S02)
6
+ // Kanban view config — embedded in ContentViewConfig.kanban (seed_views)
7
7
  // ---------------------------------------------------------------------------
8
8
  export const kanbanViewConfigSchema = z.object({
9
9
  axisBranchId: z.string().nullable(),
@@ -12,7 +12,7 @@ export const kanbanViewConfigSchema = z.object({
12
12
  collapsedColumnValues: z.array(z.string()).optional(),
13
13
  });
14
14
  // ---------------------------------------------------------------------------
15
- // Kanban card layout (view_config.card)
15
+ // Kanban card layout — embedded in ContentViewConfig.card (seed_views)
16
16
  // ---------------------------------------------------------------------------
17
17
  export const cardSlotFieldSchema = z.object({
18
18
  branchId: z.string().regex(/^br_[A-Za-z0-9]+$/),
@@ -28,10 +28,6 @@ export const kanbanCardConfigSchema = z.object({
28
28
  /** 2-column grid. Hard cap enforced by validator (see METADATA_SLOT_CAP). */
29
29
  metadata: z.array(cardSlotFieldSchema).max(6).default([]),
30
30
  });
31
- export const seedViewConfigSchema = z.object({
32
- kanban: kanbanViewConfigSchema.optional(),
33
- card: kanbanCardConfigSchema.optional(),
34
- }).passthrough();
35
31
  // ---------------------------------------------------------------------------
36
32
  // Branch-type rules used by both the default generator and the validator.
37
33
  // ---------------------------------------------------------------------------
@@ -42,6 +38,12 @@ export function isGalleryBranch(branch) {
42
38
  return branch.type === 'file'
43
39
  && (branch.multiple === true || branch.format === 'asset-list');
44
40
  }
41
+ /** True for a single-file image branch: the candidate cover of the default editor layout. */
42
+ function isCoverImageBranch(branch) {
43
+ return branch.type === 'file'
44
+ && !isGalleryBranch(branch)
45
+ && branch.fileOptions?.accept === 'image';
46
+ }
45
47
  /** Branch types currently unsupported in the Layout Builder. */
46
48
  export const UNSUPPORTED_BRANCH_TYPES = new Set([]);
47
49
  /** Aliases that are NEVER included in the editor form (handled by other UI). */
@@ -80,7 +82,14 @@ export function isCardEligibleBranch(branch) {
80
82
  return false;
81
83
  return true;
82
84
  }
83
- export function validateCardConfigAgainstSeed(config, seed) {
85
+ /** Gallery cover: a single-file branch that accepts images (a multi-file/asset-list branch has no single cover). */
86
+ export function isImageMediaBranch(branch) {
87
+ return isCardEligibleBranch(branch)
88
+ && branch.type === 'file'
89
+ && !isGalleryBranch(branch)
90
+ && branch.fileOptions?.accept === 'image';
91
+ }
92
+ export function validateCardConfigAgainstSeed(config, seed, options = {}) {
84
93
  const errors = [];
85
94
  const seen = new Set();
86
95
  const keepSingle = (f) => {
@@ -96,7 +105,9 @@ export function validateCardConfigAgainstSeed(config, seed) {
96
105
  seen.add(f.branchId);
97
106
  return { branchId: f.branchId };
98
107
  };
99
- const media = keepSingle(config.media);
108
+ const mediaBranch = config.media ? findBranchById(seed, config.media.branchId) : null;
109
+ const mediaAllowed = !options.mediaImageOnly || (mediaBranch !== null && isImageMediaBranch(mediaBranch));
110
+ const media = mediaAllowed ? keepSingle(config.media) : null;
100
111
  const header = keepSingle(config.header);
101
112
  const subtitle = keepSingle(config.subtitle);
102
113
  const metadata = [];
@@ -121,27 +132,37 @@ export function validateCardConfigAgainstSeed(config, seed) {
121
132
  // ---------------------------------------------------------------------------
122
133
  // Zod schema for shape validation
123
134
  // ---------------------------------------------------------------------------
135
+ /** Max serialized size of a form layout. Keeps the `seed_layouts` row far below D1's per-row limit. */
136
+ export const MAX_LAYOUT_BYTES = 256 * 1024;
137
+ const LAYOUT_ID_MAX = 64;
138
+ const LAYOUT_LABEL_MAX = 120;
139
+ const MAX_SECTIONS_PER_TAB = 50;
140
+ const MAX_FIELDS_PER_COLUMN = 100;
124
141
  export const layoutFieldSchema = z.object({ branchId: z.string().regex(/^br_[A-Za-z0-9]+$/) });
125
142
  export const layoutColumnSchema = z.object({
126
- id: z.string().min(1),
127
- fields: z.array(layoutFieldSchema),
143
+ id: z.string().min(1).max(LAYOUT_ID_MAX),
144
+ fields: z.array(layoutFieldSchema).max(MAX_FIELDS_PER_COLUMN),
128
145
  });
129
146
  export const layoutSectionSchema = z.object({
130
- id: z.string().min(1),
131
- label: z.string().optional(),
147
+ id: z.string().min(1).max(LAYOUT_ID_MAX),
148
+ label: z.string().max(LAYOUT_LABEL_MAX).optional(),
132
149
  hideLabel: z.boolean().optional(),
133
150
  hideBorder: z.boolean().optional(),
134
151
  collapsible: z.boolean().optional(),
135
152
  columns: z.array(layoutColumnSchema).min(1).max(4),
136
153
  });
137
154
  export const layoutTabSchema = z.object({
138
- id: z.string().min(1),
155
+ id: z.string().min(1).max(LAYOUT_ID_MAX),
139
156
  label: z.string().min(1).max(60),
140
- sections: z.array(layoutSectionSchema).min(1),
157
+ sections: z.array(layoutSectionSchema).min(1).max(MAX_SECTIONS_PER_TAB),
141
158
  });
142
- export const formLayoutSchema = z.object({
159
+ export const formLayoutSchema = z
160
+ .object({
143
161
  version: z.literal(1),
144
162
  tabs: z.array(layoutTabSchema).min(1).max(8),
163
+ })
164
+ .refine((layout) => new TextEncoder().encode(JSON.stringify(layout)).length <= MAX_LAYOUT_BYTES, {
165
+ message: `Layout exceeds the ${MAX_LAYOUT_BYTES / 1024} KB size limit.`,
145
166
  });
146
167
  // ---------------------------------------------------------------------------
147
168
  // Default layout generator
@@ -180,15 +201,40 @@ function buildSectionsForBranches(branches, newId) {
180
201
  }
181
202
  return sections;
182
203
  }
204
+ /**
205
+ * Builds the default editor layout for a Seed with no custom `layout`: full-width branches
206
+ * (richtext, json, galleries) each get their own dedicated section, the remaining branches are
207
+ * packed three per section in seed order, and a single main non-gallery image file branch
208
+ * (`fileOptions.accept === 'image'`) leads the Data tab alone in its own full-width section.
209
+ */
183
210
  export function generateDefaultLayout(seed, opts) {
184
211
  const newId = opts?.newId ?? defaultIdFactory;
185
212
  const layoutable = seed.branches.filter(isLayoutableBranch);
186
213
  const seo = layoutable.filter(isSeoBranch);
187
214
  const main = layoutable.filter((b) => !isSeoBranch(b));
215
+ const coverCandidates = main.filter(isCoverImageBranch);
216
+ const cover = coverCandidates.length === 1 ? coverCandidates[0] : null;
217
+ const dataTabId = newId();
218
+ let dataSections;
219
+ if (cover) {
220
+ const coverSection = {
221
+ id: newId(),
222
+ label: cover.label,
223
+ hideLabel: true,
224
+ columns: [{ id: newId(), fields: [{ branchId: cover.id }] }],
225
+ };
226
+ const rest = main.filter((b) => b.id !== cover.id);
227
+ dataSections = rest.length === 0
228
+ ? [coverSection]
229
+ : [coverSection, ...buildSectionsForBranches(rest, newId)];
230
+ }
231
+ else {
232
+ dataSections = buildSectionsForBranches(main, newId);
233
+ }
188
234
  const dataTab = {
189
- id: newId(),
235
+ id: dataTabId,
190
236
  label: 'Data',
191
- sections: buildSectionsForBranches(main, newId),
237
+ sections: dataSections,
192
238
  };
193
239
  const seoTab = {
194
240
  id: newId(),
@@ -267,3 +313,32 @@ export function validateLayoutAgainstSeed(layout, seed) {
267
313
  return { ok: false, errors, cleaned };
268
314
  return { ok: true, cleaned };
269
315
  }
316
+ /**
317
+ * Makes a stored layout safe to serve after the Seed changed. A layout that breaks the
318
+ * full-width rules falls back to the generated default; otherwise layoutable branches the
319
+ * layout never placed (added to the Seed later) are appended as trailing sections of the
320
+ * first tab, so the editor can always render and fill them.
321
+ */
322
+ export function reconcileLayoutWithSeed(layout, seed) {
323
+ const result = validateLayoutAgainstSeed(layout, seed);
324
+ if (!result.ok)
325
+ return generateDefaultLayout(seed);
326
+ const placed = new Set();
327
+ for (const tab of result.cleaned.tabs) {
328
+ for (const section of tab.sections) {
329
+ for (const column of section.columns) {
330
+ for (const field of column.fields)
331
+ placed.add(field.branchId);
332
+ }
333
+ }
334
+ }
335
+ const unplaced = seed.branches.filter((b) => isLayoutableBranch(b) && !placed.has(b.id));
336
+ if (unplaced.length === 0)
337
+ return result.cleaned;
338
+ const [firstTab, ...otherTabs] = result.cleaned.tabs;
339
+ const extended = {
340
+ ...firstTab,
341
+ sections: [...firstTab.sections, ...buildSectionsForBranches(unplaced, defaultIdFactory)],
342
+ };
343
+ return { ...result.cleaned, tabs: [extended, ...otherTabs] };
344
+ }
@@ -1,4 +1,4 @@
1
- import type { FormLayout, SeedViewConfig } from './seed-layout.js';
1
+ import type { FormLayout } from './seed-layout.js';
2
2
  export interface SeedLayoutRecord {
3
3
  slug: string;
4
4
  layout: FormLayout;
@@ -14,9 +14,5 @@ export interface ISeedLayoutRepository {
14
14
  upsert(slug: string, layout: FormLayout, updatedBy: string): Promise<void>;
15
15
  /** Remove the stored row — used by the "Reset" action. */
16
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
17
  }
22
18
  //# sourceMappingURL=seed-layout.repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"seed-layout.repository.d.ts","sourceRoot":"","sources":["../../src/dashboard-layout/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"}
1
+ {"version":3,"file":"seed-layout.repository.d.ts","sourceRoot":"","sources":["../../src/dashboard-layout/seed-layout.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD,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;CACpC"}
@@ -0,0 +1,154 @@
1
+ import { z } from 'zod';
2
+ import type { Seed } from './types.js';
3
+ /** A placed widget. `type` is namespaced ('core/stat', '@acme/weather').
4
+ * `config` is opaque to core/API except for the optional `seedSlug` key,
5
+ * which enables auto-cleanup when the referenced seed disappears. */
6
+ export interface DashboardWidgetInstance {
7
+ id: string;
8
+ type: string;
9
+ title?: string;
10
+ config: Record<string, unknown>;
11
+ }
12
+ export interface DashboardColumn {
13
+ id: string;
14
+ widgets: DashboardWidgetInstance[];
15
+ }
16
+ export interface DashboardSection {
17
+ id: string;
18
+ label?: string;
19
+ hideLabel?: boolean;
20
+ collapsible?: boolean;
21
+ /** 1–4 columns. Enforced by validator. */
22
+ columns: DashboardColumn[];
23
+ /** Optional spans on a 12-unit grid, parallel to `columns`, must sum to 12.
24
+ * Absent = equal split. */
25
+ columnSpans?: number[];
26
+ }
27
+ export interface DashboardPageLayout {
28
+ id: string;
29
+ /** URL identity (?page=<slug>). Unique within the layout. */
30
+ slug: string;
31
+ label: string;
32
+ /** Lucide icon name, same convention as DashboardSeedConfig.icon. */
33
+ icon?: string;
34
+ sections: DashboardSection[];
35
+ }
36
+ export interface DashboardLayout {
37
+ /** Format version — bump when introducing breaking changes. */
38
+ version: 1;
39
+ pages: DashboardPageLayout[];
40
+ }
41
+ /** Namespaced widget type — lowercase npm-name-compatible.
42
+ * Built-ins use the 'core/' prefix; custom widgets '@scope/name'. */
43
+ export declare const WIDGET_TYPE_REGEX: RegExp;
44
+ /** Max serialized size of a single widget `config`
45
+ * (`JSON.stringify(widget.config).length`) — guards D1 row bloat. */
46
+ export declare const MAX_WIDGET_CONFIG_BYTES = 8192;
47
+ export declare const dashboardWidgetInstanceSchema: z.ZodObject<{
48
+ id: z.ZodString;
49
+ type: z.ZodString;
50
+ title: z.ZodOptional<z.ZodString>;
51
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
52
+ }, z.core.$strip>;
53
+ export declare const dashboardColumnSchema: z.ZodObject<{
54
+ id: z.ZodString;
55
+ widgets: z.ZodArray<z.ZodObject<{
56
+ id: z.ZodString;
57
+ type: z.ZodString;
58
+ title: z.ZodOptional<z.ZodString>;
59
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
60
+ }, z.core.$strip>>;
61
+ }, z.core.$strip>;
62
+ export declare const dashboardSectionSchema: z.ZodObject<{
63
+ id: z.ZodString;
64
+ label: z.ZodOptional<z.ZodString>;
65
+ hideLabel: z.ZodOptional<z.ZodBoolean>;
66
+ collapsible: z.ZodOptional<z.ZodBoolean>;
67
+ columns: z.ZodArray<z.ZodObject<{
68
+ id: z.ZodString;
69
+ widgets: z.ZodArray<z.ZodObject<{
70
+ id: z.ZodString;
71
+ type: z.ZodString;
72
+ title: z.ZodOptional<z.ZodString>;
73
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
74
+ }, z.core.$strip>>;
75
+ }, z.core.$strip>>;
76
+ columnSpans: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
77
+ }, z.core.$strip>;
78
+ export declare const dashboardPageSchema: z.ZodObject<{
79
+ id: z.ZodString;
80
+ slug: z.ZodString;
81
+ label: z.ZodString;
82
+ icon: z.ZodOptional<z.ZodString>;
83
+ sections: z.ZodArray<z.ZodObject<{
84
+ id: z.ZodString;
85
+ label: z.ZodOptional<z.ZodString>;
86
+ hideLabel: z.ZodOptional<z.ZodBoolean>;
87
+ collapsible: z.ZodOptional<z.ZodBoolean>;
88
+ columns: z.ZodArray<z.ZodObject<{
89
+ id: z.ZodString;
90
+ widgets: z.ZodArray<z.ZodObject<{
91
+ id: z.ZodString;
92
+ type: z.ZodString;
93
+ title: z.ZodOptional<z.ZodString>;
94
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
95
+ }, z.core.$strip>>;
96
+ }, z.core.$strip>>;
97
+ columnSpans: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
98
+ }, z.core.$strip>>;
99
+ }, z.core.$strip>;
100
+ export declare const dashboardLayoutSchema: z.ZodObject<{
101
+ version: z.ZodLiteral<1>;
102
+ pages: z.ZodArray<z.ZodObject<{
103
+ id: z.ZodString;
104
+ slug: z.ZodString;
105
+ label: z.ZodString;
106
+ icon: z.ZodOptional<z.ZodString>;
107
+ sections: z.ZodArray<z.ZodObject<{
108
+ id: z.ZodString;
109
+ label: z.ZodOptional<z.ZodString>;
110
+ hideLabel: z.ZodOptional<z.ZodBoolean>;
111
+ collapsible: z.ZodOptional<z.ZodBoolean>;
112
+ columns: z.ZodArray<z.ZodObject<{
113
+ id: z.ZodString;
114
+ widgets: z.ZodArray<z.ZodObject<{
115
+ id: z.ZodString;
116
+ type: z.ZodString;
117
+ title: z.ZodOptional<z.ZodString>;
118
+ config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
119
+ }, z.core.$strip>>;
120
+ }, z.core.$strip>>;
121
+ columnSpans: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
122
+ }, z.core.$strip>>;
123
+ }, z.core.$strip>>;
124
+ }, z.core.$strip>;
125
+ /** Structural port of the legacy hardcoded dashboard config into a single
126
+ * 'Overview' page. Widgets fetch their own data: configs carry only the
127
+ * variant and (where relevant) the default seed binding. */
128
+ export declare function generateDefaultDashboardLayout(seeds: Seed[], opts?: {
129
+ newId: () => string;
130
+ }): DashboardLayout;
131
+ export interface DashboardLayoutContext {
132
+ /** Slugs of currently registered seeds (from ISeedRegistry.all()). */
133
+ seedSlugs: ReadonlySet<string>;
134
+ /** Optional: widget types known to the caller (frontend registry).
135
+ * When provided, unknown types produce WARNINGS, never strips. */
136
+ knownWidgetTypes?: ReadonlySet<string>;
137
+ }
138
+ export type ValidateDashboardLayoutResult = {
139
+ ok: true;
140
+ cleaned: DashboardLayout;
141
+ warnings: string[];
142
+ } | {
143
+ ok: false;
144
+ errors: string[];
145
+ cleaned: DashboardLayout;
146
+ warnings: string[];
147
+ };
148
+ /** Semantic validation on top of the Zod shape check (the caller's job).
149
+ * Widgets bound to a seed that no longer exists are silently stripped
150
+ * (auto-cleanup, recorded as a warning). Unknown widget types only warn —
151
+ * custom widgets are invisible to the Worker and must never be stripped.
152
+ * `cleaned` is always returned, errors or not. */
153
+ export declare function validateDashboardLayout(layout: DashboardLayout, ctx: DashboardLayoutContext): ValidateDashboardLayoutResult;
154
+ //# sourceMappingURL=dashboard-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-layout.d.ts","sourceRoot":"","sources":["../src/dashboard-layout.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAMtC;;sEAEsE;AACtE,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,uBAAuB,EAAE,CAAA;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,0CAA0C;IAC1C,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B;gCAC4B;IAC5B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAA;IACV,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAC7B;AAED,MAAM,WAAW,eAAe;IAC9B,+DAA+D;IAC/D,OAAO,EAAE,CAAC,CAAA;IACV,KAAK,EAAE,mBAAmB,EAAE,CAAA;CAC7B;AAMD;sEACsE;AACtE,eAAO,MAAM,iBAAiB,QAA6B,CAAA;AAE3D;sEACsE;AACtE,eAAO,MAAM,uBAAuB,OAAO,CAAA;AAM3C,eAAO,MAAM,6BAA6B;;;;;iBAKxC,CAAA;AACF,eAAO,MAAM,qBAAqB;;;;;;;;iBAGhC,CAAA;AACF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;iBAOjC,CAAA;AACF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;iBAM9B,CAAA;AACF,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;iBAGhC,CAAA;AAQF;;6DAE6D;AAC7D,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,MAAM,CAAA;CAAE,GAC7B,eAAe,CA2EjB;AAMD,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC9B;uEACmE;IACnE,gBAAgB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CACvC;AAED,MAAM,MAAM,6BAA6B,GACrC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,GAC1D;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAA;AAEjF;;;;mDAImD;AACnD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,sBAAsB,GAC1B,6BAA6B,CA2E/B"}
@@ -0,0 +1,186 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { z } from 'zod';
4
+ // ---------------------------------------------------------------------------
5
+ // Shared rules used by both the Zod schemas and the semantic validator.
6
+ // ---------------------------------------------------------------------------
7
+ /** Namespaced widget type — lowercase npm-name-compatible.
8
+ * Built-ins use the 'core/' prefix; custom widgets '@scope/name'. */
9
+ export const WIDGET_TYPE_REGEX = /^[a-z0-9@][a-z0-9@/_-]*$/;
10
+ /** Max serialized size of a single widget `config`
11
+ * (`JSON.stringify(widget.config).length`) — guards D1 row bloat. */
12
+ export const MAX_WIDGET_CONFIG_BYTES = 8192;
13
+ // ---------------------------------------------------------------------------
14
+ // Zod schemas for shape validation
15
+ // ---------------------------------------------------------------------------
16
+ export const dashboardWidgetInstanceSchema = z.object({
17
+ id: z.string().min(1),
18
+ type: z.string().regex(WIDGET_TYPE_REGEX),
19
+ title: z.string().max(80).optional(),
20
+ config: z.record(z.string(), z.unknown()),
21
+ });
22
+ export const dashboardColumnSchema = z.object({
23
+ id: z.string().min(1),
24
+ widgets: z.array(dashboardWidgetInstanceSchema),
25
+ });
26
+ export const dashboardSectionSchema = z.object({
27
+ id: z.string().min(1),
28
+ label: z.string().max(60).optional(),
29
+ hideLabel: z.boolean().optional(),
30
+ collapsible: z.boolean().optional(),
31
+ columns: z.array(dashboardColumnSchema).min(1).max(4),
32
+ columnSpans: z.array(z.number().int().min(1).max(12)).optional(),
33
+ });
34
+ export const dashboardPageSchema = z.object({
35
+ id: z.string().min(1),
36
+ slug: z.string().regex(/^[a-z0-9][a-z0-9-]*$/).max(40),
37
+ label: z.string().min(1).max(60),
38
+ icon: z.string().max(40).optional(),
39
+ sections: z.array(dashboardSectionSchema).min(1),
40
+ });
41
+ export const dashboardLayoutSchema = z.object({
42
+ version: z.literal(1),
43
+ pages: z.array(dashboardPageSchema).min(1).max(8),
44
+ });
45
+ // ---------------------------------------------------------------------------
46
+ // Default dashboard layout generator
47
+ // ---------------------------------------------------------------------------
48
+ const defaultIdFactory = () => crypto.randomUUID();
49
+ /** Structural port of the legacy hardcoded dashboard config into a single
50
+ * 'Overview' page. Widgets fetch their own data: configs carry only the
51
+ * variant and (where relevant) the default seed binding. */
52
+ export function generateDefaultDashboardLayout(seeds, opts) {
53
+ const newId = opts?.newId ?? defaultIdFactory;
54
+ const widget = (type, config) => ({ id: newId(), type, config });
55
+ const singleWidgetColumn = (w) => ({
56
+ id: newId(),
57
+ widgets: [w],
58
+ });
59
+ const sections = [];
60
+ // Setup checklist, full width.
61
+ sections.push({
62
+ id: newId(),
63
+ hideLabel: true,
64
+ columns: [singleWidgetColumn(widget('core/setup-checklist', { variant: 'full' }))],
65
+ });
66
+ // Status row: equal columns; quick-draft needs at least one seed to target.
67
+ const statusWidgets = [
68
+ widget('core/site-status', { variant: 'badge' }),
69
+ widget('core/storage', { variant: 'gauge' }),
70
+ widget('core/publication-stats', { variant: 'trio' }),
71
+ ];
72
+ if (seeds.length > 0) {
73
+ statusWidgets.push(widget('core/quick-draft', { variant: 'minimal' }));
74
+ }
75
+ sections.push({
76
+ id: newId(),
77
+ hideLabel: true,
78
+ columns: statusWidgets.map(singleWidgetColumn),
79
+ });
80
+ if (seeds.length > 0) {
81
+ const seedSlug = seeds.find((s) => s.slug === 'articoli')?.slug ?? seeds[0].slug;
82
+ // Content row: recent content | pending drafts.
83
+ sections.push({
84
+ id: newId(),
85
+ hideLabel: true,
86
+ columns: [
87
+ singleWidgetColumn(widget('core/recent-content', { seedSlug, variant: 'list' })),
88
+ singleWidgetColumn(widget('core/pending-drafts', { seedSlug, variant: 'list' })),
89
+ ],
90
+ columnSpans: [6, 6],
91
+ });
92
+ // Media & activity row: media gallery | activity feed.
93
+ sections.push({
94
+ id: newId(),
95
+ hideLabel: true,
96
+ columns: [
97
+ singleWidgetColumn(widget('core/media-gallery', { seedSlug, variant: 'grid' })),
98
+ singleWidgetColumn(widget('core/activity-feed', { seedSlug, variant: 'feed' })),
99
+ ],
100
+ columnSpans: [6, 6],
101
+ });
102
+ }
103
+ return {
104
+ version: 1,
105
+ pages: [
106
+ {
107
+ id: newId(),
108
+ slug: 'overview',
109
+ label: 'Overview',
110
+ icon: 'LayoutDashboard',
111
+ sections,
112
+ },
113
+ ],
114
+ };
115
+ }
116
+ /** Semantic validation on top of the Zod shape check (the caller's job).
117
+ * Widgets bound to a seed that no longer exists are silently stripped
118
+ * (auto-cleanup, recorded as a warning). Unknown widget types only warn —
119
+ * custom widgets are invisible to the Worker and must never be stripped.
120
+ * `cleaned` is always returned, errors or not. */
121
+ export function validateDashboardLayout(layout, ctx) {
122
+ const errors = [];
123
+ const warnings = [];
124
+ // Auto-cleanup: drop widgets whose config.seedSlug references a missing seed.
125
+ const cleanedPages = layout.pages.map((page) => ({
126
+ ...page,
127
+ sections: page.sections.map((section) => ({
128
+ ...section,
129
+ columns: section.columns.map((col) => ({
130
+ ...col,
131
+ widgets: (col.widgets ?? []).filter((w) => {
132
+ const seedSlug = (w.config ?? {})['seedSlug'];
133
+ if (typeof seedSlug === 'string' && !ctx.seedSlugs.has(seedSlug)) {
134
+ warnings.push(`Widget '${w.type}' (id=${w.id}) removed: seed '${seedSlug}' is not registered.`);
135
+ return false;
136
+ }
137
+ return true;
138
+ }),
139
+ })),
140
+ })),
141
+ }));
142
+ // Semantic checks on the cleaned layout
143
+ const seenWidgetIds = new Set();
144
+ const seenPageSlugs = new Set();
145
+ for (const page of cleanedPages) {
146
+ if (seenPageSlugs.has(page.slug)) {
147
+ errors.push(`Page slug '${page.slug}' appears more than once in the layout.`);
148
+ }
149
+ else {
150
+ seenPageSlugs.add(page.slug);
151
+ }
152
+ for (const section of page.sections) {
153
+ if (section.columnSpans !== undefined) {
154
+ if (section.columnSpans.length !== section.columns.length) {
155
+ errors.push(`Section ${section.id}: columnSpans has ${section.columnSpans.length} entries for ${section.columns.length} columns.`);
156
+ }
157
+ else {
158
+ const sum = section.columnSpans.reduce((acc, span) => acc + span, 0);
159
+ if (sum !== 12) {
160
+ errors.push(`Section ${section.id}: columnSpans must sum to 12, got ${sum}.`);
161
+ }
162
+ }
163
+ }
164
+ for (const col of section.columns) {
165
+ for (const w of col.widgets) {
166
+ if (seenWidgetIds.has(w.id)) {
167
+ errors.push(`Widget id '${w.id}' appears more than once in the layout.`);
168
+ }
169
+ else {
170
+ seenWidgetIds.add(w.id);
171
+ }
172
+ if (JSON.stringify(w.config ?? {}).length > MAX_WIDGET_CONFIG_BYTES) {
173
+ errors.push(`Widget '${w.type}' (id=${w.id}): config exceeds ${MAX_WIDGET_CONFIG_BYTES} bytes when serialized.`);
174
+ }
175
+ if (ctx.knownWidgetTypes !== undefined && !ctx.knownWidgetTypes.has(w.type)) {
176
+ warnings.push(`Widget '${w.type}' (id=${w.id}) is not a known widget type.`);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ const cleaned = { ...layout, pages: cleanedPages };
183
+ if (errors.length > 0)
184
+ return { ok: false, errors, cleaned, warnings };
185
+ return { ok: true, cleaned, warnings };
186
+ }