@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,12 @@
1
+ /**
2
+ * Checks if a value is effectively empty (e.g. null, undefined, empty string, empty array, or empty rich text).
3
+ *
4
+ * Shared by top-level required-field detection (`index.ts`) and repeater item validation
5
+ * (`schema-builders.ts`) so both tiers apply the same emptiness rule to required fields.
6
+ *
7
+ * @param value - The value to check.
8
+ * @param branchType - The type of the branch being checked.
9
+ * @returns True if effectively empty, false otherwise.
10
+ */
11
+ export declare function isEffectivelyEmpty(value: unknown, branchType?: string): boolean;
12
+ //# sourceMappingURL=emptiness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emptiness.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/emptiness.ts"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAY/E"}
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { isRichtextEnvelopeV1 } from '../../content/richtext/richtext.js';
4
+ import { cleanString, isPlainObject } from './primitives.js';
5
+ import { isRichtextDocEmpty } from './richtext-sanitizer.js';
6
+ /**
7
+ * Checks if a value is effectively empty (e.g. null, undefined, empty string, empty array, or empty rich text).
8
+ *
9
+ * Shared by top-level required-field detection (`index.ts`) and repeater item validation
10
+ * (`schema-builders.ts`) so both tiers apply the same emptiness rule to required fields.
11
+ *
12
+ * @param value - The value to check.
13
+ * @param branchType - The type of the branch being checked.
14
+ * @returns True if effectively empty, false otherwise.
15
+ */
16
+ export function isEffectivelyEmpty(value, branchType) {
17
+ if (value === null || value === undefined)
18
+ return true;
19
+ if (typeof value === 'string')
20
+ return cleanString(value).length === 0;
21
+ if (Array.isArray(value))
22
+ return value.length === 0;
23
+ if (isPlainObject(value)) {
24
+ // Richtext envelope: delegate to the richtext emptiness check.
25
+ if (isRichtextEnvelopeV1(value))
26
+ return isRichtextDocEmpty(value.doc);
27
+ // Raw TipTap doc root: only delegate for richtext branches to avoid misclassifying generic json fields.
28
+ if (branchType === 'richtext' && value.type === 'doc')
29
+ return isRichtextDocEmpty(value);
30
+ return Object.keys(value).length === 0;
31
+ }
32
+ return false;
33
+ }
@@ -1,5 +1,8 @@
1
1
  import type { Seed } from '../types.js';
2
2
  import type { IIdGenerator } from '../../common/id-generator.js';
3
+ import { type LocaleConfig } from '../localization/localization.js';
4
+ import { isEffectivelyEmpty } from './emptiness.js';
5
+ export { isEffectivelyEmpty };
3
6
  export { resolveFileOptions } from './file-branch.js';
4
7
  /**
5
8
  * @module Engine/Validation
@@ -58,6 +61,13 @@ export interface ValidateSeedPayloadOptions {
58
61
  * Do NOT pass a concrete class — inject via the middleware / factory.
59
62
  */
60
63
  idGenerator?: IIdGenerator;
64
+ /**
65
+ * Project language configuration. When provided, branches with `localized: true` accept a plain
66
+ * value (stored under `defaultLocale`) or a locale dictionary, and validate to a `LocalizedPatch`.
67
+ * When omitted, localized branches validate exactly like their base type — the pre-localization
68
+ * contract. `requiredOnCreate` / `requiredOnUpdate` then check the default locale only.
69
+ */
70
+ localeConfig?: LocaleConfig;
61
71
  }
62
72
  /**
63
73
  * The structured result returned by the seed payload validation process.
@@ -89,6 +99,7 @@ export type ResolvedOptions = {
89
99
  enforceRequiredFields: boolean;
90
100
  maxTextLength: number;
91
101
  idGenerator: IIdGenerator | undefined;
102
+ localeConfig: LocaleConfig | undefined;
92
103
  };
93
104
  /** Set of valid content status values. */
