@beechcms/core 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (362) hide show
  1. package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -1
  2. package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -1
  3. package/dist/auth/account-email.d.ts +3 -0
  4. package/dist/auth/account-email.d.ts.map +1 -0
  5. package/dist/auth/account-email.js +9 -0
  6. package/dist/auth/password-reset-token.repository.d.ts +13 -2
  7. package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
  8. package/dist/auth/user.repository.d.ts +69 -4
  9. package/dist/auth/user.repository.d.ts.map +1 -1
  10. package/dist/common/canonical-json.d.ts +2 -1
  11. package/dist/common/canonical-json.d.ts.map +1 -1
  12. package/dist/common/canonical-json.js +5 -3
  13. package/dist/content/content-scan.repository.d.ts +1 -1
  14. package/dist/content/content.repository.d.ts +156 -5
  15. package/dist/content/content.repository.d.ts.map +1 -1
  16. package/dist/content/content.repository.js +62 -0
  17. package/dist/content/seed-media-purge.repository.d.ts +34 -0
  18. package/dist/content/seed-media-purge.repository.d.ts.map +1 -0
  19. package/dist/content/seed.repository.d.ts +14 -0
  20. package/dist/content/seed.repository.d.ts.map +1 -1
  21. package/dist/dashboard-layout/content-view.d.ts +757 -0
  22. package/dist/dashboard-layout/content-view.d.ts.map +1 -0
  23. package/dist/dashboard-layout/content-view.js +285 -0
  24. package/dist/dashboard-layout/content-view.repository.d.ts +39 -0
  25. package/dist/dashboard-layout/content-view.repository.d.ts.map +1 -0
  26. package/dist/dashboard-layout/dashboard-layout.d.ts +5 -0
  27. package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
  28. package/dist/dashboard-layout/dashboard-layout.js +17 -9
  29. package/dist/dashboard-layout/kanban/kanban.d.ts +32 -18
  30. package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -1
  31. package/dist/dashboard-layout/kanban/kanban.js +45 -21
  32. package/dist/dashboard-layout/seed-layout.d.ts +22 -31
  33. package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
  34. package/dist/dashboard-layout/seed-layout.js +93 -18
  35. package/dist/dashboard-layout/seed-layout.repository.d.ts +1 -5
  36. package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -1
  37. package/dist/engine/ddl/ddl.d.ts +31 -0
  38. package/dist/engine/ddl/ddl.d.ts.map +1 -1
  39. package/dist/engine/ddl/ddl.js +92 -13
  40. package/dist/engine/ddl/schema-mutator.d.ts +3 -0
  41. package/dist/engine/ddl/schema-mutator.d.ts.map +1 -1
  42. package/dist/engine/ddl/seed-purge-guards.d.ts +7 -0
  43. package/dist/engine/ddl/seed-purge-guards.d.ts.map +1 -0
  44. package/dist/engine/ddl/seed-purge-guards.js +19 -0
  45. package/dist/engine/engine.d.ts +4 -3
  46. package/dist/engine/engine.d.ts.map +1 -1
  47. package/dist/engine/engine.js +4 -3
  48. package/dist/engine/introspection/relation-audit.d.ts +46 -0
  49. package/dist/engine/introspection/relation-audit.d.ts.map +1 -0
  50. package/dist/engine/introspection/relation-audit.js +103 -0
  51. package/dist/engine/introspection/schema-fingerprint.d.ts +2 -1
  52. package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -1
  53. package/dist/engine/introspection/schema-fingerprint.js +8 -3
  54. package/dist/engine/privacy/policies.d.ts +1 -1
  55. package/dist/engine/privacy/policies.d.ts.map +1 -1
  56. package/dist/engine/privacy/policies.js +12 -6
  57. package/dist/engine/privacy/privacy.service.d.ts +13 -0
  58. package/dist/engine/privacy/privacy.service.d.ts.map +1 -1
  59. package/dist/engine/privacy/privacy.service.js +24 -0
  60. package/dist/engine/query/active-clause.d.ts +19 -0
  61. package/dist/engine/query/active-clause.d.ts.map +1 -0
  62. package/dist/engine/query/active-clause.js +27 -0
  63. package/dist/engine/query/query.d.ts +14 -1
  64. package/dist/engine/query/query.d.ts.map +1 -1
  65. package/dist/engine/query/query.js +105 -13
  66. package/dist/engine/seeds/candidate-ids.d.ts +14 -0
  67. package/dist/engine/seeds/candidate-ids.d.ts.map +1 -0
  68. package/dist/engine/seeds/candidate-ids.js +40 -0
  69. package/dist/engine/seeds/seed-definition-diff.d.ts +38 -0
  70. package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -0
  71. package/dist/engine/seeds/seed-definition-diff.js +51 -0
  72. package/dist/engine/seeds/seed-registry.d.ts +17 -0
  73. package/dist/engine/seeds/seed-registry.d.ts.map +1 -1
  74. package/dist/engine/seeds/seed-registry.js +27 -0
  75. package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -1
  76. package/dist/engine/seeds/seed-types-generator.js +4 -5
  77. package/dist/engine/seeds/seed-validation.d.ts +5 -0
  78. package/dist/engine/seeds/seed-validation.d.ts.map +1 -1
  79. package/dist/engine/seeds/seed-validation.js +39 -19
  80. package/dist/engine/seeds/tag-migration.d.ts +73 -0
  81. package/dist/engine/seeds/tag-migration.d.ts.map +1 -0
  82. package/dist/engine/seeds/tag-migration.js +212 -0
  83. package/dist/engine/seeds/tag-options.d.ts +15 -0
  84. package/dist/engine/seeds/tag-options.d.ts.map +1 -0
  85. package/dist/engine/seeds/tag-options.js +111 -0
  86. package/dist/engine/seeds/tag-value.d.ts +31 -0
  87. package/dist/engine/seeds/tag-value.d.ts.map +1 -0
  88. package/dist/engine/seeds/tag-value.js +51 -0
  89. package/dist/engine/types.d.ts +59 -7
  90. package/dist/engine/types.d.ts.map +1 -1
  91. package/dist/engine/validation/cache.d.ts.map +1 -1
  92. package/dist/engine/validation/cache.js +20 -4
  93. package/dist/engine/validation/index.d.ts +12 -1
  94. package/dist/engine/validation/index.d.ts.map +1 -1
  95. package/dist/engine/validation/index.js +29 -33
  96. package/dist/engine/validation/primitives.d.ts +12 -0
  97. package/dist/engine/validation/primitives.d.ts.map +1 -1
  98. package/dist/engine/validation/primitives.js +22 -0
  99. package/dist/engine/validation/richtext-sanitizer.d.ts +9 -1
  100. package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
  101. package/dist/engine/validation/richtext-sanitizer.js +36 -7
  102. package/dist/engine/validation/schema-builders.d.ts +16 -0
  103. package/dist/engine/validation/schema-builders.d.ts.map +1 -1
  104. package/dist/engine/validation/schema-builders.js +125 -18
  105. package/dist/index.d.ts +34 -11
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +34 -11
  108. package/dist/media/file-types.d.ts +12 -0
  109. package/dist/media/file-types.d.ts.map +1 -1
  110. package/dist/media/file-types.js +24 -4
  111. package/dist/media/magic-bytes.d.ts.map +1 -1
  112. package/dist/media/magic-bytes.js +19 -9
  113. package/dist/media/media-transform.d.ts +8 -4
  114. package/dist/media/media-transform.d.ts.map +1 -1
  115. package/dist/media/media-transform.js +10 -6
  116. package/dist/media/media.repository.d.ts +5 -0
  117. package/dist/media/media.repository.d.ts.map +1 -1
  118. package/dist/notifications/notification-service.d.ts +1 -1
  119. package/dist/notifications/notification.repository.d.ts +11 -2
  120. package/dist/notifications/notification.repository.d.ts.map +1 -1
  121. package/dist/oauth/authorization-code.repository.d.ts +10 -5
  122. package/dist/oauth/authorization-code.repository.d.ts.map +1 -1
  123. package/dist/oauth/pkce.d.ts +1 -1
  124. package/dist/oauth/role-guard.d.ts +30 -11
  125. package/dist/oauth/role-guard.d.ts.map +1 -1
  126. package/dist/oauth/role-guard.js +27 -5
  127. package/dist/oauth/scopes.d.ts +1 -1
  128. package/dist/oauth/scopes.d.ts.map +1 -1
  129. package/dist/oauth/token.repository.d.ts +2 -0
  130. package/dist/oauth/token.repository.d.ts.map +1 -1
  131. package/dist/queue/queue.interface.d.ts +9 -1
  132. package/dist/queue/queue.interface.d.ts.map +1 -1
  133. package/dist/queue/queue.stub.d.ts +1 -1
  134. package/dist/queue/queue.stub.d.ts.map +1 -1
  135. package/dist/queue/queue.stub.js +2 -3
  136. package/dist/queue/scheduler.stub.d.ts.map +1 -1
  137. package/dist/rate-limit/token-bucket-rate-limiter.d.ts +7 -0
  138. package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
  139. package/dist/rate-limit/token-bucket-rate-limiter.js +66 -0
  140. package/dist/rbac/invitation.repository.d.ts +28 -10
  141. package/dist/rbac/invitation.repository.d.ts.map +1 -1
  142. package/dist/rbac/types.d.ts +40 -28
  143. package/dist/rbac/types.d.ts.map +1 -1
  144. package/dist/rbac/types.js +2 -1
  145. package/dist/schema/manifest-seeds.js +3 -3
  146. package/dist/search/search.repository.d.ts +5 -4
  147. package/dist/search/search.repository.d.ts.map +1 -1
  148. package/dist/search/vector-extractor.d.ts.map +1 -1
  149. package/dist/search/vector-extractor.js +23 -2
  150. package/dist/search/vector.repository.d.ts +2 -1
  151. package/dist/search/vector.repository.d.ts.map +1 -1
  152. package/dist/security/time-trap-token.repository.d.ts +5 -3
  153. package/dist/security/time-trap-token.repository.d.ts.map +1 -1
  154. package/dist/settings/site-settings.repository.d.ts +8 -0
  155. package/dist/settings/site-settings.repository.d.ts.map +1 -1
  156. package/dist/transfer/flat-seed.d.ts.map +1 -1
  157. package/dist/transfer/flat-seed.js +6 -1
  158. package/dist/transfer/row-mapping.d.ts.map +1 -1
  159. package/dist/transfer/row-mapping.js +9 -2
  160. package/package.json +9 -4
  161. package/dist/.tsbuildinfo +0 -1
  162. package/dist/automations-grammar-words.d.ts +0 -2
  163. package/dist/automations-grammar-words.d.ts.map +0 -1
  164. package/dist/automations-grammar-words.js +0 -8
  165. package/dist/automations.repository.interface.d.ts +0 -19
  166. package/dist/automations.repository.interface.d.ts.map +0 -1
  167. package/dist/automations.runner.interface.d.ts +0 -10
  168. package/dist/automations.runner.interface.d.ts.map +0 -1
  169. package/dist/automations.runner.stub.d.ts +0 -5
  170. package/dist/automations.runner.stub.d.ts.map +0 -1
  171. package/dist/automations.runner.stub.js +0 -7
  172. package/dist/automations.types.d.ts +0 -75
  173. package/dist/automations.types.d.ts.map +0 -1
  174. package/dist/automations.types.js +0 -3
  175. package/dist/clock.d.ts +0 -25
  176. package/dist/clock.d.ts.map +0 -1
  177. package/dist/clock.js +0 -11
  178. package/dist/content-scan.repository.d.ts +0 -9
  179. package/dist/content-scan.repository.d.ts.map +0 -1
  180. package/dist/content-scan.repository.js +0 -3
  181. package/dist/content.repository.d.ts +0 -247
  182. package/dist/content.repository.d.ts.map +0 -1
  183. package/dist/content.repository.js +0 -59
  184. package/dist/dashboard-layout/kanban/kanban-position.repository.d.ts +0 -19
  185. package/dist/dashboard-layout/kanban/kanban-position.repository.d.ts.map +0 -1
  186. package/dist/dashboard-layout/kanban/kanban-position.repository.js +0 -3
  187. package/dist/dashboard-layout.d.ts +0 -154
  188. package/dist/dashboard-layout.d.ts.map +0 -1
  189. package/dist/dashboard-layout.js +0 -186
  190. package/dist/dashboard-layout.repository.d.ts +0 -18
  191. package/dist/dashboard-layout.repository.d.ts.map +0 -1
  192. package/dist/dashboard-layout.repository.js +0 -3
  193. package/dist/dashboard-permissions.d.ts +0 -3
  194. package/dist/dashboard-permissions.d.ts.map +0 -1
  195. package/dist/dashboard-permissions.js +0 -11
  196. package/dist/dashboard-scopes.d.ts +0 -12
  197. package/dist/dashboard-scopes.d.ts.map +0 -1
  198. package/dist/dashboard-scopes.js +0 -30
  199. package/dist/define-seed.d.ts +0 -3
  200. package/dist/define-seed.d.ts.map +0 -1
  201. package/dist/define-seed.js +0 -5
  202. package/dist/demo-data.repository.d.ts +0 -5
  203. package/dist/demo-data.repository.d.ts.map +0 -1
  204. package/dist/demo-data.repository.js +0 -3
  205. package/dist/email.provider.d.ts +0 -32
  206. package/dist/email.provider.d.ts.map +0 -1
  207. package/dist/email.provider.js +0 -4
  208. package/dist/engine/ddl/seed-ddl-destructive.d.ts +0 -40
  209. package/dist/engine/ddl/seed-ddl-destructive.d.ts.map +0 -1
  210. package/dist/engine/ddl/seed-ddl-destructive.js +0 -149
  211. package/dist/engine/ddl.d.ts +0 -193
  212. package/dist/engine/ddl.d.ts.map +0 -1
  213. package/dist/engine/ddl.js +0 -518
  214. package/dist/engine/define-seed.d.ts +0 -3
  215. package/dist/engine/define-seed.d.ts.map +0 -1
  216. package/dist/engine/define-seed.js +0 -5
  217. package/dist/engine/introspection.d.ts +0 -101
  218. package/dist/engine/introspection.d.ts.map +0 -1
  219. package/dist/engine/introspection.js +0 -127
  220. package/dist/engine/localization.d.ts +0 -127
  221. package/dist/engine/localization.d.ts.map +0 -1
  222. package/dist/engine/localization.js +0 -212
  223. package/dist/engine/policies.d.ts +0 -58
  224. package/dist/engine/policies.d.ts.map +0 -1
  225. package/dist/engine/policies.js +0 -168
  226. package/dist/engine/privacy.service.d.ts +0 -65
  227. package/dist/engine/privacy.service.d.ts.map +0 -1
  228. package/dist/engine/privacy.service.js +0 -114
  229. package/dist/engine/query.d.ts +0 -23
  230. package/dist/engine/query.d.ts.map +0 -1
  231. package/dist/engine/query.js +0 -236
  232. package/dist/engine/relations.d.ts +0 -28
  233. package/dist/engine/relations.d.ts.map +0 -1
  234. package/dist/engine/relations.js +0 -25
  235. package/dist/engine/schema-fingerprint.d.ts +0 -73
  236. package/dist/engine/schema-fingerprint.d.ts.map +0 -1
  237. package/dist/engine/schema-fingerprint.js +0 -97
  238. package/dist/engine/schema-mutator.d.ts +0 -26
  239. package/dist/engine/schema-mutator.d.ts.map +0 -1
  240. package/dist/engine/schema-mutator.js +0 -3
  241. package/dist/engine/seed-ddl-destructive.d.ts +0 -40
  242. package/dist/engine/seed-ddl-destructive.d.ts.map +0 -1
  243. package/dist/engine/seed-ddl-destructive.js +0 -149
  244. package/dist/engine/seed-ddl.d.ts +0 -40
  245. package/dist/engine/seed-ddl.d.ts.map +0 -1
  246. package/dist/engine/seed-ddl.js +0 -100
  247. package/dist/engine/seed-registry.d.ts +0 -71
  248. package/dist/engine/seed-registry.d.ts.map +0 -1
  249. package/dist/engine/seed-registry.js +0 -167
  250. package/dist/engine/seed-types-generator.d.ts +0 -10
  251. package/dist/engine/seed-types-generator.d.ts.map +0 -1
  252. package/dist/engine/seed-types-generator.js +0 -89
  253. package/dist/engine/seed-validation.d.ts +0 -30
  254. package/dist/engine/seed-validation.d.ts.map +0 -1
  255. package/dist/engine/seed-validation.js +0 -319
  256. package/dist/engine/seeds.d.ts +0 -20
  257. package/dist/engine/seeds.d.ts.map +0 -1
  258. package/dist/engine/seeds.js +0 -21
  259. package/dist/engine/serialize.d.ts +0 -20
  260. package/dist/engine/serialize.d.ts.map +0 -1
  261. package/dist/engine/serialize.js +0 -160
  262. package/dist/engine.d.ts +0 -144
  263. package/dist/engine.d.ts.map +0 -1
  264. package/dist/engine.js +0 -741
  265. package/dist/file-types.d.ts +0 -13
  266. package/dist/file-types.d.ts.map +0 -1
  267. package/dist/file-types.js +0 -85
  268. package/dist/hooks.d.ts +0 -29
  269. package/dist/hooks.d.ts.map +0 -1
  270. package/dist/hooks.js +0 -3
  271. package/dist/id-generator.d.ts +0 -26
  272. package/dist/id-generator.d.ts.map +0 -1
  273. package/dist/id-generator.js +0 -12
  274. package/dist/idempotency.repository.d.ts +0 -13
  275. package/dist/idempotency.repository.d.ts.map +0 -1
  276. package/dist/idempotency.repository.js +0 -3
  277. package/dist/kanban-position.repository.d.ts +0 -19
  278. package/dist/kanban-position.repository.d.ts.map +0 -1
  279. package/dist/kanban-position.repository.js +0 -3
  280. package/dist/kanban.d.ts +0 -75
  281. package/dist/kanban.d.ts.map +0 -1
  282. package/dist/kanban.js +0 -82
  283. package/dist/layout-permissions.d.ts +0 -3
  284. package/dist/layout-permissions.d.ts.map +0 -1
  285. package/dist/layout-permissions.js +0 -11
  286. package/dist/media.repository.d.ts +0 -61
  287. package/dist/media.repository.d.ts.map +0 -1
  288. package/dist/media.repository.js +0 -3
  289. package/dist/policies.d.ts +0 -10
  290. package/dist/policies.d.ts.map +0 -1
  291. package/dist/policies.js +0 -33
  292. package/dist/queue.interface.d.ts +0 -39
  293. package/dist/queue.interface.d.ts.map +0 -1
  294. package/dist/queue.interface.js +0 -3
  295. package/dist/queue.stub.d.ts +0 -6
  296. package/dist/queue.stub.d.ts.map +0 -1
  297. package/dist/queue.stub.js +0 -8
  298. package/dist/relations.d.ts +0 -28
  299. package/dist/relations.d.ts.map +0 -1
  300. package/dist/relations.js +0 -25
  301. package/dist/richtext-render.d.ts +0 -11
  302. package/dist/richtext-render.d.ts.map +0 -1
  303. package/dist/richtext-render.js +0 -87
  304. package/dist/richtext.d.ts +0 -11
  305. package/dist/richtext.d.ts.map +0 -1
  306. package/dist/richtext.js +0 -13
  307. package/dist/scheduler.interface.d.ts +0 -4
  308. package/dist/scheduler.interface.d.ts.map +0 -1
  309. package/dist/scheduler.interface.js +0 -3
  310. package/dist/scheduler.stub.d.ts +0 -5
  311. package/dist/scheduler.stub.d.ts.map +0 -1
  312. package/dist/scheduler.stub.js +0 -7
  313. package/dist/schema-mutator.d.ts +0 -26
  314. package/dist/schema-mutator.d.ts.map +0 -1
  315. package/dist/schema-mutator.js +0 -3
  316. package/dist/seed-ddl-destructive.d.ts +0 -40
  317. package/dist/seed-ddl-destructive.d.ts.map +0 -1
  318. package/dist/seed-ddl-destructive.js +0 -149
  319. package/dist/seed-ddl.d.ts +0 -40
  320. package/dist/seed-ddl.d.ts.map +0 -1
  321. package/dist/seed-ddl.js +0 -100
  322. package/dist/seed-layout.d.ts +0 -145
  323. package/dist/seed-layout.d.ts.map +0 -1
  324. package/dist/seed-layout.js +0 -192
  325. package/dist/seed-layout.repository.d.ts +0 -22
  326. package/dist/seed-layout.repository.d.ts.map +0 -1
  327. package/dist/seed-layout.repository.js +0 -3
  328. package/dist/seed-registry.d.ts +0 -71
  329. package/dist/seed-registry.d.ts.map +0 -1
  330. package/dist/seed-registry.js +0 -143
  331. package/dist/seed-types-generator.d.ts +0 -10
  332. package/dist/seed-types-generator.d.ts.map +0 -1
  333. package/dist/seed-types-generator.js +0 -86
  334. package/dist/seed-validation.d.ts +0 -23
  335. package/dist/seed-validation.d.ts.map +0 -1
  336. package/dist/seed-validation.js +0 -224
  337. package/dist/seed.repository.d.ts +0 -37
  338. package/dist/seed.repository.d.ts.map +0 -1
  339. package/dist/seed.repository.js +0 -3
  340. package/dist/seeds.d.ts +0 -20
  341. package/dist/seeds.d.ts.map +0 -1
  342. package/dist/seeds.js +0 -21
  343. package/dist/site-settings.repository.d.ts +0 -16
  344. package/dist/site-settings.repository.d.ts.map +0 -1
  345. package/dist/site-settings.repository.js +0 -3
  346. package/dist/slug-utils.d.ts +0 -18
  347. package/dist/slug-utils.d.ts.map +0 -1
  348. package/dist/slug-utils.js +0 -32
  349. package/dist/storage.d.ts +0 -46
  350. package/dist/storage.d.ts.map +0 -1
  351. package/dist/storage.js +0 -3
  352. package/dist/types.d.ts +0 -243
  353. package/dist/types.d.ts.map +0 -1
  354. package/dist/types.js +0 -3
  355. package/dist/validation.d.ts +0 -50
  356. package/dist/validation.d.ts.map +0 -1
  357. package/dist/validation.js +0 -660
  358. package/dist/webhook-validation.d.ts +0 -3
  359. package/dist/webhook-validation.d.ts.map +0 -1
  360. package/dist/webhook-validation.js +0 -6
  361. /package/dist/{automations.repository.interface.js → content/seed-media-purge.repository.js} +0 -0
  362. /package/dist/{automations.runner.interface.js → dashboard-layout/content-view.repository.js} +0 -0
