@entifix/next-shell 0.1.0-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 (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/index.d.ts +21 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +19 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/lib/back-office/account-menu.d.ts +29 -0
  8. package/dist/lib/back-office/account-menu.d.ts.map +1 -0
  9. package/dist/lib/back-office/account-menu.js +70 -0
  10. package/dist/lib/back-office/account-menu.js.map +1 -0
  11. package/dist/lib/back-office/back-office-shell.d.ts +50 -0
  12. package/dist/lib/back-office/back-office-shell.d.ts.map +1 -0
  13. package/dist/lib/back-office/back-office-shell.js +128 -0
  14. package/dist/lib/back-office/back-office-shell.js.map +1 -0
  15. package/dist/lib/back-office/breadcrumbs.d.ts +16 -0
  16. package/dist/lib/back-office/breadcrumbs.d.ts.map +1 -0
  17. package/dist/lib/back-office/breadcrumbs.js +53 -0
  18. package/dist/lib/back-office/breadcrumbs.js.map +1 -0
  19. package/dist/lib/back-office/group-by-screen-type.d.ts +21 -0
  20. package/dist/lib/back-office/group-by-screen-type.d.ts.map +1 -0
  21. package/dist/lib/back-office/group-by-screen-type.js +39 -0
  22. package/dist/lib/back-office/group-by-screen-type.js.map +1 -0
  23. package/dist/lib/back-office/index.d.ts +6 -0
  24. package/dist/lib/back-office/index.d.ts.map +1 -0
  25. package/dist/lib/back-office/index.js +7 -0
  26. package/dist/lib/back-office/index.js.map +1 -0
  27. package/dist/lib/back-office/nav.d.ts +39 -0
  28. package/dist/lib/back-office/nav.d.ts.map +1 -0
  29. package/dist/lib/back-office/nav.js +3 -0
  30. package/dist/lib/back-office/nav.js.map +1 -0
  31. package/dist/lib/back-office/sidebar-nav.d.ts +26 -0
  32. package/dist/lib/back-office/sidebar-nav.d.ts.map +1 -0
  33. package/dist/lib/back-office/sidebar-nav.js +181 -0
  34. package/dist/lib/back-office/sidebar-nav.js.map +1 -0
  35. package/dist/lib/command-palette/command-palette-host.d.ts +41 -0
  36. package/dist/lib/command-palette/command-palette-host.d.ts.map +1 -0
  37. package/dist/lib/command-palette/command-palette-host.js +196 -0
  38. package/dist/lib/command-palette/command-palette-host.js.map +1 -0
  39. package/dist/lib/command-palette/index.d.ts +9 -0
  40. package/dist/lib/command-palette/index.d.ts.map +1 -0
  41. package/dist/lib/command-palette/index.js +10 -0
  42. package/dist/lib/command-palette/index.js.map +1 -0
  43. package/dist/lib/command-palette/palette-command.d.ts +32 -0
  44. package/dist/lib/command-palette/palette-command.d.ts.map +1 -0
  45. package/dist/lib/command-palette/palette-command.js +19 -0
  46. package/dist/lib/command-palette/palette-command.js.map +1 -0
  47. package/dist/lib/command-palette/use-command-recency.d.ts +28 -0
  48. package/dist/lib/command-palette/use-command-recency.d.ts.map +1 -0
  49. package/dist/lib/command-palette/use-command-recency.js +67 -0
  50. package/dist/lib/command-palette/use-command-recency.js.map +1 -0
  51. package/dist/lib/command-palette/use-command-source.d.ts +17 -0
  52. package/dist/lib/command-palette/use-command-source.d.ts.map +1 -0
  53. package/dist/lib/command-palette/use-command-source.js +70 -0
  54. package/dist/lib/command-palette/use-command-source.js.map +1 -0
  55. package/dist/lib/command-palette/use-nav-source.d.ts +21 -0
  56. package/dist/lib/command-palette/use-nav-source.d.ts.map +1 -0
  57. package/dist/lib/command-palette/use-nav-source.js +78 -0
  58. package/dist/lib/command-palette/use-nav-source.js.map +1 -0
  59. package/dist/lib/command-palette/use-records-source.d.ts +23 -0
  60. package/dist/lib/command-palette/use-records-source.d.ts.map +1 -0
  61. package/dist/lib/command-palette/use-records-source.js +199 -0
  62. package/dist/lib/command-palette/use-records-source.js.map +1 -0
  63. package/dist/lib/command-palette/use-tabs-source.d.ts +20 -0
  64. package/dist/lib/command-palette/use-tabs-source.d.ts.map +1 -0
  65. package/dist/lib/command-palette/use-tabs-source.js +52 -0
  66. package/dist/lib/command-palette/use-tabs-source.js.map +1 -0
  67. package/dist/lib/command-palette/use-use-cases-source.d.ts +22 -0
  68. package/dist/lib/command-palette/use-use-cases-source.d.ts.map +1 -0
  69. package/dist/lib/command-palette/use-use-cases-source.js +93 -0
  70. package/dist/lib/command-palette/use-use-cases-source.js.map +1 -0
  71. package/dist/lib/config/config-route.d.ts +51 -0
  72. package/dist/lib/config/config-route.d.ts.map +1 -0
  73. package/dist/lib/config/config-route.js +65 -0
  74. package/dist/lib/config/config-route.js.map +1 -0
  75. package/dist/lib/config/service-proxy-route.d.ts +28 -0
  76. package/dist/lib/config/service-proxy-route.d.ts.map +1 -0
  77. package/dist/lib/config/service-proxy-route.js +125 -0
  78. package/dist/lib/config/service-proxy-route.js.map +1 -0
  79. package/dist/lib/config/service-token.d.ts +5 -0
  80. package/dist/lib/config/service-token.d.ts.map +1 -0
  81. package/dist/lib/config/service-token.js +18 -0
  82. package/dist/lib/config/service-token.js.map +1 -0
  83. package/dist/lib/crud/entity-crud-form.d.ts +49 -0
  84. package/dist/lib/crud/entity-crud-form.d.ts.map +1 -0
  85. package/dist/lib/crud/entity-crud-form.js +133 -0
  86. package/dist/lib/crud/entity-crud-form.js.map +1 -0
  87. package/dist/lib/crud/hand-off-write.d.ts +35 -0
  88. package/dist/lib/crud/hand-off-write.d.ts.map +1 -0
  89. package/dist/lib/crud/hand-off-write.js +27 -0
  90. package/dist/lib/crud/hand-off-write.js.map +1 -0
  91. package/dist/lib/crud/index.d.ts +9 -0
  92. package/dist/lib/crud/index.d.ts.map +1 -0
  93. package/dist/lib/crud/index.js +10 -0
  94. package/dist/lib/crud/index.js.map +1 -0
  95. package/dist/lib/crud/make-entity-crud.d.ts +28 -0
  96. package/dist/lib/crud/make-entity-crud.d.ts.map +1 -0
  97. package/dist/lib/crud/make-entity-crud.js +318 -0
  98. package/dist/lib/crud/make-entity-crud.js.map +1 -0
  99. package/dist/lib/crud/make-entity-crud.types.d.ts +237 -0
  100. package/dist/lib/crud/make-entity-crud.types.d.ts.map +1 -0
  101. package/dist/lib/crud/make-entity-crud.types.js +6 -0
  102. package/dist/lib/crud/make-entity-crud.types.js.map +1 -0
  103. package/dist/lib/crud/pending-notice.d.ts +31 -0
  104. package/dist/lib/crud/pending-notice.d.ts.map +1 -0
  105. package/dist/lib/crud/pending-notice.js +72 -0
  106. package/dist/lib/crud/pending-notice.js.map +1 -0
  107. package/dist/lib/crud/slug.d.ts +13 -0
  108. package/dist/lib/crud/slug.d.ts.map +1 -0
  109. package/dist/lib/crud/slug.js +13 -0
  110. package/dist/lib/crud/slug.js.map +1 -0
  111. package/dist/lib/crud/use-entity-affordances.d.ts +20 -0
  112. package/dist/lib/crud/use-entity-affordances.d.ts.map +1 -0
  113. package/dist/lib/crud/use-entity-affordances.js +22 -0
  114. package/dist/lib/crud/use-entity-affordances.js.map +1 -0
  115. package/dist/lib/crud/use-entity-bulk.d.ts +43 -0
  116. package/dist/lib/crud/use-entity-bulk.d.ts.map +1 -0
  117. package/dist/lib/crud/use-entity-bulk.js +106 -0
  118. package/dist/lib/crud/use-entity-bulk.js.map +1 -0
  119. package/dist/lib/crud/use-entity-link-sources.d.ts +25 -0
  120. package/dist/lib/crud/use-entity-link-sources.d.ts.map +1 -0
  121. package/dist/lib/crud/use-entity-link-sources.js +37 -0
  122. package/dist/lib/crud/use-entity-link-sources.js.map +1 -0
  123. package/dist/lib/health/health-routes.d.ts +37 -0
  124. package/dist/lib/health/health-routes.d.ts.map +1 -0
  125. package/dist/lib/health/health-routes.js +90 -0
  126. package/dist/lib/health/health-routes.js.map +1 -0
  127. package/dist/lib/i18n/catalog/en.d.ts +4 -0
  128. package/dist/lib/i18n/catalog/en.d.ts.map +1 -0
  129. package/dist/lib/i18n/catalog/en.js +347 -0
  130. package/dist/lib/i18n/catalog/en.js.map +1 -0
  131. package/dist/lib/i18n/catalog/es.d.ts +397 -0
  132. package/dist/lib/i18n/catalog/es.d.ts.map +1 -0
  133. package/dist/lib/i18n/catalog/es.js +407 -0
  134. package/dist/lib/i18n/catalog/es.js.map +1 -0
  135. package/dist/lib/i18n/catalog/index.d.ts +671 -0
  136. package/dist/lib/i18n/catalog/index.d.ts.map +1 -0
  137. package/dist/lib/i18n/catalog/index.js +28 -0
  138. package/dist/lib/i18n/catalog/index.js.map +1 -0
  139. package/dist/lib/i18n/index.d.ts +2 -0
  140. package/dist/lib/i18n/index.d.ts.map +1 -0
  141. package/dist/lib/i18n/index.js +3 -0
  142. package/dist/lib/i18n/index.js.map +1 -0
  143. package/dist/lib/i18n/locale-link.d.ts +23 -0
  144. package/dist/lib/i18n/locale-link.d.ts.map +1 -0
  145. package/dist/lib/i18n/locale-link.js +43 -0
  146. package/dist/lib/i18n/locale-link.js.map +1 -0
  147. package/dist/lib/i18n/spec-catalog.d.ts +2 -0
  148. package/dist/lib/i18n/spec-catalog.d.ts.map +1 -0
  149. package/dist/lib/i18n/spec-catalog.js +84 -0
  150. package/dist/lib/i18n/spec-catalog.js.map +1 -0
  151. package/dist/lib/search/record-search-route.d.ts +44 -0
  152. package/dist/lib/search/record-search-route.d.ts.map +1 -0
  153. package/dist/lib/search/record-search-route.js +158 -0
  154. package/dist/lib/search/record-search-route.js.map +1 -0
  155. package/dist/lib/search/record-search-source.d.ts +59 -0
  156. package/dist/lib/search/record-search-source.d.ts.map +1 -0
  157. package/dist/lib/search/record-search-source.js +118 -0
  158. package/dist/lib/search/record-search-source.js.map +1 -0
  159. package/dist/lib/search/record-search.types.d.ts +85 -0
  160. package/dist/lib/search/record-search.types.d.ts.map +1 -0
  161. package/dist/lib/search/record-search.types.js +29 -0
  162. package/dist/lib/search/record-search.types.js.map +1 -0
  163. package/dist/lib/search/search-records.d.ts +25 -0
  164. package/dist/lib/search/search-records.d.ts.map +1 -0
  165. package/dist/lib/search/search-records.js +36 -0
  166. package/dist/lib/search/search-records.js.map +1 -0
  167. package/dist/lib/session/account-links.d.ts +46 -0
  168. package/dist/lib/session/account-links.d.ts.map +1 -0
  169. package/dist/lib/session/account-links.js +41 -0
  170. package/dist/lib/session/account-links.js.map +1 -0
  171. package/dist/lib/session/bearer.d.ts +25 -0
  172. package/dist/lib/session/bearer.d.ts.map +1 -0
  173. package/dist/lib/session/bearer.js +31 -0
  174. package/dist/lib/session/bearer.js.map +1 -0
  175. package/dist/lib/session/cookies.d.ts +14 -0
  176. package/dist/lib/session/cookies.d.ts.map +1 -0
  177. package/dist/lib/session/cookies.js +33 -0
  178. package/dist/lib/session/cookies.js.map +1 -0
  179. package/dist/lib/session/device.d.ts +31 -0
  180. package/dist/lib/session/device.d.ts.map +1 -0
  181. package/dist/lib/session/device.js +80 -0
  182. package/dist/lib/session/device.js.map +1 -0
  183. package/dist/lib/session/index.d.ts +3 -0
  184. package/dist/lib/session/index.d.ts.map +1 -0
  185. package/dist/lib/session/index.js +12 -0
  186. package/dist/lib/session/index.js.map +1 -0
  187. package/dist/lib/session/refresh-route.d.ts +19 -0
  188. package/dist/lib/session/refresh-route.d.ts.map +1 -0
  189. package/dist/lib/session/refresh-route.js +85 -0
  190. package/dist/lib/session/refresh-route.js.map +1 -0
  191. package/dist/lib/session/session-keepalive.d.ts +52 -0
  192. package/dist/lib/session/session-keepalive.d.ts.map +1 -0
  193. package/dist/lib/session/session-keepalive.js +72 -0
  194. package/dist/lib/session/session-keepalive.js.map +1 -0
  195. package/dist/lib/session/use-session-refresh.d.ts +43 -0
  196. package/dist/lib/session/use-session-refresh.d.ts.map +1 -0
  197. package/dist/lib/session/use-session-refresh.js +120 -0
  198. package/dist/lib/session/use-session-refresh.js.map +1 -0
  199. package/dist/lib/wizard/index.d.ts +4 -0
  200. package/dist/lib/wizard/index.d.ts.map +1 -0
  201. package/dist/lib/wizard/index.js +5 -0
  202. package/dist/lib/wizard/index.js.map +1 -0
  203. package/dist/lib/wizard/use-wizard-draft.d.ts +22 -0
  204. package/dist/lib/wizard/use-wizard-draft.d.ts.map +1 -0
  205. package/dist/lib/wizard/use-wizard-draft.js +39 -0
  206. package/dist/lib/wizard/use-wizard-draft.js.map +1 -0
  207. package/dist/lib/wizard/use-wizard-step-param.d.ts +60 -0
  208. package/dist/lib/wizard/use-wizard-step-param.d.ts.map +1 -0
  209. package/dist/lib/wizard/use-wizard-step-param.js +94 -0
  210. package/dist/lib/wizard/use-wizard-step-param.js.map +1 -0
  211. package/dist/lib/wizard/wizard-tab-kind.d.ts +31 -0
  212. package/dist/lib/wizard/wizard-tab-kind.d.ts.map +1 -0
  213. package/dist/lib/wizard/wizard-tab-kind.js +33 -0
  214. package/dist/lib/wizard/wizard-tab-kind.js.map +1 -0
  215. package/dist/lib/workspace/drafts-state.d.ts +88 -0
  216. package/dist/lib/workspace/drafts-state.d.ts.map +1 -0
  217. package/dist/lib/workspace/drafts-state.js +128 -0
  218. package/dist/lib/workspace/drafts-state.js.map +1 -0
  219. package/dist/lib/workspace/entity-nav.d.ts +26 -0
  220. package/dist/lib/workspace/entity-nav.d.ts.map +1 -0
  221. package/dist/lib/workspace/entity-nav.js +56 -0
  222. package/dist/lib/workspace/entity-nav.js.map +1 -0
  223. package/dist/lib/workspace/entity-tab-kind.d.ts +51 -0
  224. package/dist/lib/workspace/entity-tab-kind.d.ts.map +1 -0
  225. package/dist/lib/workspace/entity-tab-kind.js +47 -0
  226. package/dist/lib/workspace/entity-tab-kind.js.map +1 -0
  227. package/dist/lib/workspace/idb-state-storage.d.ts +9 -0
  228. package/dist/lib/workspace/idb-state-storage.d.ts.map +1 -0
  229. package/dist/lib/workspace/idb-state-storage.js +32 -0
  230. package/dist/lib/workspace/idb-state-storage.js.map +1 -0
  231. package/dist/lib/workspace/index.d.ts +13 -0
  232. package/dist/lib/workspace/index.d.ts.map +1 -0
  233. package/dist/lib/workspace/index.js +14 -0
  234. package/dist/lib/workspace/index.js.map +1 -0
  235. package/dist/lib/workspace/pending-state.d.ts +84 -0
  236. package/dist/lib/workspace/pending-state.d.ts.map +1 -0
  237. package/dist/lib/workspace/pending-state.js +155 -0
  238. package/dist/lib/workspace/pending-state.js.map +1 -0
  239. package/dist/lib/workspace/pending-transactions.d.ts +44 -0
  240. package/dist/lib/workspace/pending-transactions.d.ts.map +1 -0
  241. package/dist/lib/workspace/pending-transactions.js +114 -0
  242. package/dist/lib/workspace/pending-transactions.js.map +1 -0
  243. package/dist/lib/workspace/tab-kind.d.ts +48 -0
  244. package/dist/lib/workspace/tab-kind.d.ts.map +1 -0
  245. package/dist/lib/workspace/tab-kind.js +47 -0
  246. package/dist/lib/workspace/tab-kind.js.map +1 -0
  247. package/dist/lib/workspace/tab-state.d.ts +36 -0
  248. package/dist/lib/workspace/tab-state.d.ts.map +1 -0
  249. package/dist/lib/workspace/tab-state.js +81 -0
  250. package/dist/lib/workspace/tab-state.js.map +1 -0
  251. package/dist/lib/workspace/tabs-state.d.ts +46 -0
  252. package/dist/lib/workspace/tabs-state.d.ts.map +1 -0
  253. package/dist/lib/workspace/tabs-state.js +56 -0
  254. package/dist/lib/workspace/tabs-state.js.map +1 -0
  255. package/dist/lib/workspace/use-entity-draft.d.ts +25 -0
  256. package/dist/lib/workspace/use-entity-draft.d.ts.map +1 -0
  257. package/dist/lib/workspace/use-entity-draft.js +38 -0
  258. package/dist/lib/workspace/use-entity-draft.js.map +1 -0
  259. package/dist/lib/workspace/workspace-scope.d.ts +51 -0
  260. package/dist/lib/workspace/workspace-scope.d.ts.map +1 -0
  261. package/dist/lib/workspace/workspace-scope.js +42 -0
  262. package/dist/lib/workspace/workspace-scope.js.map +1 -0
  263. package/dist/lib/workspace/workspace-shell.d.ts +33 -0
  264. package/dist/lib/workspace/workspace-shell.d.ts.map +1 -0
  265. package/dist/lib/workspace/workspace-shell.js +206 -0
  266. package/dist/lib/workspace/workspace-shell.js.map +1 -0
  267. package/dist/server.d.ts +13 -0
  268. package/dist/server.d.ts.map +1 -0
  269. package/dist/server.js +41 -0
  270. package/dist/server.js.map +1 -0
  271. package/package.json +95 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-crud-form.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/entity-crud-form.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAE1B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,6BAA6B,CAAC;AAIrC,MAAM,WAAW,mBAAmB,CAAC,OAAO,SAAS,MAAM;IACzD,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACvD,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAChD,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAC/C,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS,MAAM,EAAE,EACrD,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,KAAK,EACL,MAAM,EACN,SAAiB,EACjB,QAAgB,EAChB,UAAkB,EAClB,KAAK,EACL,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,cAAc,EACd,SAAS,GACV,EAAE,mBAAmB,CAAC,OAAO,CAAC,+BAuH9B"}
@@ -0,0 +1,133 @@
1
+ 'use client';
2
+ import { _ as _extends } from "@swc/helpers/_/_extends";
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ import { reconstructEntity } from '@entifix/core';
5
+ import { EntityField, EntityForm, useT } from '@entifix/react-controls';
6
+ import { useEntityForm } from '@entifix/react-integration';
7
+ import { useEffect, useMemo } from 'react';
8
+ import { useEntityAffordances } from './use-entity-affordances.js';
9
+ import { useEntityLinkSources } from './use-entity-link-sources.js';
10
+ /**
11
+ * The generated create/update form. A thin wrapper over the agnostic
12
+ * {@link EntityForm}: every field, label and validation rule comes from the
13
+ * entity's own accessor metadata, and `reconstructEntity` derives the submit
14
+ * rebuild from that same metadata — so nothing here is per-entity but the
15
+ * constructor, the catalog key and the hidden-field list.
16
+ *
17
+ * A hidden member still round-trips. `code` is assigned by the create
18
+ * transaction and never shown, but it is a plain writable member, so the rebuild
19
+ * picks it out of the seeded draft; dropping it from the rendered fields is not
20
+ * dropping it from the draft.
21
+ *
22
+ * `useEntityForm` owns the draft and seeds it once, so the page keys this
23
+ * component by the record id to reseed when the record arrives.
24
+ */ export function EntityCrudForm({ entityConstructor, catalogKey, hiddenFields, links, entity, isLoading = false, isSaving = false, isDeleting = false, error, onSave, onDelete, backHref, draft, metadataSource, onUseCase }) {
25
+ const et = useT('entity');
26
+ const ct = useT('controls');
27
+ // `controls` copy, resolved here and handed down. The integration package
28
+ // owns the validation *rules* and this shell owns the sentences, because a
29
+ // sideways import between the two `entifix:react` packages is what the
30
+ // boundary rule forbids.
31
+ const validationMessages = useMemo(()=>{
32
+ const say = (failure)=>(field)=>ct(`validation.${failure}`, {
33
+ field
34
+ });
35
+ return {
36
+ required: say('required'),
37
+ number: say('number'),
38
+ date: say('date'),
39
+ option: say('option')
40
+ };
41
+ }, [
42
+ ct
43
+ ]);
44
+ const form = useEntityForm({
45
+ entityConstructor,
46
+ entity,
47
+ validationMessages,
48
+ draft,
49
+ onSubmit: (values)=>onSave(reconstructEntity(entityConstructor, values, {
50
+ existing: entity
51
+ }))
52
+ });
53
+ const linkSources = useEntityLinkSources(links, {
54
+ values: form.values,
55
+ selection: form.links
56
+ });
57
+ // Refill the relation sidecar from the ids the draft did keep.
58
+ //
59
+ // A draft is JSON, so it holds ids and nothing else; the instances behind them
60
+ // die with the page. Each picker already resolves its id — that is how a
61
+ // restored draft shows a brand's name instead of its key — so the instance is
62
+ // in hand and only has to be handed back. Without this, a member declared
63
+ // `linkSerialization: 'embedded'` reaches `applyEntityLinks` with an id and no
64
+ // target, which it refuses rather than writing the wrong wire shape.
65
+ //
66
+ // `hydrateLink`, not `setLink`: this is a lookup landing, not a pick, so it
67
+ // must leave the draft — and the form's dirty flag — alone.
68
+ const { links: selection, hydrateLink } = form;
69
+ useEffect(()=>{
70
+ for (const [field, source] of Object.entries(linkSources)){
71
+ const resolved = source.selected.entity;
72
+ if (resolved !== undefined && selection[field] === undefined) {
73
+ hydrateLink(field, resolved);
74
+ }
75
+ }
76
+ }, [
77
+ linkSources,
78
+ selection,
79
+ hydrateLink
80
+ ]);
81
+ const affordances = useEntityAffordances(entityConstructor, metadataSource);
82
+ /**
83
+ * May this caller write the record in front of them?
84
+ *
85
+ * The **same authority that withholds Save**, applied one step earlier.
86
+ * Absent metadata keeps the pre-ADR-0026 behaviour, so an un-migrated call
87
+ * site is untouched; present metadata is authoritative, because it is what
88
+ * the service already decided.
89
+ *
90
+ * ⚠️ Without this the form rendered inputs nobody could submit. Measured on
91
+ * the stock surface: `stock-item` answers `["read"]` — no role holds
92
+ * `stock-item:write` and there is no save route — so Save was correctly
93
+ * absent while `onHand` and `reserved` still rendered as **enabled number
94
+ * inputs**. A vendor could type a new quantity into the very field ADR 0010
95
+ * forbids writing, and nothing would happen and nothing would say why.
96
+ *
97
+ * A create is exempt: there is no record yet, so there is nothing to read and
98
+ * a `read` mode would render an empty form with no way to fill it.
99
+ */ const mayWrite = entity === undefined || affordances.metadata === undefined || affordances.metadata.actions.includes('write');
100
+ return /*#__PURE__*/ _jsx(EntityForm, _extends({
101
+ entityConstructor: entityConstructor,
102
+ entity: entity,
103
+ // Driven by the served descriptor rather than fixed: `edit` for a caller
104
+ // who may write, `read` for one who may not. Passing it at all is what
105
+ // suppresses the built-in Ver/Editar toggle, which would otherwise offer
106
+ // a way back into inputs the service will refuse.
107
+ mode: mayWrite ? 'edit' : 'read',
108
+ values: form.values,
109
+ onFieldChange: form.setField,
110
+ linkSources: linkSources,
111
+ onLinkChange: form.setLink,
112
+ errors: form.errors,
113
+ formError: form.formError,
114
+ onSubmit: form.submit,
115
+ onDelete: onDelete
116
+ }, affordances, {
117
+ onUseCase: onUseCase,
118
+ isLoading: isLoading,
119
+ isSaving: isSaving,
120
+ isDeleting: isDeleting,
121
+ error: error,
122
+ backHref: backHref,
123
+ // `entity` is undefined until the record lands, so testing it alone
124
+ // titled a loading edit form "New" and then relabelled it (#139).
125
+ title: et(entity || isLoading ? `${catalogKey}.form.editTitle` : `${catalogKey}.form.newTitle`),
126
+ children: hiddenFields.map((field)=>/*#__PURE__*/ _jsx(EntityField, {
127
+ field: field,
128
+ hidden: true
129
+ }, field))
130
+ }));
131
+ }
132
+
133
+ //# sourceMappingURL=entity-crud-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/crud/entity-crud-form.tsx"],"sourcesContent":["'use client';\n\nimport {\n type EntifixError,\n type Entity,\n type EntityConstructor,\n type EntityMetadataSource,\n reconstructEntity,\n} from '@entifix/core';\nimport { EntityField, EntityForm, useT } from '@entifix/react-controls';\nimport {\n type EntityDraftStore,\n useEntityForm,\n} from '@entifix/react-integration';\nimport { useEffect, useMemo } from 'react';\n\nimport type {\n EntityCatalogKey,\n EntityCrudLinkSource,\n} from './make-entity-crud.types.js';\nimport { useEntityAffordances } from './use-entity-affordances.js';\nimport { useEntityLinkSources } from './use-entity-link-sources.js';\n\nexport interface EntityCrudFormProps<TEntity extends Entity> {\n readonly entityConstructor: EntityConstructor<TEntity>;\n readonly catalogKey: EntityCatalogKey;\n readonly hiddenFields: readonly string[];\n readonly links: readonly EntityCrudLinkSource[];\n /** The record being edited; `undefined` means this is a create. */\n readonly entity?: TEntity;\n readonly isLoading?: boolean;\n readonly isSaving?: boolean;\n readonly isDeleting?: boolean;\n readonly error?: EntifixError;\n readonly onSave: (entity: TEntity) => void;\n /** Omitted for a create — there is nothing to delete yet. */\n readonly onDelete?: () => void;\n readonly backHref: string;\n /**\n * Where to autosave this form's draft. Omitted by a route host, which is what\n * keeps a plain route ephemeral; the workspace tab host supplies one.\n */\n readonly draft?: EntityDraftStore;\n /**\n * Where this caller's affordances come from. Absent keeps the pre-ADR-0026\n * behaviour: Save and Delete render unconditionally and no declared verb\n * appears — which is what every generated catalog did before it existed.\n */\n readonly metadataSource?: EntityMetadataSource;\n /** Runs a declared `entity`-bound verb on this record, by its key. */\n readonly onUseCase?: (key: string) => void;\n}\n\n/**\n * The generated create/update form. A thin wrapper over the agnostic\n * {@link EntityForm}: every field, label and validation rule comes from the\n * entity's own accessor metadata, and `reconstructEntity` derives the submit\n * rebuild from that same metadata — so nothing here is per-entity but the\n * constructor, the catalog key and the hidden-field list.\n *\n * A hidden member still round-trips. `code` is assigned by the create\n * transaction and never shown, but it is a plain writable member, so the rebuild\n * picks it out of the seeded draft; dropping it from the rendered fields is not\n * dropping it from the draft.\n *\n * `useEntityForm` owns the draft and seeds it once, so the page keys this\n * component by the record id to reseed when the record arrives.\n */\nexport function EntityCrudForm<TEntity extends Entity>({\n entityConstructor,\n catalogKey,\n hiddenFields,\n links,\n entity,\n isLoading = false,\n isSaving = false,\n isDeleting = false,\n error,\n onSave,\n onDelete,\n backHref,\n draft,\n metadataSource,\n onUseCase,\n}: EntityCrudFormProps<TEntity>) {\n const et = useT('entity');\n const ct = useT('controls');\n\n // `controls` copy, resolved here and handed down. The integration package\n // owns the validation *rules* and this shell owns the sentences, because a\n // sideways import between the two `entifix:react` packages is what the\n // boundary rule forbids.\n const validationMessages = useMemo(() => {\n const say =\n (failure: 'required' | 'number' | 'date' | 'option') => (field: string) =>\n ct(`validation.${failure}`, { field });\n return {\n required: say('required'),\n number: say('number'),\n date: say('date'),\n option: say('option'),\n };\n }, [ct]);\n\n const form = useEntityForm<TEntity>({\n entityConstructor,\n entity,\n validationMessages,\n draft,\n onSubmit: values =>\n onSave(\n reconstructEntity(entityConstructor, values, { existing: entity }),\n ),\n });\n\n const linkSources = useEntityLinkSources(links, {\n values: form.values,\n selection: form.links,\n });\n\n // Refill the relation sidecar from the ids the draft did keep.\n //\n // A draft is JSON, so it holds ids and nothing else; the instances behind them\n // die with the page. Each picker already resolves its id — that is how a\n // restored draft shows a brand's name instead of its key — so the instance is\n // in hand and only has to be handed back. Without this, a member declared\n // `linkSerialization: 'embedded'` reaches `applyEntityLinks` with an id and no\n // target, which it refuses rather than writing the wrong wire shape.\n //\n // `hydrateLink`, not `setLink`: this is a lookup landing, not a pick, so it\n // must leave the draft — and the form's dirty flag — alone.\n const { links: selection, hydrateLink } = form;\n useEffect(() => {\n for (const [field, source] of Object.entries(linkSources)) {\n const resolved = source.selected.entity;\n if (resolved !== undefined && selection[field] === undefined) {\n hydrateLink(field, resolved);\n }\n }\n }, [linkSources, selection, hydrateLink]);\n\n const affordances = useEntityAffordances(entityConstructor, metadataSource);\n\n /**\n * May this caller write the record in front of them?\n *\n * The **same authority that withholds Save**, applied one step earlier.\n * Absent metadata keeps the pre-ADR-0026 behaviour, so an un-migrated call\n * site is untouched; present metadata is authoritative, because it is what\n * the service already decided.\n *\n * ⚠️ Without this the form rendered inputs nobody could submit. Measured on\n * the stock surface: `stock-item` answers `[\"read\"]` — no role holds\n * `stock-item:write` and there is no save route — so Save was correctly\n * absent while `onHand` and `reserved` still rendered as **enabled number\n * inputs**. A vendor could type a new quantity into the very field ADR 0010\n * forbids writing, and nothing would happen and nothing would say why.\n *\n * A create is exempt: there is no record yet, so there is nothing to read and\n * a `read` mode would render an empty form with no way to fill it.\n */\n const mayWrite =\n entity === undefined ||\n affordances.metadata === undefined ||\n affordances.metadata.actions.includes('write');\n\n return (\n <EntityForm<TEntity>\n entityConstructor={entityConstructor}\n entity={entity}\n // Driven by the served descriptor rather than fixed: `edit` for a caller\n // who may write, `read` for one who may not. Passing it at all is what\n // suppresses the built-in Ver/Editar toggle, which would otherwise offer\n // a way back into inputs the service will refuse.\n mode={mayWrite ? 'edit' : 'read'}\n values={form.values}\n onFieldChange={form.setField}\n linkSources={linkSources}\n onLinkChange={form.setLink}\n errors={form.errors}\n formError={form.formError}\n onSubmit={form.submit}\n onDelete={onDelete}\n {...affordances}\n onUseCase={onUseCase}\n isLoading={isLoading}\n isSaving={isSaving}\n isDeleting={isDeleting}\n error={error}\n backHref={backHref}\n // `entity` is undefined until the record lands, so testing it alone\n // titled a loading edit form \"New\" and then relabelled it (#139).\n title={et(\n entity || isLoading\n ? `${catalogKey}.form.editTitle`\n : `${catalogKey}.form.newTitle`,\n )}\n >\n {hiddenFields.map(field => (\n <EntityField<TEntity> key={field} field={field} hidden />\n ))}\n </EntityForm>\n );\n}\n"],"names":["reconstructEntity","EntityField","EntityForm","useT","useEntityForm","useEffect","useMemo","useEntityAffordances","useEntityLinkSources","EntityCrudForm","entityConstructor","catalogKey","hiddenFields","links","entity","isLoading","isSaving","isDeleting","error","onSave","onDelete","backHref","draft","metadataSource","onUseCase","et","ct","validationMessages","say","failure","field","required","number","date","option","form","onSubmit","values","existing","linkSources","selection","hydrateLink","source","Object","entries","resolved","selected","undefined","affordances","mayWrite","metadata","actions","includes","mode","onFieldChange","setField","onLinkChange","setLink","errors","formError","submit","title","map","hidden"],"mappings":"AAAA;;;AAEA,SAKEA,iBAAiB,QACZ,gBAAgB;AACvB,SAASC,WAAW,EAAEC,UAAU,EAAEC,IAAI,QAAQ,0BAA0B;AACxE,SAEEC,aAAa,QACR,6BAA6B;AACpC,SAASC,SAAS,EAAEC,OAAO,QAAQ,QAAQ;AAM3C,SAASC,oBAAoB,QAAQ,8BAA8B;AACnE,SAASC,oBAAoB,QAAQ,+BAA+B;AAgCpE;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,eAAuC,EACrDC,iBAAiB,EACjBC,UAAU,EACVC,YAAY,EACZC,KAAK,EACLC,MAAM,EACNC,YAAY,KAAK,EACjBC,WAAW,KAAK,EAChBC,aAAa,KAAK,EAClBC,KAAK,EACLC,MAAM,EACNC,QAAQ,EACRC,QAAQ,EACRC,KAAK,EACLC,cAAc,EACdC,SAAS,EACoB;IAC7B,MAAMC,KAAKtB,KAAK;IAChB,MAAMuB,KAAKvB,KAAK;IAEhB,0EAA0E;IAC1E,2EAA2E;IAC3E,uEAAuE;IACvE,yBAAyB;IACzB,MAAMwB,qBAAqBrB,QAAQ;QACjC,MAAMsB,MACJ,CAACC,UAAuD,CAACC,QACvDJ,GAAG,CAAC,WAAW,EAAEG,SAAS,EAAE;oBAAEC;gBAAM;QACxC,OAAO;YACLC,UAAUH,IAAI;YACdI,QAAQJ,IAAI;YACZK,MAAML,IAAI;YACVM,QAAQN,IAAI;QACd;IACF,GAAG;QAACF;KAAG;IAEP,MAAMS,OAAO/B,cAAuB;QAClCM;QACAI;QACAa;QACAL;QACAc,UAAUC,CAAAA,SACRlB,OACEnB,kBAAkBU,mBAAmB2B,QAAQ;gBAAEC,UAAUxB;YAAO;IAEtE;IAEA,MAAMyB,cAAc/B,qBAAqBK,OAAO;QAC9CwB,QAAQF,KAAKE,MAAM;QACnBG,WAAWL,KAAKtB,KAAK;IACvB;IAEA,+DAA+D;IAC/D,EAAE;IACF,+EAA+E;IAC/E,yEAAyE;IACzE,8EAA8E;IAC9E,0EAA0E;IAC1E,+EAA+E;IAC/E,qEAAqE;IACrE,EAAE;IACF,4EAA4E;IAC5E,4DAA4D;IAC5D,MAAM,EAAEA,OAAO2B,SAAS,EAAEC,WAAW,EAAE,GAAGN;IAC1C9B,UAAU;QACR,KAAK,MAAM,CAACyB,OAAOY,OAAO,IAAIC,OAAOC,OAAO,CAACL,aAAc;YACzD,MAAMM,WAAWH,OAAOI,QAAQ,CAAChC,MAAM;YACvC,IAAI+B,aAAaE,aAAaP,SAAS,CAACV,MAAM,KAAKiB,WAAW;gBAC5DN,YAAYX,OAAOe;YACrB;QACF;IACF,GAAG;QAACN;QAAaC;QAAWC;KAAY;IAExC,MAAMO,cAAczC,qBAAqBG,mBAAmBa;IAE5D;;;;;;;;;;;;;;;;;GAiBC,GACD,MAAM0B,WACJnC,WAAWiC,aACXC,YAAYE,QAAQ,KAAKH,aACzBC,YAAYE,QAAQ,CAACC,OAAO,CAACC,QAAQ,CAAC;IAExC,qBACE,KAAClD;QACCQ,mBAAmBA;QACnBI,QAAQA;QACR,yEAAyE;QACzE,uEAAuE;QACvE,yEAAyE;QACzE,kDAAkD;QAClDuC,MAAMJ,WAAW,SAAS;QAC1BZ,QAAQF,KAAKE,MAAM;QACnBiB,eAAenB,KAAKoB,QAAQ;QAC5BhB,aAAaA;QACbiB,cAAcrB,KAAKsB,OAAO;QAC1BC,QAAQvB,KAAKuB,MAAM;QACnBC,WAAWxB,KAAKwB,SAAS;QACzBvB,UAAUD,KAAKyB,MAAM;QACrBxC,UAAUA;OACN4B;QACJxB,WAAWA;QACXT,WAAWA;QACXC,UAAUA;QACVC,YAAYA;QACZC,OAAOA;QACPG,UAAUA;QACV,oEAAoE;QACpE,kEAAkE;QAClEwC,OAAOpC,GACLX,UAAUC,YACN,GAAGJ,WAAW,eAAe,CAAC,GAC9B,GAAGA,WAAW,cAAc,CAAC;kBAGlCC,aAAakD,GAAG,CAAChC,CAAAA,sBAChB,KAAC7B;gBAAiC6B,OAAOA;gBAAOiC,MAAM;eAA3BjC;;AAInC"}
@@ -0,0 +1,35 @@
1
+ import type { PendingTransactionStore } from '@entifix/transactions';
2
+ export interface HandOffWriteOptions {
3
+ /** The saved record's id — the transaction id, for a tracked create. */
4
+ readonly id: string;
5
+ /** The record as the browser built it, shown until the service confirms. */
6
+ readonly record: unknown;
7
+ readonly pending: PendingTransactionStore;
8
+ /** The draft the write was composed in, if it had one. */
9
+ readonly draft?: {
10
+ clear(): void;
11
+ };
12
+ }
13
+ /**
14
+ * What happens to a draft once its write has been handed over.
15
+ *
16
+ * A transactional create resolves at the `202`, **before** the write is
17
+ * durable, so the save adapter has already announced it and the id is in the
18
+ * pending set ([ADR 0043](../../../../../../docs/adr/0043-the-optimistic-mutation-contract.md)).
19
+ * Handing the record over *is* the question: the returned entity is otherwise
20
+ * indistinguishable from a plain REST create, and asking `entries.some(...)`
21
+ * beforehand would read a set captured before the `await` that announced it.
22
+ *
23
+ * ⚠️ A **still-pending write keeps its draft**. The write has not committed, and
24
+ * a failure minutes from now would otherwise have destroyed the operator's only
25
+ * copy of what they typed. Only a plain, committed write spends it.
26
+ *
27
+ * It lives here rather than inside `makeEntityCrud` because it is not the
28
+ * generator's rule — it is the rule for every screen that writes. A wizard's
29
+ * Finalizar performs the same three steps, and a second copy of them is where
30
+ * one of the two would eventually stop keeping the draft.
31
+ *
32
+ * @returns whether the write is still in flight.
33
+ */
34
+ export declare function handOffWrite({ id, record, pending, draft, }: HandOffWriteOptions): boolean;
35
+ //# sourceMappingURL=hand-off-write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hand-off-write.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/hand-off-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC1C,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,KAAK,IAAI,IAAI,CAAA;KAAE,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,EAAE,EACF,MAAM,EACN,OAAO,EACP,KAAK,GACN,EAAE,mBAAmB,GAAG,OAAO,CAK/B"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * What happens to a draft once its write has been handed over.
3
+ *
4
+ * A transactional create resolves at the `202`, **before** the write is
5
+ * durable, so the save adapter has already announced it and the id is in the
6
+ * pending set ([ADR 0043](../../../../../../docs/adr/0043-the-optimistic-mutation-contract.md)).
7
+ * Handing the record over *is* the question: the returned entity is otherwise
8
+ * indistinguishable from a plain REST create, and asking `entries.some(...)`
9
+ * beforehand would read a set captured before the `await` that announced it.
10
+ *
11
+ * ⚠️ A **still-pending write keeps its draft**. The write has not committed, and
12
+ * a failure minutes from now would otherwise have destroyed the operator's only
13
+ * copy of what they typed. Only a plain, committed write spends it.
14
+ *
15
+ * It lives here rather than inside `makeEntityCrud` because it is not the
16
+ * generator's rule — it is the rule for every screen that writes. A wizard's
17
+ * Finalizar performs the same three steps, and a second copy of them is where
18
+ * one of the two would eventually stop keeping the draft.
19
+ *
20
+ * @returns whether the write is still in flight.
21
+ */ export function handOffWrite({ id, record, pending, draft }) {
22
+ if (pending.attach(id, record)) return true;
23
+ draft == null ? void 0 : draft.clear();
24
+ return false;
25
+ }
26
+
27
+ //# sourceMappingURL=hand-off-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/crud/hand-off-write.ts"],"sourcesContent":["import type { PendingTransactionStore } from '@entifix/transactions';\n\nexport interface HandOffWriteOptions {\n /** The saved record's id — the transaction id, for a tracked create. */\n readonly id: string;\n /** The record as the browser built it, shown until the service confirms. */\n readonly record: unknown;\n readonly pending: PendingTransactionStore;\n /** The draft the write was composed in, if it had one. */\n readonly draft?: { clear(): void };\n}\n\n/**\n * What happens to a draft once its write has been handed over.\n *\n * A transactional create resolves at the `202`, **before** the write is\n * durable, so the save adapter has already announced it and the id is in the\n * pending set ([ADR 0043](../../../../../../docs/adr/0043-the-optimistic-mutation-contract.md)).\n * Handing the record over *is* the question: the returned entity is otherwise\n * indistinguishable from a plain REST create, and asking `entries.some(...)`\n * beforehand would read a set captured before the `await` that announced it.\n *\n * ⚠️ A **still-pending write keeps its draft**. The write has not committed, and\n * a failure minutes from now would otherwise have destroyed the operator's only\n * copy of what they typed. Only a plain, committed write spends it.\n *\n * It lives here rather than inside `makeEntityCrud` because it is not the\n * generator's rule — it is the rule for every screen that writes. A wizard's\n * Finalizar performs the same three steps, and a second copy of them is where\n * one of the two would eventually stop keeping the draft.\n *\n * @returns whether the write is still in flight.\n */\nexport function handOffWrite({\n id,\n record,\n pending,\n draft,\n}: HandOffWriteOptions): boolean {\n if (pending.attach(id, record)) return true;\n\n draft?.clear();\n return false;\n}\n"],"names":["handOffWrite","id","record","pending","draft","attach","clear"],"mappings":"AAYA;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASA,aAAa,EAC3BC,EAAE,EACFC,MAAM,EACNC,OAAO,EACPC,KAAK,EACe;IACpB,IAAID,QAAQE,MAAM,CAACJ,IAAIC,SAAS,OAAO;IAEvCE,yBAAAA,MAAOE,KAAK;IACZ,OAAO;AACT"}
@@ -0,0 +1,9 @@
1
+ export * from './entity-crud-form.js';
2
+ export * from './hand-off-write.js';
3
+ export * from './make-entity-crud.js';
4
+ export * from './make-entity-crud.types.js';
5
+ export * from './slug.js';
6
+ export * from './use-entity-affordances.js';
7
+ export * from './use-entity-bulk.js';
8
+ export * from './use-entity-link-sources.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,WAAW,CAAC;AAC1B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC"}
@@ -0,0 +1,10 @@
1
+ export * from './entity-crud-form.js';
2
+ export * from './hand-off-write.js';
3
+ export * from './make-entity-crud.js';
4
+ export * from './make-entity-crud.types.js';
5
+ export * from './slug.js';
6
+ export * from './use-entity-affordances.js';
7
+ export * from './use-entity-bulk.js';
8
+ export * from './use-entity-link-sources.js';
9
+
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/crud/index.ts"],"sourcesContent":["export * from './entity-crud-form.js';\nexport * from './hand-off-write.js';\nexport * from './make-entity-crud.js';\nexport * from './make-entity-crud.types.js';\nexport * from './slug.js';\nexport * from './use-entity-affordances.js';\nexport * from './use-entity-bulk.js';\nexport * from './use-entity-link-sources.js';\n"],"names":[],"mappings":"AAAA,cAAc,wBAAwB;AACtC,cAAc,sBAAsB;AACpC,cAAc,wBAAwB;AACtC,cAAc,8BAA8B;AAC5C,cAAc,YAAY;AAC1B,cAAc,8BAA8B;AAC5C,cAAc,uBAAuB;AACrC,cAAc,+BAA+B"}
@@ -0,0 +1,28 @@
1
+ import { type ConfigurationRepositoryTag, type EntityRepositoryTag } from '@entifix/business';
2
+ import { type Entity, type EntityConstructor } from '@entifix/core';
3
+ import type { TransactionSink } from '@entifix/transactions';
4
+ import { TransactionSinkTag } from '@entifix/transactions';
5
+ import { Context } from 'effect';
6
+ import type { EntityCrud, EntityCrudOptions } from './make-entity-crud.types.js';
7
+ /**
8
+ * What the generated pages' use-cases run against.
9
+ *
10
+ * Exported because a hand-built screen needs the same three: a wizard reads
11
+ * records, resolves pickers and writes through exactly these ports, and
12
+ * rebuilding the union at each such call site is how one of them ends up
13
+ * missing `TransactionSinkTag` and quietly losing its optimistic treatment.
14
+ */
15
+ export type CrudContext = EntityRepositoryTag | ConfigurationRepositoryTag | TransactionSinkTag;
16
+ /**
17
+ * Merges the configuration adapter with one repository adapter into the context
18
+ * the use-cases run against.
19
+ *
20
+ * The casts are the price of naming adapters by key: `AdapterKey` narrows which
21
+ * names may be *offered*, but TypeScript will not carry that narrowing through
22
+ * an index access on a generic record. The alternative is every call site
23
+ * passing two `Context` values per entity — the duplication this factory exists
24
+ * to remove.
25
+ */
26
+ export declare function mergeCrudContext<TAdapters>(adapters: TAdapters, configuration: keyof TAdapters, repository: keyof TAdapters, sink: TransactionSink): Context.Context<CrudContext>;
27
+ export declare function makeEntityCrud<TEntity extends Entity, TAdapters>(entityConstructor: EntityConstructor<TEntity>, options: EntityCrudOptions<TAdapters, TEntity>): EntityCrud<TEntity>;
28
+ //# sourceMappingURL=make-entity-crud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make-entity-crud.d.ts","sourceRoot":"","sources":["../../../src/lib/crud/make-entity-crud.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,0BAA0B,EAE/B,KAAK,mBAAmB,EAIzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAKL,KAAK,MAAM,EACX,KAAK,iBAAiB,EAGvB,MAAM,eAAe,CAAC;AAQvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAYjC,OAAO,KAAK,EACV,UAAU,EAEV,iBAAiB,EAElB,MAAM,6BAA6B,CAAC;AASrC;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GACrB,mBAAmB,GAAG,0BAA0B,GAAG,kBAAkB,CAAC;AAExE;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EACxC,QAAQ,EAAE,SAAS,EACnB,aAAa,EAAE,MAAM,SAAS,EAC9B,UAAU,EAAE,MAAM,SAAS,EAC3B,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAS9B;AAwCD,wBAAgB,cAAc,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,EAC9D,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,EAC7C,OAAO,EAAE,iBAAiB,CAAC,SAAS,EAAE,OAAO,CAAC,GAC7C,UAAU,CAAC,OAAO,CAAC,CAyUrB"}
@@ -0,0 +1,318 @@
1
+ 'use client';
2
+ import { _ as _extends } from "@swc/helpers/_/_extends";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ import { deleteUCFactory, getUCFactory, loadUCFactory, saveUCFactory } from '@entifix/business';
5
+ import { describeEntityColumns, EntifixBuildError, EntifixLogicError, envelopeEntityName, extractMetaEntity } from '@entifix/core';
6
+ import { EntityTable, useCasesForSurface } from '@entifix/react-controls';
7
+ import { entityQueryScope, useDataLoading, useEntityMutation, useEntityRecord } from '@entifix/react-integration';
8
+ import { TransactionSinkTag } from '@entifix/transactions';
9
+ import { useQueryClient } from '@tanstack/react-query';
10
+ import { Context } from 'effect';
11
+ import { useParams, useRouter } from 'next/navigation';
12
+ import { useState } from 'react';
13
+ import { useLocaleHref } from '../i18n/index.js';
14
+ import { pendingFor, pendingRecordsFor, usePendingTransactions } from '../workspace/pending-transactions.js';
15
+ import { EntityCrudForm } from './entity-crud-form.js';
16
+ import { handOffWrite } from './hand-off-write.js';
17
+ import { PendingNotice } from './pending-notice.js';
18
+ import { CATALOG_NEW_SLUG, slugToEntityId } from './slug.js';
19
+ import { useEntityAffordances } from './use-entity-affordances.js';
20
+ import { useEntityBulk } from './use-entity-bulk.js';
21
+ /** What a picker defaults to reading off its target. */ const TARGET_NAME_PROPERTY = 'name';
22
+ /**
23
+ * Merges the configuration adapter with one repository adapter into the context
24
+ * the use-cases run against.
25
+ *
26
+ * The casts are the price of naming adapters by key: `AdapterKey` narrows which
27
+ * names may be *offered*, but TypeScript will not carry that narrowing through
28
+ * an index access on a generic record. The alternative is every call site
29
+ * passing two `Context` values per entity — the duplication this factory exists
30
+ * to remove.
31
+ */ export function mergeCrudContext(adapters, configuration, repository, sink) {
32
+ return Context.add(Context.merge(adapters[configuration], adapters[repository]), TransactionSinkTag, sink);
33
+ }
34
+ /**
35
+ * Builds the list page and the single-record page for one entity.
36
+ *
37
+ * Every catalog entity used to cost ~10 files and ~300 lines whose only variable
38
+ * was a class name: two organisms with a `.types.ts` each, two client pages, two
39
+ * route files. The route files stay (Next needs a module per path); everything
40
+ * between them is derived from the entity's own metadata, so a new reference
41
+ * entity is this call plus a nav entry.
42
+ *
43
+ * Two things are checked here rather than at the call site, because both fail
44
+ * silently otherwise. The catalog key must be the entity's own `@entity({ key })`
45
+ * — they are the same string by convention, and a drifted one titles the form
46
+ * after a different entity in a way only a reader of both catalogs would catch.
47
+ * And a `links` entry must name a real member — a picker aimed at nothing renders
48
+ * identically to a read-only field.
49
+ *
50
+ * **Serves compile-time `@entity()` classes only.** Operator reference data ships
51
+ * with the code, so making this spec-driven would trade type safety for nothing;
52
+ * a vendor-authored `EntitySpecification` renders through a separate path
53
+ * (ADR 0014).
54
+ */ /**
55
+ * A rejected verb, as something the form's error slot can render.
56
+ *
57
+ * `useErrorMessage` resolves `details.code` through the shared `errors`
58
+ * catalog, so a shell that throws an `EntifixError` carrying one gets its
59
+ * translated sentence; anything else — a network failure, a thrown string —
60
+ * keeps its message rather than being relabelled with a code it never had.
61
+ */ function asEntifixError(failure) {
62
+ return failure instanceof EntifixLogicError || failure instanceof EntifixBuildError ? failure : new EntifixLogicError(failure instanceof Error ? failure.message : String(failure));
63
+ }
64
+ export function makeEntityCrud(entityConstructor, options) {
65
+ const { useAdapters, basePath, catalogKey, repository, configuration, hiddenFields = [], columns, toolbar, links = [], metadataSource, runBulkUseCase, runUseCase } = options;
66
+ const meta = extractMetaEntity(entityConstructor);
67
+ if (meta.key !== catalogKey) {
68
+ throw new EntifixBuildError(`${entityConstructor.name} declares key "${String(meta.key)}" but was given catalog key "${catalogKey}"`);
69
+ }
70
+ // Read off `@entity()` rather than rebuilt from `catalogKey`. Both spellings
71
+ // produce the same string today, and that is exactly the problem: a second
72
+ // place that knows how an entity's catalog subtree is laid out is a second
73
+ // place to fix when one moves. `MetaEntityOptions` makes them optional, so an
74
+ // entity that never declared them fails here — where a screen is generated —
75
+ // rather than on the render that first tries to title a tab with `undefined`.
76
+ const { labelKey: entityLabelKey, pluralKey: entityPluralKey } = meta;
77
+ if (entityLabelKey === undefined || entityPluralKey === undefined) {
78
+ throw new EntifixBuildError(`${entityConstructor.name} must declare labelKey and pluralKey on @entity() to be generated`);
79
+ }
80
+ // Computed once: a descriptor is a property of the class, so nothing about it
81
+ // depends on the record being edited. `linkLabelProperty`/`linkSearchProperty`
82
+ // are overridden here because a scalar foreign key's `@accessor()` cannot name
83
+ // the target's members — it may not import the target at all.
84
+ // The wire name, which is also what a pending entry records and what
85
+ // `entityQueryScopeFor` keys on — one derivation, so a notice and an
86
+ // invalidation can never disagree about which entity they mean.
87
+ const entityName = envelopeEntityName(entityConstructor);
88
+ const descriptors = describeEntityColumns(entityConstructor);
89
+ const linkPlans = links.map((link)=>{
90
+ var _link_labelProperty, _link_searchProperty;
91
+ const descriptor = descriptors.find((entry)=>entry.name === link.field);
92
+ if (descriptor === undefined) {
93
+ throw new EntifixBuildError(`${entityConstructor.name} has no member "${link.field}" for a link source`);
94
+ }
95
+ return _extends({}, link, {
96
+ descriptor: _extends({}, descriptor, {
97
+ linkLabelProperty: (_link_labelProperty = link.labelProperty) != null ? _link_labelProperty : TARGET_NAME_PROPERTY,
98
+ linkSearchProperty: (_link_searchProperty = link.searchProperty) != null ? _link_searchProperty : TARGET_NAME_PROPERTY
99
+ })
100
+ });
101
+ });
102
+ function ListPage() {
103
+ var _affordances_metadata, _affordances_metadata1;
104
+ const adapters = useAdapters();
105
+ // Outside a `PendingTransactionsProvider` this watches nothing, so a host
106
+ // that has not mounted one behaves exactly as it did before.
107
+ const pending = usePendingTransactions();
108
+ // Every internal href carries the locale. An unprefixed one still resolves —
109
+ // the middleware redirects it — but the visitor pays a round trip per click.
110
+ const withLocale = useLocaleHref();
111
+ const queryClient = useQueryClient();
112
+ const scope = entityQueryScope(entityConstructor);
113
+ const affordances = useEntityAffordances(entityConstructor, metadataSource);
114
+ // Records this browser has created but the service has not finished
115
+ // writing. Prepended below rather than patched into the cache, which a
116
+ // refetch would undo.
117
+ const optimistic = pendingRecordsFor(pending, entityName);
118
+ const pager = useDataLoading({
119
+ uc: loadUCFactory(),
120
+ ctx: mergeCrudContext(adapters, configuration, repository, pending),
121
+ // Scoped rather than left to the per-instance fallback, which is correct
122
+ // but unshared: with the entity's own scope one invalidation refreshes
123
+ // every page and filter of it, which is what a bulk run needs — and it is
124
+ // the same prefix `useReactiveInvalidation`already targets, so a generated
125
+ // list now also refreshes on a reactive change event.
126
+ queryKey: scope
127
+ });
128
+ // Only when this caller may actually run something over a selection.
129
+ //
130
+ // Measured live: an `admin` holds `catalog-reference:*:read` and no
131
+ // `retire`, so the service filters the verb out of `$metadata` — but the
132
+ // selection column still rendered, offering a set that no action could be
133
+ // taken on. A checkbox that can lead nowhere is worse than no checkbox: it
134
+ // reads as a permission the user does not have.
135
+ //
136
+ // Decided from the served document rather than from the runner, because
137
+ // the runner is a property of the *shell* (it knows the route) and the
138
+ // grant is a property of the *caller*. The column therefore appears when
139
+ // the document lands, which is the asynchrony ADR 0026 already accepts for
140
+ // every action surface.
141
+ const canRunBulk = useCasesForSurface('bulk-bar', (_affordances_metadata = affordances.metadata) == null ? void 0 : _affordances_metadata.useCases).length > 0;
142
+ const bulk = useEntityBulk({
143
+ run: canRunBulk ? runBulkUseCase : undefined,
144
+ // A bulk write changed the rows underneath the listing, so the page is
145
+ // re-read; the selection deliberately survives it (#121 — "the selection
146
+ // is still there afterwards"), because the operator's next act is usually
147
+ // to retry the failures or run a second verb on the same rows.
148
+ onCompleted: ()=>{
149
+ void queryClient.invalidateQueries({
150
+ queryKey: scope
151
+ });
152
+ }
153
+ });
154
+ // What a row's overflow menu runs, and the one place the list reports a
155
+ // verb that failed.
156
+ //
157
+ // ⚠️ **A row verb and the form's are the same runner with different
158
+ // arguments.** `EntityTable` hands back the whole record, because that is
159
+ // what it has; `runUseCase` takes an id, because that is what a route needs
160
+ // (#216). The list has no error slot of its own, so a failure lands in the
161
+ // table's own error region — the same one a failed page read uses.
162
+ const [useCaseError, setUseCaseError] = useState(undefined);
163
+ // Called unconditionally and the *result* gated, the way the surfaces are
164
+ // read in `EntityForm`: the name begins with `use`, so short-circuiting it
165
+ // reads to `react-hooks/rules-of-hooks` as a conditional hook.
166
+ const rowUseCases = useCasesForSurface('row-menu', (_affordances_metadata1 = affordances.metadata) == null ? void 0 : _affordances_metadata1.useCases);
167
+ const canRunRowUseCase = runUseCase !== undefined && rowUseCases.length > 0;
168
+ const handleRowUseCase = canRunRowUseCase ? (key, item)=>{
169
+ if (item.id == null) return;
170
+ setUseCaseError(undefined);
171
+ void runUseCase(key, item.id).then(()=>queryClient.invalidateQueries({
172
+ queryKey: scope
173
+ }), (failure)=>setUseCaseError(asEntifixError(failure)));
174
+ } : undefined;
175
+ return /*#__PURE__*/ _jsxs("div", {
176
+ className: "flex flex-col gap-s",
177
+ children: [
178
+ /*#__PURE__*/ _jsx(PendingNotice, {
179
+ entries: pendingFor(pending, entityName),
180
+ onDismiss: pending.dismiss
181
+ }),
182
+ /*#__PURE__*/ _jsxs(EntityTable, _extends({
183
+ entityConstructor: entityConstructor
184
+ }, pager, {
185
+ items: [
186
+ ...optimistic,
187
+ ...pager.items
188
+ ],
189
+ totalItems: pager.totalItems + optimistic.length,
190
+ hrefFor: (id)=>withLocale(`${basePath}/${String(id)}`),
191
+ newHref: withLocale(`${basePath}/${CATALOG_NEW_SLUG}`)
192
+ }, affordances, bulk.tableProps, {
193
+ onUseCase: handleRowUseCase,
194
+ error: useCaseError != null ? useCaseError : pager.error,
195
+ children: [
196
+ columns,
197
+ toolbar
198
+ ]
199
+ }))
200
+ ]
201
+ });
202
+ }
203
+ /**
204
+ * Dual-host: as a route it reads its slug from the URL and returns to the list
205
+ * on save; in a workspace tab it takes the slug and the post-save action as
206
+ * props.
207
+ */ function SingleViewPage({ slug, onSaved, onDeleted, draft } = {}) {
208
+ var _ref, _ref1;
209
+ const adapters = useAdapters();
210
+ const pending = usePendingTransactions();
211
+ const router = useRouter();
212
+ const withLocale = useLocaleHref();
213
+ const params = useParams();
214
+ // A verb's failure is this page's, not the record fetch's or the mutation's,
215
+ // so it needs a slot of its own to reach the form's error area.
216
+ const [useCaseError, setUseCaseError] = useState(undefined);
217
+ const id = slugToEntityId(slug != null ? slug : params.slug);
218
+ const ctx = mergeCrudContext(adapters, configuration, repository, pending);
219
+ const { entity, isLoading, error: loadError, reload } = useEntityRecord({
220
+ uc: getUCFactory(),
221
+ ctx,
222
+ id
223
+ });
224
+ const { save, remove, isSaving, isDeleting, error: writeError } = useEntityMutation({
225
+ saveUc: saveUCFactory(),
226
+ deleteUc: deleteUCFactory(),
227
+ ctx
228
+ });
229
+ // Rebuilt inline on every render on purpose: `useEntityLinkSource` holds
230
+ // these in a ref and keeps them out of its query keys, precisely so a caller
231
+ // does not have to memoise them.
232
+ const linkSources = linkPlans.map((plan)=>({
233
+ field: plan.field,
234
+ descriptor: plan.descriptor,
235
+ config: {
236
+ entityConstructor: plan.entityConstructor,
237
+ loadUc: loadUCFactory(),
238
+ getUc: getUCFactory(),
239
+ ctx: mergeCrudContext(adapters, configuration, plan.repository, pending)
240
+ }
241
+ }));
242
+ const afterSave = onSaved != null ? onSaved : ()=>router.push(withLocale(basePath));
243
+ const afterDelete = onDeleted != null ? onDeleted : ()=>router.push(withLocale(basePath));
244
+ // The draft is spent once the write *commits*, and only here is that known:
245
+ // `useEntityForm` neither fetches nor saves, so it cannot clear its own.
246
+ // A failed mutation deliberately keeps the draft — the edit is still the
247
+ // user's only copy of what they typed.
248
+ const handleSave = async (next)=>{
249
+ const saved = await save(next);
250
+ if (saved === undefined) return;
251
+ // ⚠️ Deliberately *not* a `setQueriesData` patch. The list refetches on
252
+ // mount — precisely when the operator arrives, having just been navigated
253
+ // here — and the server legitimately does not hold the record yet, so the
254
+ // refetch would replace the patched page and the row would vanish a
255
+ // moment after appearing. The pending set outlives refetches.
256
+ handOffWrite({
257
+ id: String(saved.id),
258
+ record: saved,
259
+ pending,
260
+ draft
261
+ });
262
+ afterSave();
263
+ };
264
+ const handleDelete = async ()=>{
265
+ if (await remove(id)) {
266
+ draft == null ? void 0 : draft.clear();
267
+ afterDelete();
268
+ }
269
+ };
270
+ /**
271
+ * An `entity`-bound verb, on the record being shown.
272
+ *
273
+ * It **reloads** rather than patching what it has: the service decides the
274
+ * resulting state — a `publish` may land on `published` from four different
275
+ * places — and a client that assumed the new value would show the wrong one
276
+ * the first time the rule changed. `id == null` means the create form,
277
+ * where there is no record to act on and the form renders no verbs anyway.
278
+ *
279
+ * ⚠️ The rejection is **caught and shown**, never left to the promise.
280
+ * `EntityForm`'s `onUseCase` returns `void`, so an uncaught rejection here
281
+ * reaches the console and nothing else — measured on an offering refusing
282
+ * `unpublish` from `draft`: the service answered `409` with a coded body,
283
+ * the record correctly did not move, and the operator saw a button that
284
+ * appeared to do nothing at all.
285
+ */ const handleUseCase = runUseCase === undefined || id == null ? undefined : (key)=>{
286
+ setUseCaseError(undefined);
287
+ void runUseCase(key, id).then(()=>reload(), (failure)=>setUseCaseError(asEntifixError(failure)));
288
+ };
289
+ return /*#__PURE__*/ _jsx(EntityCrudForm, {
290
+ entityConstructor: entityConstructor,
291
+ catalogKey: catalogKey,
292
+ metadataSource: metadataSource,
293
+ hiddenFields: hiddenFields,
294
+ links: linkSources,
295
+ entity: entity,
296
+ isLoading: isLoading,
297
+ isSaving: isSaving,
298
+ isDeleting: isDeleting,
299
+ error: (_ref = loadError != null ? loadError : writeError) != null ? _ref : useCaseError,
300
+ onSave: handleSave,
301
+ onDelete: id == null ? undefined : handleDelete,
302
+ onUseCase: handleUseCase,
303
+ backHref: withLocale(basePath),
304
+ draft: draft
305
+ }, String((_ref1 = entity == null ? void 0 : entity.id) != null ? _ref1 : CATALOG_NEW_SLUG));
306
+ }
307
+ return {
308
+ entityConstructor,
309
+ entityKey: catalogKey,
310
+ entityLabelKey,
311
+ entityPluralKey,
312
+ basePath,
313
+ ListPage,
314
+ SingleViewPage
315
+ };
316
+ }
317
+
318
+ //# sourceMappingURL=make-entity-crud.js.map