@shipfox/client-shell 10.0.1 → 12.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 (70) hide show
  1. package/dist/components/user-menu.d.ts.map +1 -1
  2. package/dist/components/user-menu.js +26 -0
  3. package/dist/components/user-menu.js.map +1 -1
  4. package/dist/compose/compose-client-features.d.ts +2 -1
  5. package/dist/compose/compose-client-features.d.ts.map +1 -1
  6. package/dist/compose/compose-client-features.js +5 -3
  7. package/dist/compose/compose-client-features.js.map +1 -1
  8. package/dist/compose/compose-routes.d.ts +6 -2
  9. package/dist/compose/compose-routes.d.ts.map +1 -1
  10. package/dist/compose/compose-routes.js +137 -2
  11. package/dist/compose/compose-routes.js.map +1 -1
  12. package/dist/compose/errors.d.ts +4 -0
  13. package/dist/compose/errors.d.ts.map +1 -1
  14. package/dist/compose/errors.js +6 -0
  15. package/dist/compose/errors.js.map +1 -1
  16. package/dist/compose/validate-registries.d.ts +9 -2
  17. package/dist/compose/validate-registries.d.ts.map +1 -1
  18. package/dist/compose/validate-registries.js +83 -2
  19. package/dist/compose/validate-registries.js.map +1 -1
  20. package/dist/contract.d.ts +23 -3
  21. package/dist/contract.d.ts.map +1 -1
  22. package/dist/contract.js.map +1 -1
  23. package/dist/core/session.d.ts +1 -0
  24. package/dist/core/session.d.ts.map +1 -1
  25. package/dist/core/session.js.map +1 -1
  26. package/dist/hooks/api/session-auth.d.ts.map +1 -1
  27. package/dist/hooks/api/session-auth.js +2 -1
  28. package/dist/hooks/api/session-auth.js.map +1 -1
  29. package/dist/runtime/anchor-paths.d.ts +2 -0
  30. package/dist/runtime/anchor-paths.d.ts.map +1 -1
  31. package/dist/runtime/anchor-paths.js +15 -0
  32. package/dist/runtime/anchor-paths.js.map +1 -1
  33. package/dist/runtime/anchors.d.ts.map +1 -1
  34. package/dist/runtime/anchors.js +6 -3
  35. package/dist/runtime/anchors.js.map +1 -1
  36. package/dist/runtime/assemble-route-tree.d.ts +3 -2
  37. package/dist/runtime/assemble-route-tree.d.ts.map +1 -1
  38. package/dist/runtime/assemble-route-tree.js +76 -5
  39. package/dist/runtime/assemble-route-tree.js.map +1 -1
  40. package/dist/runtime/chrome-context.d.ts +6 -0
  41. package/dist/runtime/chrome-context.d.ts.map +1 -1
  42. package/dist/runtime/chrome-context.js.map +1 -1
  43. package/dist/runtime/index.d.ts +1 -0
  44. package/dist/runtime/index.d.ts.map +1 -1
  45. package/dist/runtime/index.js +1 -0
  46. package/dist/runtime/index.js.map +1 -1
  47. package/dist/runtime/layout-navigation.d.ts +7 -0
  48. package/dist/runtime/layout-navigation.d.ts.map +1 -0
  49. package/dist/runtime/layout-navigation.js +18 -0
  50. package/dist/runtime/layout-navigation.js.map +1 -0
  51. package/dist/runtime/provider-stack.d.ts.map +1 -1
  52. package/dist/runtime/provider-stack.js +7 -3
  53. package/dist/runtime/provider-stack.js.map +1 -1
  54. package/dist/runtime/registries.d.ts +2 -1
  55. package/dist/runtime/registries.d.ts.map +1 -1
  56. package/dist/runtime/registries.js +10 -0
  57. package/dist/runtime/registries.js.map +1 -1
  58. package/dist/runtime/workspace-setup.d.ts +4 -0
  59. package/dist/runtime/workspace-setup.d.ts.map +1 -1
  60. package/dist/runtime/workspace-setup.js +23 -0
  61. package/dist/runtime/workspace-setup.js.map +1 -1
  62. package/dist/tsconfig.test.tsbuildinfo +1 -1
  63. package/dist/vite/generate.d.ts +3 -2
  64. package/dist/vite/generate.d.ts.map +1 -1
  65. package/dist/vite/generate.js +108 -20
  66. package/dist/vite/generate.js.map +1 -1
  67. package/dist/vite/plugin.d.ts.map +1 -1
  68. package/dist/vite/plugin.js +7 -3
  69. package/dist/vite/plugin.js.map +1 -1
  70. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"user-menu.d.ts","sourceRoot":"","sources":["../../src/components/user-menu.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,QAAQ,gCAqCvB"}
1
+ {"version":3,"file":"user-menu.d.ts","sourceRoot":"","sources":["../../src/components/user-menu.tsx"],"names":[],"mappings":"AA0CA,wBAAgB,QAAQ,gCA2CvB"}
@@ -3,7 +3,9 @@ import { Avatar } from '@shipfox/react-ui/avatar';
3
3
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@shipfox/react-ui/dropdown-menu';
4
4
  import { useTheme } from '@shipfox/react-ui/hooks';
5
5
  import { Link } from '@tanstack/react-router';
6
+ import { Component } from 'react';
6
7
  import { useAuthState } from '#runtime/auth.js';
8
+ import { useChrome } from '#runtime/chrome-context.js';
7
9
  const themeOptions = [
8
10
  {
9
11
  value: 'light',
@@ -18,8 +20,29 @@ const themeOptions = [
18
20
  label: 'System'
19
21
  }
20
22
  ];
23
+ let AccountMenuEntryBoundary = class AccountMenuEntryBoundary extends Component {
24
+ static getDerivedStateFromError() {
25
+ return {
26
+ hasError: true
27
+ };
28
+ }
29
+ componentDidCatch(error) {
30
+ globalThis.reportError?.(new Error('Failed to render account menu entry.', {
31
+ cause: error
32
+ }));
33
+ }
34
+ render() {
35
+ return this.state.hasError ? null : this.props.children;
36
+ }
37
+ constructor(...args){
38
+ super(...args), this.state = {
39
+ hasError: false
40
+ };
41
+ }
42
+ };
21
43
  export function UserMenu() {
22
44
  const { user } = useAuthState();
45
+ const { AccountMenuEntry } = useChrome();
23
46
  const { theme, setTheme } = useTheme();
24
47
  const email = user?.email ?? '';
25
48
  return /*#__PURE__*/ _jsxs(DropdownMenu, {
@@ -59,6 +82,9 @@ export function UserMenu() {
59
82
  children: option.label
60
83
  }, option.value))
61
84
  }),
85
+ AccountMenuEntry ? /*#__PURE__*/ _jsx(AccountMenuEntryBoundary, {
86
+ children: /*#__PURE__*/ _jsx(AccountMenuEntry, {})
87
+ }) : undefined,
62
88
  /*#__PURE__*/ _jsx(DropdownMenuSeparator, {}),
