@plumix/core 0.6.0 → 0.7.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 (418) hide show
  1. package/dist/cache/embedded-tags.d.ts +5 -0
  2. package/dist/cache/embedded-tags.d.ts.map +1 -0
  3. package/dist/cache/embedded-tags.js +30 -0
  4. package/dist/cache/embedded-tags.js.map +1 -0
  5. package/dist/entries/read-service.d.ts.map +1 -1
  6. package/dist/entries/read-service.js +5 -3
  7. package/dist/entries/read-service.js.map +1 -1
  8. package/dist/index.d.ts +3 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/plugin/errors.d.ts +7 -1
  13. package/dist/plugin/errors.d.ts.map +1 -1
  14. package/dist/plugin/errors.js +5 -0
  15. package/dist/plugin/errors.js.map +1 -1
  16. package/dist/plugin/fields/builder.d.ts +106 -0
  17. package/dist/plugin/fields/builder.d.ts.map +1 -0
  18. package/dist/plugin/fields/builder.js +162 -0
  19. package/dist/plugin/fields/builder.js.map +1 -0
  20. package/dist/plugin/fields/color.d.ts +78 -18
  21. package/dist/plugin/fields/color.d.ts.map +1 -1
  22. package/dist/plugin/fields/color.js +116 -25
  23. package/dist/plugin/fields/color.js.map +1 -1
  24. package/dist/plugin/fields/condition.d.ts +40 -0
  25. package/dist/plugin/fields/condition.d.ts.map +1 -0
  26. package/dist/plugin/fields/condition.js +115 -0
  27. package/dist/plugin/fields/condition.js.map +1 -0
  28. package/dist/plugin/fields/contributions.d.ts +233 -0
  29. package/dist/plugin/fields/contributions.d.ts.map +1 -0
  30. package/dist/plugin/fields/contributions.js +1 -0
  31. package/dist/plugin/fields/contributions.js.map +1 -0
  32. package/dist/plugin/fields/entry.d.ts +18 -22
  33. package/dist/plugin/fields/entry.d.ts.map +1 -1
  34. package/dist/plugin/fields/entry.js +13 -21
  35. package/dist/plugin/fields/entry.js.map +1 -1
  36. package/dist/plugin/fields/errors.d.ts +23 -11
  37. package/dist/plugin/fields/errors.d.ts.map +1 -1
  38. package/dist/plugin/fields/errors.js +20 -13
  39. package/dist/plugin/fields/errors.js.map +1 -1
  40. package/dist/plugin/fields/group.d.ts +90 -0
  41. package/dist/plugin/fields/group.d.ts.map +1 -0
  42. package/dist/plugin/fields/group.js +124 -0
  43. package/dist/plugin/fields/group.js.map +1 -0
  44. package/dist/plugin/fields/index.d.ts +22 -45
  45. package/dist/plugin/fields/index.d.ts.map +1 -1
  46. package/dist/plugin/fields/index.js +25 -30
  47. package/dist/plugin/fields/index.js.map +1 -1
  48. package/dist/plugin/fields/json.d.ts +71 -9
  49. package/dist/plugin/fields/json.d.ts.map +1 -1
  50. package/dist/plugin/fields/json.js +110 -16
  51. package/dist/plugin/fields/json.js.map +1 -1
  52. package/dist/plugin/fields/link.d.ts +96 -0
  53. package/dist/plugin/fields/link.d.ts.map +1 -0
  54. package/dist/plugin/fields/link.js +160 -0
  55. package/dist/plugin/fields/link.js.map +1 -0
  56. package/dist/plugin/fields/number.d.ts +86 -13
  57. package/dist/plugin/fields/number.d.ts.map +1 -1
  58. package/dist/plugin/fields/number.js +133 -20
  59. package/dist/plugin/fields/number.js.map +1 -1
  60. package/dist/plugin/fields/range.d.ts +101 -17
  61. package/dist/plugin/fields/range.d.ts.map +1 -1
  62. package/dist/plugin/fields/range.js +153 -38
  63. package/dist/plugin/fields/range.js.map +1 -1
  64. package/dist/plugin/fields/reference.d.ts +171 -0
  65. package/dist/plugin/fields/reference.d.ts.map +1 -0
  66. package/dist/plugin/fields/reference.js +193 -0
  67. package/dist/plugin/fields/reference.js.map +1 -0
  68. package/dist/plugin/fields/repeater.d.ts +111 -8
  69. package/dist/plugin/fields/repeater.d.ts.map +1 -1
  70. package/dist/plugin/fields/repeater.js +157 -66
  71. package/dist/plugin/fields/repeater.js.map +1 -1
  72. package/dist/plugin/fields/richtext-validate.d.ts +7 -0
  73. package/dist/plugin/fields/richtext-validate.d.ts.map +1 -1
  74. package/dist/plugin/fields/richtext-validate.js +11 -5
  75. package/dist/plugin/fields/richtext-validate.js.map +1 -1
  76. package/dist/plugin/fields/richtext.d.ts +78 -12
  77. package/dist/plugin/fields/richtext.d.ts.map +1 -1
  78. package/dist/plugin/fields/richtext.js +121 -26
  79. package/dist/plugin/fields/richtext.js.map +1 -1
  80. package/dist/plugin/fields/select.d.ts +150 -10
  81. package/dist/plugin/fields/select.d.ts.map +1 -1
  82. package/dist/plugin/fields/select.js +205 -17
  83. package/dist/plugin/fields/select.js.map +1 -1
  84. package/dist/plugin/fields/sub-fields.d.ts +12 -0
  85. package/dist/plugin/fields/sub-fields.d.ts.map +1 -0
  86. package/dist/plugin/fields/sub-fields.js +62 -0
  87. package/dist/plugin/fields/sub-fields.js.map +1 -0
  88. package/dist/plugin/fields/temporal.d.ts +124 -0
  89. package/dist/plugin/fields/temporal.d.ts.map +1 -0
  90. package/dist/plugin/fields/temporal.js +180 -0
  91. package/dist/plugin/fields/temporal.js.map +1 -0
  92. package/dist/plugin/fields/term.d.ts +11 -16
  93. package/dist/plugin/fields/term.d.ts.map +1 -1
  94. package/dist/plugin/fields/term.js +11 -19
  95. package/dist/plugin/fields/term.js.map +1 -1
  96. package/dist/plugin/fields/toggle.d.ts +97 -0
  97. package/dist/plugin/fields/toggle.d.ts.map +1 -0
  98. package/dist/plugin/fields/toggle.js +135 -0
  99. package/dist/plugin/fields/toggle.js.map +1 -0
  100. package/dist/plugin/fields/user.d.ts +14 -26
  101. package/dist/plugin/fields/user.d.ts.map +1 -1
  102. package/dist/plugin/fields/user.js +11 -25
  103. package/dist/plugin/fields/user.js.map +1 -1
  104. package/dist/plugin/lookup.d.ts +70 -1
  105. package/dist/plugin/lookup.d.ts.map +1 -1
  106. package/dist/plugin/manifest.d.ts +308 -128
  107. package/dist/plugin/manifest.d.ts.map +1 -1
  108. package/dist/plugin/manifest.js +94 -4
  109. package/dist/plugin/manifest.js.map +1 -1
  110. package/dist/plugin/setup-context.d.ts +5 -4
  111. package/dist/plugin/setup-context.d.ts.map +1 -1
  112. package/dist/plugin/setup-context.js +10 -3
  113. package/dist/plugin/setup-context.js.map +1 -1
  114. package/dist/plugin/validation/meta-box-fields.d.ts.map +1 -1
  115. package/dist/plugin/validation/meta-box-fields.js +16 -0
  116. package/dist/plugin/validation/meta-box-fields.js.map +1 -1
  117. package/dist/route/render/build-resolved-entries.d.ts.map +1 -1
  118. package/dist/route/render/build-resolved-entries.js +7 -2
  119. package/dist/route/render/build-resolved-entries.js.map +1 -1
  120. package/dist/route/render/render-template.d.ts.map +1 -1
  121. package/dist/route/render/render-template.js +12 -2
  122. package/dist/route/render/render-template.js.map +1 -1
  123. package/dist/route/render/template-builders.d.ts +15 -10
  124. package/dist/route/render/template-builders.d.ts.map +1 -1
  125. package/dist/route/render/template-builders.js.map +1 -1
  126. package/dist/rpc/base.d.ts +7 -0
  127. package/dist/rpc/base.d.ts.map +1 -1
  128. package/dist/rpc/errors.d.ts +7 -0
  129. package/dist/rpc/errors.d.ts.map +1 -1
  130. package/dist/rpc/errors.js +16 -0
  131. package/dist/rpc/errors.js.map +1 -1
  132. package/dist/rpc/meta/core.d.ts +89 -10
  133. package/dist/rpc/meta/core.d.ts.map +1 -1
  134. package/dist/rpc/meta/core.js +465 -282
  135. package/dist/rpc/meta/core.js.map +1 -1
  136. package/dist/rpc/meta/field-messages.d.ts +67 -0
  137. package/dist/rpc/meta/field-messages.d.ts.map +1 -0
  138. package/dist/rpc/meta/field-messages.js +67 -0
  139. package/dist/rpc/meta/field-messages.js.map +1 -0
  140. package/dist/rpc/meta/field-pipeline.d.ts +52 -0
  141. package/dist/rpc/meta/field-pipeline.d.ts.map +1 -0
  142. package/dist/rpc/meta/field-pipeline.js +501 -0
  143. package/dist/rpc/meta/field-pipeline.js.map +1 -0
  144. package/dist/rpc/procedures/auth/allowed-domains/create.d.ts +7 -0
  145. package/dist/rpc/procedures/auth/allowed-domains/create.d.ts.map +1 -1
  146. package/dist/rpc/procedures/auth/allowed-domains/delete.d.ts +7 -0
  147. package/dist/rpc/procedures/auth/allowed-domains/delete.d.ts.map +1 -1
  148. package/dist/rpc/procedures/auth/allowed-domains/index.d.ts +28 -0
  149. package/dist/rpc/procedures/auth/allowed-domains/index.d.ts.map +1 -1
  150. package/dist/rpc/procedures/auth/allowed-domains/list.d.ts +7 -0
  151. package/dist/rpc/procedures/auth/allowed-domains/list.d.ts.map +1 -1
  152. package/dist/rpc/procedures/auth/allowed-domains/update.d.ts +7 -0
  153. package/dist/rpc/procedures/auth/allowed-domains/update.d.ts.map +1 -1
  154. package/dist/rpc/procedures/auth/api-tokens/admin-list.d.ts +7 -0
  155. package/dist/rpc/procedures/auth/api-tokens/admin-list.d.ts.map +1 -1
  156. package/dist/rpc/procedures/auth/api-tokens/admin-revoke.d.ts +7 -0
  157. package/dist/rpc/procedures/auth/api-tokens/admin-revoke.d.ts.map +1 -1
  158. package/dist/rpc/procedures/auth/api-tokens/create.d.ts +7 -0
  159. package/dist/rpc/procedures/auth/api-tokens/create.d.ts.map +1 -1
  160. package/dist/rpc/procedures/auth/api-tokens/index.d.ts +35 -0
  161. package/dist/rpc/procedures/auth/api-tokens/index.d.ts.map +1 -1
  162. package/dist/rpc/procedures/auth/api-tokens/list.d.ts +7 -0
  163. package/dist/rpc/procedures/auth/api-tokens/list.d.ts.map +1 -1
  164. package/dist/rpc/procedures/auth/api-tokens/revoke.d.ts +7 -0
  165. package/dist/rpc/procedures/auth/api-tokens/revoke.d.ts.map +1 -1
  166. package/dist/rpc/procedures/auth/credentials/delete.d.ts +7 -0
  167. package/dist/rpc/procedures/auth/credentials/delete.d.ts.map +1 -1
  168. package/dist/rpc/procedures/auth/credentials/index.d.ts +21 -0
  169. package/dist/rpc/procedures/auth/credentials/index.d.ts.map +1 -1
  170. package/dist/rpc/procedures/auth/credentials/list.d.ts +7 -0
  171. package/dist/rpc/procedures/auth/credentials/list.d.ts.map +1 -1
  172. package/dist/rpc/procedures/auth/credentials/rename.d.ts +7 -0
  173. package/dist/rpc/procedures/auth/credentials/rename.d.ts.map +1 -1
  174. package/dist/rpc/procedures/auth/device-flow/approve.d.ts +7 -0
  175. package/dist/rpc/procedures/auth/device-flow/approve.d.ts.map +1 -1
  176. package/dist/rpc/procedures/auth/device-flow/deny.d.ts +7 -0
  177. package/dist/rpc/procedures/auth/device-flow/deny.d.ts.map +1 -1
  178. package/dist/rpc/procedures/auth/device-flow/index.d.ts +21 -0
  179. package/dist/rpc/procedures/auth/device-flow/index.d.ts.map +1 -1
  180. package/dist/rpc/procedures/auth/device-flow/lookup.d.ts +7 -0
  181. package/dist/rpc/procedures/auth/device-flow/lookup.d.ts.map +1 -1
  182. package/dist/rpc/procedures/auth/index.d.ts +154 -0
  183. package/dist/rpc/procedures/auth/index.d.ts.map +1 -1
  184. package/dist/rpc/procedures/auth/login-links.d.ts +7 -0
  185. package/dist/rpc/procedures/auth/login-links.d.ts.map +1 -1
  186. package/dist/rpc/procedures/auth/mailer/index.d.ts +7 -0
  187. package/dist/rpc/procedures/auth/mailer/index.d.ts.map +1 -1
  188. package/dist/rpc/procedures/auth/mailer/test-send.d.ts +7 -0
  189. package/dist/rpc/procedures/auth/mailer/test-send.d.ts.map +1 -1
  190. package/dist/rpc/procedures/auth/oauth-providers.d.ts +7 -0
  191. package/dist/rpc/procedures/auth/oauth-providers.d.ts.map +1 -1
  192. package/dist/rpc/procedures/auth/session.d.ts +7 -0
  193. package/dist/rpc/procedures/auth/session.d.ts.map +1 -1
  194. package/dist/rpc/procedures/auth/sessions/index.d.ts +21 -0
  195. package/dist/rpc/procedures/auth/sessions/index.d.ts.map +1 -1
  196. package/dist/rpc/procedures/auth/sessions/list.d.ts +7 -0
  197. package/dist/rpc/procedures/auth/sessions/list.d.ts.map +1 -1
  198. package/dist/rpc/procedures/auth/sessions/revoke-others.d.ts +7 -0
  199. package/dist/rpc/procedures/auth/sessions/revoke-others.d.ts.map +1 -1
  200. package/dist/rpc/procedures/auth/sessions/revoke.d.ts +7 -0
  201. package/dist/rpc/procedures/auth/sessions/revoke.d.ts.map +1 -1
  202. package/dist/rpc/procedures/entry/activity.d.ts +7 -0
  203. package/dist/rpc/procedures/entry/activity.d.ts.map +1 -1
  204. package/dist/rpc/procedures/entry/bulk.d.ts +21 -0
  205. package/dist/rpc/procedures/entry/bulk.d.ts.map +1 -1
  206. package/dist/rpc/procedures/entry/create-preview-link.d.ts +7 -0
  207. package/dist/rpc/procedures/entry/create-preview-link.d.ts.map +1 -1
  208. package/dist/rpc/procedures/entry/create.d.ts +8 -1
  209. package/dist/rpc/procedures/entry/create.d.ts.map +1 -1
  210. package/dist/rpc/procedures/entry/create.js +8 -8
  211. package/dist/rpc/procedures/entry/create.js.map +1 -1
  212. package/dist/rpc/procedures/entry/dashboard.d.ts +14 -0
  213. package/dist/rpc/procedures/entry/dashboard.d.ts.map +1 -1
  214. package/dist/rpc/procedures/entry/delete-permanent.d.ts +7 -0
  215. package/dist/rpc/procedures/entry/delete-permanent.d.ts.map +1 -1
  216. package/dist/rpc/procedures/entry/discard-draft.d.ts +7 -0
  217. package/dist/rpc/procedures/entry/discard-draft.d.ts.map +1 -1
  218. package/dist/rpc/procedures/entry/duplicate.d.ts +7 -0
  219. package/dist/rpc/procedures/entry/duplicate.d.ts.map +1 -1
  220. package/dist/rpc/procedures/entry/duplicate.js +2 -2
  221. package/dist/rpc/procedures/entry/duplicate.js.map +1 -1
  222. package/dist/rpc/procedures/entry/get.d.ts +7 -0
  223. package/dist/rpc/procedures/entry/get.d.ts.map +1 -1
  224. package/dist/rpc/procedures/entry/get.js +2 -2
  225. package/dist/rpc/procedures/entry/get.js.map +1 -1
  226. package/dist/rpc/procedures/entry/index.d.ts +156 -2
  227. package/dist/rpc/procedures/entry/index.d.ts.map +1 -1
  228. package/dist/rpc/procedures/entry/list.d.ts +7 -0
  229. package/dist/rpc/procedures/entry/list.d.ts.map +1 -1
  230. package/dist/rpc/procedures/entry/lookup.d.ts +8 -2
  231. package/dist/rpc/procedures/entry/lookup.d.ts.map +1 -1
  232. package/dist/rpc/procedures/entry/lookup.js +60 -1
  233. package/dist/rpc/procedures/entry/lookup.js.map +1 -1
  234. package/dist/rpc/procedures/entry/meta.d.ts +37 -3
  235. package/dist/rpc/procedures/entry/meta.d.ts.map +1 -1
  236. package/dist/rpc/procedures/entry/meta.js +63 -7
  237. package/dist/rpc/procedures/entry/meta.js.map +1 -1
  238. package/dist/rpc/procedures/entry/publish.d.ts +7 -0
  239. package/dist/rpc/procedures/entry/publish.d.ts.map +1 -1
  240. package/dist/rpc/procedures/entry/publish.js +6 -4
  241. package/dist/rpc/procedures/entry/publish.js.map +1 -1
  242. package/dist/rpc/procedures/entry/refresh-block-loader.d.ts +7 -0
  243. package/dist/rpc/procedures/entry/refresh-block-loader.d.ts.map +1 -1
  244. package/dist/rpc/procedures/entry/restore.d.ts +7 -0
  245. package/dist/rpc/procedures/entry/restore.d.ts.map +1 -1
  246. package/dist/rpc/procedures/entry/revisions.d.ts +56 -0
  247. package/dist/rpc/procedures/entry/revisions.d.ts.map +1 -1
  248. package/dist/rpc/procedures/entry/schemas.d.ts +2 -2
  249. package/dist/rpc/procedures/entry/schemas.d.ts.map +1 -1
  250. package/dist/rpc/procedures/entry/schemas.js +9 -2
  251. package/dist/rpc/procedures/entry/schemas.js.map +1 -1
  252. package/dist/rpc/procedures/entry/trash.d.ts +7 -0
  253. package/dist/rpc/procedures/entry/trash.d.ts.map +1 -1
  254. package/dist/rpc/procedures/entry/update.d.ts +8 -1
  255. package/dist/rpc/procedures/entry/update.d.ts.map +1 -1
  256. package/dist/rpc/procedures/entry/update.js +77 -18
  257. package/dist/rpc/procedures/entry/update.js.map +1 -1
  258. package/dist/rpc/procedures/lookup/index.d.ts +14 -0
  259. package/dist/rpc/procedures/lookup/index.d.ts.map +1 -1
  260. package/dist/rpc/procedures/lookup/list.d.ts +7 -0
  261. package/dist/rpc/procedures/lookup/list.d.ts.map +1 -1
  262. package/dist/rpc/procedures/lookup/resolve.d.ts +7 -0
  263. package/dist/rpc/procedures/lookup/resolve.d.ts.map +1 -1
  264. package/dist/rpc/procedures/lookup/resolve.js +1 -1
  265. package/dist/rpc/procedures/lookup/resolve.js.map +1 -1
  266. package/dist/rpc/procedures/search/index.d.ts +7 -0
  267. package/dist/rpc/procedures/search/index.d.ts.map +1 -1
  268. package/dist/rpc/procedures/settings/get.d.ts +7 -0
  269. package/dist/rpc/procedures/settings/get.d.ts.map +1 -1
  270. package/dist/rpc/procedures/settings/index.d.ts +14 -0
  271. package/dist/rpc/procedures/settings/index.d.ts.map +1 -1
  272. package/dist/rpc/procedures/settings/upsert.d.ts +7 -0
  273. package/dist/rpc/procedures/settings/upsert.d.ts.map +1 -1
  274. package/dist/rpc/procedures/settings/upsert.js +9 -0
  275. package/dist/rpc/procedures/settings/upsert.js.map +1 -1
  276. package/dist/rpc/procedures/term/create.d.ts +7 -0
  277. package/dist/rpc/procedures/term/create.d.ts.map +1 -1
  278. package/dist/rpc/procedures/term/create.js +3 -3
  279. package/dist/rpc/procedures/term/create.js.map +1 -1
  280. package/dist/rpc/procedures/term/delete.d.ts +7 -0
  281. package/dist/rpc/procedures/term/delete.d.ts.map +1 -1
  282. package/dist/rpc/procedures/term/get.d.ts +7 -0
  283. package/dist/rpc/procedures/term/get.d.ts.map +1 -1
  284. package/dist/rpc/procedures/term/index.d.ts +35 -0
  285. package/dist/rpc/procedures/term/index.d.ts.map +1 -1
  286. package/dist/rpc/procedures/term/list.d.ts +7 -0
  287. package/dist/rpc/procedures/term/list.d.ts.map +1 -1
  288. package/dist/rpc/procedures/term/lookup.d.ts +7 -2
  289. package/dist/rpc/procedures/term/lookup.d.ts.map +1 -1
  290. package/dist/rpc/procedures/term/lookup.js +26 -0
  291. package/dist/rpc/procedures/term/lookup.js.map +1 -1
  292. package/dist/rpc/procedures/term/meta.d.ts +3 -2
  293. package/dist/rpc/procedures/term/meta.d.ts.map +1 -1
  294. package/dist/rpc/procedures/term/meta.js +7 -5
  295. package/dist/rpc/procedures/term/meta.js.map +1 -1
  296. package/dist/rpc/procedures/term/update.d.ts +7 -0
  297. package/dist/rpc/procedures/term/update.d.ts.map +1 -1
  298. package/dist/rpc/procedures/term/update.js +4 -4
  299. package/dist/rpc/procedures/term/update.js.map +1 -1
  300. package/dist/rpc/procedures/user/cancel-email-change.d.ts +7 -0
  301. package/dist/rpc/procedures/user/cancel-email-change.d.ts.map +1 -1
  302. package/dist/rpc/procedures/user/delete.d.ts +7 -0
  303. package/dist/rpc/procedures/user/delete.d.ts.map +1 -1
  304. package/dist/rpc/procedures/user/disable.d.ts +7 -0
  305. package/dist/rpc/procedures/user/disable.d.ts.map +1 -1
  306. package/dist/rpc/procedures/user/enable.d.ts +7 -0
  307. package/dist/rpc/procedures/user/enable.d.ts.map +1 -1
  308. package/dist/rpc/procedures/user/get.d.ts +7 -0
  309. package/dist/rpc/procedures/user/get.d.ts.map +1 -1
  310. package/dist/rpc/procedures/user/get.js +2 -2
  311. package/dist/rpc/procedures/user/get.js.map +1 -1
  312. package/dist/rpc/procedures/user/index.d.ts +77 -0
  313. package/dist/rpc/procedures/user/index.d.ts.map +1 -1
  314. package/dist/rpc/procedures/user/invite.d.ts +7 -0
  315. package/dist/rpc/procedures/user/invite.d.ts.map +1 -1
  316. package/dist/rpc/procedures/user/list.d.ts +7 -0
  317. package/dist/rpc/procedures/user/list.d.ts.map +1 -1
  318. package/dist/rpc/procedures/user/lookup.d.ts +6 -2
  319. package/dist/rpc/procedures/user/lookup.d.ts.map +1 -1
  320. package/dist/rpc/procedures/user/lookup.js +29 -0
  321. package/dist/rpc/procedures/user/lookup.js.map +1 -1
  322. package/dist/rpc/procedures/user/meta.d.ts +3 -2
  323. package/dist/rpc/procedures/user/meta.d.ts.map +1 -1
  324. package/dist/rpc/procedures/user/meta.js +7 -5
  325. package/dist/rpc/procedures/user/meta.js.map +1 -1
  326. package/dist/rpc/procedures/user/pending-email-change.d.ts +7 -0
  327. package/dist/rpc/procedures/user/pending-email-change.d.ts.map +1 -1
  328. package/dist/rpc/procedures/user/request-email-change.d.ts +7 -0
  329. package/dist/rpc/procedures/user/request-email-change.d.ts.map +1 -1
  330. package/dist/rpc/procedures/user/set-locale.d.ts +7 -0
  331. package/dist/rpc/procedures/user/set-locale.d.ts.map +1 -1
  332. package/dist/rpc/procedures/user/update.d.ts +7 -0
  333. package/dist/rpc/procedures/user/update.d.ts.map +1 -1
  334. package/dist/rpc/procedures/user/update.js +4 -4
  335. package/dist/rpc/procedures/user/update.js.map +1 -1
  336. package/dist/rpc/router.d.ts +457 -2
  337. package/dist/rpc/router.d.ts.map +1 -1
  338. package/dist/rpc/validation.d.ts +1 -0
  339. package/dist/rpc/validation.d.ts.map +1 -1
  340. package/dist/rpc/validation.js +1 -0
  341. package/dist/rpc/validation.js.map +1 -1
  342. package/dist/runtime/dispatcher.d.ts.map +1 -1
  343. package/dist/runtime/dispatcher.js +20 -10
  344. package/dist/runtime/dispatcher.js.map +1 -1
  345. package/dist/seo/feed.d.ts.map +1 -1
  346. package/dist/seo/feed.js +7 -1
  347. package/dist/seo/feed.js.map +1 -1
  348. package/dist/settings-core.js +1 -1
  349. package/dist/settings-core.js.map +1 -1
  350. package/dist/template-registry.d.ts +0 -16
  351. package/dist/template-registry.d.ts.map +1 -1
  352. package/dist/terms/read-service.js +2 -2
  353. package/dist/terms/read-service.js.map +1 -1
  354. package/dist/test/traced-context.d.ts +1 -1
  355. package/dist/test/traced-context.d.ts.map +1 -1
  356. package/dist/test/traced-context.js +2 -2
  357. package/dist/test/traced-context.js.map +1 -1
  358. package/package.json +2 -2
  359. package/dist/plugin/fields/checkbox.d.ts +0 -19
  360. package/dist/plugin/fields/checkbox.d.ts.map +0 -1
  361. package/dist/plugin/fields/checkbox.js +0 -20
  362. package/dist/plugin/fields/checkbox.js.map +0 -1
  363. package/dist/plugin/fields/date.d.ts +0 -24
  364. package/dist/plugin/fields/date.d.ts.map +0 -1
  365. package/dist/plugin/fields/date.js +0 -23
  366. package/dist/plugin/fields/date.js.map +0 -1
  367. package/dist/plugin/fields/datetime.d.ts +0 -26
  368. package/dist/plugin/fields/datetime.d.ts.map +0 -1
  369. package/dist/plugin/fields/datetime.js +0 -25
  370. package/dist/plugin/fields/datetime.js.map +0 -1
  371. package/dist/plugin/fields/email.d.ts +0 -17
  372. package/dist/plugin/fields/email.d.ts.map +0 -1
  373. package/dist/plugin/fields/email.js +0 -18
  374. package/dist/plugin/fields/email.js.map +0 -1
  375. package/dist/plugin/fields/entry-list.d.ts +0 -28
  376. package/dist/plugin/fields/entry-list.d.ts.map +0 -1
  377. package/dist/plugin/fields/entry-list.js +0 -31
  378. package/dist/plugin/fields/entry-list.js.map +0 -1
  379. package/dist/plugin/fields/multiselect.d.ts +0 -21
  380. package/dist/plugin/fields/multiselect.d.ts.map +0 -1
  381. package/dist/plugin/fields/multiselect.js +0 -43
  382. package/dist/plugin/fields/multiselect.js.map +0 -1
  383. package/dist/plugin/fields/password.d.ts +0 -24
  384. package/dist/plugin/fields/password.d.ts.map +0 -1
  385. package/dist/plugin/fields/password.js +0 -25
  386. package/dist/plugin/fields/password.js.map +0 -1
  387. package/dist/plugin/fields/radio.d.ts +0 -16
  388. package/dist/plugin/fields/radio.d.ts.map +0 -1
  389. package/dist/plugin/fields/radio.js +0 -17
  390. package/dist/plugin/fields/radio.js.map +0 -1
  391. package/dist/plugin/fields/repeater-validate.d.ts +0 -34
  392. package/dist/plugin/fields/repeater-validate.d.ts.map +0 -1
  393. package/dist/plugin/fields/repeater-validate.js +0 -104
  394. package/dist/plugin/fields/repeater-validate.js.map +0 -1
  395. package/dist/plugin/fields/term-list.d.ts +0 -27
  396. package/dist/plugin/fields/term-list.d.ts.map +0 -1
  397. package/dist/plugin/fields/term-list.js +0 -30
  398. package/dist/plugin/fields/term-list.js.map +0 -1
  399. package/dist/plugin/fields/text.d.ts +0 -37
  400. package/dist/plugin/fields/text.d.ts.map +0 -1
  401. package/dist/plugin/fields/text.js +0 -31
  402. package/dist/plugin/fields/text.js.map +0 -1
  403. package/dist/plugin/fields/textarea.d.ts +0 -20
  404. package/dist/plugin/fields/textarea.d.ts.map +0 -1
  405. package/dist/plugin/fields/textarea.js +0 -21
  406. package/dist/plugin/fields/textarea.js.map +0 -1
  407. package/dist/plugin/fields/time.d.ts +0 -24
  408. package/dist/plugin/fields/time.d.ts.map +0 -1
  409. package/dist/plugin/fields/time.js +0 -23
  410. package/dist/plugin/fields/time.js.map +0 -1
  411. package/dist/plugin/fields/url.d.ts +0 -17
  412. package/dist/plugin/fields/url.d.ts.map +0 -1
  413. package/dist/plugin/fields/url.js +0 -18
  414. package/dist/plugin/fields/url.js.map +0 -1
  415. package/dist/plugin/fields/user-list.d.ts +0 -29
  416. package/dist/plugin/fields/user-list.d.ts.map +0 -1
  417. package/dist/plugin/fields/user-list.js +0 -31
  418. package/dist/plugin/fields/user-list.js.map +0 -1
