@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,20 @@
1
+ import { type CommandSource } from '@entifix/core';
2
+ /**
3
+ * The workspace's open tabs, as one group.
4
+ *
5
+ * Read straight from the store rather than through a prop, because the palette
6
+ * is mounted in the chrome and the tabs are the workspace's — there is no
7
+ * component between them to thread it through.
8
+ *
9
+ * ⚠️ Outside `/workspace` the store is deliberately **unhydrated**
10
+ * (`skipHydration: true`, rehydrated by `WorkspaceShell`), so this group is
11
+ * empty there and the palette drops it. That is right rather than a limitation:
12
+ * a tab is a thing you can return to inside the workspace, and offering one from
13
+ * a plain route would navigate somewhere the person did not ask to be.
14
+ *
15
+ * Selecting one is a **deep link**, not a store call: `?tab=` is the tab's
16
+ * address (ADR 0042), so the workspace focuses it on arrival and the palette
17
+ * does not have to be inside the workspace tree to reach the store's actions.
18
+ */
19
+ export declare function useTabsSource(term: string): CommandSource;
20
+ //# sourceMappingURL=use-tabs-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tabs-source.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/use-tabs-source.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,eAAe,CAAC;AAOvB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAwBzD"}
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+ import { matchesCommand } from '@entifix/core';
3
+ import { useT } from '@entifix/react-controls';
4
+ import { useMemo } from 'react';
5
+ import { useLocaleHref } from '../i18n/index.js';
6
+ import { useTabsState } from '../workspace/tabs-state.js';
7
+ /**
8
+ * The workspace's open tabs, as one group.
9
+ *
10
+ * Read straight from the store rather than through a prop, because the palette
11
+ * is mounted in the chrome and the tabs are the workspace's — there is no
12
+ * component between them to thread it through.
13
+ *
14
+ * ⚠️ Outside `/workspace` the store is deliberately **unhydrated**
15
+ * (`skipHydration: true`, rehydrated by `WorkspaceShell`), so this group is
16
+ * empty there and the palette drops it. That is right rather than a limitation:
17
+ * a tab is a thing you can return to inside the workspace, and offering one from
18
+ * a plain route would navigate somewhere the person did not ask to be.
19
+ *
20
+ * Selecting one is a **deep link**, not a store call: `?tab=` is the tab's
21
+ * address (ADR 0042), so the workspace focuses it on arrival and the palette
22
+ * does not have to be inside the workspace tree to reach the store's actions.
23
+ */ export function useTabsSource(term) {
24
+ const t = useT('shell');
25
+ const withLocale = useLocaleHref();
26
+ const tabs = useTabsState((state)=>state.tabs);
27
+ return useMemo(()=>{
28
+ const options = tabs.map((tab)=>({
29
+ id: `tab:${tab.param}`,
30
+ label: tab.title,
31
+ href: withLocale(`/workspace?tab=${encodeURIComponent(tab.param)}`)
32
+ }));
33
+ return {
34
+ key: 'tabs',
35
+ groups: [
36
+ {
37
+ key: 'tabs',
38
+ label: t('commandPalette.groups.tabs'),
39
+ options: options.filter((option)=>matchesCommand(term, option)),
40
+ isLoading: false
41
+ }
42
+ ]
43
+ };
44
+ }, [
45
+ tabs,
46
+ term,
47
+ t,
48
+ withLocale
49
+ ]);
50
+ }
51
+
52
+ //# sourceMappingURL=use-tabs-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/command-palette/use-tabs-source.ts"],"sourcesContent":["'use client';\n\nimport {\n type CommandOption,\n type CommandSource,\n matchesCommand,\n} from '@entifix/core';\nimport { useT } from '@entifix/react-controls';\nimport { useMemo } from 'react';\n\nimport { useLocaleHref } from '../i18n/index.js';\nimport { useTabsState } from '../workspace/tabs-state.js';\n\n/**\n * The workspace's open tabs, as one group.\n *\n * Read straight from the store rather than through a prop, because the palette\n * is mounted in the chrome and the tabs are the workspace's — there is no\n * component between them to thread it through.\n *\n * ⚠️ Outside `/workspace` the store is deliberately **unhydrated**\n * (`skipHydration: true`, rehydrated by `WorkspaceShell`), so this group is\n * empty there and the palette drops it. That is right rather than a limitation:\n * a tab is a thing you can return to inside the workspace, and offering one from\n * a plain route would navigate somewhere the person did not ask to be.\n *\n * Selecting one is a **deep link**, not a store call: `?tab=` is the tab's\n * address (ADR 0042), so the workspace focuses it on arrival and the palette\n * does not have to be inside the workspace tree to reach the store's actions.\n */\nexport function useTabsSource(term: string): CommandSource {\n const t = useT('shell');\n const withLocale = useLocaleHref();\n const tabs = useTabsState(state => state.tabs);\n\n return useMemo(() => {\n const options: CommandOption[] = tabs.map(tab => ({\n id: `tab:${tab.param}`,\n label: tab.title,\n href: withLocale(`/workspace?tab=${encodeURIComponent(tab.param)}`),\n }));\n\n return {\n key: 'tabs',\n groups: [\n {\n key: 'tabs',\n label: t('commandPalette.groups.tabs'),\n options: options.filter(option => matchesCommand(term, option)),\n isLoading: false,\n },\n ],\n } satisfies CommandSource;\n }, [tabs, term, t, withLocale]);\n}\n"],"names":["matchesCommand","useT","useMemo","useLocaleHref","useTabsState","useTabsSource","term","t","withLocale","tabs","state","options","map","tab","id","param","label","title","href","encodeURIComponent","key","groups","filter","option","isLoading"],"mappings":"AAAA;AAEA,SAGEA,cAAc,QACT,gBAAgB;AACvB,SAASC,IAAI,QAAQ,0BAA0B;AAC/C,SAASC,OAAO,QAAQ,QAAQ;AAEhC,SAASC,aAAa,QAAQ,mBAAmB;AACjD,SAASC,YAAY,QAAQ,6BAA6B;AAE1D;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASC,cAAcC,IAAY;IACxC,MAAMC,IAAIN,KAAK;IACf,MAAMO,aAAaL;IACnB,MAAMM,OAAOL,aAAaM,CAAAA,QAASA,MAAMD,IAAI;IAE7C,OAAOP,QAAQ;QACb,MAAMS,UAA2BF,KAAKG,GAAG,CAACC,CAAAA,MAAQ,CAAA;gBAChDC,IAAI,CAAC,IAAI,EAAED,IAAIE,KAAK,EAAE;gBACtBC,OAAOH,IAAII,KAAK;gBAChBC,MAAMV,WAAW,CAAC,eAAe,EAAEW,mBAAmBN,IAAIE,KAAK,GAAG;YACpE,CAAA;QAEA,OAAO;YACLK,KAAK;YACLC,QAAQ;gBACN;oBACED,KAAK;oBACLJ,OAAOT,EAAE;oBACTI,SAASA,QAAQW,MAAM,CAACC,CAAAA,SAAUvB,eAAeM,MAAMiB;oBACvDC,WAAW;gBACb;aACD;QACH;IACF,GAAG;QAACf;QAAMH;QAAMC;QAAGC;KAAW;AAChC"}
@@ -0,0 +1,22 @@
1
+ import { type CommandSource, type Entity, type EntityConstructor, type EntityMetadataSource } from '@entifix/core';
2
+ /** How a declared verb is actually carried out, keyed by the verb. */
3
+ export type UseCaseHandlers = Readonly<Record<string, () => Promise<void>>>;
4
+ /** One entity whose served affordances contribute palette commands. */
5
+ export interface UseCaseCommandEntity<TEntity extends Entity = Entity> {
6
+ readonly entityConstructor: EntityConstructor<TEntity>;
7
+ /** Where `GET /api/<entity>/$metadata` is reached. */
8
+ readonly metadataSource: EntityMetadataSource;
9
+ readonly handlers: UseCaseHandlers;
10
+ }
11
+ /**
12
+ * Every declared entity's palette verbs, as one source per entity.
13
+ *
14
+ * **Why the rule is disabled below.** `entities` is declared once at module
15
+ * scope by the host and closed over, so it is the same array on every render and
16
+ * the hook count is fixed — the invariant `react-hooks/rules-of-hooks` protects
17
+ * and cannot see, because it reasons about the loop syntactically rather than
18
+ * about the array's provenance. It is isolated here so the disable is written
19
+ * once, with its reason, exactly as `useEntityLinkSources` does for pickers.
20
+ */
21
+ export declare function useUseCaseSources(entities: readonly UseCaseCommandEntity[], term: string): CommandSource[];
22
+ //# sourceMappingURL=use-use-cases-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-use-cases-source.d.ts","sourceRoot":"","sources":["../../../src/lib/command-palette/use-use-cases-source.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,aAAa,EAElB,KAAK,MAAM,EACX,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAI1B,MAAM,eAAe,CAAC;AASvB,sEAAsE;AACtE,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAE5E,uEAAuE;AACvE,MAAM,WAAW,oBAAoB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM;IACnE,QAAQ,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACvD,sDAAsD;IACtD,QAAQ,CAAC,cAAc,EAAE,oBAAoB,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACpC;AAqFD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,SAAS,oBAAoB,EAAE,EACzC,IAAI,EAAE,MAAM,GACX,aAAa,EAAE,CAOjB"}
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+ import { _ as _extends } from "@swc/helpers/_/_extends";
3
+ import { EntifixLogicError, envelopeEntityName, matchesCommand, parseKeywords } from '@entifix/core';
4
+ import { useCasesForSurface, useT, useTranslateKey } from '@entifix/react-controls';
5
+ import { useEntityUseCases } from '@entifix/react-integration';
6
+ import { useMemo } from 'react';
7
+ /**
8
+ * The `unbound` verbs this caller may invoke, as one group.
9
+ *
10
+ * This is the surface ADR 0035 mapped and nothing rendered: all three
11
+ * `unbound:*` cells resolve to `'command-palette'`, and until now the palette
12
+ * did not exist, so a verb declared there was granted, exported, passed every
13
+ * `@r10c/slices` invariant and simply never appeared.
14
+ *
15
+ * The descriptors are **served, not imported**: `$metadata` filters them through
16
+ * `PolicyDecisionTag` against the verified principal, which is a stronger check
17
+ * than anything the browser could do — and it keeps every `@useCase()` class's
18
+ * `Effect` body, repository tags and import closure out of the client bundle.
19
+ *
20
+ * ⚠️ A descriptor with **no handler throws**, at the first render of the
21
+ * palette rather than on the click. A verb that appears and does nothing is
22
+ * worse than one that is absent: it reads as a broken feature rather than a
23
+ * missing wire, and it is the same posture `assertSearchable` and `surfaceFor`
24
+ * already take.
25
+ */ function useUseCasesSource(entity, term) {
26
+ const t = useT('shell');
27
+ const translateKey = useTranslateKey();
28
+ const { metadata, isLoading } = useEntityUseCases(entity.entityConstructor, entity.metadataSource);
29
+ // Outside the memo: `useCasesForSurface` is a plain filter, but the name reads
30
+ // as a hook to `react-hooks/rules-of-hooks`, which then refuses it inside a
31
+ // callback. Hoisting is free — it is pure and cheap.
32
+ const descriptors = useCasesForSurface('command-palette', metadata == null ? void 0 : metadata.useCases);
33
+ return useMemo(()=>{
34
+ const name = envelopeEntityName(entity.entityConstructor);
35
+ const options = descriptors.map((descriptor)=>{
36
+ const run = entity.handlers[descriptor.key];
37
+ if (run === undefined) {
38
+ throw new EntifixLogicError(`The use case "${descriptor.key}" on ${name} reaches the command palette but this host registered no handler for it. ` + 'Add one to the entity’s `handlers`, or stop declaring the verb `unbound`.', undefined, {
39
+ entity: name,
40
+ key: descriptor.key
41
+ });
42
+ }
43
+ return _extends({
44
+ id: `use-case:${name}:${descriptor.key}`,
45
+ label: translateKey(descriptor.labelKey),
46
+ keywords: parseKeywords(descriptor.keywordsKey === undefined ? undefined : translateKey(descriptor.keywordsKey)),
47
+ run
48
+ }, descriptor.confirm === undefined ? {} : {
49
+ confirm: {
50
+ tone: descriptor.confirm.tone,
51
+ message: translateKey(descriptor.confirm.messageKey)
52
+ }
53
+ });
54
+ });
55
+ return {
56
+ key: `use-cases:${name}`,
57
+ groups: [
58
+ {
59
+ key: `use-cases:${name}`,
60
+ label: t('commandPalette.groups.actions'),
61
+ options: options.filter((option)=>matchesCommand(term, option)),
62
+ isLoading
63
+ }
64
+ ]
65
+ };
66
+ }, [
67
+ entity,
68
+ descriptors,
69
+ isLoading,
70
+ term,
71
+ t,
72
+ translateKey
73
+ ]);
74
+ }
75
+ /**
76
+ * Every declared entity's palette verbs, as one source per entity.
77
+ *
78
+ * **Why the rule is disabled below.** `entities` is declared once at module
79
+ * scope by the host and closed over, so it is the same array on every render and
80
+ * the hook count is fixed — the invariant `react-hooks/rules-of-hooks` protects
81
+ * and cannot see, because it reasons about the loop syntactically rather than
82
+ * about the array's provenance. It is isolated here so the disable is written
83
+ * once, with its reason, exactly as `useEntityLinkSources` does for pickers.
84
+ */ export function useUseCaseSources(entities, term) {
85
+ const sources = [];
86
+ for (const entity of entities){
87
+ // eslint-disable-next-line react-hooks/rules-of-hooks -- fixed-length loop over a module-scope array; see the note above.
88
+ sources.push(useUseCasesSource(entity, term));
89
+ }
90
+ return sources;
91
+ }
92
+
93
+ //# sourceMappingURL=use-use-cases-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/command-palette/use-use-cases-source.ts"],"sourcesContent":["'use client';\n\nimport {\n type CommandOption,\n type CommandSource,\n EntifixLogicError,\n type Entity,\n type EntityConstructor,\n type EntityMetadataSource,\n envelopeEntityName,\n matchesCommand,\n parseKeywords,\n} from '@entifix/core';\nimport {\n useCasesForSurface,\n useT,\n useTranslateKey,\n} from '@entifix/react-controls';\nimport { useEntityUseCases } from '@entifix/react-integration';\nimport { useMemo } from 'react';\n\n/** How a declared verb is actually carried out, keyed by the verb. */\nexport type UseCaseHandlers = Readonly<Record<string, () => Promise<void>>>;\n\n/** One entity whose served affordances contribute palette commands. */\nexport interface UseCaseCommandEntity<TEntity extends Entity = Entity> {\n readonly entityConstructor: EntityConstructor<TEntity>;\n /** Where `GET /api/<entity>/$metadata` is reached. */\n readonly metadataSource: EntityMetadataSource;\n readonly handlers: UseCaseHandlers;\n}\n\n/**\n * The `unbound` verbs this caller may invoke, as one group.\n *\n * This is the surface ADR 0035 mapped and nothing rendered: all three\n * `unbound:*` cells resolve to `'command-palette'`, and until now the palette\n * did not exist, so a verb declared there was granted, exported, passed every\n * `@r10c/slices` invariant and simply never appeared.\n *\n * The descriptors are **served, not imported**: `$metadata` filters them through\n * `PolicyDecisionTag` against the verified principal, which is a stronger check\n * than anything the browser could do — and it keeps every `@useCase()` class's\n * `Effect` body, repository tags and import closure out of the client bundle.\n *\n * ⚠️ A descriptor with **no handler throws**, at the first render of the\n * palette rather than on the click. A verb that appears and does nothing is\n * worse than one that is absent: it reads as a broken feature rather than a\n * missing wire, and it is the same posture `assertSearchable` and `surfaceFor`\n * already take.\n */\nfunction useUseCasesSource(\n entity: UseCaseCommandEntity,\n term: string,\n): CommandSource {\n const t = useT('shell');\n const translateKey = useTranslateKey();\n const { metadata, isLoading } = useEntityUseCases(\n entity.entityConstructor,\n entity.metadataSource,\n );\n\n // Outside the memo: `useCasesForSurface` is a plain filter, but the name reads\n // as a hook to `react-hooks/rules-of-hooks`, which then refuses it inside a\n // callback. Hoisting is free — it is pure and cheap.\n const descriptors = useCasesForSurface('command-palette', metadata?.useCases);\n\n return useMemo(() => {\n const name = envelopeEntityName(entity.entityConstructor);\n\n const options: CommandOption[] = descriptors.map(descriptor => {\n const run = entity.handlers[descriptor.key];\n if (run === undefined) {\n throw new EntifixLogicError(\n `The use case \"${descriptor.key}\" on ${name} reaches the command palette but this host registered no handler for it. ` +\n 'Add one to the entity’s `handlers`, or stop declaring the verb `unbound`.',\n undefined,\n { entity: name, key: descriptor.key },\n );\n }\n\n return {\n id: `use-case:${name}:${descriptor.key}`,\n label: translateKey(descriptor.labelKey),\n keywords: parseKeywords(\n descriptor.keywordsKey === undefined\n ? undefined\n : translateKey(descriptor.keywordsKey),\n ),\n run,\n ...(descriptor.confirm === undefined\n ? {}\n : {\n confirm: {\n tone: descriptor.confirm.tone,\n message: translateKey(descriptor.confirm.messageKey),\n },\n }),\n } satisfies CommandOption;\n });\n\n return {\n key: `use-cases:${name}`,\n groups: [\n {\n key: `use-cases:${name}`,\n label: t('commandPalette.groups.actions'),\n options: options.filter(option => matchesCommand(term, option)),\n isLoading,\n },\n ],\n } satisfies CommandSource;\n }, [entity, descriptors, isLoading, term, t, translateKey]);\n}\n\n/**\n * Every declared entity's palette verbs, as one source per entity.\n *\n * **Why the rule is disabled below.** `entities` is declared once at module\n * scope by the host and closed over, so it is the same array on every render and\n * the hook count is fixed — the invariant `react-hooks/rules-of-hooks` protects\n * and cannot see, because it reasons about the loop syntactically rather than\n * about the array's provenance. It is isolated here so the disable is written\n * once, with its reason, exactly as `useEntityLinkSources` does for pickers.\n */\nexport function useUseCaseSources(\n entities: readonly UseCaseCommandEntity[],\n term: string,\n): CommandSource[] {\n const sources: CommandSource[] = [];\n for (const entity of entities) {\n // eslint-disable-next-line react-hooks/rules-of-hooks -- fixed-length loop over a module-scope array; see the note above.\n sources.push(useUseCasesSource(entity, term));\n }\n return sources;\n}\n"],"names":["EntifixLogicError","envelopeEntityName","matchesCommand","parseKeywords","useCasesForSurface","useT","useTranslateKey","useEntityUseCases","useMemo","useUseCasesSource","entity","term","t","translateKey","metadata","isLoading","entityConstructor","metadataSource","descriptors","useCases","name","options","map","descriptor","run","handlers","key","undefined","id","label","labelKey","keywords","keywordsKey","confirm","tone","message","messageKey","groups","filter","option","useUseCaseSources","entities","sources","push"],"mappings":"AAAA;;AAEA,SAGEA,iBAAiB,EAIjBC,kBAAkB,EAClBC,cAAc,EACdC,aAAa,QACR,gBAAgB;AACvB,SACEC,kBAAkB,EAClBC,IAAI,EACJC,eAAe,QACV,0BAA0B;AACjC,SAASC,iBAAiB,QAAQ,6BAA6B;AAC/D,SAASC,OAAO,QAAQ,QAAQ;AAahC;;;;;;;;;;;;;;;;;;CAkBC,GACD,SAASC,kBACPC,MAA4B,EAC5BC,IAAY;IAEZ,MAAMC,IAAIP,KAAK;IACf,MAAMQ,eAAeP;IACrB,MAAM,EAAEQ,QAAQ,EAAEC,SAAS,EAAE,GAAGR,kBAC9BG,OAAOM,iBAAiB,EACxBN,OAAOO,cAAc;IAGvB,+EAA+E;IAC/E,4EAA4E;IAC5E,qDAAqD;IACrD,MAAMC,cAAcd,mBAAmB,mBAAmBU,4BAAAA,SAAUK,QAAQ;IAE5E,OAAOX,QAAQ;QACb,MAAMY,OAAOnB,mBAAmBS,OAAOM,iBAAiB;QAExD,MAAMK,UAA2BH,YAAYI,GAAG,CAACC,CAAAA;YAC/C,MAAMC,MAAMd,OAAOe,QAAQ,CAACF,WAAWG,GAAG,CAAC;YAC3C,IAAIF,QAAQG,WAAW;gBACrB,MAAM,IAAI3B,kBACR,CAAC,cAAc,EAAEuB,WAAWG,GAAG,CAAC,KAAK,EAAEN,KAAK,yEAAyE,CAAC,GACpH,6EACFO,WACA;oBAAEjB,QAAQU;oBAAMM,KAAKH,WAAWG,GAAG;gBAAC;YAExC;YAEA,OAAO;gBACLE,IAAI,CAAC,SAAS,EAAER,KAAK,CAAC,EAAEG,WAAWG,GAAG,EAAE;gBACxCG,OAAOhB,aAAaU,WAAWO,QAAQ;gBACvCC,UAAU5B,cACRoB,WAAWS,WAAW,KAAKL,YACvBA,YACAd,aAAaU,WAAWS,WAAW;gBAEzCR;eACID,WAAWU,OAAO,KAAKN,YACvB,CAAC,IACD;gBACEM,SAAS;oBACPC,MAAMX,WAAWU,OAAO,CAACC,IAAI;oBAC7BC,SAAStB,aAAaU,WAAWU,OAAO,CAACG,UAAU;gBACrD;YACF;QAER;QAEA,OAAO;YACLV,KAAK,CAAC,UAAU,EAAEN,MAAM;YACxBiB,QAAQ;gBACN;oBACEX,KAAK,CAAC,UAAU,EAAEN,MAAM;oBACxBS,OAAOjB,EAAE;oBACTS,SAASA,QAAQiB,MAAM,CAACC,CAAAA,SAAUrC,eAAeS,MAAM4B;oBACvDxB;gBACF;aACD;QACH;IACF,GAAG;QAACL;QAAQQ;QAAaH;QAAWJ;QAAMC;QAAGC;KAAa;AAC5D;AAEA;;;;;;;;;CASC,GACD,OAAO,SAAS2B,kBACdC,QAAyC,EACzC9B,IAAY;IAEZ,MAAM+B,UAA2B,EAAE;IACnC,KAAK,MAAMhC,UAAU+B,SAAU;QAC7B,0HAA0H;QAC1HC,QAAQC,IAAI,CAAClC,kBAAkBC,QAAQC;IACzC;IACA,OAAO+B;AACT"}
@@ -0,0 +1,51 @@
1
+ /** A group of parameters as `ConfigurationPlain` carries them. */
2
+ type PlainConfiguration = Record<string, Array<{
3
+ key: string;
4
+ value: unknown;
5
+ }>>;
6
+ /**
7
+ * Rewrites backend addresses to the app's own same-origin proxy paths.
8
+ *
9
+ * config-service holds the real address, which is what the *server* needs. A
10
+ * browser pointed at it would be making a cross-origin request that carries no
11
+ * session cookie and gets a `401` — `entifix_at` is httpOnly and same-origin, and
12
+ * host-scoping it does not help, since that governs which host stores the cookie
13
+ * rather than which cross-origin requests send it.
14
+ *
15
+ * So each service domain is swapped for a path on this app's own origin before
16
+ * the browser ever sees it, which leaves the entity adapters unaware that a proxy
17
+ * exists at all. `map` is keyed by the configuration key, e.g.
18
+ * `{ 'config-service-domain': '/api/system' }`.
19
+ *
20
+ * Keys absent from `map` are passed through untouched, so an app only names the
21
+ * domains it actually proxies.
22
+ */
23
+ export declare const rewriteServiceDomains: (plain: PlainConfiguration, map: Readonly<Record<string, string>>) => PlainConfiguration;
24
+ /**
25
+ * Where config-service answers, for the callers that reach it directly.
26
+ *
27
+ * One declaration rather than two: this route is no longer the only server-side
28
+ * caller — the storefront's server components read their own configuration from
29
+ * config-service without a route in between, because a Next app cannot fetch
30
+ * its own `/api/config` while prerendering itself.
31
+ */
32
+ export declare const configApiUrl: () => string;
33
+ export interface ConfigRouteOptions {
34
+ /** This app's key in the `configuration` table, e.g. `back-office-app`. */
35
+ readonly service: string;
36
+ /** Service domains to rewrite to same-origin proxy paths. */
37
+ readonly proxies?: Readonly<Record<string, string>>;
38
+ /** Overrides `CONFIG_API_URL`; mainly for tests. */
39
+ readonly configApiUrl?: string;
40
+ }
41
+ /**
42
+ * Builds the `GET /api/config` route handler every Next app exposes: fetch this
43
+ * app's configuration from config-service, rewrite the proxied domains, and hand
44
+ * the result to the browser.
45
+ *
46
+ * Shared because all three apps did the same thing, and because the shared-token
47
+ * header is the kind of detail that goes stale in one copy out of three.
48
+ */
49
+ export declare const createConfigRoute: ({ service, proxies, configApiUrl: configApiUrlOverride, }: ConfigRouteOptions) => () => Promise<Response>;
50
+ export {};
51
+ //# sourceMappingURL=config-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-route.d.ts","sourceRoot":"","sources":["../../../src/lib/config/config-route.ts"],"names":[],"mappings":"AAIA,kEAAkE;AAClE,KAAK,kBAAkB,GAAG,MAAM,CAC9B,MAAM,EACN,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CACvC,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,GAChC,OAAO,kBAAkB,EACzB,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KACpC,kBAOD,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,QAAO,MAC0B,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAAI,2DAI/B,kBAAkB,WACG,OAAO,CAAC,QAAQ,CAkBrC,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { NextResponse } from 'next/server';
3
+ import { SERVICE_TOKEN_HEADER, serviceToken } from './service-token.js';
4
+ /**
5
+ * Rewrites backend addresses to the app's own same-origin proxy paths.
6
+ *
7
+ * config-service holds the real address, which is what the *server* needs. A
8
+ * browser pointed at it would be making a cross-origin request that carries no
9
+ * session cookie and gets a `401` — `entifix_at` is httpOnly and same-origin, and
10
+ * host-scoping it does not help, since that governs which host stores the cookie
11
+ * rather than which cross-origin requests send it.
12
+ *
13
+ * So each service domain is swapped for a path on this app's own origin before
14
+ * the browser ever sees it, which leaves the entity adapters unaware that a proxy
15
+ * exists at all. `map` is keyed by the configuration key, e.g.
16
+ * `{ 'config-service-domain': '/api/system' }`.
17
+ *
18
+ * Keys absent from `map` are passed through untouched, so an app only names the
19
+ * domains it actually proxies.
20
+ */ export const rewriteServiceDomains = (plain, map)=>{
21
+ var _plain_uri;
22
+ return _extends({}, plain, {
23
+ uri: ((_plain_uri = plain['uri']) != null ? _plain_uri : []).map((parameter)=>parameter.key in map ? _extends({}, parameter, {
24
+ value: map[parameter.key]
25
+ }) : parameter)
26
+ });
27
+ };
28
+ /**
29
+ * Where config-service answers, for the callers that reach it directly.
30
+ *
31
+ * One declaration rather than two: this route is no longer the only server-side
32
+ * caller — the storefront's server components read their own configuration from
33
+ * config-service without a route in between, because a Next app cannot fetch
34
+ * its own `/api/config` while prerendering itself.
35
+ */ export const configApiUrl = ()=>{
36
+ var _process_env_CONFIG_API_URL;
37
+ return (_process_env_CONFIG_API_URL = process.env['CONFIG_API_URL']) != null ? _process_env_CONFIG_API_URL : 'http://localhost:3190';
38
+ };
39
+ /**
40
+ * Builds the `GET /api/config` route handler every Next app exposes: fetch this
41
+ * app's configuration from config-service, rewrite the proxied domains, and hand
42
+ * the result to the browser.
43
+ *
44
+ * Shared because all three apps did the same thing, and because the shared-token
45
+ * header is the kind of detail that goes stale in one copy out of three.
46
+ */ export const createConfigRoute = ({ service, proxies = {}, configApiUrl: configApiUrlOverride })=>async function GET() {
47
+ const baseUrl = configApiUrlOverride != null ? configApiUrlOverride : configApiUrl();
48
+ const response = await fetch(`${baseUrl}/api/config/${service}`, {
49
+ cache: 'no-store',
50
+ headers: {
51
+ [SERVICE_TOKEN_HEADER]: serviceToken()
52
+ }
53
+ });
54
+ if (!response.ok) {
55
+ return NextResponse.json({
56
+ error: 'Failed to load configuration'
57
+ }, {
58
+ status: 502
59
+ });
60
+ }
61
+ const plain = await response.json();
62
+ return NextResponse.json(rewriteServiceDomains(plain, proxies));
63
+ };
64
+
65
+ //# sourceMappingURL=config-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/config/config-route.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\n\nimport { SERVICE_TOKEN_HEADER, serviceToken } from './service-token.js';\n\n/** A group of parameters as `ConfigurationPlain` carries them. */\ntype PlainConfiguration = Record<\n string,\n Array<{ key: string; value: unknown }>\n>;\n\n/**\n * Rewrites backend addresses to the app's own same-origin proxy paths.\n *\n * config-service holds the real address, which is what the *server* needs. A\n * browser pointed at it would be making a cross-origin request that carries no\n * session cookie and gets a `401` — `entifix_at` is httpOnly and same-origin, and\n * host-scoping it does not help, since that governs which host stores the cookie\n * rather than which cross-origin requests send it.\n *\n * So each service domain is swapped for a path on this app's own origin before\n * the browser ever sees it, which leaves the entity adapters unaware that a proxy\n * exists at all. `map` is keyed by the configuration key, e.g.\n * `{ 'config-service-domain': '/api/system' }`.\n *\n * Keys absent from `map` are passed through untouched, so an app only names the\n * domains it actually proxies.\n */\nexport const rewriteServiceDomains = (\n plain: PlainConfiguration,\n map: Readonly<Record<string, string>>,\n): PlainConfiguration => ({\n ...plain,\n uri: (plain['uri'] ?? []).map(parameter =>\n parameter.key in map\n ? { ...parameter, value: map[parameter.key] }\n : parameter,\n ),\n});\n\n/**\n * Where config-service answers, for the callers that reach it directly.\n *\n * One declaration rather than two: this route is no longer the only server-side\n * caller — the storefront's server components read their own configuration from\n * config-service without a route in between, because a Next app cannot fetch\n * its own `/api/config` while prerendering itself.\n */\nexport const configApiUrl = (): string =>\n process.env['CONFIG_API_URL'] ?? 'http://localhost:3190';\n\nexport interface ConfigRouteOptions {\n /** This app's key in the `configuration` table, e.g. `back-office-app`. */\n readonly service: string;\n /** Service domains to rewrite to same-origin proxy paths. */\n readonly proxies?: Readonly<Record<string, string>>;\n /** Overrides `CONFIG_API_URL`; mainly for tests. */\n readonly configApiUrl?: string;\n}\n\n/**\n * Builds the `GET /api/config` route handler every Next app exposes: fetch this\n * app's configuration from config-service, rewrite the proxied domains, and hand\n * the result to the browser.\n *\n * Shared because all three apps did the same thing, and because the shared-token\n * header is the kind of detail that goes stale in one copy out of three.\n */\nexport const createConfigRoute = ({\n service,\n proxies = {},\n configApiUrl: configApiUrlOverride,\n}: ConfigRouteOptions) =>\n async function GET(): Promise<Response> {\n const baseUrl = configApiUrlOverride ?? configApiUrl();\n\n const response = await fetch(`${baseUrl}/api/config/${service}`, {\n cache: 'no-store',\n headers: { [SERVICE_TOKEN_HEADER]: serviceToken() },\n });\n\n if (!response.ok) {\n return NextResponse.json(\n { error: 'Failed to load configuration' },\n { status: 502 },\n );\n }\n\n const plain = (await response.json()) as PlainConfiguration;\n\n return NextResponse.json(rewriteServiceDomains(plain, proxies));\n };\n"],"names":["NextResponse","SERVICE_TOKEN_HEADER","serviceToken","rewriteServiceDomains","plain","map","uri","parameter","key","value","configApiUrl","process","env","createConfigRoute","service","proxies","configApiUrlOverride","GET","baseUrl","response","fetch","cache","headers","ok","json","error","status"],"mappings":";AAAA,SAASA,YAAY,QAAQ,cAAc;AAE3C,SAASC,oBAAoB,EAAEC,YAAY,QAAQ,qBAAqB;AAQxE;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMC,wBAAwB,CACnCC,OACAC;QAGMD;WAFkB,aACrBA;QACHE,KAAK,EAACF,aAAAA,KAAK,CAAC,MAAM,YAAZA,aAAgB,EAAE,EAAEC,GAAG,CAACE,CAAAA,YAC5BA,UAAUC,GAAG,IAAIH,MACb,aAAKE;gBAAWE,OAAOJ,GAAG,CAACE,UAAUC,GAAG,CAAC;iBACzCD;;EAEL;AAEH;;;;;;;CAOC,GACD,OAAO,MAAMG,eAAe;QAC1BC;YAAAA,8BAAAA,QAAQC,GAAG,CAAC,iBAAiB,YAA7BD,8BAAiC;EAAwB;AAW3D;;;;;;;CAOC,GACD,OAAO,MAAME,oBAAoB,CAAC,EAChCC,OAAO,EACPC,UAAU,CAAC,CAAC,EACZL,cAAcM,oBAAoB,EACf,GACnB,eAAeC;QACb,MAAMC,UAAUF,+BAAAA,uBAAwBN;QAExC,MAAMS,WAAW,MAAMC,MAAM,GAAGF,QAAQ,YAAY,EAAEJ,SAAS,EAAE;YAC/DO,OAAO;YACPC,SAAS;gBAAE,CAACrB,qBAAqB,EAAEC;YAAe;QACpD;QAEA,IAAI,CAACiB,SAASI,EAAE,EAAE;YAChB,OAAOvB,aAAawB,IAAI,CACtB;gBAAEC,OAAO;YAA+B,GACxC;gBAAEC,QAAQ;YAAI;QAElB;QAEA,MAAMtB,QAAS,MAAMe,SAASK,IAAI;QAElC,OAAOxB,aAAawB,IAAI,CAACrB,sBAAsBC,OAAOW;IACxD,EAAE"}
@@ -0,0 +1,28 @@
1
+ type Params = {
2
+ params: Promise<{
3
+ path: string[];
4
+ }>;
5
+ };
6
+ export interface ServiceProxyRouteOptions {
7
+ /** Where the service listens, without the `/api` suffix. */
8
+ readonly baseUrl: string;
9
+ /**
10
+ * A segment to put back in front of the forwarded path.
11
+ *
12
+ * ⚠️ **Needed when the proxy's namespace *is* the upstream's route name.**
13
+ * `/api/stock/stock-item` forwards to `/api/stock-item` because `stock` names
14
+ * the service and `stock-item` names the entity — two different words. But
15
+ * `/api/transaction/events` forwards to `/api/events`, which exists nowhere:
16
+ * there the namespace and the route are the same word, and stripping it
17
+ * strips half the address.
18
+ *
19
+ * Caught on the live lab rather than by any check, and the symptom is exactly
20
+ * the one this proxy exists to prevent — the SSE stream 404s, a pending write
21
+ * never settles, and every probe stays green
22
+ * ([ADR 0036](../../../../../../docs/adr/0036-the-reactive-stream-is-server-sent-and-same-origin.md)).
23
+ */
24
+ readonly pathPrefix?: string;
25
+ }
26
+ export declare const createServiceProxyRoute: ({ baseUrl, pathPrefix, }: ServiceProxyRouteOptions) => (request: Request, { params }: Params) => Promise<Response>;
27
+ export {};
28
+ //# sourceMappingURL=service-proxy-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-proxy-route.d.ts","sourceRoot":"","sources":["../../../src/lib/config/service-proxy-route.ts"],"names":[],"mappings":"AAIA,KAAK,MAAM,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC;AAEtD,MAAM,WAAW,wBAAwB;IACvC,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAgED,eAAO,MAAM,uBAAuB,GAAI,0BAGrC,wBAAwB,eAEd,OAAO,cACJ,MAAM,KACjB,OAAO,CAAC,QAAQ,CA6DpB,CAAC"}
@@ -0,0 +1,125 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { NextResponse } from 'next/server';
3
+ import { bearerHeader, sessionToken } from '../session/bearer.js';
4
+ /**
5
+ * Builds a same-origin proxy for one backend service, mounted as a catch-all
6
+ * route handler — e.g. `app/api/admin/[...path]/route.ts`.
7
+ *
8
+ * The browser can never call `:310N` directly: `entifix_at` is httpOnly and
9
+ * same-origin, so a cross-origin request carries no cookie and a guarded route
10
+ * answers `401`. Host-scoping the cookie does not help — that governs which host
11
+ * *stores* it, not which cross-origin requests send it. Routing through the app's
12
+ * own origin makes the cookie automatic and needs no CORS, which is the same
13
+ * reason the credential routes are shaped this way.
14
+ *
15
+ * The cookie is forwarded upstream as a bearer token. **This proxy grants
16
+ * nothing** — the service still verifies the token and applies its own
17
+ * `requirePermission`. It only carries.
18
+ *
19
+ * It also carries the **caching contract** in both directions, which it did not
20
+ * always: rebuilding every response as a fresh body dropped `ETag`,
21
+ * `Cache-Control` and `Vary`, and forwarded no `If-None-Match`. The effect was
22
+ * invisible and entirely wasteful — `GET /api/<entity>/$metadata` computes and
23
+ * hashes a permission-filtered document per request, and a validator that never
24
+ * reaches the service means it can never answer `304`. Worse, `Vary` is a
25
+ * *correctness* header here: the document differs per caller, and one stripped
26
+ * of `Vary: Cookie, Authorization` may be cached and served to a different
27
+ * principal.
28
+ *
29
+ * A factory, because a host mounts one of these per backend it talks to, and
30
+ * back-office-app now talks to two: marketplace-admin-service for the catalog a
31
+ * vendor authors, marketplace-service for the platform vocabulary it is
32
+ * classified in (ADR 0022).
33
+ */ /**
34
+ * Response headers that must survive the rebuild.
35
+ *
36
+ * Deliberately a short allow-list rather than copying every header: the
37
+ * upstream's `content-length` and `content-encoding` describe *its* body, and
38
+ * carrying them onto a response this function reconstructs is how a proxy
39
+ * serves a truncated payload.
40
+ */ const CACHE_HEADERS = [
41
+ 'etag',
42
+ 'cache-control',
43
+ 'vary'
44
+ ];
45
+ /**
46
+ * Whether the upstream answer must be piped rather than rebuilt.
47
+ *
48
+ * Reading a `text/event-stream` body to completion holds the request open
49
+ * forever and delivers nothing — no error, no timeout, and the browser's
50
+ * `EventSource` sits in `CONNECTING` while the service is happily emitting.
51
+ * It is the most likely way to build the reactive stream, see silence, and go
52
+ * looking in the wrong service (ADR 0036).
53
+ */ const isStreamed = (upstream)=>{
54
+ var _upstream_headers_get;
55
+ return ((_upstream_headers_get = upstream.headers.get('content-type')) == null ? void 0 : _upstream_headers_get.startsWith('text/event-stream')) === true;
56
+ };
57
+ const passThrough = (upstream)=>{
58
+ const headers = {};
59
+ for (const name of CACHE_HEADERS){
60
+ const value = upstream.headers.get(name);
61
+ if (value !== null) headers[name] = value;
62
+ }
63
+ return headers;
64
+ };
65
+ export const createServiceProxyRoute = ({ baseUrl, pathPrefix })=>{
66
+ const forward = async (request, { params })=>{
67
+ const { path } = await params;
68
+ const search = new URL(request.url).search;
69
+ const headers = _extends({
70
+ 'content-type': 'application/json'
71
+ }, bearerHeader(await sessionToken()));
72
+ // Forwarded, or the validator never reaches the service and every
73
+ // `$metadata` read is a full document — see {@link CACHE_HEADERS}.
74
+ const ifNoneMatch = request.headers.get('if-none-match');
75
+ if (ifNoneMatch !== null) {
76
+ headers['if-none-match'] = ifNoneMatch;
77
+ }
78
+ const hasBody = request.method !== 'GET' && request.method !== 'DELETE';
79
+ const upstream = await fetch(`${baseUrl}/api/${[
80
+ pathPrefix,
81
+ ...path
82
+ ].filter(Boolean).join('/')}${search}`, {
83
+ method: request.method,
84
+ headers,
85
+ body: hasBody ? await request.text() : undefined,
86
+ cache: 'no-store'
87
+ });
88
+ const passed = passThrough(upstream);
89
+ // A `304` carries no body by definition, and neither does a `204`. Running
90
+ // either through a body read is not merely wasteful — a `304` reconstructed
91
+ // with a body is no longer a `304`.
92
+ if (upstream.status === 304) {
93
+ return new NextResponse(null, {
94
+ status: 304,
95
+ headers: passed
96
+ });
97
+ }
98
+ // Piped, never buffered, and never re-typed as JSON: the body is an open
99
+ // stream of frames, and `no-store` is correctness rather than politeness —
100
+ // this response is scoped to one principal.
101
+ if (isStreamed(upstream)) {
102
+ return new NextResponse(upstream.body, {
103
+ status: upstream.status,
104
+ headers: _extends({}, passed, {
105
+ 'content-type': 'text/event-stream',
106
+ 'cache-control': 'no-store'
107
+ })
108
+ });
109
+ }
110
+ // 204s and empty bodies must not be run through `json()`.
111
+ const text = await upstream.text();
112
+ return text === '' ? new NextResponse(null, {
113
+ status: upstream.status,
114
+ headers: passed
115
+ }) : new NextResponse(text, {
116
+ status: upstream.status,
117
+ headers: _extends({}, passed, {
118
+ 'content-type': 'application/json'
119
+ })
120
+ });
121
+ };
122
+ return forward;
123
+ };
124
+
125
+ //# sourceMappingURL=service-proxy-route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/config/service-proxy-route.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\n\nimport { bearerHeader, sessionToken } from '../session/bearer.js';\n\ntype Params = { params: Promise<{ path: string[] }> };\n\nexport interface ServiceProxyRouteOptions {\n /** Where the service listens, without the `/api` suffix. */\n readonly baseUrl: string;\n /**\n * A segment to put back in front of the forwarded path.\n *\n * ⚠️ **Needed when the proxy's namespace *is* the upstream's route name.**\n * `/api/stock/stock-item` forwards to `/api/stock-item` because `stock` names\n * the service and `stock-item` names the entity — two different words. But\n * `/api/transaction/events` forwards to `/api/events`, which exists nowhere:\n * there the namespace and the route are the same word, and stripping it\n * strips half the address.\n *\n * Caught on the live lab rather than by any check, and the symptom is exactly\n * the one this proxy exists to prevent — the SSE stream 404s, a pending write\n * never settles, and every probe stays green\n * ([ADR 0036](../../../../../../docs/adr/0036-the-reactive-stream-is-server-sent-and-same-origin.md)).\n */\n readonly pathPrefix?: string;\n}\n\n/**\n * Builds a same-origin proxy for one backend service, mounted as a catch-all\n * route handler — e.g. `app/api/admin/[...path]/route.ts`.\n *\n * The browser can never call `:310N` directly: `entifix_at` is httpOnly and\n * same-origin, so a cross-origin request carries no cookie and a guarded route\n * answers `401`. Host-scoping the cookie does not help — that governs which host\n * *stores* it, not which cross-origin requests send it. Routing through the app's\n * own origin makes the cookie automatic and needs no CORS, which is the same\n * reason the credential routes are shaped this way.\n *\n * The cookie is forwarded upstream as a bearer token. **This proxy grants\n * nothing** — the service still verifies the token and applies its own\n * `requirePermission`. It only carries.\n *\n * It also carries the **caching contract** in both directions, which it did not\n * always: rebuilding every response as a fresh body dropped `ETag`,\n * `Cache-Control` and `Vary`, and forwarded no `If-None-Match`. The effect was\n * invisible and entirely wasteful — `GET /api/<entity>/$metadata` computes and\n * hashes a permission-filtered document per request, and a validator that never\n * reaches the service means it can never answer `304`. Worse, `Vary` is a\n * *correctness* header here: the document differs per caller, and one stripped\n * of `Vary: Cookie, Authorization` may be cached and served to a different\n * principal.\n *\n * A factory, because a host mounts one of these per backend it talks to, and\n * back-office-app now talks to two: marketplace-admin-service for the catalog a\n * vendor authors, marketplace-service for the platform vocabulary it is\n * classified in (ADR 0022).\n */\n/**\n * Response headers that must survive the rebuild.\n *\n * Deliberately a short allow-list rather than copying every header: the\n * upstream's `content-length` and `content-encoding` describe *its* body, and\n * carrying them onto a response this function reconstructs is how a proxy\n * serves a truncated payload.\n */\nconst CACHE_HEADERS = ['etag', 'cache-control', 'vary'] as const;\n\n/**\n * Whether the upstream answer must be piped rather than rebuilt.\n *\n * Reading a `text/event-stream` body to completion holds the request open\n * forever and delivers nothing — no error, no timeout, and the browser's\n * `EventSource` sits in `CONNECTING` while the service is happily emitting.\n * It is the most likely way to build the reactive stream, see silence, and go\n * looking in the wrong service (ADR 0036).\n */\nconst isStreamed = (upstream: Response): boolean =>\n upstream.headers.get('content-type')?.startsWith('text/event-stream') ===\n true;\n\nconst passThrough = (upstream: Response): Record<string, string> => {\n const headers: Record<string, string> = {};\n for (const name of CACHE_HEADERS) {\n const value = upstream.headers.get(name);\n if (value !== null) headers[name] = value;\n }\n return headers;\n};\n\nexport const createServiceProxyRoute = ({\n baseUrl,\n pathPrefix,\n}: ServiceProxyRouteOptions) => {\n const forward = async (\n request: Request,\n { params }: Params,\n ): Promise<Response> => {\n const { path } = await params;\n const search = new URL(request.url).search;\n\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n ...bearerHeader(await sessionToken()),\n };\n\n // Forwarded, or the validator never reaches the service and every\n // `$metadata` read is a full document — see {@link CACHE_HEADERS}.\n const ifNoneMatch = request.headers.get('if-none-match');\n if (ifNoneMatch !== null) {\n headers['if-none-match'] = ifNoneMatch;\n }\n\n const hasBody = request.method !== 'GET' && request.method !== 'DELETE';\n const upstream = await fetch(\n `${baseUrl}/api/${[pathPrefix, ...path].filter(Boolean).join('/')}${search}`,\n {\n method: request.method,\n headers,\n body: hasBody ? await request.text() : undefined,\n cache: 'no-store',\n },\n );\n\n const passed = passThrough(upstream);\n\n // A `304` carries no body by definition, and neither does a `204`. Running\n // either through a body read is not merely wasteful — a `304` reconstructed\n // with a body is no longer a `304`.\n if (upstream.status === 304) {\n return new NextResponse(null, { status: 304, headers: passed });\n }\n\n // Piped, never buffered, and never re-typed as JSON: the body is an open\n // stream of frames, and `no-store` is correctness rather than politeness —\n // this response is scoped to one principal.\n if (isStreamed(upstream)) {\n return new NextResponse(upstream.body, {\n status: upstream.status,\n headers: {\n ...passed,\n 'content-type': 'text/event-stream',\n 'cache-control': 'no-store',\n },\n });\n }\n\n // 204s and empty bodies must not be run through `json()`.\n const text = await upstream.text();\n return text === ''\n ? new NextResponse(null, { status: upstream.status, headers: passed })\n : new NextResponse(text, {\n status: upstream.status,\n headers: { ...passed, 'content-type': 'application/json' },\n });\n };\n\n return forward;\n};\n"],"names":["NextResponse","bearerHeader","sessionToken","CACHE_HEADERS","isStreamed","upstream","headers","get","startsWith","passThrough","name","value","createServiceProxyRoute","baseUrl","pathPrefix","forward","request","params","path","search","URL","url","ifNoneMatch","hasBody","method","fetch","filter","Boolean","join","body","text","undefined","cache","passed","status"],"mappings":";AAAA,SAASA,YAAY,QAAQ,cAAc;AAE3C,SAASC,YAAY,EAAEC,YAAY,QAAQ,uBAAuB;AAyBlE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BC,GACD;;;;;;;CAOC,GACD,MAAMC,gBAAgB;IAAC;IAAQ;IAAiB;CAAO;AAEvD;;;;;;;;CAQC,GACD,MAAMC,aAAa,CAACC;QAClBA;WAAAA,EAAAA,wBAAAA,SAASC,OAAO,CAACC,GAAG,CAAC,oCAArBF,sBAAsCG,UAAU,CAAC,0BACjD;;AAEF,MAAMC,cAAc,CAACJ;IACnB,MAAMC,UAAkC,CAAC;IACzC,KAAK,MAAMI,QAAQP,cAAe;QAChC,MAAMQ,QAAQN,SAASC,OAAO,CAACC,GAAG,CAACG;QACnC,IAAIC,UAAU,MAAML,OAAO,CAACI,KAAK,GAAGC;IACtC;IACA,OAAOL;AACT;AAEA,OAAO,MAAMM,0BAA0B,CAAC,EACtCC,OAAO,EACPC,UAAU,EACe;IACzB,MAAMC,UAAU,OACdC,SACA,EAAEC,MAAM,EAAU;QAElB,MAAM,EAAEC,IAAI,EAAE,GAAG,MAAMD;QACvB,MAAME,SAAS,IAAIC,IAAIJ,QAAQK,GAAG,EAAEF,MAAM;QAE1C,MAAMb,UAAkC;YACtC,gBAAgB;WACbL,aAAa,MAAMC;QAGxB,kEAAkE;QAClE,mEAAmE;QACnE,MAAMoB,cAAcN,QAAQV,OAAO,CAACC,GAAG,CAAC;QACxC,IAAIe,gBAAgB,MAAM;YACxBhB,OAAO,CAAC,gBAAgB,GAAGgB;QAC7B;QAEA,MAAMC,UAAUP,QAAQQ,MAAM,KAAK,SAASR,QAAQQ,MAAM,KAAK;QAC/D,MAAMnB,WAAW,MAAMoB,MACrB,GAAGZ,QAAQ,KAAK,EAAE;YAACC;eAAeI;SAAK,CAACQ,MAAM,CAACC,SAASC,IAAI,CAAC,OAAOT,QAAQ,EAC5E;YACEK,QAAQR,QAAQQ,MAAM;YACtBlB;YACAuB,MAAMN,UAAU,MAAMP,QAAQc,IAAI,KAAKC;YACvCC,OAAO;QACT;QAGF,MAAMC,SAASxB,YAAYJ;QAE3B,2EAA2E;QAC3E,4EAA4E;QAC5E,oCAAoC;QACpC,IAAIA,SAAS6B,MAAM,KAAK,KAAK;YAC3B,OAAO,IAAIlC,aAAa,MAAM;gBAAEkC,QAAQ;gBAAK5B,SAAS2B;YAAO;QAC/D;QAEA,yEAAyE;QACzE,2EAA2E;QAC3E,4CAA4C;QAC5C,IAAI7B,WAAWC,WAAW;YACxB,OAAO,IAAIL,aAAaK,SAASwB,IAAI,EAAE;gBACrCK,QAAQ7B,SAAS6B,MAAM;gBACvB5B,SAAS,aACJ2B;oBACH,gBAAgB;oBAChB,iBAAiB;;YAErB;QACF;QAEA,0DAA0D;QAC1D,MAAMH,OAAO,MAAMzB,SAASyB,IAAI;QAChC,OAAOA,SAAS,KACZ,IAAI9B,aAAa,MAAM;YAAEkC,QAAQ7B,SAAS6B,MAAM;YAAE5B,SAAS2B;QAAO,KAClE,IAAIjC,aAAa8B,MAAM;YACrBI,QAAQ7B,SAAS6B,MAAM;YACvB5B,SAAS,aAAK2B;gBAAQ,gBAAgB;;QACxC;IACN;IAEA,OAAOlB;AACT,EAAE"}
@@ -0,0 +1,5 @@
1
+ /** Header config-service reads the shared fleet token from. */
2
+ export declare const SERVICE_TOKEN_HEADER = "x-service-token";
3
+ /** Read on each call so a test (or a rotated deployment) can change it. */
4
+ export declare const serviceToken: () => string;
5
+ //# sourceMappingURL=service-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-token.d.ts","sourceRoot":"","sources":["../../../src/lib/config/service-token.ts"],"names":[],"mappings":"AAaA,+DAA+D;AAC/D,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,YAAY,QAAO,MACuB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * config-service's fleet lookup is gated on a shared token, since it serves real
3
+ * connection strings and the JWT signing key. The literal default keeps local dev
4
+ * zero-config; it is replaced per environment through `CONFIG_SERVICE_TOKEN`.
5
+ *
6
+ * This module is server-only, so the token never reaches the browser.
7
+ *
8
+ * It lives on its own because *two* server routes call the gated endpoint: the
9
+ * `/api/config` fetch and the readiness probe. Readiness reaching it without the
10
+ * header is how an app answers `503` forever while being perfectly healthy.
11
+ */ const DEV_SERVICE_TOKEN = 'dev-config-service-token-change-me';
12
+ /** Header config-service reads the shared fleet token from. */ export const SERVICE_TOKEN_HEADER = 'x-service-token';
13
+ /** Read on each call so a test (or a rotated deployment) can change it. */ export const serviceToken = ()=>{
14
+ var _process_env_CONFIG_SERVICE_TOKEN;
15
+ return (_process_env_CONFIG_SERVICE_TOKEN = process.env.CONFIG_SERVICE_TOKEN) != null ? _process_env_CONFIG_SERVICE_TOKEN : DEV_SERVICE_TOKEN;
16
+ };
17
+
18
+ //# sourceMappingURL=service-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/config/service-token.ts"],"sourcesContent":["/**\n * config-service's fleet lookup is gated on a shared token, since it serves real\n * connection strings and the JWT signing key. The literal default keeps local dev\n * zero-config; it is replaced per environment through `CONFIG_SERVICE_TOKEN`.\n *\n * This module is server-only, so the token never reaches the browser.\n *\n * It lives on its own because *two* server routes call the gated endpoint: the\n * `/api/config` fetch and the readiness probe. Readiness reaching it without the\n * header is how an app answers `503` forever while being perfectly healthy.\n */\nconst DEV_SERVICE_TOKEN = 'dev-config-service-token-change-me';\n\n/** Header config-service reads the shared fleet token from. */\nexport const SERVICE_TOKEN_HEADER = 'x-service-token';\n\n/** Read on each call so a test (or a rotated deployment) can change it. */\nexport const serviceToken = (): string =>\n process.env.CONFIG_SERVICE_TOKEN ?? DEV_SERVICE_TOKEN;\n"],"names":["DEV_SERVICE_TOKEN","SERVICE_TOKEN_HEADER","serviceToken","process","env","CONFIG_SERVICE_TOKEN"],"mappings":"AAAA;;;;;;;;;;CAUC,GACD,MAAMA,oBAAoB;AAE1B,6DAA6D,GAC7D,OAAO,MAAMC,uBAAuB,kBAAkB;AAEtD,yEAAyE,GACzE,OAAO,MAAMC,eAAe;QAC1BC;YAAAA,oCAAAA,QAAQC,GAAG,CAACC,oBAAoB,YAAhCF,oCAAoCH;EAAkB"}
@@ -0,0 +1,49 @@
1
+ import { type EntifixError, type Entity, type EntityConstructor, type EntityMetadataSource } from '@entifix/core';
2
+ import { type EntityDraftStore } from '@entifix/react-integration';
3
+ import type { EntityCatalogKey, EntityCrudLinkSource } from './make-entity-crud.types.js';
4
+ export interface EntityCrudFormProps<TEntity extends Entity> {
5
+ readonly entityConstructor: EntityConstructor<TEntity>;
6
+ readonly catalogKey: EntityCatalogKey;
7
+ readonly hiddenFields: readonly string[];
8
+ readonly links: readonly EntityCrudLinkSource[];
9
+ /** The record being edited; `undefined` means this is a create. */
10
+ readonly entity?: TEntity;
11
+ readonly isLoading?: boolean;
12
+ readonly isSaving?: boolean;
13
+ readonly isDeleting?: boolean;
14
+ readonly error?: EntifixError;
15
+ readonly onSave: (entity: TEntity) => void;
16
+ /** Omitted for a create — there is nothing to delete yet. */
17
+ readonly onDelete?: () => void;
18
+ readonly backHref: string;
19
+ /**
20
+ * Where to autosave this form's draft. Omitted by a route host, which is what
21
+ * keeps a plain route ephemeral; the workspace tab host supplies one.
22
+ */
23
+ readonly draft?: EntityDraftStore;
24
+ /**
25
+ * Where this caller's affordances come from. Absent keeps the pre-ADR-0026
26
+ * behaviour: Save and Delete render unconditionally and no declared verb
27
+ * appears — which is what every generated catalog did before it existed.
28
+ */
29
+ readonly metadataSource?: EntityMetadataSource;
30
+ /** Runs a declared `entity`-bound verb on this record, by its key. */
31
+ readonly onUseCase?: (key: string) => void;
32
+ }
33
+ /**
34
+ * The generated create/update form. A thin wrapper over the agnostic
35
+ * {@link EntityForm}: every field, label and validation rule comes from the
36
+ * entity's own accessor metadata, and `reconstructEntity` derives the submit
37
+ * rebuild from that same metadata — so nothing here is per-entity but the
38
+ * constructor, the catalog key and the hidden-field list.
39
+ *
40
+ * A hidden member still round-trips. `code` is assigned by the create
41
+ * transaction and never shown, but it is a plain writable member, so the rebuild
42
+ * picks it out of the seeded draft; dropping it from the rendered fields is not
43
+ * dropping it from the draft.
44
+ *
45
+ * `useEntityForm` owns the draft and seeds it once, so the page keys this
46
+ * component by the record id to reseed when the record arrives.
47
+ */
48
+ export declare function EntityCrudForm<TEntity extends Entity>({ entityConstructor, catalogKey, hiddenFields, links, entity, isLoading, isSaving, isDeleting, error, onSave, onDelete, backHref, draft, metadataSource, onUseCase, }: EntityCrudFormProps<TEntity>): import("react").JSX.Element;
49
+ //# sourceMappingURL=entity-crud-form.d.ts.map