94
105
  declare const STATUS_VALUES: readonly ["draft", "review", "published"];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAOhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,0GAA0G;IAC1G,QAAQ,EAAE,MAAM,CAAA;IAChB,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC/B;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,YAAY,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,+EAA+E;IAC/E,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,8FAA8F;IAC9F,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,4FAA4F;IAC5F,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,sEAAsE;IACtE,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,uFAAuF;IACvF,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC9B,2BAA2B,EAAE,OAAO,CAAA;IACpC,qBAAqB,EAAE,OAAO,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,YAAY,GAAG,SAAS,CAAA;CACtC,CAAA;AAKD,0CAA0C;AAC1C,QAAA,MAAM,aAAa,2CAA4C,CAAA;AA+R/D;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,GAAE,0BAA+B,GACvC,yBAAyB,CAqD3B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAE5F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAChE,OAAO,EAAuC,KAAK,YAAY,EAAE,MAAM,iCAAiC,CAAA;AAExG,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAE,kBAAkB,EAAE,CAAA;AAG7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAErD;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAA;IACb,0GAA0G;IAC1G,QAAQ,EAAE,MAAM,CAAA;IAChB,kGAAkG;IAClG,QAAQ,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC/B;;;OAGG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAA;IACrC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,YAAY,CAAA;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAA;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,+EAA+E;IAC/E,OAAO,EAAE,gBAAgB,EAAE,CAAA;IAC3B,8FAA8F;IAC9F,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,4FAA4F;IAC5F,eAAe,EAAE,MAAM,EAAE,CAAA;IACzB,sEAAsE;IACtE,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,uFAAuF;IACvF,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,OAAO,CAAA;IAClB,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC9B,2BAA2B,EAAE,OAAO,CAAA;IACpC,qBAAqB,EAAE,OAAO,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,YAAY,GAAG,SAAS,CAAA;IACrC,YAAY,EAAE,YAAY,GAAG,SAAS,CAAA;CACvC,CAAA;AAKD,0CAA0C;AAC1C,QAAA,MAAM,aAAa,2CAA4C,CAAA;AAyR/D;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,GAAE,0BAA+B,GACvC,yBAAyB,CA0D3B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAE5F"}
@@ -1,10 +1,10 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
3
  import { z } from 'zod';
4
- import { isRichtextEnvelopeV1 } from '../../content/richtext/richtext.js';
5
- import { cleanString, isPlainObject } from './primitives.js';
6
- import { isRichtextDocEmpty } from './richtext-sanitizer.js';
4
+ import { isLocalizedBranch, toLocalizedPatch } from '../localization/localization.js';
7
5
  import { compileSeedSchema } from './cache.js';
6
+ import { isEffectivelyEmpty } from './emptiness.js';
7
+ export { isEffectivelyEmpty };
8
8
  // Re-export the public file-branch symbol so the barrel surface stays complete.
9
9
  export { resolveFileOptions } from './file-branch.js';
10
10
  /** Default maximum character length allowed for text and rich text branches. */
@@ -13,31 +13,6 @@ const DEFAULT_MAX_TEXT_LENGTH = 50_000;
13
13
  const STATUS_VALUES = ['draft', 'review', 'published'];
14
14
  /** Zod schema for content status enum validation. */
15
15
  const statusSchema = z.enum(STATUS_VALUES);