63
89
  /*#__PURE__*/ _jsx(DropdownMenuItem, {
64
90
  asChild: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/user-menu.tsx"],"sourcesContent":["import {Avatar} from '@shipfox/react-ui/avatar';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {useTheme} from '@shipfox/react-ui/hooks';\nimport type {Theme} from '@shipfox/react-ui/theme';\nimport {Link} from '@tanstack/react-router';\nimport {useAuthState} from '#runtime/auth.js';\n\nconst themeOptions: Array<{value: Theme; label: string}> = [\n {value: 'light', label: 'Light'},\n {value: 'dark', label: 'Dark'},\n {value: 'system', label: 'System'},\n];\nexport function UserMenu() {\n const {user} = useAuthState();\n const {theme, setTheme} = useTheme();\n const email = user?.email ?? '';\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"User menu\"\n className=\"rounded-full focus-visible:outline-none focus-visible:shadow-button-neutral-focus\"\n >\n <Avatar size=\"sm\" content=\"letters\" fallback={email} />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" sideOffset={8} className=\"min-w-[220px]\">\n <DropdownMenuLabel className=\"text-xs text-foreground-neutral-muted truncate\">\n {email}\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuLabel className=\"text-xs text-foreground-neutral-muted\">\n Theme\n </DropdownMenuLabel>\n <DropdownMenuRadioGroup value={theme} onValueChange={(value) => setTheme(value as Theme)}>\n {themeOptions.map((option) => (\n <DropdownMenuRadioItem key={option.value} value={option.value}>\n {option.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild>\n <Link to={'/auth/logout' as never}>Logout</Link>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"names":["Avatar","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuLabel","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuSeparator","DropdownMenuTrigger","useTheme","Link","useAuthState","themeOptions","value","label","UserMenu","user","theme","setTheme","email","asChild","button","type","aria-label","className","size","content","fallback","align","sideOffset","onValueChange","map","option","to"],"mappings":";AAAA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SACEC,YAAY,EACZC,mBAAmB,EACnBC,gBAAgB,EAChBC,iBAAiB,EACjBC,sBAAsB,EACtBC,qBAAqB,EACrBC,qBAAqB,EACrBC,mBAAmB,QACd,kCAAkC;AACzC,SAAQC,QAAQ,QAAO,0BAA0B;AAEjD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,YAAY,QAAO,mBAAmB;AAE9C,MAAMC,eAAqD;IACzD;QAACC,OAAO;QAASC,OAAO;IAAO;IAC/B;QAACD,OAAO;QAAQC,OAAO;IAAM;IAC7B;QAACD,OAAO;QAAUC,OAAO;IAAQ;CAClC;AACD,OAAO,SAASC;IACd,MAAM,EAACC,IAAI,EAAC,GAAGL;IACf,MAAM,EAACM,KAAK,EAAEC,QAAQ,EAAC,GAAGT;IAC1B,MAAMU,QAAQH,MAAMG,SAAS;IAC7B,qBACE,MAAClB;;0BACC,KAACO;gBAAoBY,OAAO;0BAC1B,cAAA,KAACC;oBACCC,MAAK;oBACLC,cAAW;oBACXC,WAAU;8BAEV,cAAA,KAACxB;wBAAOyB,MAAK;wBAAKC,SAAQ;wBAAUC,UAAUR;;;;0BAGlD,MAACjB;gBAAoB0B,OAAM;gBAAMC,YAAY;gBAAGL,WAAU;;kCACxD,KAACpB;wBAAkBoB,WAAU;kCAC1BL;;kCAEH,KAACZ;kCACD,KAACH;wBAAkBoB,WAAU;kCAAwC;;kCAGrE,KAACnB;wBAAuBQ,OAAOI;wBAAOa,eAAe,CAACjB,QAAUK,SAASL;kCACtED,aAAamB,GAAG,CAAC,CAACC,uBACjB,KAAC1B;gCAAyCO,OAAOmB,OAAOnB,KAAK;0CAC1DmB,OAAOlB,KAAK;+BADakB,OAAOnB,KAAK;;kCAK5C,KAACN;kCACD,KAACJ;wBAAiBiB,OAAO;kCACvB,cAAA,KAACV;4BAAKuB,IAAI;sCAAyB;;;;;;;AAK7C"}
1
+ {"version":3,"sources":["../../src/components/user-menu.tsx"],"sourcesContent":["import {Avatar} from '@shipfox/react-ui/avatar';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {useTheme} from '@shipfox/react-ui/hooks';\nimport type {Theme} from '@shipfox/react-ui/theme';\nimport {Link} from '@tanstack/react-router';\nimport {Component, type PropsWithChildren} from 'react';\nimport {useAuthState} from '#runtime/auth.js';\nimport {useChrome} from '#runtime/chrome-context.js';\n\nconst themeOptions: Array<{value: Theme; label: string}> = [\n {value: 'light', label: 'Light'},\n {value: 'dark', label: 'Dark'},\n {value: 'system', label: 'System'},\n];\n\ntype AccountMenuEntryBoundaryState = {hasError: boolean};\n\nclass AccountMenuEntryBoundary extends Component<PropsWithChildren, AccountMenuEntryBoundaryState> {\n override state: AccountMenuEntryBoundaryState = {hasError: false};\n\n static getDerivedStateFromError(): AccountMenuEntryBoundaryState {\n return {hasError: true};\n }\n\n override componentDidCatch(error: unknown): void {\n globalThis.reportError?.(new Error('Failed to render account menu entry.', {cause: error}));\n }\n\n override render() {\n return this.state.hasError ? null : this.props.children;\n }\n}\n\nexport function UserMenu() {\n const {user} = useAuthState();\n const {AccountMenuEntry} = useChrome();\n const {theme, setTheme} = useTheme();\n const email = user?.email ?? '';\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"User menu\"\n className=\"rounded-full focus-visible:outline-none focus-visible:shadow-button-neutral-focus\"\n >\n <Avatar size=\"sm\" content=\"letters\" fallback={email} />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" sideOffset={8} className=\"min-w-[220px]\">\n <DropdownMenuLabel className=\"text-xs text-foreground-neutral-muted truncate\">\n {email}\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuLabel className=\"text-xs text-foreground-neutral-muted\">\n Theme\n </DropdownMenuLabel>\n <DropdownMenuRadioGroup value={theme} onValueChange={(value) => setTheme(value as Theme)}>\n {themeOptions.map((option) => (\n <DropdownMenuRadioItem key={option.value} value={option.value}>\n {option.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n {AccountMenuEntry ? (\n <AccountMenuEntryBoundary>\n <AccountMenuEntry />\n </AccountMenuEntryBoundary>\n ) : undefined}\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild>\n <Link to={'/auth/logout' as never}>Logout</Link>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"names":["Avatar","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuLabel","DropdownMenuRadioGroup","DropdownMenuRadioItem","DropdownMenuSeparator","DropdownMenuTrigger","useTheme","Link","Component","useAuthState","useChrome","themeOptions","value","label","AccountMenuEntryBoundary","getDerivedStateFromError","hasError","componentDidCatch","error","globalThis","reportError","Error","cause","render","state","props","children","UserMenu","user","AccountMenuEntry","theme","setTheme","email","asChild","button","type","aria-label","className","size","content","fallback","align","sideOffset","onValueChange","map","option","undefined","to"],"mappings":";AAAA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SACEC,YAAY,EACZC,mBAAmB,EACnBC,gBAAgB,EAChBC,iBAAiB,EACjBC,sBAAsB,EACtBC,qBAAqB,EACrBC,qBAAqB,EACrBC,mBAAmB,QACd,kCAAkC;AACzC,SAAQC,QAAQ,QAAO,0BAA0B;AAEjD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,SAAS,QAA+B,QAAQ;AACxD,SAAQC,YAAY,QAAO,mBAAmB;AAC9C,SAAQC,SAAS,QAAO,6BAA6B;AAErD,MAAMC,eAAqD;IACzD;QAACC,OAAO;QAASC,OAAO;IAAO;IAC/B;QAACD,OAAO;QAAQC,OAAO;IAAM;IAC7B;QAACD,OAAO;QAAUC,OAAO;IAAQ;CAClC;AAID,IAAA,AAAMC,2BAAN,MAAMA,iCAAiCN;IAGrC,OAAOO,2BAA0D;QAC/D,OAAO;YAACC,UAAU;QAAI;IACxB;IAESC,kBAAkBC,KAAc,EAAQ;QAC/CC,WAAWC,WAAW,GAAG,IAAIC,MAAM,wCAAwC;YAACC,OAAOJ;QAAK;IAC1F;IAESK,SAAS;QAChB,OAAO,IAAI,CAACC,KAAK,CAACR,QAAQ,GAAG,OAAO,IAAI,CAACS,KAAK,CAACC,QAAQ;IACzD;;QAbF,qBACWF,QAAuC;YAACR,UAAU;QAAK;;AAalE;AAEA,OAAO,SAASW;IACd,MAAM,EAACC,IAAI,EAAC,GAAGnB;IACf,MAAM,EAACoB,gBAAgB,EAAC,GAAGnB;IAC3B,MAAM,EAACoB,KAAK,EAAEC,QAAQ,EAAC,GAAGzB;IAC1B,MAAM0B,QAAQJ,MAAMI,SAAS;IAC7B,qBACE,MAAClC;;0BACC,KAACO;gBAAoB4B,OAAO;0BAC1B,cAAA,KAACC;oBACCC,MAAK;oBACLC,cAAW;oBACXC,WAAU;8BAEV,cAAA,KAACxC;wBAAOyC,MAAK;wBAAKC,SAAQ;wBAAUC,UAAUR;;;;0BAGlD,MAACjC;gBAAoB0C,OAAM;gBAAMC,YAAY;gBAAGL,WAAU;;kCACxD,KAACpC;wBAAkBoC,WAAU;kCAC1BL;;kCAEH,KAAC5B;kCACD,KAACH;wBAAkBoC,WAAU;kCAAwC;;kCAGrE,KAACnC;wBAAuBU,OAAOkB;wBAAOa,eAAe,CAAC/B,QAAUmB,SAASnB;kCACtED,aAAaiC,GAAG,CAAC,CAACC,uBACjB,KAAC1C;gCAAyCS,OAAOiC,OAAOjC,KAAK;0CAC1DiC,OAAOhC,KAAK;+BADagC,OAAOjC,KAAK;;oBAK3CiB,iCACC,KAACf;kCACC,cAAA,KAACe;yBAEDiB;kCACJ,KAAC1C;kCACD,KAACJ;wBAAiBiC,OAAO;kCACvB,cAAA,KAAC1B;4BAAKwC,IAAI;sCAAyB;;;;;;;AAK7C"}
@@ -1,8 +1,9 @@
1
1
  import type { ClientFeature, NavTabEntry, SettingsSectionEntry } from '#contract.js';
2
- import { type ComposedRoute } from './compose-routes.js';
2
+ import { type ComposedLayout, type ComposedRoute } from './compose-routes.js';
3
3
  import { mergeConfigShapes } from './merge-config.js';
4
4
  export interface ComposedClientFeatures {
5
5
  configShape: ReturnType<typeof mergeConfigShapes>;
6
+ layouts: ComposedLayout[];
6
7
  navigation: NavTabEntry[];
7
8
  routes: ComposedRoute[];
8
9
  settingsSections: SettingsSectionEntry[];
@@ -1 +1 @@
1
- {"version":3,"file":"compose-client-features.d.ts","sourceRoot":"","sources":["../../src/compose/compose-client-features.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAE,WAAW,EAAE,oBAAoB,EAAC,MAAM,cAAc,CAAC;AAEnF,OAAO,EAAC,KAAK,aAAa,EAAgB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAC,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AAIpD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IAClD,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;CAC1C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,sBAAsB,CAYhG"}
1
+ {"version":3,"file":"compose-client-features.d.ts","sourceRoot":"","sources":["../../src/compose/compose-client-features.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAE,WAAW,EAAE,oBAAoB,EAAC,MAAM,cAAc,CAAC;AAEnF,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EAGnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAC,iBAAiB,EAAC,MAAM,mBAAmB,CAAC;AAIpD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IAClD,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;CAC1C;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,sBAAsB,CAchG"}
@@ -1,15 +1,17 @@
1
1
  import { navigationEntries, settingsEntries } from '#runtime/registries.js';
2
- import { composeRoutes } from './compose-routes.js';
2
+ import { composeLayouts, composeRoutes } from './compose-routes.js';
3
3
  import { mergeConfigShapes } from './merge-config.js';
4
4
  import { validateProviderIds } from './validate-providers.js';
5
5
  import { validateNavigation, validateSettingsSections } from './validate-registries.js';
6
6
  export function composeClientFeatures(features) {
7
- const routes = composeRoutes(features);
7
+ const layouts = composeLayouts(features);
8
+ const routes = composeRoutes(features, layouts);
8
9
  validateProviderIds(features);
9
- validateNavigation(features, routes);
10
+ validateNavigation(features, routes, layouts);
10
11
  validateSettingsSections(features, routes);
11
12
  return {
12
13
  configShape: mergeConfigShapes(features),
14
+ layouts,
13
15
  navigation: navigationEntries(features),
14
16
  routes,
15
17
  settingsSections: settingsEntries(features)
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/compose/compose-client-features.ts"],"sourcesContent":["import type {ClientFeature, NavTabEntry, SettingsSectionEntry} from '#contract.js';\nimport {navigationEntries, settingsEntries} from '#runtime/registries.js';\nimport {type ComposedRoute, composeRoutes} from './compose-routes.js';\nimport {mergeConfigShapes} from './merge-config.js';\nimport {validateProviderIds} from './validate-providers.js';\nimport {validateNavigation, validateSettingsSections} from './validate-registries.js';\n\nexport interface ComposedClientFeatures {\n configShape: ReturnType<typeof mergeConfigShapes>;\n navigation: NavTabEntry[];\n routes: ComposedRoute[];\n settingsSections: SettingsSectionEntry[];\n}\n\nexport function composeClientFeatures(features: readonly ClientFeature[]): ComposedClientFeatures {\n const routes = composeRoutes(features);\n validateProviderIds(features);\n validateNavigation(features, routes);\n validateSettingsSections(features, routes);\n\n return {\n configShape: mergeConfigShapes(features),\n navigation: navigationEntries(features),\n routes,\n settingsSections: settingsEntries(features),\n };\n}\n"],"names":["navigationEntries","settingsEntries","composeRoutes","mergeConfigShapes","validateProviderIds","validateNavigation","validateSettingsSections","composeClientFeatures","features","routes","configShape","navigation","settingsSections"],"mappings":"AACA,SAAQA,iBAAiB,EAAEC,eAAe,QAAO,yBAAyB;AAC1E,SAA4BC,aAAa,QAAO,sBAAsB;AACtE,SAAQC,iBAAiB,QAAO,oBAAoB;AACpD,SAAQC,mBAAmB,QAAO,0BAA0B;AAC5D,SAAQC,kBAAkB,EAAEC,wBAAwB,QAAO,2BAA2B;AAStF,OAAO,SAASC,sBAAsBC,QAAkC;IACtE,MAAMC,SAASP,cAAcM;IAC7BJ,oBAAoBI;IACpBH,mBAAmBG,UAAUC;IAC7BH,yBAAyBE,UAAUC;IAEnC,OAAO;QACLC,aAAaP,kBAAkBK;QAC/BG,YAAYX,kBAAkBQ;QAC9BC;QACAG,kBAAkBX,gBAAgBO;IACpC;AACF"}
1
+ {"version":3,"sources":["../../src/compose/compose-client-features.ts"],"sourcesContent":["import type {ClientFeature, NavTabEntry, SettingsSectionEntry} from '#contract.js';\nimport {navigationEntries, settingsEntries} from '#runtime/registries.js';\nimport {\n type ComposedLayout,\n type ComposedRoute,\n composeLayouts,\n composeRoutes,\n} from './compose-routes.js';\nimport {mergeConfigShapes} from './merge-config.js';\nimport {validateProviderIds} from './validate-providers.js';\nimport {validateNavigation, validateSettingsSections} from './validate-registries.js';\n\nexport interface ComposedClientFeatures {\n configShape: ReturnType<typeof mergeConfigShapes>;\n layouts: ComposedLayout[];\n navigation: NavTabEntry[];\n routes: ComposedRoute[];\n settingsSections: SettingsSectionEntry[];\n}\n\nexport function composeClientFeatures(features: readonly ClientFeature[]): ComposedClientFeatures {\n const layouts = composeLayouts(features);\n const routes = composeRoutes(features, layouts);\n validateProviderIds(features);\n validateNavigation(features, routes, layouts);\n validateSettingsSections(features, routes);\n\n return {\n configShape: mergeConfigShapes(features),\n layouts,\n navigation: navigationEntries(features),\n routes,\n settingsSections: settingsEntries(features),\n };\n}\n"],"names":["navigationEntries","settingsEntries","composeLayouts","composeRoutes","mergeConfigShapes","validateProviderIds","validateNavigation","validateSettingsSections","composeClientFeatures","features","layouts","routes","configShape","navigation","settingsSections"],"mappings":"AACA,SAAQA,iBAAiB,EAAEC,eAAe,QAAO,yBAAyB;AAC1E,SAGEC,cAAc,EACdC,aAAa,QACR,sBAAsB;AAC7B,SAAQC,iBAAiB,QAAO,oBAAoB;AACpD,SAAQC,mBAAmB,QAAO,0BAA0B;AAC5D,SAAQC,kBAAkB,EAAEC,wBAAwB,QAAO,2BAA2B;AAUtF,OAAO,SAASC,sBAAsBC,QAAkC;IACtE,MAAMC,UAAUR,eAAeO;IAC/B,MAAME,SAASR,cAAcM,UAAUC;IACvCL,oBAAoBI;IACpBH,mBAAmBG,UAAUE,QAAQD;IACrCH,yBAAyBE,UAAUE;IAEnC,OAAO;QACLC,aAAaR,kBAAkBK;QAC/BC;QACAG,YAAYb,kBAAkBS;QAC9BE;QACAG,kBAAkBb,gBAAgBQ;IACpC;AACF"}
@@ -1,7 +1,11 @@
1
- import type { ClientFeature, RouteContribution } from '#contract.js';
1
+ import type { ClientFeature, LayoutContribution, RouteContribution } from '#contract.js';
2
+ export interface ComposedLayout extends LayoutContribution {
3
+ featureId: string;
4
+ }
2
5
  export interface ComposedRoute extends RouteContribution {
3
6
  featureId: string;
4
7
  ownerFeatureId: string;
5
8
  }
6
- export declare function composeRoutes(features: readonly ClientFeature[]): ComposedRoute[];
9
+ export declare function composeLayouts(features: readonly ClientFeature[]): ComposedLayout[];
10
+ export declare function composeRoutes(features: readonly ClientFeature[], providedLayouts?: readonly ComposedLayout[]): ComposedRoute[];
7
11
  //# sourceMappingURL=compose-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compose-routes.d.ts","sourceRoot":"","sources":["../../src/compose/compose-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAE,iBAAiB,EAAC,MAAM,cAAc,CAAC;AAInE,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,EAAE,CAqDjF"}
1
+ {"version":3,"file":"compose-routes.d.ts","sourceRoot":"","sources":["../../src/compose/compose-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EAElB,MAAM,cAAc,CAAC;AAKtB,MAAM,WAAW,cAAe,SAAQ,kBAAkB;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AA0ID,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,cAAc,EAAE,CAanF;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,CAAC,EAAE,SAAS,cAAc,EAAE,GAC1C,aAAa,EAAE,CAqFjB"}
@@ -1,6 +1,127 @@
1
- import { RouteCompositionError } from './errors.js';
1
+ import { anchorPaths } from '#runtime/anchor-paths.js';
2
+ import { LayoutCompositionError, RouteCompositionError } from './errors.js';
2
3
  import { normalizeRoutePath } from './normalize-route-path.js';
3
- export function composeRoutes(features) {
4
+ function isShellAnchor(parent) {
5
+ return Object.hasOwn(anchorPaths, parent);
6
+ }
7
+ function layoutParentPath(parent, layouts) {
8
+ if (isShellAnchor(parent)) return anchorPaths[parent];
9
+ return layouts.get(parent)?.path;
10
+ }
11
+ function resolvedShellAnchor(parent, layouts) {
12
+ let current = parent;
13
+ const visited = new Set();
14
+ while(!isShellAnchor(current)){
15
+ if (visited.has(current)) return undefined;
16
+ visited.add(current);
17
+ const next = layouts.get(current)?.parent;
18
+ if (next === undefined) return undefined;
19
+ current = next;
20
+ }
21
+ return current;
22
+ }
23
+ function validateNestedRoute(path, parent, layouts, featureId) {
24
+ if (resolvedShellAnchor(parent, layouts) === 'root') {
25
+ const protectedAnchor = protectedAnchorForRootPath(path);
26
+ if (protectedAnchor) {
27
+ const [anchor, anchorPath] = protectedAnchor;
28
+ throw new RouteCompositionError(path, `Route "${path}" in feature "${featureId}" cannot use root parent inside reserved anchor "${anchor}" (${anchorPath}). Use parent "${anchor}".`, [
29
+ featureId
30
+ ]);
31
+ }
32
+ }
33
+ const parentPath = layoutParentPath(parent, layouts);
34
+ if (!parentPath) {
35
+ if (isShellAnchor(parent)) return;
36
+ throw new RouteCompositionError(path, `Route "${path}" in feature "${featureId}" targets missing layout parent "${parent}".`, [
37
+ featureId
38
+ ]);
39
+ }
40
+ if (parentPath !== '/' && path !== parentPath && !path.startsWith(`${parentPath}/`)) {
41
+ const parentKind = isShellAnchor(parent) ? 'anchor' : 'layout';
42
+ throw new RouteCompositionError(path, `Route "${path}" must be nested under ${parentKind} "${parent}" (${parentPath}).`, [
43
+ featureId
44
+ ]);
45
+ }
46
+ }
47
+ const anchorPathValues = new Set(Object.values(anchorPaths));
48
+ const protectedAnchorPaths = Object.entries(anchorPaths).filter(([anchor])=>anchor !== 'root').sort(([, leftPath], [, rightPath])=>rightPath.length - leftPath.length);
49
+ function protectedAnchorForRootPath(path) {
50
+ return protectedAnchorPaths.find(([, anchorPath])=>path === anchorPath || path.startsWith(`${anchorPath}/`));
51
+ }
52
+ function validateLayoutParents(layouts) {
53
+ const byId = new Map();
54
+ for (const layout of layouts){
55
+ if (isShellAnchor(layout.id)) {
56
+ throw new LayoutCompositionError(layout.id, `Layout id "${layout.id}" in feature "${layout.featureId}" is reserved by the shell.`, [
57
+ layout.featureId
58
+ ]);
59
+ }
60
+ if (anchorPathValues.has(layout.path)) {
61
+ throw new LayoutCompositionError(layout.id, `Layout "${layout.id}" in feature "${layout.featureId}" targets path "${layout.path}" which is reserved by a shell anchor.`, [
62
+ layout.featureId
63
+ ]);
64
+ }
65
+ const existing = byId.get(layout.id);
66
+ if (existing) {
67
+ throw new LayoutCompositionError(layout.id, `Layout id "${layout.id}" is contributed by both features "${existing.featureId}" and "${layout.featureId}".`, [
68
+ existing.featureId,
69
+ layout.featureId
70
+ ]);
71
+ }
72
+ byId.set(layout.id, layout);
73
+ }
74
+ const visiting = new Set();
75
+ const visited = new Set();
76
+ function visit(layout) {
77
+ if (visited.has(layout.id) || isShellAnchor(layout.parent)) return;
78
+ if (visiting.has(layout.id)) {
79
+ throw new LayoutCompositionError(layout.id, `Layout "${layout.id}" in feature "${layout.featureId}" has a cyclic parent reference.`, [
80
+ layout.featureId
81
+ ]);
82
+ }
83
+ const parent = byId.get(layout.parent);
84
+ if (!parent) {
85
+ throw new LayoutCompositionError(layout.id, `Layout "${layout.id}" in feature "${layout.featureId}" targets missing layout parent "${layout.parent}".`, [
86
+ layout.featureId
87
+ ]);
88
+ }
89
+ visiting.add(layout.id);
90
+ visit(parent);
91
+ visiting.delete(layout.id);
92
+ visited.add(layout.id);
93
+ }
94
+ for (const layout of layouts)visit(layout);
95
+ return byId;
96
+ }
97
+ export function composeLayouts(features) {
98
+ const layouts = features.flatMap((feature)=>(feature.layouts ?? []).map((layout)=>({
99
+ ...layout,
100
+ path: normalizeRoutePath(layout.path),
101
+ featureId: feature.id
102
+ })));
103
+ const layoutById = validateLayoutParents(layouts);
104
+ for (const layout of layouts){
105
+ validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);
106
+ }
107
+ return layouts;
108
+ }
109
+ export function composeRoutes(features, providedLayouts) {
110
+ const layouts = providedLayouts ? [
111
+ ...providedLayouts
112
+ ] : composeLayouts(features);
113
+ const layoutById = validateLayoutParents(layouts);
114
+ const layoutPaths = new Map();
115
+ for (const layout of layouts){
116
+ const existing = layoutPaths.get(layout.path);
117
+ if (existing) {
118
+ throw new RouteCompositionError(layout.path, `Route "${layout.path}" is contributed by both features "${existing.featureId}" and "${layout.featureId}".`, [
119
+ existing.featureId,
120
+ layout.featureId
121
+ ]);
122
+ }
123
+ layoutPaths.set(layout.path, layout);
124
+ }
4
125
  const routes = new Map();
5
126
  for (const feature of features){
6
127
  for (const contribution of feature.routes ?? []){
@@ -8,6 +129,14 @@ export function composeRoutes(features) {
8
129
  ...contribution,
9
130
  path: normalizeRoutePath(contribution.path)
10
131
  };
132
+ const layoutAtPath = layoutPaths.get(normalizedContribution.path);
133
+ if (layoutAtPath) {
134
+ const message = normalizedContribution.override ? `Route override for "${normalizedContribution.path}" from feature "${feature.id}" cannot replace layout "${layoutAtPath.id}" contributed by feature "${layoutAtPath.featureId}".` : `Route "${normalizedContribution.path}" from feature "${feature.id}" conflicts with layout "${layoutAtPath.id}" contributed by feature "${layoutAtPath.featureId}". Routes cannot replace layouts.`;
135
+ throw new RouteCompositionError(normalizedContribution.path, message, [
136
+ layoutAtPath.featureId,
137
+ feature.id
138
+ ]);
139
+ }
11
140
  const existing = routes.get(normalizedContribution.path);
12
141
  if (!existing) {
13
142
  if (normalizedContribution.override) {
@@ -47,6 +176,12 @@ export function composeRoutes(features) {
47
176
  });
48
177
  }
49
178
  }
179
+ for (const layout of layouts){
180
+ validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);
181
+ }
182
+ for (const route of routes.values()){
183
+ validateNestedRoute(route.path, route.parent, layoutById, route.featureId);
184
+ }
50
185
  return [
51
186
  ...routes.values()
52
187
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/compose/compose-routes.ts"],"sourcesContent":["import type {ClientFeature, RouteContribution} from '#contract.js';\nimport {RouteCompositionError} from './errors.js';\nimport {normalizeRoutePath} from './normalize-route-path.js';\n\nexport interface ComposedRoute extends RouteContribution {\n featureId: string;\n ownerFeatureId: string;\n}\n\nexport function composeRoutes(features: readonly ClientFeature[]): ComposedRoute[] {\n const routes = new Map<string, ComposedRoute>();\n for (const feature of features) {\n for (const contribution of feature.routes ?? []) {\n const normalizedContribution = {\n ...contribution,\n path: normalizeRoutePath(contribution.path),\n };\n const existing = routes.get(normalizedContribution.path);\n if (!existing) {\n if (normalizedContribution.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" has no route to replace.`,\n [feature.id],\n );\n }\n routes.set(normalizedContribution.path, {\n ...normalizedContribution,\n featureId: feature.id,\n ownerFeatureId: feature.id,\n });\n continue;\n }\n if (!normalizedContribution.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route \"${normalizedContribution.path}\" is contributed by both features \"${existing.featureId}\" and \"${feature.id}\". Set override: true to replace it explicitly.`,\n [existing.featureId, feature.id],\n );\n }\n if (existing.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route \"${normalizedContribution.path}\" has competing overrides from features \"${existing.featureId}\" and \"${feature.id}\".`,\n [existing.featureId, feature.id],\n );\n }\n if (existing.parent !== normalizedContribution.parent) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" cannot change anchor from \"${existing.parent}\" in feature \"${existing.featureId}\" to \"${normalizedContribution.parent}\".`,\n [existing.featureId, feature.id],\n );\n }\n routes.set(normalizedContribution.path, {\n ...normalizedContribution,\n featureId: feature.id,\n ownerFeatureId: existing.ownerFeatureId,\n });\n }\n }\n return [...routes.values()];\n}\n"],"names":["RouteCompositionError","normalizeRoutePath","composeRoutes","features","routes","Map","feature","contribution","normalizedContribution","path","existing","get","override","id","set","featureId","ownerFeatureId","parent","values"],"mappings":"AACA,SAAQA,qBAAqB,QAAO,cAAc;AAClD,SAAQC,kBAAkB,QAAO,4BAA4B;AAO7D,OAAO,SAASC,cAAcC,QAAkC;IAC9D,MAAMC,SAAS,IAAIC;IACnB,KAAK,MAAMC,WAAWH,SAAU;QAC9B,KAAK,MAAMI,gBAAgBD,QAAQF,MAAM,IAAI,EAAE,CAAE;YAC/C,MAAMI,yBAAyB;gBAC7B,GAAGD,YAAY;gBACfE,MAAMR,mBAAmBM,aAAaE,IAAI;YAC5C;YACA,MAAMC,WAAWN,OAAOO,GAAG,CAACH,uBAAuBC,IAAI;YACvD,IAAI,CAACC,UAAU;gBACb,IAAIF,uBAAuBI,QAAQ,EAAE;oBACnC,MAAM,IAAIZ,sBACRQ,uBAAuBC,IAAI,EAC3B,CAAC,oBAAoB,EAAED,uBAAuBC,IAAI,CAAC,gBAAgB,EAAEH,QAAQO,EAAE,CAAC,0BAA0B,CAAC,EAC3G;wBAACP,QAAQO,EAAE;qBAAC;gBAEhB;gBACAT,OAAOU,GAAG,CAACN,uBAAuBC,IAAI,EAAE;oBACtC,GAAGD,sBAAsB;oBACzBO,WAAWT,QAAQO,EAAE;oBACrBG,gBAAgBV,QAAQO,EAAE;gBAC5B;gBACA;YACF;YACA,IAAI,CAACL,uBAAuBI,QAAQ,EAAE;gBACpC,MAAM,IAAIZ,sBACRQ,uBAAuBC,IAAI,EAC3B,CAAC,OAAO,EAAED,uBAAuBC,IAAI,CAAC,mCAAmC,EAAEC,SAASK,SAAS,CAAC,OAAO,EAAET,QAAQO,EAAE,CAAC,+CAA+C,CAAC,EAClK;oBAACH,SAASK,SAAS;oBAAET,QAAQO,EAAE;iBAAC;YAEpC;YACA,IAAIH,SAASE,QAAQ,EAAE;gBACrB,MAAM,IAAIZ,sBACRQ,uBAAuBC,IAAI,EAC3B,CAAC,OAAO,EAAED,uBAAuBC,IAAI,CAAC,yCAAyC,EAAEC,SAASK,SAAS,CAAC,OAAO,EAAET,QAAQO,EAAE,CAAC,EAAE,CAAC,EAC3H;oBAACH,SAASK,SAAS;oBAAET,QAAQO,EAAE;iBAAC;YAEpC;YACA,IAAIH,SAASO,MAAM,KAAKT,uBAAuBS,MAAM,EAAE;gBACrD,MAAM,IAAIjB,sBACRQ,uBAAuBC,IAAI,EAC3B,CAAC,oBAAoB,EAAED,uBAAuBC,IAAI,CAAC,gBAAgB,EAAEH,QAAQO,EAAE,CAAC,6BAA6B,EAAEH,SAASO,MAAM,CAAC,cAAc,EAAEP,SAASK,SAAS,CAAC,MAAM,EAAEP,uBAAuBS,MAAM,CAAC,EAAE,CAAC,EAC3M;oBAACP,SAASK,SAAS;oBAAET,QAAQO,EAAE;iBAAC;YAEpC;YACAT,OAAOU,GAAG,CAACN,uBAAuBC,IAAI,EAAE;gBACtC,GAAGD,sBAAsB;gBACzBO,WAAWT,QAAQO,EAAE;gBACrBG,gBAAgBN,SAASM,cAAc;YACzC;QACF;IACF;IACA,OAAO;WAAIZ,OAAOc,MAAM;KAAG;AAC7B"}
1
+ {"version":3,"sources":["../../src/compose/compose-routes.ts"],"sourcesContent":["import type {\n AnchorId,\n ClientFeature,\n LayoutContribution,\n RouteContribution,\n RouteParentId,\n} from '#contract.js';\nimport {anchorPaths} from '#runtime/anchor-paths.js';\nimport {LayoutCompositionError, RouteCompositionError} from './errors.js';\nimport {normalizeRoutePath} from './normalize-route-path.js';\n\nexport interface ComposedLayout extends LayoutContribution {\n featureId: string;\n}\n\nexport interface ComposedRoute extends RouteContribution {\n featureId: string;\n ownerFeatureId: string;\n}\n\nfunction isShellAnchor(parent: RouteParentId): parent is AnchorId {\n return Object.hasOwn(anchorPaths, parent);\n}\n\nfunction layoutParentPath(\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n): string | undefined {\n if (isShellAnchor(parent)) return anchorPaths[parent];\n return layouts.get(parent)?.path;\n}\n\nfunction resolvedShellAnchor(\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n): AnchorId | undefined {\n let current = parent;\n const visited = new Set<string>();\n while (!isShellAnchor(current)) {\n if (visited.has(current)) return undefined;\n visited.add(current);\n const next = layouts.get(current)?.parent;\n if (next === undefined) return undefined;\n current = next;\n }\n return current;\n}\n\nfunction validateNestedRoute(\n path: string,\n parent: RouteParentId,\n layouts: ReadonlyMap<string, ComposedLayout>,\n featureId: string,\n): void {\n if (resolvedShellAnchor(parent, layouts) === 'root') {\n const protectedAnchor = protectedAnchorForRootPath(path);\n if (protectedAnchor) {\n const [anchor, anchorPath] = protectedAnchor;\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" in feature \"${featureId}\" cannot use root parent inside reserved anchor \"${anchor}\" (${anchorPath}). Use parent \"${anchor}\".`,\n [featureId],\n );\n }\n }\n const parentPath = layoutParentPath(parent, layouts);\n if (!parentPath) {\n if (isShellAnchor(parent)) return;\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" in feature \"${featureId}\" targets missing layout parent \"${parent}\".`,\n [featureId],\n );\n }\n if (parentPath !== '/' && path !== parentPath && !path.startsWith(`${parentPath}/`)) {\n const parentKind = isShellAnchor(parent) ? 'anchor' : 'layout';\n throw new RouteCompositionError(\n path,\n `Route \"${path}\" must be nested under ${parentKind} \"${parent}\" (${parentPath}).`,\n [featureId],\n );\n }\n}\n\nconst anchorPathValues: ReadonlySet<string> = new Set(Object.values(anchorPaths));\nconst protectedAnchorPaths = Object.entries(anchorPaths)\n .filter(([anchor]) => anchor !== 'root')\n .sort(([, leftPath], [, rightPath]) => rightPath.length - leftPath.length) as Array<\n [Exclude<AnchorId, 'root'>, string]\n>;\n\nfunction protectedAnchorForRootPath(\n path: string,\n): readonly [Exclude<AnchorId, 'root'>, string] | undefined {\n return protectedAnchorPaths.find(\n ([, anchorPath]) => path === anchorPath || path.startsWith(`${anchorPath}/`),\n );\n}\n\nfunction validateLayoutParents(layouts: readonly ComposedLayout[]): Map<string, ComposedLayout> {\n const byId = new Map<string, ComposedLayout>();\n for (const layout of layouts) {\n if (isShellAnchor(layout.id)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout id \"${layout.id}\" in feature \"${layout.featureId}\" is reserved by the shell.`,\n [layout.featureId],\n );\n }\n if (anchorPathValues.has(layout.path)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" targets path \"${layout.path}\" which is reserved by a shell anchor.`,\n [layout.featureId],\n );\n }\n const existing = byId.get(layout.id);\n if (existing) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout id \"${layout.id}\" is contributed by both features \"${existing.featureId}\" and \"${layout.featureId}\".`,\n [existing.featureId, layout.featureId],\n );\n }\n byId.set(layout.id, layout);\n }\n\n const visiting = new Set<string>();\n const visited = new Set<string>();\n function visit(layout: ComposedLayout): void {\n if (visited.has(layout.id) || isShellAnchor(layout.parent)) return;\n if (visiting.has(layout.id)) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" has a cyclic parent reference.`,\n [layout.featureId],\n );\n }\n const parent = byId.get(layout.parent);\n if (!parent) {\n throw new LayoutCompositionError(\n layout.id,\n `Layout \"${layout.id}\" in feature \"${layout.featureId}\" targets missing layout parent \"${layout.parent}\".`,\n [layout.featureId],\n );\n }\n visiting.add(layout.id);\n visit(parent);\n visiting.delete(layout.id);\n visited.add(layout.id);\n }\n\n for (const layout of layouts) visit(layout);\n return byId;\n}\n\nexport function composeLayouts(features: readonly ClientFeature[]): ComposedLayout[] {\n const layouts = features.flatMap((feature) =>\n (feature.layouts ?? []).map((layout) => ({\n ...layout,\n path: normalizeRoutePath(layout.path),\n featureId: feature.id,\n })),\n );\n const layoutById = validateLayoutParents(layouts);\n for (const layout of layouts) {\n validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);\n }\n return layouts;\n}\n\nexport function composeRoutes(\n features: readonly ClientFeature[],\n providedLayouts?: readonly ComposedLayout[],\n): ComposedRoute[] {\n const layouts = providedLayouts ? [...providedLayouts] : composeLayouts(features);\n const layoutById = validateLayoutParents(layouts);\n const layoutPaths = new Map<string, ComposedLayout>();\n for (const layout of layouts) {\n const existing = layoutPaths.get(layout.path);\n if (existing) {\n throw new RouteCompositionError(\n layout.path,\n `Route \"${layout.path}\" is contributed by both features \"${existing.featureId}\" and \"${layout.featureId}\".`,\n [existing.featureId, layout.featureId],\n );\n }\n layoutPaths.set(layout.path, layout);\n }\n\n const routes = new Map<string, ComposedRoute>();\n for (const feature of features) {\n for (const contribution of feature.routes ?? []) {\n const normalizedContribution = {\n ...contribution,\n path: normalizeRoutePath(contribution.path),\n };\n const layoutAtPath = layoutPaths.get(normalizedContribution.path);\n if (layoutAtPath) {\n const message = normalizedContribution.override\n ? `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" cannot replace layout \"${layoutAtPath.id}\" contributed by feature \"${layoutAtPath.featureId}\".`\n : `Route \"${normalizedContribution.path}\" from feature \"${feature.id}\" conflicts with layout \"${layoutAtPath.id}\" contributed by feature \"${layoutAtPath.featureId}\". Routes cannot replace layouts.`;\n throw new RouteCompositionError(normalizedContribution.path, message, [\n layoutAtPath.featureId,\n feature.id,\n ]);\n }\n const existing = routes.get(normalizedContribution.path);\n if (!existing) {\n if (normalizedContribution.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" has no route to replace.`,\n [feature.id],\n );\n }\n routes.set(normalizedContribution.path, {\n ...normalizedContribution,\n featureId: feature.id,\n ownerFeatureId: feature.id,\n });\n continue;\n }\n if (!normalizedContribution.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route \"${normalizedContribution.path}\" is contributed by both features \"${existing.featureId}\" and \"${feature.id}\". Set override: true to replace it explicitly.`,\n [existing.featureId, feature.id],\n );\n }\n if (existing.override) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route \"${normalizedContribution.path}\" has competing overrides from features \"${existing.featureId}\" and \"${feature.id}\".`,\n [existing.featureId, feature.id],\n );\n }\n if (existing.parent !== normalizedContribution.parent) {\n throw new RouteCompositionError(\n normalizedContribution.path,\n `Route override for \"${normalizedContribution.path}\" from feature \"${feature.id}\" cannot change anchor from \"${existing.parent}\" in feature \"${existing.featureId}\" to \"${normalizedContribution.parent}\".`,\n [existing.featureId, feature.id],\n );\n }\n routes.set(normalizedContribution.path, {\n ...normalizedContribution,\n featureId: feature.id,\n ownerFeatureId: existing.ownerFeatureId,\n });\n }\n }\n\n for (const layout of layouts) {\n validateNestedRoute(layout.path, layout.parent, layoutById, layout.featureId);\n }\n for (const route of routes.values()) {\n validateNestedRoute(route.path, route.parent, layoutById, route.featureId);\n }\n return [...routes.values()];\n}\n"],"names":["anchorPaths","LayoutCompositionError","RouteCompositionError","normalizeRoutePath","isShellAnchor","parent","Object","hasOwn","layoutParentPath","layouts","get","path","resolvedShellAnchor","current","visited","Set","has","undefined","add","next","validateNestedRoute","featureId","protectedAnchor","protectedAnchorForRootPath","anchor","anchorPath","parentPath","startsWith","parentKind","anchorPathValues","values","protectedAnchorPaths","entries","filter","sort","leftPath","rightPath","length","find","validateLayoutParents","byId","Map","layout","id","existing","set","visiting","visit","delete","composeLayouts","features","flatMap","feature","map","layoutById","composeRoutes","providedLayouts","layoutPaths","routes","contribution","normalizedContribution","layoutAtPath","message","override","ownerFeatureId","route"],"mappings":"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"}
@@ -6,6 +6,10 @@ export declare class RouteCompositionError extends CompositionError {
6
6
  readonly path: string;
7
7
  constructor(path: string, message: string, featureIds: readonly string[]);
8
8
  }
9
+ export declare class LayoutCompositionError extends CompositionError {
10
+ readonly id: string;
11
+ constructor(id: string, message: string, featureIds: readonly string[]);
12
+ }
9
13
  export declare class ProviderCompositionError extends CompositionError {
10
14
  readonly id: string;
11
15
  constructor(id: string, message: string, featureIds: readonly string[]);
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/compose/errors.ts"],"names":[],"mappings":"AAAA,uBAAe,gBAAiB,SAAQ,KAAK;IAC3C,SAAgB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAE9C,SAAS,aAAa,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAKnF;AAED,qBAAa,qBAAsB,SAAQ,gBAAgB;IACzD,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIzE;AAED,qBAAa,wBAAyB,SAAQ,gBAAgB;IAC5D,SAAgB,EAAE,EAAE,MAAM,CAAC;gBAEf,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIvE;AAED,qBAAa,mBAAoB,SAAQ,gBAAgB;IACvD,SAAgB,EAAE,EAAE,MAAM,CAAC;gBAEf,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIvE;AAED,qBAAa,wBAAyB,SAAQ,gBAAgB;IAC5D,SAAgB,EAAE,EAAE,MAAM,CAAC;gBAEf,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIvE;AAED,qBAAa,sBAAuB,SAAQ,gBAAgB;IAC1D,SAAgB,GAAG,EAAE,MAAM,CAAC;gBAEhB,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIxE"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/compose/errors.ts"],"names":[],"mappings":"AAAA,uBAAe,gBAAiB,SAAQ,KAAK;IAC3C,SAAgB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAE9C,SAAS,aAAa,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAKnF;AAED,qBAAa,qBAAsB,SAAQ,gBAAgB;IACzD,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIzE;AAED,qBAAa,sBAAuB,SAAQ,gBAAgB;IAC1D,SAAgB,EAAE,EAAE,MAAM,CAAC;gBAEf,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIvE;AAED,qBAAa,wBAAyB,SAAQ,gBAAgB;IAC5D,SAAgB,EAAE,EAAE,MAAM,CAAC;gBAEf,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIvE;AAED,qBAAa,mBAAoB,SAAQ,gBAAgB;IACvD,SAAgB,EAAE,EAAE,MAAM,CAAC;gBAEf,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIvE;AAED,qBAAa,wBAAyB,SAAQ,gBAAgB;IAC5D,SAAgB,EAAE,EAAE,MAAM,CAAC;gBAEf,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIvE;AAED,qBAAa,sBAAuB,SAAQ,gBAAgB;IAC1D,SAAgB,GAAG,EAAE,MAAM,CAAC;gBAEhB,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE;CAIxE"}
@@ -11,6 +11,12 @@ export class RouteCompositionError extends CompositionError {
11
11
  this.path = path;
12
12
  }
13
13
  }
14
+ export class LayoutCompositionError extends CompositionError {
15
+ constructor(id, message, featureIds){
16
+ super('LayoutCompositionError', message, featureIds);
17
+ this.id = id;
18
+ }
19
+ }
14
20
  export class ProviderCompositionError extends CompositionError {
15
21
  constructor(id, message, featureIds){
16
22
  super('ProviderCompositionError', message, featureIds);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/compose/errors.ts"],"sourcesContent":["abstract class CompositionError extends Error {\n public readonly featureIds: readonly string[];\n\n protected constructor(name: string, message: string, featureIds: readonly string[]) {\n super(message);\n this.name = name;\n this.featureIds = featureIds;\n }\n}\n\nexport class RouteCompositionError extends CompositionError {\n public readonly path: string;\n\n constructor(path: string, message: string, featureIds: readonly string[]) {\n super('RouteCompositionError', message, featureIds);\n this.path = path;\n }\n}\n\nexport class ProviderCompositionError extends CompositionError {\n public readonly id: string;\n\n constructor(id: string, message: string, featureIds: readonly string[]) {\n super('ProviderCompositionError', message, featureIds);\n this.id = id;\n }\n}\n\nexport class NavCompositionError extends CompositionError {\n public readonly id: string;\n\n constructor(id: string, message: string, featureIds: readonly string[]) {\n super('NavCompositionError', message, featureIds);\n this.id = id;\n }\n}\n\nexport class SettingsCompositionError extends CompositionError {\n public readonly id: string;\n\n constructor(id: string, message: string, featureIds: readonly string[]) {\n super('SettingsCompositionError', message, featureIds);\n this.id = id;\n }\n}\n\nexport class ConfigCompositionError extends CompositionError {\n public readonly key: string;\n\n constructor(key: string, message: string, featureIds: readonly string[]) {\n super('ConfigCompositionError', message, featureIds);\n this.key = key;\n }\n}\n"],"names":["CompositionError","Error","name","message","featureIds","RouteCompositionError","path","ProviderCompositionError","id","NavCompositionError","SettingsCompositionError","ConfigCompositionError","key"],"mappings":"AAAA,IAAA,AAAeA,mBAAf,MAAeA,yBAAyBC;IAGtC,YAAsBC,IAAY,EAAEC,OAAe,EAAEC,UAA6B,CAAE;QAClF,KAAK,CAACD;QACN,IAAI,CAACD,IAAI,GAAGA;QACZ,IAAI,CAACE,UAAU,GAAGA;IACpB;AACF;AAEA,OAAO,MAAMC,8BAA8BL;IAGzC,YAAYM,IAAY,EAAEH,OAAe,EAAEC,UAA6B,CAAE;QACxE,KAAK,CAAC,yBAAyBD,SAASC;QACxC,IAAI,CAACE,IAAI,GAAGA;IACd;AACF;AAEA,OAAO,MAAMC,iCAAiCP;IAG5C,YAAYQ,EAAU,EAAEL,OAAe,EAAEC,UAA6B,CAAE;QACtE,KAAK,CAAC,4BAA4BD,SAASC;QAC3C,IAAI,CAACI,EAAE,GAAGA;IACZ;AACF;AAEA,OAAO,MAAMC,4BAA4BT;IAGvC,YAAYQ,EAAU,EAAEL,OAAe,EAAEC,UAA6B,CAAE;QACtE,KAAK,CAAC,uBAAuBD,SAASC;QACtC,IAAI,CAACI,EAAE,GAAGA;IACZ;AACF;AAEA,OAAO,MAAME,iCAAiCV;IAG5C,YAAYQ,EAAU,EAAEL,OAAe,EAAEC,UAA6B,CAAE;QACtE,KAAK,CAAC,4BAA4BD,SAASC;QAC3C,IAAI,CAACI,EAAE,GAAGA;IACZ;AACF;AAEA,OAAO,MAAMG,+BAA+BX;IAG1C,YAAYY,GAAW,EAAET,OAAe,EAAEC,UAA6B,CAAE;QACvE,KAAK,CAAC,0BAA0BD,SAASC;QACzC,IAAI,CAACQ,GAAG,GAAGA;IACb;AACF"}
1
+ {"version":3,"sources":["../../src/compose/errors.ts"],"sourcesContent":["abstract class CompositionError extends Error {\n public readonly featureIds: readonly string[];\n\n protected constructor(name: string, message: string, featureIds: readonly string[]) {\n super(message);\n this.name = name;\n this.featureIds = featureIds;\n }\n}\n\nexport class RouteCompositionError extends CompositionError {\n public readonly path: string;\n\n constructor(path: string, message: string, featureIds: readonly string[]) {\n super('RouteCompositionError', message, featureIds);\n this.path = path;\n }\n}\n\nexport class LayoutCompositionError extends CompositionError {\n public readonly id: string;\n\n constructor(id: string, message: string, featureIds: readonly string[]) {\n super('LayoutCompositionError', message, featureIds);\n this.id = id;\n }\n}\n\nexport class ProviderCompositionError extends CompositionError {\n public readonly id: string;\n\n constructor(id: string, message: string, featureIds: readonly string[]) {\n super('ProviderCompositionError', message, featureIds);\n this.id = id;\n }\n}\n\nexport class NavCompositionError extends CompositionError {\n public readonly id: string;\n\n constructor(id: string, message: string, featureIds: readonly string[]) {\n super('NavCompositionError', message, featureIds);\n this.id = id;\n }\n}\n\nexport class SettingsCompositionError extends CompositionError {\n public readonly id: string;\n\n constructor(id: string, message: string, featureIds: readonly string[]) {\n super('SettingsCompositionError', message, featureIds);\n this.id = id;\n }\n}\n\nexport class ConfigCompositionError extends CompositionError {\n public readonly key: string;\n\n constructor(key: string, message: string, featureIds: readonly string[]) {\n super('ConfigCompositionError', message, featureIds);\n this.key = key;\n }\n}\n"],"names":["CompositionError","Error","name","message","featureIds","RouteCompositionError","path","LayoutCompositionError","id","ProviderCompositionError","NavCompositionError","SettingsCompositionError","ConfigCompositionError","key"],"mappings":"AAAA,IAAA,AAAeA,mBAAf,MAAeA,yBAAyBC;IAGtC,YAAsBC,IAAY,EAAEC,OAAe,EAAEC,UAA6B,CAAE;QAClF,KAAK,CAACD;QACN,IAAI,CAACD,IAAI,GAAGA;QACZ,IAAI,CAACE,UAAU,GAAGA;IACpB;AACF;AAEA,OAAO,MAAMC,8BAA8BL;IAGzC,YAAYM,IAAY,EAAEH,OAAe,EAAEC,UAA6B,CAAE;QACxE,KAAK,CAAC,yBAAyBD,SAASC;QACxC,IAAI,CAACE,IAAI,GAAGA;IACd;AACF;AAEA,OAAO,MAAMC,+BAA+BP;IAG1C,YAAYQ,EAAU,EAAEL,OAAe,EAAEC,UAA6B,CAAE;QACtE,KAAK,CAAC,0BAA0BD,SAASC;QACzC,IAAI,CAACI,EAAE,GAAGA;IACZ;AACF;AAEA,OAAO,MAAMC,iCAAiCT;IAG5C,YAAYQ,EAAU,EAAEL,OAAe,EAAEC,UAA6B,CAAE;QACtE,KAAK,CAAC,4BAA4BD,SAASC;QAC3C,IAAI,CAACI,EAAE,GAAGA;IACZ;AACF;AAEA,OAAO,MAAME,4BAA4BV;IAGvC,YAAYQ,EAAU,EAAEL,OAAe,EAAEC,UAA6B,CAAE;QACtE,KAAK,CAAC,uBAAuBD,SAASC;QACtC,IAAI,CAACI,EAAE,GAAGA;IACZ;AACF;AAEA,OAAO,MAAMG,iCAAiCX;IAG5C,YAAYQ,EAAU,EAAEL,OAAe,EAAEC,UAA6B,CAAE;QACtE,KAAK,CAAC,4BAA4BD,SAASC;QAC3C,IAAI,CAACI,EAAE,GAAGA;IACZ;AACF;AAEA,OAAO,MAAMI,+BAA+BZ;IAG1C,YAAYa,GAAW,EAAEV,OAAe,EAAEC,UAA6B,CAAE;QACvE,KAAK,CAAC,0BAA0BD,SAASC;QACzC,IAAI,CAACS,GAAG,GAAGA;IACb;AACF"}
@@ -1,11 +1,18 @@
1
- import type { ClientFeature } from '#contract.js';
1
+ import type { ClientFeature, RouteParentId } from '#contract.js';
2
2
  interface RouteReference {
3
3
  path: string;
4
4
  featureId?: string;
5
5
  ownerFeatureId?: string;
6
+ parent?: RouteParentId;
6
7
  }
7
8
  type RouteReferences = Iterable<string | RouteReference>;
8
- export declare function validateNavigation(features: readonly ClientFeature[], routeReferences: RouteReferences): void;
9
+ interface LayoutReference {
10
+ id: string;
11
+ path: string;
12
+ featureId?: string;
13
+ parent?: RouteParentId;
14
+ }
15
+ export declare function validateNavigation(features: readonly ClientFeature[], routeReferences: RouteReferences, layoutReferences?: readonly LayoutReference[]): void;
9
16
  export declare function validateSettingsSections(features: readonly ClientFeature[], routeReferences: RouteReferences): void;
10
17
  export {};
11
18
  //# sourceMappingURL=validate-registries.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-registries.d.ts","sourceRoot":"","sources":["../../src/compose/validate-registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAIhD,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,KAAK,eAAe,GAAG,QAAQ,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;AAkBzD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,EAAE,eAAe,GAC/B,IAAI,CAgCN;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,EAAE,eAAe,GAC/B,IAAI,CAiCN"}
1
+ {"version":3,"file":"validate-registries.d.ts","sourceRoot":"","sources":["../../src/compose/validate-registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAe,aAAa,EAAC,MAAM,cAAc,CAAC;AAI5E,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,KAAK,eAAe,GAAG,QAAQ,CAAC,MAAM,GAAG,cAAc,CAAC,CAAC;AAEzD,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AA0CD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,EAAE,eAAe,EAChC,gBAAgB,GAAE,SAAS,eAAe,EAAO,GAChD,IAAI,CAuEN;AA4BD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,aAAa,EAAE,EAClC,eAAe,EAAE,eAAe,GAC/B,IAAI,CAiCN"}
@@ -11,11 +11,50 @@ function routeOwners(routeReferences) {
11
11
  function hasExplicitCoordinator(feature) {
12
12
  return feature.coordinator === feature.id;
13
13
  }
14
- export function validateNavigation(features, routeReferences) {
15
- const routes = routeOwners(routeReferences);
14
+ function validateRoleMetadata(entry, feature) {
15
+ const rawEntry = entry;
16
+ const hasMinimumRole = Object.hasOwn(rawEntry, 'minimumRole');
17
+ if (entry.scope !== 'layout') {
18
+ if (hasMinimumRole) {
19
+ throw new NavCompositionError(entry.id, `Navigation entry "${entry.id}" in feature "${feature.id}" has minimum role metadata but is not layout-scoped.`, [
20
+ feature.id
21
+ ]);
22
+ }
23
+ return;
24
+ }
25
+ if (hasMinimumRole) {
26
+ if (typeof rawEntry.minimumRole !== 'string' || rawEntry.minimumRole.trim() === '') {
27
+ throw new NavCompositionError(entry.id, `Navigation entry "${entry.id}" in feature "${feature.id}" has invalid minimum role metadata. Expected a non-empty string.`, [
28
+ feature.id
29
+ ]);
30
+ }
31
+ }
32
+ }
33
+ export function validateNavigation(features, routeReferences, layoutReferences = []) {
34
+ const routeReferencesWithLayouts = [
35
+ ...routeReferences
36
+ ];
37
+ for (const layout of layoutReferences){
38
+ routeReferencesWithLayouts.push({
39
+ path: layout.path,
40
+ ...layout.featureId ? {
41
+ featureId: layout.featureId,
42
+ ownerFeatureId: layout.featureId
43
+ } : {},
44
+ ...layout.parent ? {
45
+ parent: layout.parent
46
+ } : {}
47
+ });
48
+ }
49
+ const routes = routeOwners(routeReferencesWithLayouts);
50
+ const layouts = new Map(layoutReferences.map((layout)=>[
51
+ layout.id,
52
+ layout
53
+ ]));
16
54
  const entries = new Map();
17
55
  for (const feature of features){
18
56
  for (const entry of feature.navigation ?? []){
57
+ validateRoleMetadata(entry, feature);
19
58
  const existingFeatureId = entries.get(entry.id);
20
59
  if (existingFeatureId) {
21
60
  throw new NavCompositionError(entry.id, `Navigation entry "${entry.id}" is contributed by both features "${existingFeatureId}" and "${feature.id}".`, [
@@ -30,6 +69,29 @@ export function validateNavigation(features, routeReferences) {
30
69
  feature.id
31
70
  ]);
32
71
  }
72
+ if (entry.scope === 'layout') {
73
+ if (typeof entry.layout !== 'string' || entry.layout.trim() === '') {
74
+ throw new NavCompositionError(entry.id, `Navigation entry "${entry.id}" in feature "${feature.id}" has invalid layout metadata. Expected a non-empty layout id.`, [
75
+ feature.id
76
+ ]);
77
+ }
78
+ const layout = layouts.get(entry.layout);
79
+ if (!layout) {
80
+ throw new NavCompositionError(entry.id, `Navigation entry "${entry.id}" in feature "${feature.id}" targets missing layout "${entry.layout}".`, [
81
+ feature.id
82
+ ]);
83
+ }
84
+ const route = routeReferencesForPath(routeReferencesWithLayouts, target);
85
+ const isLayoutRoot = route?.path === layout.path;
86
+ const isLayoutDescendant = routeDescendsFromLayout(route, entry.layout, layouts);
87
+ if (!isLayoutRoot && !isLayoutDescendant) {
88
+ throw new NavCompositionError(entry.id, `Navigation entry "${entry.id}" in feature "${feature.id}" targets route "${target}" outside layout "${entry.layout}".`, [
89
+ feature.id
90
+ ]);
91
+ }
92
+ entries.set(entry.id, feature.id);
93
+ continue;
94
+ }
33
95
  if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {
34
96
  throw new NavCompositionError(entry.id, `Navigation entry "${entry.id}" in feature "${feature.id}" targets route "${target}" owned by feature "${routeOwner}". Declare coordinator: "${feature.id}" to own this cross-feature contribution.`, [
35
97
  routeOwner,
@@ -40,6 +102,25 @@ export function validateNavigation(features, routeReferences) {
40
102
  }
41
103
  }
42
104
  }
105
+ function routeReferencesForPath(routeReferences, path) {
106
+ for (const route of routeReferences){
107
+ const reference = typeof route === 'string' ? {
108
+ path: route
109
+ } : route;
110
+ if (normalizeRoutePath(reference.path) === path) return reference;
111
+ }
112
+ return undefined;
113
+ }
114
+ function routeDescendsFromLayout(route, layoutId, layouts) {
115
+ const visited = new Set();
116
+ let parent = route?.parent;
117
+ while(parent && !visited.has(parent)){
118
+ if (parent === layoutId) return true;
119
+ visited.add(parent);
120
+ parent = layouts.get(parent)?.parent;
121
+ }
122
+ return false;
123
+ }
43
124
  export function validateSettingsSections(features, routeReferences) {
44
125
  const routes = routeOwners(routeReferences);
45
126
  const sections = new Map();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/compose/validate-registries.ts"],"sourcesContent":["import type {ClientFeature} from '#contract.js';\nimport {NavCompositionError, SettingsCompositionError} from './errors.js';\nimport {normalizeRoutePath} from './normalize-route-path.js';\n\ninterface RouteReference {\n path: string;\n featureId?: string;\n ownerFeatureId?: string;\n}\n\ntype RouteReferences = Iterable<string | RouteReference>;\n\nfunction routeOwners(routeReferences: RouteReferences): Map<string, string | undefined> {\n const owners = new Map<string, string | undefined>();\n for (const route of routeReferences) {\n const path = typeof route === 'string' ? route : route.path;\n owners.set(\n normalizeRoutePath(path),\n typeof route === 'string' ? undefined : (route.ownerFeatureId ?? route.featureId),\n );\n }\n return owners;\n}\n\nfunction hasExplicitCoordinator(feature: ClientFeature): boolean {\n return feature.coordinator === feature.id;\n}\n\nexport function validateNavigation(\n features: readonly ClientFeature[],\n routeReferences: RouteReferences,\n): void {\n const routes = routeOwners(routeReferences);\n const entries = new Map<string, string>();\n for (const feature of features) {\n for (const entry of feature.navigation ?? []) {\n const existingFeatureId = entries.get(entry.id);\n if (existingFeatureId) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" is contributed by both features \"${existingFeatureId}\" and \"${feature.id}\".`,\n [existingFeatureId, feature.id],\n );\n }\n const target = normalizeRoutePath(entry.to);\n const routeOwner = routes.get(target);\n if (routeOwner === undefined && !routes.has(target)) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets missing route \"${target}\".`,\n [feature.id],\n );\n }\n if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets route \"${target}\" owned by feature \"${routeOwner}\". Declare coordinator: \"${feature.id}\" to own this cross-feature contribution.`,\n [routeOwner, feature.id],\n );\n }\n entries.set(entry.id, feature.id);\n }\n }\n}\n\nexport function validateSettingsSections(\n features: readonly ClientFeature[],\n routeReferences: RouteReferences,\n): void {\n const routes = routeOwners(routeReferences);\n const sections = new Map<string, string>();\n for (const feature of features) {\n for (const section of feature.settingsSections ?? []) {\n const existingFeatureId = sections.get(section.id);\n if (existingFeatureId) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" is contributed by both features \"${existingFeatureId}\" and \"${feature.id}\".`,\n [existingFeatureId, feature.id],\n );\n }\n const path = `/workspaces/$wid/settings/${section.pathSegment}`;\n const normalizedPath = normalizeRoutePath(path);\n const routeOwner = routes.get(normalizedPath);\n if (routeOwner === undefined && !routes.has(normalizedPath)) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" in feature \"${feature.id}\" requires route \"${path}\".`,\n [feature.id],\n );\n }\n if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" in feature \"${feature.id}\" targets route \"${path}\" owned by feature \"${routeOwner}\". Declare coordinator: \"${feature.id}\" to own this cross-feature contribution.`,\n [routeOwner, feature.id],\n );\n }\n sections.set(section.id, feature.id);\n }\n }\n}\n"],"names":["NavCompositionError","SettingsCompositionError","normalizeRoutePath","routeOwners","routeReferences","owners","Map","route","path","set","undefined","ownerFeatureId","featureId","hasExplicitCoordinator","feature","coordinator","id","validateNavigation","features","routes","entries","entry","navigation","existingFeatureId","get","target","to","routeOwner","has","validateSettingsSections","sections","section","settingsSections","pathSegment","normalizedPath"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/compose/validate-registries.ts"],"sourcesContent":["import type {ClientFeature, NavTabEntry, RouteParentId} from '#contract.js';\nimport {NavCompositionError, SettingsCompositionError} from './errors.js';\nimport {normalizeRoutePath} from './normalize-route-path.js';\n\ninterface RouteReference {\n path: string;\n featureId?: string;\n ownerFeatureId?: string;\n parent?: RouteParentId;\n}\n\ntype RouteReferences = Iterable<string | RouteReference>;\n\ninterface LayoutReference {\n id: string;\n path: string;\n featureId?: string;\n parent?: RouteParentId;\n}\n\nfunction routeOwners(routeReferences: RouteReferences): Map<string, string | undefined> {\n const owners = new Map<string, string | undefined>();\n for (const route of routeReferences) {\n const path = typeof route === 'string' ? route : route.path;\n owners.set(\n normalizeRoutePath(path),\n typeof route === 'string' ? undefined : (route.ownerFeatureId ?? route.featureId),\n );\n }\n return owners;\n}\n\nfunction hasExplicitCoordinator(feature: ClientFeature): boolean {\n return feature.coordinator === feature.id;\n}\n\nfunction validateRoleMetadata(entry: NavTabEntry, feature: ClientFeature): void {\n const rawEntry = entry as NavTabEntry & {minimumRole?: unknown};\n const hasMinimumRole = Object.hasOwn(rawEntry, 'minimumRole');\n if (entry.scope !== 'layout') {\n if (hasMinimumRole) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has minimum role metadata but is not layout-scoped.`,\n [feature.id],\n );\n }\n return;\n }\n if (hasMinimumRole) {\n if (typeof rawEntry.minimumRole !== 'string' || rawEntry.minimumRole.trim() === '') {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has invalid minimum role metadata. Expected a non-empty string.`,\n [feature.id],\n );\n }\n }\n}\n\nexport function validateNavigation(\n features: readonly ClientFeature[],\n routeReferences: RouteReferences,\n layoutReferences: readonly LayoutReference[] = [],\n): void {\n const routeReferencesWithLayouts: Array<string | RouteReference> = [...routeReferences];\n for (const layout of layoutReferences) {\n routeReferencesWithLayouts.push({\n path: layout.path,\n ...(layout.featureId ? {featureId: layout.featureId, ownerFeatureId: layout.featureId} : {}),\n ...(layout.parent ? {parent: layout.parent} : {}),\n });\n }\n const routes = routeOwners(routeReferencesWithLayouts);\n const layouts = new Map(layoutReferences.map((layout) => [layout.id, layout]));\n const entries = new Map<string, string>();\n for (const feature of features) {\n for (const entry of feature.navigation ?? []) {\n validateRoleMetadata(entry, feature);\n const existingFeatureId = entries.get(entry.id);\n if (existingFeatureId) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" is contributed by both features \"${existingFeatureId}\" and \"${feature.id}\".`,\n [existingFeatureId, feature.id],\n );\n }\n const target = normalizeRoutePath(entry.to);\n const routeOwner = routes.get(target);\n if (routeOwner === undefined && !routes.has(target)) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets missing route \"${target}\".`,\n [feature.id],\n );\n }\n if (entry.scope === 'layout') {\n if (typeof entry.layout !== 'string' || entry.layout.trim() === '') {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" has invalid layout metadata. Expected a non-empty layout id.`,\n [feature.id],\n );\n }\n const layout = layouts.get(entry.layout);\n if (!layout) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets missing layout \"${entry.layout}\".`,\n [feature.id],\n );\n }\n const route = routeReferencesForPath(routeReferencesWithLayouts, target);\n const isLayoutRoot = route?.path === layout.path;\n const isLayoutDescendant = routeDescendsFromLayout(route, entry.layout, layouts);\n if (!isLayoutRoot && !isLayoutDescendant) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets route \"${target}\" outside layout \"${entry.layout}\".`,\n [feature.id],\n );\n }\n entries.set(entry.id, feature.id);\n continue;\n }\n if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {\n throw new NavCompositionError(\n entry.id,\n `Navigation entry \"${entry.id}\" in feature \"${feature.id}\" targets route \"${target}\" owned by feature \"${routeOwner}\". Declare coordinator: \"${feature.id}\" to own this cross-feature contribution.`,\n [routeOwner, feature.id],\n );\n }\n entries.set(entry.id, feature.id);\n }\n }\n}\n\nfunction routeReferencesForPath(\n routeReferences: RouteReferences,\n path: string,\n): RouteReference | undefined {\n for (const route of routeReferences) {\n const reference = typeof route === 'string' ? {path: route} : route;\n if (normalizeRoutePath(reference.path) === path) return reference;\n }\n return undefined;\n}\n\nfunction routeDescendsFromLayout(\n route: RouteReference | undefined,\n layoutId: string,\n layouts: ReadonlyMap<string, LayoutReference>,\n): boolean {\n const visited = new Set<string>();\n let parent = route?.parent;\n while (parent && !visited.has(parent)) {\n if (parent === layoutId) return true;\n visited.add(parent);\n parent = layouts.get(parent)?.parent;\n }\n return false;\n}\n\nexport function validateSettingsSections(\n features: readonly ClientFeature[],\n routeReferences: RouteReferences,\n): void {\n const routes = routeOwners(routeReferences);\n const sections = new Map<string, string>();\n for (const feature of features) {\n for (const section of feature.settingsSections ?? []) {\n const existingFeatureId = sections.get(section.id);\n if (existingFeatureId) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" is contributed by both features \"${existingFeatureId}\" and \"${feature.id}\".`,\n [existingFeatureId, feature.id],\n );\n }\n const path = `/workspaces/$wid/settings/${section.pathSegment}`;\n const normalizedPath = normalizeRoutePath(path);\n const routeOwner = routes.get(normalizedPath);\n if (routeOwner === undefined && !routes.has(normalizedPath)) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" in feature \"${feature.id}\" requires route \"${path}\".`,\n [feature.id],\n );\n }\n if (routeOwner && routeOwner !== feature.id && !hasExplicitCoordinator(feature)) {\n throw new SettingsCompositionError(\n section.id,\n `Settings section \"${section.id}\" in feature \"${feature.id}\" targets route \"${path}\" owned by feature \"${routeOwner}\". Declare coordinator: \"${feature.id}\" to own this cross-feature contribution.`,\n [routeOwner, feature.id],\n );\n }\n sections.set(section.id, feature.id);\n }\n }\n}\n"],"names":["NavCompositionError","SettingsCompositionError","normalizeRoutePath","routeOwners","routeReferences","owners","Map","route","path","set","undefined","ownerFeatureId","featureId","hasExplicitCoordinator","feature","coordinator","id","validateRoleMetadata","entry","rawEntry","hasMinimumRole","Object","hasOwn","scope","minimumRole","trim","validateNavigation","features","layoutReferences","routeReferencesWithLayouts","layout","push","parent","routes","layouts","map","entries","navigation","existingFeatureId","get","target","to","routeOwner","has","routeReferencesForPath","isLayoutRoot","isLayoutDescendant","routeDescendsFromLayout","reference","layoutId","visited","Set","add","validateSettingsSections","sections","section","settingsSections","pathSegment","normalizedPath"],"mappings":"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"}