@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
@@ -0,0 +1,212 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { draftTableName, indexableSearchBranches, tableName } from '../ddl/ddl.js';
4
+ import { cleanString, isPlainObject } from '../validation/primitives.js';
5
+ import { TAG_OPTION_COLOR_RE, tagOptionsIssues } from './tag-options.js';
6
+ import { normalizeTagValue, TAG_OPTIONS_MAX_COUNT } from './tag-value.js';
7
+ /**
8
+ * Colors assigned, in declaration order, to legacy options that never carried one.
9
+ * Deterministic on purpose: re-running the plan yields the same definition.
10
+ */
11
+ export const TAG_MIGRATION_PALETTE = [
12
+ '#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6', '#14b8a6', '#ec4899', '#64748b',
13
+ ];
14
+ /** True when the plan has anything to report or do. */
15
+ export function hasTagMigrationWork(plan) {
16
+ return plan.steps.length > 0;
17
+ }
18
+ /** Steps a human must resolve. */
19
+ export function manualTagSteps(plan) {
20
+ return plan.steps.filter(step => step.action === 'manual');
21
+ }
22
+ function paletteColor(index) {
23
+ return TAG_MIGRATION_PALETTE[index % TAG_MIGRATION_PALETTE.length];
24
+ }
25
+ /**
26
+ * Converts whatever a legacy `options` array held into the `{ value, color }` contract.
27
+ * Returns `null` when nothing salvageable remains (the caller then asks for a manual fix).
28
+ */
29
+ function rewriteOptions(raw) {
30
+ if (!Array.isArray(raw) || raw.length === 0 || raw.length > TAG_OPTIONS_MAX_COUNT)
31
+ return null;
32
+ const seen = new Set();
33
+ const options = [];
34
+ raw.forEach((entry, index) => {
35
+ let candidate;
36
+ let color;
37
+ if (typeof entry === 'string') {
38
+ candidate = entry;
39
+ }
40
+ else if (isPlainObject(entry) && typeof entry.value === 'string') {
41
+ candidate = entry.value;
42
+ color = typeof entry.color === 'string' && TAG_OPTION_COLOR_RE.test(entry.color) ? entry.color : undefined;
43
+ }
44
+ if (candidate === undefined)
45
+ return;
46
+ const normalized = normalizeTagValue(cleanString(candidate));
47
+ if (!normalized.ok || normalized.value === '' || seen.has(normalized.value))
48
+ return;
49
+ seen.add(normalized.value);
50
+ options.push({ value: normalized.value, color: color ?? paletteColor(options.length) });
51
+ });
52
+ return options.length > 0 ? options : null;
53
+ }
54
+ function manual(branch, summary) {
55
+ return { branch, step: { alias: branch.alias, action: 'manual', summary, warnings: [] } };
56
+ }
57
+ function isSafeIdentifier(name) {
58
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name);
59
+ }
60
+ /** Plans the rewrite of one top-level branch; `null` when it already honours the contract. */
61
+ function planBranch(seed, branch) {
62
+ const raw = branch;
63
+ const ownIssues = tagOptionsIssues({ ...branch, fields: [] });
64
+ const nestedIssues = tagOptionsIssues(branch).length - ownIssues.length;
65
+ if (ownIssues.length === 0) {
66
+ return nestedIssues > 0
67
+ ? manual(branch, `a repeater sub-field of '${branch.alias}' uses the legacy options shape; edit the seed by hand`)
68
+ : null;
69
+ }
70
+ if (!isSafeIdentifier(branch.alias)) {
71
+ return manual(branch, `alias '${branch.alias}' is not a plain SQL identifier`);
72
+ }
73
+ if (branch.type !== 'text' && branch.type !== 'tags') {
74
+ return manual(branch, `options are not supported on type '${branch.type}'; remove them from the seed`);
75
+ }
76
+ if (branch.localized === true) {
77
+ return manual(branch, `'${branch.alias}' is localized; its stored locale dictionary cannot be wrapped automatically`);
78
+ }
79
+ if (nestedIssues > 0) {
80
+ return manual(branch, `'${branch.alias}' also has legacy options in a repeater sub-field; edit the seed by hand`);
81
+ }
82
+ const options = rewriteOptions(raw.options);
83
+ if (!options) {
84
+ return manual(branch, `'${branch.alias}' has no usable options (empty, over ${TAG_OPTIONS_MAX_COUNT}, or all invalid)`);
85
+ }
86
+ const rewritten = branch.type === 'text'
87
+ ? { ...branch, type: 'tags', multiple: false, options }
88
+ : { ...branch, options };
89
+ if (tagOptionsIssues(rewritten).length > 0) {
90
+ return manual(branch, `'${branch.alias}' still violates the tags contract after conversion`);
91
+ }
92
+ const warnings = [];
93
+ if (branch.type === 'text' && indexableSearchBranches({ ...seed, branches: [branch] }).length > 0) {
94
+ warnings.push(`'${branch.alias}' was full-text indexed as text; run \`beech schema diff\` to check the search index`);
95
+ }
96
+ return branch.type === 'text'
97
+ ? {
98
+ branch: rewritten,
99
+ step: {
100
+ alias: branch.alias,
101
+ action: 'text-to-tags',
102
+ summary: `'${branch.alias}': text + options → single-choice tags (${options.length} options, values wrapped into arrays)`,
103
+ warnings,
104
+ },
105
+ }
106
+ : {
107
+ branch: rewritten,
108
+ step: {
109
+ alias: branch.alias,
110
+ action: 'tags-options',
111
+ summary: `'${branch.alias}': options rewritten as { value, color } (${options.length} options, values untouched)`,
112
+ warnings,
113
+ },
114
+ };
115
+ }
116
+ /**
117
+ * Computes what moving `seeds` to the 0.9 tags contract takes. Seeds that already comply produce no
118
+ * step and no rewritten definition, so a clean database yields an empty plan (the operation is idempotent).
119
+ */
120
+ export function planTagMigration(seeds) {
121
+ const steps = [];
122
+ const rewrittenSeeds = [];
123
+ for (const seed of seeds) {
124
+ const seedSteps = [];
125
+ let changed = false;
126
+ const branches = seed.branches.map(branch => {
127
+ const rewrite = planBranch(seed, branch);
128
+ if (!rewrite)
129
+ return branch;
130
+ seedSteps.push({ seed: seed.slug, ...rewrite.step });
131
+ if (rewrite.step.action !== 'manual')
132
+ changed = true;
133
+ return rewrite.branch;
134
+ });
135
+ steps.push(...seedSteps);
136
+ // A seed with an unresolved branch is left exactly as stored: half a migration is worse than none.
137
+ if (changed && !seedSteps.some(step => step.action === 'manual')) {
138
+ rewrittenSeeds.push({ ...seed, branches });
139
+ }
140
+ }
141
+ return { steps, seeds: rewrittenSeeds };
142
+ }
143
+ function quote(value) {
144
+ return `'${value.replace(/'/g, "''")}'`;
145
+ }
146
+ /** `text-to-tags` steps of `seed` that the plan will rewrite. */
147
+ function wrapAliases(plan, seed) {
148
+ const rewritten = plan.seeds.some(candidate => candidate.slug === seed.slug);
149
+ if (!rewritten)
150
+ return [];
151
+ return plan.steps
152
+ .filter(step => step.seed === seed.slug && step.action === 'text-to-tags')
153
+ .map(step => step.alias);
154
+ }
155
+ /**
156
+ * The statements that carry the plan out, in execution order: stored values first, then the seed
157
+ * definitions, then the registry-version bump that tells every isolate to rebuild its cached registry.
158
+ * Value wrapping touches the live table and, when drafts are on, the drafts table.
159
+ *
160
+ * `originals` are the definitions the plan was computed from (the physical tables are named after them).
161
+ */
162
+ export function tagMigrationStatements(plan, originals) {
163
+ const statements = [];
164
+ for (const original of originals) {
165
+ for (const alias of wrapAliases(plan, original)) {
166
+ const tables = [tableName(original), ...(original.allowDrafts ? [draftTableName(original)] : [])];
167
+ for (const table of tables) {
168
+ statements.push(`UPDATE ${table} SET ${alias} = NULL WHERE ${alias} = '';`);
169
+ statements.push(`UPDATE ${table} SET ${alias} = json_array(${alias}) `
170
+ + `WHERE ${alias} IS NOT NULL AND NOT (json_valid(${alias}) AND json_type(${alias}) = 'array');`);
171
+ }
172
+ }
173
+ }
174
+ for (const seed of plan.seeds) {
175
+ statements.push(`UPDATE seeds SET definition = ${quote(JSON.stringify(seed))}, updated_at = unixepoch() WHERE slug = ${quote(seed.slug)};`);
176
+ }
177
+ if (plan.seeds.length > 0) {
178
+ statements.push(`UPDATE seed_meta SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT) WHERE id = 'registry_version';`);
179
+ }
180
+ return statements;
181
+ }
182
+ /**
183
+ * Reads, before anything is changed, the stored values the new vocabulary would not contain. They are
184
+ * reported, never deleted or rewritten: the operator decides whether to add an option or fix the rows.
185
+ * Read-only (`SELECT` through the injected executor).
186
+ */
187
+ export async function findTagOrphans(executor, plan, originals) {
188
+ const orphans = [];
189
+ for (const rewritten of plan.seeds) {
190
+ const original = originals.find(seed => seed.slug === rewritten.slug);
191
+ if (!original)
192
+ continue;
193
+ const table = tableName(original);
194
+ for (const step of plan.steps.filter(candidate => candidate.seed === rewritten.slug && candidate.action !== 'manual')) {
195
+ const branch = rewritten.branches.find(candidate => candidate.alias === step.alias);
196
+ if (!branch?.options)
197
+ continue;
198
+ const allowed = branch.options.map(option => quote(option.value)).join(', ');
199
+ const sql = step.action === 'text-to-tags'
200
+ ? `SELECT ${step.alias} AS value, COUNT(*) AS n FROM ${table} `
201
+ + `WHERE ${step.alias} IS NOT NULL AND ${step.alias} != '' AND ${step.alias} NOT IN (${allowed}) GROUP BY ${step.alias}`
202
+ : `SELECT j.value AS value, COUNT(*) AS n FROM ${table}, `
203
+ + `json_each(CASE WHEN json_valid(${table}.${step.alias}) THEN ${table}.${step.alias} ELSE '[]' END) AS j `
204
+ + `WHERE j.value NOT IN (${allowed}) GROUP BY j.value`;
205
+ const rows = await executor.all(sql);
206
+ for (const row of rows) {
207
+ orphans.push({ seed: rewritten.slug, alias: step.alias, value: String(row.value), rows: Number(row.n) });
208
+ }
209
+ }
210
+ }
211
+ return orphans;
212
+ }
@@ -0,0 +1,15 @@
1
+ import type { Branch, TagOption } from '../types.js';
2
+ /** Accepted option color format: 6-digit hex, the value `<input type="color">` emits. */
3
+ export declare const TAG_OPTION_COLOR_RE: RegExp;
4
+ /** True only for a `tags` branch with `multiple === false`. Omitted `multiple` on `tags` means multiple choice. */
5
+ export declare function isSingleChoiceTags(branch: Pick<Branch, 'type' | 'multiple'>): boolean;
6
+ /** Option values in declared order; `[]` when the branch has no options. */
7
+ export declare function tagOptionValues(branch: Pick<Branch, 'options'>): string[];
8
+ /** The option whose `value` equals `value` exactly, or `undefined`. */
9
+ export declare function findTagOption(branch: Pick<Branch, 'options'>, value: string): TagOption | undefined;
10
+ /**
11
+ * Seed-definition issues for `options` / `multiple` on this branch AND, recursively, its repeater sub-fields.
12
+ * Pure, never throws, works on untyped input (stored JSON may carry any shape). `[]` = valid.
13
+ */
14
+ export declare function tagOptionsIssues(branch: Branch): string[];
15
+ //# sourceMappingURL=tag-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-options.d.ts","sourceRoot":"","sources":["../../../src/engine/seeds/tag-options.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAIpD,yFAAyF;AACzF,eAAO,MAAM,mBAAmB,QAAsB,CAAA;AAEtD,mHAAmH;AACnH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG,OAAO,CAErF;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,MAAM,EAAE,CAEzE;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAEnG;AA8ED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAOzD"}
@@ -0,0 +1,111 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { cleanString, isPlainObject } from '../validation/primitives.js';
4
+ import { normalizeTagValue, TAG_MAX_LENGTH, TAG_OPTIONS_MAX_COUNT } from './tag-value.js';
5
+ /** Accepted option color format: 6-digit hex, the value `<input type="color">` emits. */
6
+ export const TAG_OPTION_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
7
+ /** True only for a `tags` branch with `multiple === false`. Omitted `multiple` on `tags` means multiple choice. */
8
+ export function isSingleChoiceTags(branch) {
9
+ return branch.type === 'tags' && branch.multiple === false;
10
+ }
11
+ /** Option values in declared order; `[]` when the branch has no options. */
12
+ export function tagOptionValues(branch) {
13
+ return (branch.options ?? []).map(option => option.value);
14
+ }
15
+ /** The option whose `value` equals `value` exactly, or `undefined`. */
16
+ export function findTagOption(branch, value) {
17
+ return branch.options?.find(option => option.value === value);
18
+ }
19
+ /** Issues for one option entry; `seenValues` accumulates the values already declared on the branch. */
20
+ function optionEntryIssues(prefix, entry, index, seenValues) {
21
+ if (typeof entry === 'string') {
22
+ return [`${prefix}option #${index} must be an object { value, color }, not a plain string.`];
23
+ }
24
+ if (!isPlainObject(entry)) {
25
+ return [`${prefix}option #${index} must be an object { value, color }.`];
26
+ }
27
+ const issues = [];
28
+ const { value, color } = entry;
29
+ const validValue = typeof value === 'string' && value !== '' && value === cleanString(value);
30
+ if (!validValue) {
31
+ issues.push(`${prefix}option #${index} value must be a non-empty string without leading or trailing whitespace.`);
32
+ }
33
+ else {
34
+ const normalized = normalizeTagValue(value);
35
+ if (!normalized.ok) {
36
+ issues.push(normalized.reason === 'too-long'
37
+ ? `${prefix}option '${value.slice(0, 40)}' value is too long (max ${TAG_MAX_LENGTH} characters).`
38
+ : `${prefix}option #${index} value is not valid text (lone surrogate).`);
39
+ }
40
+ else if (normalized.value !== value) {
41
+ issues.push(`${prefix}option #${index} value must already be normalized: no invisible or control characters, `
42
+ + `single spaces, Unicode NFC (use ${JSON.stringify(normalized.value)}).`);
43
+ }
44
+ else if (seenValues.has(value)) {
45
+ issues.push(`${prefix}duplicate option value '${value}'.`);
46
+ }
47
+ else {
48
+ seenValues.add(value);
49
+ }
50
+ }
51
+ if (typeof color !== 'string' || !TAG_OPTION_COLOR_RE.test(color)) {
52
+ const optionLabel = validValue ? `'${value}'` : `#${index}`;
53
+ issues.push(`${prefix}option ${optionLabel} color must be a hex color #RRGGBB (got '${String(color)}').`);
54
+ }
55
+ return issues;
56
+ }
57
+ function branchTagOptionsIssues(prefix, branch) {
58
+ const raw = branch;
59
+ const issues = [];
60
+ if (raw.options !== undefined) {
61
+ if (branch.type === 'text') {
62
+ return [
63
+ `${prefix}options are not supported on type 'text'. `
64
+ + `Use type 'tags' with options (add multiple: false for a single choice).`,
65
+ ];
66
+ }
67
+ if (branch.type !== 'tags') {
68
+ return [`${prefix}options are only supported on type 'tags' (got '${String(branch.type)}').`];
69
+ }
70
+ }
71
+ if (branch.type === 'tags') {
72
+ if (raw.multiple !== undefined && typeof raw.multiple !== 'boolean') {
73
+ issues.push(`${prefix}multiple must be a boolean.`);
74
+ }
75
+ if (raw.options !== undefined) {
76
+ if (!Array.isArray(raw.options) || raw.options.length === 0) {
77
+ issues.push(`${prefix}options must be a non-empty array of { value, color } objects.`);
78
+ }
79
+ else if (raw.options.length > TAG_OPTIONS_MAX_COUNT) {
80
+ issues.push(`${prefix}options must contain at most ${TAG_OPTIONS_MAX_COUNT} entries (got ${raw.options.length}).`);
81
+ }
82
+ else {
83
+ const seenValues = new Set();
84
+ raw.options.forEach((entry, index) => {
85
+ issues.push(...optionEntryIssues(prefix, entry, index, seenValues));
86
+ });
87
+ }
88
+ }
89
+ }
90
+ return issues;
91
+ }
92
+ /**
93
+ * Seed-definition issues for `options` / `multiple` on this branch AND, recursively, its repeater sub-fields.
94
+ * Pure, never throws, works on untyped input (stored JSON may carry any shape). `[]` = valid.
95
+ */
96
+ export function tagOptionsIssues(branch) {
97
+ const issues = branchTagOptionsIssues(`branch '${branch.alias}': `, branch);
98
+ const subFields = Array.isArray(branch.fields) ? branch.fields : [];
99
+ for (const sub of subFields) {
100
+ issues.push(...subFieldIssues(branch.alias, sub));
101
+ }
102
+ return issues;
103
+ }
104
+ function subFieldIssues(parentAlias, sub) {
105
+ const issues = branchTagOptionsIssues(`branch '${parentAlias}': sub-field '${sub.alias}': `, sub);
106
+ const nested = Array.isArray(sub.fields) ? sub.fields : [];
107
+ for (const child of nested) {
108
+ issues.push(...subFieldIssues(sub.alias, child));
109
+ }
110
+ return issues;
111
+ }
@@ -0,0 +1,31 @@
1
+ /** Max tags stored in one `tags` field. Tags flag small things; more than this stops being a label set. */
2
+ export declare const TAG_MAX_COUNT = 10;
3
+ /** Max user-perceived characters (grapheme clusters) in one tag, so one emoji or `é` counts as 1. */
4
+ export declare const TAG_MAX_LENGTH = 25;
5
+ /** Hard UTF-8 byte ceiling per tag: bounds stacked combining marks ("zalgo") that fit in 25 graphemes. */
6
+ export declare const TAG_MAX_BYTES = 100;
7
+ /** Max options a closed-vocabulary `tags` branch may declare. */
8
+ export declare const TAG_OPTIONS_MAX_COUNT = 50;
9
+ /** Array size accepted before cleaning, so blanks the cleaner drops do not eat the real budget. */
10
+ export declare const TAG_RAW_MAX_COUNT = 50;
11
+ /** Why a tag value cannot be stored. */
12
+ export type TagValueRejection = 'ill-formed' | 'too-long';
13
+ /** Outcome of {@link normalizeTagValue}: the canonical value (possibly `''`), or why it was rejected. */
14
+ export type TagValueResult = {
15
+ readonly ok: true;
16
+ readonly value: string;
17
+ } | {
18
+ readonly ok: false;
19
+ readonly reason: TagValueRejection;
20
+ };
21
+ /**
22
+ * Canonical form of one tag, the single rule shared by payload validation, seed option validation and filters:
23
+ * invisible and control characters removed (an emoji-gluing joiner survives), every whitespace run (tab, newline,
24
+ * NBSP, line separator…) collapsed to one space, trimmed, Unicode NFC. Case is preserved (`Foo` ≠ `foo`).
25
+ *
26
+ * `ok: true` with `''` means the tag was blank after cleaning: callers decide whether to drop or reject it.
27
+ * Rejected, never repaired: a lone UTF-16 surrogate (not valid UTF-8) and a value over
28
+ * {@link TAG_MAX_LENGTH} characters or {@link TAG_MAX_BYTES} bytes. Pure, never throws.
29
+ */
30
+ export declare function normalizeTagValue(raw: string): TagValueResult;
31
+ //# sourceMappingURL=tag-value.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tag-value.d.ts","sourceRoot":"","sources":["../../../src/engine/seeds/tag-value.ts"],"names":[],"mappings":"AAKA,2GAA2G;AAC3G,eAAO,MAAM,aAAa,KAAK,CAAA;AAC/B,qGAAqG;AACrG,eAAO,MAAM,cAAc,KAAK,CAAA;AAChC,0GAA0G;AAC1G,eAAO,MAAM,aAAa,MAAM,CAAA;AAChC,iEAAiE;AACjE,eAAO,MAAM,qBAAqB,KAAK,CAAA;AACvC,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,KAAK,CAAA;AAgBnC,wCAAwC;AACxC,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,UAAU,CAAA;AAEzD,yGAAyG;AACzG,MAAM,MAAM,cAAc,GACtB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAA;AAM9D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAc7D"}
@@ -0,0 +1,51 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { byteLength, stripControlChars } from '../validation/primitives.js';
4
+ /** Max tags stored in one `tags` field. Tags flag small things; more than this stops being a label set. */
5
+ export const TAG_MAX_COUNT = 10;
6
+ /** Max user-perceived characters (grapheme clusters) in one tag, so one emoji or `é` counts as 1. */
7
+ export const TAG_MAX_LENGTH = 25;
8
+ /** Hard UTF-8 byte ceiling per tag: bounds stacked combining marks ("zalgo") that fit in 25 graphemes. */
9
+ export const TAG_MAX_BYTES = 100;
10
+ /** Max options a closed-vocabulary `tags` branch may declare. */
11
+ export const TAG_OPTIONS_MAX_COUNT = 50;
12
+ /** Array size accepted before cleaning, so blanks the cleaner drops do not eat the real budget. */
13
+ export const TAG_RAW_MAX_COUNT = 50;
14
+ /**
15
+ * Invisible or layout-altering code points that render as nothing but make two tags differ:
16
+ * C1 controls, soft hyphen, Mongolian vowel separator, zero-width space/non-joiner, LRM/RLM,
17
+ * bidi embeddings and isolates (spoofing), word joiner and invisible operators, BOM, and the
18
+ * Unicode "tag" block (invisible payloads). The zero-width joiner is handled by {@link STRAY_JOINER_RE}.
19
+ */
20
+ const INVISIBLE_CHARS_RE = /[\u0080-\u009F\u00AD\u180E\u200B\u200C\u200E\u200F\u202A-\u202E\u2060-\u206F\uFEFF\u{E0000}-\u{E007F}]/gu;
21
+ /** Zero-width joiner not gluing two emoji pictographs; between two (family, profession emoji) it is meaningful and kept. */
22
+ const STRAY_JOINER_RE = /(?<!\p{Extended_Pictographic}\uFE0F?)\u200D|\u200D(?!\p{Extended_Pictographic})/gu;
23
+ const LONE_SURROGATE_RE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
24
+ const UNICODE_WHITESPACE_RUN_RE = /\s+/g;
25
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
26
+ function graphemeCount(input) {
27
+ return [...graphemeSegmenter.segment(input)].length;
28
+ }
29
+ /**
30
+ * Canonical form of one tag, the single rule shared by payload validation, seed option validation and filters:
31
+ * invisible and control characters removed (an emoji-gluing joiner survives), every whitespace run (tab, newline,
32
+ * NBSP, line separator…) collapsed to one space, trimmed, Unicode NFC. Case is preserved (`Foo` ≠ `foo`).
33
+ *
34
+ * `ok: true` with `''` means the tag was blank after cleaning: callers decide whether to drop or reject it.
35
+ * Rejected, never repaired: a lone UTF-16 surrogate (not valid UTF-8) and a value over
36
+ * {@link TAG_MAX_LENGTH} characters or {@link TAG_MAX_BYTES} bytes. Pure, never throws.
37
+ */
38
+ export function normalizeTagValue(raw) {
39
+ if (LONE_SURROGATE_RE.test(raw))
40
+ return { ok: false, reason: 'ill-formed' };
41
+ const value = stripControlChars(raw)
42
+ .replaceAll(INVISIBLE_CHARS_RE, '')
43
+ .replaceAll(STRAY_JOINER_RE, '')
44
+ .replaceAll(UNICODE_WHITESPACE_RUN_RE, ' ')
45
+ .trim()
46
+ .normalize('NFC');
47
+ if (byteLength(value) > TAG_MAX_BYTES || graphemeCount(value) > TAG_MAX_LENGTH) {
48
+ return { ok: false, reason: 'too-long' };
49
+ }
50
+ return { ok: true, value };
51
+ }
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import type { FileAccept } from '../media/file-types.js';
8
8
  import type { DashboardView } from '../dashboard-layout/view-authorization.js';