16
- /**
17
- * Checks if a value is effectively empty (e.g. null, undefined, empty string, empty array, or empty rich text).
18
- *
19
- * @param value - The value to check.
20
- * @param branchType - The type of the branch being checked.
21
- * @returns True if effectively empty, false otherwise.
22
- */
23
- function isEffectivelyEmpty(value, branchType) {
24
- if (value === null || value === undefined)
25
- return true;
26
- if (typeof value === 'string')
27
- return cleanString(value).length === 0;
28
- if (Array.isArray(value))
29
- return value.length === 0;
30
- if (isPlainObject(value)) {
31
- // Richtext envelope: delegate to the richtext emptiness check.
32
- if (isRichtextEnvelopeV1(value))
33
- return isRichtextDocEmpty(value.doc);
34
- // Raw TipTap doc root: only delegate for richtext branches to avoid misclassifying generic json fields.
35
- if (branchType === 'richtext' && value.type === 'doc')
36
- return isRichtextDocEmpty(value);
37
- return Object.keys(value).length === 0;
38
- }
39
- return false;
40
- }
41
16
  /**
42
17
  * Detects missing or empty required fields based on whether the operation is create or update.
43
18
  *
@@ -57,7 +32,13 @@ function detectMissingRequired(seed, rawPayload, filtered, parsedData, parseSucc
57
32
  const isRequired = op === 'create' ? branch.requiredOnCreate : branch.requiredOnUpdate;
58
33
  if (!isRequired)
59
34
  continue;
35
+ const isLocalizedUpdate = op === 'update' && options.localeConfig != null && isLocalizedBranch(branch);
60
36
  if (!Object.hasOwn(rawPayload, branch.alias)) {
37
+ // Localized branches use patch semantics on update: the dashboard omits the branch entirely
38
+ // when no locale was touched (see buildLocalizedPatch), which is equivalent to a patch that
39
+ // does not name the default locale — leave it as stored rather than flagging it missing.
40
+ if (isLocalizedUpdate)
41
+ continue;
61
42
  missing.push(branch.alias);
62
43
  details.push({
63
44
  field: branch.alias,
@@ -67,7 +48,18 @@ function detectMissingRequired(seed, rawPayload, filtered, parsedData, parseSucc
67
48
  });
68
49
  continue;
69
50
  }
70
- const candidate = parseSucceeded ? parsedData[branch.alias] : filtered[branch.alias];
51
+ const value = parseSucceeded ? parsedData[branch.alias] : filtered[branch.alias];
52
+ let candidate = value;
53
+ if (options.localeConfig && isLocalizedBranch(branch)) {
54
+ // A localized branch is present when its default locale is; other locales are optional (brief §4).
55
+ const { defaultLocale } = options.localeConfig;
56
+ const patch = toLocalizedPatch(value, options.localeConfig);
57
+ // An update that does not name the default locale leaves it as stored — the write path merges
58
+ // (applyLocalizedPatch) — so saving one translation cannot trip requiredOnUpdate.
59
+ if (op === 'update' && !Object.hasOwn(patch, defaultLocale))
60
+ continue;
61
+ candidate = patch[defaultLocale];
62
+ }
71
63
  if (isEffectivelyEmpty(candidate, branch.type)) {
72
64
  missing.push(branch.alias);
73
65
  details.push({
@@ -241,9 +233,8 @@ function processZodIssues(seed, issues, filtered, options) {
241
233
  continue;
242
234
  }
243
235
  // detectMissingRequired() already reports top-level required-and-missing branches;
244
- // skip the redundant native invalid_type/undefined issue Zod v4 raises for the same field.
245
- if (issue.code === 'invalid_type' &&
246
- options.enforceRequiredFields &&
236
+ // skip redundant Zod issues when a required top-level field is omitted/undefined.
237
+ if (options.enforceRequiredFields &&
247
238
  issuePath.length === 1 &&
248
239
  valByPath === undefined) {
249
240
  continue;
@@ -290,6 +281,7 @@ export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
290
281
  enforceRequiredFields: options.enforceRequiredFields ?? true,
291
282
  maxTextLength: options.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH,
292
283
  idGenerator: options.idGenerator,
284
+ localeConfig: options.localeConfig,
293
285
  };
294
286
  const { filtered, unknown: preUnknown, details: preDetails } = splitUnknownAliases(seed, payload);
295
287
  const schema = compileSeedSchema(seed, resolved);
@@ -299,7 +291,11 @@ export function validateAndSanitizeSeedPayload(seed, payload, options = {}) {
299
291
  const dangerousFields = new Set();
300
292
  let data = {};
301
293
  if (parsed.success) {
302
- data = { ...parsed.data };
294
+ for (const [key, value] of Object.entries(parsed.data)) {
295
+ if (value !== undefined) {
296
+ data[key] = value;
297
+ }
298
+ }
303
299
  }
304
300
  else {
305
301
  const zodOutcome = processZodIssues(seed, parsed.error.issues, filtered, resolved);
@@ -22,6 +22,18 @@ export declare function cleanString(input: string): string;
22
22
  * @returns True if the value is a plain object, false otherwise.
23
23
  */
24
24
  export declare function isPlainObject(input: unknown): input is Record<string, unknown>;
25
+ /**
26
+ * Checks whether a JSON-like value nests deeper than `maxDepth`, without ever recursing
27
+ * past that bound. Safe to call on adversarial input BEFORE any operation (like
28
+ * `JSON.stringify`, which recurses unboundedly) that would otherwise overflow the call
29
+ * stack on a few thousand levels of nesting.
30
+ *
31
+ * @param value - The value to inspect.
32
+ * @param maxDepth - The maximum nesting depth allowed.
33
+ * @param depth - Current recursion depth (internal).
34
+ * @returns True if the value nests deeper than `maxDepth`.
35
+ */
36
+ export declare function exceedsMaxDepth(value: unknown, maxDepth: number, depth?: number): boolean;
25
37
  /**
26
38
  * Measures the real UTF-8 byte length of a string, not UTF-16 code units.
27
39
  * `.length` undercounts multi-byte chars (CJK, emoji) by up to 4x, which matters
@@ -1 +1 @@
1
- {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/primitives.ts"],"names":[],"mappings":"AAGA,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,QAAoD,CAAA;AAEpF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAID;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD"}
1
+ {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/primitives.ts"],"names":[],"mappings":"AAGA,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,QAAoD,CAAA;AAEpF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAI9E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CASpF;AAID;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD"}
@@ -33,6 +33,28 @@ export function isPlainObject(input) {
33
33
  const proto = Object.getPrototypeOf(input);
34
34
  return proto === Object.prototype || proto === null;
35
35
  }
36
+ /**
37
+ * Checks whether a JSON-like value nests deeper than `maxDepth`, without ever recursing
38
+ * past that bound. Safe to call on adversarial input BEFORE any operation (like
39
+ * `JSON.stringify`, which recurses unboundedly) that would otherwise overflow the call
40
+ * stack on a few thousand levels of nesting.
41
+ *
42
+ * @param value - The value to inspect.
43
+ * @param maxDepth - The maximum nesting depth allowed.
44
+ * @param depth - Current recursion depth (internal).
45
+ * @returns True if the value nests deeper than `maxDepth`.
46
+ */
47
+ export function exceedsMaxDepth(value, maxDepth, depth = 0) {
48
+ if (depth > maxDepth)
49
+ return true;
50
+ if (Array.isArray(value)) {
51
+ return value.some((item) => exceedsMaxDepth(item, maxDepth, depth + 1));
52
+ }
53
+ if (isPlainObject(value)) {
54
+ return Object.values(value).some((item) => exceedsMaxDepth(item, maxDepth, depth + 1));
55
+ }
56
+ return false;
57
+ }
36
58
  const byteLengthEncoder = new TextEncoder();