@@ -1,127 +0,0 @@
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
- }
@@ -1,127 +0,0 @@
1
- /**
2
- * @module engine/localization
3
- * Field-level localization primitives for the Botanical Engine. A localized branch keeps its native
4
- * TEXT column and stores a locale dictionary in it (`{"it":"Scarpa","en":"Shoe"}`), so ids, relations
5
- * and metrics are untouched and toggling `Branch.localized` never emits DDL. Pure functions: no I/O and
6
- * no settings access — callers pass the project's {@link LocaleConfig}.
7
- */
8
- import type { Branch, BranchType, Seed } from './types.js';
9
- /**
10
- * Locale code grammar (v1): ISO 639 language (2–3 lowercase letters), optionally followed by an ISO 3166
11
- * region (2 uppercase letters) or a UN M.49 area (3 digits) — `it`, `en`, `pt-BR`, `es-419`. Script
12
- * subtags (`zh-Hant`) are outside v1. The grammar doubles as an injection guard: validated codes are
13
- * later interpolated into SQLite JSON paths.
14
- */
15
- export declare const LOCALE_CODE_RE: RegExp;
16
- /** Branch types that may carry `localized: true`. */
17
- export declare const LOCALIZABLE_BRANCH_TYPES: ReadonlySet<BranchType>;
18
- /**
19
- * Project-level language configuration. Invariant (guaranteed by its producer): `locales` is non-empty,
20
- * every entry matches {@link LOCALE_CODE_RE}, and it contains `defaultLocale`.
21
- */
22
- export interface LocaleConfig {
23
- readonly locales: readonly string[];
24
- readonly defaultLocale: string;
25
- }
26
- /** A locale dictionary as stored in the branch column: locale code → value. */
27
- export type LocalizedDictionary = Record<string, unknown>;
28
- /**
29
- * A validated write for a localized branch: registered locale code → new value, or `null` meaning
30
- * "clear this locale". Locales absent from the patch must be left untouched by the write path.
31
- */
32
- export type LocalizedPatch = Record<string, unknown>;
33
- /** True when `value` is a string matching {@link LOCALE_CODE_RE}. */
34
- export declare function isLocaleCode(value: unknown): value is string;
35
- /**
36
- * True when the branch is localized AND its type supports it. A malformed definition that slipped past
37
- * seed validation (e.g. `localized: true` on a `number`) is treated as not localized, never as a dictionary.
38
- */
39
- export declare function isLocalizedBranch(branch: Pick<Branch, 'type' | 'localized'>): boolean;
40
- /**
41
- * Structural check used on READ paths (no config available): a non-empty plain object whose every key
42
- * matches the locale-code grammar. A richtext envelope (`schemaVersion`, `doc`) and a TipTap doc
43
- * (`type`, `content`) never match.
44
- */
45
- export declare function isLocaleDictionary(value: unknown): value is LocalizedDictionary;
46
- /**
47
- * Stricter check used on WRITE paths: a locale dictionary with at least one REGISTERED locale key.
48
- * Without the registration requirement a legitimate json value such as `{"url": "…", "alt": "…"}` —
49
- * whose keys happen to match the grammar — would be read as a dictionary of unregistered locales and
50
- * silently emptied.
51
- */
52
- export declare function isLocalizedWriteDictionary(value: unknown, config: LocaleConfig): value is LocalizedDictionary;
53
- /**
54
- * Normalises any accepted write value of a localized branch into a {@link LocalizedPatch}:
55
- * - a write dictionary keeps its registered locales, in `config.locales` order; unregistered locale
56
- * keys are dropped;
57
- * - any other value (string, richtext doc, non-dictionary json) is the default-locale value;
58
- * - `null`, `undefined` and blank strings become `null` ("clear this locale").
59
- * Idempotent: a patch passed back in yields the same patch.
60
- */
61
- export declare function toLocalizedPatch(value: unknown, config: LocaleConfig): LocalizedPatch;
62
- /**
63
- * Removes `null` / `undefined` / blank-string entries. Returns `null` when nothing remains, so an
64
- * all-cleared dictionary is stored as SQL NULL rather than `{}`. Keys are otherwise preserved as-is —
65
- * including locales no longer registered, which must never be dropped by a write (brief §2, no data loss).
66
- */
67
- export declare function compactLocalizedDictionary(value: LocalizedDictionary): LocalizedDictionary | null;
68
- /**
69
- * The settings subset {@link resolveLocaleConfig} reads. Structural, so this module stays free of
70
- * settings imports; `SiteSettings` satisfies it.
71
- */
72
- export interface LocaleSettings {
73
- readonly locales?: readonly string[] | null;
74
- readonly defaultLocale?: string | null;
75
- readonly defaultLanguage: string;
76
- }
77
- /**
78
- * Builds the project's {@link LocaleConfig}, repairing whatever a hand-edited settings row could break so
79
- * the LocaleConfig invariant always holds:
80
- * - `locales`: the stored list minus invalid codes and duplicates; when nothing remains (never configured,
81
- * `[]`, corrupt), the implicit single-language config `[defaultLanguage]` (brief §2), which keeps the
82
- * feature invisible to a mono-lingual project;
83
- * - `defaultLocale`: the stored value when it belongs to `locales`, else `locales[0]`.
84
- */
85
- export declare function resolveLocaleConfig(settings: LocaleSettings): LocaleConfig;
86
- /**
87
- * Merges a write into the value stored for a localized branch. The write path's only way to persist a
88
- * localized value, so a translation the write does not mention is never dropped (brief §2):
89
- * - `write === null` clears the whole field (an explicit null keeps its pre-localization meaning);
90
- * - otherwise the write is normalised with {@link toLocalizedPatch}: every registered locale it names
91
- * overwrites that locale, a `null` entry clears that locale only, and every other stored locale —
92
- * including ones no longer registered — is kept as-is;
93
- * - a stored legacy value (written before the branch became localized) is the default-locale value.
94
- * @returns The compacted dictionary, or `null` when no locale keeps a value.
95
- */
96
- export declare function applyLocalizedPatch(branch: Pick<Branch, 'type' | 'localized'>, stored: unknown, write: unknown, config: LocaleConfig): LocalizedDictionary | null;
97
- /** Aliases of the localized branches a write carries — the fields it must merge rather than replace. */
98
- export declare function localizedAliasesIn(seed: Pick<Seed, 'branches'>, data: Record<string, unknown>): string[];
99
- /**
100
- * Returns `data` with every localized branch it carries merged into `stored` via
101
- * {@link applyLocalizedPatch}; other keys pass through untouched. `stored` is the entry's current values
102
- * (`null` on create, which only compacts). Returns `data` itself when `config` is undefined or no localized
103
- * branch is written.
104
- */
105
- export declare function mergeLocalizedFields(seed: Pick<Seed, 'branches'>, stored: Record<string, unknown> | null, data: Record<string, unknown>, config: LocaleConfig | undefined): Record<string, unknown>;
106
- /**
107
- * Resolves a stored localized value to one language: the requested locale, then the default locale, then the
108
- * first stored translation (stored key order), then `null`. A legacy value that is not a dictionary is returned
109
- * as-is. Non-localized branches return `value` unchanged. `buildSelectQuery` applies the same chain in SQL, so
110
- * filters and ORDER BY compare exactly what a reader sees.
111
- */
112
- export declare function resolveLocalizedValue(branch: Pick<Branch, 'type' | 'localized'>, value: unknown, locale: string, config: LocaleConfig): unknown;
113
- /**
114
- * Returns a copy of `data` with every localized branch resolved to `locale` (default: the default locale)
115
- * via {@link resolveLocalizedValue}. Returns `data` itself when `config` is undefined.
116
- */
117
- export declare function resolveLocalizedFields(seed: Pick<Seed, 'branches'>, data: Record<string, unknown>, config: LocaleConfig | undefined, locale?: string): Record<string, unknown>;
118
- /**
119
- * The whole dictionary of a stored localized value, for readers asking every language (`?lang=all`):
120
- * a stored dictionary as-is — unregistered locales included, nothing is hidden (brief §2) — a legacy
121
- * value as `{ [defaultLocale]: value }` (the write path's reading of it), a blank value as `null`.
122
- * Non-localized branches return `value` unchanged.
123
- */
124
- export declare function asLocaleDictionary(branch: Pick<Branch, 'type' | 'localized'>, value: unknown, config: LocaleConfig): unknown;
125
- /** Returns a copy of `data` with every localized branch it carries passed through {@link asLocaleDictionary}. */
126
- export declare function asLocaleDictionaries(seed: Pick<Seed, 'branches'>, data: Record<string, unknown>, config: LocaleConfig): Record<string, unknown>;
127
- //# sourceMappingURL=localization.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"localization.d.ts","sourceRoot":"","sources":["../../src/engine/localization.ts"],"names":[],"mappings":"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"}
@@ -1,212 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- // Copyright (c) 2024–2026 Flavio De Musso
3
- import { cleanString, isPlainObject } from './validation/primitives.js';
4
- /**
5
- * Locale code grammar (v1): ISO 639 language (2–3 lowercase letters), optionally followed by an ISO 3166
6
- * region (2 uppercase letters) or a UN M.49 area (3 digits) — `it`, `en`, `pt-BR`, `es-419`. Script
7
- * subtags (`zh-Hant`) are outside v1. The grammar doubles as an injection guard: validated codes are
8
- * later interpolated into SQLite JSON paths.
9
- */
10
- export const LOCALE_CODE_RE = /^[a-z]{2,3}(?:-(?:[A-Z]{2}|[0-9]{3}))?$/;
11
- /** Branch types that may carry `localized: true`. */
12
- export const LOCALIZABLE_BRANCH_TYPES = new Set(['text', 'richtext', 'json']);
13
- /** True when `value` is a string matching {@link LOCALE_CODE_RE}. */
14
- export function isLocaleCode(value) {
15
- return typeof value === 'string' && LOCALE_CODE_RE.test(value);
16
- }
17
- /**
18
- * True when the branch is localized AND its type supports it. A malformed definition that slipped past
19
- * seed validation (e.g. `localized: true` on a `number`) is treated as not localized, never as a dictionary.
20
- */
21
- export function isLocalizedBranch(branch) {
22
- return branch.localized === true && LOCALIZABLE_BRANCH_TYPES.has(branch.type);
23
- }
24
- /**
25
- * Structural check used on READ paths (no config available): a non-empty plain object whose every key
26
- * matches the locale-code grammar. A richtext envelope (`schemaVersion`, `doc`) and a TipTap doc
27
- * (`type`, `content`) never match.
28
- */
29
- export function isLocaleDictionary(value) {
30
- if (!isPlainObject(value))
31
- return false;
32
- const keys = Object.keys(value);
33
- return keys.length > 0 && keys.every(isLocaleCode);
34
- }
35
- /**
36
- * Stricter check used on WRITE paths: a locale dictionary with at least one REGISTERED locale key.
37
- * Without the registration requirement a legitimate json value such as `{"url": "…", "alt": "…"}` —
38
- * whose keys happen to match the grammar — would be read as a dictionary of unregistered locales and
39
- * silently emptied.
40
- */
41
- export function isLocalizedWriteDictionary(value, config) {
42
- return isLocaleDictionary(value) && Object.keys(value).some((key) => config.locales.includes(key));
43
- }
44
- function isBlankLocaleValue(value) {
45
- return value === null || value === undefined || (typeof value === 'string' && cleanString(value) === '');
46
- }
47
- /**
48
- * Normalises any accepted write value of a localized branch into a {@link LocalizedPatch}:
49
- * - a write dictionary keeps its registered locales, in `config.locales` order; unregistered locale
50
- * keys are dropped;
51
- * - any other value (string, richtext doc, non-dictionary json) is the default-locale value;
52
- * - `null`, `undefined` and blank strings become `null` ("clear this locale").
53
- * Idempotent: a patch passed back in yields the same patch.
54
- */
55
- export function toLocalizedPatch(value, config) {
56
- const source = isLocalizedWriteDictionary(value, config)
57
- ? value
58
- : { [config.defaultLocale]: value };
59
- const patch = {};
60
- for (const locale of config.locales) {
61
- if (!Object.hasOwn(source, locale))
62
- continue;
63
- patch[locale] = isBlankLocaleValue(source[locale]) ? null : source[locale];
64
- }
65
- return patch;
66
- }
67
- /**
68
- * Removes `null` / `undefined` / blank-string entries. Returns `null` when nothing remains, so an
69
- * all-cleared dictionary is stored as SQL NULL rather than `{}`. Keys are otherwise preserved as-is —
70
- * including locales no longer registered, which must never be dropped by a write (brief §2, no data loss).
71
- */
72
- export function compactLocalizedDictionary(value) {
73
- const compact = {};
74
- for (const [locale, localeValue] of Object.entries(value)) {
75
- if (isBlankLocaleValue(localeValue))
76
- continue;
77
- compact[locale] = localeValue;
78
- }
79
- return Object.keys(compact).length > 0 ? compact : null;
80
- }
81
- /** Content locale used when neither stored `locales` nor a valid `defaultLanguage` is available. */
82
- const FALLBACK_LOCALE = 'en';
83
- /**
84
- * Builds the project's {@link LocaleConfig}, repairing whatever a hand-edited settings row could break so
85
- * the LocaleConfig invariant always holds:
86
- * - `locales`: the stored list minus invalid codes and duplicates; when nothing remains (never configured,
87
- * `[]`, corrupt), the implicit single-language config `[defaultLanguage]` (brief §2), which keeps the
88
- * feature invisible to a mono-lingual project;
89
- * - `defaultLocale`: the stored value when it belongs to `locales`, else `locales[0]`.
90
- */
91
- export function resolveLocaleConfig(settings) {
92
- const stored = [...new Set((settings.locales ?? []).filter(isLocaleCode))];
93
- const implicit = isLocaleCode(settings.defaultLanguage) ? settings.defaultLanguage : FALLBACK_LOCALE;
94
- const locales = stored.length > 0 ? stored : [implicit];
95
- const defaultLocale = settings.defaultLocale && locales.includes(settings.defaultLocale)
96
- ? settings.defaultLocale
97
- : locales[0];
98
- return { locales, defaultLocale };
99
- }
100
- /**
101
- * True when a STORED value of a localized branch is a locale dictionary. Text and richtext cannot hold an
102
- * object keyed only by locale codes for any other reason; json can (`{"url": "…", "alt": "…"}`), so a json
103
- * object counts only when at least one key is a registered locale — otherwise it is a legacy value written
104
- * before the branch became localized.
105
- */
106
- function isStoredLocaleDictionary(branch, value, config) {
107
- if (!isLocalizedBranch(branch) || !isLocaleDictionary(value))
108
- return false;
109
- return branch.type !== 'json' || Object.keys(value).some((key) => config.locales.includes(key));
110
- }
111
- /**
112
- * Merges a write into the value stored for a localized branch. The write path's only way to persist a
113
- * localized value, so a translation the write does not mention is never dropped (brief §2):
114
- * - `write === null` clears the whole field (an explicit null keeps its pre-localization meaning);
115
- * - otherwise the write is normalised with {@link toLocalizedPatch}: every registered locale it names
116
- * overwrites that locale, a `null` entry clears that locale only, and every other stored locale —
117
- * including ones no longer registered — is kept as-is;
118
- * - a stored legacy value (written before the branch became localized) is the default-locale value.
119
- * @returns The compacted dictionary, or `null` when no locale keeps a value.
120
- */
121
- export function applyLocalizedPatch(branch, stored, write, config) {
122
- if (write === null)
123
- return null;
124
- const merged = isStoredLocaleDictionary(branch, stored, config)
125
- ? { ...stored }
126
- : isBlankLocaleValue(stored) ? {} : { [config.defaultLocale]: stored };
127
- Object.assign(merged, toLocalizedPatch(write, config));
128
- return compactLocalizedDictionary(merged);
129
- }
130
- /** Aliases of the localized branches a write carries — the fields it must merge rather than replace. */
131
- export function localizedAliasesIn(seed, data) {
132
- return seed.branches
133
- .filter((branch) => isLocalizedBranch(branch) && Object.hasOwn(data, branch.alias) && data[branch.alias] !== undefined)
134
- .map((branch) => branch.alias);
135
- }
136
- /**
137
- * Returns `data` with every localized branch it carries merged into `stored` via
138
- * {@link applyLocalizedPatch}; other keys pass through untouched. `stored` is the entry's current values
139
- * (`null` on create, which only compacts). Returns `data` itself when `config` is undefined or no localized
140
- * branch is written.
141
- */
142
- export function mergeLocalizedFields(seed, stored, data, config) {
143
- if (!config)
144
- return data;
145
- const aliases = localizedAliasesIn(seed, data);
146
- if (aliases.length === 0)
147
- return data;
148
- const merged = { ...data };
149
- for (const branch of seed.branches) {
150
- if (!aliases.includes(branch.alias))
151
- continue;
152
- merged[branch.alias] = applyLocalizedPatch(branch, stored?.[branch.alias], data[branch.alias], config);
153
- }
154
- return merged;
155
- }
156
- /**
157
- * Resolves a stored localized value to one language: the requested locale, then the default locale, then the
158
- * first stored translation (stored key order), then `null`. A legacy value that is not a dictionary is returned
159
- * as-is. Non-localized branches return `value` unchanged. `buildSelectQuery` applies the same chain in SQL, so
160
- * filters and ORDER BY compare exactly what a reader sees.
161
- */
162
- export function resolveLocalizedValue(branch, value, locale, config) {
163
- if (!isStoredLocaleDictionary(branch, value, config))
164
- return value;
165
- if (!isBlankLocaleValue(value[locale]))
166
- return value[locale];
167
- if (!isBlankLocaleValue(value[config.defaultLocale]))
168
- return value[config.defaultLocale];
169
- // An implicit config follows `defaultLanguage`; changing it before languages are configured leaves every
170
- // dictionary without its default key. Returning null there would blank the public site (brief §2).
171
- const firstStored = Object.values(value).find((translation) => !isBlankLocaleValue(translation));
172
- return firstStored ?? null;
173
- }
174
- /**
175
- * Returns a copy of `data` with every localized branch resolved to `locale` (default: the default locale)
176
- * via {@link resolveLocalizedValue}. Returns `data` itself when `config` is undefined.
177
- */
178
- export function resolveLocalizedFields(seed, data, config, locale) {
179
- if (!config)
180
- return data;
181
- const target = locale ?? config.defaultLocale;
182
- const resolved = { ...data };
183
- for (const branch of seed.branches) {
184
- if (isLocalizedBranch(branch) && Object.hasOwn(resolved, branch.alias)) {
185
- resolved[branch.alias] = resolveLocalizedValue(branch, resolved[branch.alias], target, config);
186
- }
187
- }
188
- return resolved;
189
- }
190
- /**
191
- * The whole dictionary of a stored localized value, for readers asking every language (`?lang=all`):
192
- * a stored dictionary as-is — unregistered locales included, nothing is hidden (brief §2) — a legacy
193
- * value as `{ [defaultLocale]: value }` (the write path's reading of it), a blank value as `null`.
194
- * Non-localized branches return `value` unchanged.
195
- */
196
- export function asLocaleDictionary(branch, value, config) {
197
- if (!isLocalizedBranch(branch))
198
- return value;
199
- if (isStoredLocaleDictionary(branch, value, config))
200
- return value;
201
- return isBlankLocaleValue(value) ? null : { [config.defaultLocale]: value };
202
- }
203
- /** Returns a copy of `data` with every localized branch it carries passed through {@link asLocaleDictionary}. */
204
- export function asLocaleDictionaries(seed, data, config) {
205
- const expanded = { ...data };
206
- for (const branch of seed.branches) {
207
- if (isLocalizedBranch(branch) && Object.hasOwn(expanded, branch.alias)) {
208
- expanded[branch.alias] = asLocaleDictionary(branch, expanded[branch.alias], config);
209
- }
210
- }
211
- return expanded;
212
- }
@@ -1,58 +0,0 @@
1
- import type { ActorContext, Branch, DataClassification, Seed } from './types.js';
2
- /**
3
- * Computes an un-salted SHA-256 hex digest of a string value.
4
- * Used for basic digest operations.
5
- * @param value - The raw string value to hash.
6
- * @returns A Promise resolving to a 64-character hex string.
7
- */
8
- export declare function sha256hex(value: string): Promise<string>;
9
- /**
10
- * Verifies if a candidate string matches a stored SHA-256 hash digest.
11
- * @param stored - The expected 64-character SHA-256 hex digest.
12
- * @param candidate - The raw candidate string to verify.
13
- * @returns A Promise resolving to true if candidate matches stored digest, false otherwise.
14
- */
15
- export declare function verifyHashField(stored: string, candidate: string): Promise<boolean>;
16
- /**
17
- * Resolved classification rules for a branch, bundling storage strategy and API visibility defaults.
18
- */
19
- export interface ResolvedClassification {
20
- /** Canonical data classification tier. */
21
- classification: DataClassification;
22
- /** Storage mechanism at rest (`plain`, `encrypt` via AES-GCM, or `hash` via HMAC-SHA256). */
23
- storage: 'plain' | 'encrypt' | 'hash';
24
- /** Default visibility rule for unauthenticated public API endpoints (`full` or `hidden`). */
25
- publicVisibility: 'full' | 'hidden';
26
- /** Default visibility rule for authenticated API endpoints (`full` or `hidden`). */
27
- authVisibility: 'full' | 'hidden';
28
- }
29
- /** Normalizes raw policy strings to canonical DataClassification enum. */
30
- export declare function normalizeClassification(raw?: string): DataClassification;
31
- /**
32
- * Resolves the 4-tier DataClassification for a branch, bundling its storage
33
- * strategy at rest and its API visibility rules (public vs auth context).
34
- * @param branch - The seed branch definition to inspect.
35
- * @returns The resolved classification details and visibility rules.
36
- */
37
- export declare function resolveClassification(branch: Branch): ResolvedClassification;
38
- /**
39
- * Resolves branch policies applying all defaults and classification rules.
40
- * @param branch - The seed branch definition to inspect.
41
- * @returns Complete required policies object for the branch.
42
- */
43
- export declare function resolvePolicies(branch: Branch): Required<NonNullable<Branch['policies']>>;
44
- /**
45
- * Filters entry payload fields based on the caller's {@link ActorContext} and branch classification tier.
46
- *
47
- * Rules:
48
- * - Public actor: receives `public` fields; `internal`, `confidential`, and `restricted` fields are omitted.
49
- * - Authenticated actor: receives `public`, `internal`, and `confidential` fields; `restricted` fields are ALWAYS omitted.
50
- * - System actor: receives all fields including `restricted` (used for internal worker/automation orchestration).
51
- *
52
- * @param data - Raw record fields object.
53
- * @param seed - Content type seed definition containing branch definitions.
54
- * @param actor - Context of the caller requesting the entry payload (defaults to authenticated).
55
- * @returns Filtered data record containing only authorized fields for the given actor context.
56
- */
57
- export declare function filterEntryForActor(data: Record<string, unknown>, seed: Seed, actor?: ActorContext): Record<string, unknown>;
58
- //# sourceMappingURL=policies.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../../src/engine/policies.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAEhF;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9D;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEzF;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,cAAc,EAAE,kBAAkB,CAAA;IAClC,6FAA6F;IAC7F,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAA;IACrC,6FAA6F;IAC7F,gBAAgB,EAAE,MAAM,GAAG,QAAQ,CAAA;IACnC,oFAAoF;IACpF,cAAc,EAAE,MAAM,GAAG,QAAQ,CAAA;CAClC;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAQxE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB,CAmC5E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CA2BzF;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,EAAE,IAAI,EACV,KAAK,GAAE,YAAwC,GAC9C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgDzB"}