everything-dev 2.0.0-rc.1 → 2.0.0-rc.4
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.
- package/dist/auth-login.cjs +1 -1
- package/dist/auth-login.mjs +1 -1
- package/dist/build.cjs +2 -2
- package/dist/build.mjs +2 -2
- package/dist/bundle-fs-resolve.cjs +9 -17
- package/dist/bundle-fs-resolve.cjs.map +1 -1
- package/dist/bundle-fs-resolve.d.cts +6 -0
- package/dist/bundle-fs-resolve.d.mts +6 -0
- package/dist/bundle-fs-resolve.mjs +9 -17
- package/dist/bundle-fs-resolve.mjs.map +1 -1
- package/dist/bundle-path.cjs +48 -0
- package/dist/bundle-path.cjs.map +1 -0
- package/dist/bundle-path.mjs +45 -0
- package/dist/bundle-path.mjs.map +1 -0
- package/dist/cli/init.cjs +4 -4
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.mjs +4 -4
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/status.cjs +69 -3
- package/dist/cli/status.cjs.map +1 -1
- package/dist/cli/status.mjs +67 -4
- package/dist/cli/status.mjs.map +1 -1
- package/dist/cli/sync.cjs +1 -1
- package/dist/cli/sync.cjs.map +1 -1
- package/dist/cli/sync.mjs +1 -1
- package/dist/cli/sync.mjs.map +1 -1
- package/dist/cli/upgrade.cjs +2 -2
- package/dist/cli/upgrade.cjs.map +1 -1
- package/dist/cli/upgrade.mjs +2 -2
- package/dist/cli/upgrade.mjs.map +1 -1
- package/dist/cli.cjs +5 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +5 -7
- package/dist/cli.mjs.map +1 -1
- package/dist/commands/deploy.cjs +26 -16
- package/dist/commands/deploy.cjs.map +1 -1
- package/dist/commands/deploy.mjs +26 -16
- package/dist/commands/deploy.mjs.map +1 -1
- package/dist/commands/init.cjs +1 -1
- package/dist/commands/init.cjs.map +1 -1
- package/dist/commands/init.mjs +1 -1
- package/dist/commands/init.mjs.map +1 -1
- package/dist/commands/plugins.cjs +2 -2
- package/dist/commands/plugins.cjs.map +1 -1
- package/dist/commands/plugins.mjs +2 -2
- package/dist/commands/plugins.mjs.map +1 -1
- package/dist/commands/upgrade.cjs +1 -1
- package/dist/commands/upgrade.mjs +1 -1
- package/dist/components/dev-render.cjs +1 -1
- package/dist/components/dev-render.mjs +1 -1
- package/dist/config.cjs +3 -3
- package/dist/config.cjs.map +1 -1
- package/dist/config.mjs +3 -3
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +2 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +3 -0
- package/dist/contract.d.mts +3 -0
- package/dist/contract.meta.cjs +2 -1
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +4 -1
- package/dist/contract.meta.d.mts +4 -1
- package/dist/contract.meta.mjs +2 -1
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +2 -0
- package/dist/contract.mjs.map +1 -1
- package/dist/db/driver.cjs +22 -2
- package/dist/db/driver.cjs.map +1 -1
- package/dist/db/driver.d.cts +13 -2
- package/dist/db/driver.d.mts +13 -2
- package/dist/db/driver.mjs +22 -3
- package/dist/db/driver.mjs.map +1 -1
- package/dist/db/index.cjs +1 -0
- package/dist/db/index.d.cts +2 -2
- package/dist/db/index.d.mts +2 -2
- package/dist/db/index.mjs +2 -2
- package/dist/deploy-diff.cjs +1 -1
- package/dist/deploy-diff.cjs.map +1 -1
- package/dist/deploy-diff.mjs +1 -1
- package/dist/deploy-diff.mjs.map +1 -1
- package/dist/dev-program.cjs +10 -2
- package/dist/dev-program.cjs.map +1 -1
- package/dist/dev-program.mjs +10 -2
- package/dist/dev-program.mjs.map +1 -1
- package/dist/image-deploy.cjs +65 -17
- package/dist/image-deploy.cjs.map +1 -1
- package/dist/image-deploy.mjs +64 -18
- package/dist/image-deploy.mjs.map +1 -1
- package/dist/internal/manifest-normalizer.cjs.map +1 -1
- package/dist/internal/manifest-normalizer.mjs.map +1 -1
- package/dist/plugin.d.cts +902 -1
- package/dist/plugin.d.mts +902 -1
- package/dist/publish.cjs +51 -2
- package/dist/publish.cjs.map +1 -1
- package/dist/publish.mjs +51 -4
- package/dist/publish.mjs.map +1 -1
- package/dist/resolution/session.cjs.map +1 -1
- package/dist/resolution/session.d.cts +5 -5
- package/dist/resolution/session.d.mts +5 -5
- package/dist/resolution/session.mjs.map +1 -1
- package/dist/types.d.cts +31 -1
- package/dist/types.d.mts +31 -1
- package/dist/ui/auth-guards.d.cts +78 -2
- package/dist/ui/auth-guards.d.mts +78 -2
- package/dist/ui/auth.cjs.map +1 -1
- package/dist/ui/auth.d.cts +4133 -7
- package/dist/ui/auth.d.mts +4133 -7
- package/dist/ui/auth.mjs.map +1 -1
- package/dist/ui/hydrate.cjs +2 -2
- package/dist/ui/hydrate.cjs.map +1 -1
- package/dist/ui/hydrate.mjs +2 -2
- package/dist/ui/hydrate.mjs.map +1 -1
- package/dist/utils/theme.cjs +2 -2
- package/dist/utils/theme.cjs.map +1 -1
- package/dist/utils/theme.mjs +2 -2
- package/dist/utils/theme.mjs.map +1 -1
- package/dist/version-manifest-resolve.cjs +55 -10
- package/dist/version-manifest-resolve.cjs.map +1 -1
- package/dist/version-manifest-resolve.mjs +53 -10
- package/dist/version-manifest-resolve.mjs.map +1 -1
- package/dist/workspace.cjs +26 -13
- package/dist/workspace.cjs.map +1 -1
- package/dist/workspace.mjs +26 -14
- package/dist/workspace.mjs.map +1 -1
- package/package.json +11 -10
package/dist/ui/auth.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.mjs","names":["createBetterAuthClient"],"sources":["../../src/ui/auth.ts"],"sourcesContent":["import { apiKeyClient } from \"@better-auth/api-key/client\";\nimport { passkeyClient } from \"@better-auth/passkey/client\";\nimport { type QueryClient, useQuery } from \"@tanstack/react-query\";\nimport { useRouter } from \"@tanstack/react-router\";\nimport {\n adminClient,\n anonymousClient,\n deviceAuthorizationClient,\n inferAdditionalFields,\n organizationClient,\n phoneNumberClient,\n} from \"better-auth/client/plugins\";\nimport { createAuthClient as createBetterAuthClient } from \"better-auth/react\";\nimport type { PasskeyWalletLink, RelayedTransactionT } from \"better-near-auth\";\nimport { DEFAULT_DEVICE_LINK_CLIENT_ID, siwnClient } from \"better-near-auth/client\";\nimport type { ClientRuntimeConfig } from \"../types\";\nimport { getRuntimeConfig } from \"./runtime\";\n\ntype RuntimeAuthVariables = {\n siwn: {\n recipient?: string;\n recipients?: {\n mainnet?: string;\n testnet?: string;\n };\n };\n deviceLink?: {\n clientId?: string;\n };\n};\n\nexport type CreateAuthClientOptions = {\n runtimeConfig?: Partial<ClientRuntimeConfig>;\n headers?: HeadersInit;\n cspNonce?: string;\n};\n\ntype SiwnClientConfig = Parameters<typeof siwnClient>[0] & {\n cspNonce?: string;\n};\n\nfunction hasAuthVariables(auth: ClientRuntimeConfig[\"auth\"] | undefined): auth is NonNullable<\n ClientRuntimeConfig[\"auth\"]\n> & {\n variables: RuntimeAuthVariables;\n} {\n return !!auth && typeof auth === \"object\" && typeof auth.variables === \"object\";\n}\n\nfunction readRuntimeConfig(config?: Partial<ClientRuntimeConfig>) {\n if (config) return config;\n if (typeof window === \"undefined\") return undefined;\n try {\n return getRuntimeConfig();\n } catch {\n return undefined;\n }\n}\n\nfunction getAuthVariables(config?: Partial<ClientRuntimeConfig>): RuntimeAuthVariables {\n const runtimeConfig = readRuntimeConfig(config);\n if (!runtimeConfig || !hasAuthVariables(runtimeConfig.auth)) {\n throw new Error(\"Missing auth runtime configuration\");\n }\n return runtimeConfig.auth.variables;\n}\n\nfunction getSiwnClientConfig(options: CreateAuthClientOptions): SiwnClientConfig {\n const runtimeConfig = readRuntimeConfig(options.runtimeConfig);\n const variables = getAuthVariables(options.runtimeConfig);\n const siwn = variables.siwn;\n\n const mainnetRecipient = siwn.recipients?.mainnet ?? siwn.recipient;\n if (!mainnetRecipient) {\n throw new Error(\"Missing auth SIWN recipient\");\n }\n\n const networkId =\n runtimeConfig?.networkId ?? (mainnetRecipient.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\");\n const testnetRecipient = siwn.recipients?.testnet;\n\n if (testnetRecipient) {\n return {\n recipients: { mainnet: mainnetRecipient, testnet: testnetRecipient },\n networkId,\n cspNonce: options.cspNonce,\n };\n }\n\n return { recipient: mainnetRecipient, networkId, cspNonce: options.cspNonce };\n}\n\nfunction getHostUrl(config?: Partial<ClientRuntimeConfig>) {\n const runtimeConfig = readRuntimeConfig(config);\n if (runtimeConfig?.hostUrl) return runtimeConfig.hostUrl;\n if (typeof window !== \"undefined\") return window.location.origin;\n return \"\";\n}\n\nexport function getDeviceLinkClientId(config?: Partial<ClientRuntimeConfig>): string {\n const runtimeConfig = readRuntimeConfig(config);\n const variables = runtimeConfig?.auth?.variables as RuntimeAuthVariables | undefined;\n return variables?.deviceLink?.clientId ?? DEFAULT_DEVICE_LINK_CLIENT_ID;\n}\n\nexport function createAuthClient(options: CreateAuthClientOptions = {}) {\n const nearAuthConfig = getSiwnClientConfig(options);\n\n return createBetterAuthClient({\n baseURL: getHostUrl(options.runtimeConfig),\n fetchOptions: {\n credentials: \"include\",\n ...(options.headers ? { headers: options.headers } : {}),\n },\n plugins: [\n inferAdditionalFields({\n user: {\n locale: { type: \"string\", required: false, input: true },\n },\n }),\n siwnClient(nearAuthConfig),\n adminClient(),\n anonymousClient(),\n phoneNumberClient(),\n passkeyClient(),\n organizationClient({ teams: { enabled: true } }),\n apiKeyClient(),\n deviceAuthorizationClient(),\n ],\n });\n}\n\nexport type { AuthContext } from \"./auth-guards\";\nexport {\n clearAuthenticatedQueries,\n requireAdmin,\n requireSession,\n} from \"./auth-guards\";\nexport { pluginHref, pluginPath, pluginSearch } from \"./plugin-path\";\n\nexport type AuthClient = ReturnType<typeof createAuthClient>;\ntype OrganizationListResult = Awaited<ReturnType<AuthClient[\"organization\"][\"list\"]>>;\ntype PasskeyListResult = Awaited<ReturnType<AuthClient[\"passkey\"][\"listUserPasskeys\"]>>;\n\nexport type SessionData = AuthClient[\"$Infer\"][\"Session\"];\nexport type Organization = NonNullable<OrganizationListResult[\"data\"]>[number];\nexport type Passkey = NonNullable<PasskeyListResult[\"data\"]>[number];\n\nexport function useAuthClient(): AuthClient {\n return useRouter().options.context.authClient as AuthClient;\n}\n\nexport const sessionQueryKey = [\"session\"] as const;\n\nexport function sessionQueryOptions(authClient: AuthClient) {\n return {\n queryKey: sessionQueryKey,\n queryFn: async () => {\n const { data: session, error } = await authClient.getSession({\n query: { disableCookieCache: true },\n });\n if (error) throw new Error(error.message || \"Unable to load your session\");\n return session ?? null;\n },\n staleTime: 60 * 1000,\n gcTime: 10 * 60 * 1000,\n };\n}\n\nexport async function refreshSessionCache(\n authClient: AuthClient,\n queryClient: QueryClient,\n): Promise<SessionData | null> {\n return queryClient.fetchQuery({ ...sessionQueryOptions(authClient), staleTime: 0 });\n}\n\n/**\n * Resolves the session for the root route: the query cache wins (it is kept\n * authoritative by the single sessionQueryOptions read path), and a populated\n * router-context session — the host's SSR resolution — seeds an empty cache so\n * the dehydrated state carries it to the client.\n */\nexport function resolveSessionFromCache<T>(\n queryClient: QueryClient | undefined,\n contextSession: T | null | undefined,\n): T | null | undefined {\n if (!queryClient) return contextSession;\n\n const cachedSession = queryClient.getQueryData<T | null>(sessionQueryKey);\n if (cachedSession !== undefined) return cachedSession;\n\n if (contextSession !== undefined) {\n queryClient.setQueryData(sessionQueryKey, contextSession);\n }\n return contextSession;\n}\n\nexport function useRelayHistory(session: SessionData | null | undefined, authClient: AuthClient) {\n return useQuery({\n queryKey: [\"relay-history\"],\n queryFn: async (): Promise<RelayedTransactionT[]> => {\n const res = await authClient.near.relayHistory();\n return res?.data?.transactions ?? [];\n },\n enabled: !!session,\n refetchInterval: 2000,\n });\n}\n\nexport type PasskeyWalletStatus = PasskeyWalletLink;\n\nexport type PasskeyCeremonyError = Error & { code?: string };\n\nconst UNSUPPORTED_AUTHENTICATOR_CODES = new Set([\n \"PASSKEY_UNSUPPORTED_AUTHENTICATOR\",\n \"ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG\",\n \"ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT\",\n \"ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT\",\n]);\n\nexport function isUnsupportedAuthenticatorError(error: unknown): boolean {\n if (!(error instanceof Error) || !(\"code\" in error)) return false;\n return typeof error.code === \"string\" && UNSUPPORTED_AUTHENTICATOR_CODES.has(error.code);\n}\n\nfunction toPasskeyError(\n error: { code?: string; message?: string } | null | undefined,\n fallback: string,\n): PasskeyCeremonyError {\n return Object.assign(new Error(error?.message || fallback), { code: error?.code });\n}\n\nexport async function isPasskeyAutofillAvailable(): Promise<boolean> {\n if (typeof window === \"undefined\" || !window.PublicKeyCredential) return false;\n try {\n return (await window.PublicKeyCredential.isConditionalMediationAvailable?.()) === true;\n } catch {\n return false;\n }\n}\n\nexport async function createAccountWithPasskey(\n authClient: AuthClient,\n options?: {\n onSuccess?: (result: { passkeyWallet?: PasskeyWalletStatus }) => void;\n onError?: (error: PasskeyCeremonyError) => void;\n },\n): Promise<void> {\n try {\n const { data, error } = await authClient.passkey.addPasskey();\n if (error || !data) {\n options?.onError?.(toPasskeyError(error, \"Could not create your account\"));\n return;\n }\n const { passkeyWallet } = data as { passkeyWallet?: PasskeyWalletStatus };\n options?.onSuccess?.({ passkeyWallet });\n } catch (error) {\n options?.onError?.(\n toPasskeyError(error instanceof Error ? error : null, \"Could not create your account\"),\n );\n }\n}\n\nexport async function signInWithPasskey(\n authClient: AuthClient,\n options?: {\n autoFill?: boolean;\n onSuccess?: () => void;\n onError?: (error: PasskeyCeremonyError) => void;\n },\n): Promise<void> {\n try {\n const { data, error } = await authClient.signIn.passkey({ autoFill: options?.autoFill });\n if (error || !data) {\n options?.onError?.(toPasskeyError(error, \"Passkey sign-in failed\"));\n return;\n }\n options?.onSuccess?.();\n } catch (error) {\n options?.onError?.(\n toPasskeyError(error instanceof Error ? error : null, \"Passkey sign-in failed\"),\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.mjs","names":["createBetterAuthClient"],"sources":["../../src/ui/auth.ts"],"sourcesContent":["import { apiKeyClient } from \"@better-auth/api-key/client\";\nimport { passkeyClient } from \"@better-auth/passkey/client\";\nimport { type QueryClient, useQuery } from \"@tanstack/react-query\";\nimport { useRouter } from \"@tanstack/react-router\";\nimport {\n adminClient,\n anonymousClient,\n deviceAuthorizationClient,\n inferAdditionalFields,\n organizationClient,\n phoneNumberClient,\n} from \"better-auth/client/plugins\";\nimport { createAuthClient as createBetterAuthClient } from \"better-auth/react\";\nimport type { PasskeyWalletLink, RelayedTransactionT } from \"better-near-auth\";\nimport { DEFAULT_DEVICE_LINK_CLIENT_ID, siwnClient } from \"better-near-auth/client\";\nimport type { ClientRuntimeConfig } from \"../types\";\nimport { getRuntimeConfig } from \"./runtime\";\n\ntype RuntimeAuthVariables = {\n siwn: {\n recipient?: string;\n recipients?: {\n mainnet?: string;\n testnet?: string;\n };\n };\n deviceLink?: {\n clientId?: string;\n };\n};\n\nexport type CreateAuthClientOptions = {\n runtimeConfig?: Partial<ClientRuntimeConfig>;\n headers?: HeadersInit;\n cspNonce?: string;\n};\n\ntype SiwnClientConfig = Parameters<typeof siwnClient>[0] & {\n cspNonce?: string;\n};\n\nfunction hasAuthVariables(auth: ClientRuntimeConfig[\"auth\"] | undefined): auth is NonNullable<\n ClientRuntimeConfig[\"auth\"]\n> & {\n variables: RuntimeAuthVariables;\n} {\n return !!auth && typeof auth === \"object\" && typeof auth.variables === \"object\";\n}\n\nfunction readRuntimeConfig(config?: Partial<ClientRuntimeConfig>) {\n if (config) return config;\n if (typeof window === \"undefined\") return undefined;\n try {\n return getRuntimeConfig();\n } catch {\n return undefined;\n }\n}\n\nfunction getAuthVariables(config?: Partial<ClientRuntimeConfig>): RuntimeAuthVariables {\n const runtimeConfig = readRuntimeConfig(config);\n if (!runtimeConfig || !hasAuthVariables(runtimeConfig.auth)) {\n throw new Error(\"Missing auth runtime configuration\");\n }\n return runtimeConfig.auth.variables;\n}\n\nfunction getSiwnClientConfig(options: CreateAuthClientOptions): SiwnClientConfig {\n const runtimeConfig = readRuntimeConfig(options.runtimeConfig);\n const variables = getAuthVariables(options.runtimeConfig);\n const siwn = variables.siwn;\n\n const mainnetRecipient = siwn.recipients?.mainnet ?? siwn.recipient;\n if (!mainnetRecipient) {\n throw new Error(\"Missing auth SIWN recipient\");\n }\n\n const networkId =\n runtimeConfig?.networkId ?? (mainnetRecipient.endsWith(\".testnet\") ? \"testnet\" : \"mainnet\");\n const testnetRecipient = siwn.recipients?.testnet;\n\n if (testnetRecipient) {\n return {\n recipients: { mainnet: mainnetRecipient, testnet: testnetRecipient },\n networkId,\n cspNonce: options.cspNonce,\n };\n }\n\n return { recipient: mainnetRecipient, networkId, cspNonce: options.cspNonce };\n}\n\nfunction getHostUrl(config?: Partial<ClientRuntimeConfig>) {\n const runtimeConfig = readRuntimeConfig(config);\n if (runtimeConfig?.hostUrl) return runtimeConfig.hostUrl;\n if (typeof window !== \"undefined\") return window.location.origin;\n return \"\";\n}\n\nexport function getDeviceLinkClientId(config?: Partial<ClientRuntimeConfig>): string {\n const runtimeConfig = readRuntimeConfig(config);\n const variables = runtimeConfig?.auth?.variables as RuntimeAuthVariables | undefined;\n return variables?.deviceLink?.clientId ?? DEFAULT_DEVICE_LINK_CLIENT_ID;\n}\n\nexport function createAuthClient(options: CreateAuthClientOptions = {}) {\n const nearAuthConfig = getSiwnClientConfig(options);\n\n return createBetterAuthClient({\n baseURL: getHostUrl(options.runtimeConfig),\n fetchOptions: {\n credentials: \"include\",\n ...(options.headers ? { headers: options.headers } : {}),\n },\n plugins: [\n inferAdditionalFields({\n user: {\n locale: { type: \"string\", required: false, input: true },\n },\n }),\n siwnClient(nearAuthConfig),\n adminClient(),\n anonymousClient(),\n phoneNumberClient(),\n passkeyClient(),\n organizationClient({ teams: { enabled: true } }),\n apiKeyClient(),\n deviceAuthorizationClient(),\n ],\n });\n}\n\nexport type { AuthContext } from \"./auth-guards\";\nexport {\n clearAuthenticatedQueries,\n requireAdmin,\n requireSession,\n} from \"./auth-guards\";\nexport { pluginHref, pluginPath, pluginSearch } from \"./plugin-path\";\n\nexport type AuthClient = ReturnType<typeof createAuthClient>;\n\nexport type SessionData = AuthClient[\"$Infer\"][\"Session\"];\n\n/**\n * Explicit shapes for the organization/passkey records — better-auth's client\n * inference collapses to `any` for these plugin endpoints, which would leak\n * `any` into every consumer union.\n */\nexport interface Organization {\n id: string;\n name: string;\n slug?: string | null;\n logo?: string | null;\n createdAt?: Date | string | null;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface Passkey {\n id: string;\n name?: string | null;\n createdAt?: Date | string | null;\n lastUsedAt?: Date | string | null;\n}\n\nexport function useAuthClient(): AuthClient {\n return useRouter().options.context.authClient as AuthClient;\n}\n\nexport const sessionQueryKey = [\"session\"] as const;\n\nexport function sessionQueryOptions(authClient: AuthClient) {\n return {\n queryKey: sessionQueryKey,\n queryFn: async () => {\n const { data: session, error } = await authClient.getSession({\n query: { disableCookieCache: true },\n });\n if (error) throw new Error(error.message || \"Unable to load your session\");\n return session ?? null;\n },\n staleTime: 60 * 1000,\n gcTime: 10 * 60 * 1000,\n };\n}\n\nexport async function refreshSessionCache(\n authClient: AuthClient,\n queryClient: QueryClient,\n): Promise<SessionData | null> {\n return queryClient.fetchQuery({ ...sessionQueryOptions(authClient), staleTime: 0 });\n}\n\n/**\n * Resolves the session for the root route: the query cache wins (it is kept\n * authoritative by the single sessionQueryOptions read path), and a populated\n * router-context session — the host's SSR resolution — seeds an empty cache so\n * the dehydrated state carries it to the client.\n */\nexport function resolveSessionFromCache<T>(\n queryClient: QueryClient | undefined,\n contextSession: T | null | undefined,\n): T | null | undefined {\n if (!queryClient) return contextSession;\n\n const cachedSession = queryClient.getQueryData<T | null>(sessionQueryKey);\n if (cachedSession !== undefined) return cachedSession;\n\n if (contextSession !== undefined) {\n queryClient.setQueryData(sessionQueryKey, contextSession);\n }\n return contextSession;\n}\n\nexport function useRelayHistory(session: SessionData | null | undefined, authClient: AuthClient) {\n return useQuery({\n queryKey: [\"relay-history\"],\n queryFn: async (): Promise<RelayedTransactionT[]> => {\n const res = await authClient.near.relayHistory();\n return res?.data?.transactions ?? [];\n },\n enabled: !!session,\n refetchInterval: 2000,\n });\n}\n\nexport type PasskeyWalletStatus = PasskeyWalletLink;\n\nexport type PasskeyCeremonyError = Error & { code?: string };\n\nconst UNSUPPORTED_AUTHENTICATOR_CODES = new Set([\n \"PASSKEY_UNSUPPORTED_AUTHENTICATOR\",\n \"ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG\",\n \"ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT\",\n \"ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT\",\n]);\n\nexport function isUnsupportedAuthenticatorError(error: unknown): boolean {\n if (!(error instanceof Error) || !(\"code\" in error)) return false;\n return typeof error.code === \"string\" && UNSUPPORTED_AUTHENTICATOR_CODES.has(error.code);\n}\n\nfunction toPasskeyError(\n error: { code?: string; message?: string } | null | undefined,\n fallback: string,\n): PasskeyCeremonyError {\n return Object.assign(new Error(error?.message || fallback), { code: error?.code });\n}\n\nexport async function isPasskeyAutofillAvailable(): Promise<boolean> {\n if (typeof window === \"undefined\" || !window.PublicKeyCredential) return false;\n try {\n return (await window.PublicKeyCredential.isConditionalMediationAvailable?.()) === true;\n } catch {\n return false;\n }\n}\n\nexport async function createAccountWithPasskey(\n authClient: AuthClient,\n options?: {\n onSuccess?: (result: { passkeyWallet?: PasskeyWalletStatus }) => void;\n onError?: (error: PasskeyCeremonyError) => void;\n },\n): Promise<void> {\n try {\n const { data, error } = await authClient.passkey.addPasskey();\n if (error || !data) {\n options?.onError?.(toPasskeyError(error, \"Could not create your account\"));\n return;\n }\n const { passkeyWallet } = data as { passkeyWallet?: PasskeyWalletStatus };\n options?.onSuccess?.({ passkeyWallet });\n } catch (error) {\n options?.onError?.(\n toPasskeyError(error instanceof Error ? error : null, \"Could not create your account\"),\n );\n }\n}\n\nexport async function signInWithPasskey(\n authClient: AuthClient,\n options?: {\n autoFill?: boolean;\n onSuccess?: () => void;\n onError?: (error: PasskeyCeremonyError) => void;\n },\n): Promise<void> {\n try {\n const { data, error } = await authClient.signIn.passkey({ autoFill: options?.autoFill });\n if (error || !data) {\n options?.onError?.(toPasskeyError(error, \"Passkey sign-in failed\"));\n return;\n }\n options?.onSuccess?.();\n } catch (error) {\n options?.onError?.(\n toPasskeyError(error instanceof Error ? error : null, \"Passkey sign-in failed\"),\n );\n }\n}\n"],"mappings":"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"}
|
package/dist/ui/hydrate.cjs
CHANGED
|
@@ -180,12 +180,12 @@ async function hydrate(options) {
|
|
|
180
180
|
const cspNonce = require_ui_runtime.getCspNonce();
|
|
181
181
|
if (!runtimeConfig.hostUrl || !runtimeConfig.rpcBase) throw new Error("Missing hostUrl or rpcBase in runtime config");
|
|
182
182
|
const [{ QueryClientProvider }, routerFactory] = await Promise.all([import("@tanstack/react-query"), options.createRouter ? Promise.resolve(options.createRouter) : Promise.resolve().then(() => require("./router-client.cjs")).then((m) => m.createRouter)]);
|
|
183
|
-
const
|
|
183
|
+
const coreRouteConfig = await options.routeConfig().then((mod) => "default" in mod ? mod.default : mod);
|
|
184
184
|
const coreManifest = options.manifest ? await options.manifest().then((raw) => typeof raw === "object" && raw !== null && "default" in raw ? raw.default : raw).then((raw) => (0, require_ui_manifest_index.manifest_exports.parsePluginManifest)(raw)) : void 0;
|
|
185
185
|
const client = options.createQueryClient?.() ?? require_ui_router_defaults.defaultQueryClient();
|
|
186
186
|
mark(`$_TSR present: ${Boolean(window.$_TSR)}`);
|
|
187
187
|
const composed = await composeFromPayload(runtimeConfig, coreRouteConfig, coreManifest);
|
|
188
|
-
const { router, queryClient: builtClient } =
|
|
188
|
+
const { router, queryClient: builtClient } = routerFactory({
|
|
189
189
|
routeTree: composed?.routeTree,
|
|
190
190
|
context: {
|
|
191
191
|
pluginNav: composed?.nav,
|
package/dist/ui/hydrate.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.cjs","names":["getRuntimeConfig","getCspNonce","defaultQueryClient","createApiClient","createAuthClient"],"sources":["../../src/ui/hydrate.tsx"],"sourcesContent":["/**\n * Client bootstrap — creates browser-side QueryClient, Router, and auth/API\n * clients; composes the route tree from the runtime-config payload before\n * hydration (payload -> registerRemotes -> loadRemote route configs ->\n * constructTree -> hydrateRoot(RouterClient)). Called from the host-rendered\n * HTML shell via the app's thin entry/hydrate stubs. The build's MF runtime\n * owns the page's share scope — the global registerRemotes/loadRemote API\n * bridges it (one React/router across the core client and plugin containers\n * by share-scope negotiation).\n */\n\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { ClientRuntimeConfig } from \"../types\";\nimport { type ClientServiceConfig, createApiClient } from \"./api\";\nimport { createAuthClient } from \"./auth\";\nimport {\n CORE_UI_PLUGIN_KEY,\n ComposePayloadSchema,\n constructTree,\n type NavManifest,\n type PluginManifest,\n parsePluginManifest,\n type RouteConfigModule,\n} from \"./manifest\";\nimport { defaultQueryClient } from \"./router-defaults\";\nimport { getCspNonce, getRuntimeConfig } from \"./runtime\";\nimport type { AppRouterFactory } from \"./types\";\n\ndeclare global {\n interface Window {\n __EVERYTHING_DEV_HYDRATE_PROMISE__?: Promise<void>;\n __EVERYTHING_DEV_SSR__?: boolean;\n __CLIENT_PROGRESS__?: string[];\n $_TSR?: unknown;\n }\n}\n\nconst mark = (message: string) => {\n const progress = window.__CLIENT_PROGRESS__ ?? [];\n window.__CLIENT_PROGRESS__ = [...progress, message];\n if (import.meta.env.DEV) console.log(`[Hydrate] ${message}`);\n};\n\nfunction isAbsoluteHttpUrl(value: string | undefined): value is string {\n if (!value) return false;\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport type { AppRouterFactory } from \"./types\";\n\nexport interface CoreHydrateOptions {\n /**\n * Loads the app's generated core route config — the only app-specific\n * input; clients, compose machinery, and the router come from the package.\n */\n routeConfig: () => Promise<RouteConfigModule | { default: RouteConfigModule }>;\n /**\n * Loads the app's generated core manifest — the degradation target when the\n * runtime config carries no (or no usable) compose payload: a plugin-free\n * deployment has no payload at all, yet the core-only tree is still the\n * tree. Absent, a payload-less page cannot construct a tree. The value is\n * parsed through the manifest contract at the boundary (a skewed version\n * or shape fails loudly here).\n */\n manifest?: () => Promise<unknown>;\n /**\n * The app's authored router factory — notFound/pending/error components,\n * scroll behavior, and router defaults live there. Absent, the framework's\n * own factory runs.\n */\n createRouter?: AppRouterFactory;\n /** The app's query client factory (staleTime, gcTime, …). Absent, the framework default. */\n createQueryClient?: () => QueryClient;\n /** Overrides the runtime config source (tests, embeds). */\n config?: ClientRuntimeConfig;\n /** Localized copy for the API connection-failure toast (see `createApiClient`). */\n apiConnectionError?: ClientServiceConfig[\"connectionError\"];\n}\n\ninterface ComposedTree {\n routeTree: unknown;\n nav: NavManifest;\n /** `true` when plugin composition failed and only the core tree is usable. */\n degraded: boolean;\n}\n\n/**\n * Manifest composition: reconstruct the server's tree from the payload — the\n * same manifests, the same MF names, the same construction code. Digest\n * parity proves the SSR'd tree and this tree are identical before hydrate;\n * any failure degrades to the core-only tree (still a real route tree — the\n * page must render, just without plugin routes) rather than leaving the\n * router without a tree.\n */\nasync function composeFromPayload(\n runtimeConfig: ClientRuntimeConfig,\n coreRouteConfig: RouteConfigModule,\n coreManifest: PluginManifest | undefined,\n): Promise<ComposedTree | undefined> {\n const coreUiName = runtimeConfig.ui?.name ?? CORE_UI_PLUGIN_KEY;\n\n // The core-only tree is the degradation target for every failure below —\n // the payload's core manifest plus the app's own route config, no remotes.\n const composeCoreOnly = async (\n candidateManifests: PluginManifest[],\n ): Promise<ComposedTree | undefined> => {\n try {\n const core = candidateManifests.find((m) => m.name === CORE_UI_PLUGIN_KEY);\n if (!core) return undefined;\n const tree = await constructTree({\n name: \"core-fallback\",\n plugins: [{ key: core.name, mfName: coreUiName }],\n resolve: async (ref) => ({\n key: ref.key,\n manifest: core,\n routeConfig: coreRouteConfig,\n }),\n rootOptions: coreRouteConfig.rootMeta,\n });\n mark(\n `core-only tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`,\n );\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: true };\n } catch (error) {\n console.error(\"[Hydrate] Core-only fallback failed:\", error);\n mark(`CORE-ONLY FALLBACK ERROR: ${(error as Error).message}`);\n return undefined;\n }\n };\n\n const payload = runtimeConfig.ui?.compose;\n if (!payload?.remotes) {\n // No payload at all — a plugin-free deployment. The core-only tree IS\n // the tree; without the app manifest there is nothing to construct.\n return coreManifest ? composeCoreOnly([coreManifest]) : undefined;\n }\n\n const parsed = ComposePayloadSchema.safeParse(payload);\n if (!parsed.success) {\n mark(`compose payload malformed: ${parsed.error.message}`);\n return coreManifest ? composeCoreOnly([coreManifest]) : undefined;\n }\n const { manifests } = parsed.data;\n const manifestByKey = new Map(manifests.map((m) => [m.name, m]));\n\n try {\n const { registerRemotes, loadRemote } = await import(\"@module-federation/enhanced/runtime\");\n\n mark(`compose payload: digest ${payload.digest}, ${payload.remotes.length} remote(s)`);\n\n registerRemotes(\n payload.remotes.map((remote) => ({\n name: remote.name,\n alias: remote.name,\n // Manifest-driven registration: the manifest carries the remote's\n // true container identity (its build-time package name), which the\n // plain remoteEntry URL cannot resolve — the entry script's global\n // name doesn't match the registered name. Versioned deploys carry\n // the (hashed) manifest URL in the payload; legacy payloads derive\n // it by stripping the fixed entry name. Only absolute URLs qualify —\n // a relative manifestUrl would resolve against the page origin and\n // register the wrong container.\n entry: isAbsoluteHttpUrl(remote.manifestUrl)\n ? remote.manifestUrl\n : remote.entry.replace(/\\/?remoteEntry\\.js$/, \"/mf-manifest.json\"),\n })),\n );\n for (const remote of payload.remotes) {\n mark(`remote registered ${remote.name} @ ${remote.entry}`);\n }\n\n // Per-source isolation (ADR 0024 §5): a remote whose route config fails\n // to load drops only its own routes. The dropped set degrades the compose\n // (client-render — the SSR'd tree included it), never the whole page.\n const loadedRouteConfigs = new Map<string, RouteConfigModule>();\n const dropped: string[] = [];\n const healthyManifests: PluginManifest[] = [];\n for (const manifest of manifests) {\n if (manifest.name === CORE_UI_PLUGIN_KEY) {\n healthyManifests.push(manifest);\n continue;\n }\n const remote = payload.remotes.find((r) => r.key === manifest.name);\n if (!remote) {\n dropped.push(manifest.name);\n mark(`dropped ${manifest.name} — payload has no matching remote`);\n continue;\n }\n try {\n const mod = await loadRemote(`${remote.name}/routeConfig`, { from: \"build\" });\n const routeConfig = ((mod as { default?: RouteConfigModule })?.default ??\n mod) as RouteConfigModule;\n if (!routeConfig?.routeConfigLoaders) {\n throw new Error(`loadRemote(${remote.name}/routeConfig) returned no route config`);\n }\n loadedRouteConfigs.set(manifest.name, routeConfig);\n healthyManifests.push(manifest);\n mark(`loaded ${remote.name}/routeConfig`);\n } catch (error) {\n dropped.push(manifest.name);\n mark(`dropped ${manifest.name} — ${(error as Error).message}`);\n }\n }\n\n const tree = await constructTree({\n name: \"client\",\n plugins: healthyManifests.map((manifest) => {\n const remote = payload.remotes.find((r) => r.key === manifest.name);\n return {\n key: manifest.name,\n mfName:\n remote?.name ?? (manifest.name === CORE_UI_PLUGIN_KEY ? coreUiName : manifest.name),\n };\n }),\n resolve: async (ref) => {\n if (ref.key === CORE_UI_PLUGIN_KEY) {\n const manifest = manifestByKey.get(ref.key);\n if (!manifest) throw new Error(`compose payload has no manifest for \"${ref.key}\"`);\n return { key: ref.key, manifest, routeConfig: coreRouteConfig };\n }\n const manifest = manifestByKey.get(ref.key);\n const routeConfig = loadedRouteConfigs.get(ref.key);\n if (!manifest || !routeConfig) {\n throw new Error(`composition source \"${ref.key}\" is not fully resolved`);\n }\n return { key: ref.key, manifest, routeConfig };\n },\n rootOptions: coreRouteConfig.rootMeta,\n });\n\n if (dropped.length > 0) {\n mark(`degraded compose: ${dropped.join(\", \")} dropped — client-render`);\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: true };\n }\n\n if (tree.digest !== payload.digest) {\n console.warn(\n `[Hydrate] Compose digest mismatch (client ${tree.digest} vs server ${payload.digest}); core-only fallback`,\n );\n mark(`DIGEST PARITY FAILURE server=${payload.digest} client=${tree.digest}`);\n return composeCoreOnly(manifests);\n }\n\n mark(\n `tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`,\n );\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: false };\n } catch (error) {\n console.error(\"[Hydrate] Client compose failed; core-only fallback:\", error);\n mark(`CLIENT COMPOSE ERROR: ${(error as Error).message}`);\n return composeCoreOnly(manifests);\n }\n}\n\nfunction isServerRendered(): boolean {\n if (document.documentElement.hasAttribute(\"data-everything-ssr\")) {\n return true;\n }\n if (window.__EVERYTHING_DEV_SSR__ !== undefined) {\n return window.__EVERYTHING_DEV_SSR__;\n }\n return window.$_TSR !== undefined;\n}\n\nfunction watchHydrationConsumed() {\n const bootstrapWatch = setInterval(() => {\n if (window.$_TSR === undefined) {\n clearInterval(bootstrapWatch);\n mark(\"hydration consumed ($_TSR deleted — h() ran)\");\n }\n }, 50);\n}\n\nexport async function hydrate(options: CoreHydrateOptions) {\n if (window.__EVERYTHING_DEV_HYDRATE_PROMISE__) {\n return window.__EVERYTHING_DEV_HYDRATE_PROMISE__;\n }\n\n const hydratePromise = (async () => {\n console.log(\"[Hydrate] Starting...\");\n\n const runtimeConfig = options.config ?? getRuntimeConfig();\n const cspNonce = getCspNonce();\n\n if (!runtimeConfig.hostUrl || !runtimeConfig.rpcBase) {\n throw new Error(\"Missing hostUrl or rpcBase in runtime config\");\n }\n\n const [{ QueryClientProvider }, routerFactory] = await Promise.all([\n import(\"@tanstack/react-query\"),\n options.createRouter\n ? Promise.resolve(options.createRouter)\n : import(\"./router-client\").then((m) => m.createRouter as AppRouterFactory),\n ]);\n const [coreRouteConfig] = await Promise.all([\n options.routeConfig().then((mod) => (\"default\" in mod ? mod.default : mod)),\n ]);\n const coreManifest = options.manifest\n ? await options\n .manifest()\n .then((raw) =>\n typeof raw === \"object\" && raw !== null && \"default\" in raw\n ? (raw as { default: unknown }).default\n : raw,\n )\n .then((raw) => parsePluginManifest(raw))\n : undefined;\n const client = options.createQueryClient?.() ?? defaultQueryClient();\n\n mark(`$_TSR present: ${Boolean(window.$_TSR)}`);\n\n const composed = await composeFromPayload(runtimeConfig, coreRouteConfig, coreManifest);\n\n const { router, queryClient: builtClient } = await routerFactory({\n routeTree: composed?.routeTree,\n context: {\n pluginNav: composed?.nav,\n queryClient: client,\n runtimeConfig,\n cspNonce,\n locale: isServerRendered() ? document.documentElement.lang : undefined,\n apiClient: createApiClient({\n hostUrl: runtimeConfig.hostUrl,\n rpcBase: runtimeConfig.rpcBase,\n connectionError: options.apiConnectionError,\n }),\n authClient: createAuthClient({ runtimeConfig, cspNonce }),\n },\n });\n const providerClient = builtClient ?? client;\n\n // A server-rendered page whose compose degraded to core-only would\n // hydrate a DIFFERENT tree than the HTML contains — a guaranteed React\n // hydration failure. Client-render cleanly instead; hydration is only\n // safe when the composed tree is the one the server rendered.\n const canHydrate = isServerRendered() && Boolean(composed) && !composed!.degraded;\n\n if (canHydrate) {\n const { hydrateRoot } = await import(\"react-dom/client\");\n const { RouterClient } = await import(\"@tanstack/react-router/ssr/client\");\n\n console.log(\"[Hydrate] Calling hydrateRoot...\");\n hydrateRoot(\n document,\n <QueryClientProvider client={providerClient}>\n <RouterClient router={router} />\n </QueryClientProvider>,\n );\n watchHydrationConsumed();\n } else {\n const { createRoot } = await import(\"react-dom/client\");\n const { RouterProvider } = await import(\"@tanstack/react-router\");\n\n console.log(\"[Hydrate] Calling createRoot...\");\n createRoot(document).render(\n <QueryClientProvider client={providerClient}>\n <RouterProvider router={router} />\n </QueryClientProvider>,\n );\n }\n\n if (composed?.degraded) {\n const { toast } = await import(\"sonner\");\n toast.error(\"Some application features couldn't load\", {\n id: \"application-compose-error\",\n description: \"Plugin pages are unavailable. Reload to try again.\",\n duration: Number.POSITIVE_INFINITY,\n action: { label: \"Reload\", onClick: () => window.location.reload() },\n });\n }\n\n console.log(\"[Hydrate] Complete!\");\n })().catch((error) => {\n console.error(\"[Hydrate] Failed:\", error);\n window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = undefined;\n const message = document.createElement(\"main\");\n message.setAttribute(\"role\", \"alert\");\n message.dataset.testid = \"application-startup-error\";\n message.className =\n \"min-h-screen flex flex-col items-center justify-center gap-4 bg-background text-foreground p-6\";\n const heading = document.createElement(\"h1\");\n heading.textContent = \"The application couldn't load\";\n const detail = document.createElement(\"p\");\n detail.textContent = \"Check your connection and reload to try again.\";\n const retry = document.createElement(\"button\");\n retry.textContent = \"Reload\";\n retry.className = \"rounded-md border border-border px-4 py-2\";\n retry.addEventListener(\"click\", () => window.location.reload());\n message.append(heading, detail, retry);\n document.body.replaceChildren(message);\n throw error;\n });\n\n window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = hydratePromise;\n return hydratePromise;\n}\n\nexport default hydrate;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hydrate.cjs","names":["getRuntimeConfig","getCspNonce","defaultQueryClient","createApiClient","createAuthClient"],"sources":["../../src/ui/hydrate.tsx"],"sourcesContent":["/**\n * Client bootstrap — creates browser-side QueryClient, Router, and auth/API\n * clients; composes the route tree from the runtime-config payload before\n * hydration (payload -> registerRemotes -> loadRemote route configs ->\n * constructTree -> hydrateRoot(RouterClient)). Called from the host-rendered\n * HTML shell via the app's thin entry/hydrate stubs. The build's MF runtime\n * owns the page's share scope — the global registerRemotes/loadRemote API\n * bridges it (one React/router across the core client and plugin containers\n * by share-scope negotiation).\n */\n\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { ClientRuntimeConfig } from \"../types\";\nimport { type ClientServiceConfig, createApiClient } from \"./api\";\nimport { createAuthClient } from \"./auth\";\nimport {\n CORE_UI_PLUGIN_KEY,\n ComposePayloadSchema,\n constructTree,\n type NavManifest,\n type PluginManifest,\n parsePluginManifest,\n type RouteConfigModule,\n} from \"./manifest\";\nimport { defaultQueryClient } from \"./router-defaults\";\nimport { getCspNonce, getRuntimeConfig } from \"./runtime\";\nimport type { AppRouterFactory } from \"./types\";\n\ndeclare global {\n interface Window {\n __EVERYTHING_DEV_HYDRATE_PROMISE__?: Promise<void>;\n __EVERYTHING_DEV_SSR__?: boolean;\n __CLIENT_PROGRESS__?: string[];\n $_TSR?: unknown;\n }\n}\n\nconst mark = (message: string) => {\n const progress = window.__CLIENT_PROGRESS__ ?? [];\n window.__CLIENT_PROGRESS__ = [...progress, message];\n if (import.meta.env.DEV) console.log(`[Hydrate] ${message}`);\n};\n\nfunction isAbsoluteHttpUrl(value: string | undefined): value is string {\n if (!value) return false;\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport type { AppRouterFactory } from \"./types\";\n\nexport interface CoreHydrateOptions {\n /**\n * Loads the app's generated core route config — the only app-specific\n * input; clients, compose machinery, and the router come from the package.\n */\n routeConfig: () => Promise<RouteConfigModule | { default: RouteConfigModule }>;\n /**\n * Loads the app's generated core manifest — the degradation target when the\n * runtime config carries no (or no usable) compose payload: a plugin-free\n * deployment has no payload at all, yet the core-only tree is still the\n * tree. Absent, a payload-less page cannot construct a tree. The value is\n * parsed through the manifest contract at the boundary (a skewed version\n * or shape fails loudly here).\n */\n manifest?: () => Promise<unknown>;\n /**\n * The app's authored router factory — notFound/pending/error components,\n * scroll behavior, and router defaults live there. Absent, the framework's\n * own factory runs.\n */\n createRouter?: AppRouterFactory;\n /** The app's query client factory (staleTime, gcTime, …). Absent, the framework default. */\n createQueryClient?: () => QueryClient;\n /** Overrides the runtime config source (tests, embeds). */\n config?: ClientRuntimeConfig;\n /** Localized copy for the API connection-failure toast (see `createApiClient`). */\n apiConnectionError?: ClientServiceConfig[\"connectionError\"];\n}\n\ninterface ComposedTree {\n routeTree: unknown;\n nav: NavManifest;\n /** `true` when plugin composition failed and only the core tree is usable. */\n degraded: boolean;\n}\n\n/**\n * Manifest composition: reconstruct the server's tree from the payload — the\n * same manifests, the same MF names, the same construction code. Digest\n * parity proves the SSR'd tree and this tree are identical before hydrate;\n * any failure degrades to the core-only tree (still a real route tree — the\n * page must render, just without plugin routes) rather than leaving the\n * router without a tree.\n */\nasync function composeFromPayload(\n runtimeConfig: ClientRuntimeConfig,\n coreRouteConfig: RouteConfigModule,\n coreManifest: PluginManifest | undefined,\n): Promise<ComposedTree | undefined> {\n const coreUiName = runtimeConfig.ui?.name ?? CORE_UI_PLUGIN_KEY;\n\n // The core-only tree is the degradation target for every failure below —\n // the payload's core manifest plus the app's own route config, no remotes.\n const composeCoreOnly = async (\n candidateManifests: PluginManifest[],\n ): Promise<ComposedTree | undefined> => {\n try {\n const core = candidateManifests.find((m) => m.name === CORE_UI_PLUGIN_KEY);\n if (!core) return undefined;\n const tree = await constructTree({\n name: \"core-fallback\",\n plugins: [{ key: core.name, mfName: coreUiName }],\n resolve: async (ref) => ({\n key: ref.key,\n manifest: core,\n routeConfig: coreRouteConfig,\n }),\n rootOptions: coreRouteConfig.rootMeta,\n });\n mark(\n `core-only tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`,\n );\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: true };\n } catch (error) {\n console.error(\"[Hydrate] Core-only fallback failed:\", error);\n mark(`CORE-ONLY FALLBACK ERROR: ${(error as Error).message}`);\n return undefined;\n }\n };\n\n const payload = runtimeConfig.ui?.compose;\n if (!payload?.remotes) {\n // No payload at all — a plugin-free deployment. The core-only tree IS\n // the tree; without the app manifest there is nothing to construct.\n return coreManifest ? composeCoreOnly([coreManifest]) : undefined;\n }\n\n const parsed = ComposePayloadSchema.safeParse(payload);\n if (!parsed.success) {\n mark(`compose payload malformed: ${parsed.error.message}`);\n return coreManifest ? composeCoreOnly([coreManifest]) : undefined;\n }\n const { manifests } = parsed.data;\n const manifestByKey = new Map(manifests.map((m) => [m.name, m]));\n\n try {\n const { registerRemotes, loadRemote } = await import(\"@module-federation/enhanced/runtime\");\n\n mark(`compose payload: digest ${payload.digest}, ${payload.remotes.length} remote(s)`);\n\n registerRemotes(\n payload.remotes.map((remote) => ({\n name: remote.name,\n alias: remote.name,\n // Manifest-driven registration: the manifest carries the remote's\n // true container identity (its build-time package name), which the\n // plain remoteEntry URL cannot resolve — the entry script's global\n // name doesn't match the registered name. Versioned deploys carry\n // the (hashed) manifest URL in the payload; legacy payloads derive\n // it by stripping the fixed entry name. Only absolute URLs qualify —\n // a relative manifestUrl would resolve against the page origin and\n // register the wrong container.\n entry: isAbsoluteHttpUrl(remote.manifestUrl)\n ? remote.manifestUrl\n : remote.entry.replace(/\\/?remoteEntry\\.js$/, \"/mf-manifest.json\"),\n })),\n );\n for (const remote of payload.remotes) {\n mark(`remote registered ${remote.name} @ ${remote.entry}`);\n }\n\n // Per-source isolation (ADR 0024 §5): a remote whose route config fails\n // to load drops only its own routes. The dropped set degrades the compose\n // (client-render — the SSR'd tree included it), never the whole page.\n const loadedRouteConfigs = new Map<string, RouteConfigModule>();\n const dropped: string[] = [];\n const healthyManifests: PluginManifest[] = [];\n for (const manifest of manifests) {\n if (manifest.name === CORE_UI_PLUGIN_KEY) {\n healthyManifests.push(manifest);\n continue;\n }\n const remote = payload.remotes.find((r) => r.key === manifest.name);\n if (!remote) {\n dropped.push(manifest.name);\n mark(`dropped ${manifest.name} — payload has no matching remote`);\n continue;\n }\n try {\n const mod = await loadRemote(`${remote.name}/routeConfig`, { from: \"build\" });\n const routeConfig = ((mod as { default?: RouteConfigModule })?.default ??\n mod) as RouteConfigModule;\n if (!routeConfig?.routeConfigLoaders) {\n throw new Error(`loadRemote(${remote.name}/routeConfig) returned no route config`);\n }\n loadedRouteConfigs.set(manifest.name, routeConfig);\n healthyManifests.push(manifest);\n mark(`loaded ${remote.name}/routeConfig`);\n } catch (error) {\n dropped.push(manifest.name);\n mark(`dropped ${manifest.name} — ${(error as Error).message}`);\n }\n }\n\n const tree = await constructTree({\n name: \"client\",\n plugins: healthyManifests.map((manifest) => {\n const remote = payload.remotes.find((r) => r.key === manifest.name);\n return {\n key: manifest.name,\n mfName:\n remote?.name ?? (manifest.name === CORE_UI_PLUGIN_KEY ? coreUiName : manifest.name),\n };\n }),\n resolve: async (ref) => {\n if (ref.key === CORE_UI_PLUGIN_KEY) {\n const manifest = manifestByKey.get(ref.key);\n if (!manifest) throw new Error(`compose payload has no manifest for \"${ref.key}\"`);\n return { key: ref.key, manifest, routeConfig: coreRouteConfig };\n }\n const manifest = manifestByKey.get(ref.key);\n const routeConfig = loadedRouteConfigs.get(ref.key);\n if (!manifest || !routeConfig) {\n throw new Error(`composition source \"${ref.key}\" is not fully resolved`);\n }\n return { key: ref.key, manifest, routeConfig };\n },\n rootOptions: coreRouteConfig.rootMeta,\n });\n\n if (dropped.length > 0) {\n mark(`degraded compose: ${dropped.join(\", \")} dropped — client-render`);\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: true };\n }\n\n if (tree.digest !== payload.digest) {\n console.warn(\n `[Hydrate] Compose digest mismatch (client ${tree.digest} vs server ${payload.digest}); core-only fallback`,\n );\n mark(`DIGEST PARITY FAILURE server=${payload.digest} client=${tree.digest}`);\n return composeCoreOnly(manifests);\n }\n\n mark(\n `tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`,\n );\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: false };\n } catch (error) {\n console.error(\"[Hydrate] Client compose failed; core-only fallback:\", error);\n mark(`CLIENT COMPOSE ERROR: ${(error as Error).message}`);\n return composeCoreOnly(manifests);\n }\n}\n\nfunction isServerRendered(): boolean {\n if (document.documentElement.hasAttribute(\"data-everything-ssr\")) {\n return true;\n }\n if (window.__EVERYTHING_DEV_SSR__ !== undefined) {\n return window.__EVERYTHING_DEV_SSR__;\n }\n return window.$_TSR !== undefined;\n}\n\nfunction watchHydrationConsumed() {\n const bootstrapWatch = setInterval(() => {\n if (window.$_TSR === undefined) {\n clearInterval(bootstrapWatch);\n mark(\"hydration consumed ($_TSR deleted — h() ran)\");\n }\n }, 50);\n}\n\nexport async function hydrate(options: CoreHydrateOptions) {\n if (window.__EVERYTHING_DEV_HYDRATE_PROMISE__) {\n return window.__EVERYTHING_DEV_HYDRATE_PROMISE__;\n }\n\n const hydratePromise = (async () => {\n console.log(\"[Hydrate] Starting...\");\n\n const runtimeConfig = options.config ?? getRuntimeConfig();\n const cspNonce = getCspNonce();\n\n if (!runtimeConfig.hostUrl || !runtimeConfig.rpcBase) {\n throw new Error(\"Missing hostUrl or rpcBase in runtime config\");\n }\n\n const [{ QueryClientProvider }, routerFactory] = await Promise.all([\n import(\"@tanstack/react-query\"),\n options.createRouter\n ? Promise.resolve(options.createRouter)\n : import(\"./router-client\").then((m) => m.createRouter as AppRouterFactory),\n ]);\n const coreRouteConfig = await options\n .routeConfig()\n .then((mod) => (\"default\" in mod ? mod.default : mod));\n const coreManifest = options.manifest\n ? await options\n .manifest()\n .then((raw) =>\n typeof raw === \"object\" && raw !== null && \"default\" in raw\n ? (raw as { default: unknown }).default\n : raw,\n )\n .then((raw) => parsePluginManifest(raw))\n : undefined;\n const client = options.createQueryClient?.() ?? defaultQueryClient();\n\n mark(`$_TSR present: ${Boolean(window.$_TSR)}`);\n\n const composed = await composeFromPayload(runtimeConfig, coreRouteConfig, coreManifest);\n\n const { router, queryClient: builtClient } = routerFactory({\n routeTree: composed?.routeTree,\n context: {\n pluginNav: composed?.nav,\n queryClient: client,\n runtimeConfig,\n cspNonce,\n locale: isServerRendered() ? document.documentElement.lang : undefined,\n apiClient: createApiClient({\n hostUrl: runtimeConfig.hostUrl,\n rpcBase: runtimeConfig.rpcBase,\n connectionError: options.apiConnectionError,\n }),\n authClient: createAuthClient({ runtimeConfig, cspNonce }),\n },\n });\n const providerClient = builtClient ?? client;\n\n // A server-rendered page whose compose degraded to core-only would\n // hydrate a DIFFERENT tree than the HTML contains — a guaranteed React\n // hydration failure. Client-render cleanly instead; hydration is only\n // safe when the composed tree is the one the server rendered.\n const canHydrate = isServerRendered() && Boolean(composed) && !composed!.degraded;\n\n if (canHydrate) {\n const { hydrateRoot } = await import(\"react-dom/client\");\n const { RouterClient } = await import(\"@tanstack/react-router/ssr/client\");\n\n console.log(\"[Hydrate] Calling hydrateRoot...\");\n hydrateRoot(\n document,\n <QueryClientProvider client={providerClient}>\n <RouterClient router={router} />\n </QueryClientProvider>,\n );\n watchHydrationConsumed();\n } else {\n const { createRoot } = await import(\"react-dom/client\");\n const { RouterProvider } = await import(\"@tanstack/react-router\");\n\n console.log(\"[Hydrate] Calling createRoot...\");\n createRoot(document).render(\n <QueryClientProvider client={providerClient}>\n <RouterProvider router={router} />\n </QueryClientProvider>,\n );\n }\n\n if (composed?.degraded) {\n const { toast } = await import(\"sonner\");\n toast.error(\"Some application features couldn't load\", {\n id: \"application-compose-error\",\n description: \"Plugin pages are unavailable. Reload to try again.\",\n duration: Number.POSITIVE_INFINITY,\n action: { label: \"Reload\", onClick: () => window.location.reload() },\n });\n }\n\n console.log(\"[Hydrate] Complete!\");\n })().catch((error) => {\n console.error(\"[Hydrate] Failed:\", error);\n window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = undefined;\n const message = document.createElement(\"main\");\n message.setAttribute(\"role\", \"alert\");\n message.dataset.testid = \"application-startup-error\";\n message.className =\n \"min-h-screen flex flex-col items-center justify-center gap-4 bg-background text-foreground p-6\";\n const heading = document.createElement(\"h1\");\n heading.textContent = \"The application couldn't load\";\n const detail = document.createElement(\"p\");\n detail.textContent = \"Check your connection and reload to try again.\";\n const retry = document.createElement(\"button\");\n retry.textContent = \"Reload\";\n retry.className = \"rounded-md border border-border px-4 py-2\";\n retry.addEventListener(\"click\", () => window.location.reload());\n message.append(heading, detail, retry);\n document.body.replaceChildren(message);\n throw error;\n });\n\n window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = hydratePromise;\n return hydratePromise;\n}\n\nexport default hydrate;\n"],"mappings":"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"}
|
package/dist/ui/hydrate.mjs
CHANGED
|
@@ -179,12 +179,12 @@ async function hydrate(options) {
|
|
|
179
179
|
const cspNonce = getCspNonce();
|
|
180
180
|
if (!runtimeConfig.hostUrl || !runtimeConfig.rpcBase) throw new Error("Missing hostUrl or rpcBase in runtime config");
|
|
181
181
|
const [{ QueryClientProvider }, routerFactory] = await Promise.all([import("@tanstack/react-query"), options.createRouter ? Promise.resolve(options.createRouter) : import("./router-client.mjs").then((m) => m.createRouter)]);
|
|
182
|
-
const
|
|
182
|
+
const coreRouteConfig = await options.routeConfig().then((mod) => "default" in mod ? mod.default : mod);
|
|
183
183
|
const coreManifest = options.manifest ? await options.manifest().then((raw) => typeof raw === "object" && raw !== null && "default" in raw ? raw.default : raw).then((raw) => (0, manifest_exports.parsePluginManifest)(raw)) : void 0;
|
|
184
184
|
const client = options.createQueryClient?.() ?? defaultQueryClient();
|
|
185
185
|
mark(`$_TSR present: ${Boolean(window.$_TSR)}`);
|
|
186
186
|
const composed = await composeFromPayload(runtimeConfig, coreRouteConfig, coreManifest);
|
|
187
|
-
const { router, queryClient: builtClient } =
|
|
187
|
+
const { router, queryClient: builtClient } = routerFactory({
|
|
188
188
|
routeTree: composed?.routeTree,
|
|
189
189
|
context: {
|
|
190
190
|
pluginNav: composed?.nav,
|
package/dist/ui/hydrate.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.mjs","names":["CORE_UI_PLUGIN_KEY","constructTree","ComposePayloadSchema","parsePluginManifest"],"sources":["../../src/ui/hydrate.tsx"],"sourcesContent":["/**\n * Client bootstrap — creates browser-side QueryClient, Router, and auth/API\n * clients; composes the route tree from the runtime-config payload before\n * hydration (payload -> registerRemotes -> loadRemote route configs ->\n * constructTree -> hydrateRoot(RouterClient)). Called from the host-rendered\n * HTML shell via the app's thin entry/hydrate stubs. The build's MF runtime\n * owns the page's share scope — the global registerRemotes/loadRemote API\n * bridges it (one React/router across the core client and plugin containers\n * by share-scope negotiation).\n */\n\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { ClientRuntimeConfig } from \"../types\";\nimport { type ClientServiceConfig, createApiClient } from \"./api\";\nimport { createAuthClient } from \"./auth\";\nimport {\n CORE_UI_PLUGIN_KEY,\n ComposePayloadSchema,\n constructTree,\n type NavManifest,\n type PluginManifest,\n parsePluginManifest,\n type RouteConfigModule,\n} from \"./manifest\";\nimport { defaultQueryClient } from \"./router-defaults\";\nimport { getCspNonce, getRuntimeConfig } from \"./runtime\";\nimport type { AppRouterFactory } from \"./types\";\n\ndeclare global {\n interface Window {\n __EVERYTHING_DEV_HYDRATE_PROMISE__?: Promise<void>;\n __EVERYTHING_DEV_SSR__?: boolean;\n __CLIENT_PROGRESS__?: string[];\n $_TSR?: unknown;\n }\n}\n\nconst mark = (message: string) => {\n const progress = window.__CLIENT_PROGRESS__ ?? [];\n window.__CLIENT_PROGRESS__ = [...progress, message];\n if (import.meta.env.DEV) console.log(`[Hydrate] ${message}`);\n};\n\nfunction isAbsoluteHttpUrl(value: string | undefined): value is string {\n if (!value) return false;\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport type { AppRouterFactory } from \"./types\";\n\nexport interface CoreHydrateOptions {\n /**\n * Loads the app's generated core route config — the only app-specific\n * input; clients, compose machinery, and the router come from the package.\n */\n routeConfig: () => Promise<RouteConfigModule | { default: RouteConfigModule }>;\n /**\n * Loads the app's generated core manifest — the degradation target when the\n * runtime config carries no (or no usable) compose payload: a plugin-free\n * deployment has no payload at all, yet the core-only tree is still the\n * tree. Absent, a payload-less page cannot construct a tree. The value is\n * parsed through the manifest contract at the boundary (a skewed version\n * or shape fails loudly here).\n */\n manifest?: () => Promise<unknown>;\n /**\n * The app's authored router factory — notFound/pending/error components,\n * scroll behavior, and router defaults live there. Absent, the framework's\n * own factory runs.\n */\n createRouter?: AppRouterFactory;\n /** The app's query client factory (staleTime, gcTime, …). Absent, the framework default. */\n createQueryClient?: () => QueryClient;\n /** Overrides the runtime config source (tests, embeds). */\n config?: ClientRuntimeConfig;\n /** Localized copy for the API connection-failure toast (see `createApiClient`). */\n apiConnectionError?: ClientServiceConfig[\"connectionError\"];\n}\n\ninterface ComposedTree {\n routeTree: unknown;\n nav: NavManifest;\n /** `true` when plugin composition failed and only the core tree is usable. */\n degraded: boolean;\n}\n\n/**\n * Manifest composition: reconstruct the server's tree from the payload — the\n * same manifests, the same MF names, the same construction code. Digest\n * parity proves the SSR'd tree and this tree are identical before hydrate;\n * any failure degrades to the core-only tree (still a real route tree — the\n * page must render, just without plugin routes) rather than leaving the\n * router without a tree.\n */\nasync function composeFromPayload(\n runtimeConfig: ClientRuntimeConfig,\n coreRouteConfig: RouteConfigModule,\n coreManifest: PluginManifest | undefined,\n): Promise<ComposedTree | undefined> {\n const coreUiName = runtimeConfig.ui?.name ?? CORE_UI_PLUGIN_KEY;\n\n // The core-only tree is the degradation target for every failure below —\n // the payload's core manifest plus the app's own route config, no remotes.\n const composeCoreOnly = async (\n candidateManifests: PluginManifest[],\n ): Promise<ComposedTree | undefined> => {\n try {\n const core = candidateManifests.find((m) => m.name === CORE_UI_PLUGIN_KEY);\n if (!core) return undefined;\n const tree = await constructTree({\n name: \"core-fallback\",\n plugins: [{ key: core.name, mfName: coreUiName }],\n resolve: async (ref) => ({\n key: ref.key,\n manifest: core,\n routeConfig: coreRouteConfig,\n }),\n rootOptions: coreRouteConfig.rootMeta,\n });\n mark(\n `core-only tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`,\n );\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: true };\n } catch (error) {\n console.error(\"[Hydrate] Core-only fallback failed:\", error);\n mark(`CORE-ONLY FALLBACK ERROR: ${(error as Error).message}`);\n return undefined;\n }\n };\n\n const payload = runtimeConfig.ui?.compose;\n if (!payload?.remotes) {\n // No payload at all — a plugin-free deployment. The core-only tree IS\n // the tree; without the app manifest there is nothing to construct.\n return coreManifest ? composeCoreOnly([coreManifest]) : undefined;\n }\n\n const parsed = ComposePayloadSchema.safeParse(payload);\n if (!parsed.success) {\n mark(`compose payload malformed: ${parsed.error.message}`);\n return coreManifest ? composeCoreOnly([coreManifest]) : undefined;\n }\n const { manifests } = parsed.data;\n const manifestByKey = new Map(manifests.map((m) => [m.name, m]));\n\n try {\n const { registerRemotes, loadRemote } = await import(\"@module-federation/enhanced/runtime\");\n\n mark(`compose payload: digest ${payload.digest}, ${payload.remotes.length} remote(s)`);\n\n registerRemotes(\n payload.remotes.map((remote) => ({\n name: remote.name,\n alias: remote.name,\n // Manifest-driven registration: the manifest carries the remote's\n // true container identity (its build-time package name), which the\n // plain remoteEntry URL cannot resolve — the entry script's global\n // name doesn't match the registered name. Versioned deploys carry\n // the (hashed) manifest URL in the payload; legacy payloads derive\n // it by stripping the fixed entry name. Only absolute URLs qualify —\n // a relative manifestUrl would resolve against the page origin and\n // register the wrong container.\n entry: isAbsoluteHttpUrl(remote.manifestUrl)\n ? remote.manifestUrl\n : remote.entry.replace(/\\/?remoteEntry\\.js$/, \"/mf-manifest.json\"),\n })),\n );\n for (const remote of payload.remotes) {\n mark(`remote registered ${remote.name} @ ${remote.entry}`);\n }\n\n // Per-source isolation (ADR 0024 §5): a remote whose route config fails\n // to load drops only its own routes. The dropped set degrades the compose\n // (client-render — the SSR'd tree included it), never the whole page.\n const loadedRouteConfigs = new Map<string, RouteConfigModule>();\n const dropped: string[] = [];\n const healthyManifests: PluginManifest[] = [];\n for (const manifest of manifests) {\n if (manifest.name === CORE_UI_PLUGIN_KEY) {\n healthyManifests.push(manifest);\n continue;\n }\n const remote = payload.remotes.find((r) => r.key === manifest.name);\n if (!remote) {\n dropped.push(manifest.name);\n mark(`dropped ${manifest.name} — payload has no matching remote`);\n continue;\n }\n try {\n const mod = await loadRemote(`${remote.name}/routeConfig`, { from: \"build\" });\n const routeConfig = ((mod as { default?: RouteConfigModule })?.default ??\n mod) as RouteConfigModule;\n if (!routeConfig?.routeConfigLoaders) {\n throw new Error(`loadRemote(${remote.name}/routeConfig) returned no route config`);\n }\n loadedRouteConfigs.set(manifest.name, routeConfig);\n healthyManifests.push(manifest);\n mark(`loaded ${remote.name}/routeConfig`);\n } catch (error) {\n dropped.push(manifest.name);\n mark(`dropped ${manifest.name} — ${(error as Error).message}`);\n }\n }\n\n const tree = await constructTree({\n name: \"client\",\n plugins: healthyManifests.map((manifest) => {\n const remote = payload.remotes.find((r) => r.key === manifest.name);\n return {\n key: manifest.name,\n mfName:\n remote?.name ?? (manifest.name === CORE_UI_PLUGIN_KEY ? coreUiName : manifest.name),\n };\n }),\n resolve: async (ref) => {\n if (ref.key === CORE_UI_PLUGIN_KEY) {\n const manifest = manifestByKey.get(ref.key);\n if (!manifest) throw new Error(`compose payload has no manifest for \"${ref.key}\"`);\n return { key: ref.key, manifest, routeConfig: coreRouteConfig };\n }\n const manifest = manifestByKey.get(ref.key);\n const routeConfig = loadedRouteConfigs.get(ref.key);\n if (!manifest || !routeConfig) {\n throw new Error(`composition source \"${ref.key}\" is not fully resolved`);\n }\n return { key: ref.key, manifest, routeConfig };\n },\n rootOptions: coreRouteConfig.rootMeta,\n });\n\n if (dropped.length > 0) {\n mark(`degraded compose: ${dropped.join(\", \")} dropped — client-render`);\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: true };\n }\n\n if (tree.digest !== payload.digest) {\n console.warn(\n `[Hydrate] Compose digest mismatch (client ${tree.digest} vs server ${payload.digest}); core-only fallback`,\n );\n mark(`DIGEST PARITY FAILURE server=${payload.digest} client=${tree.digest}`);\n return composeCoreOnly(manifests);\n }\n\n mark(\n `tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`,\n );\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: false };\n } catch (error) {\n console.error(\"[Hydrate] Client compose failed; core-only fallback:\", error);\n mark(`CLIENT COMPOSE ERROR: ${(error as Error).message}`);\n return composeCoreOnly(manifests);\n }\n}\n\nfunction isServerRendered(): boolean {\n if (document.documentElement.hasAttribute(\"data-everything-ssr\")) {\n return true;\n }\n if (window.__EVERYTHING_DEV_SSR__ !== undefined) {\n return window.__EVERYTHING_DEV_SSR__;\n }\n return window.$_TSR !== undefined;\n}\n\nfunction watchHydrationConsumed() {\n const bootstrapWatch = setInterval(() => {\n if (window.$_TSR === undefined) {\n clearInterval(bootstrapWatch);\n mark(\"hydration consumed ($_TSR deleted — h() ran)\");\n }\n }, 50);\n}\n\nexport async function hydrate(options: CoreHydrateOptions) {\n if (window.__EVERYTHING_DEV_HYDRATE_PROMISE__) {\n return window.__EVERYTHING_DEV_HYDRATE_PROMISE__;\n }\n\n const hydratePromise = (async () => {\n console.log(\"[Hydrate] Starting...\");\n\n const runtimeConfig = options.config ?? getRuntimeConfig();\n const cspNonce = getCspNonce();\n\n if (!runtimeConfig.hostUrl || !runtimeConfig.rpcBase) {\n throw new Error(\"Missing hostUrl or rpcBase in runtime config\");\n }\n\n const [{ QueryClientProvider }, routerFactory] = await Promise.all([\n import(\"@tanstack/react-query\"),\n options.createRouter\n ? Promise.resolve(options.createRouter)\n : import(\"./router-client\").then((m) => m.createRouter as AppRouterFactory),\n ]);\n const [coreRouteConfig] = await Promise.all([\n options.routeConfig().then((mod) => (\"default\" in mod ? mod.default : mod)),\n ]);\n const coreManifest = options.manifest\n ? await options\n .manifest()\n .then((raw) =>\n typeof raw === \"object\" && raw !== null && \"default\" in raw\n ? (raw as { default: unknown }).default\n : raw,\n )\n .then((raw) => parsePluginManifest(raw))\n : undefined;\n const client = options.createQueryClient?.() ?? defaultQueryClient();\n\n mark(`$_TSR present: ${Boolean(window.$_TSR)}`);\n\n const composed = await composeFromPayload(runtimeConfig, coreRouteConfig, coreManifest);\n\n const { router, queryClient: builtClient } = await routerFactory({\n routeTree: composed?.routeTree,\n context: {\n pluginNav: composed?.nav,\n queryClient: client,\n runtimeConfig,\n cspNonce,\n locale: isServerRendered() ? document.documentElement.lang : undefined,\n apiClient: createApiClient({\n hostUrl: runtimeConfig.hostUrl,\n rpcBase: runtimeConfig.rpcBase,\n connectionError: options.apiConnectionError,\n }),\n authClient: createAuthClient({ runtimeConfig, cspNonce }),\n },\n });\n const providerClient = builtClient ?? client;\n\n // A server-rendered page whose compose degraded to core-only would\n // hydrate a DIFFERENT tree than the HTML contains — a guaranteed React\n // hydration failure. Client-render cleanly instead; hydration is only\n // safe when the composed tree is the one the server rendered.\n const canHydrate = isServerRendered() && Boolean(composed) && !composed!.degraded;\n\n if (canHydrate) {\n const { hydrateRoot } = await import(\"react-dom/client\");\n const { RouterClient } = await import(\"@tanstack/react-router/ssr/client\");\n\n console.log(\"[Hydrate] Calling hydrateRoot...\");\n hydrateRoot(\n document,\n <QueryClientProvider client={providerClient}>\n <RouterClient router={router} />\n </QueryClientProvider>,\n );\n watchHydrationConsumed();\n } else {\n const { createRoot } = await import(\"react-dom/client\");\n const { RouterProvider } = await import(\"@tanstack/react-router\");\n\n console.log(\"[Hydrate] Calling createRoot...\");\n createRoot(document).render(\n <QueryClientProvider client={providerClient}>\n <RouterProvider router={router} />\n </QueryClientProvider>,\n );\n }\n\n if (composed?.degraded) {\n const { toast } = await import(\"sonner\");\n toast.error(\"Some application features couldn't load\", {\n id: \"application-compose-error\",\n description: \"Plugin pages are unavailable. Reload to try again.\",\n duration: Number.POSITIVE_INFINITY,\n action: { label: \"Reload\", onClick: () => window.location.reload() },\n });\n }\n\n console.log(\"[Hydrate] Complete!\");\n })().catch((error) => {\n console.error(\"[Hydrate] Failed:\", error);\n window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = undefined;\n const message = document.createElement(\"main\");\n message.setAttribute(\"role\", \"alert\");\n message.dataset.testid = \"application-startup-error\";\n message.className =\n \"min-h-screen flex flex-col items-center justify-center gap-4 bg-background text-foreground p-6\";\n const heading = document.createElement(\"h1\");\n heading.textContent = \"The application couldn't load\";\n const detail = document.createElement(\"p\");\n detail.textContent = \"Check your connection and reload to try again.\";\n const retry = document.createElement(\"button\");\n retry.textContent = \"Reload\";\n retry.className = \"rounded-md border border-border px-4 py-2\";\n retry.addEventListener(\"click\", () => window.location.reload());\n message.append(heading, detail, retry);\n document.body.replaceChildren(message);\n throw error;\n });\n\n window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = hydratePromise;\n return hydratePromise;\n}\n\nexport default hydrate;\n"],"mappings":";;;;;;;;AAqCA,MAAM,QAAQ,YAAoB;CAChC,MAAM,WAAW,OAAO,uBAAuB,CAAC;CAChD,OAAO,sBAAsB,CAAC,GAAG,UAAU,OAAO;CAClD,IAAI,YAAY,IAAI,KAAK,QAAQ,IAAI,aAAa,SAAS;AAC7D;AAEA,SAAS,kBAAkB,OAA4C;CACrE,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,KAAK;EACzB,OAAO,IAAI,aAAa,WAAW,IAAI,aAAa;CACtD,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAgDA,eAAe,mBACb,eACA,iBACA,cACmC;CACnC,MAAM,aAAa,cAAc,IAAI,QAAQA;CAI7C,MAAM,kBAAkB,OACtB,uBACsC;EACtC,IAAI;GACF,MAAM,OAAO,mBAAmB,MAAM,MAAM,EAAE,SAASA,mCAAkB;GACzE,IAAI,CAAC,MAAM,OAAO;GAClB,MAAM,OAAO,UAAMC,gCAAc;IAC/B,MAAM;IACN,SAAS,CAAC;KAAE,KAAK,KAAK;KAAM,QAAQ;IAAW,CAAC;IAChD,SAAS,OAAO,SAAS;KACvB,KAAK,IAAI;KACT,UAAU;KACV,aAAa;IACf;IACA,aAAa,gBAAgB;GAC/B,CAAC;GACD,KACE,+BAA+B,KAAK,UAAU,OAAO,cAAc,KAAK,IAAI,MAAM,OAAO,aAC3F;GACA,OAAO;IAAE,WAAW,KAAK;IAAW,KAAK,KAAK;IAAK,UAAU;GAAK;EACpE,SAAS,OAAO;GACd,QAAQ,MAAM,wCAAwC,KAAK;GAC3D,KAAK,6BAA8B,MAAgB,SAAS;GAC5D;EACF;CACF;CAEA,MAAM,UAAU,cAAc,IAAI;CAClC,IAAI,CAAC,SAAS,SAGZ,OAAO,eAAe,gBAAgB,CAAC,YAAY,CAAC,IAAI;CAG1D,MAAM,SAASC,sCAAqB,UAAU,OAAO;CACrD,IAAI,CAAC,OAAO,SAAS;EACnB,KAAK,8BAA8B,OAAO,MAAM,SAAS;EACzD,OAAO,eAAe,gBAAgB,CAAC,YAAY,CAAC,IAAI;CAC1D;CACA,MAAM,EAAE,cAAc,OAAO;CAC7B,MAAM,gBAAgB,IAAI,IAAI,UAAU,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;CAE/D,IAAI;EACF,MAAM,EAAE,iBAAiB,eAAe,MAAM,OAAO;EAErD,KAAK,2BAA2B,QAAQ,OAAO,IAAI,QAAQ,QAAQ,OAAO,WAAW;EAErF,gBACE,QAAQ,QAAQ,KAAK,YAAY;GAC/B,MAAM,OAAO;GACb,OAAO,OAAO;GASd,OAAO,kBAAkB,OAAO,WAAW,IACvC,OAAO,cACP,OAAO,MAAM,QAAQ,uBAAuB,mBAAmB;EACrE,EAAE,CACJ;EACA,KAAK,MAAM,UAAU,QAAQ,SAC3B,KAAK,qBAAqB,OAAO,KAAK,KAAK,OAAO,OAAO;EAM3D,MAAM,qCAAqB,IAAI,IAA+B;EAC9D,MAAM,UAAoB,CAAC;EAC3B,MAAM,mBAAqC,CAAC;EAC5C,KAAK,MAAM,YAAY,WAAW;GAChC,IAAI,SAAS,SAASF,qCAAoB;IACxC,iBAAiB,KAAK,QAAQ;IAC9B;GACF;GACA,MAAM,SAAS,QAAQ,QAAQ,MAAM,MAAM,EAAE,QAAQ,SAAS,IAAI;GAClE,IAAI,CAAC,QAAQ;IACX,QAAQ,KAAK,SAAS,IAAI;IAC1B,KAAK,WAAW,SAAS,KAAK,kCAAkC;IAChE;GACF;GACA,IAAI;IACF,MAAM,MAAM,MAAM,WAAW,GAAG,OAAO,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;IAC5E,MAAM,cAAgB,KAAyC,WAC7D;IACF,IAAI,CAAC,aAAa,oBAChB,MAAM,IAAI,MAAM,cAAc,OAAO,KAAK,uCAAuC;IAEnF,mBAAmB,IAAI,SAAS,MAAM,WAAW;IACjD,iBAAiB,KAAK,QAAQ;IAC9B,KAAK,UAAU,OAAO,KAAK,aAAa;GAC1C,SAAS,OAAO;IACd,QAAQ,KAAK,SAAS,IAAI;IAC1B,KAAK,WAAW,SAAS,KAAK,KAAM,MAAgB,SAAS;GAC/D;EACF;EAEA,MAAM,OAAO,UAAMC,gCAAc;GAC/B,MAAM;GACN,SAAS,iBAAiB,KAAK,aAAa;IAC1C,MAAM,SAAS,QAAQ,QAAQ,MAAM,MAAM,EAAE,QAAQ,SAAS,IAAI;IAClE,OAAO;KACL,KAAK,SAAS;KACd,QACE,QAAQ,SAAS,SAAS,SAASD,sCAAqB,aAAa,SAAS;IAClF;GACF,CAAC;GACD,SAAS,OAAO,QAAQ;IACtB,IAAI,IAAI,QAAQA,qCAAoB;KAClC,MAAM,WAAW,cAAc,IAAI,IAAI,GAAG;KAC1C,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,wCAAwC,IAAI,IAAI,EAAE;KACjF,OAAO;MAAE,KAAK,IAAI;MAAK;MAAU,aAAa;KAAgB;IAChE;IACA,MAAM,WAAW,cAAc,IAAI,IAAI,GAAG;IAC1C,MAAM,cAAc,mBAAmB,IAAI,IAAI,GAAG;IAClD,IAAI,CAAC,YAAY,CAAC,aAChB,MAAM,IAAI,MAAM,uBAAuB,IAAI,IAAI,wBAAwB;IAEzE,OAAO;KAAE,KAAK,IAAI;KAAK;KAAU;IAAY;GAC/C;GACA,aAAa,gBAAgB;EAC/B,CAAC;EAED,IAAI,QAAQ,SAAS,GAAG;GACtB,KAAK,qBAAqB,QAAQ,KAAK,IAAI,EAAE,yBAAyB;GACtE,OAAO;IAAE,WAAW,KAAK;IAAW,KAAK,KAAK;IAAK,UAAU;GAAK;EACpE;EAEA,IAAI,KAAK,WAAW,QAAQ,QAAQ;GAClC,QAAQ,KACN,6CAA6C,KAAK,OAAO,aAAa,QAAQ,OAAO,sBACvF;GACA,KAAK,gCAAgC,QAAQ,OAAO,UAAU,KAAK,QAAQ;GAC3E,OAAO,gBAAgB,SAAS;EAClC;EAEA,KACE,qBAAqB,KAAK,UAAU,OAAO,cAAc,KAAK,IAAI,MAAM,OAAO,aACjF;EACA,OAAO;GAAE,WAAW,KAAK;GAAW,KAAK,KAAK;GAAK,UAAU;EAAM;CACrE,SAAS,OAAO;EACd,QAAQ,MAAM,wDAAwD,KAAK;EAC3E,KAAK,yBAA0B,MAAgB,SAAS;EACxD,OAAO,gBAAgB,SAAS;CAClC;AACF;AAEA,SAAS,mBAA4B;CACnC,IAAI,SAAS,gBAAgB,aAAa,qBAAqB,GAC7D,OAAO;CAET,IAAI,OAAO,2BAA2B,QACpC,OAAO,OAAO;CAEhB,OAAO,OAAO,UAAU;AAC1B;AAEA,SAAS,yBAAyB;CAChC,MAAM,iBAAiB,kBAAkB;EACvC,IAAI,OAAO,UAAU,QAAW;GAC9B,cAAc,cAAc;GAC5B,KAAK,8CAA8C;EACrD;CACF,GAAG,EAAE;AACP;AAEA,eAAsB,QAAQ,SAA6B;CACzD,IAAI,OAAO,oCACT,OAAO,OAAO;CAGhB,MAAM,kBAAkB,YAAY;EAClC,QAAQ,IAAI,uBAAuB;EAEnC,MAAM,gBAAgB,QAAQ,UAAU,iBAAiB;EACzD,MAAM,WAAW,YAAY;EAE7B,IAAI,CAAC,cAAc,WAAW,CAAC,cAAc,SAC3C,MAAM,IAAI,MAAM,8CAA8C;EAGhE,MAAM,CAAC,EAAE,uBAAuB,iBAAiB,MAAM,QAAQ,IAAI,CACjE,OAAO,0BACP,QAAQ,eACJ,QAAQ,QAAQ,QAAQ,YAAY,IACpC,OAAO,sBAAkB,CAAC,MAAM,MAAM,EAAE,YAAgC,CAC9E,CAAC;EACD,MAAM,CAAC,mBAAmB,MAAM,QAAQ,IAAI,CAC1C,QAAQ,YAAY,CAAC,CAAC,MAAM,QAAS,aAAa,MAAM,IAAI,UAAU,GAAI,CAC5E,CAAC;EACD,MAAM,eAAe,QAAQ,WACzB,MAAM,QACH,SAAS,CAAC,CACV,MAAM,QACL,OAAO,QAAQ,YAAY,QAAQ,QAAQ,aAAa,MACnD,IAA6B,UAC9B,GACN,CAAC,CACA,MAAM,YAAQG,sCAAoB,GAAG,CAAC,IACzC;EACJ,MAAM,SAAS,QAAQ,oBAAoB,KAAK,mBAAmB;EAEnE,KAAK,kBAAkB,QAAQ,OAAO,KAAK,GAAG;EAE9C,MAAM,WAAW,MAAM,mBAAmB,eAAe,iBAAiB,YAAY;EAEtF,MAAM,EAAE,QAAQ,aAAa,gBAAgB,MAAM,cAAc;GAC/D,WAAW,UAAU;GACrB,SAAS;IACP,WAAW,UAAU;IACrB,aAAa;IACb;IACA;IACA,QAAQ,iBAAiB,IAAI,SAAS,gBAAgB,OAAO;IAC7D,WAAW,gBAAgB;KACzB,SAAS,cAAc;KACvB,SAAS,cAAc;KACvB,iBAAiB,QAAQ;IAC3B,CAAC;IACD,YAAY,iBAAiB;KAAE;KAAe;IAAS,CAAC;GAC1D;EACF,CAAC;EACD,MAAM,iBAAiB,eAAe;EAQtC,IAFmB,iBAAiB,KAAK,QAAQ,QAAQ,KAAK,CAAC,SAAU,UAEzD;GACd,MAAM,EAAE,gBAAgB,MAAM,OAAO;GACrC,MAAM,EAAE,iBAAiB,MAAM,OAAO;GAEtC,QAAQ,IAAI,kCAAkC;GAC9C,YACE,UACA,oBAAC,qBAAD;IAAqB,QAAQ;cAC3B,oBAAC,cAAD,EAAsB,OAAS;GACZ,EACvB;GACA,uBAAuB;EACzB,OAAO;GACL,MAAM,EAAE,eAAe,MAAM,OAAO;GACpC,MAAM,EAAE,mBAAmB,MAAM,OAAO;GAExC,QAAQ,IAAI,iCAAiC;GAC7C,WAAW,QAAQ,CAAC,CAAC,OACnB,oBAAC,qBAAD;IAAqB,QAAQ;cAC3B,oBAAC,gBAAD,EAAwB,OAAS;GACd,EACvB;EACF;EAEA,IAAI,UAAU,UAAU;GACtB,MAAM,EAAE,UAAU,MAAM,OAAO;GAC/B,MAAM,MAAM,2CAA2C;IACrD,IAAI;IACJ,aAAa;IACb,UAAU,OAAO;IACjB,QAAQ;KAAE,OAAO;KAAU,eAAe,OAAO,SAAS,OAAO;IAAE;GACrE,CAAC;EACH;EAEA,QAAQ,IAAI,qBAAqB;CACnC,EAAC,CAAE,CAAC,CAAC,OAAO,UAAU;EACpB,QAAQ,MAAM,qBAAqB,KAAK;EACxC,OAAO,qCAAqC;EAC5C,MAAM,UAAU,SAAS,cAAc,MAAM;EAC7C,QAAQ,aAAa,QAAQ,OAAO;EACpC,QAAQ,QAAQ,SAAS;EACzB,QAAQ,YACN;EACF,MAAM,UAAU,SAAS,cAAc,IAAI;EAC3C,QAAQ,cAAc;EACtB,MAAM,SAAS,SAAS,cAAc,GAAG;EACzC,OAAO,cAAc;EACrB,MAAM,QAAQ,SAAS,cAAc,QAAQ;EAC7C,MAAM,cAAc;EACpB,MAAM,YAAY;EAClB,MAAM,iBAAiB,eAAe,OAAO,SAAS,OAAO,CAAC;EAC9D,QAAQ,OAAO,SAAS,QAAQ,KAAK;EACrC,SAAS,KAAK,gBAAgB,OAAO;EACrC,MAAM;CACR,CAAC;CAED,OAAO,qCAAqC;CAC5C,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"hydrate.mjs","names":["CORE_UI_PLUGIN_KEY","constructTree","ComposePayloadSchema","parsePluginManifest"],"sources":["../../src/ui/hydrate.tsx"],"sourcesContent":["/**\n * Client bootstrap — creates browser-side QueryClient, Router, and auth/API\n * clients; composes the route tree from the runtime-config payload before\n * hydration (payload -> registerRemotes -> loadRemote route configs ->\n * constructTree -> hydrateRoot(RouterClient)). Called from the host-rendered\n * HTML shell via the app's thin entry/hydrate stubs. The build's MF runtime\n * owns the page's share scope — the global registerRemotes/loadRemote API\n * bridges it (one React/router across the core client and plugin containers\n * by share-scope negotiation).\n */\n\nimport type { QueryClient } from \"@tanstack/react-query\";\nimport type { ClientRuntimeConfig } from \"../types\";\nimport { type ClientServiceConfig, createApiClient } from \"./api\";\nimport { createAuthClient } from \"./auth\";\nimport {\n CORE_UI_PLUGIN_KEY,\n ComposePayloadSchema,\n constructTree,\n type NavManifest,\n type PluginManifest,\n parsePluginManifest,\n type RouteConfigModule,\n} from \"./manifest\";\nimport { defaultQueryClient } from \"./router-defaults\";\nimport { getCspNonce, getRuntimeConfig } from \"./runtime\";\nimport type { AppRouterFactory } from \"./types\";\n\ndeclare global {\n interface Window {\n __EVERYTHING_DEV_HYDRATE_PROMISE__?: Promise<void>;\n __EVERYTHING_DEV_SSR__?: boolean;\n __CLIENT_PROGRESS__?: string[];\n $_TSR?: unknown;\n }\n}\n\nconst mark = (message: string) => {\n const progress = window.__CLIENT_PROGRESS__ ?? [];\n window.__CLIENT_PROGRESS__ = [...progress, message];\n if (import.meta.env.DEV) console.log(`[Hydrate] ${message}`);\n};\n\nfunction isAbsoluteHttpUrl(value: string | undefined): value is string {\n if (!value) return false;\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport type { AppRouterFactory } from \"./types\";\n\nexport interface CoreHydrateOptions {\n /**\n * Loads the app's generated core route config — the only app-specific\n * input; clients, compose machinery, and the router come from the package.\n */\n routeConfig: () => Promise<RouteConfigModule | { default: RouteConfigModule }>;\n /**\n * Loads the app's generated core manifest — the degradation target when the\n * runtime config carries no (or no usable) compose payload: a plugin-free\n * deployment has no payload at all, yet the core-only tree is still the\n * tree. Absent, a payload-less page cannot construct a tree. The value is\n * parsed through the manifest contract at the boundary (a skewed version\n * or shape fails loudly here).\n */\n manifest?: () => Promise<unknown>;\n /**\n * The app's authored router factory — notFound/pending/error components,\n * scroll behavior, and router defaults live there. Absent, the framework's\n * own factory runs.\n */\n createRouter?: AppRouterFactory;\n /** The app's query client factory (staleTime, gcTime, …). Absent, the framework default. */\n createQueryClient?: () => QueryClient;\n /** Overrides the runtime config source (tests, embeds). */\n config?: ClientRuntimeConfig;\n /** Localized copy for the API connection-failure toast (see `createApiClient`). */\n apiConnectionError?: ClientServiceConfig[\"connectionError\"];\n}\n\ninterface ComposedTree {\n routeTree: unknown;\n nav: NavManifest;\n /** `true` when plugin composition failed and only the core tree is usable. */\n degraded: boolean;\n}\n\n/**\n * Manifest composition: reconstruct the server's tree from the payload — the\n * same manifests, the same MF names, the same construction code. Digest\n * parity proves the SSR'd tree and this tree are identical before hydrate;\n * any failure degrades to the core-only tree (still a real route tree — the\n * page must render, just without plugin routes) rather than leaving the\n * router without a tree.\n */\nasync function composeFromPayload(\n runtimeConfig: ClientRuntimeConfig,\n coreRouteConfig: RouteConfigModule,\n coreManifest: PluginManifest | undefined,\n): Promise<ComposedTree | undefined> {\n const coreUiName = runtimeConfig.ui?.name ?? CORE_UI_PLUGIN_KEY;\n\n // The core-only tree is the degradation target for every failure below —\n // the payload's core manifest plus the app's own route config, no remotes.\n const composeCoreOnly = async (\n candidateManifests: PluginManifest[],\n ): Promise<ComposedTree | undefined> => {\n try {\n const core = candidateManifests.find((m) => m.name === CORE_UI_PLUGIN_KEY);\n if (!core) return undefined;\n const tree = await constructTree({\n name: \"core-fallback\",\n plugins: [{ key: core.name, mfName: coreUiName }],\n resolve: async (ref) => ({\n key: ref.key,\n manifest: core,\n routeConfig: coreRouteConfig,\n }),\n rootOptions: coreRouteConfig.rootMeta,\n });\n mark(\n `core-only tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`,\n );\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: true };\n } catch (error) {\n console.error(\"[Hydrate] Core-only fallback failed:\", error);\n mark(`CORE-ONLY FALLBACK ERROR: ${(error as Error).message}`);\n return undefined;\n }\n };\n\n const payload = runtimeConfig.ui?.compose;\n if (!payload?.remotes) {\n // No payload at all — a plugin-free deployment. The core-only tree IS\n // the tree; without the app manifest there is nothing to construct.\n return coreManifest ? composeCoreOnly([coreManifest]) : undefined;\n }\n\n const parsed = ComposePayloadSchema.safeParse(payload);\n if (!parsed.success) {\n mark(`compose payload malformed: ${parsed.error.message}`);\n return coreManifest ? composeCoreOnly([coreManifest]) : undefined;\n }\n const { manifests } = parsed.data;\n const manifestByKey = new Map(manifests.map((m) => [m.name, m]));\n\n try {\n const { registerRemotes, loadRemote } = await import(\"@module-federation/enhanced/runtime\");\n\n mark(`compose payload: digest ${payload.digest}, ${payload.remotes.length} remote(s)`);\n\n registerRemotes(\n payload.remotes.map((remote) => ({\n name: remote.name,\n alias: remote.name,\n // Manifest-driven registration: the manifest carries the remote's\n // true container identity (its build-time package name), which the\n // plain remoteEntry URL cannot resolve — the entry script's global\n // name doesn't match the registered name. Versioned deploys carry\n // the (hashed) manifest URL in the payload; legacy payloads derive\n // it by stripping the fixed entry name. Only absolute URLs qualify —\n // a relative manifestUrl would resolve against the page origin and\n // register the wrong container.\n entry: isAbsoluteHttpUrl(remote.manifestUrl)\n ? remote.manifestUrl\n : remote.entry.replace(/\\/?remoteEntry\\.js$/, \"/mf-manifest.json\"),\n })),\n );\n for (const remote of payload.remotes) {\n mark(`remote registered ${remote.name} @ ${remote.entry}`);\n }\n\n // Per-source isolation (ADR 0024 §5): a remote whose route config fails\n // to load drops only its own routes. The dropped set degrades the compose\n // (client-render — the SSR'd tree included it), never the whole page.\n const loadedRouteConfigs = new Map<string, RouteConfigModule>();\n const dropped: string[] = [];\n const healthyManifests: PluginManifest[] = [];\n for (const manifest of manifests) {\n if (manifest.name === CORE_UI_PLUGIN_KEY) {\n healthyManifests.push(manifest);\n continue;\n }\n const remote = payload.remotes.find((r) => r.key === manifest.name);\n if (!remote) {\n dropped.push(manifest.name);\n mark(`dropped ${manifest.name} — payload has no matching remote`);\n continue;\n }\n try {\n const mod = await loadRemote(`${remote.name}/routeConfig`, { from: \"build\" });\n const routeConfig = ((mod as { default?: RouteConfigModule })?.default ??\n mod) as RouteConfigModule;\n if (!routeConfig?.routeConfigLoaders) {\n throw new Error(`loadRemote(${remote.name}/routeConfig) returned no route config`);\n }\n loadedRouteConfigs.set(manifest.name, routeConfig);\n healthyManifests.push(manifest);\n mark(`loaded ${remote.name}/routeConfig`);\n } catch (error) {\n dropped.push(manifest.name);\n mark(`dropped ${manifest.name} — ${(error as Error).message}`);\n }\n }\n\n const tree = await constructTree({\n name: \"client\",\n plugins: healthyManifests.map((manifest) => {\n const remote = payload.remotes.find((r) => r.key === manifest.name);\n return {\n key: manifest.name,\n mfName:\n remote?.name ?? (manifest.name === CORE_UI_PLUGIN_KEY ? coreUiName : manifest.name),\n };\n }),\n resolve: async (ref) => {\n if (ref.key === CORE_UI_PLUGIN_KEY) {\n const manifest = manifestByKey.get(ref.key);\n if (!manifest) throw new Error(`compose payload has no manifest for \"${ref.key}\"`);\n return { key: ref.key, manifest, routeConfig: coreRouteConfig };\n }\n const manifest = manifestByKey.get(ref.key);\n const routeConfig = loadedRouteConfigs.get(ref.key);\n if (!manifest || !routeConfig) {\n throw new Error(`composition source \"${ref.key}\" is not fully resolved`);\n }\n return { key: ref.key, manifest, routeConfig };\n },\n rootOptions: coreRouteConfig.rootMeta,\n });\n\n if (dropped.length > 0) {\n mark(`degraded compose: ${dropped.join(\", \")} dropped — client-render`);\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: true };\n }\n\n if (tree.digest !== payload.digest) {\n console.warn(\n `[Hydrate] Compose digest mismatch (client ${tree.digest} vs server ${payload.digest}); core-only fallback`,\n );\n mark(`DIGEST PARITY FAILURE server=${payload.digest} client=${tree.digest}`);\n return composeCoreOnly(manifests);\n }\n\n mark(\n `tree constructed: ${tree.manifests.length} source(s), ${tree.nav.items.length} nav item(s)`,\n );\n return { routeTree: tree.rootRoute, nav: tree.nav, degraded: false };\n } catch (error) {\n console.error(\"[Hydrate] Client compose failed; core-only fallback:\", error);\n mark(`CLIENT COMPOSE ERROR: ${(error as Error).message}`);\n return composeCoreOnly(manifests);\n }\n}\n\nfunction isServerRendered(): boolean {\n if (document.documentElement.hasAttribute(\"data-everything-ssr\")) {\n return true;\n }\n if (window.__EVERYTHING_DEV_SSR__ !== undefined) {\n return window.__EVERYTHING_DEV_SSR__;\n }\n return window.$_TSR !== undefined;\n}\n\nfunction watchHydrationConsumed() {\n const bootstrapWatch = setInterval(() => {\n if (window.$_TSR === undefined) {\n clearInterval(bootstrapWatch);\n mark(\"hydration consumed ($_TSR deleted — h() ran)\");\n }\n }, 50);\n}\n\nexport async function hydrate(options: CoreHydrateOptions) {\n if (window.__EVERYTHING_DEV_HYDRATE_PROMISE__) {\n return window.__EVERYTHING_DEV_HYDRATE_PROMISE__;\n }\n\n const hydratePromise = (async () => {\n console.log(\"[Hydrate] Starting...\");\n\n const runtimeConfig = options.config ?? getRuntimeConfig();\n const cspNonce = getCspNonce();\n\n if (!runtimeConfig.hostUrl || !runtimeConfig.rpcBase) {\n throw new Error(\"Missing hostUrl or rpcBase in runtime config\");\n }\n\n const [{ QueryClientProvider }, routerFactory] = await Promise.all([\n import(\"@tanstack/react-query\"),\n options.createRouter\n ? Promise.resolve(options.createRouter)\n : import(\"./router-client\").then((m) => m.createRouter as AppRouterFactory),\n ]);\n const coreRouteConfig = await options\n .routeConfig()\n .then((mod) => (\"default\" in mod ? mod.default : mod));\n const coreManifest = options.manifest\n ? await options\n .manifest()\n .then((raw) =>\n typeof raw === \"object\" && raw !== null && \"default\" in raw\n ? (raw as { default: unknown }).default\n : raw,\n )\n .then((raw) => parsePluginManifest(raw))\n : undefined;\n const client = options.createQueryClient?.() ?? defaultQueryClient();\n\n mark(`$_TSR present: ${Boolean(window.$_TSR)}`);\n\n const composed = await composeFromPayload(runtimeConfig, coreRouteConfig, coreManifest);\n\n const { router, queryClient: builtClient } = routerFactory({\n routeTree: composed?.routeTree,\n context: {\n pluginNav: composed?.nav,\n queryClient: client,\n runtimeConfig,\n cspNonce,\n locale: isServerRendered() ? document.documentElement.lang : undefined,\n apiClient: createApiClient({\n hostUrl: runtimeConfig.hostUrl,\n rpcBase: runtimeConfig.rpcBase,\n connectionError: options.apiConnectionError,\n }),\n authClient: createAuthClient({ runtimeConfig, cspNonce }),\n },\n });\n const providerClient = builtClient ?? client;\n\n // A server-rendered page whose compose degraded to core-only would\n // hydrate a DIFFERENT tree than the HTML contains — a guaranteed React\n // hydration failure. Client-render cleanly instead; hydration is only\n // safe when the composed tree is the one the server rendered.\n const canHydrate = isServerRendered() && Boolean(composed) && !composed!.degraded;\n\n if (canHydrate) {\n const { hydrateRoot } = await import(\"react-dom/client\");\n const { RouterClient } = await import(\"@tanstack/react-router/ssr/client\");\n\n console.log(\"[Hydrate] Calling hydrateRoot...\");\n hydrateRoot(\n document,\n <QueryClientProvider client={providerClient}>\n <RouterClient router={router} />\n </QueryClientProvider>,\n );\n watchHydrationConsumed();\n } else {\n const { createRoot } = await import(\"react-dom/client\");\n const { RouterProvider } = await import(\"@tanstack/react-router\");\n\n console.log(\"[Hydrate] Calling createRoot...\");\n createRoot(document).render(\n <QueryClientProvider client={providerClient}>\n <RouterProvider router={router} />\n </QueryClientProvider>,\n );\n }\n\n if (composed?.degraded) {\n const { toast } = await import(\"sonner\");\n toast.error(\"Some application features couldn't load\", {\n id: \"application-compose-error\",\n description: \"Plugin pages are unavailable. Reload to try again.\",\n duration: Number.POSITIVE_INFINITY,\n action: { label: \"Reload\", onClick: () => window.location.reload() },\n });\n }\n\n console.log(\"[Hydrate] Complete!\");\n })().catch((error) => {\n console.error(\"[Hydrate] Failed:\", error);\n window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = undefined;\n const message = document.createElement(\"main\");\n message.setAttribute(\"role\", \"alert\");\n message.dataset.testid = \"application-startup-error\";\n message.className =\n \"min-h-screen flex flex-col items-center justify-center gap-4 bg-background text-foreground p-6\";\n const heading = document.createElement(\"h1\");\n heading.textContent = \"The application couldn't load\";\n const detail = document.createElement(\"p\");\n detail.textContent = \"Check your connection and reload to try again.\";\n const retry = document.createElement(\"button\");\n retry.textContent = \"Reload\";\n retry.className = \"rounded-md border border-border px-4 py-2\";\n retry.addEventListener(\"click\", () => window.location.reload());\n message.append(heading, detail, retry);\n document.body.replaceChildren(message);\n throw error;\n });\n\n window.__EVERYTHING_DEV_HYDRATE_PROMISE__ = hydratePromise;\n return hydratePromise;\n}\n\nexport default hydrate;\n"],"mappings":"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"}
|
package/dist/utils/theme.cjs
CHANGED
|
@@ -10,8 +10,8 @@ const truecolorRamp = (from, to) => (text) => {
|
|
|
10
10
|
3,
|
|
11
11
|
5
|
|
12
12
|
].map((i) => parseInt(code.slice(i, i + 2), 16));
|
|
13
|
-
const [r1, g1, b1] = rgb(from);
|
|
14
|
-
const [r2, g2, b2] = rgb(to);
|
|
13
|
+
const [r1 = 0, g1 = 0, b1 = 0] = rgb(from);
|
|
14
|
+
const [r2 = 0, g2 = 0, b2 = 0] = rgb(to);
|
|
15
15
|
const chars = Array.from(text);
|
|
16
16
|
const total = chars.filter((ch) => ch !== "\n").length;
|
|
17
17
|
let seen = 0;
|
package/dist/utils/theme.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.cjs","names":[],"sources":["../../src/utils/theme.ts"],"sourcesContent":["import chalk from \"chalk\";\n\nconst hex =\n (code: string) =>\n (text: string): string =>\n chalk.hex(code)(text);\n\nconst truecolorRamp =\n (from: string, to: string) =>\n (text: string): string => {\n const rgb = (code: string) => [1, 3, 5].map((i) => parseInt(code.slice(i, i + 2), 16));\n const [r1, g1, b1] = rgb(from);\n const [r2, g2, b2] = rgb(to);\n const chars = Array.from(text);\n const total = chars.filter((ch) => ch !== \"\\n\").length;\n let seen = 0;\n return chars\n .map((ch) => {\n if (ch === \"\\n\") return ch;\n const t = total > 1 ? seen / (total - 1) : 0;\n seen += 1;\n return chalk.rgb(\n Math.round(r1 + (r2 - r1) * t),\n Math.round(g1 + (g2 - g1) * t),\n Math.round(b1 + (b2 - b1) * t),\n )(ch);\n })\n .join(\"\");\n };\n\nexport const gradients: Record<string, (text: string) => string> = {\n cyber: truecolorRamp(\"#00ffff\", \"#ff00ff\"),\n};\n\nexport const colors = {\n cyan: hex(\"#00ffff\"),\n magenta: hex(\"#ff00ff\"),\n green: hex(\"#00ff41\"),\n blue: hex(\"#0080ff\"),\n yellow: hex(\"#ffcc00\"),\n orange: hex(\"#ffaa00\"),\n white: hex(\"#f0f0f0\"),\n gray: hex(\"#555555\"),\n dim: chalk.dim,\n error: hex(\"#ff3366\"),\n};\n\nexport const icons = {\n scan: \"○\",\n run: \"▶\",\n ok: \"✓\",\n err: \"✗\",\n pending: \"○\",\n arrow: \"→\",\n dot: \"·\",\n app: \"◉\",\n config: \"⚙\",\n};\n\nexport const frames = {\n top: (width: number) => `┌${\"─\".repeat(width - 2)}┐`,\n bottom: (width: number) => `└${\"─\".repeat(width - 2)}┘`,\n};\n\nexport function divider(width = 48): string {\n return chalk.dim(\"─\".repeat(width));\n}\n"],"mappings":";;;;;AAEA,MAAM,OACH,UACA,SACC,cAAM,IAAI,IAAI,CAAC,CAAC,IAAI;AAExB,MAAM,iBACH,MAAc,QACd,SAAyB;CACxB,MAAM,OAAO,SAAiB;EAAC;EAAG;EAAG;CAAC,CAAC,CAAC,KAAK,MAAM,SAAS,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;CACrF,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"theme.cjs","names":[],"sources":["../../src/utils/theme.ts"],"sourcesContent":["import chalk from \"chalk\";\n\nconst hex =\n (code: string) =>\n (text: string): string =>\n chalk.hex(code)(text);\n\nconst truecolorRamp =\n (from: string, to: string) =>\n (text: string): string => {\n const rgb = (code: string) => [1, 3, 5].map((i) => parseInt(code.slice(i, i + 2), 16));\n const [r1 = 0, g1 = 0, b1 = 0] = rgb(from);\n const [r2 = 0, g2 = 0, b2 = 0] = rgb(to);\n const chars = Array.from(text);\n const total = chars.filter((ch) => ch !== \"\\n\").length;\n let seen = 0;\n return chars\n .map((ch) => {\n if (ch === \"\\n\") return ch;\n const t = total > 1 ? seen / (total - 1) : 0;\n seen += 1;\n return chalk.rgb(\n Math.round(r1 + (r2 - r1) * t),\n Math.round(g1 + (g2 - g1) * t),\n Math.round(b1 + (b2 - b1) * t),\n )(ch);\n })\n .join(\"\");\n };\n\nexport const gradients: Record<string, (text: string) => string> = {\n cyber: truecolorRamp(\"#00ffff\", \"#ff00ff\"),\n};\n\nexport const colors = {\n cyan: hex(\"#00ffff\"),\n magenta: hex(\"#ff00ff\"),\n green: hex(\"#00ff41\"),\n blue: hex(\"#0080ff\"),\n yellow: hex(\"#ffcc00\"),\n orange: hex(\"#ffaa00\"),\n white: hex(\"#f0f0f0\"),\n gray: hex(\"#555555\"),\n dim: chalk.dim,\n error: hex(\"#ff3366\"),\n};\n\nexport const icons = {\n scan: \"○\",\n run: \"▶\",\n ok: \"✓\",\n err: \"✗\",\n pending: \"○\",\n arrow: \"→\",\n dot: \"·\",\n app: \"◉\",\n config: \"⚙\",\n};\n\nexport const frames = {\n top: (width: number) => `┌${\"─\".repeat(width - 2)}┐`,\n bottom: (width: number) => `└${\"─\".repeat(width - 2)}┘`,\n};\n\nexport function divider(width = 48): string {\n return chalk.dim(\"─\".repeat(width));\n}\n"],"mappings":";;;;;AAEA,MAAM,OACH,UACA,SACC,cAAM,IAAI,IAAI,CAAC,CAAC,IAAI;AAExB,MAAM,iBACH,MAAc,QACd,SAAyB;CACxB,MAAM,OAAO,SAAiB;EAAC;EAAG;EAAG;CAAC,CAAC,CAAC,KAAK,MAAM,SAAS,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;CACrF,MAAM,CAAC,KAAK,GAAG,KAAK,GAAG,KAAK,KAAK,IAAI,IAAI;CACzC,MAAM,CAAC,KAAK,GAAG,KAAK,GAAG,KAAK,KAAK,IAAI,EAAE;CACvC,MAAM,QAAQ,MAAM,KAAK,IAAI;CAC7B,MAAM,QAAQ,MAAM,QAAQ,OAAO,OAAO,IAAI,CAAC,CAAC;CAChD,IAAI,OAAO;CACX,OAAO,MACJ,KAAK,OAAO;EACX,IAAI,OAAO,MAAM,OAAO;EACxB,MAAM,IAAI,QAAQ,IAAI,QAAQ,QAAQ,KAAK;EAC3C,QAAQ;EACR,OAAO,cAAM,IACX,KAAK,MAAM,MAAM,KAAK,MAAM,CAAC,GAC7B,KAAK,MAAM,MAAM,KAAK,MAAM,CAAC,GAC7B,KAAK,MAAM,MAAM,KAAK,MAAM,CAAC,CAC/B,CAAC,CAAC,EAAE;CACN,CAAC,CAAC,CACD,KAAK,EAAE;AACZ;AAEF,MAAa,YAAsD,EACjE,OAAO,cAAc,WAAW,SAAS,EAC3C;AAEA,MAAa,SAAS;CACpB,MAAM,IAAI,SAAS;CACnB,SAAS,IAAI,SAAS;CACtB,OAAO,IAAI,SAAS;CACpB,MAAM,IAAI,SAAS;CACnB,QAAQ,IAAI,SAAS;CACrB,QAAQ,IAAI,SAAS;CACrB,OAAO,IAAI,SAAS;CACpB,MAAM,IAAI,SAAS;CACnB,KAAK,cAAM;CACX,OAAO,IAAI,SAAS;AACtB;AAEA,MAAa,QAAQ;CACnB,MAAM;CACN,KAAK;CACL,IAAI;CACJ,KAAK;CACL,SAAS;CACT,OAAO;CACP,KAAK;CACL,KAAK;CACL,QAAQ;AACV;AAEA,MAAa,SAAS;CACpB,MAAM,UAAkB,IAAI,IAAI,OAAO,QAAQ,CAAC,EAAE;CAClD,SAAS,UAAkB,IAAI,IAAI,OAAO,QAAQ,CAAC,EAAE;AACvD;AAEA,SAAgB,QAAQ,QAAQ,IAAY;CAC1C,OAAO,cAAM,IAAI,IAAI,OAAO,KAAK,CAAC;AACpC"}
|
package/dist/utils/theme.mjs
CHANGED
|
@@ -8,8 +8,8 @@ const truecolorRamp = (from, to) => (text) => {
|
|
|
8
8
|
3,
|
|
9
9
|
5
|
|
10
10
|
].map((i) => parseInt(code.slice(i, i + 2), 16));
|
|
11
|
-
const [r1, g1, b1] = rgb(from);
|
|
12
|
-
const [r2, g2, b2] = rgb(to);
|
|
11
|
+
const [r1 = 0, g1 = 0, b1 = 0] = rgb(from);
|
|
12
|
+
const [r2 = 0, g2 = 0, b2 = 0] = rgb(to);
|
|
13
13
|
const chars = Array.from(text);
|
|
14
14
|
const total = chars.filter((ch) => ch !== "\n").length;
|
|
15
15
|
let seen = 0;
|
package/dist/utils/theme.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.mjs","names":[],"sources":["../../src/utils/theme.ts"],"sourcesContent":["import chalk from \"chalk\";\n\nconst hex =\n (code: string) =>\n (text: string): string =>\n chalk.hex(code)(text);\n\nconst truecolorRamp =\n (from: string, to: string) =>\n (text: string): string => {\n const rgb = (code: string) => [1, 3, 5].map((i) => parseInt(code.slice(i, i + 2), 16));\n const [r1, g1, b1] = rgb(from);\n const [r2, g2, b2] = rgb(to);\n const chars = Array.from(text);\n const total = chars.filter((ch) => ch !== \"\\n\").length;\n let seen = 0;\n return chars\n .map((ch) => {\n if (ch === \"\\n\") return ch;\n const t = total > 1 ? seen / (total - 1) : 0;\n seen += 1;\n return chalk.rgb(\n Math.round(r1 + (r2 - r1) * t),\n Math.round(g1 + (g2 - g1) * t),\n Math.round(b1 + (b2 - b1) * t),\n )(ch);\n })\n .join(\"\");\n };\n\nexport const gradients: Record<string, (text: string) => string> = {\n cyber: truecolorRamp(\"#00ffff\", \"#ff00ff\"),\n};\n\nexport const colors = {\n cyan: hex(\"#00ffff\"),\n magenta: hex(\"#ff00ff\"),\n green: hex(\"#00ff41\"),\n blue: hex(\"#0080ff\"),\n yellow: hex(\"#ffcc00\"),\n orange: hex(\"#ffaa00\"),\n white: hex(\"#f0f0f0\"),\n gray: hex(\"#555555\"),\n dim: chalk.dim,\n error: hex(\"#ff3366\"),\n};\n\nexport const icons = {\n scan: \"○\",\n run: \"▶\",\n ok: \"✓\",\n err: \"✗\",\n pending: \"○\",\n arrow: \"→\",\n dot: \"·\",\n app: \"◉\",\n config: \"⚙\",\n};\n\nexport const frames = {\n top: (width: number) => `┌${\"─\".repeat(width - 2)}┐`,\n bottom: (width: number) => `└${\"─\".repeat(width - 2)}┘`,\n};\n\nexport function divider(width = 48): string {\n return chalk.dim(\"─\".repeat(width));\n}\n"],"mappings":";;;AAEA,MAAM,OACH,UACA,SACC,MAAM,IAAI,IAAI,CAAC,CAAC,IAAI;AAExB,MAAM,iBACH,MAAc,QACd,SAAyB;CACxB,MAAM,OAAO,SAAiB;EAAC;EAAG;EAAG;CAAC,CAAC,CAAC,KAAK,MAAM,SAAS,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;CACrF,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"theme.mjs","names":[],"sources":["../../src/utils/theme.ts"],"sourcesContent":["import chalk from \"chalk\";\n\nconst hex =\n (code: string) =>\n (text: string): string =>\n chalk.hex(code)(text);\n\nconst truecolorRamp =\n (from: string, to: string) =>\n (text: string): string => {\n const rgb = (code: string) => [1, 3, 5].map((i) => parseInt(code.slice(i, i + 2), 16));\n const [r1 = 0, g1 = 0, b1 = 0] = rgb(from);\n const [r2 = 0, g2 = 0, b2 = 0] = rgb(to);\n const chars = Array.from(text);\n const total = chars.filter((ch) => ch !== \"\\n\").length;\n let seen = 0;\n return chars\n .map((ch) => {\n if (ch === \"\\n\") return ch;\n const t = total > 1 ? seen / (total - 1) : 0;\n seen += 1;\n return chalk.rgb(\n Math.round(r1 + (r2 - r1) * t),\n Math.round(g1 + (g2 - g1) * t),\n Math.round(b1 + (b2 - b1) * t),\n )(ch);\n })\n .join(\"\");\n };\n\nexport const gradients: Record<string, (text: string) => string> = {\n cyber: truecolorRamp(\"#00ffff\", \"#ff00ff\"),\n};\n\nexport const colors = {\n cyan: hex(\"#00ffff\"),\n magenta: hex(\"#ff00ff\"),\n green: hex(\"#00ff41\"),\n blue: hex(\"#0080ff\"),\n yellow: hex(\"#ffcc00\"),\n orange: hex(\"#ffaa00\"),\n white: hex(\"#f0f0f0\"),\n gray: hex(\"#555555\"),\n dim: chalk.dim,\n error: hex(\"#ff3366\"),\n};\n\nexport const icons = {\n scan: \"○\",\n run: \"▶\",\n ok: \"✓\",\n err: \"✗\",\n pending: \"○\",\n arrow: \"→\",\n dot: \"·\",\n app: \"◉\",\n config: \"⚙\",\n};\n\nexport const frames = {\n top: (width: number) => `┌${\"─\".repeat(width - 2)}┐`,\n bottom: (width: number) => `└${\"─\".repeat(width - 2)}┘`,\n};\n\nexport function divider(width = 48): string {\n return chalk.dim(\"─\".repeat(width));\n}\n"],"mappings":";;;AAEA,MAAM,OACH,UACA,SACC,MAAM,IAAI,IAAI,CAAC,CAAC,IAAI;AAExB,MAAM,iBACH,MAAc,QACd,SAAyB;CACxB,MAAM,OAAO,SAAiB;EAAC;EAAG;EAAG;CAAC,CAAC,CAAC,KAAK,MAAM,SAAS,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;CACrF,MAAM,CAAC,KAAK,GAAG,KAAK,GAAG,KAAK,KAAK,IAAI,IAAI;CACzC,MAAM,CAAC,KAAK,GAAG,KAAK,GAAG,KAAK,KAAK,IAAI,EAAE;CACvC,MAAM,QAAQ,MAAM,KAAK,IAAI;CAC7B,MAAM,QAAQ,MAAM,QAAQ,OAAO,OAAO,IAAI,CAAC,CAAC;CAChD,IAAI,OAAO;CACX,OAAO,MACJ,KAAK,OAAO;EACX,IAAI,OAAO,MAAM,OAAO;EACxB,MAAM,IAAI,QAAQ,IAAI,QAAQ,QAAQ,KAAK;EAC3C,QAAQ;EACR,OAAO,MAAM,IACX,KAAK,MAAM,MAAM,KAAK,MAAM,CAAC,GAC7B,KAAK,MAAM,MAAM,KAAK,MAAM,CAAC,GAC7B,KAAK,MAAM,MAAM,KAAK,MAAM,CAAC,CAC/B,CAAC,CAAC,EAAE;CACN,CAAC,CAAC,CACD,KAAK,EAAE;AACZ;AAEF,MAAa,YAAsD,EACjE,OAAO,cAAc,WAAW,SAAS,EAC3C;AAEA,MAAa,SAAS;CACpB,MAAM,IAAI,SAAS;CACnB,SAAS,IAAI,SAAS;CACtB,OAAO,IAAI,SAAS;CACpB,MAAM,IAAI,SAAS;CACnB,QAAQ,IAAI,SAAS;CACrB,QAAQ,IAAI,SAAS;CACrB,OAAO,IAAI,SAAS;CACpB,MAAM,IAAI,SAAS;CACnB,KAAK,MAAM;CACX,OAAO,IAAI,SAAS;AACtB;AAEA,MAAa,QAAQ;CACnB,MAAM;CACN,KAAK;CACL,IAAI;CACJ,KAAK;CACL,SAAS;CACT,OAAO;CACP,KAAK;CACL,KAAK;CACL,QAAQ;AACV;AAEA,MAAa,SAAS;CACpB,MAAM,UAAkB,IAAI,IAAI,OAAO,QAAQ,CAAC,EAAE;CAClD,SAAS,UAAkB,IAAI,IAAI,OAAO,QAAQ,CAAC,EAAE;AACvD;AAEA,SAAgB,QAAQ,QAAQ,IAAY;CAC1C,OAAO,MAAM,IAAI,IAAI,OAAO,KAAK,CAAC;AACpC"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
3
|
+
const require_bundle_path = require('./bundle-path.cjs');
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
node_path = require_runtime.__toESM(node_path, 1);
|
|
2
7
|
let node_crypto = require("node:crypto");
|
|
3
8
|
let every_plugin_version_manifest = require("every-plugin/version-manifest");
|
|
4
9
|
|
|
@@ -13,25 +18,65 @@ function fetchText(url, fetchImpl) {
|
|
|
13
18
|
return response.text();
|
|
14
19
|
});
|
|
15
20
|
}
|
|
21
|
+
function slotVersionFromManifest(base, manifest) {
|
|
22
|
+
const trimmedBase = base.replace(/\/$/, "");
|
|
23
|
+
return {
|
|
24
|
+
entryUrl: `${trimmedBase}/${manifest.entry}`,
|
|
25
|
+
entryIntegrity: manifest.entryIntegrity,
|
|
26
|
+
...manifest.browserManifest ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` } : {},
|
|
27
|
+
...manifest.ssr ? {
|
|
28
|
+
ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,
|
|
29
|
+
ssrIntegrity: manifest.ssr.integrity
|
|
30
|
+
} : {},
|
|
31
|
+
...manifest.files ? { files: manifest.files } : {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime
|
|
36
|
+
* stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the
|
|
37
|
+
* staged dist's own version manifest instead of the config's pin — pinned
|
|
38
|
+
* manifest bytes that predate the image (deploy lag after a release bump)
|
|
39
|
+
* cannot exist in the staged namespace, so their hashed files would 404 the
|
|
40
|
+
* disk and fall through to the network, where the stale immutable bytes
|
|
41
|
+
* still answer 200 and boot a foreign build. The staged manifest is trusted
|
|
42
|
+
* (staged by the image build itself) and skipped entirely when the slot is
|
|
43
|
+
* absent from the staged namespace or the pinned manifest is present (the
|
|
44
|
+
* normal healthy case, which keeps the pin's SRI check).
|
|
45
|
+
*/
|
|
46
|
+
function readStagedSlotVersion(base, pin) {
|
|
47
|
+
const bundleDir = process.env.BOS_BUNDLE_DIR;
|
|
48
|
+
if (!bundleDir) return null;
|
|
49
|
+
const slotDir = require_bundle_path.bundleUrlToStagedPath(base, bundleDir);
|
|
50
|
+
if (!slotDir || !(0, node_fs.existsSync)(slotDir)) return null;
|
|
51
|
+
const pinnedManifestPath = require_bundle_path.resolveContained(slotDir, pin.manifest);
|
|
52
|
+
if (!pinnedManifestPath || (0, node_fs.existsSync)(pinnedManifestPath)) return null;
|
|
53
|
+
const versionsDir = node_path.default.join(slotDir, "versions");
|
|
54
|
+
if (!(0, node_fs.existsSync)(versionsDir)) return null;
|
|
55
|
+
const manifests = (0, node_fs.readdirSync)(versionsDir).filter((name) => name.endsWith(".json")).map((name) => node_path.default.join(versionsDir, name)).filter((file) => (0, node_fs.statSync)(file).isFile()).sort((a, b) => (0, node_fs.statSync)(b).mtimeMs - (0, node_fs.statSync)(a).mtimeMs || (a < b ? -1 : 1));
|
|
56
|
+
if (manifests.length === 0) return null;
|
|
57
|
+
const manifestPath = manifests[0];
|
|
58
|
+
if (!manifestPath) return null;
|
|
59
|
+
try {
|
|
60
|
+
return slotVersionFromManifest(base, every_plugin_version_manifest.WorkspaceVersionManifestSchema.parse(JSON.parse((0, node_fs.readFileSync)(manifestPath, "utf8"))));
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
16
65
|
async function resolveSlotVersion(input) {
|
|
17
66
|
const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`;
|
|
18
67
|
const cached = cache.get(key);
|
|
19
68
|
if (cached) return cached;
|
|
69
|
+
const staged = readStagedSlotVersion(input.base, input.pin);
|
|
70
|
+
if (staged) {
|
|
71
|
+
cache.set(key, staged);
|
|
72
|
+
return staged;
|
|
73
|
+
}
|
|
20
74
|
const manifestUrl = `${input.base.replace(/\/$/, "")}/${input.pin.manifest.replace(/^\//, "")}`;
|
|
21
75
|
const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch);
|
|
22
76
|
const computed = `sha384-${(0, node_crypto.createHash)("sha384").update(body).digest("base64")}`;
|
|
23
77
|
if (computed !== input.pin.integrity) throw new Error(`[SRI] version manifest integrity mismatch for ${manifestUrl}\n Expected: ${input.pin.integrity}\n Computed: ${computed}`);
|
|
24
78
|
const manifest = every_plugin_version_manifest.WorkspaceVersionManifestSchema.parse(JSON.parse(body));
|
|
25
|
-
const resolved =
|
|
26
|
-
entryUrl: `${input.base.replace(/\/$/, "")}/${manifest.entry}`,
|
|
27
|
-
entryIntegrity: manifest.entryIntegrity,
|
|
28
|
-
...manifest.browserManifest ? { browserManifestUrl: `${input.base.replace(/\/$/, "")}/${manifest.browserManifest.file}` } : {},
|
|
29
|
-
...manifest.ssr ? {
|
|
30
|
-
ssrEntryUrl: `${input.base.replace(/\/$/, "")}/${manifest.ssr.entry}`,
|
|
31
|
-
ssrIntegrity: manifest.ssr.integrity
|
|
32
|
-
} : {},
|
|
33
|
-
...manifest.files ? { files: manifest.files } : {}
|
|
34
|
-
};
|
|
79
|
+
const resolved = slotVersionFromManifest(input.base, manifest);
|
|
35
80
|
cache.set(key, resolved);
|
|
36
81
|
return resolved;
|
|
37
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version-manifest-resolve.cjs","names":["
|
|
1
|
+
{"version":3,"file":"version-manifest-resolve.cjs","names":["bundleUrlToStagedPath","existsSync","resolveContained","path","readdirSync","statSync","WorkspaceVersionManifestSchema","readFileSync","createHash"],"sources":["../src/version-manifest-resolve.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type WorkspaceVersionManifest,\n WorkspaceVersionManifestSchema,\n} from \"every-plugin/version-manifest\";\nimport { bundleUrlToStagedPath, resolveContained } from \"./bundle-path\";\n\n/**\n * Version-manifest slot resolution (atomic-deploys 04): a config slot's\n * `pin` ({ manifest, integrity }) resolves to the derived entry-level fields\n * every consumer needs. The cache is keyed by the full pin — the pinned\n * bytes are immutable, so a verified entry is cached for the process\n * lifetime and serves as last-known-good if the origin later fails; a fetch\n * or SRI failure with nothing cached propagates (a boot-time resolution\n * failure fails the deploy, which is the platform's rollback).\n */\n\nexport interface ResolvedSlotVersion {\n /** Absolute URL of the content-hashed container entry. */\n entryUrl: string;\n entryIntegrity: string;\n /** Absolute URL of the hashed browser manifest (mf-manifest), when present. */\n browserManifestUrl?: string;\n /** Absolute URL of the content-hashed SSR entry, when present. */\n ssrEntryUrl?: string;\n ssrIntegrity?: string;\n /** Full per-file digest map of the dist (object path → SRI), when the\n * pinned manifest carries one — the diff base for skip-unchanged uploads. */\n files?: Record<string, string>;\n}\n\nexport interface SlotPin {\n /** versioned manifest filename, relative to the slot's production base */\n manifest: string;\n /** the manifest document's own SRI */\n integrity: string;\n}\n\nconst cache = new Map<string, ResolvedSlotVersion>();\n\nexport function clearSlotVersionCache(): void {\n cache.clear();\n}\n\nfunction fetchText(url: string, fetchImpl: typeof fetch): Promise<string> {\n return fetchImpl(url).then((response) => {\n if (!response.ok) throw new Error(`version manifest fetch failed: ${response.status}`);\n return response.text();\n });\n}\n\nfunction slotVersionFromManifest(\n base: string,\n manifest: WorkspaceVersionManifest,\n): ResolvedSlotVersion {\n const trimmedBase = base.replace(/\\/$/, \"\");\n return {\n entryUrl: `${trimmedBase}/${manifest.entry}`,\n entryIntegrity: manifest.entryIntegrity,\n ...(manifest.browserManifest\n ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` }\n : {}),\n ...(manifest.ssr\n ? {\n ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,\n ssrIntegrity: manifest.ssr.integrity,\n }\n : {}),\n ...(manifest.files ? { files: manifest.files } : {}),\n };\n}\n\n/**\n * Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime\n * stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the\n * staged dist's own version manifest instead of the config's pin — pinned\n * manifest bytes that predate the image (deploy lag after a release bump)\n * cannot exist in the staged namespace, so their hashed files would 404 the\n * disk and fall through to the network, where the stale immutable bytes\n * still answer 200 and boot a foreign build. The staged manifest is trusted\n * (staged by the image build itself) and skipped entirely when the slot is\n * absent from the staged namespace or the pinned manifest is present (the\n * normal healthy case, which keeps the pin's SRI check).\n */\nfunction readStagedSlotVersion(base: string, pin: SlotPin): ResolvedSlotVersion | null {\n const bundleDir = process.env.BOS_BUNDLE_DIR;\n if (!bundleDir) return null;\n\n const slotDir = bundleUrlToStagedPath(base, bundleDir);\n if (!slotDir || !existsSync(slotDir)) return null;\n\n const pinnedManifestPath = resolveContained(slotDir, pin.manifest);\n if (!pinnedManifestPath || existsSync(pinnedManifestPath)) return null;\n\n const versionsDir = path.join(slotDir, \"versions\");\n if (!existsSync(versionsDir)) return null;\n const manifests = readdirSync(versionsDir)\n .filter((name) => name.endsWith(\".json\"))\n .map((name) => path.join(versionsDir, name))\n .filter((file) => statSync(file).isFile())\n .sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs || (a < b ? -1 : 1));\n if (manifests.length === 0) return null;\n const manifestPath = manifests[0];\n if (!manifestPath) return null;\n\n try {\n const manifest = WorkspaceVersionManifestSchema.parse(\n JSON.parse(readFileSync(manifestPath, \"utf8\")),\n );\n return slotVersionFromManifest(base, manifest);\n } catch {\n return null;\n }\n}\n\nexport async function resolveSlotVersion(input: {\n base: string;\n pin: SlotPin;\n fetchImpl?: typeof fetch;\n}): Promise<ResolvedSlotVersion> {\n const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`;\n // content-addressed: a verified pin is cached for the process lifetime and\n // never refetched — the cache IS the last-known-good\n const cached = cache.get(key);\n if (cached) return cached;\n\n const staged = readStagedSlotVersion(input.base, input.pin);\n if (staged) {\n cache.set(key, staged);\n return staged;\n }\n\n const manifestUrl = `${input.base.replace(/\\/$/, \"\")}/${input.pin.manifest.replace(/^\\//, \"\")}`;\n const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch);\n const computed = `sha384-${createHash(\"sha384\").update(body).digest(\"base64\")}`;\n if (computed !== input.pin.integrity) {\n throw new Error(\n `[SRI] version manifest integrity mismatch for ${manifestUrl}\\n Expected: ${input.pin.integrity}\\n Computed: ${computed}`,\n );\n }\n const manifest: WorkspaceVersionManifest = WorkspaceVersionManifestSchema.parse(JSON.parse(body));\n\n const resolved = slotVersionFromManifest(input.base, manifest);\n cache.set(key, resolved);\n return resolved;\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { bundleUrlToStagedPath, resolveContained } from "./bundle-path.mjs";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
1
4
|
import { createHash } from "node:crypto";
|
|
2
5
|
import { WorkspaceVersionManifestSchema } from "every-plugin/version-manifest";
|
|
3
6
|
|
|
@@ -12,25 +15,65 @@ function fetchText(url, fetchImpl) {
|
|
|
12
15
|
return response.text();
|
|
13
16
|
});
|
|
14
17
|
}
|
|
18
|
+
function slotVersionFromManifest(base, manifest) {
|
|
19
|
+
const trimmedBase = base.replace(/\/$/, "");
|
|
20
|
+
return {
|
|
21
|
+
entryUrl: `${trimmedBase}/${manifest.entry}`,
|
|
22
|
+
entryIntegrity: manifest.entryIntegrity,
|
|
23
|
+
...manifest.browserManifest ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` } : {},
|
|
24
|
+
...manifest.ssr ? {
|
|
25
|
+
ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`,
|
|
26
|
+
ssrIntegrity: manifest.ssr.integrity
|
|
27
|
+
} : {},
|
|
28
|
+
...manifest.files ? { files: manifest.files } : {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime
|
|
33
|
+
* stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the
|
|
34
|
+
* staged dist's own version manifest instead of the config's pin — pinned
|
|
35
|
+
* manifest bytes that predate the image (deploy lag after a release bump)
|
|
36
|
+
* cannot exist in the staged namespace, so their hashed files would 404 the
|
|
37
|
+
* disk and fall through to the network, where the stale immutable bytes
|
|
38
|
+
* still answer 200 and boot a foreign build. The staged manifest is trusted
|
|
39
|
+
* (staged by the image build itself) and skipped entirely when the slot is
|
|
40
|
+
* absent from the staged namespace or the pinned manifest is present (the
|
|
41
|
+
* normal healthy case, which keeps the pin's SRI check).
|
|
42
|
+
*/
|
|
43
|
+
function readStagedSlotVersion(base, pin) {
|
|
44
|
+
const bundleDir = process.env.BOS_BUNDLE_DIR;
|
|
45
|
+
if (!bundleDir) return null;
|
|
46
|
+
const slotDir = bundleUrlToStagedPath(base, bundleDir);
|
|
47
|
+
if (!slotDir || !existsSync(slotDir)) return null;
|
|
48
|
+
const pinnedManifestPath = resolveContained(slotDir, pin.manifest);
|
|
49
|
+
if (!pinnedManifestPath || existsSync(pinnedManifestPath)) return null;
|
|
50
|
+
const versionsDir = path.join(slotDir, "versions");
|
|
51
|
+
if (!existsSync(versionsDir)) return null;
|
|
52
|
+
const manifests = readdirSync(versionsDir).filter((name) => name.endsWith(".json")).map((name) => path.join(versionsDir, name)).filter((file) => statSync(file).isFile()).sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs || (a < b ? -1 : 1));
|
|
53
|
+
if (manifests.length === 0) return null;
|
|
54
|
+
const manifestPath = manifests[0];
|
|
55
|
+
if (!manifestPath) return null;
|
|
56
|
+
try {
|
|
57
|
+
return slotVersionFromManifest(base, WorkspaceVersionManifestSchema.parse(JSON.parse(readFileSync(manifestPath, "utf8"))));
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
15
62
|
async function resolveSlotVersion(input) {
|
|
16
63
|
const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`;
|
|
17
64
|
const cached = cache.get(key);
|
|
18
65
|
if (cached) return cached;
|
|
66
|
+
const staged = readStagedSlotVersion(input.base, input.pin);
|
|
67
|
+
if (staged) {
|
|
68
|
+
cache.set(key, staged);
|
|
69
|
+
return staged;
|
|
70
|
+
}
|
|
19
71
|
const manifestUrl = `${input.base.replace(/\/$/, "")}/${input.pin.manifest.replace(/^\//, "")}`;
|
|
20
72
|
const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch);
|
|
21
73
|
const computed = `sha384-${createHash("sha384").update(body).digest("base64")}`;
|
|
22
74
|
if (computed !== input.pin.integrity) throw new Error(`[SRI] version manifest integrity mismatch for ${manifestUrl}\n Expected: ${input.pin.integrity}\n Computed: ${computed}`);
|
|
23
75
|
const manifest = WorkspaceVersionManifestSchema.parse(JSON.parse(body));
|
|
24
|
-
const resolved =
|
|
25
|
-
entryUrl: `${input.base.replace(/\/$/, "")}/${manifest.entry}`,
|
|
26
|
-
entryIntegrity: manifest.entryIntegrity,
|
|
27
|
-
...manifest.browserManifest ? { browserManifestUrl: `${input.base.replace(/\/$/, "")}/${manifest.browserManifest.file}` } : {},
|
|
28
|
-
...manifest.ssr ? {
|
|
29
|
-
ssrEntryUrl: `${input.base.replace(/\/$/, "")}/${manifest.ssr.entry}`,
|
|
30
|
-
ssrIntegrity: manifest.ssr.integrity
|
|
31
|
-
} : {},
|
|
32
|
-
...manifest.files ? { files: manifest.files } : {}
|
|
33
|
-
};
|
|
76
|
+
const resolved = slotVersionFromManifest(input.base, manifest);
|
|
34
77
|
cache.set(key, resolved);
|
|
35
78
|
return resolved;
|
|
36
79
|
}
|