37
59
  /**
38
60
  * Measures the real UTF-8 byte length of a string, not UTF-16 code units.
@@ -12,7 +12,7 @@ interface RichtextSanitizeResult {
12
12
  }
13
13
  /**
14
14
  * Main entrance helper to sanitize rich text, handling both v1 envelope formats and raw JSON payloads.
15
- * String-form input is rejected (JSON-only). Byte size is fail-fast checked before the sanitizing walk.
15
+ * String-form input (JSON string or plain text) is coerced to a valid TipTap doc. Byte size is fail-fast checked before the sanitizing walk.
16
16
  *
17
17
  * @param raw - The raw rich text input.
18
18
  * @param maxBytes - Maximum allowed serialized size, checked before the walk.
@@ -26,5 +26,13 @@ export declare function sanitizeRichtext(raw: unknown, maxBytes: number): Richte
26
26
  * @returns True if empty, false otherwise.
27
27
  */
28
28
  export declare function isRichtextDocEmpty(value: unknown): boolean;
29
+ /**
30
+ * Flattens a rich text value (raw TipTap doc or v1 envelope) into plain text for indexing,
31
+ * joining block-level pieces with spaces. Depth-guarded via {@link gatherRichtextText}.
32
+ *
33
+ * @param value - The deserialized richtext value (object), as returned by `deserializeFromDb`.
34
+ * @returns The flattened text, or an empty string if `value` carries no text.
35
+ */
36
+ export declare function extractRichtextText(value: unknown): string;
29
37
  export {};
30
38
  //# sourceMappingURL=richtext-sanitizer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"richtext-sanitizer.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/richtext-sanitizer.ts"],"names":[],"mappings":"AA2BA,+DAA+D;AAC/D,UAAU,sBAAsB;IAC9B,2CAA2C;IAC3C,KAAK,EAAE,OAAO,CAAA;IACd,6DAA6D;IAC7D,SAAS,EAAE,OAAO,CAAA;IAClB,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAA;IACd,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAoID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,sBAAsB,CAgCvF;AA2BD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS1D"}
1
+ {"version":3,"file":"richtext-sanitizer.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/richtext-sanitizer.ts"],"names":[],"mappings":"AA2BA,+DAA+D;AAC/D,UAAU,sBAAsB;IAC9B,2CAA2C;IAC3C,KAAK,EAAE,OAAO,CAAA;IACd,6DAA6D;IAC7D,SAAS,EAAE,OAAO,CAAA;IAClB,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAA;IACd,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAqID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,sBAAsB,CA2CvF;AA8BD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAS1D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAM1D"}
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
3
  import { RICHTEXT_SCHEMA_VERSION, isRichtextEnvelopeV1 } from '../../content/richtext/richtext.js';
4
- import { stripControlChars, cleanString, isPlainObject, byteLength } from './primitives.js';
4
+ import { stripControlChars, cleanString, isPlainObject, byteLength, exceedsMaxDepth } from './primitives.js';
5
5
  /** Allowlisted TipTap node `type` values. Keep in sync with
6
6
  * richtext-render.ts::createRichTextHtmlExtensions. */
