@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,46 @@
1
+ /**
2
+ * @module engine/relation-audit
3
+ * Finds relation branches whose physical storage no longer matches the deployed definition.
4
+ *
5
+ * Releases before the additive relation gate could persist a changed `multiple`, `targetSeed` or
6
+ * `onDelete` without migrating the FK column, the `rel_<slug>_<alias>` junction table or the FK
7
+ * policy. The gate prevents new mismatches; this audit identifies databases that already have one.
8
+ *
9
+ * Read-only by contract: only `PRAGMA` and `SELECT COUNT(*)` statements are issued, through the
10
+ * injected executor. Nothing is repaired and no row is ever touched.
11
+ */
12
+ import type { Seed } from '../types.js';
13
+ import { type SchemaQueryExecutor } from './introspection.js';
14
+ /** What is wrong with the storage of one relation branch. */
15
+ export type RelationStorageIssue =
16
+ /** Definition says single (or multiple) but the data lives in the other shape. */
17
+ 'wrong_storage'
18
+ /** Neither the FK column nor the junction table exists. */
19
+ | 'missing_storage'
20
+ /** The FK points at a different table than `targetSeed`. */
21
+ | 'fk_target_mismatch'
22
+ /** The FK `ON DELETE` rule differs from the effective `onDelete`. */
23
+ | 'fk_on_delete_mismatch';
24
+ export interface RelationStorageFinding {
25
+ seed: string;
26
+ alias: string;
27
+ issue: RelationStorageIssue;
28
+ /** What the deployed definition requires, e.g. `junction rel_posts_tags`. */
29
+ expected: string;
30
+ /** What the database holds. */
31
+ actual: string;
32
+ /**
33
+ * Rows or values sitting in storage the definition no longer points at. Always 0 for
34
+ * `missing_storage` and the FK findings: those carry no stranded values by themselves.
35
+ */
36
+ strandedRows: number;
37
+ }
38
+ /**
39
+ * Compares every relation branch of the given (active) seeds against the physical tables.
40
+ *
41
+ * Seeds whose `content_<slug>` table is missing are skipped: that is plain schema drift and is
42
+ * reported by the schema diff, not a relation-storage mismatch. Findings are ordered by seed, then
43
+ * branch order, so the report is deterministic.
44
+ */
45
+ export declare function auditRelationStorage(executor: SchemaQueryExecutor, seeds: Seed[]): Promise<RelationStorageFinding[]>;
46
+ //# sourceMappingURL=relation-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation-audit.d.ts","sourceRoot":"","sources":["../../../src/engine/introspection/relation-audit.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAU,IAAI,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,EAIL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAA;AAE3B,6DAA6D;AAC7D,MAAM,MAAM,oBAAoB;AAC9B,kFAAkF;AAChF,eAAe;AACjB,2DAA2D;GACzD,iBAAiB;AACnB,4DAA4D;GAC1D,oBAAoB;AACtB,qEAAqE;GACnE,uBAAuB,CAAA;AAE3B,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,oBAAoB,CAAA;IAC3B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAA;IAChB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;CACrB;AAkGD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,IAAI,EAAE,GACZ,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAiBnC"}
@@ -0,0 +1,103 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ import { junctionTableName } from '../ddl/ddl.js';
4
+ import { assertSafeIdentifier, introspectTable, } from './introspection.js';
5
+ /** Default `ON DELETE` rule, per storage shape (mirrors `engine/ddl`). */
6
+ function effectiveOnDelete(branch) {
7
+ return (branch.onDelete ?? (branch.multiple === true ? 'CASCADE' : 'SET NULL')).toUpperCase();
8
+ }
9
+ async function countRows(executor, sql) {
10
+ const rows = await executor.all(sql);
11
+ return Number(rows[0]?.n ?? 0);
12
+ }
13
+ /** Checks the FK that carries the reference: the `alias` column, or the junction's `target_id`. */
14
+ function fkFindings(seed, branch, fk, location) {
15
+ const expectedTarget = `content_${branch.targetSeed}`;
16
+ const expectedRule = effectiveOnDelete(branch);
17
+ const base = { seed: seed.slug, alias: branch.alias, strandedRows: 0 };
18
+ const expected = `${location} -> ${expectedTarget}(id) ON DELETE ${expectedRule}`;
19
+ if (!fk) {
20
+ return [{ ...base, issue: 'fk_target_mismatch', expected, actual: `${location} has no foreign key` }];
21
+ }
22
+ const actual = `${location} -> ${fk.targetTable}(${fk.targetColumn}) ON DELETE ${fk.onDelete}`;
23
+ const findings = [];
24
+ if (fk.targetTable !== expectedTarget)
25
+ findings.push({ ...base, issue: 'fk_target_mismatch', expected, actual });
26
+ if (fk.onDelete !== expectedRule)
27
+ findings.push({ ...base, issue: 'fk_on_delete_mismatch', expected, actual });
28
+ return findings;
29
+ }
30
+ async function auditBranch(executor, seed, branch, contentColumns, contentForeignKeys) {
31
+ const isMultiple = branch.multiple === true;
32
+ const junction = junctionTableName(seed.slug, branch.alias);
33
+ const draftJunction = `${junction}_drafts`;
34
+ const alias = assertSafeIdentifier(branch.alias);
35
+ const base = { seed: seed.slug, alias: branch.alias };
36
+ const liveJunction = await introspectTable(executor, junction);
37
+ const liveDraftJunction = await introspectTable(executor, draftJunction);
38
+ const hasColumn = contentColumns.has(alias);
39
+ if (isMultiple) {
40
+ if (hasColumn) {
41
+ const strandedRows = await countRows(executor, `SELECT COUNT(*) AS n FROM content_${seed.slug} WHERE "${alias}" IS NOT NULL`);
42
+ return [{
43
+ ...base,
44
+ issue: 'wrong_storage',
45
+ expected: `junction ${junction}`,
46
+ actual: `${liveJunction.exists ? `junction ${junction} and ` : ''}column content_${seed.slug}.${alias}`,
47
+ strandedRows,
48
+ }];
49
+ }
50
+ if (!liveJunction.exists) {
51
+ return [{ ...base, issue: 'missing_storage', expected: `junction ${junction}`, actual: 'no junction table', strandedRows: 0 }];
52
+ }
53
+ const findings = fkFindings(seed, branch, liveJunction.foreignKeys.find(fk => fk.column === 'target_id'), `${junction}.target_id`);
54
+ if (seed.allowDrafts && !liveDraftJunction.exists) {
55
+ findings.push({ ...base, issue: 'missing_storage', expected: `drafts junction ${draftJunction}`, actual: 'no drafts junction table', strandedRows: 0 });
56
+ }
57
+ return findings;
58
+ }
59
+ // Single relation: the value lives in a column; any junction left behind holds stranded links.
60
+ if (liveJunction.exists || liveDraftJunction.exists) {
61
+ let strandedRows = 0;
62
+ if (liveJunction.exists)
63
+ strandedRows += await countRows(executor, `SELECT COUNT(*) AS n FROM ${junction}`);
64
+ if (liveDraftJunction.exists)
65
+ strandedRows += await countRows(executor, `SELECT COUNT(*) AS n FROM ${draftJunction}`);
66
+ return [{
67
+ ...base,
68
+ issue: 'wrong_storage',
69
+ expected: `column content_${seed.slug}.${alias}`,
70
+ actual: `${hasColumn ? `column content_${seed.slug}.${alias} and ` : ''}junction ${junction}`,
71
+ strandedRows,
72
+ }];
73
+ }
74
+ if (!hasColumn) {
75
+ return [{ ...base, issue: 'missing_storage', expected: `column content_${seed.slug}.${alias}`, actual: 'no column', strandedRows: 0 }];
76
+ }
77
+ return fkFindings(seed, branch, contentForeignKeys.get(alias), `content_${seed.slug}.${alias}`);
78
+ }
79
+ /**
80
+ * Compares every relation branch of the given (active) seeds against the physical tables.
81
+ *
82
+ * Seeds whose `content_<slug>` table is missing are skipped: that is plain schema drift and is
83
+ * reported by the schema diff, not a relation-storage mismatch. Findings are ordered by seed, then
84
+ * branch order, so the report is deterministic.
85
+ */
86
+ export async function auditRelationStorage(executor, seeds) {
87
+ const findings = [];
88
+ const ordered = [...seeds].sort((a, b) => a.slug.localeCompare(b.slug));
89
+ // Sequential on purpose: see `introspectSchema`.
90
+ for (const seed of ordered) {
91
+ const content = await introspectTable(executor, `content_${assertSafeIdentifier(seed.slug)}`);
92
+ if (!content.exists)
93
+ continue;
94
+ const columns = new Set(content.columns.map(column => column.name));
95
+ const foreignKeys = new Map(content.foreignKeys.map(fk => [fk.column, fk]));
96
+ for (const branch of seed.branches) {
97
+ if (branch.type !== 'relation' || !branch.targetSeed)
98
+ continue;
99
+ findings.push(...await auditBranch(executor, seed, branch, columns, foreignKeys));
100
+ }
101
+ }
102
+ return findings;
103
+ }
@@ -0,0 +1,74 @@
1
+ import type { Branch, Seed } from '../types.js';
2
+ /**
3
+ * Bumped ONLY when the projection below or the canonical byte format changes — i.e. when
4
+ * fingerprints computed by two builds are no longer comparable. It is carried in the fingerprint
5
+ * string itself so a version skew reads as a mismatch instead of as an accidental match.
6
+ */
7
+ export declare const SCHEMA_FINGERPRINT_VERSION = 2;
8
+ /** The response-shape-bearing subset of a `Branch`. */
9
+ export interface ContractBranch {
10
+ alias: string;
11
+ type: Branch['type'];
12
+ format?: Branch['format'];
13
+ multiple?: boolean;
14
+ options?: string[];
15
+ requiredOnCreate: boolean;
16
+ requiredOnUpdate: boolean;
17
+ targetSeed?: string;
18
+ /** From `resolvePolicies`, so an explicitly-written default hashes like an omitted one. */
19
+ visibility: 'full' | 'masked' | 'hidden';
20
+ publicRead: boolean;
21
+ publicEdit: boolean;
22
+ minItems?: number;
23
+ maxItems?: number;
24
+ /** Present only when true: the dictionary shape is part of the public contract (`?lang=all`, writes). */
25
+ localized?: boolean;
26
+ /** Repeater sub-fields, same projection, recursively. */
27
+ fields?: ContractBranch[];
28
+ }
29
+ /** The response-shape-bearing subset of a `Seed`. */
30
+ export interface ContractSeed {
31
+ slug: string;
32
+ displayNameAlias: string;
33
+ allowDrafts: boolean;
34
+ allowPublicRead: boolean;
35
+ allowPublicPost: boolean;
36
+ allowPublicEdit: boolean;
37
+ branches: ContractBranch[];
38
+ }
39
+ /** The exact value that gets canonicalized and hashed. */
40
+ export interface SchemaContract {
41
+ fingerprintVersion: number;
42
+ seeds: ContractSeed[];
43
+ }
44
+ /**
45
+ * Reduces seeds to what a response shape depends on.
46
+ *
47
+ * Deliberately EXCLUDED, and each for a reason a future reader must not "fix":
48
+ * - `id` (`br_XX`) — a stable internal handle; it never appears in a payload, and minting order
49
+ * differs between an environment built by migration and one grown through the dashboard.
50
+ * - `label`, `labelPlural`, `hint`, `dashboard`, `layout` — presentation, never response shape.
51
+ * - `retentionDays`, `onDelete`, `policies.search|filter|sort`, `numberOptions`, `fileOptions`,
52
+ * `policies.classification|privacy` — behaviour and storage concerns that leave the response
53
+ * shape identical. (`privacy` DOES change a stored value's encoding, but the API type stays
54
+ * `string`; drift there is a data concern, not a type concern.)
55
+ * - option `color` — presentation only; a color edit cannot change a response byte.
56
+ * - anything physical (column order, index names) — see the VETO Audit.
57
+ *
58
+ * Seeds are sorted by slug; BRANCH ORDER IS PRESERVED, because it is the declared field order the
59
+ * generated types and the dashboard form both follow.
60
+ */
61
+ export declare function projectSchemaContract(seeds: Seed[]): SchemaContract;
62
+ /**
63
+ * `v{VERSION}:{32 lowercase hex}` — SHA-256 over the canonical JSON of the contract projection,
64
+ * truncated to 128 bits.
65
+ *
66
+ * Truncation is deliberate: this value travels on EVERY public response as `X-Schema-Revision`,
67
+ * and 128 bits of a SHA-256 digest carries no realistic collision risk for a value space of a few
68
+ * hundred schema revisions. It is a revision marker, never a security token.
69
+ *
70
+ * Web Crypto only — the same API the Worker and Node ≥ 18 both expose, as `webhook-crypto.ts`
71
+ * already relies on. `@beechcms/core` gains no dependency.
72
+ */
73
+ export declare function computeSchemaFingerprint(seeds: Seed[]): Promise<string>;
74
+ //# sourceMappingURL=schema-fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-fingerprint.d.ts","sourceRoot":"","sources":["../../../src/engine/introspection/schema-fingerprint.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAG/C;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,gBAAgB,EAAE,OAAO,CAAA;IACzB,gBAAgB,EAAE,OAAO,CAAA;IACzB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAA;IACxC,UAAU,EAAE,OAAO,CAAA;IACnB,UAAU,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yGAAyG;IACzG,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,yDAAyD;IACzD,MAAM,CAAC,EAAE,cAAc,EAAE,CAAA;CAC1B;AAED,qDAAqD;AACrD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;IACpB,eAAe,EAAE,OAAO,CAAA;IACxB,eAAe,EAAE,OAAO,CAAA;IACxB,eAAe,EAAE,OAAO,CAAA;IACxB,QAAQ,EAAE,cAAc,EAAE,CAAA;CAC3B;AAED,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,kBAAkB,EAAE,MAAM,CAAA;IAC1B,KAAK,EAAE,YAAY,EAAE,CAAA;CACtB;AA4BD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,cAAc,CAenE;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAI7E"}
@@ -0,0 +1,102 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ /**
4
+ * @module engine/schema-fingerprint
5
+ * A deterministic name for "the shape this API answers in".
6
+ *
7
+ * Computed from live seed definitions (`introspectSeedDefinitions`), embedded in generated client
8
+ * types at build time (sprint 3), returned on every public response as `X-Schema-Revision`
9
+ * (sprint 4), and compared by the client at request time (sprint 5). Both ends MUST compute it
10
+ * with this function — two implementations would be free to disagree, and a drift detector whose
11
+ * ends disagree is worse than none.
12
+ *
13
+ * What is hashed is a CONTRACT PROJECTION, not the whole seed definition: labels, hints, dashboard
14
+ * icons and layout cannot change a single response byte, and hashing them would fire a hard
15
+ * `BeechProblem` in someone else's production client over a typo fix.
16
+ */
17
+ import { canonicalStringify } from '../../common/canonical-json.js';
18
+ import { resolvePolicies } from '../privacy/policies.js';
19
+ import { isSingleChoiceTags, tagOptionValues } from '../seeds/tag-options.js';
20
+ /**
21
+ * Bumped ONLY when the projection below or the canonical byte format changes — i.e. when
22
+ * fingerprints computed by two builds are no longer comparable. It is carried in the fingerprint
23
+ * string itself so a version skew reads as a mismatch instead of as an accidental match.
24
+ */
25
+ export const SCHEMA_FINGERPRINT_VERSION = 2;
26
+ function projectBranch(branch) {
27
+ const policies = resolvePolicies(branch);
28
+ return {
29
+ alias: branch.alias,
30
+ type: branch.type,
31
+ ...(branch.format !== undefined ? { format: branch.format } : {}),
32
+ // `tags` always carry the resolved mode, so an omitted `multiple` hashes like `true`.
33
+ ...(branch.type === 'tags'
34
+ ? { multiple: !isSingleChoiceTags(branch) }
35
+ : branch.multiple !== undefined ? { multiple: branch.multiple } : {}),
36
+ // Option order is the contract: it is the order a select renders and a union type lists.
37
+ ...(branch.options !== undefined ? { options: tagOptionValues(branch) } : {}),
38
+ requiredOnCreate: branch.requiredOnCreate ?? false,
39
+ requiredOnUpdate: branch.requiredOnUpdate ?? false,
40
+ ...(branch.targetSeed !== undefined ? { targetSeed: branch.targetSeed } : {}),
41
+ visibility: policies.visibility,
42
+ publicRead: policies.public,
43
+ publicEdit: policies.publicEdit,
44
+ ...(branch.minItems !== undefined ? { minItems: branch.minItems } : {}),
45
+ ...(branch.maxItems !== undefined ? { maxItems: branch.maxItems } : {}),
46
+ // Spread only when true so every schema without localized branches keeps its fingerprint.
47
+ ...(branch.localized === true ? { localized: true } : {}),
48
+ ...(branch.fields ? { fields: branch.fields.map(projectBranch) } : {}),
49
+ };
50
+ }
51
+ /**
52
+ * Reduces seeds to what a response shape depends on.
53
+ *
54
+ * Deliberately EXCLUDED, and each for a reason a future reader must not "fix":
55
+ * - `id` (`br_XX`) — a stable internal handle; it never appears in a payload, and minting order
56
+ * differs between an environment built by migration and one grown through the dashboard.
57
+ * - `label`, `labelPlural`, `hint`, `dashboard`, `layout` — presentation, never response shape.
58
+ * - `retentionDays`, `onDelete`, `policies.search|filter|sort`, `numberOptions`, `fileOptions`,
59
+ * `policies.classification|privacy` — behaviour and storage concerns that leave the response
60
+ * shape identical. (`privacy` DOES change a stored value's encoding, but the API type stays
61
+ * `string`; drift there is a data concern, not a type concern.)
62
+ * - option `color` — presentation only; a color edit cannot change a response byte.
63
+ * - anything physical (column order, index names) — see the VETO Audit.
64
+ *
65
+ * Seeds are sorted by slug; BRANCH ORDER IS PRESERVED, because it is the declared field order the
66
+ * generated types and the dashboard form both follow.
67
+ */
68
+ export function projectSchemaContract(seeds) {
69
+ return {
70
+ fingerprintVersion: SCHEMA_FINGERPRINT_VERSION,
71
+ seeds: [...seeds]
72
+ .sort((a, b) => a.slug.localeCompare(b.slug))
73
+ .map(seed => ({
74
+ slug: seed.slug,
75
+ displayNameAlias: seed.displayNameAlias,
76
+ allowDrafts: seed.allowDrafts ?? false,
77
+ allowPublicRead: seed.allowPublicRead ?? false,
78
+ allowPublicPost: seed.allowPublicPost ?? false,
79
+ allowPublicEdit: seed.allowPublicEdit ?? false,
80
+ branches: seed.branches.map(projectBranch),
81
+ })),
82
+ };
83
+ }
84
+ function toHex(buffer) {
85
+ return Array.from(new Uint8Array(buffer), byte => byte.toString(16).padStart(2, '0')).join('');
86
+ }
87
+ /**
88
+ * `v{VERSION}:{32 lowercase hex}` — SHA-256 over the canonical JSON of the contract projection,
89
+ * truncated to 128 bits.
90
+ *
91
+ * Truncation is deliberate: this value travels on EVERY public response as `X-Schema-Revision`,
92
+ * and 128 bits of a SHA-256 digest carries no realistic collision risk for a value space of a few
93
+ * hundred schema revisions. It is a revision marker, never a security token.
94
+ *
95
+ * Web Crypto only — the same API the Worker and Node ≥ 18 both expose, as `webhook-crypto.ts`
96
+ * already relies on. `@beechcms/core` gains no dependency.
97
+ */
98
+ export async function computeSchemaFingerprint(seeds) {
99
+ const bytes = new TextEncoder().encode(canonicalStringify(projectSchemaContract(seeds)));
100
+ const digest = await crypto.subtle.digest('SHA-256', bytes);
101
+ return `v${SCHEMA_FINGERPRINT_VERSION}:${toHex(digest).slice(0, 32)}`;
102
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @module engine/introspection
3
+ * Reads live schema state out of D1 through an INJECTED executor.
4
+ *
5
+ * Executor-agnostic on purpose: the CLI reaches D1 through a synchronous shell/SQLite path
6
+ * (`queryD1`) and the Worker through its `D1Database` binding, and neither tier may import the
7
+ * other (`graphify path "createBeechApp" "queryD1"` must keep returning no path). `@beechcms/core`
8
+ * owns the STATEMENTS and the SHAPES; the caller owns the connection.
9
+ *
10
+ * Read-only by contract: every statement here is a `PRAGMA` or a `SELECT`. Schema mutation stays
11
+ * the exclusive business of the Botanical Engine's DDL path (`engine/seed-ddl.ts`).
12
+ */
13
+ import type { Seed } from './types.js';
14
+ /**
15
+ * The one capability the introspection primitive needs from a database connection.
16
+ *
17
+ * Implementations MUST: execute the statement as-is, return one object per row with column names
18
+ * as keys, and reject/throw on failure. They MUST NOT cache, batch, rewrite or retry — a stale
19
+ * answer here becomes a wrong fingerprint, which becomes a false "your types are stale" error in
20
+ * someone else's production client.
21
+ */
22
+ export interface SchemaQueryExecutor {
23
+ all<T extends Record<string, unknown>>(sql: string): Promise<T[]>;
24
+ }
25
+ /** Thrown when live state cannot be read or cannot be trusted. */
26
+ export declare class IntrospectionError extends Error {
27
+ readonly cause?: unknown | undefined;
28
+ constructor(message: string, cause?: unknown | undefined);
29
+ }
30
+ /** One physical column, as SQLite reports it. */
31
+ export interface LiveColumn {
32
+ name: string;
33
+ /** Declared storage type, upper-cased (`TEXT` | `INTEGER` | `REAL` | …). */
34
+ sqlType: string;
35
+ notNull: boolean;
36
+ isPk: boolean;
37
+ /** Declared DEFAULT expression verbatim, or null. */
38
+ defaultValue: string | null;
39
+ }
40
+ /** One foreign-key constraint on a physical table. */
41
+ export interface LiveForeignKey {
42
+ /** Local column carrying the reference. */
43
+ column: string;
44
+ /** Referenced table, e.g. `content_team`. */
45
+ targetTable: string;
46
+ /** Referenced column, e.g. `id`. */
47
+ targetColumn: string;
48
+ /** Upper-cased, e.g. `SET NULL`. */
49
+ onDelete: string;
50
+ /** Upper-cased, e.g. `NO ACTION`. */
51
+ onUpdate: string;
52
+ }
53
+ /** One index on a physical table. SQLite's implicit `sqlite_autoindex_*` entries are excluded. */
54
+ export interface LiveIndex {
55
+ name: string;
56
+ unique: boolean;
57
+ }
58
+ /** The physical state of one table. `exists: false` means SQLite reports no such table. */
59
+ export interface LiveTable {
60
+ name: string;
61
+ exists: boolean;
62
+ /** Physical order, as returned by `PRAGMA table_info` (cid order). */
63
+ columns: LiveColumn[];
64
+ /** Sorted by `column`, then `targetTable`, for deterministic output. */
65
+ foreignKeys: LiveForeignKey[];
66
+ /** Sorted by `name`. */
67
+ indexes: LiveIndex[];
68
+ }
69
+ /** The physical state of a set of tables. `tables` is sorted by name. */
70
+ export interface LiveSchema {
71
+ tables: LiveTable[];
72
+ }
73
+ /** Returns `name` unchanged when it is a safe SQL identifier; throws otherwise. */
74
+ export declare function assertSafeIdentifier(name: string): string;
75
+ /**
76
+ * Physical state of one table. A table SQLite does not know returns `{ exists: false }` with empty
77
+ * lists rather than throwing: "missing" is a legitimate diff outcome, not an error.
78
+ *
79
+ * `PRAGMA table_info` answers an unknown table with zero rows on the SQLite path and raises on some
80
+ * wrangler paths, so both outcomes collapse to the same result.
81
+ */
82
+ export declare function introspectTable(executor: SchemaQueryExecutor, table: string): Promise<LiveTable>;
83
+ /**
84
+ * Names of the physical tables SQLite holds, optionally restricted to a `LIKE` prefix
85
+ * (e.g. `content_`). Sorted, `sqlite_*` internals excluded.
86
+ */
87
+ export declare function listTables(executor: SchemaQueryExecutor, prefix?: string): Promise<string[]>;
88
+ /**
89
+ * Physical state of several tables at once. With no explicit list, every `content_*` table is
90
+ * introspected — the set a schema export or a drift check cares about.
91
+ */
92
+ export declare function introspectSchema(executor: SchemaQueryExecutor, tables?: string[]): Promise<LiveSchema>;
93
+ /**
94
+ * The DECLARED schema: every active seed definition, read live from D1.
95
+ *
96
+ * This — not `beech.schema.ts` — is the source of truth for generated types and for the schema
97
+ * fingerprint (feature brief, business rule 1). The manifest file is a desired-state artifact and
98
+ * may legitimately be ahead of, or behind, what is deployed.
99
+ */
100
+ export declare function introspectSeedDefinitions(executor: SchemaQueryExecutor): Promise<Seed[]>;
101
+ //# sourceMappingURL=introspection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspection.d.ts","sourceRoot":"","sources":["../../src/engine/introspection.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEtC;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAA;CAClE;AAED,kEAAkE;AAClE,qBAAa,kBAAmB,SAAQ,KAAK;IACd,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO;gBAAzC,OAAO,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,OAAO,YAAA;CAItD;AAED,iDAAiD;AACjD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,OAAO,CAAA;IACb,qDAAqD;IACrD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAA;IACd,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,kGAAkG;AAClG,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,2FAA2F;AAC3F,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,sEAAsE;IACtE,OAAO,EAAE,UAAU,EAAE,CAAA;IACrB,wEAAwE;IACxE,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,wBAAwB;IACxB,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAA;CACpB;AA6CD,mFAAmF;AACnF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOzD;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,SAAS,CAAC,CAgDpB;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,CAAC,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,UAAU,CAAC,CAUrB;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,EAAE,CAAC,CAoBjB"}
@@ -0,0 +1,127 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+ /** Thrown when live state cannot be read or cannot be trusted. */
4
+ export class IntrospectionError extends Error {
5
+ cause;
6
+ constructor(message, cause) {
7
+ super(message);
8
+ this.cause = cause;
9
+ this.name = 'IntrospectionError';
10
+ }
11
+ }
12
+ /**
13
+ * SQLite identifiers cannot be bound as parameters, so every table name reaching a PRAGMA is
14
+ * interpolated — and therefore must be proven safe first. Table names in this system are always
15
+ * `content_{slug}` / `rel_{slug}_{alias}` / a structural table, all of which match this pattern.
16
+ */
17
+ const SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
18
+ /** Returns `name` unchanged when it is a safe SQL identifier; throws otherwise. */
19
+ export function assertSafeIdentifier(name) {
20
+ if (!SAFE_IDENTIFIER.test(name)) {
21
+ throw new IntrospectionError(`Refusing to introspect '${name}': not a plain SQL identifier. Table names are never user input in BeechCMS.`);
22
+ }
23
+ return name;
24
+ }
25
+ /**
26
+ * Physical state of one table. A table SQLite does not know returns `{ exists: false }` with empty
27
+ * lists rather than throwing: "missing" is a legitimate diff outcome, not an error.
28
+ *
29
+ * `PRAGMA table_info` answers an unknown table with zero rows on the SQLite path and raises on some
30
+ * wrangler paths, so both outcomes collapse to the same result.
31
+ */
32
+ export async function introspectTable(executor, table) {
33
+ const name = assertSafeIdentifier(table);
34
+ const empty = { name, exists: false, columns: [], foreignKeys: [], indexes: [] };
35
+ let info;
36
+ try {
37
+ info = await executor.all(`PRAGMA table_info(${name})`);
38
+ }
39
+ catch {
40
+ return empty;
41
+ }
42
+ if (info.length === 0)
43
+ return empty;
44
+ const columns = info.map(row => ({
45
+ name: row.name,
46
+ sqlType: String(row.type ?? '').toUpperCase(),
47
+ notNull: row.notnull === 1,
48
+ isPk: row.pk > 0,
49
+ defaultValue: row.dflt_value ?? null,
50
+ }));
51
+ let fkRows = [];
52
+ let indexRows = [];
53
+ try {
54
+ fkRows = await executor.all(`PRAGMA foreign_key_list(${name})`);
55
+ indexRows = await executor.all(`PRAGMA index_list(${name})`);
56
+ }
57
+ catch {
58
+ // A table with neither FKs nor indexes answers with zero rows on some drivers and raises on
59
+ // others; an unreadable constraint list must not hide the column list we already have.
60
+ }
61
+ const foreignKeys = fkRows
62
+ .map(row => ({
63
+ column: row.from,
64
+ targetTable: row.table,
65
+ targetColumn: row.to ?? 'id',
66
+ onDelete: String(row.on_delete ?? '').toUpperCase(),
67
+ onUpdate: String(row.on_update ?? '').toUpperCase(),
68
+ }))
69
+ .sort((a, b) => a.column.localeCompare(b.column) || a.targetTable.localeCompare(b.targetTable));
70
+ const indexes = indexRows
71
+ // Implicit indexes SQLite mints for UNIQUE/PK are not schema an author declared, and their
72
+ // names are not stable across environments.
73
+ .filter(row => !row.name.startsWith('sqlite_autoindex_'))
74
+ .map(row => ({ name: row.name, unique: row.unique === 1 }))
75
+ .sort((a, b) => a.name.localeCompare(b.name));
76
+ return { name, exists: true, columns, foreignKeys, indexes };
77
+ }
78
+ /**
79
+ * Names of the physical tables SQLite holds, optionally restricted to a `LIKE` prefix
80
+ * (e.g. `content_`). Sorted, `sqlite_*` internals excluded.
81
+ */
82
+ export async function listTables(executor, prefix) {
83
+ if (prefix !== undefined)
84
+ assertSafeIdentifier(`${prefix}x`);
85
+ const filter = prefix ? ` AND name LIKE '${prefix}%'` : '';
86
+ const rows = await executor.all(`SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'${filter} ORDER BY name ASC`);
87
+ return rows.map(row => row.name);
88
+ }
89
+ /**
90
+ * Physical state of several tables at once. With no explicit list, every `content_*` table is
91
+ * introspected — the set a schema export or a drift check cares about.
92
+ */
93
+ export async function introspectSchema(executor, tables) {
94
+ const names = tables ?? await listTables(executor, 'content_');
95
+ const sorted = [...names].sort((a, b) => a.localeCompare(b));
96
+ const result = [];
97
+ // Sequential on purpose: the CLI executor is a synchronous shell/SQLite call behind a Promise,
98
+ // and D1 rejects unbounded concurrent statements from one request.
99
+ for (const name of sorted) {
100
+ result.push(await introspectTable(executor, name));
101
+ }
102
+ return { tables: result };
103
+ }
104
+ /**
105
+ * The DECLARED schema: every active seed definition, read live from D1.
106
+ *
107
+ * This — not `beech.schema.ts` — is the source of truth for generated types and for the schema
108
+ * fingerprint (feature brief, business rule 1). The manifest file is a desired-state artifact and
109
+ * may legitimately be ahead of, or behind, what is deployed.
110
+ */
111
+ export async function introspectSeedDefinitions(executor) {
112
+ let rows;
113
+ try {
114
+ rows = await executor.all(`SELECT slug, definition FROM seeds WHERE status = 'active' ORDER BY slug ASC`);
115
+ }
116
+ catch (error) {
117
+ throw new IntrospectionError('Failed to read the `seeds` table from D1.', error);
118
+ }
119
+ return rows.map(row => {
120
+ try {
121
+ return JSON.parse(row.definition);
122
+ }
123
+ catch (error) {
124
+ throw new IntrospectionError(`Seed '${row.slug}' holds a definition that is not valid JSON; D1 state is corrupt.`, error);
125
+ }
126
+ });
127
+ }