@@ -1,6 +1,11 @@
1
1
  import { sql } from "drizzle-orm";
2
+ import { accumulateEmbeddedTags } from "../../cache/embedded-tags.js";
2
3
  import { eq } from "../../db/index.js";
4
+ import { isConditionHidden } from "../../plugin/fields/condition.js";
5
+ import { anchorTemporalUtc } from "../../plugin/manifest.js";
3
6
  import { MetaReferenceError } from "./errors.js";
7
+ import { META_FIELD_MESSAGES } from "./field-messages.js";
8
+ import { extractStringId, healReferenceValue, isGroupField, isRepeaterField, referenceTargetOf, runFieldPipeline, } from "./field-pipeline.js";
4
9
  // Shared meta plumbing for every entity that stores a `meta` JSON
5
10
  // column (entries, terms, and — eventually — users). The storage
6
11
  // contract: writes merge into the bag via SQLite `json_set` /
@@ -32,70 +37,100 @@ export class MetaSanitizationError extends Error {
32
37
  return new MetaSanitizationError(ctx.key, "value_too_large");
33
38
  }
34
39
  }
40
+ /**
41
+ * The whole-patch rejection produced by `sanitizeMetaInput` when any
42
+ * field's pipeline reports errors: every `{ path, message }` across
43
+ * every key of the request, so the admin form can address each
44
+ * offending input in one round-trip. Nothing is written when this
45
+ * throws.
46
+ */
47
+ export class MetaValidationError extends Error {
48
+ static {
49
+ MetaValidationError.prototype.name = "MetaValidationError";
50
+ }
51
+ errors;
52
+ constructor(errors) {
53
+ super(`meta validation failed: ${errors.map((error) => error.path).join(", ")}`);
54
+ this.errors = errors;
55
+ }
56
+ }
35
57
  /**
36
58
  * Validate an incoming meta map against a field-lookup fn produced by
37
59
  * the caller (entry vs term differ only in which registry they walk).
38
- * Unregistered keys and type-coercion failures throw so the caller can
39
- * surface them as 4xx before any write. `null` / `undefined` values
40
- * are deletion requests; everything else is coerced + sanitized.
60
+ * `null` / `undefined` values are deletion requests; everything else
61
+ * runs the per-field pipeline (coercion `.sanitize()` declarative
62
+ * constraints `.validate()`). Pipeline rejections aggregate across
63
+ * the whole patch into one `MetaValidationError`; unregistered keys
64
+ * and oversized values keep the legacy fail-fast
65
+ * `MetaSanitizationError` surface.
41
66
  */