7
7
  const ALLOWED_RICHTEXT_NODE_TYPES = new Set([
@@ -67,7 +67,7 @@ function isProtocolAllowed(raw) {
67
67
  * @param state - The shared sanitization state tracking danger flags.
68
68
  * @returns The cleaned rich text node, or `undefined` if it must be removed.
69
69
  */
70
- function walkRichtextNode(node, state) {
70
+ function walkRichtextNode(node, state, insideAttrs = false) {
71
71
  if (state.depth > RICHTEXT_MAX_DEPTH) {
72
72
  state.dangerous = true;
73
73
  return undefined;
@@ -78,7 +78,7 @@ function walkRichtextNode(node, state) {
78
78
  state.depth++;
79
79
  const mapped = [];
80
80
  for (const child of node) {
81
- const walked = walkRichtextNode(child, state);
81
+ const walked = walkRichtextNode(child, state, insideAttrs);
82
82
  if (walked !== undefined)
83
83
  mapped.push(walked);
84
84
  }
@@ -88,8 +88,9 @@ function walkRichtextNode(node, state) {
88
88
  if (!isPlainObject(node))
89
89
  return node;
90
90
  // Node/mark type allowlist: any present `type` that isn't an allowlisted string is dropped.
91
- // Objects with no `type` key (e.g. attrs bags) are not nodes and skip this check.
92
- if ('type' in node) {
91
+ // Skipped inside `attrs` bags: a `type` key there is ordinary attribute data (e.g. TipTap's
92
+ // OrderedList `attrs.type` list-style marker), not a node/mark discriminator.
93
+ if (!insideAttrs && 'type' in node) {
93
94
  const rawType = node.type;
94
95
  const isAllowed = typeof rawType === 'string' &&
95
96
  (ALLOWED_RICHTEXT_NODE_TYPES.has(rawType) || ALLOWED_RICHTEXT_MARK_TYPES.has(rawType));
@@ -115,7 +116,7 @@ function walkRichtextNode(node, state) {
115
116
  state.dangerous = true;
116
117
  continue; // drop disallowed URL
117
118
  }
118
- const walked = walkRichtextNode(entry, state);
119
+ const walked = walkRichtextNode(entry, state, insideAttrs || key === 'attrs');
119
120
  if (walked !== undefined)
120
121
  result[key] = walked;
121
122
  }
@@ -144,7 +145,7 @@ function sanitizeRichtextJson(raw) {
144
145
  }
145
146
  /**
146
147
  * Main entrance helper to sanitize rich text, handling both v1 envelope formats and raw JSON payloads.
147
- * String-form input is rejected (JSON-only). Byte size is fail-fast checked before the sanitizing walk.
148
+ * String-form input (JSON string or plain text) is coerced to a valid TipTap doc. Byte size is fail-fast checked before the sanitizing walk.
148
149
  *
149
150
  * @param raw - The raw rich text input.
150
151
  * @param maxBytes - Maximum allowed serialized size, checked before the walk.
@@ -154,11 +155,21 @@ export function sanitizeRichtext(raw, maxBytes) {
154
155
  const envelopeMode = isRichtextEnvelopeV1(raw);
155
156
  const payload = envelopeMode ? raw.doc : raw;
156
157
  if (typeof payload === 'string') {
158
+ const rawSize = byteLength(payload);
159
+ if (rawSize > maxBytes) {
160
+ return { value: raw, dangerous: false, valid: false, size: rawSize, oversize: true };
161
+ }
157
162
  return sanitizeRichtextString(payload);
158
163
  }
159
164
  if (!isPlainObject(payload)) {
160
165
  return { value: raw, dangerous: false, valid: false, size: 0 };
161
166
  }
167
+ // Fail-fast depth pre-check BEFORE anything touches JSON.stringify: it recurses
168
+ // unboundedly and throws RangeError on a few thousand levels of nesting, well
169
+ // before the size check below or the depth-guarded walk ever run (#443).
170
+ if (exceedsMaxDepth(payload, RICHTEXT_MAX_DEPTH)) {
171
+ return { value: raw, dangerous: true, valid: false, size: 0, oversize: true };
172
+ }
162
173
  // Fail-fast DoS pre-check: size BEFORE the sanitizing walk.
163
174
  const rawSize = byteLength(JSON.stringify(payload));
164
175
  if (rawSize > maxBytes) {
@@ -203,6 +214,9 @@ function gatherRichtextText(node, sink, depth = 0) {
203
214
  if (isPlainObject(node.attrs) && typeof node.attrs.latex === 'string') {
204
215
  sink.push(cleanString(node.attrs.latex));
205
216
  }
217
+ if (node.type === 'image' || node.type === 'horizontalRule' || node.type === 'table') {
218
+ sink.push(node.type);
219
+ }
206
220
  if (Array.isArray(node.content)) {
207
221
  for (const child of node.content)
208
222
  gatherRichtextText(child, sink, depth + 1);
@@ -226,3 +240,18 @@ export function isRichtextDocEmpty(value) {
226
240
  gatherRichtextText(value, sink);
227
241
  return sink.join('').trim().length === 0;
228
242
  }
243
+ /**
244
+ * Flattens a rich text value (raw TipTap doc or v1 envelope) into plain text for indexing,
245
+ * joining block-level pieces with spaces. Depth-guarded via {@link gatherRichtextText}.
246
+ *
247
+ * @param value - The deserialized richtext value (object), as returned by `deserializeFromDb`.
248
+ * @returns The flattened text, or an empty string if `value` carries no text.
249
+ */
250
+ export function extractRichtextText(value) {
251
+ if (!isPlainObject(value))
252
+ return '';
253
+ const doc = isRichtextEnvelopeV1(value) ? value.doc : value;
254
+ const sink = [];
255
+ gatherRichtextText(doc, sink);
256
+ return sink.join(' ').trim();
257
+ }
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import type { Branch } from '../types.js';
3
3
  import type { ResolvedOptions } from './index.js';
4
+ import { type LocaleConfig } from '../localization/localization.js';
4
5
  /**
5
6
  * Returns the appropriate Zod schema builder for a given branch based on its type.
6
7
  *
@@ -9,4 +10,19 @@ import type { ResolvedOptions } from './index.js';
9
10
  * @returns The compiled schema.
10
11
  */
11
12
  export declare function schemaForBranch(branch: Branch, options: ResolvedOptions): z.ZodTypeAny;
13
+ /**
14
+ * Compiles the schema of a localized branch. Top level only: `compileSeedSchema` is the sole caller, so
15
+ * repeater sub-fields (validated through `schemaForBranch`) can never reach it.
16
+ *
17
+ * Accepts a plain value (→ default locale) or a locale dictionary, normalises it with
18
+ * {@link toLocalizedPatch}, and validates every non-null locale value with the branch's base-type schema.
19
+ * Output is a {@link LocalizedPatch}. Failures are reported at `<alias>.<locale>` for dictionary input
20
+ * and at `<alias>` for a plain value, i.e. always at a path that exists in the client's payload.
21
+ *
22
+ * @param branch - A branch for which `isLocalizedBranch(branch)` is true.
23
+ * @param options - The resolved validation options.
24
+ * @param config - The project language configuration.
25
+ * @returns The compiled localized schema.
26
+ */
27
+ export declare function localizedSchema(branch: Branch, options: ResolvedOptions, config: LocaleConfig): z.ZodTypeAny;
12
28
  //# sourceMappingURL=schema-builders.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-builders.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/schema-builders.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,aAAa,CAAA;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmajD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,UAAU,CAMtF"}
1
+ {"version":3,"file":"schema-builders.d.ts","sourceRoot":"","sources":["../../../src/engine/validation/schema-builders.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,aAAa,CAAA;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,OAAO,EAAgD,KAAK,YAAY,EAAuB,MAAM,iCAAiC,CAAA;AAydtI;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAAC,UAAU,CAMtF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,GAAG,CAAC,CAAC,UAAU,CAiC5G"}
@@ -2,7 +2,11 @@
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
3
  import { z } from 'zod';
4
4
  import { extensionFromUrl, isExtensionAccepted } from '../../media/file-types.js';
5
- import { cleanString, stripControlChars, byteLength, isPlainObject } from './primitives.js';
5
+ import { isLocalizedWriteDictionary, toLocalizedPatch } from '../localization/localization.js';
6
+ import { cleanString, stripControlChars, byteLength, isPlainObject, exceedsMaxDepth } from './primitives.js';
7
+ import { isEffectivelyEmpty } from './emptiness.js';
8
+ import { isSingleChoiceTags, tagOptionValues } from '../seeds/tag-options.js';
9
+ import { normalizeTagValue, TAG_MAX_COUNT, TAG_MAX_LENGTH, TAG_RAW_MAX_COUNT } from '../seeds/tag-value.js';
6
10
  import { sanitizeRichtext } from './richtext-sanitizer.js';
7
11
  import { resolveFileOptions, isAssetListBranch, collectAssetListItems, extractFileCandidate } from './file-branch.js';
8
12
  /**
@@ -180,8 +184,6 @@ function dateSchema(allowNull) {
180
184
  }
181
185
  /** Max nesting depth accepted when walking a `json` branch value. */
182
186
  const JSON_MAX_DEPTH = 50;
183
- /** Max number of items accepted in a `tags` branch array. */
184
- const MAX_TAGS_COUNT = 100;
185
187
  /**
186
188
  * Recursively strips control characters from string leaves while enforcing a
187
189
  * depth cap, mirroring the richtext sanitizer's DoS guards for arbitrary JSON.
@@ -229,6 +231,13 @@ function jsonSchema(options, allowNull) {
229
231
  const base = z
230
232
  .union([z.record(z.string(), z.unknown()), z.array(z.unknown())])
231
233
  .transform((val, ctx) => {
234
+ // Fail-fast depth pre-check BEFORE any JSON.stringify: deep nesting stack-
235
+ // overflows JSON.stringify (recursive) before the size check or the
236
+ // depth-guarded sanitizing walk below ever run (#443).
237
+ if (exceedsMaxDepth(val, JSON_MAX_DEPTH)) {
238
+ ctx.addIssue({ code: 'custom', message: `Expected json(maxDepth:${JSON_MAX_DEPTH})` });
239
+ return z.NEVER;
240
+ }
232
241
  // Fail-fast size check before the sanitizing walk, same order as richtext.
233
242
  const rawSize = byteLength(JSON.stringify(val));
234
243
  if (rawSize > options.maxTextLength) {
@@ -245,23 +254,59 @@ function jsonSchema(options, allowNull) {
245
254
  return withEmptyPreprocessing(base, allowNull);
246
255
  }
247
256
  /**
248
- * Compiles a Zod validation schema for a `tags` branch, constraining it to a
249
- * bounded array of cleaned strings (fixes #183: previously any array/object
250
- * shape passed, breaking callers like the kanban swap that cast to `string[]`).
257
+ * Compiles a Zod validation schema for a `tags` branch: a small array of short labels.
258
+ * Free values go through {@link normalizeTagValue} (invisible/control characters removed, whitespace
259
+ * collapsed, NFC) and may not exceed {@link TAG_MAX_LENGTH} characters; at most {@link TAG_MAX_COUNT} survive.
260
+ * With `options` the array is a closed vocabulary (exact, case-sensitive match after NFC, no trimming);
261
+ * without, values are normalized and those that clean to nothing are dropped. Duplicates are rejected, never merged,
262
+ * and a single-choice branch (`multiple: false`) accepts at most one value. The first violated
263
+ * rule emits one issue for the whole array.
251
264
  *
252
- * @param options - The resolved validation options.
265
+ * @param branch - The tags branch definition.
253
266
  * @param allowNull - Whether null values are allowed.
254
267
  * @returns The compiled tags schema.
255
268
  */
256
- function tagsSchema(options, allowNull) {
257
- const tagSchema = z
258
- .string()
259
- .transform((value) => cleanString(value))
260
- .refine((value) => byteLength(value) <= options.maxTextLength, {
261
- message: `Expected string(max:${options.maxTextLength})`,
262
- });
263
- const base = z.array(tagSchema).max(MAX_TAGS_COUNT, {
264
- message: `Expected tags(max:${MAX_TAGS_COUNT})`,
269
+ function tagsSchema(branch, allowNull) {
270
+ const allowedValues = tagOptionValues(branch);
271
+ const isClosedVocabulary = allowedValues.length > 0;
272
+ const base = z
273
+ .array(z.string())
274
+ // Coarse guard so a huge payload is not scanned; the real cap applies after blanks are dropped.
275
+ .max(TAG_RAW_MAX_COUNT, { message: `Expected tags(max:${TAG_MAX_COUNT})` })
276
+ .transform((tags, ctx) => {
277
+ const values = [];
278
+ for (const tag of tags) {
279
+ if (isClosedVocabulary) {
280
+ // Exact match: only NFC is applied, so a padded or invisible-char variant of an option is rejected.
281
+ values.push(tag.normalize('NFC'));
282
+ continue;
283
+ }
284
+ const normalized = normalizeTagValue(tag);
285
+ if (!normalized.ok) {
286
+ const expected = normalized.reason === 'too-long' ? `tags(item-max:${TAG_MAX_LENGTH})` : 'tags(valid-text)';
287
+ ctx.addIssue({ code: 'custom', message: `Expected ${expected}` });
288
+ return z.NEVER;
289
+ }
290
+ if (normalized.value.length > 0)
291
+ values.push(normalized.value);
292
+ }
293
+ if (isClosedVocabulary && values.some((tag) => !allowedValues.includes(tag))) {
294
+ ctx.addIssue({ code: 'custom', message: `Expected tags(one-of:${allowedValues.join('|')})` });
295
+ return z.NEVER;
296
+ }
297
+ if (values.length > TAG_MAX_COUNT) {
298
+ ctx.addIssue({ code: 'custom', message: `Expected tags(max:${TAG_MAX_COUNT})` });
299
+ return z.NEVER;
300
+ }
301
+ if (new Set(values).size !== values.length) {
302
+ ctx.addIssue({ code: 'custom', message: 'Expected tags(unique)' });
303
+ return z.NEVER;
304
+ }
305
+ if (isSingleChoiceTags(branch) && values.length > 1) {
306
+ ctx.addIssue({ code: 'custom', message: 'Expected tags(max:1)' });
307
+ return z.NEVER;
308
+ }
309
+ return values;
265
310
  });
266
311
  return withEmptyPreprocessing(base, allowNull);
267
312
  }
@@ -363,14 +408,27 @@ function repeaterSchema(branch, options) {
363
408
  const requiredFlag = options.operation === 'create' ? 'requiredOnCreate' : 'requiredOnUpdate';
364
409
  const subBranches = (branch.fields ?? []).filter((sub) => !REPEATER_DISALLOWED_SUBTYPES.has(sub.type));
365
410
  const shape = {};
411
+ const requiredSubs = [];
366
412
  for (const sub of subBranches) {
367
413
  const subSchema = schemaForBranch(sub, options);
368
414
  const isRequired = options.enforceRequiredFields && sub[requiredFlag];
369
415
  shape[sub.alias] = isRequired ? subSchema : subSchema.optional();
416
+ if (isRequired)
417
+ requiredSubs.push(sub);
370
418
  }
371
419
  // z.object() strips unknown keys by default — old item shapes from a renamed/
372
420
  // removed sub-field are dropped rather than rejected (sprint 10 §5.1).
373
- const itemSchema = z.object(shape);
421
+ // Making the key non-optional above only guards presence: leaf schemas fold ""
422
+ // to `undefined` (withEmptyPreprocessing) or accept whitespace-only text, so a
423
+ // required sub-field still needs the same value-level emptiness check top-level
424
+ // required branches get from detectMissingRequired (issue #444).
425
+ const itemSchema = z.object(shape).superRefine((item, ctx) => {
426
+ for (const sub of requiredSubs) {
427
+ if (isEffectivelyEmpty(item[sub.alias], sub.type)) {
428
+ ctx.addIssue({ code: 'custom', path: [sub.alias], message: 'Expected non-empty(required)' });
429
+ }
430
+ }
431
+ });
374
432
  let arraySchema = z.array(itemSchema);
375
433
  if (Number.isInteger(branch.minItems) && branch.minItems >= 0) {
376
434
  arraySchema = arraySchema.min(branch.minItems, {
@@ -392,7 +450,7 @@ const BRANCH_SCHEMA_BUILDERS = {
392
450
  boolean: (_branch, options) => booleanSchema(options.allowNull),
393
451
  date: (_branch, options) => dateSchema(options.allowNull),
394
452
  json: (_branch, options) => jsonSchema(options, options.allowNull),
395
- tags: (_branch, options) => tagsSchema(options, options.allowNull),
453
+ tags: (branch, options) => tagsSchema(branch, options.allowNull),
396
454
  file: (branch, options) => fileSchema(branch, options.allowNull),
397
455
  relation: (branch, options) => relationSchema(branch, options),
398
456
  repeater: (branch, options) => repeaterSchema(branch, options),
@@ -411,3 +469,52 @@ export function schemaForBranch(branch, options) {
411
469
  }
412
470
  return builder(branch, options);
413
471
  }
472
+ /**
473
+ * Compiles the schema of a localized branch. Top level only: `compileSeedSchema` is the sole caller, so
474
+ * repeater sub-fields (validated through `schemaForBranch`) can never reach it.
475
+ *
476
+ * Accepts a plain value (→ default locale) or a locale dictionary, normalises it with
477
+ * {@link toLocalizedPatch}, and validates every non-null locale value with the branch's base-type schema.
478
+ * Output is a {@link LocalizedPatch}. Failures are reported at `<alias>.<locale>` for dictionary input
479
+ * and at `<alias>` for a plain value, i.e. always at a path that exists in the client's payload.
480
+ *
481
+ * @param branch - A branch for which `isLocalizedBranch(branch)` is true.
482
+ * @param options - The resolved validation options.
483
+ * @param config - The project language configuration.
484
+ * @returns The compiled localized schema.
485
+ */
486
+ export function localizedSchema(branch, options, config) {
487
+ const valueSchema = schemaForBranch({ ...branch, localized: false }, { ...options, allowNull: false });
488
+ return z.any().transform((raw, ctx) => {
489
+ if (raw === null) {
490
+ if (!options.allowNull)
491
+ ctx.addIssue({ code: 'custom', message: 'Expected localized-value' });
492
+ return null;
493
+ }
494
+ const isDictionaryInput = isLocalizedWriteDictionary(raw, config);
495
+ const patch = toLocalizedPatch(raw, config);
496
+ const validated = {};
497
+ for (const [locale, localeValue] of Object.entries(patch)) {
498
+ if (localeValue === null) {
499
+ validated[locale] = null;
500
+ continue;
501
+ }
502
+ const parsed = valueSchema.safeParse(localeValue);
503
+ if (parsed.success) {
504
+ validated[locale] = parsed.data;
505
+ continue;
506
+ }
507
+ for (const issue of parsed.error.issues) {
508
+ const innerPath = issue.path;
509
+ ctx.addIssue({
510
+ code: 'custom',
511
+ path: isDictionaryInput ? [locale, ...innerPath] : innerPath,
512
+ // Keep the "Expected <type>" convention expectedFromIssue() relies on.
513
+ message: issue.code === 'invalid_type' ? `Expected ${issue.expected}` : issue.message,
514
+ params: issue.params,
515
+ });
516
+ }
517
+ }
518
+ return validated;
519
+ });
520
+ }