9
+ import type { LocaleConfig } from './localization/localization.js';
9
10
  /** All supported field value types for a Branch. */
10
11
  export type BranchType = 'text' | 'number' | 'boolean' | 'json' | 'date' | 'richtext' | 'file' | 'tags' | 'relation' | 'repeater';
11
12
  /** Specialized configuration for a branch of type 'number'. */
@@ -57,6 +58,16 @@ export interface ActorContext {
57
58
  userId?: string;
58
59
  role?: string;
59
60
  }
61
+ /** One entry of a `tags` branch's closed vocabulary. Array order is significant (menu, kanban columns, sort). */
62
+ export interface TagOption {
63
+ /**
64
+ * Stored value and identity. Unique within the branch; compared exactly (case-sensitive, never trimmed).
65
+ * Must already be in canonical form (`normalizeTagValue(value).value === value`) and at most `TAG_MAX_LENGTH` characters.
66
+ */
67
+ value: string;
68
+ /** Presentation-only color, `#RRGGBB`. Lives in the seed only — never persisted in entries. */
69
+ color: string;
70
+ }
60
71
  /** Branch: definition of a single field. `alias` is the SQL column name. */
61
72
  export interface Branch {
62
73
  /**
@@ -83,16 +94,19 @@ export interface Branch {
83
94
  */
84
95
  format?: 'plain' | 'markdown' | 'html' | 'date' | 'datetime' | 'asset-list';
85
96
  /**
86
- * Optional cardinality for media fields:
87
- * - false/undefined: single asset (string URL)
88
- * - true: asset list (string[] URL)
97
+ * Cardinality:
98
+ * - `file`: false/undefined → single URL; true → URL list.
99
+ * - `relation`: false/undefined → FK column; true → junction table.
100
+ * - `tags`: undefined/true → multiple choice (default); false → at most one value. The value is always an array.
89
101
  */
90
102
  multiple?: boolean;
91
103
  /**
92
- * Predefined vocabulary for tag/select/multiselect fields.
93
- * Static list defined in the Seed (not persisted to the DB).
104
+ * Closed vocabulary — valid ONLY on `tags` (any other type is a fatal seed error).
105
+ * When present: non-empty, at most `TAG_OPTIONS_MAX_COUNT` entries, unique canonical values, `#RRGGBB` colors;
106
+ * written values must be one of the option values.
107
+ * Static seed metadata, never persisted in entries.
94
108
  */
95
- options?: string[];
109
+ options?: TagOption[];
96
110
  /** Required on create — generates NOT NULL in generateCreateTable. */
97
111
  requiredOnCreate?: boolean;
98
112
  /** Required on update. */
@@ -123,6 +137,15 @@ export interface Branch {
123
137
  numberOptions?: NumberFieldOptions;
124
138
  /** Advanced options for file fields. Ignored if type !== 'file'. */
125
139
  fileOptions?: FileFieldOptions;
140
+ /**
141
+ * Field-level localization. When true, the branch's existing column stores a locale dictionary
142
+ * (`{"it": "Scarpa", "en": "Shoe"}`) instead of a single value. Metadata-only: toggling it never
143
+ * emits DDL and never rewrites rows — values written before the toggle stay readable as-is.
144
+ * Valid only on top-level `text | richtext | json` branches with `plain` storage (not
145
+ * `confidential` / `restricted`); never on repeater sub-fields. Enforced by seed-validation.ts
146
+ * (Fatal 17). Default: false.
147
+ */
148
+ localized?: boolean;
126
149
  /**
127
150
  * Slug of the referenced Seed (without the `content_` prefix).
128
151
  * REQUIRED when `type === 'relation'`. Ignored otherwise.
@@ -213,6 +236,13 @@ export interface Seed {
213
236
  * Default: false.
214
237
  */
215
238
  allowDrafts?: boolean;
239
+ /**
240
+ * Enables the Trash for this content type. When true, `content_{slug}` carries a
241
+ * `deleted_at INTEGER NULL` column, `DELETE` becomes a reversible soft delete, and every
242
+ * read excludes trashed rows unless the caller opts in via `SelectOptions.trashed`.
243
+ * Default: false.
244
+ */
245
+ softDelete?: boolean;
216
246
  /**
217
247
  * Alias of the branch used as the entry's human-readable name (e.g. "title", "name", "author").
218
248
  * Required — UIs use it for display without heuristics.
@@ -235,7 +265,7 @@ export interface Seed {
235
265
  /** Comparison/matching operator applied by a filter condition. */
236
266
  export type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'contains' | 'not_contains' | 'starts_with' | 'ends_with' | 'is_empty' | 'is_not_empty' | 'in' | 'not_in' | 'has_tag' | 'has_any_tag' | 'has_all_tags';
237
267
  /** Declared value type of a filtered column, used to normalize/coerce filter values before binding. */
238
- export type FilterType = 'text' | 'number' | 'date' | 'boolean' | 'tags' | 'select' | 'system' | 'json';
268
+ export type FilterType = 'text' | 'number' | 'date' | 'boolean' | 'tags' | 'select' | 'system' | 'json' | 'repeater';
239
269
  /** A single operator + value pair applied to a FilterGroup's column. */
240
270
  export interface FilterCondition {
241
271
  /** Operator to apply. */
@@ -251,6 +281,20 @@ export interface FilterGroup {
251
281
  /** Conditions applied to this column, ANDed together. */
252
282
  conditions: FilterCondition[];
253
283
  }
284
+ /**
285
+ * Soft-delete visibility for a read.
286
+ * - 'active' (default) — only rows with `deleted_at IS NULL`
287
+ * - 'trashed' — only rows with `deleted_at IS NOT NULL` (the Trash view)
288
+ * - 'any' — no predicate (restore/purge lookups, reconciliation)
289
+ * Ignored entirely for seeds without `softDelete: true`.
290
+ */
291
+ export type TrashedMode = 'active' | 'trashed' | 'any';
292
+ /** The language a read resolves localized branches to (consumed by `buildSelectQuery`). */
293
+ export interface SelectLocale {
294
+ /** Locale that filters and ORDER BY compare in. Must match `LOCALE_CODE_RE`; the builder throws otherwise. */
295
+ readonly code: string;
296
+ readonly config: LocaleConfig;
297
+ }
254
298
  export interface SelectOptions {
255
299
  /** Filter groups. Joined by `filterLogic` (default AND); conditions within a group are always ANDed. */
256
300
  filters?: FilterGroup[];
@@ -268,6 +312,8 @@ export interface SelectOptions {
268
312
  };
269
313
  /** Filters by status. null = no status filter. */
270
314
  status?: string | null;
315
+ /** Soft-delete visibility. Defaults to 'active': omitting it can never leak a trashed row. */
316
+ trashed?: TrashedMode;
271
317
  /** Full-text search — uses FTS5 if the seed has indexable richtext/text branches. */
272
318
  search?: string;
273
319
  /** Column projection. Empty = SELECT *. */
@@ -284,6 +330,12 @@ export interface SelectOptions {
284
330
  * while keeping the join/where clauses intact for accurate counts.
285
331
  */
286
332
  isCount?: boolean;
333
+ /**
334
+ * When set, filters and ORDER BY on localized branches compare the value resolved to `locale.code`
335
+ * (requested → default → first stored translation → legacy raw value) instead of the stored JSON.
336
+ * Absent: every column is compared raw, exactly as before localization existed.
337
+ */
338
+ locale?: SelectLocale;
287
339
  }
288
340
  /** SQL string paired with its ordered parameter bindings, ready for a D1 `.bind(...)` call. */
289
341
  export interface ParameterizedQuery {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/cache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAsB,IAAI,EAAwC,MAAM,aAAa,CAAA;AAEjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAgIjD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAsCjH"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/cache.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAsB,IAAI,EAAwC,MAAM,aAAa,CAAA;AAEjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AA2IjD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CA+CjH"}
@@ -1,7 +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 { schemaForBranch } from './schema-builders.js';
4
+ import { isSingleChoiceTags, tagOptionValues } from '../seeds/tag-options.js';
5
+ import { isLocalizedBranch } from '../localization/localization.js';
6
+ import { schemaForBranch, localizedSchema } from './schema-builders.js';
5
7
  /** Max entries kept per cache map before evicting the least recently used. */
6
8
  const SEED_SCHEMA_CACHE_MAX_SIZE = 150;
7
9
  /** Cache storing compiled Zod schemas for relation-free seeds. */
@@ -54,7 +56,8 @@ function buildBranchFingerprint(branch) {
54
56
  a: branch.alias,
55
57
  t: branch.type,
56
58
  f: branch.format ?? null,
57
- m: branch.multiple === true,
59
+ m: branch.type === 'tags' ? !isSingleChoiceTags(branch) : branch.multiple === true,
60
+ o: branch.options ? tagOptionValues(branch) : null,
58
61
  rc: branch.requiredOnCreate === true,
59
62
  ru: branch.requiredOnUpdate === true,
60
63
  n: branch.numberOptions ?? null,
@@ -62,6 +65,7 @@ function buildBranchFingerprint(branch) {
62
65
  mi: branch.minItems ?? null,
63
66
  ma: branch.maxItems ?? null,
64
67
  sub: branch.fields?.map(buildBranchFingerprint) ?? null,
68
+ lo: branch.localized === true,
65
69
  };
66
70
  }
67
71
  /**
@@ -88,6 +92,9 @@ function buildCacheKey(seed, options) {
88
92
  options.allowNull ? '1' : '0',
89
93
  options.enforceRequiredFields ? '1' : '0',
90
94
  String(options.maxTextLength),
95
+ options.localeConfig
96
+ ? `${options.localeConfig.defaultLocale}>${options.localeConfig.locales.join(',')}`
97
+ : '-',
91
98
  ].join('|');
92
99
  }
93
100
  /**
@@ -125,8 +132,17 @@ export function compileSeedSchema(seed, options) {
125
132
  const requiredFlag = options.operation === 'create' ? 'requiredOnCreate' : 'requiredOnUpdate';
126
133
  const shape = {};
127
134
  for (const branch of seed.branches) {
128
- const branchSchema = schemaForBranch(branch, options);
129
- const isRequired = branch[requiredFlag] && options.enforceRequiredFields;
135
+ const branchLocaleConfig = options.localeConfig && isLocalizedBranch(branch) ? options.localeConfig : undefined;
136
+ const branchSchema = branchLocaleConfig
137
+ ? localizedSchema(branch, options, branchLocaleConfig)
138
+ : schemaForBranch(branch, options);
139
+ // A localized branch uses patch semantics on update (buildLocalizedPatch omits it entirely when no
140
+ // locale was touched), so the object schema must accept the key being absent even when required —
141
+ // detectMissingRequired() checks the *value* (default-locale presence/emptiness) with full patch
142
+ // awareness; a non-optional key here would fail the whole payload's parse and silently drop every
143
+ // other submitted field with it.
144
+ const isPatchExempt = branchLocaleConfig != null && options.operation === 'update';
145
+ const isRequired = branch[requiredFlag] && options.enforceRequiredFields && !isPatchExempt;
130
146
  shape[branch.alias] = isRequired ? branchSchema : branchSchema.optional();
131
147
  }
132
148
  const compiled = z.object(shape).strict();