@shipfox/client-shell 4.0.0 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/components/auth-shell.d.ts +13 -0
  2. package/dist/components/auth-shell.d.ts.map +1 -0
  3. package/dist/components/auth-shell.js +68 -0
  4. package/dist/components/auth-shell.js.map +1 -0
  5. package/dist/components/main-layout.d.ts.map +1 -1
  6. package/dist/components/main-layout.js +3 -4
  7. package/dist/components/main-layout.js.map +1 -1
  8. package/dist/components/nav-tabs.d.ts.map +1 -1
  9. package/dist/components/nav-tabs.js +3 -4
  10. package/dist/components/nav-tabs.js.map +1 -1
  11. package/dist/components/settings-nav.d.ts.map +1 -1
  12. package/dist/components/settings-nav.js +3 -4
  13. package/dist/components/settings-nav.js.map +1 -1
  14. package/dist/compose/compose-client-features.d.ts +11 -0
  15. package/dist/compose/compose-client-features.d.ts.map +1 -0
  16. package/dist/compose/compose-client-features.js +19 -0
  17. package/dist/compose/compose-client-features.js.map +1 -0
  18. package/dist/compose/compose-routes.d.ts +1 -0
  19. package/dist/compose/compose-routes.d.ts.map +1 -1
  20. package/dist/compose/compose-routes.js +4 -2
  21. package/dist/compose/compose-routes.js.map +1 -1
  22. package/dist/compose/validate-registries.d.ts +9 -2
  23. package/dist/compose/validate-registries.d.ts.map +1 -1
  24. package/dist/compose/validate-registries.js +32 -8
  25. package/dist/compose/validate-registries.js.map +1 -1
  26. package/dist/contract.d.ts +6 -0
  27. package/dist/contract.d.ts.map +1 -1
  28. package/dist/contract.js.map +1 -1
  29. package/dist/core/session.d.ts +20 -0
  30. package/dist/core/session.d.ts.map +1 -0
  31. package/dist/core/session.js +3 -0
  32. package/dist/core/session.js.map +1 -0
  33. package/dist/hooks/api/session-auth.d.ts +15 -0
  34. package/dist/hooks/api/session-auth.d.ts.map +1 -0
  35. package/dist/hooks/api/session-auth.js +55 -0
  36. package/dist/hooks/api/session-auth.js.map +1 -0
  37. package/dist/hooks/api/session-mapper.d.ts +5 -0
  38. package/dist/hooks/api/session-mapper.d.ts.map +1 -0
  39. package/dist/hooks/api/session-mapper.js +20 -0
  40. package/dist/hooks/api/session-mapper.js.map +1 -0
  41. package/dist/runtime/active-workspace.d.ts.map +1 -1
  42. package/dist/runtime/active-workspace.js +2 -4
  43. package/dist/runtime/active-workspace.js.map +1 -1
  44. package/dist/runtime/auth.d.ts +7 -27
  45. package/dist/runtime/auth.d.ts.map +1 -1
  46. package/dist/runtime/auth.js +16 -49
  47. package/dist/runtime/auth.js.map +1 -1
  48. package/dist/runtime/compose-client-app.d.ts.map +1 -1
  49. package/dist/runtime/compose-client-app.js +3 -9
  50. package/dist/runtime/compose-client-app.js.map +1 -1
  51. package/dist/runtime/index.d.ts +5 -0
  52. package/dist/runtime/index.d.ts.map +1 -1
  53. package/dist/runtime/index.js +4 -0
  54. package/dist/runtime/index.js.map +1 -1
  55. package/dist/runtime/route-inputs.d.ts +18 -0
  56. package/dist/runtime/route-inputs.d.ts.map +1 -0
  57. package/dist/runtime/route-inputs.js +49 -0
  58. package/dist/runtime/route-inputs.js.map +1 -0
  59. package/dist/tsconfig.test.tsbuildinfo +1 -1
  60. package/dist/vite/plugin.d.ts.map +1 -1
  61. package/dist/vite/plugin.js +6 -14
  62. package/dist/vite/plugin.js.map +1 -1
  63. package/package.json +10 -11
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/api/session-auth.ts"],"sourcesContent":["import {loginResponseSchema} from '@shipfox/api-auth-dto';\nimport {listUserWorkspacesResponseSchema} from '@shipfox/api-workspaces-dto';\nimport {checkedApiRequest} from '@shipfox/client-api';\nimport {type FetchQueryOptions, queryOptions} from '@tanstack/react-query';\nimport type {AuthenticatedSession, WorkspaceSummary} from '#core/session.js';\nimport {toAuthenticatedSession} from './session-mapper.js';\n\nexport interface UserWorkspaces {\n memberships: WorkspaceSummary[];\n}\n\nexport const authRefreshQueryKey = ['auth', 'refresh'] as const;\nexport const userWorkspacesQueryKey = ['workspaces', 'mine'] as const;\n\ntype AuthRefreshQueryOptions = FetchQueryOptions<\n AuthenticatedSession,\n Error,\n AuthenticatedSession,\n typeof authRefreshQueryKey\n>;\n\ntype UserWorkspacesQueryOptions = FetchQueryOptions<\n UserWorkspaces,\n Error,\n UserWorkspaces,\n typeof userWorkspacesQueryKey\n>;\n\nexport async function listUserWorkspaces(\n token?: string,\n signal?: AbortSignal,\n): Promise<UserWorkspaces> {\n const response = await checkedApiRequest(\n listUserWorkspacesResponseSchema,\n '/workspaces',\n token ? {headers: {authorization: `Bearer ${token}`}, signal} : {signal},\n );\n return {\n memberships: response.memberships.map((membership) => ({\n id: membership.workspace_id,\n name: membership.workspace_name,\n membershipId: membership.id,\n })),\n };\n}\n\nexport function authRefreshQueryOptions(): AuthRefreshQueryOptions {\n return queryOptions({\n queryKey: authRefreshQueryKey,\n queryFn: ({signal}) => refreshAuthenticatedSession(signal),\n retry: false,\n staleTime: 0,\n });\n}\n\nexport function userWorkspacesQueryOptions(token?: string): UserWorkspacesQueryOptions {\n return queryOptions({\n queryKey: userWorkspacesQueryKey,\n queryFn: ({signal}) => listUserWorkspaces(token, signal),\n retry: false,\n staleTime: 0,\n });\n}\n\nexport async function refreshAuthenticatedSession(\n signal?: AbortSignal,\n): Promise<AuthenticatedSession> {\n const response = await checkedApiRequest(loginResponseSchema, '/auth/refresh', {\n method: 'POST',\n signal,\n });\n return toAuthenticatedSession(response);\n}\n"],"names":["loginResponseSchema","listUserWorkspacesResponseSchema","checkedApiRequest","queryOptions","toAuthenticatedSession","authRefreshQueryKey","userWorkspacesQueryKey","listUserWorkspaces","token","signal","response","headers","authorization","memberships","map","membership","id","workspace_id","name","workspace_name","membershipId","authRefreshQueryOptions","queryKey","queryFn","refreshAuthenticatedSession","retry","staleTime","userWorkspacesQueryOptions","method"],"mappings":"AAAA,SAAQA,mBAAmB,QAAO,wBAAwB;AAC1D,SAAQC,gCAAgC,QAAO,8BAA8B;AAC7E,SAAQC,iBAAiB,QAAO,sBAAsB;AACtD,SAAgCC,YAAY,QAAO,wBAAwB;AAE3E,SAAQC,sBAAsB,QAAO,sBAAsB;AAM3D,OAAO,MAAMC,sBAAsB;IAAC;IAAQ;CAAU,CAAU;AAChE,OAAO,MAAMC,yBAAyB;IAAC;IAAc;CAAO,CAAU;AAgBtE,OAAO,eAAeC,mBACpBC,KAAc,EACdC,MAAoB;IAEpB,MAAMC,WAAW,MAAMR,kBACrBD,kCACA,eACAO,QAAQ;QAACG,SAAS;YAACC,eAAe,CAAC,OAAO,EAAEJ,OAAO;QAAA;QAAGC;IAAM,IAAI;QAACA;IAAM;IAEzE,OAAO;QACLI,aAAaH,SAASG,WAAW,CAACC,GAAG,CAAC,CAACC,aAAgB,CAAA;gBACrDC,IAAID,WAAWE,YAAY;gBAC3BC,MAAMH,WAAWI,cAAc;gBAC/BC,cAAcL,WAAWC,EAAE;YAC7B,CAAA;IACF;AACF;AAEA,OAAO,SAASK;IACd,OAAOlB,aAAa;QAClBmB,UAAUjB;QACVkB,SAAS,CAAC,EAACd,MAAM,EAAC,GAAKe,4BAA4Bf;QACnDgB,OAAO;QACPC,WAAW;IACb;AACF;AAEA,OAAO,SAASC,2BAA2BnB,KAAc;IACvD,OAAOL,aAAa;QAClBmB,UAAUhB;QACViB,SAAS,CAAC,EAACd,MAAM,EAAC,GAAKF,mBAAmBC,OAAOC;QACjDgB,OAAO;QACPC,WAAW;IACb;AACF;AAEA,OAAO,eAAeF,4BACpBf,MAAoB;IAEpB,MAAMC,WAAW,MAAMR,kBAAkBF,qBAAqB,iBAAiB;QAC7E4B,QAAQ;QACRnB;IACF;IACA,OAAOL,uBAAuBM;AAChC"}
@@ -0,0 +1,5 @@
1
+ import type { LoginResponseDto, UserDto } from '@shipfox/api-auth-dto';
2
+ import type { AuthenticatedSession, UserIdentity } from '#core/session.js';
3
+ export declare function toUserIdentity(dto: UserDto): UserIdentity;
4
+ export declare function toAuthenticatedSession(dto: LoginResponseDto): AuthenticatedSession;
5
+ //# sourceMappingURL=session-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-mapper.d.ts","sourceRoot":"","sources":["../../../src/hooks/api/session-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,gBAAgB,EAAE,OAAO,EAAC,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAC,oBAAoB,EAAE,YAAY,EAAC,MAAM,kBAAkB,CAAC;AAEzE,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAOzD;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,gBAAgB,GAAG,oBAAoB,CAElF"}
@@ -0,0 +1,20 @@
1
+ export function toUserIdentity(dto) {
2
+ return {
3
+ id: dto.id,
4
+ email: dto.email,
5
+ ...dto.name ? {
6
+ name: dto.name
7
+ } : {},
8
+ ...dto.email_verified_at ? {
9
+ emailVerifiedAt: dto.email_verified_at
10
+ } : {}
11
+ };
12
+ }
13
+ export function toAuthenticatedSession(dto) {
14
+ return {
15
+ accessToken: dto.token,
16
+ user: toUserIdentity(dto.user)
17
+ };
18
+ }
19
+
20
+ //# sourceMappingURL=session-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/hooks/api/session-mapper.ts"],"sourcesContent":["import type {LoginResponseDto, UserDto} from '@shipfox/api-auth-dto';\nimport type {AuthenticatedSession, UserIdentity} from '#core/session.js';\n\nexport function toUserIdentity(dto: UserDto): UserIdentity {\n return {\n id: dto.id,\n email: dto.email,\n ...(dto.name ? {name: dto.name} : {}),\n ...(dto.email_verified_at ? {emailVerifiedAt: dto.email_verified_at} : {}),\n };\n}\n\nexport function toAuthenticatedSession(dto: LoginResponseDto): AuthenticatedSession {\n return {accessToken: dto.token, user: toUserIdentity(dto.user)};\n}\n"],"names":["toUserIdentity","dto","id","email","name","email_verified_at","emailVerifiedAt","toAuthenticatedSession","accessToken","token","user"],"mappings":"AAGA,OAAO,SAASA,eAAeC,GAAY;IACzC,OAAO;QACLC,IAAID,IAAIC,EAAE;QACVC,OAAOF,IAAIE,KAAK;QAChB,GAAIF,IAAIG,IAAI,GAAG;YAACA,MAAMH,IAAIG,IAAI;QAAA,IAAI,CAAC,CAAC;QACpC,GAAIH,IAAII,iBAAiB,GAAG;YAACC,iBAAiBL,IAAII,iBAAiB;QAAA,IAAI,CAAC,CAAC;IAC3E;AACF;AAEA,OAAO,SAASE,uBAAuBN,GAAqB;IAC1D,OAAO;QAACO,aAAaP,IAAIQ,KAAK;QAAEC,MAAMV,eAAeC,IAAIS,IAAI;IAAC;AAChE"}
@@ -1 +1 @@
1
- {"version":3,"file":"active-workspace.d.ts","sourceRoot":"","sources":["../../src/runtime/active-workspace.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,SAAS,EAAC,MAAM,WAAW,CAAC;AAEvD,wBAAgB,kBAAkB,IAAI,SAAS,CAI9C;AAED,wBAAgB,uBAAuB,IAAI,SAAS,GAAG,SAAS,CAG/D"}
1
+ {"version":3,"file":"active-workspace.d.ts","sourceRoot":"","sources":["../../src/runtime/active-workspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,SAAS,EAAC,MAAM,WAAW,CAAC;AAGvD,wBAAgB,kBAAkB,IAAI,SAAS,CAI9C;AAED,wBAAgB,uBAAuB,IAAI,SAAS,GAAG,SAAS,CAG/D"}
@@ -1,14 +1,12 @@
1
- import { useParams } from '@tanstack/react-router';
2
1
  import { useAuthState } from './auth.js';
2
+ import { parseWorkspaceParams, useRouteParams } from './route-inputs.js';
3
3
  export function useActiveWorkspace() {
4
4
  const workspace = useMaybeActiveWorkspace();
5
5
  if (!workspace) throw new Error('useActiveWorkspace called outside a /workspaces/$wid route');
6
6
  return workspace;
7
7
  }
8
8
  export function useMaybeActiveWorkspace() {
9
- const { wid } = useParams({
10
- strict: false
11
- });
9
+ const { wid } = useRouteParams(parseWorkspaceParams);
12
10
  return useAuthState().workspaces.find((workspace)=>workspace.id === wid);
13
11
  }
14
12
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runtime/active-workspace.ts"],"sourcesContent":["import {useParams} from '@tanstack/react-router';\nimport {useAuthState, type Workspace} from './auth.js';\n\nexport function useActiveWorkspace(): Workspace {\n const workspace = useMaybeActiveWorkspace();\n if (!workspace) throw new Error('useActiveWorkspace called outside a /workspaces/$wid route');\n return workspace;\n}\n\nexport function useMaybeActiveWorkspace(): Workspace | undefined {\n const {wid} = useParams({strict: false}) as {wid?: string};\n return useAuthState().workspaces.find((workspace) => workspace.id === wid);\n}\n"],"names":["useParams","useAuthState","useActiveWorkspace","workspace","useMaybeActiveWorkspace","Error","wid","strict","workspaces","find","id"],"mappings":"AAAA,SAAQA,SAAS,QAAO,yBAAyB;AACjD,SAAQC,YAAY,QAAuB,YAAY;AAEvD,OAAO,SAASC;IACd,MAAMC,YAAYC;IAClB,IAAI,CAACD,WAAW,MAAM,IAAIE,MAAM;IAChC,OAAOF;AACT;AAEA,OAAO,SAASC;IACd,MAAM,EAACE,GAAG,EAAC,GAAGN,UAAU;QAACO,QAAQ;IAAK;IACtC,OAAON,eAAeO,UAAU,CAACC,IAAI,CAAC,CAACN,YAAcA,UAAUO,EAAE,KAAKJ;AACxE"}
1
+ {"version":3,"sources":["../../src/runtime/active-workspace.ts"],"sourcesContent":["import {useAuthState, type Workspace} from './auth.js';\nimport {parseWorkspaceParams, useRouteParams} from './route-inputs.js';\n\nexport function useActiveWorkspace(): Workspace {\n const workspace = useMaybeActiveWorkspace();\n if (!workspace) throw new Error('useActiveWorkspace called outside a /workspaces/$wid route');\n return workspace;\n}\n\nexport function useMaybeActiveWorkspace(): Workspace | undefined {\n const {wid} = useRouteParams(parseWorkspaceParams);\n return useAuthState().workspaces.find((workspace) => workspace.id === wid);\n}\n"],"names":["useAuthState","parseWorkspaceParams","useRouteParams","useActiveWorkspace","workspace","useMaybeActiveWorkspace","Error","wid","workspaces","find","id"],"mappings":"AAAA,SAAQA,YAAY,QAAuB,YAAY;AACvD,SAAQC,oBAAoB,EAAEC,cAAc,QAAO,oBAAoB;AAEvE,OAAO,SAASC;IACd,MAAMC,YAAYC;IAClB,IAAI,CAACD,WAAW,MAAM,IAAIE,MAAM;IAChC,OAAOF;AACT;AAEA,OAAO,SAASC;IACd,MAAM,EAACE,GAAG,EAAC,GAAGL,eAAeD;IAC7B,OAAOD,eAAeQ,UAAU,CAACC,IAAI,CAAC,CAACL,YAAcA,UAAUM,EAAE,KAAKH;AACxE"}
@@ -1,16 +1,11 @@
1
- import type { LoginResponseDto, UserDto } from '@shipfox/api-auth-dto';
2
- import type { MembershipWithWorkspaceDto } from '@shipfox/api-workspaces-dto';
3
1
  import type { PropsWithChildren } from 'react';
2
+ import type { AuthenticatedSession, UserIdentity, WorkspaceSummary } from '#core/session.js';
4
3
  export type AuthStatus = 'loading' | 'authenticated' | 'guest';
5
- export interface Workspace {
6
- id: string;
7
- name: string;
8
- membershipId: string;
9
- }
4
+ export type Workspace = WorkspaceSummary;
10
5
  export interface AuthState {
11
6
  status: AuthStatus;
12
7
  token?: string;
13
- user?: UserDto;
8
+ user?: UserIdentity;
14
9
  workspaces?: Workspace[];
15
10
  }
16
11
  export interface AuthStateValue extends AuthState {
@@ -23,30 +18,15 @@ export declare const initialAuthState: AuthState;
23
18
  export declare const authStateAtom: import("jotai").PrimitiveAtom<AuthState> & {
24
19
  init: AuthState;
25
20
  };
26
- export declare const authRefreshQueryKey: readonly ["auth", "refresh"];
27
- export declare const userWorkspacesQueryKey: readonly ["workspaces", "mine"];
28
- export declare function toAuthenticatedState(result: LoginResponseDto, memberships?: MembershipWithWorkspaceDto[]): AuthState;
21
+ export declare function toAuthenticatedState(session: AuthenticatedSession, workspaces?: WorkspaceSummary[]): AuthState;
29
22
  export declare function useAuthState(): AuthStateValue;
30
- export declare function listUserWorkspaces(token?: string): Promise<{
31
- memberships: MembershipWithWorkspaceDto[];
32
- }>;
23
+ export { authRefreshQueryKey, authRefreshQueryOptions, listUserWorkspaces, userWorkspacesQueryKey, userWorkspacesQueryOptions, } from '#hooks/api/session-auth.js';
33
24
  export declare function getAuthRefreshDelayMs(token: string, nowMs?: number): number | undefined;
34
25
  export declare function useAuthTransition(): {
35
- enterAuthenticated: (result: LoginResponseDto) => Promise<void>;
26
+ enterAuthenticated: (session: AuthenticatedSession) => Promise<void>;
36
27
  enterGuest: () => Promise<void>;
37
28
  };
38
- export declare function useRefreshAuth(): () => Promise<{
39
- token: string;
40
- user: {
41
- id: string;
42
- email: string;
43
- name: string | null;
44
- email_verified_at: string | null;
45
- status: "active" | "suspended" | "deleted";
46
- created_at: string;
47
- updated_at: string;
48
- };
49
- }>;
29
+ export declare function useRefreshAuth(): () => Promise<AuthenticatedSession>;
50
30
  export interface AuthRuntimeProps extends PropsWithChildren {
51
31
  effects?: boolean;
52
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/runtime/auth.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,gBAAgB,EAAE,OAAO,EAAC,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AAI5E,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAO7C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,eAAe,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,gBAAgB,EAAE,SAA+B,CAAC;AAC/D,eAAO,MAAM,aAAa;;CAAoC,CAAC;AAE/D,eAAO,MAAM,mBAAmB,8BAA+B,CAAC;AAChE,eAAO,MAAM,sBAAsB,iCAAkC,CAAC;AAEtE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,WAAW,GAAE,0BAA0B,EAAO,GAC7C,SAAS,CAWX;AAED,wBAAgB,YAAY,IAAI,cAAc,CAY7C;AAED,wBAAsB,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM;iBACf,0BAA0B,EAAE;GAInE;AAwBD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED,wBAAgB,iBAAiB;iCAmBd,gBAAgB;;EAgClC;AAED,wBAAgB,cAAc;;;;;;;;;;;GAkB7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,EAAC,QAAQ,EAAE,OAAc,EAAC,EAAE,gBAAgB,6BAyEvE"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/runtime/auth.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAE7C,OAAO,KAAK,EAAC,oBAAoB,EAAE,YAAY,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAW3F,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,eAAe,GAAG,OAAO,CAAC;AAE/D,MAAM,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEzC,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,gBAAgB,EAAE,SAA+B,CAAC;AAC/D,eAAO,MAAM,aAAa;;CAAoC,CAAC;AAG/D,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,oBAAoB,EAC7B,UAAU,GAAE,gBAAgB,EAAO,GAClC,SAAS,CAOX;AAED,wBAAgB,YAAY,IAAI,cAAc,CAY7C;AAED,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,4BAA4B,CAAC;AAoBpC,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED,wBAAgB,iBAAiB;kCAmBb,oBAAoB;;EA4BvC;AAED,wBAAgB,cAAc,wCAa7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,EAAC,QAAQ,EAAE,OAAc,EAAC,EAAE,gBAAgB,6BAyEvE"}
@@ -1,7 +1,8 @@
1
- import { ApiError, apiRequest, configureApiClient } from '@shipfox/client-api';
1
+ import { ApiError, configureApiClient } from '@shipfox/client-api';
2
2
  import { useQueryClient } from '@tanstack/react-query';
3
3
  import { atom, useAtomValue, useSetAtom, useStore } from 'jotai';
4
4
  import { useCallback, useEffect, useMemo } from 'react';
5
+ import { authRefreshQueryKey, authRefreshQueryOptions, userWorkspacesQueryOptions } from '#hooks/api/session-auth.js';
5
6
  const REFRESH_EARLY_MS = 5 * 60 * 1000;
6
7
  const REFRESH_RETRY_DELAY_MS = 60_000;
7
8
  const BASE64_URL_REPLACEMENTS = {
@@ -13,24 +14,12 @@ export const initialAuthState = {
13
14
  };
14
15
  export const authStateAtom = atom(initialAuthState);
15
16
  const authTransitionEpochAtom = atom(0);
16
- export const authRefreshQueryKey = [
17
- 'auth',
18
- 'refresh'
19
- ];
20
- export const userWorkspacesQueryKey = [
21
- 'workspaces',
22
- 'mine'
23
- ];
24
- export function toAuthenticatedState(result, memberships = []) {
17
+ export function toAuthenticatedState(session, workspaces = []) {
25
18
  return {
26
19
  status: 'authenticated',
27
- token: result.token,
28
- user: result.user,
29
- workspaces: memberships.map((membership)=>({
30
- id: membership.workspace_id,
31
- name: membership.workspace_name,
32
- membershipId: membership.id
33
- }))
20
+ token: session.accessToken,
21
+ user: session.user,
22
+ workspaces
34
23
  };
35
24
  }
36
25
  export function useAuthState() {
@@ -45,18 +34,7 @@ export function useAuthState() {
45
34
  state
46
35
  ]);
47
36
  }
48
- export async function listUserWorkspaces(token) {
49
- return await apiRequest('/workspaces', token ? {
50
- headers: {
51
- authorization: `Bearer ${token}`
52
- }
53
- } : {});
54
- }
55
- async function refreshAuthRequest() {
56
- return await apiRequest('/auth/refresh', {
57
- method: 'POST'
58
- });
59
- }
37
+ export { authRefreshQueryKey, authRefreshQueryOptions, listUserWorkspaces, userWorkspacesQueryKey, userWorkspacesQueryOptions } from '#hooks/api/session-auth.js';
60
38
  function decodeBase64Url(value) {
61
39
  const base64 = value.replace(BASE64_URL_REPLACEMENTS.dash, '+').replace(BASE64_URL_REPLACEMENTS.underscore, '/');
62
40
  return atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='));
@@ -98,29 +76,23 @@ export function useAuthTransition() {
98
76
  setState,
99
77
  store
100
78
  ]);
101
- const enterAuthenticated = useCallback(async (result)=>{
79
+ const enterAuthenticated = useCallback(async (session)=>{
102
80
  const transitionEpoch = store.get(authTransitionEpochAtom) + 1;
103
81
  store.set(authTransitionEpochAtom, transitionEpoch);
104
82
  const previousState = store.get(authStateAtom);
105
- const principalChanged = previousState.status === 'authenticated' && previousState.user?.id !== result.user.id;
83
+ const principalChanged = previousState.status === 'authenticated' && previousState.user?.id !== session.user.id;
106
84
  if (principalChanged) await clearPrivateState();
107
85
  if (store.get(authTransitionEpochAtom) !== transitionEpoch) return;
108
- queryClient.setQueryData(authRefreshQueryKey, result);
109
- let memberships = [];
86
+ queryClient.setQueryData(authRefreshQueryKey, session);
87
+ let workspaces = [];
110
88
  try {
111
- const workspaces = await queryClient.fetchQuery({
112
- queryKey: userWorkspacesQueryKey,
113
- queryFn: ()=>listUserWorkspaces(result.token),
114
- retry: false,
115
- staleTime: 0
116
- });
117
- memberships = workspaces.memberships;
118
- queryClient.setQueryData(userWorkspacesQueryKey, workspaces);
89
+ const hydratedWorkspaces = await queryClient.fetchQuery(userWorkspacesQueryOptions(session.accessToken));
90
+ workspaces = hydratedWorkspaces.memberships;
119
91
  } catch {
120
92
  // The authenticated session remains usable while workspace hydration retries on the next route load.
121
93
  }
122
94
  if (store.get(authTransitionEpochAtom) !== transitionEpoch) return;
123
- setState(toAuthenticatedState(result, memberships));
95
+ setState(toAuthenticatedState(session, workspaces));
124
96
  }, [
125
97
  clearPrivateState,
126
98
  queryClient,
@@ -137,12 +109,7 @@ export function useRefreshAuth() {
137
109
  const { enterAuthenticated, enterGuest } = useAuthTransition();
138
110
  return useCallback(async ()=>{
139
111
  try {
140
- const result = await queryClient.fetchQuery({
141
- queryKey: authRefreshQueryKey,
142
- queryFn: refreshAuthRequest,
143
- retry: false,
144
- staleTime: 0
145
- });
112
+ const result = await queryClient.fetchQuery(authRefreshQueryOptions());
146
113
  await enterAuthenticated(result);
147
114
  return result;
148
115
  } catch (error) {
@@ -163,7 +130,7 @@ export function AuthRuntime({ children, effects = true }) {
163
130
  if (!effects) return;
164
131
  configureApiClient({
165
132
  getAccessToken: ()=>store.get(authStateAtom).token,
166
- refreshAccessToken: async ()=>(await refreshAuth()).token
133
+ refreshAccessToken: async ()=>(await refreshAuth()).accessToken
167
134
  });
168
135
  }, [
169
136
  effects,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runtime/auth.tsx"],"sourcesContent":["import type {LoginResponseDto, UserDto} from '@shipfox/api-auth-dto';\nimport type {MembershipWithWorkspaceDto} from '@shipfox/api-workspaces-dto';\nimport {ApiError, apiRequest, configureApiClient} from '@shipfox/client-api';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {atom, useAtomValue, useSetAtom, useStore} from 'jotai';\nimport type {PropsWithChildren} from 'react';\nimport {useCallback, useEffect, useMemo} from 'react';\n\nconst REFRESH_EARLY_MS = 5 * 60 * 1000;\nconst REFRESH_RETRY_DELAY_MS = 60_000;\nconst BASE64_URL_REPLACEMENTS = {dash: /-/g, underscore: /_/g} as const;\n\nexport type AuthStatus = 'loading' | 'authenticated' | 'guest';\n\nexport interface Workspace {\n id: string;\n name: string;\n membershipId: string;\n}\n\nexport interface AuthState {\n status: AuthStatus;\n token?: string;\n user?: UserDto;\n workspaces?: Workspace[];\n}\n\nexport interface AuthStateValue extends AuthState {\n isLoading: boolean;\n isAuthenticated: boolean;\n workspaces: Workspace[];\n hasWorkspace: boolean;\n}\n\nexport const initialAuthState: AuthState = {status: 'loading'};\nexport const authStateAtom = atom<AuthState>(initialAuthState);\nconst authTransitionEpochAtom = atom(0);\nexport const authRefreshQueryKey = ['auth', 'refresh'] as const;\nexport const userWorkspacesQueryKey = ['workspaces', 'mine'] as const;\n\nexport function toAuthenticatedState(\n result: LoginResponseDto,\n memberships: MembershipWithWorkspaceDto[] = [],\n): AuthState {\n return {\n status: 'authenticated',\n token: result.token,\n user: result.user,\n workspaces: memberships.map((membership) => ({\n id: membership.workspace_id,\n name: membership.workspace_name,\n membershipId: membership.id,\n })),\n };\n}\n\nexport function useAuthState(): AuthStateValue {\n const state = useAtomValue(authStateAtom);\n return useMemo(\n () => ({\n ...state,\n workspaces: state.workspaces ?? [],\n isLoading: state.status === 'loading',\n isAuthenticated: state.status === 'authenticated',\n hasWorkspace: (state.workspaces ?? []).length > 0,\n }),\n [state],\n );\n}\n\nexport async function listUserWorkspaces(token?: string) {\n return await apiRequest<{memberships: MembershipWithWorkspaceDto[]}>(\n '/workspaces',\n token ? {headers: {authorization: `Bearer ${token}`}} : {},\n );\n}\n\nasync function refreshAuthRequest() {\n return await apiRequest<LoginResponseDto>('/auth/refresh', {method: 'POST'});\n}\n\nfunction decodeBase64Url(value: string): string {\n const base64 = value\n .replace(BASE64_URL_REPLACEMENTS.dash, '+')\n .replace(BASE64_URL_REPLACEMENTS.underscore, '/');\n return atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='));\n}\n\nfunction readJwtExp(token: string): number | undefined {\n const [, payload] = token.split('.');\n if (!payload) return undefined;\n try {\n const parsed = JSON.parse(decodeBase64Url(payload)) as {exp?: unknown};\n return typeof parsed.exp === 'number' && Number.isFinite(parsed.exp) ? parsed.exp : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function getAuthRefreshDelayMs(token: string, nowMs = Date.now()): number | undefined {\n const exp = readJwtExp(token);\n return exp === undefined ? undefined : exp * 1000 - nowMs - REFRESH_EARLY_MS;\n}\n\nexport function useAuthTransition() {\n const queryClient = useQueryClient();\n const store = useStore();\n const setState = useSetAtom(authStateAtom);\n\n const clearPrivateState = useCallback(async () => {\n await queryClient.cancelQueries();\n queryClient.clear();\n }, [queryClient]);\n\n const enterGuest = useCallback(async () => {\n const transitionEpoch = store.get(authTransitionEpochAtom) + 1;\n store.set(authTransitionEpochAtom, transitionEpoch);\n await clearPrivateState();\n if (store.get(authTransitionEpochAtom) !== transitionEpoch) return;\n setState({status: 'guest'});\n }, [clearPrivateState, setState, store]);\n\n const enterAuthenticated = useCallback(\n async (result: LoginResponseDto) => {\n const transitionEpoch = store.get(authTransitionEpochAtom) + 1;\n store.set(authTransitionEpochAtom, transitionEpoch);\n const previousState = store.get(authStateAtom);\n const principalChanged =\n previousState.status === 'authenticated' && previousState.user?.id !== result.user.id;\n\n if (principalChanged) await clearPrivateState();\n if (store.get(authTransitionEpochAtom) !== transitionEpoch) return;\n\n queryClient.setQueryData(authRefreshQueryKey, result);\n let memberships: MembershipWithWorkspaceDto[] = [];\n try {\n const workspaces = await queryClient.fetchQuery({\n queryKey: userWorkspacesQueryKey,\n queryFn: () => listUserWorkspaces(result.token),\n retry: false,\n staleTime: 0,\n });\n memberships = workspaces.memberships;\n queryClient.setQueryData(userWorkspacesQueryKey, workspaces);\n } catch {\n // The authenticated session remains usable while workspace hydration retries on the next route load.\n }\n if (store.get(authTransitionEpochAtom) !== transitionEpoch) return;\n\n setState(toAuthenticatedState(result, memberships));\n },\n [clearPrivateState, queryClient, setState, store],\n );\n\n return {enterAuthenticated, enterGuest};\n}\n\nexport function useRefreshAuth() {\n const queryClient = useQueryClient();\n const {enterAuthenticated, enterGuest} = useAuthTransition();\n return useCallback(async () => {\n try {\n const result = await queryClient.fetchQuery({\n queryKey: authRefreshQueryKey,\n queryFn: refreshAuthRequest,\n retry: false,\n staleTime: 0,\n });\n await enterAuthenticated(result);\n return result;\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) await enterGuest();\n throw error;\n }\n }, [enterAuthenticated, enterGuest, queryClient]);\n}\n\nexport interface AuthRuntimeProps extends PropsWithChildren {\n effects?: boolean;\n}\n\nexport function AuthRuntime({children, effects = true}: AuthRuntimeProps) {\n const store = useStore();\n const authState = useAtomValue(authStateAtom);\n const refreshAuth = useRefreshAuth();\n\n useEffect(() => {\n if (!effects) return;\n configureApiClient({\n getAccessToken: () => store.get(authStateAtom).token,\n refreshAccessToken: async () => (await refreshAuth()).token,\n });\n }, [effects, refreshAuth, store]);\n\n useEffect(() => {\n if (!effects) return;\n refreshAuth().catch(() => undefined);\n }, [effects, refreshAuth]);\n\n useEffect(() => {\n if (!effects || authState.status !== 'authenticated' || !authState.token) return;\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let disposed = false;\n let refreshing = false;\n const clearRefreshTimer = () => {\n if (timeout !== undefined) clearTimeout(timeout);\n timeout = undefined;\n };\n const scheduleRefresh = (delayMs: number) => {\n clearRefreshTimer();\n timeout = setTimeout(runRefresh, Math.max(0, delayMs));\n };\n const retryIfStillDue = () => {\n const current = store.get(authStateAtom);\n if (current.status !== 'authenticated' || !current.token) return;\n const delay = getAuthRefreshDelayMs(current.token);\n if (delay !== undefined && delay <= 0) scheduleRefresh(REFRESH_RETRY_DELAY_MS);\n };\n function runRefresh() {\n if (disposed || refreshing) return;\n refreshing = true;\n clearRefreshTimer();\n refreshAuth()\n .catch(() => undefined)\n .finally(() => {\n refreshing = false;\n if (!disposed) retryIfStillDue();\n });\n }\n const refreshIfDue = () => {\n const current = store.get(authStateAtom);\n if (current.status !== 'authenticated' || !current.token) return;\n const delay = getAuthRefreshDelayMs(current.token);\n if (delay !== undefined && delay <= 0) runRefresh();\n };\n const refreshIfVisible = () => {\n if (document.visibilityState === 'visible') refreshIfDue();\n };\n const delay = getAuthRefreshDelayMs(authState.token);\n if (delay !== undefined) scheduleRefresh(delay);\n window.addEventListener('focus', refreshIfDue);\n window.addEventListener('online', refreshIfDue);\n document.addEventListener('visibilitychange', refreshIfVisible);\n return () => {\n disposed = true;\n clearRefreshTimer();\n window.removeEventListener('focus', refreshIfDue);\n window.removeEventListener('online', refreshIfDue);\n document.removeEventListener('visibilitychange', refreshIfVisible);\n };\n }, [authState.status, authState.token, effects, refreshAuth, store]);\n\n return children;\n}\n"],"names":["ApiError","apiRequest","configureApiClient","useQueryClient","atom","useAtomValue","useSetAtom","useStore","useCallback","useEffect","useMemo","REFRESH_EARLY_MS","REFRESH_RETRY_DELAY_MS","BASE64_URL_REPLACEMENTS","dash","underscore","initialAuthState","status","authStateAtom","authTransitionEpochAtom","authRefreshQueryKey","userWorkspacesQueryKey","toAuthenticatedState","result","memberships","token","user","workspaces","map","membership","id","workspace_id","name","workspace_name","membershipId","useAuthState","state","isLoading","isAuthenticated","hasWorkspace","length","listUserWorkspaces","headers","authorization","refreshAuthRequest","method","decodeBase64Url","value","base64","replace","atob","padEnd","Math","ceil","readJwtExp","payload","split","undefined","parsed","JSON","parse","exp","Number","isFinite","getAuthRefreshDelayMs","nowMs","Date","now","useAuthTransition","queryClient","store","setState","clearPrivateState","cancelQueries","clear","enterGuest","transitionEpoch","get","set","enterAuthenticated","previousState","principalChanged","setQueryData","fetchQuery","queryKey","queryFn","retry","staleTime","useRefreshAuth","error","AuthRuntime","children","effects","authState","refreshAuth","getAccessToken","refreshAccessToken","catch","timeout","disposed","refreshing","clearRefreshTimer","clearTimeout","scheduleRefresh","delayMs","setTimeout","runRefresh","max","retryIfStillDue","current","delay","finally","refreshIfDue","refreshIfVisible","document","visibilityState","window","addEventListener","removeEventListener"],"mappings":"AAEA,SAAQA,QAAQ,EAAEC,UAAU,EAAEC,kBAAkB,QAAO,sBAAsB;AAC7E,SAAQC,cAAc,QAAO,wBAAwB;AACrD,SAAQC,IAAI,EAAEC,YAAY,EAAEC,UAAU,EAAEC,QAAQ,QAAO,QAAQ;AAE/D,SAAQC,WAAW,EAAEC,SAAS,EAAEC,OAAO,QAAO,QAAQ;AAEtD,MAAMC,mBAAmB,IAAI,KAAK;AAClC,MAAMC,yBAAyB;AAC/B,MAAMC,0BAA0B;IAACC,MAAM;IAAMC,YAAY;AAAI;AAwB7D,OAAO,MAAMC,mBAA8B;IAACC,QAAQ;AAAS,EAAE;AAC/D,OAAO,MAAMC,gBAAgBd,KAAgBY,kBAAkB;AAC/D,MAAMG,0BAA0Bf,KAAK;AACrC,OAAO,MAAMgB,sBAAsB;IAAC;IAAQ;CAAU,CAAU;AAChE,OAAO,MAAMC,yBAAyB;IAAC;IAAc;CAAO,CAAU;AAEtE,OAAO,SAASC,qBACdC,MAAwB,EACxBC,cAA4C,EAAE;IAE9C,OAAO;QACLP,QAAQ;QACRQ,OAAOF,OAAOE,KAAK;QACnBC,MAAMH,OAAOG,IAAI;QACjBC,YAAYH,YAAYI,GAAG,CAAC,CAACC,aAAgB,CAAA;gBAC3CC,IAAID,WAAWE,YAAY;gBAC3BC,MAAMH,WAAWI,cAAc;gBAC/BC,cAAcL,WAAWC,EAAE;YAC7B,CAAA;IACF;AACF;AAEA,OAAO,SAASK;IACd,MAAMC,QAAQ/B,aAAaa;IAC3B,OAAOR,QACL,IAAO,CAAA;YACL,GAAG0B,KAAK;YACRT,YAAYS,MAAMT,UAAU,IAAI,EAAE;YAClCU,WAAWD,MAAMnB,MAAM,KAAK;YAC5BqB,iBAAiBF,MAAMnB,MAAM,KAAK;YAClCsB,cAAc,AAACH,CAAAA,MAAMT,UAAU,IAAI,EAAE,AAAD,EAAGa,MAAM,GAAG;QAClD,CAAA,GACA;QAACJ;KAAM;AAEX;AAEA,OAAO,eAAeK,mBAAmBhB,KAAc;IACrD,OAAO,MAAMxB,WACX,eACAwB,QAAQ;QAACiB,SAAS;YAACC,eAAe,CAAC,OAAO,EAAElB,OAAO;QAAA;IAAC,IAAI,CAAC;AAE7D;AAEA,eAAemB;IACb,OAAO,MAAM3C,WAA6B,iBAAiB;QAAC4C,QAAQ;IAAM;AAC5E;AAEA,SAASC,gBAAgBC,KAAa;IACpC,MAAMC,SAASD,MACZE,OAAO,CAACpC,wBAAwBC,IAAI,EAAE,KACtCmC,OAAO,CAACpC,wBAAwBE,UAAU,EAAE;IAC/C,OAAOmC,KAAKF,OAAOG,MAAM,CAACC,KAAKC,IAAI,CAACL,OAAOR,MAAM,GAAG,KAAK,GAAG;AAC9D;AAEA,SAASc,WAAW7B,KAAa;IAC/B,MAAM,GAAG8B,QAAQ,GAAG9B,MAAM+B,KAAK,CAAC;IAChC,IAAI,CAACD,SAAS,OAAOE;IACrB,IAAI;QACF,MAAMC,SAASC,KAAKC,KAAK,CAACd,gBAAgBS;QAC1C,OAAO,OAAOG,OAAOG,GAAG,KAAK,YAAYC,OAAOC,QAAQ,CAACL,OAAOG,GAAG,IAAIH,OAAOG,GAAG,GAAGJ;IACtF,EAAE,OAAM;QACN,OAAOA;IACT;AACF;AAEA,OAAO,SAASO,sBAAsBvC,KAAa,EAAEwC,QAAQC,KAAKC,GAAG,EAAE;IACrE,MAAMN,MAAMP,WAAW7B;IACvB,OAAOoC,QAAQJ,YAAYA,YAAYI,MAAM,OAAOI,QAAQtD;AAC9D;AAEA,OAAO,SAASyD;IACd,MAAMC,cAAclE;IACpB,MAAMmE,QAAQ/D;IACd,MAAMgE,WAAWjE,WAAWY;IAE5B,MAAMsD,oBAAoBhE,YAAY;QACpC,MAAM6D,YAAYI,aAAa;QAC/BJ,YAAYK,KAAK;IACnB,GAAG;QAACL;KAAY;IAEhB,MAAMM,aAAanE,YAAY;QAC7B,MAAMoE,kBAAkBN,MAAMO,GAAG,CAAC1D,2BAA2B;QAC7DmD,MAAMQ,GAAG,CAAC3D,yBAAyByD;QACnC,MAAMJ;QACN,IAAIF,MAAMO,GAAG,CAAC1D,6BAA6ByD,iBAAiB;QAC5DL,SAAS;YAACtD,QAAQ;QAAO;IAC3B,GAAG;QAACuD;QAAmBD;QAAUD;KAAM;IAEvC,MAAMS,qBAAqBvE,YACzB,OAAOe;QACL,MAAMqD,kBAAkBN,MAAMO,GAAG,CAAC1D,2BAA2B;QAC7DmD,MAAMQ,GAAG,CAAC3D,yBAAyByD;QACnC,MAAMI,gBAAgBV,MAAMO,GAAG,CAAC3D;QAChC,MAAM+D,mBACJD,cAAc/D,MAAM,KAAK,mBAAmB+D,cAActD,IAAI,EAAEI,OAAOP,OAAOG,IAAI,CAACI,EAAE;QAEvF,IAAImD,kBAAkB,MAAMT;QAC5B,IAAIF,MAAMO,GAAG,CAAC1D,6BAA6ByD,iBAAiB;QAE5DP,YAAYa,YAAY,CAAC9D,qBAAqBG;QAC9C,IAAIC,cAA4C,EAAE;QAClD,IAAI;YACF,MAAMG,aAAa,MAAM0C,YAAYc,UAAU,CAAC;gBAC9CC,UAAU/D;gBACVgE,SAAS,IAAM5C,mBAAmBlB,OAAOE,KAAK;gBAC9C6D,OAAO;gBACPC,WAAW;YACb;YACA/D,cAAcG,WAAWH,WAAW;YACpC6C,YAAYa,YAAY,CAAC7D,wBAAwBM;QACnD,EAAE,OAAM;QACN,qGAAqG;QACvG;QACA,IAAI2C,MAAMO,GAAG,CAAC1D,6BAA6ByD,iBAAiB;QAE5DL,SAASjD,qBAAqBC,QAAQC;IACxC,GACA;QAACgD;QAAmBH;QAAaE;QAAUD;KAAM;IAGnD,OAAO;QAACS;QAAoBJ;IAAU;AACxC;AAEA,OAAO,SAASa;IACd,MAAMnB,cAAclE;IACpB,MAAM,EAAC4E,kBAAkB,EAAEJ,UAAU,EAAC,GAAGP;IACzC,OAAO5D,YAAY;QACjB,IAAI;YACF,MAAMe,SAAS,MAAM8C,YAAYc,UAAU,CAAC;gBAC1CC,UAAUhE;gBACViE,SAASzC;gBACT0C,OAAO;gBACPC,WAAW;YACb;YACA,MAAMR,mBAAmBxD;YACzB,OAAOA;QACT,EAAE,OAAOkE,OAAO;YACd,IAAIA,iBAAiBzF,YAAYyF,MAAMxE,MAAM,KAAK,KAAK,MAAM0D;YAC7D,MAAMc;QACR;IACF,GAAG;QAACV;QAAoBJ;QAAYN;KAAY;AAClD;AAMA,OAAO,SAASqB,YAAY,EAACC,QAAQ,EAAEC,UAAU,IAAI,EAAmB;IACtE,MAAMtB,QAAQ/D;IACd,MAAMsF,YAAYxF,aAAaa;IAC/B,MAAM4E,cAAcN;IAEpB/E,UAAU;QACR,IAAI,CAACmF,SAAS;QACd1F,mBAAmB;YACjB6F,gBAAgB,IAAMzB,MAAMO,GAAG,CAAC3D,eAAeO,KAAK;YACpDuE,oBAAoB,UAAY,AAAC,CAAA,MAAMF,aAAY,EAAGrE,KAAK;QAC7D;IACF,GAAG;QAACmE;QAASE;QAAaxB;KAAM;IAEhC7D,UAAU;QACR,IAAI,CAACmF,SAAS;QACdE,cAAcG,KAAK,CAAC,IAAMxC;IAC5B,GAAG;QAACmC;QAASE;KAAY;IAEzBrF,UAAU;QACR,IAAI,CAACmF,WAAWC,UAAU5E,MAAM,KAAK,mBAAmB,CAAC4E,UAAUpE,KAAK,EAAE;QAE1E,IAAIyE;QACJ,IAAIC,WAAW;QACf,IAAIC,aAAa;QACjB,MAAMC,oBAAoB;YACxB,IAAIH,YAAYzC,WAAW6C,aAAaJ;YACxCA,UAAUzC;QACZ;QACA,MAAM8C,kBAAkB,CAACC;YACvBH;YACAH,UAAUO,WAAWC,YAAYtD,KAAKuD,GAAG,CAAC,GAAGH;QAC/C;QACA,MAAMI,kBAAkB;YACtB,MAAMC,UAAUvC,MAAMO,GAAG,CAAC3D;YAC1B,IAAI2F,QAAQ5F,MAAM,KAAK,mBAAmB,CAAC4F,QAAQpF,KAAK,EAAE;YAC1D,MAAMqF,QAAQ9C,sBAAsB6C,QAAQpF,KAAK;YACjD,IAAIqF,UAAUrD,aAAaqD,SAAS,GAAGP,gBAAgB3F;QACzD;QACA,SAAS8F;YACP,IAAIP,YAAYC,YAAY;YAC5BA,aAAa;YACbC;YACAP,cACGG,KAAK,CAAC,IAAMxC,WACZsD,OAAO,CAAC;gBACPX,aAAa;gBACb,IAAI,CAACD,UAAUS;YACjB;QACJ;QACA,MAAMI,eAAe;YACnB,MAAMH,UAAUvC,MAAMO,GAAG,CAAC3D;YAC1B,IAAI2F,QAAQ5F,MAAM,KAAK,mBAAmB,CAAC4F,QAAQpF,KAAK,EAAE;YAC1D,MAAMqF,QAAQ9C,sBAAsB6C,QAAQpF,KAAK;YACjD,IAAIqF,UAAUrD,aAAaqD,SAAS,GAAGJ;QACzC;QACA,MAAMO,mBAAmB;YACvB,IAAIC,SAASC,eAAe,KAAK,WAAWH;QAC9C;QACA,MAAMF,QAAQ9C,sBAAsB6B,UAAUpE,KAAK;QACnD,IAAIqF,UAAUrD,WAAW8C,gBAAgBO;QACzCM,OAAOC,gBAAgB,CAAC,SAASL;QACjCI,OAAOC,gBAAgB,CAAC,UAAUL;QAClCE,SAASG,gBAAgB,CAAC,oBAAoBJ;QAC9C,OAAO;YACLd,WAAW;YACXE;YACAe,OAAOE,mBAAmB,CAAC,SAASN;YACpCI,OAAOE,mBAAmB,CAAC,UAAUN;YACrCE,SAASI,mBAAmB,CAAC,oBAAoBL;QACnD;IACF,GAAG;QAACpB,UAAU5E,MAAM;QAAE4E,UAAUpE,KAAK;QAAEmE;QAASE;QAAaxB;KAAM;IAEnE,OAAOqB;AACT"}
1
+ {"version":3,"sources":["../../src/runtime/auth.tsx"],"sourcesContent":["import {ApiError, configureApiClient} from '@shipfox/client-api';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {atom, useAtomValue, useSetAtom, useStore} from 'jotai';\nimport type {PropsWithChildren} from 'react';\nimport {useCallback, useEffect, useMemo} from 'react';\nimport type {AuthenticatedSession, UserIdentity, WorkspaceSummary} from '#core/session.js';\nimport {\n authRefreshQueryKey,\n authRefreshQueryOptions,\n userWorkspacesQueryOptions,\n} from '#hooks/api/session-auth.js';\n\nconst REFRESH_EARLY_MS = 5 * 60 * 1000;\nconst REFRESH_RETRY_DELAY_MS = 60_000;\nconst BASE64_URL_REPLACEMENTS = {dash: /-/g, underscore: /_/g} as const;\n\nexport type AuthStatus = 'loading' | 'authenticated' | 'guest';\n\nexport type Workspace = WorkspaceSummary;\n\nexport interface AuthState {\n status: AuthStatus;\n token?: string;\n user?: UserIdentity;\n workspaces?: Workspace[];\n}\n\nexport interface AuthStateValue extends AuthState {\n isLoading: boolean;\n isAuthenticated: boolean;\n workspaces: Workspace[];\n hasWorkspace: boolean;\n}\n\nexport const initialAuthState: AuthState = {status: 'loading'};\nexport const authStateAtom = atom<AuthState>(initialAuthState);\nconst authTransitionEpochAtom = atom(0);\n\nexport function toAuthenticatedState(\n session: AuthenticatedSession,\n workspaces: WorkspaceSummary[] = [],\n): AuthState {\n return {\n status: 'authenticated',\n token: session.accessToken,\n user: session.user,\n workspaces,\n };\n}\n\nexport function useAuthState(): AuthStateValue {\n const state = useAtomValue(authStateAtom);\n return useMemo(\n () => ({\n ...state,\n workspaces: state.workspaces ?? [],\n isLoading: state.status === 'loading',\n isAuthenticated: state.status === 'authenticated',\n hasWorkspace: (state.workspaces ?? []).length > 0,\n }),\n [state],\n );\n}\n\nexport {\n authRefreshQueryKey,\n authRefreshQueryOptions,\n listUserWorkspaces,\n userWorkspacesQueryKey,\n userWorkspacesQueryOptions,\n} from '#hooks/api/session-auth.js';\n\nfunction decodeBase64Url(value: string): string {\n const base64 = value\n .replace(BASE64_URL_REPLACEMENTS.dash, '+')\n .replace(BASE64_URL_REPLACEMENTS.underscore, '/');\n return atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='));\n}\n\nfunction readJwtExp(token: string): number | undefined {\n const [, payload] = token.split('.');\n if (!payload) return undefined;\n try {\n const parsed = JSON.parse(decodeBase64Url(payload)) as {exp?: unknown};\n return typeof parsed.exp === 'number' && Number.isFinite(parsed.exp) ? parsed.exp : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function getAuthRefreshDelayMs(token: string, nowMs = Date.now()): number | undefined {\n const exp = readJwtExp(token);\n return exp === undefined ? undefined : exp * 1000 - nowMs - REFRESH_EARLY_MS;\n}\n\nexport function useAuthTransition() {\n const queryClient = useQueryClient();\n const store = useStore();\n const setState = useSetAtom(authStateAtom);\n\n const clearPrivateState = useCallback(async () => {\n await queryClient.cancelQueries();\n queryClient.clear();\n }, [queryClient]);\n\n const enterGuest = useCallback(async () => {\n const transitionEpoch = store.get(authTransitionEpochAtom) + 1;\n store.set(authTransitionEpochAtom, transitionEpoch);\n await clearPrivateState();\n if (store.get(authTransitionEpochAtom) !== transitionEpoch) return;\n setState({status: 'guest'});\n }, [clearPrivateState, setState, store]);\n\n const enterAuthenticated = useCallback(\n async (session: AuthenticatedSession) => {\n const transitionEpoch = store.get(authTransitionEpochAtom) + 1;\n store.set(authTransitionEpochAtom, transitionEpoch);\n const previousState = store.get(authStateAtom);\n const principalChanged =\n previousState.status === 'authenticated' && previousState.user?.id !== session.user.id;\n\n if (principalChanged) await clearPrivateState();\n if (store.get(authTransitionEpochAtom) !== transitionEpoch) return;\n\n queryClient.setQueryData(authRefreshQueryKey, session);\n let workspaces: WorkspaceSummary[] = [];\n try {\n const hydratedWorkspaces = await queryClient.fetchQuery(\n userWorkspacesQueryOptions(session.accessToken),\n );\n workspaces = hydratedWorkspaces.memberships;\n } catch {\n // The authenticated session remains usable while workspace hydration retries on the next route load.\n }\n if (store.get(authTransitionEpochAtom) !== transitionEpoch) return;\n\n setState(toAuthenticatedState(session, workspaces));\n },\n [clearPrivateState, queryClient, setState, store],\n );\n\n return {enterAuthenticated, enterGuest};\n}\n\nexport function useRefreshAuth() {\n const queryClient = useQueryClient();\n const {enterAuthenticated, enterGuest} = useAuthTransition();\n return useCallback(async () => {\n try {\n const result = await queryClient.fetchQuery(authRefreshQueryOptions());\n await enterAuthenticated(result);\n return result;\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) await enterGuest();\n throw error;\n }\n }, [enterAuthenticated, enterGuest, queryClient]);\n}\n\nexport interface AuthRuntimeProps extends PropsWithChildren {\n effects?: boolean;\n}\n\nexport function AuthRuntime({children, effects = true}: AuthRuntimeProps) {\n const store = useStore();\n const authState = useAtomValue(authStateAtom);\n const refreshAuth = useRefreshAuth();\n\n useEffect(() => {\n if (!effects) return;\n configureApiClient({\n getAccessToken: () => store.get(authStateAtom).token,\n refreshAccessToken: async () => (await refreshAuth()).accessToken,\n });\n }, [effects, refreshAuth, store]);\n\n useEffect(() => {\n if (!effects) return;\n refreshAuth().catch(() => undefined);\n }, [effects, refreshAuth]);\n\n useEffect(() => {\n if (!effects || authState.status !== 'authenticated' || !authState.token) return;\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let disposed = false;\n let refreshing = false;\n const clearRefreshTimer = () => {\n if (timeout !== undefined) clearTimeout(timeout);\n timeout = undefined;\n };\n const scheduleRefresh = (delayMs: number) => {\n clearRefreshTimer();\n timeout = setTimeout(runRefresh, Math.max(0, delayMs));\n };\n const retryIfStillDue = () => {\n const current = store.get(authStateAtom);\n if (current.status !== 'authenticated' || !current.token) return;\n const delay = getAuthRefreshDelayMs(current.token);\n if (delay !== undefined && delay <= 0) scheduleRefresh(REFRESH_RETRY_DELAY_MS);\n };\n function runRefresh() {\n if (disposed || refreshing) return;\n refreshing = true;\n clearRefreshTimer();\n refreshAuth()\n .catch(() => undefined)\n .finally(() => {\n refreshing = false;\n if (!disposed) retryIfStillDue();\n });\n }\n const refreshIfDue = () => {\n const current = store.get(authStateAtom);\n if (current.status !== 'authenticated' || !current.token) return;\n const delay = getAuthRefreshDelayMs(current.token);\n if (delay !== undefined && delay <= 0) runRefresh();\n };\n const refreshIfVisible = () => {\n if (document.visibilityState === 'visible') refreshIfDue();\n };\n const delay = getAuthRefreshDelayMs(authState.token);\n if (delay !== undefined) scheduleRefresh(delay);\n window.addEventListener('focus', refreshIfDue);\n window.addEventListener('online', refreshIfDue);\n document.addEventListener('visibilitychange', refreshIfVisible);\n return () => {\n disposed = true;\n clearRefreshTimer();\n window.removeEventListener('focus', refreshIfDue);\n window.removeEventListener('online', refreshIfDue);\n document.removeEventListener('visibilitychange', refreshIfVisible);\n };\n }, [authState.status, authState.token, effects, refreshAuth, store]);\n\n return children;\n}\n"],"names":["ApiError","configureApiClient","useQueryClient","atom","useAtomValue","useSetAtom","useStore","useCallback","useEffect","useMemo","authRefreshQueryKey","authRefreshQueryOptions","userWorkspacesQueryOptions","REFRESH_EARLY_MS","REFRESH_RETRY_DELAY_MS","BASE64_URL_REPLACEMENTS","dash","underscore","initialAuthState","status","authStateAtom","authTransitionEpochAtom","toAuthenticatedState","session","workspaces","token","accessToken","user","useAuthState","state","isLoading","isAuthenticated","hasWorkspace","length","listUserWorkspaces","userWorkspacesQueryKey","decodeBase64Url","value","base64","replace","atob","padEnd","Math","ceil","readJwtExp","payload","split","undefined","parsed","JSON","parse","exp","Number","isFinite","getAuthRefreshDelayMs","nowMs","Date","now","useAuthTransition","queryClient","store","setState","clearPrivateState","cancelQueries","clear","enterGuest","transitionEpoch","get","set","enterAuthenticated","previousState","principalChanged","id","setQueryData","hydratedWorkspaces","fetchQuery","memberships","useRefreshAuth","result","error","AuthRuntime","children","effects","authState","refreshAuth","getAccessToken","refreshAccessToken","catch","timeout","disposed","refreshing","clearRefreshTimer","clearTimeout","scheduleRefresh","delayMs","setTimeout","runRefresh","max","retryIfStillDue","current","delay","finally","refreshIfDue","refreshIfVisible","document","visibilityState","window","addEventListener","removeEventListener"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"compose-client-app.d.ts","sourceRoot":"","sources":["../../src/runtime/compose-client-app.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAC,KAAK,SAAS,EAAiB,MAAM,wBAAwB,CAAC;AAQtE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,cAAc,GACf,EAAE;IACD,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACrC;mBAkBkB,WAAW,GAAG,IAAI;EAiCpC"}
1
+ {"version":3,"file":"compose-client-app.d.ts","sourceRoot":"","sources":["../../src/runtime/compose-client-app.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAC,KAAK,SAAS,EAAiB,MAAM,wBAAwB,CAAC;AAKtE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,cAAc,GACf,EAAE;IACD,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACrC;mBASkB,WAAW,GAAG,IAAI;EAiCpC"}
@@ -9,19 +9,13 @@ import { RouterProvider } from '@tanstack/react-router';
9
9
  import { createStore } from 'jotai';
10
10
  import { StrictMode, useEffect } from 'react';
11
11
  import { createRoot } from 'react-dom/client';
12
- import { composeRoutes } from '#compose/compose-routes.js';
13
- import { mergeConfigShapes } from '#compose/merge-config.js';
14
- import { validateProviderIds } from '#compose/validate-providers.js';
15
- import { validateNavigation, validateSettingsSections } from '#compose/validate-registries.js';
12
+ import { composeClientFeatures } from '#compose/compose-client-features.js';
16
13
  import { useAuthState } from './auth.js';
17
14
  import { ChromeProvider } from './chrome-context.js';
18
15
  import { ShellProviderStack } from './provider-stack.js';
19
16
  export function composeClientApp({ features, router, chrome, workspaceSetup }) {
20
- const routes = composeRoutes(features);
21
- validateProviderIds(features);
22
- validateNavigation(features, routes.map((route)=>route.path));
23
- validateSettingsSections(features, routes.map((route)=>route.path));
24
- const config = loadConfig(mergeConfigShapes(features), {
17
+ const composition = composeClientFeatures(features);
18
+ const config = loadConfig(composition.configShape, {
25
19
  runtime: getWindowRuntimeConfig(),
26
20
  build: import.meta.env
27
21
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runtime/compose-client-app.tsx"],"sourcesContent":["import {configureApiClient} from '@shipfox/client-api';\nimport {\n ConfigErrorScreen,\n getWindowRuntimeConfig,\n loadConfig,\n setLoadedConfig,\n} from '@shipfox/client-config';\nimport {ThemeProvider} from '@shipfox/react-ui/theme';\nimport {Toaster} from '@shipfox/react-ui/toast';\nimport {TooltipProvider} from '@shipfox/react-ui/tooltip';\nimport {QueryClient} from '@tanstack/react-query';\nimport {type AnyRouter, RouterProvider} from '@tanstack/react-router';\nimport {createStore} from 'jotai';\nimport {StrictMode, useEffect} from 'react';\nimport {createRoot} from 'react-dom/client';\nimport {composeRoutes} from '#compose/compose-routes.js';\nimport {mergeConfigShapes} from '#compose/merge-config.js';\nimport {validateProviderIds} from '#compose/validate-providers.js';\nimport {validateNavigation, validateSettingsSections} from '#compose/validate-registries.js';\nimport type {ClientFeature} from '#contract.js';\nimport {useAuthState} from './auth.js';\nimport {ChromeProvider, type ChromeSlots} from './chrome-context.js';\nimport {ShellProviderStack} from './provider-stack.js';\nimport type {WorkspaceSetupGate} from './workspace-setup.js';\n\nexport function composeClientApp({\n features,\n router,\n chrome,\n workspaceSetup,\n}: {\n features: readonly ClientFeature[];\n router: AnyRouter;\n chrome?: ChromeSlots;\n workspaceSetup?: WorkspaceSetupGate;\n}) {\n const routes = composeRoutes(features);\n validateProviderIds(features);\n validateNavigation(\n features,\n routes.map((route) => route.path),\n );\n validateSettingsSections(\n features,\n routes.map((route) => route.path),\n );\n const config = loadConfig(mergeConfigShapes(features), {\n runtime: getWindowRuntimeConfig(),\n build: (import.meta as ImportMeta & {env?: Record<string, unknown>}).env,\n });\n if (config.ok) setLoadedConfig(config.config);\n\n return {\n mount(element: HTMLElement): void {\n const root = createRoot(element);\n if (!config.ok) {\n root.render(\n <StrictMode>\n <ThemeProvider>\n <TooltipProvider>\n <ConfigErrorScreen errors={config.errors} />\n </TooltipProvider>\n </ThemeProvider>\n </StrictMode>,\n );\n return;\n }\n\n configureApiClient({baseUrl: configApiUrl(config.config)});\n const queryClient = new QueryClient();\n root.render(\n <StrictMode>\n <ChromeProvider chrome={chrome}>\n <ShellProviderStack features={features} queryClient={queryClient} store={createStore()}>\n <RoutedApp\n router={router}\n queryClient={queryClient}\n workspaceSetup={workspaceSetup}\n />\n <Toaster />\n </ShellProviderStack>\n </ChromeProvider>\n </StrictMode>,\n );\n },\n };\n}\n\nfunction RoutedApp({\n router,\n queryClient,\n workspaceSetup,\n}: {\n router: AnyRouter;\n queryClient: QueryClient;\n workspaceSetup: WorkspaceSetupGate | undefined;\n}) {\n const auth = useAuthState();\n\n useEffect(() => {\n if (!auth.isLoading) router.invalidate();\n }, [auth.isLoading, router]);\n\n return (\n <RouterProvider\n router={router as never}\n context={{auth, queryClient, workspaceSetup} as never}\n />\n );\n}\n\nfunction configApiUrl(config: unknown): string {\n if (\n typeof config !== 'object' ||\n config === null ||\n !('apiUrl' in config) ||\n typeof config.apiUrl !== 'string'\n ) {\n throw new Error('Composed client configuration must include a string apiUrl.');\n }\n return config.apiUrl;\n}\n"],"names":["configureApiClient","ConfigErrorScreen","getWindowRuntimeConfig","loadConfig","setLoadedConfig","ThemeProvider","Toaster","TooltipProvider","QueryClient","RouterProvider","createStore","StrictMode","useEffect","createRoot","composeRoutes","mergeConfigShapes","validateProviderIds","validateNavigation","validateSettingsSections","useAuthState","ChromeProvider","ShellProviderStack","composeClientApp","features","router","chrome","workspaceSetup","routes","map","route","path","config","runtime","build","env","ok","mount","element","root","render","errors","baseUrl","configApiUrl","queryClient","store","RoutedApp","auth","isLoading","invalidate","context","apiUrl","Error"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,sBAAsB;AACvD,SACEC,iBAAiB,EACjBC,sBAAsB,EACtBC,UAAU,EACVC,eAAe,QACV,yBAAyB;AAChC,SAAQC,aAAa,QAAO,0BAA0B;AACtD,SAAQC,OAAO,QAAO,0BAA0B;AAChD,SAAQC,eAAe,QAAO,4BAA4B;AAC1D,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAwBC,cAAc,QAAO,yBAAyB;AACtE,SAAQC,WAAW,QAAO,QAAQ;AAClC,SAAQC,UAAU,EAAEC,SAAS,QAAO,QAAQ;AAC5C,SAAQC,UAAU,QAAO,mBAAmB;AAC5C,SAAQC,aAAa,QAAO,6BAA6B;AACzD,SAAQC,iBAAiB,QAAO,2BAA2B;AAC3D,SAAQC,mBAAmB,QAAO,iCAAiC;AACnE,SAAQC,kBAAkB,EAAEC,wBAAwB,QAAO,kCAAkC;AAE7F,SAAQC,YAAY,QAAO,YAAY;AACvC,SAAQC,cAAc,QAAyB,sBAAsB;AACrE,SAAQC,kBAAkB,QAAO,sBAAsB;AAGvD,OAAO,SAASC,iBAAiB,EAC/BC,QAAQ,EACRC,MAAM,EACNC,MAAM,EACNC,cAAc,EAMf;IACC,MAAMC,SAASb,cAAcS;IAC7BP,oBAAoBO;IACpBN,mBACEM,UACAI,OAAOC,GAAG,CAAC,CAACC,QAAUA,MAAMC,IAAI;IAElCZ,yBACEK,UACAI,OAAOC,GAAG,CAAC,CAACC,QAAUA,MAAMC,IAAI;IAElC,MAAMC,SAAS5B,WAAWY,kBAAkBQ,WAAW;QACrDS,SAAS9B;QACT+B,OAAO,AAAC,YAA6DC,GAAG;IAC1E;IACA,IAAIH,OAAOI,EAAE,EAAE/B,gBAAgB2B,OAAOA,MAAM;IAE5C,OAAO;QACLK,OAAMC,OAAoB;YACxB,MAAMC,OAAOzB,WAAWwB;YACxB,IAAI,CAACN,OAAOI,EAAE,EAAE;gBACdG,KAAKC,MAAM,eACT,KAAC5B;8BACC,cAAA,KAACN;kCACC,cAAA,KAACE;sCACC,cAAA,KAACN;gCAAkBuC,QAAQT,OAAOS,MAAM;;;;;gBAKhD;YACF;YAEAxC,mBAAmB;gBAACyC,SAASC,aAAaX,OAAOA,MAAM;YAAC;YACxD,MAAMY,cAAc,IAAInC;YACxB8B,KAAKC,MAAM,eACT,KAAC5B;0BACC,cAAA,KAACS;oBAAeK,QAAQA;8BACtB,cAAA,MAACJ;wBAAmBE,UAAUA;wBAAUoB,aAAaA;wBAAaC,OAAOlC;;0CACvE,KAACmC;gCACCrB,QAAQA;gCACRmB,aAAaA;gCACbjB,gBAAgBA;;0CAElB,KAACpB;;;;;QAKX;IACF;AACF;AAEA,SAASuC,UAAU,EACjBrB,MAAM,EACNmB,WAAW,EACXjB,cAAc,EAKf;IACC,MAAMoB,OAAO3B;IAEbP,UAAU;QACR,IAAI,CAACkC,KAAKC,SAAS,EAAEvB,OAAOwB,UAAU;IACxC,GAAG;QAACF,KAAKC,SAAS;QAAEvB;KAAO;IAE3B,qBACE,KAACf;QACCe,QAAQA;QACRyB,SAAS;YAACH;YAAMH;YAAajB;QAAc;;AAGjD;AAEA,SAASgB,aAAaX,MAAe;IACnC,IACE,OAAOA,WAAW,YAClBA,WAAW,QACX,CAAE,CAAA,YAAYA,MAAK,KACnB,OAAOA,OAAOmB,MAAM,KAAK,UACzB;QACA,MAAM,IAAIC,MAAM;IAClB;IACA,OAAOpB,OAAOmB,MAAM;AACtB"}
1
+ {"version":3,"sources":["../../src/runtime/compose-client-app.tsx"],"sourcesContent":["import {configureApiClient} from '@shipfox/client-api';\nimport {\n ConfigErrorScreen,\n getWindowRuntimeConfig,\n loadConfig,\n setLoadedConfig,\n} from '@shipfox/client-config';\nimport {ThemeProvider} from '@shipfox/react-ui/theme';\nimport {Toaster} from '@shipfox/react-ui/toast';\nimport {TooltipProvider} from '@shipfox/react-ui/tooltip';\nimport {QueryClient} from '@tanstack/react-query';\nimport {type AnyRouter, RouterProvider} from '@tanstack/react-router';\nimport {createStore} from 'jotai';\nimport {StrictMode, useEffect} from 'react';\nimport {createRoot} from 'react-dom/client';\nimport {composeClientFeatures} from '#compose/compose-client-features.js';\nimport type {ClientFeature} from '#contract.js';\nimport {useAuthState} from './auth.js';\nimport {ChromeProvider, type ChromeSlots} from './chrome-context.js';\nimport {ShellProviderStack} from './provider-stack.js';\nimport type {WorkspaceSetupGate} from './workspace-setup.js';\n\nexport function composeClientApp({\n features,\n router,\n chrome,\n workspaceSetup,\n}: {\n features: readonly ClientFeature[];\n router: AnyRouter;\n chrome?: ChromeSlots;\n workspaceSetup?: WorkspaceSetupGate;\n}) {\n const composition = composeClientFeatures(features);\n const config = loadConfig(composition.configShape, {\n runtime: getWindowRuntimeConfig(),\n build: (import.meta as ImportMeta & {env?: Record<string, unknown>}).env,\n });\n if (config.ok) setLoadedConfig(config.config);\n\n return {\n mount(element: HTMLElement): void {\n const root = createRoot(element);\n if (!config.ok) {\n root.render(\n <StrictMode>\n <ThemeProvider>\n <TooltipProvider>\n <ConfigErrorScreen errors={config.errors} />\n </TooltipProvider>\n </ThemeProvider>\n </StrictMode>,\n );\n return;\n }\n\n configureApiClient({baseUrl: configApiUrl(config.config)});\n const queryClient = new QueryClient();\n root.render(\n <StrictMode>\n <ChromeProvider chrome={chrome}>\n <ShellProviderStack features={features} queryClient={queryClient} store={createStore()}>\n <RoutedApp\n router={router}\n queryClient={queryClient}\n workspaceSetup={workspaceSetup}\n />\n <Toaster />\n </ShellProviderStack>\n </ChromeProvider>\n </StrictMode>,\n );\n },\n };\n}\n\nfunction RoutedApp({\n router,\n queryClient,\n workspaceSetup,\n}: {\n router: AnyRouter;\n queryClient: QueryClient;\n workspaceSetup: WorkspaceSetupGate | undefined;\n}) {\n const auth = useAuthState();\n\n useEffect(() => {\n if (!auth.isLoading) router.invalidate();\n }, [auth.isLoading, router]);\n\n return (\n <RouterProvider\n router={router as never}\n context={{auth, queryClient, workspaceSetup} as never}\n />\n );\n}\n\nfunction configApiUrl(config: unknown): string {\n if (\n typeof config !== 'object' ||\n config === null ||\n !('apiUrl' in config) ||\n typeof config.apiUrl !== 'string'\n ) {\n throw new Error('Composed client configuration must include a string apiUrl.');\n }\n return config.apiUrl;\n}\n"],"names":["configureApiClient","ConfigErrorScreen","getWindowRuntimeConfig","loadConfig","setLoadedConfig","ThemeProvider","Toaster","TooltipProvider","QueryClient","RouterProvider","createStore","StrictMode","useEffect","createRoot","composeClientFeatures","useAuthState","ChromeProvider","ShellProviderStack","composeClientApp","features","router","chrome","workspaceSetup","composition","config","configShape","runtime","build","env","ok","mount","element","root","render","errors","baseUrl","configApiUrl","queryClient","store","RoutedApp","auth","isLoading","invalidate","context","apiUrl","Error"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,sBAAsB;AACvD,SACEC,iBAAiB,EACjBC,sBAAsB,EACtBC,UAAU,EACVC,eAAe,QACV,yBAAyB;AAChC,SAAQC,aAAa,QAAO,0BAA0B;AACtD,SAAQC,OAAO,QAAO,0BAA0B;AAChD,SAAQC,eAAe,QAAO,4BAA4B;AAC1D,SAAQC,WAAW,QAAO,wBAAwB;AAClD,SAAwBC,cAAc,QAAO,yBAAyB;AACtE,SAAQC,WAAW,QAAO,QAAQ;AAClC,SAAQC,UAAU,EAAEC,SAAS,QAAO,QAAQ;AAC5C,SAAQC,UAAU,QAAO,mBAAmB;AAC5C,SAAQC,qBAAqB,QAAO,sCAAsC;AAE1E,SAAQC,YAAY,QAAO,YAAY;AACvC,SAAQC,cAAc,QAAyB,sBAAsB;AACrE,SAAQC,kBAAkB,QAAO,sBAAsB;AAGvD,OAAO,SAASC,iBAAiB,EAC/BC,QAAQ,EACRC,MAAM,EACNC,MAAM,EACNC,cAAc,EAMf;IACC,MAAMC,cAAcT,sBAAsBK;IAC1C,MAAMK,SAASrB,WAAWoB,YAAYE,WAAW,EAAE;QACjDC,SAASxB;QACTyB,OAAO,AAAC,YAA6DC,GAAG;IAC1E;IACA,IAAIJ,OAAOK,EAAE,EAAEzB,gBAAgBoB,OAAOA,MAAM;IAE5C,OAAO;QACLM,OAAMC,OAAoB;YACxB,MAAMC,OAAOnB,WAAWkB;YACxB,IAAI,CAACP,OAAOK,EAAE,EAAE;gBACdG,KAAKC,MAAM,eACT,KAACtB;8BACC,cAAA,KAACN;kCACC,cAAA,KAACE;sCACC,cAAA,KAACN;gCAAkBiC,QAAQV,OAAOU,MAAM;;;;;gBAKhD;YACF;YAEAlC,mBAAmB;gBAACmC,SAASC,aAAaZ,OAAOA,MAAM;YAAC;YACxD,MAAMa,cAAc,IAAI7B;YACxBwB,KAAKC,MAAM,eACT,KAACtB;0BACC,cAAA,KAACK;oBAAeK,QAAQA;8BACtB,cAAA,MAACJ;wBAAmBE,UAAUA;wBAAUkB,aAAaA;wBAAaC,OAAO5B;;0CACvE,KAAC6B;gCACCnB,QAAQA;gCACRiB,aAAaA;gCACbf,gBAAgBA;;0CAElB,KAAChB;;;;;QAKX;IACF;AACF;AAEA,SAASiC,UAAU,EACjBnB,MAAM,EACNiB,WAAW,EACXf,cAAc,EAKf;IACC,MAAMkB,OAAOzB;IAEbH,UAAU;QACR,IAAI,CAAC4B,KAAKC,SAAS,EAAErB,OAAOsB,UAAU;IACxC,GAAG;QAACF,KAAKC,SAAS;QAAErB;KAAO;IAE3B,qBACE,KAACX;QACCW,QAAQA;QACRuB,SAAS;YAACH;YAAMH;YAAaf;QAAc;;AAGjD;AAEA,SAASc,aAAaZ,MAAe;IACnC,IACE,OAAOA,WAAW,YAClBA,WAAW,QACX,CAAE,CAAA,YAAYA,MAAK,KACnB,OAAOA,OAAOoB,MAAM,KAAK,UACzB;QACA,MAAM,IAAIC,MAAM;IAClB;IACA,OAAOrB,OAAOoB,MAAM;AACtB"}
@@ -1,5 +1,9 @@
1
+ export { AuthActions, AuthShell, type AuthShellProps } from '#components/auth-shell.js';
1
2
  export { WorkspaceCrumb, type WorkspaceCrumbProps } from '#components/workspace-crumb.js';
2
3
  export { WorkspaceSwitcher } from '#components/workspace-switcher.js';
4
+ export type { AuthenticatedSession, UserIdentity, WorkspaceMembership, WorkspaceSummary, } from '#core/session.js';
5
+ export { toAuthenticatedSession, toUserIdentity } from '#hooks/api/session-mapper.js';
6
+ export * from '../compose/compose-client-features.js';
3
7
  export * from '../compose/compose-routes.js';
4
8
  export * from '../compose/errors.js';
5
9
  export * from '../compose/merge-config.js';
@@ -15,6 +19,7 @@ export * from './compose-client-app.js';
15
19
  export * from './define-route.js';
16
20
  export * from './last-workspace.js';
17
21
  export * from './nav-order.js';
22
+ export * from './route-inputs.js';
18
23
  export * from './router-context.js';
19
24
  export * from './workspace-setup.js';
20
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,cAAc,EAAE,KAAK,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AACxF,OAAO,EAAC,iBAAiB,EAAC,MAAM,mCAAmC,CAAC;AACpE,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAC;AACtF,OAAO,EAAC,cAAc,EAAE,KAAK,mBAAmB,EAAC,MAAM,gCAAgC,CAAC;AACxF,OAAO,EAAC,iBAAiB,EAAC,MAAM,mCAAmC,CAAC;AACpE,YAAY,EACV,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAC,sBAAsB,EAAE,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACpF,cAAc,uCAAuC,CAAC;AACtD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oCAAoC,CAAC;AACnD,cAAc,kCAAkC,CAAC;AACjD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC"}
@@ -1,5 +1,8 @@
1
+ export { AuthActions, AuthShell } from '#components/auth-shell.js';
1
2
  export { WorkspaceCrumb } from '#components/workspace-crumb.js';
2
3
  export { WorkspaceSwitcher } from '#components/workspace-switcher.js';
4
+ export { toAuthenticatedSession, toUserIdentity } from '#hooks/api/session-mapper.js';
5
+ export * from '../compose/compose-client-features.js';
3
6
  export * from '../compose/compose-routes.js';
4
7
  export * from '../compose/errors.js';
5
8
  export * from '../compose/merge-config.js';
@@ -15,6 +18,7 @@ export * from './compose-client-app.js';
15
18
  export * from './define-route.js';
16
19
  export * from './last-workspace.js';
17
20
  export * from './nav-order.js';
21
+ export * from './route-inputs.js';
18
22
  export * from './router-context.js';
19
23
  export * from './workspace-setup.js';
20
24
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runtime/index.ts"],"sourcesContent":["export {WorkspaceCrumb, type WorkspaceCrumbProps} from '#components/workspace-crumb.js';\nexport {WorkspaceSwitcher} from '#components/workspace-switcher.js';\nexport * from '../compose/compose-routes.js';\nexport * from '../compose/errors.js';\nexport * from '../compose/merge-config.js';\nexport * from '../compose/normalize-route-path.js';\nexport * from '../compose/validate-providers.js';\nexport * from '../compose/validate-registries.js';\nexport * from './active-workspace.js';\nexport * from './anchor-paths.js';\nexport * from './anchors.js';\nexport * from './auth.js';\nexport * from './chrome-context.js';\nexport * from './compose-client-app.js';\nexport * from './define-route.js';\nexport * from './last-workspace.js';\nexport * from './nav-order.js';\nexport * from './router-context.js';\nexport * from './workspace-setup.js';\n"],"names":["WorkspaceCrumb","WorkspaceSwitcher"],"mappings":"AAAA,SAAQA,cAAc,QAAiC,iCAAiC;AACxF,SAAQC,iBAAiB,QAAO,oCAAoC;AACpE,cAAc,+BAA+B;AAC7C,cAAc,uBAAuB;AACrC,cAAc,6BAA6B;AAC3C,cAAc,qCAAqC;AACnD,cAAc,mCAAmC;AACjD,cAAc,oCAAoC;AAClD,cAAc,wBAAwB;AACtC,cAAc,oBAAoB;AAClC,cAAc,eAAe;AAC7B,cAAc,YAAY;AAC1B,cAAc,sBAAsB;AACpC,cAAc,0BAA0B;AACxC,cAAc,oBAAoB;AAClC,cAAc,sBAAsB;AACpC,cAAc,iBAAiB;AAC/B,cAAc,sBAAsB;AACpC,cAAc,uBAAuB"}
1
+ {"version":3,"sources":["../../src/runtime/index.ts"],"sourcesContent":["export {AuthActions, AuthShell, type AuthShellProps} from '#components/auth-shell.js';\nexport {WorkspaceCrumb, type WorkspaceCrumbProps} from '#components/workspace-crumb.js';\nexport {WorkspaceSwitcher} from '#components/workspace-switcher.js';\nexport type {\n AuthenticatedSession,\n UserIdentity,\n WorkspaceMembership,\n WorkspaceSummary,\n} from '#core/session.js';\nexport {toAuthenticatedSession, toUserIdentity} from '#hooks/api/session-mapper.js';\nexport * from '../compose/compose-client-features.js';\nexport * from '../compose/compose-routes.js';\nexport * from '../compose/errors.js';\nexport * from '../compose/merge-config.js';\nexport * from '../compose/normalize-route-path.js';\nexport * from '../compose/validate-providers.js';\nexport * from '../compose/validate-registries.js';\nexport * from './active-workspace.js';\nexport * from './anchor-paths.js';\nexport * from './anchors.js';\nexport * from './auth.js';\nexport * from './chrome-context.js';\nexport * from './compose-client-app.js';\nexport * from './define-route.js';\nexport * from './last-workspace.js';\nexport * from './nav-order.js';\nexport * from './route-inputs.js';\nexport * from './router-context.js';\nexport * from './workspace-setup.js';\n"],"names":["AuthActions","AuthShell","WorkspaceCrumb","WorkspaceSwitcher","toAuthenticatedSession","toUserIdentity"],"mappings":"AAAA,SAAQA,WAAW,EAAEC,SAAS,QAA4B,4BAA4B;AACtF,SAAQC,cAAc,QAAiC,iCAAiC;AACxF,SAAQC,iBAAiB,QAAO,oCAAoC;AAOpE,SAAQC,sBAAsB,EAAEC,cAAc,QAAO,+BAA+B;AACpF,cAAc,wCAAwC;AACtD,cAAc,+BAA+B;AAC7C,cAAc,uBAAuB;AACrC,cAAc,6BAA6B;AAC3C,cAAc,qCAAqC;AACnD,cAAc,mCAAmC;AACjD,cAAc,oCAAoC;AAClD,cAAc,wBAAwB;AACtC,cAAc,oBAAoB;AAClC,cAAc,eAAe;AAC7B,cAAc,YAAY;AAC1B,cAAc,sBAAsB;AACpC,cAAc,0BAA0B;AACxC,cAAc,oBAAoB;AAClC,cAAc,sBAAsB;AACpC,cAAc,iBAAiB;AAC/B,cAAc,oBAAoB;AAClC,cAAc,sBAAsB;AACpC,cAAc,uBAAuB"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Reads inputs at the dynamic feature-route boundary. Feature routes must immediately
3
+ * validate the result and pass typed values to pages; Shell never interprets feature input.
4
+ */
5
+ export declare function useRouteSearch<T>(parse: (search: Record<string, unknown>) => T): T;
6
+ /**
7
+ * Reads path parameters at the dynamic feature-route boundary without making Shell
8
+ * responsible for a feature's parameter contract.
9
+ */
10
+ export declare function useRouteParams<T>(parse: (params: Record<string, unknown>) => T): T;
11
+ export declare function parseWorkspaceParams(input: Record<string, unknown>): {
12
+ wid?: string;
13
+ };
14
+ export declare function parseWorkspaceProjectParams(input: Record<string, unknown>): {
15
+ wid?: string;
16
+ pid?: string;
17
+ };
18
+ //# sourceMappingURL=route-inputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-inputs.d.ts","sourceRoot":"","sources":["../../src/runtime/route-inputs.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAGlF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAGlF;AAMD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAC,CAGnF;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAOA"}
@@ -0,0 +1,49 @@
1
+ import { useParams, useSearch } from '@tanstack/react-router';
2
+ import { useMemo } from 'react';
3
+ /**
4
+ * Reads inputs at the dynamic feature-route boundary. Feature routes must immediately
5
+ * validate the result and pass typed values to pages; Shell never interprets feature input.
6
+ */ export function useRouteSearch(parse) {
7
+ const search = useSearch({
8
+ strict: false
9
+ });
10
+ return useMemo(()=>parse(search), [
11
+ parse,
12
+ search
13
+ ]);
14
+ }
15
+ /**
16
+ * Reads path parameters at the dynamic feature-route boundary without making Shell
17
+ * responsible for a feature's parameter contract.
18
+ */ export function useRouteParams(parse) {
19
+ const params = useParams({
20
+ strict: false
21
+ });
22
+ return useMemo(()=>parse(params), [
23
+ parse,
24
+ params
25
+ ]);
26
+ }
27
+ function optionalRouteString(value) {
28
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
29
+ }
30
+ export function parseWorkspaceParams(input) {
31
+ const wid = optionalRouteString(input.wid);
32
+ return wid ? {
33
+ wid
34
+ } : {};
35
+ }
36
+ export function parseWorkspaceProjectParams(input) {
37
+ const wid = optionalRouteString(input.wid);
38
+ const pid = optionalRouteString(input.pid);
39
+ return {
40
+ ...wid ? {
41
+ wid
42
+ } : {},
43
+ ...pid ? {
44
+ pid
45
+ } : {}
46
+ };
47
+ }
48
+
49
+ //# sourceMappingURL=route-inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/runtime/route-inputs.ts"],"sourcesContent":["import {useParams, useSearch} from '@tanstack/react-router';\nimport {useMemo} from 'react';\n\n/**\n * Reads inputs at the dynamic feature-route boundary. Feature routes must immediately\n * validate the result and pass typed values to pages; Shell never interprets feature input.\n */\nexport function useRouteSearch<T>(parse: (search: Record<string, unknown>) => T): T {\n const search = useSearch({strict: false}) as Record<string, unknown>;\n return useMemo(() => parse(search), [parse, search]);\n}\n\n/**\n * Reads path parameters at the dynamic feature-route boundary without making Shell\n * responsible for a feature's parameter contract.\n */\nexport function useRouteParams<T>(parse: (params: Record<string, unknown>) => T): T {\n const params = useParams({strict: false}) as Record<string, unknown>;\n return useMemo(() => parse(params), [parse, params]);\n}\n\nfunction optionalRouteString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nexport function parseWorkspaceParams(input: Record<string, unknown>): {wid?: string} {\n const wid = optionalRouteString(input.wid);\n return wid ? {wid} : {};\n}\n\nexport function parseWorkspaceProjectParams(input: Record<string, unknown>): {\n wid?: string;\n pid?: string;\n} {\n const wid = optionalRouteString(input.wid);\n const pid = optionalRouteString(input.pid);\n return {\n ...(wid ? {wid} : {}),\n ...(pid ? {pid} : {}),\n };\n}\n"],"names":["useParams","useSearch","useMemo","useRouteSearch","parse","search","strict","useRouteParams","params","optionalRouteString","value","length","undefined","parseWorkspaceParams","input","wid","parseWorkspaceProjectParams","pid"],"mappings":"AAAA,SAAQA,SAAS,EAAEC,SAAS,QAAO,yBAAyB;AAC5D,SAAQC,OAAO,QAAO,QAAQ;AAE9B;;;CAGC,GACD,OAAO,SAASC,eAAkBC,KAA6C;IAC7E,MAAMC,SAASJ,UAAU;QAACK,QAAQ;IAAK;IACvC,OAAOJ,QAAQ,IAAME,MAAMC,SAAS;QAACD;QAAOC;KAAO;AACrD;AAEA;;;CAGC,GACD,OAAO,SAASE,eAAkBH,KAA6C;IAC7E,MAAMI,SAASR,UAAU;QAACM,QAAQ;IAAK;IACvC,OAAOJ,QAAQ,IAAME,MAAMI,SAAS;QAACJ;QAAOI;KAAO;AACrD;AAEA,SAASC,oBAAoBC,KAAc;IACzC,OAAO,OAAOA,UAAU,YAAYA,MAAMC,MAAM,GAAG,IAAID,QAAQE;AACjE;AAEA,OAAO,SAASC,qBAAqBC,KAA8B;IACjE,MAAMC,MAAMN,oBAAoBK,MAAMC,GAAG;IACzC,OAAOA,MAAM;QAACA;IAAG,IAAI,CAAC;AACxB;AAEA,OAAO,SAASC,4BAA4BF,KAA8B;IAIxE,MAAMC,MAAMN,oBAAoBK,MAAMC,GAAG;IACzC,MAAME,MAAMR,oBAAoBK,MAAMG,GAAG;IACzC,OAAO;QACL,GAAIF,MAAM;YAACA;QAAG,IAAI,CAAC,CAAC;QACpB,GAAIE,MAAM;YAACA;QAAG,IAAI,CAAC,CAAC;IACtB;AACF"}