42
- export function sanitizeMetaInput(findField, input) {
67
+ export async function sanitizeMetaInput(findField, input, mode = "strict") {
43
68
  if (input === undefined)
44
69
  return null;
45
70
  const upserts = new Map();
46
71
  const deletes = [];
72
+ const fieldErrors = [];
47
73
  for (const [key, rawValue] of Object.entries(input)) {
48
74
  const field = findField(key);
49
75
  if (!field) {
76
+ // A delete request (null/undefined) for a key the field system doesn't
77
+ // own is a harmless no-op — never fail the whole write over an untracked
78
+ // foreign key (e.g. one written by another plugin). Unknown *upserts*
79
+ // still reject so junk can't enter through this gate.
80
+ if (rawValue === null || rawValue === undefined)
81
+ continue;
50
82
  throw MetaSanitizationError.notRegistered({ key });
51
83
  }
52
- if (rawValue === null || rawValue === undefined) {
84
+ if (isConditionHidden(field, input))
85
+ continue;
86
+ const result = await runFieldPipeline(field, rawValue, key, mode);
87
+ if (result.errors.length > 0) {
88
+ fieldErrors.push(...result.errors);
89
+ continue;
90
+ }
91
+ if (result.isDeletion === true) {
53
92
  deletes.push(key);
54
93
  continue;
55
94
  }
56
- const coerced = coerceToType(field.type, key, rawValue);
57
- const sanitized = field.sanitize
58
- ? runSanitize(field.sanitize, key, coerced)
59
- : coerced;
60
- assertEncodedSize(key, sanitized);
61
- upserts.set(key, sanitized);
95
+ assertEncodedSize(key, result.value);
96
+ upserts.set(key, result.value);
97
+ }
98
+ if (fieldErrors.length > 0) {
99
+ throw new MetaValidationError(fieldErrors);
62
100
  }
63
101
  return { upserts, deletes };
64
102
  }
65
103
  /**
66
- * Run a field's sanitize callback inside a try/catch any thrown
67
- * `MetaSanitizationError` propagates as-is (so callbacks that already
68
- * use the precise error type can opt into specific reasons), but
69
- * generic errors (including plain `Error("invalid_value")` thrown
70
- * from builder-injected default sanitizers) are translated into a
71
- * uniform `invalid_value` failure. Callbacks therefore stay free to
72
- * throw vanilla errors without importing the package-internal error
73
- * class.
104
+ * Thin wrapper that translates thrown meta errors into the RPC
105
+ * handler's CONFLICT envelope. Pipeline rejections ship their
106
+ * `{ path, message }` list under `data.errors` (with `key` pointing
107
+ * at the first error's top-level field for legacy consumers); the
108
+ * fail-fast `MetaSanitizationError` reasons keep their existing
109
+ * `data.reason`/`data.key` shape.
74
110
  */
75
- function runSanitize(sanitize, key, value) {
76
- try {
77
- return sanitize(value);
78
- }
79
- catch (error) {
80
- if (error instanceof MetaSanitizationError)
81
- throw error;
82
- // Buggy sanitize callbacks otherwise round to a generic
83
- // `invalid_value` envelope, which is fine for the editor's UX
84
- // but loses the underlying stack. Log it before translating so
85
- // server logs preserve the diagnostic trail.
86
- console.error(`[plumix] sanitize callback for meta key ${JSON.stringify(key)} threw:`, error);
87
- throw MetaSanitizationError.invalidValue({ key });
88
- }
89
- }
90
111
  /**
91
- * Thin wrapper that translates a thrown `MetaSanitizationError` into
92
- * the RPC handler's CONFLICT envelope.
112
+ * Map a whole-patch meta validation failure onto the RPC `CONFLICT`
113
+ * envelope — one place so the wire shape (`key` at the first error's
114
+ * top-level field, the full `errors` list) can't drift between the write
115
+ * path and the publish gate.
93
116
  */
94
- export function sanitizeMetaForRpc(findField, input, errors) {
117
+ export function metaValidationConflict(error, errors) {
118
+ return errors.CONFLICT({
119
+ data: {
120
+ reason: "meta_invalid_value",
121
+ key: error.errors[0]?.path.split(".")[0],
122
+ errors: [...error.errors],
123
+ },
124
+ });
125
+ }
126
+ export async function sanitizeMetaForRpc(findField, input, errors, mode = "strict") {
95
127
  try {
96
- return sanitizeMetaInput(findField, input);
128
+ return await sanitizeMetaInput(findField, input, mode);
97
129
  }
98
130
  catch (error) {
131
+ if (error instanceof MetaValidationError) {
132
+ throw metaValidationConflict(error, errors);
133
+ }
99
134
  if (error instanceof MetaSanitizationError) {
100
135
  throw errors.CONFLICT({
101
136
  data: { reason: `meta_${error.reason}`, key: error.key },
@@ -104,6 +139,58 @@ export function sanitizeMetaForRpc(findField, input, errors) {
104
139
  throw error;
105
140
  }
106
141
  }
142
+ /**
143
+ * Strict publish gate: validate a whole *stored* meta bag against the full
144
+ * field list before `entry.publish` promotes it onto the live row. Draft
145
+ * autosaves are lenient (business rules skipped so work-in-progress always
146
+ * saves), so publish is where required fields, bounds, formats, option
147
+ * membership, and row counts are finally enforced. It walks the field
148
+ * *definitions*, not just the bag, so a required field ABSENT from the bag
149
+ * is caught as well as one stored empty — `runFieldPipeline` sees
150
+ * `undefined` and rejects it in strict mode.
151
+ *
152
+ * Rejections aggregate across the bag into one `MetaValidationError` the
153
+ * RPC layer maps onto the admin inputs, blocking the publish. A passing bag
154
+ * comes back canonicalized (each registered value re-run through
155
+ * `.sanitize()`); a conditionally-hidden field can't be required, so it's
156
+ * skipped; keys the field system doesn't own (e.g. from an uninstalled
157
+ * plugin) pass through untouched, matching the read path. Field
158
+ * capabilities and reference existence are not re-checked here — a
159
+ * whole-bag gate would block a publisher over a co-author's field, and both
160
+ * were already enforced at write time.
161
+ */
162
+ export async function validateAndPromoteMetaBag(fields, bag) {
163
+ const out = {};
164
+ const owned = new Set();
165
+ const fieldErrors = [];
166
+ for (const field of fields) {
167
+ owned.add(field.key);
168
+ // A hidden field is inactive, so it can't be required — but its stored
169
+ // value is kept untouched (not validated, not dropped), or the value a
170
+ // driver hides would be lost on publish and gone when the driver flips
171
+ // back.
172
+ if (isConditionHidden(field, bag)) {
173
+ if (field.key in bag)
174
+ out[field.key] = bag[field.key];
175
+ continue;
176
+ }
177
+ const result = await runFieldPipeline(field, bag[field.key], field.key);
178
+ if (result.errors.length > 0) {
179
+ fieldErrors.push(...result.errors);
180
+ continue;
181
+ }
182
+ if (result.isDeletion === true)
183
+ continue;
184
+ out[field.key] = result.value;
185
+ }
186
+ for (const [key, value] of Object.entries(bag)) {
187
+ if (!owned.has(key))
188
+ out[key] = value;
189
+ }
190
+ if (fieldErrors.length > 0)
191
+ throw new MetaValidationError(fieldErrors);
192
+ return out;
193
+ }
107
194
  /**
108
195
  * Walk a sanitized patch, group every reference upsert by
109
196
  * `(kind, scope)`, and issue one `LookupAdapter.list({ ids })`
@@ -118,7 +205,7 @@ export function sanitizeMetaForRpc(findField, input, errors) {
118
205
  * TOCTOU note: validate runs in a separate query from the eventual
119
206
  * `applyMetaPatch`, and callers don't share a transaction. A
120
207
  * concurrent delete between validate and apply leaves an orphan id
121
- * in the meta bag; `filterMetaOrphans` masks it on read. Wrap the
208
+ * in the meta bag; `hydrateMetaBags` masks it on read. Wrap the
122
209
  * validate/apply pair in `ctx.db.transaction()` if a caller needs
123
210
  * serializable consistency.
124
211
  */
@@ -142,17 +229,15 @@ export async function validateMetaReferences(ctx, findField, patch) {
142
229
  group.contributions.push({ errorKey: key, ids });
143
230
  continue;
144
231
  }
145
- // Repeater fields don't carry a `referenceTarget` themselves but
146
- // their rows can. Walk into rows so nested `entry` / `term` /
147
- // `user` / `media` refs flow through the same `(kind, scope)`
148
- // batch as top-level fields. Errors attribute to the top-level
149
- // repeater key the row index + subKey live in the developer
150
- // log, not the wire response (per slice acceptance).
151
- if (!isRepeaterField(field))
152
- continue;
153
- if (!Array.isArray(value))
154
- continue;
155
- collectRepeaterReferences(ctx, key, field, value, groups);
232
+ // Composite fields (repeater / group) don't carry a
233
+ // `referenceTarget` themselves but their rows / members can, at any
234
+ // nesting depth. Walk them so nested `entry` / `term` / `user` /
235
+ // `media` refs flow through the same `(kind, scope)` batch as
236
+ // top-level fields. Errors attribute to the top-level key the
237
+ // dotted sub-path lives in the developer log, not the wire response.
238
+ if (isRepeaterField(field) || isGroupField(field)) {
239
+ collectCompositeReferences(ctx, key, key, field, value, groups);
240
+ }
156
241
  }
157
242
  for (const group of groups.values()) {
158
243
  if (group.ids.size === 0)
@@ -163,15 +248,13 @@ export async function validateMetaReferences(ctx, findField, patch) {
163
248
  if (!liveIds.has(id)) {
164
249
  if (contribution.diagnostic !== undefined) {
165
250
  // Wire error keys on the top-level field; this log line
166
- // is the only place the row index + subKey surface, so
167
- // an engineer debugging a `meta_invalid_value` on a
168
- // repeater can locate the offending subField without
169
- // bisecting the saved bag. `JSON.stringify(id)` escapes
170
- // control characters so a user-supplied id with newlines
171
- // can't poison the log stream.
172
- console.error(`[plumix] meta repeater ${JSON.stringify(contribution.errorKey)} ` +
173
- `row ${String(contribution.diagnostic.rowIdx)} ` +
174
- `subField ${JSON.stringify(contribution.diagnostic.subKey)} ` +
251
+ // is the only place the nested sub-path surfaces, so an
252
+ // engineer debugging a `meta_invalid_value` on a composite
253
+ // field can locate the offending cell without bisecting the
254
+ // saved bag. `JSON.stringify` escapes control characters so
255
+ // a user-supplied id with newlines can't poison the log.
256
+ console.error(`[plumix] meta composite ${JSON.stringify(contribution.errorKey)} ` +
257
+ `at ${JSON.stringify(contribution.diagnostic.path)} ` +
175
258
  `references missing id ${JSON.stringify(id)}`);
176
259
  }
177
260
  throw MetaSanitizationError.invalidValue({
@@ -182,44 +265,57 @@ export async function validateMetaReferences(ctx, findField, patch) {
182
265
  }
183
266
  }
184
267
  }
185
- function isRepeaterField(field) {
186
- if (!field)
187
- return false;
188
- return field.inputType === "repeater";
189
- }
190
- function collectRepeaterReferences(ctx, topKey, field, rows, groups) {
191
- // Nested repeaters are rejected at registration by `repeater()`, so
192
- // any subField that's itself a repeater wouldn't get here in
193
- // practice. We don't recurse into subField repeaters either way —
194
- // single-source-of-truth on the registration guard.
195
- for (const [rowIdx, row] of rows.entries()) {
196
- if (!row || typeof row !== "object" || Array.isArray(row))
197
- continue;
198
- const rowObj = row;
199
- for (const subField of field.subFields) {
200
- const target = referenceTargetOf(subField);
201
- if (!target)
268
+ // Walk a composite field's live value, collecting + normalizing every
269
+ // nested reference in place. `path` is the dotted address of `field`
270
+ // from the top-level key (developer-log only). Recurses through nested
271
+ // repeaters and groups, so a reference at any depth flows through the
272
+ // same `(kind, scope)` batch and is orphan-checked at write time.
273
+ function collectCompositeReferences(ctx, topKey, path, field, value, groups) {
274
+ if (isRepeaterField(field)) {
275
+ if (!Array.isArray(value))
276
+ return;
277
+ for (const [rowIdx, row] of value.entries()) {
278
+ if (!isPlainObject(row))
202
279
  continue;
203
- const subValue = rowObj[subField.key];
280
+ collectMemberReferences(ctx, topKey, `${path}.${String(rowIdx)}`, field.subFields, row, groups);
281
+ }
282
+ return;
283
+ }
284
+ if (isGroupField(field)) {
285
+ if (!isPlainObject(value))
286
+ return;
287
+ collectMemberReferences(ctx, topKey, path, field.fields, value, groups);
288
+ }
289
+ }
290
+ function collectMemberReferences(ctx, topKey, path, members, container, groups) {
291
+ for (const member of members) {
292
+ const memberPath = `${path}.${member.key}`;
293
+ const subValue = container[member.key];
294
+ const target = referenceTargetOf(member);
295
+ if (target) {
204
296
  if (subValue === undefined || subValue === null)
205
297
  continue;
206
298
  const registered = ctx.plugins.lookupAdapters.get(target.kind);
207
299
  if (!registered) {
208
300
  throw MetaSanitizationError.invalidValue({ key: topKey });
209
301
  }
210
- const ids = referenceIdsForValidation(topKey, subValue, target, fieldMax(subField));
211
- // Normalize the row's slot in place — the top-level upsert array
212
- // is the live storage shape, so mutating an object inside it is
213
- // what the caller will serialize.
214
- rowObj[subField.key] = target.multiple ? ids : ids[0];
302
+ const ids = referenceIdsForValidation(topKey, subValue, target, fieldMax(member));
303
+ // Normalize the cell in place — the top-level upsert value is the
304
+ // live storage shape, so mutating an object inside it is what the
305
+ // caller serializes.
306
+ container[member.key] = target.multiple ? ids : ids[0];
215
307
  const group = upsertGroup(groups, target, registered);
216
308
  for (const id of ids)
217
309
  group.ids.add(id);
218
310
  group.contributions.push({
219
311
  errorKey: topKey,
220
312
  ids,
221
- diagnostic: { rowIdx, subKey: subField.key },
313
+ diagnostic: { path: memberPath },
222
314
  });
315
+ continue;
316
+ }
317
+ if (isRepeaterField(member) || isGroupField(member)) {
318
+ collectCompositeReferences(ctx, topKey, memberPath, member, subValue, groups);
223
319
  }
224
320
  }
225
321
  }
@@ -319,15 +415,6 @@ function referenceItemId(key, item) {
319
415
  return id;
320
416
  throw MetaSanitizationError.invalidValue({ key });
321
417
  }
322
- // Returns the `id` string of a `{ id: string, ... }` object, or null
323
- // for any other shape (string, array, null, primitive, missing key).
324
- function extractStringId(value) {
325
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
326
- return null;
327
- }
328
- const id = value.id;
329
- return typeof id === "string" ? id : null;
330
- }
331
418
  function fieldMax(field) {
332
419
  return field?.max;
333
420
  }
@@ -344,145 +431,303 @@ export async function validateMetaReferencesForRpc(ctx, findField, patch, errors
344
431
  catch (error) {
345
432
  if (error instanceof MetaSanitizationError) {
346
433
  throw errors.CONFLICT({
347
- data: { reason: `meta_${error.reason}`, key: error.key },
434
+ data: {
435
+ reason: `meta_${error.reason}`,
436
+ key: error.key,
437
+ // Reference failures address the top-level field (row/subKey
438
+ // detail stays in the server log) — shipping them under
439
+ // `errors` too lets the admin form surface them inline.
440
+ errors: [
441
+ { path: error.key, message: META_FIELD_MESSAGES.invalidOption },
442
+ ],
443
+ },
348
444
  });
349
445
  }
350
446
  throw error;
351
447
  }
352
448
  }
449
+ // A reference field authored with `.returns("id")` reads the bare
450
+ // stored id — the hydration walk skips it so no lookup query runs and
451
+ // the id survives untouched. `"returns" in field` narrows to the field
452
+ // variants that carry the flag (temporal's `"date"` never matches).
453
+ function readsRawReferenceId(field) {
454
+ return field !== undefined && "returns" in field && field.returns === "id";
455
+ }
353
456
  /**
354
457
  * Yield every reference-field occurrence in a decoded meta bag: top-level
355
- * reference fields, plus reference subFields inside each repeater row. One
356
- * structural walk so the orphan-strip pass doesn't reinline the
357
- * top-level/repeater traversal twice.
458
+ * reference fields, plus references nested inside repeater rows and
459
+ * groups at any depth. One structural walk so the hydration pass doesn't
460
+ * reinline the traversal twice.
358
461
  */
359
462
  function* referenceOccurrences(entries, findField) {
360
463
  for (const [key, value] of entries) {
361
- const field = findField(key);
362
- const target = referenceTargetOf(field);
363
- if (target) {
364
- yield { key, target, value };
365
- continue;
366
- }
367
- if (!isRepeaterField(field))
368
- continue;
464
+ yield* fieldOccurrences([key], findField(key), value);
465
+ }
466
+ }
467
+ // Recurse a single field's decoded value, yielding reference occurrences
468
+ // with their full path. Nested field definitions come straight off the
469
+ // composite field (`subFields` / `fields`) — `findField` only resolves
470
+ // top-level keys.
471
+ function* fieldOccurrences(path, field, value) {
472
+ const target = referenceTargetOf(field);
473
+ if (target) {
474
+ // `.returns("id")` opts out of the hydration join at any depth —
475
+ // leave the stored id(s) untouched (no resolve, no orphan-strip).
476
+ if (!readsRawReferenceId(field))
477
+ yield { path, target, value };
478
+ return;
479
+ }
480
+ if (isRepeaterField(field)) {
369
481
  if (!Array.isArray(value))
370
- continue;
482
+ return;
371
483
  for (const [rowIdx, row] of value.entries()) {
372
- if (!row || typeof row !== "object" || Array.isArray(row))
484
+ if (!isPlainObject(row))
373
485
  continue;
374
- const rowObj = row;
375
486
  for (const subField of field.subFields) {
376
- const subTarget = referenceTargetOf(subField);
377
- if (!subTarget)
378
- continue;
379
- yield {
380
- key,
381
- target: subTarget,
382
- value: rowObj[subField.key],
383
- nested: { rowIdx, subKey: subField.key },
384
- };
487
+ yield* fieldOccurrences([...path, rowIdx, subField.key], subField, row[subField.key]);
385
488
  }
386
489
  }
490
+ return;
387
491
  }
492
+ if (isGroupField(field)) {
493
+ if (!isPlainObject(value))
494
+ return;
495
+ for (const member of field.fields) {
496
+ yield* fieldOccurrences([...path, member.key], member, value[member.key]);
497
+ }
498
+ }
499
+ }
500
+ /** Single-bag convenience over {@link hydrateMetaBags}. */
501
+ export async function hydrateMetaReferences(ctx, findField, decoded) {
502
+ const [bag] = await hydrateMetaBags(ctx, [{ findField, decoded }]);
503
+ // hydrateMetaBags returns one bag per input by construction.
504
+ return bag ?? decoded;
388
505
  }
389
- export async function filterMetaOrphans(ctx, findField, decoded) {
506
+ export async function hydrateMetaBags(ctx, bags) {
390
507
  const candidates = [];
391
508
  const groups = new Map();
392
- // Top-level refs and repeater-row subField refs get identical orphan-strip
393
- // treatment, so the single `referenceOccurrences` walk feeds both. Nested
394
- // candidates carry their `rowIdx`/`subKey`; Pass 3 rewrites the row slot in
395
- // a per-key clone so the caller's `decoded` bag stays untouched.
396
- for (const occ of referenceOccurrences(Object.entries(decoded), findField)) {
397
- const registered = ctx.plugins.lookupAdapters.get(occ.target.kind);
398
- if (!registered)
399
- continue;
400
- const ids = orphanCandidateIds(occ.target, occ.value);
401
- if (ids === null)
402
- continue; // non-array multi / non-string single — leave untouched
403
- const groupKey = referenceGroupKey(occ.target);
404
- candidates.push({
405
- key: occ.key,
406
- multiple: occ.target.multiple === true,
407
- groupKey,
408
- ids,
409
- nested: occ.nested,
410
- });
411
- if (ids.length === 0)
412
- continue;
413
- let group = groups.get(groupKey);
414
- if (!group) {
415
- group = { registered, scope: occ.target.scope, ids: new Set() };
416
- groups.set(groupKey, group);
417
- }
418
- for (const id of ids)
419
- group.ids.add(id);
420
- }
421
- // Pass 2: one `list({ ids })` per group, keyed for O(1) apply lookup.
422
- const liveIdsByGroup = new Map();
423
- for (const [groupKey, group] of groups) {
424
- const liveIds = await fetchLiveIds(ctx, group.registered, group.scope, group.ids, "filterMetaOrphans");
425
- liveIdsByGroup.set(groupKey, liveIds);
426
- }
427
- // Pass 3: apply the filter. Multi refs filter the string[] dense
428
- // and in order; single refs null on missing. Nested-in-repeater
429
- // candidates rewrite the row's subField slot in a per-key cloned
430
- // array so the input bag stays untouched.
431
- const out = { ...decoded };
432
- const empty = new Set();
433
- for (const candidate of candidates) {
434
- const { key, multiple, groupKey, ids, nested } = candidate;
435
- const liveIds = liveIdsByGroup.get(groupKey) ?? empty;
436
- if (nested !== undefined) {
437
- const rowObj = takeWritableRow(out, decoded, key, nested.rowIdx);
438
- if (!rowObj)
509
+ // Top-level refs and nested (repeater / group) refs get identical
510
+ // treatment, so the single `referenceOccurrences` walk feeds both.
511
+ // Every candidate carries its full path; Pass 3 rewrites the slot in a
512
+ // per-path copy-on-write so callers' bags stay untouched.
513
+ const out = [];
514
+ for (const bag of bags) {
515
+ const outBag = { ...bag.decoded };
516
+ out.push(outBag);
517
+ for (const occ of referenceOccurrences(Object.entries(bag.decoded), bag.findField)) {
518
+ const registered = ctx.plugins.lookupAdapters.get(occ.target.kind);
519
+ if (!registered)
439
520
  continue;
440
- applyOrphanToSlot(rowObj, nested.subKey, multiple, ids, liveIds);
441
- continue;
521
+ const ids = referenceCandidateIds(occ.target, occ.value);
522
+ if (ids === null)
523
+ continue; // non-array multi / non-string single — leave untouched
524
+ const groupKey = referenceGroupKey(occ.target);
525
+ candidates.push({
526
+ outBag,
527
+ decoded: bag.decoded,
528
+ path: occ.path,
529
+ multiple: occ.target.multiple === true,
530
+ groupKey,
531
+ ids,
532
+ });
533
+ if (ids.length === 0)
534
+ continue;
535
+ let group = groups.get(groupKey);
536
+ if (!group) {
537
+ group = { registered, scope: occ.target.scope, ids: new Set() };
538
+ groups.set(groupKey, group);
539
+ }
540
+ for (const id of ids)
541
+ group.ids.add(id);
442
542
  }
443
- if (multiple) {
444
- out[key] = ids.filter((id) => liveIds.has(id));
543
+ }
544
+ // Pass 2: one `hydrate({ ids })` (or `list({ ids })` fallback) per
545
+ // group, keyed for O(1) apply lookup. Groups are independent, so a
546
+ // bag mixing entry + user + media refs resolves them concurrently.
547
+ const resolutions = new Map(await Promise.all([...groups].map(async ([groupKey, group]) => [
548
+ groupKey,
549
+ await resolveGroup(ctx, group.registered.adapter, group.scope, group.ids),
550
+ ])));
551
+ // Pass 3: apply. Hydrated groups replace ids with their payloads;
552
+ // id-only groups keep ids. Multi refs stay dense and in stored
553
+ // order; single refs null on missing. Nested candidates copy-on-write
554
+ // each container down their path so callers' bags stay untouched.
555
+ const emptyResolution = { kind: "ids", liveIds: new Set() };
556
+ for (const candidate of candidates) {
557
+ const { outBag, decoded, path, multiple, groupKey, ids } = candidate;
558
+ const resolution = resolutions.get(groupKey) ?? emptyResolution;
559
+ const slot = takeWritableSlot(outBag, decoded, path);
560
+ if (!slot)
445
561
  continue;
446
- }
447
- const [singleId] = ids;
448
- if (singleId === undefined)
449
- continue; // single non-string already filtered upstream
450
- if (!liveIds.has(singleId))
451
- out[key] = null;
562
+ applyResolutionToSlot(slot.parent, slot.leafKey, multiple, ids, resolution);
452
563
  }
453
564
  return out;
454
565
  }
455
- function takeWritableRow(out, decoded, key, rowIdx) {
456
- // Lazily clone the rows array (and the targeted row inside it) on
457
- // first nested write so the caller's `decoded` bag stays untouched.
458
- // Subsequent nested writes hit the same cloned array.
459
- let arr = out[key];
460
- if (arr === decoded[key]) {
461
- if (!Array.isArray(arr))
462
- return null;
463
- const cloned = arr.map((row) => row && typeof row === "object" && !Array.isArray(row)
464
- ? { ...row }
465
- : row);
466
- out[key] = cloned;
467
- arr = cloned;
566
+ /**
567
+ * Batched, tag-accounted hydration of a raw id set for one reference kind
568
+ * the theme-facing counterpart to the meta pipeline's hydration (#1508).
569
+ * A theme holding an id-only reference field (a field declared
570
+ * `.returns("id")`, or one contributed by a third-party plugin) resolves
571
+ * it here instead of hand-rolled per-item fetches: ids resolve through the
572
+ * adapter's batched `hydrate` (chunked, one in-query per chunk) and every
573
+ * resolved entity folds its cache tag into the page through the same
574
+ * accumulator the meta pipeline uses, so the page is purged when an
575
+ * embedded entity changes.
576
+ *
577
+ * Returns the hydrated payloads dense and in the requested id order — ids
578
+ * that are gone or out of scope are dropped, mirroring multi-reference
579
+ * field hydration. An unregistered kind, an adapter without the `hydrate`
580
+ * contract, or an empty id set yields `[]`.
581
+ */
582
+ export async function hydrateReferences(ctx, kind, ids, options = {}) {
583
+ const registered = ctx.plugins.lookupAdapters.get(kind);
584
+ if (!registered?.adapter.hydrate)
585
+ return [];
586
+ const unique = new Set(ids.filter((id) => id !== ""));
587
+ if (unique.size === 0)
588
+ return [];
589
+ const resolution = await resolveGroup(ctx, registered.adapter, options.scope, unique);
590
+ if (resolution.kind !== "hydrated")
591
+ return [];
592
+ return ids
593
+ .map((id) => resolution.byId.get(id))
594
+ .filter((p) => p !== undefined);
595
+ }
596
+ // Resolve one `(kind, scope)` group's aggregated ids. Chunked at
597
+ // `HYDRATION_QUERY_ID_LIMIT` per in-query: a response-level group can
598
+ // legitimately aggregate more ids than one query may carry (a
599
+ // 100-entry archive × multi-reference fields), and a read-path throw
600
+ // would kill the render — unlike the write-side `fetchLiveIds`, which
601
+ // keeps throwing because a single patch exceeding the ceiling is a
602
+ // caller bug. Ids are de-duped before chunking, so per-query batches
603
+ // stay bounded and nothing is truncated.
604
+ async function resolveGroup(ctx, adapter, scope, ids) {
605
+ const idList = [...ids];
606
+ if (adapter.hydrate) {
607
+ const byId = new Map();
608
+ for (const chunk of chunkIds(idList)) {
609
+ const payloads = await adapter.hydrate(ctx, { ids: chunk, scope });
610
+ for (const payload of payloads) {
611
+ byId.set(payload.id, payload);
612
+ // Fold this embedded entity's cache tag into the page's tags so
613
+ // a change to it purges the page that hydrated it (#1508). Runs
614
+ // on every read surface; only the public read-through reads the
615
+ // accumulator back, so admin/REST reads populate it harmlessly.
616
+ if (adapter.embeddedCacheTags) {
617
+ accumulateEmbeddedTags(ctx, adapter.embeddedCacheTags(payload));
618
+ }
619
+ }
620
+ }
621
+ return { kind: "hydrated", byId };
622
+ }
623
+ const liveIds = new Set();
624
+ for (const chunk of chunkIds(idList)) {
625
+ const rows = await adapter.list(ctx, {
626
+ ids: chunk,
627
+ scope,
628
+ limit: chunk.length,
629
+ });
630
+ for (const row of rows)
631
+ liveIds.add(row.id);
632
+ }
633
+ return { kind: "ids", liveIds };
634
+ }
635
+ // Per-query id cap for the read path. 100 (not the 1000 aggregate
636
+ // ceiling) because Cloudflare D1 caps bound parameters at 100 per
637
+ // statement and `inArray` binds one per id — a bigger chunk works on
638
+ // local SQLite and dies in production.
639
+ const HYDRATION_QUERY_ID_LIMIT = 100;
640
+ function* chunkIds(ids) {
641
+ for (let i = 0; i < ids.length; i += HYDRATION_QUERY_ID_LIMIT) {
642
+ yield ids.slice(i, i + HYDRATION_QUERY_ID_LIMIT);
468
643
  }
469
- if (!Array.isArray(arr))
470
- return null;
471
- const row = arr[rowIdx];
472
- if (!row || typeof row !== "object" || Array.isArray(row))
473
- return null;
474
- return row;
475
644
  }
476
- function applyOrphanToSlot(rowObj, subKey, multiple, ids, liveIds) {
645
+ // Write one candidate's resolved value into its slot — the leaf object
646
+ // key of a container reached by walking the candidate's path.
647
+ function applyResolutionToSlot(slot, key, multiple, ids, resolution) {
477
648
  if (multiple) {
478
- rowObj[subKey] = ids.filter((id) => liveIds.has(id));
649
+ slot[key] =
650
+ resolution.kind === "hydrated"
651
+ ? ids
652
+ .map((id) => resolution.byId.get(id))
653
+ .filter((payload) => payload !== undefined)
654
+ : ids.filter((id) => resolution.liveIds.has(id));
479
655
  return;
480
656
  }
481
657
  const [singleId] = ids;
482
658
  if (singleId === undefined)
659
+ return; // single non-string already filtered upstream
660
+ if (resolution.kind === "hydrated") {
661
+ slot[key] = resolution.byId.get(singleId) ?? null;
483
662
  return;
484
- if (!liveIds.has(singleId))
485
- rowObj[subKey] = null;
663
+ }
664
+ if (!resolution.liveIds.has(singleId))
665
+ slot[key] = null;
666
+ }
667
+ // Walk the candidate's path from the output bag to the parent container
668
+ // of its leaf slot, copy-on-writing each container the first time it's
669
+ // descended so the caller's `decoded` bag stays untouched. Containers
670
+ // cloned by an earlier candidate (identity no longer matches `decoded`)
671
+ // are reused, so sibling references in the same row/group land in one
672
+ // clone. Returns the writable parent object and the leaf key, or null if
673
+ // any segment's runtime shape doesn't match the declared structure
674
+ // (hand-edited / migrated bags).
675
+ function takeWritableSlot(outBag, decoded, path) {
676
+ let outContainer = outBag;
677
+ let decContainer = decoded;
678
+ for (let i = 0; i < path.length - 1; i++) {
679
+ const seg = path[i];
680
+ if (seg === undefined)
681
+ return null;
682
+ const outChild = readSegment(outContainer, seg);
683
+ const decChild = readSegment(decContainer, seg);
684
+ let writable = outChild;
685
+ // Still the shared decoded child → clone it into the output tree.
686
+ if (outChild === decChild) {
687
+ const clone = cloneContainer(outChild);
688
+ if (clone === null)
689
+ return null;
690
+ writeSegment(outContainer, seg, clone);
691
+ writable = clone;
692
+ }
693
+ outContainer = writable;
694
+ decContainer = decChild;
695
+ }
696
+ const leaf = path[path.length - 1];
697
+ if (typeof leaf !== "string")
698
+ return null;
699
+ if (!isPlainObject(outContainer))
700
+ return null;
701
+ return { parent: outContainer, leafKey: leaf };
702
+ }
703
+ function readSegment(container, seg) {
704
+ if (typeof seg === "number") {
705
+ return Array.isArray(container) ? container[seg] : undefined;
706
+ }
707
+ return isPlainObject(container) ? container[seg] : undefined;
708
+ }
709
+ function writeSegment(container, seg, value) {
710
+ if (typeof seg === "number") {
711
+ if (Array.isArray(container))
712
+ container[seg] = value;
713
+ return;
714
+ }
715
+ if (isPlainObject(container))
716
+ container[seg] = value;
717
+ }
718
+ // Shallow clone an array or plain object; null for any other shape (the
719
+ // path expected a container but the stored value isn't one).
720
+ function cloneContainer(value) {
721
+ // `Array.isArray` widens to `any[]`; cast before spread so the clone
722
+ // stays `unknown[]` rather than leaking `any` into the walk.
723
+ if (Array.isArray(value))
724
+ return [...value];
725
+ if (isPlainObject(value))
726
+ return { ...value };
727
+ return null;
728
+ }
729
+ function isPlainObject(value) {
730
+ return typeof value === "object" && value !== null && !Array.isArray(value);
486
731
  }
487
732
  // Returns `null` to mean "no candidates from this key" (skip), or
488
733
  // the ids to feed into the group's batch query. Mirrors the storage-
@@ -490,7 +735,7 @@ function applyOrphanToSlot(rowObj, subKey, multiple, ids, liveIds) {
490
735
  // going to be skipped. Callers pass values already decoded through
491
736
  // `decodeMetaBag`, so legacy object shapes have been healed to plain
492
737
  // ids by the time this runs.
493
- function orphanCandidateIds(target, value) {
738
+ function referenceCandidateIds(target, value) {
494
739
  if (target.multiple) {
495
740
  if (!Array.isArray(value))
496
741
  return null;
@@ -498,12 +743,6 @@ function orphanCandidateIds(target, value) {
498
743
  }
499
744
  return typeof value === "string" ? [value] : null;
500
745
  }
501
- function referenceTargetOf(field) {
502
- if (!field)
503
- return undefined;
504
- return field
505
- .referenceTarget;
506
- }
507
746
  /**
508
747
  * Decode a raw meta bag (as returned by drizzle's JSON-mode column)
509
748
  * into the plugin-typed shape RPC consumers expect. Unregistered keys
@@ -532,20 +771,30 @@ function decodeFieldValue(field, value) {
532
771
  if (isRepeaterField(field) && Array.isArray(value)) {
533
772
  return value.map((row) => healRepeaterRow(field, row));
534
773
  }
774
+ if (isTemporalField(field) && field.returns === "date") {
775
+ return projectTemporalDate(field.inputType, value);
776
+ }
535
777
  return coerceOnRead(field.type, value);
536
778
  }
537
- function healReferenceValue(target, value) {
538
- if (target.multiple) {
539
- if (!Array.isArray(value))
540
- return value;
541
- // Identity-preserving on the already-plain path — `healRepeaterRow`
542
- // clones a row only when the healed slot differs.
543
- if (!value.some((item) => extractStringId(item) !== null)) {
544
- return value;
545
- }
546
- return value.map((item) => extractStringId(item) ?? item);
547
- }
548
- return extractStringId(value) ?? value;
779
+ function isTemporalField(field) {
780
+ return (field.inputType === "date" ||
781
+ field.inputType === "datetime" ||
782
+ field.inputType === "time");
783
+ }
784
+ // `.returns("date")` decode projection. All three variants anchor to
785
+ // UTC `date` at UTC midnight, `time` on 1970-01-01 UTC — so the
786
+ // wall-clock components survive every server/browser timezone
787
+ // combination (decode runs server-side, often UTC on Workers, while
788
+ // the admin formats in the viewer's browser). Consumers read the
789
+ // parts back with `getUTC*` or `timeZone: "UTC"` formatting; the
790
+ // projection is the exact inverse of the write-side `Date` encoding
791
+ // (`formatTemporalValue`). Unparseable stored values round to "no
792
+ // value", matching the forgiving-read posture of `coerceOnRead`.
793
+ function projectTemporalDate(inputType, value) {
794
+ if (typeof value !== "string" || value === "")
795
+ return undefined;
796
+ const parsed = new Date(anchorTemporalUtc(inputType, value));
797
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
549
798
  }
550
799
  function healRepeaterRow(field, row) {
551
800
  if (!row || typeof row !== "object" || Array.isArray(row))
@@ -609,72 +858,6 @@ export async function loadMeta(ctx, table, idColumn, id, findField) {
609
858
  return decodeMetaBag(findField, row?.meta);
610
859
  }
611
860
  // --- internals below ---------------------------------------------------
612
- function coerceToType(type, key, value) {
613
- switch (type) {
614
- case "string":
615
- return coerceString(key, value);
616
- case "number":
617
- return coerceNumber(key, value);
618
- case "boolean":
619
- return coerceBoolean(key, value);
620
- case "json":
621
- return coerceJson(key, value);
622
- }
623
- }
624
- function coerceString(key, value) {
625
- if (typeof value === "string")
626
- return value;
627
- if (typeof value === "number" && Number.isFinite(value))
628
- return String(value);
629
- if (typeof value === "boolean")
630
- return String(value);
631
- throw MetaSanitizationError.invalidValue({ key });
632
- }
633
- function coerceNumber(key, value) {
634
- if (typeof value === "number" && Number.isFinite(value))
635
- return value;
636
- if (typeof value === "string") {
637
- // Empty string comes from cleared form inputs; the admin dispatcher
638
- // already sends `null` for those, but a direct RPC caller might send
639
- // "" — reject here so we don't silently coerce to 0 (`Number("") === 0`).
640
- if (value.trim() === "") {
641
- throw MetaSanitizationError.invalidValue({ key });
642
- }
643
- const parsed = Number(value);
644
- if (Number.isFinite(parsed))
645
- return parsed;
646
- }
647
- if (typeof value === "boolean")
648
- return value ? 1 : 0;
649
- throw MetaSanitizationError.invalidValue({ key });
650
- }
651
- function coerceBoolean(key, value) {
652
- if (typeof value === "boolean")
653
- return value;
654
- if (value === 1 || value === "1" || value === "true")
655
- return true;
656
- if (value === 0 || value === "0" || value === "false")
657
- return false;
658
- throw MetaSanitizationError.invalidValue({ key });
659
- }
660
- function coerceJson(key, value) {
661
- // json keys take anything round-trippable through JSON.stringify —
662
- // we reject values that throw (BigInt) or silently drop (functions,
663
- // Symbols) so reads don't hand back `undefined` for something a
664
- // plugin thought it stored. TS types JSON.stringify as always-
665
- // string, but at runtime it returns `undefined` for unserializable
666
- // inputs — hence the cast.
667
- try {
668
- const encoded = JSON.stringify(value);
669
- if (encoded === undefined) {
670
- throw MetaSanitizationError.invalidValue({ key });
671
- }
672
- return JSON.parse(encoded);
673
- }
674
- catch {
675
- throw MetaSanitizationError.invalidValue({ key });
676
- }
677
- }
678
861
  function assertEncodedSize(key, value) {
679
862
  const encoded = JSON.stringify(value);
680
863
  if (encoded === undefined)