@flyo/nitro-next 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.mts +73 -2
- package/dist/client.d.ts +73 -2
- package/dist/client.js +28 -0
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +27 -0
- package/dist/client.mjs.map +1 -1
- package/dist/proxy.d.mts +2 -2
- package/dist/proxy.d.ts +2 -2
- package/dist/proxy.js +8 -2
- package/dist/proxy.js.map +1 -1
- package/dist/proxy.mjs +8 -2
- package/dist/proxy.mjs.map +1 -1
- package/dist/server.d.mts +154 -5
- package/dist/server.d.ts +154 -5
- package/dist/server.js +122 -13
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +117 -12
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.tsx"],"sourcesContent":["import { cache } from 'react';\nimport type { Metadata, MetadataRoute } from 'next';\nimport { notFound } from 'next/navigation';\nimport {\n Page,\n Block,\n Entity,\n ConfigApi,\n ConfigResponse,\n Configuration,\n PagesApi,\n EntitiesApi,\n SitemapApi,\n SearchApi\n} from '@flyo/nitro-typescript';\n\n/**\n * Read-only configuration state\n */\nexport interface NitroState {\n readonly accessToken: string;\n readonly lang: string | null;\n readonly baseUrl: string | null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly components: Record<string, any>;\n readonly showMissingComponentAlert: boolean;\n readonly liveEdit: boolean;\n readonly serverCacheTtl: number;\n readonly clientCacheTtl: number;\n}\n\n/**\n * Route params type for Next.js catch-all routes\n */\ntype RouteParams = {\n params: Promise<{ slug?: string[] }>;\n};\n\n/**\n * Generic route params type for entity routes\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Entity resolver function type\n * Users provide this to resolve entities from their route params\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;\n\n/**\n * The Flyo instance returned by initNitro().\n * Carries all API access methods and configuration state.\n */\nexport interface FlyoInstance {\n /** Read-only configuration state */\n readonly state: NitroState;\n /** Fetch and cache the Nitro CMS configuration (React-cached per request) */\n getNitroConfig(): Promise<ConfigResponse>;\n /** Create a PagesApi client */\n getNitroPages(): PagesApi;\n /** Create an EntitiesApi client */\n getNitroEntities(): EntitiesApi;\n /** Create a SitemapApi client */\n getNitroSitemap(): SitemapApi;\n /** Create a SearchApi client */\n getNitroSearch(): SearchApi;\n /** Resolve a page from catch-all route params (React-cached per request) */\n pageResolveRoute(props: RouteParams): Promise<{ page: Page; path: string; cfg: ConfigResponse }>;\n /** Generate a Next.js sitemap from Flyo Nitro content */\n sitemap(): Promise<MetadataRoute.Sitemap>;\n}\n\n/**\n * Initialize and return a FlyoInstance.\n *\n * Call once in your flyo.config.tsx and export the result.\n * The returned object carries all methods bound to the configuration,\n * eliminating the need for global state.\n *\n * @example\n * ```tsx\n * // flyo.config.tsx\n * import { initNitro } from '@flyo/nitro-next/server';\n *\n * export const flyo = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN || '',\n * lang: 'en',\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * components: { HeroBanner, Text },\n * });\n * ```\n */\nexport function initNitro({\n accessToken,\n lang,\n baseUrl,\n components,\n showMissingComponentAlert,\n liveEdit,\n serverCacheTtl,\n clientCacheTtl,\n}: {\n accessToken: string;\n lang?: string;\n baseUrl?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components?: Record<string, any>;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): FlyoInstance {\n const configuration = new Configuration({ apiKey: accessToken });\n\n const state: NitroState = {\n accessToken,\n lang: lang ?? null,\n baseUrl: baseUrl ?? null,\n components: components ?? {},\n showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,\n liveEdit: liveEdit ?? false,\n serverCacheTtl: serverCacheTtl ?? 1200,\n clientCacheTtl: clientCacheTtl ?? 900,\n };\n\n const getNitroConfig = cache(async (): Promise<ConfigResponse> => {\n const configApi = new ConfigApi(configuration);\n const useLang = state.lang ?? undefined;\n return configApi.config({ lang: useLang });\n });\n\n const pageResolveRoute = cache(async ({ params }: RouteParams) => {\n const { slug } = await params;\n const path = slug?.join('/') ?? '';\n\n const cfg = await getNitroConfig();\n\n if (!cfg.pages?.includes(path)) {\n notFound();\n }\n\n const page = await new PagesApi(configuration)\n .page({ slug: path })\n .catch((error: unknown) => {\n console.error('Error fetching page:', path, error);\n notFound();\n });\n\n if (!page) {\n notFound();\n }\n\n return { page, path, cfg };\n });\n\n const sitemap = async (): Promise<MetadataRoute.Sitemap> => {\n const sitemapApi = new SitemapApi(configuration);\n const sitemapLang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured. Please set it in initNitro().');\n }\n\n const items = await sitemapApi.sitemap({ lang: sitemapLang });\n const cleanBaseUrl = state.baseUrl.endsWith('/') ? state.baseUrl.slice(0, -1) : state.baseUrl;\n\n return items.map((item) => {\n let path = '';\n\n if (item.routes && typeof item.routes === 'object') {\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified: new Date(),\n };\n });\n };\n\n return {\n state,\n getNitroConfig,\n getNitroPages: () => new PagesApi(configuration),\n getNitroEntities: () => new EntitiesApi(configuration),\n getNitroSitemap: () => new SitemapApi(configuration),\n getNitroSearch: () => new SearchApi(configuration),\n pageResolveRoute,\n sitemap,\n };\n}\n\n// ─── Helper ──────────────────────────────────────────────────────────────────\n\n/**\n * Helper function to read environment variables with fallback\n */\nconst readEnv = (key: string, fallback = \"\"): string => {\n const value = process.env[key];\n if (value !== undefined && value !== \"\") {\n return String(value);\n }\n return fallback;\n};\n\n/**\n * Internal helper to wrap and cache entity resolvers\n */\nfunction createCachedEntityResolver<T>(\n resolver: EntityResolver<T>\n): (props: EntityRouteParams<T>) => Promise<Entity> {\n return cache(async ({ params }: EntityRouteParams<T>) => {\n const entity = await resolver(params);\n\n if (!entity) {\n notFound();\n }\n\n return entity;\n });\n}\n\n// ─── Server Components ───────────────────────────────────────────────────────\n\n/**\n * NitroDebugInfo Component\n *\n * Async server component that outputs debug information as an HTML comment.\n * Resolves config internally from the flyo instance.\n *\n * @example\n * ```tsx\n * <NitroDebugInfo flyo={flyo} />\n * ```\n */\nexport async function NitroDebugInfo({ flyo }: { flyo: FlyoInstance }) {\n try {\n const config = await flyo.getNitroConfig();\n const { state } = flyo;\n\n const mode = readEnv(\"NODE_ENV\", \"-\");\n const vercelDeploymentId = readEnv(\"VERCEL_DEPLOYMENT_ID\", \"-\");\n const vercelGitCommitSha = readEnv(\"VERCEL_GIT_COMMIT_SHA\", \"-\");\n const version = readEnv(\"VERSION\", \"\");\n\n const tokenValue = state.accessToken || \"\";\n const token = typeof tokenValue === \"string\" ? tokenValue : \"\";\n const tokenType = token.startsWith(\"p-\")\n ? \"production\"\n : token.startsWith(\"d-\")\n ? \"develop\"\n : \"unknown\";\n\n const debug = state.liveEdit;\n\n const apiVersion = config.nitro?.version?.toString() || \"-\";\n const apiLastUpdate = config.nitro?.updated_at\n ? new Date(config.nitro.updated_at * 1000).toLocaleString(\"de-CH\", {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n })\n : \"-\";\n\n const debugInfoParts = [\n `liveedit:${debug}`,\n `env:${mode}`,\n `version:${apiVersion}`,\n `versiondate:${apiLastUpdate}`,\n `tokentype:${tokenType}`,\n `did:${vercelDeploymentId}`,\n `csha:${vercelGitCommitSha}`,\n ];\n\n if (version) {\n debugInfoParts.push(`release:${version}`);\n }\n\n const debugInfo = debugInfoParts.join(\" | \");\n\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch {\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Renders a JSON-LD structured data script tag from an Entity's jsonld field.\n * Safely escapes HTML to prevent XSS.\n */\nexport function NitroEntityJsonLd({ entity }: { entity: Entity }) {\n if (!entity?.jsonld) {\n return null;\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(entity.jsonld).replace(/</g, '\\\\u003c'),\n }}\n />\n );\n}\n\n/**\n * NitroPage renders all blocks from a Flyo page.\n */\nexport function NitroPage({\n page,\n flyo,\n}: {\n page: Page;\n flyo: FlyoInstance;\n}) {\n if (!page?.json || !Array.isArray(page.json)) {\n return null;\n }\n\n return (\n <>\n {page.json.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n/**\n * NitroBlock renders a single block using the registered component.\n */\nexport function NitroBlock({\n block,\n flyo,\n}: {\n block: Block;\n flyo: FlyoInstance;\n}) {\n if (!block) {\n return null;\n }\n\n const Component = block.component ? flyo.state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (flyo.state.showMissingComponentAlert) {\n return (\n <div style={{ border: '1px solid #fff', padding: '1rem', marginBottom: '1rem', backgroundColor: 'red' }}>\n Component <b>{block.component}</b> not found.\n </div>\n );\n }\n\n return null;\n}\n\n/**\n * NitroSlot renders nested blocks from a slot.\n * Used for recursive block rendering when blocks contain slots.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { NitroSlot } from '@flyo/nitro-next/server';\n *\n * export function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots?.content} flyo={flyo} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n flyo,\n}: {\n slot?: {\n content?: Block[];\n };\n flyo: FlyoInstance;\n}) {\n if (!slot?.content || !Array.isArray(slot.content)) {\n return null;\n }\n\n return (\n <>\n {slot.content.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n// ─── Factory Functions ───────────────────────────────────────────────────────\n\n/**\n * Create a page route handler for Nitro pages.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageRoute } from '@flyo/nitro-next/server';\n *\n * export default nitroPageRoute(flyo);\n * ```\n */\nexport function nitroPageRoute(flyo: FlyoInstance) {\n async function pageRoute(props: RouteParams) {\n const { page } = await flyo.pageResolveRoute(props);\n return <NitroPage page={page} flyo={flyo} />;\n }\n return pageRoute;\n}\n\n/**\n * Create a metadata generator for Nitro pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroPageGenerateMetadata(flyo);\n * ```\n */\nexport function nitroPageGenerateMetadata(flyo: FlyoInstance) {\n return async (props: RouteParams): Promise<Metadata> => {\n const { page } = await flyo.pageResolveRoute(props);\n\n const meta = page.meta_json;\n const title = meta?.title || page.title || '';\n const description = meta?.description ?? '';\n const image = meta?.image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}\n\n/**\n * Create a static params generator for Nitro pages.\n * Returns a Next.js generateStaticParams function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateStaticParams } from '@flyo/nitro-next/server';\n *\n * export const generateStaticParams = nitroPageGenerateStaticParams(flyo);\n * ```\n */\nexport function nitroPageGenerateStaticParams(flyo: FlyoInstance) {\n return async () => {\n const cfg = await flyo.getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n };\n}\n\n/**\n * Create an entity route handler with a custom resolver.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityRoute } from '@flyo/nitro-next/server';\n *\n * const resolver = async (params) => {\n * const { slug } = await params;\n * return flyo.getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n *\n * export default nitroEntityRoute(flyo, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>,\n * });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n\n async function entityRoute(props: EntityRouteParams<T>) {\n const entity = await cachedResolver(props);\n\n if (options.render) {\n return options.render(entity);\n }\n\n return <div>{entity.entity?.entity_title}</div>;\n }\n return entityRoute;\n}\n\n/**\n * Create a metadata generator for entity detail pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroEntityGenerateMetadata(flyo, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityGenerateMetadata<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n\n return async (props: EntityRouteParams<T>): Promise<Metadata> => {\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title || '';\n const description = entity.entity?.entity_teaser ?? '';\n const image = entity.entity?.entity_image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAsB;AAEtB,wBAAyB;AACzB,8BAWO;AA2RD;AAxMC,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAUiB;AACf,QAAM,gBAAgB,IAAI,sCAAc,EAAE,QAAQ,YAAY,CAAC;AAE/D,QAAM,QAAoB;AAAA,IACxB;AAAA,IACA,MAAM,QAAQ;AAAA,IACd,SAAS,WAAW;AAAA,IACpB,YAAY,cAAc,CAAC;AAAA,IAC3B,2BAA2B,6BAA6B,YAAY;AAAA,IACpE,UAAU,YAAY;AAAA,IACtB,gBAAgB,kBAAkB;AAAA,IAClC,gBAAgB,kBAAkB;AAAA,EACpC;AAEA,QAAM,qBAAiB,oBAAM,YAAqC;AAChE,UAAM,YAAY,IAAI,kCAAU,aAAa;AAC7C,UAAM,UAAU,MAAM,QAAQ;AAC9B,WAAO,UAAU,OAAO,EAAE,MAAM,QAAQ,CAAC;AAAA,EAC3C,CAAC;AAED,QAAM,uBAAmB,oBAAM,OAAO,EAAE,OAAO,MAAmB;AAChE,UAAM,EAAE,KAAK,IAAI,MAAM;AACvB,UAAM,OAAO,MAAM,KAAK,GAAG,KAAK;AAEhC,UAAM,MAAM,MAAM,eAAe;AAEjC,QAAI,CAAC,IAAI,OAAO,SAAS,IAAI,GAAG;AAC9B,sCAAS;AAAA,IACX;AAEA,UAAM,OAAO,MAAM,IAAI,iCAAS,aAAa,EAC1C,KAAK,EAAE,MAAM,KAAK,CAAC,EACnB,MAAM,CAAC,UAAmB;AACzB,cAAQ,MAAM,wBAAwB,MAAM,KAAK;AACjD,sCAAS;AAAA,IACX,CAAC;AAEH,QAAI,CAAC,MAAM;AACT,sCAAS;AAAA,IACX;AAEA,WAAO,EAAE,MAAM,MAAM,IAAI;AAAA,EAC3B,CAAC;AAED,QAAM,UAAU,YAA4C;AAC1D,UAAM,aAAa,IAAI,mCAAW,aAAa;AAC/C,UAAM,cAAc,MAAM,QAAQ;AAElC,QAAI,CAAC,MAAM,SAAS;AAClB,YAAM,IAAI,MAAM,0DAA0D;AAAA,IAC5E;AAEA,UAAM,QAAQ,MAAM,WAAW,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC5D,UAAM,eAAe,MAAM,QAAQ,SAAS,GAAG,IAAI,MAAM,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAEtF,WAAO,MAAM,IAAI,CAAC,SAAS;AACzB,UAAI,OAAO;AAEX,UAAI,KAAK,UAAU,OAAO,KAAK,WAAW,UAAU;AAClD,cAAM,cAAc,OAAO,OAAO,KAAK,MAAM;AAC7C,YAAI,YAAY,SAAS,GAAG;AAC1B,iBAAO,YAAY,CAAC;AAAA,QACtB;AAAA,MACF;AAEA,UAAI,CAAC,QAAQ,KAAK,aAAa;AAC7B,eAAO,KAAK;AAAA,MACd;AAEA,YAAM,YAAY,QAAQ,CAAC,KAAK,WAAW,GAAG,IAAI,IAAI,IAAI,KAAK;AAE/D,aAAO;AAAA,QACL,KAAK,GAAG,YAAY,GAAG,SAAS;AAAA,QAChC,cAAc,oBAAI,KAAK;AAAA,MACzB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe,MAAM,IAAI,iCAAS,aAAa;AAAA,IAC/C,kBAAkB,MAAM,IAAI,oCAAY,aAAa;AAAA,IACrD,iBAAiB,MAAM,IAAI,mCAAW,aAAa;AAAA,IACnD,gBAAgB,MAAM,IAAI,kCAAU,aAAa;AAAA,IACjD;AAAA,IACA;AAAA,EACF;AACF;AAOA,IAAM,UAAU,CAAC,KAAa,WAAW,OAAe;AACtD,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,UAAa,UAAU,IAAI;AACvC,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,SAAO;AACT;AAKA,SAAS,2BACP,UACkD;AAClD,aAAO,oBAAM,OAAO,EAAE,OAAO,MAA4B;AACvD,UAAM,SAAS,MAAM,SAAS,MAAM;AAEpC,QAAI,CAAC,QAAQ;AACX,sCAAS;AAAA,IACX;AAEA,WAAO;AAAA,EACT,CAAC;AACH;AAeA,eAAsB,eAAe,EAAE,KAAK,GAA2B;AACrE,MAAI;AACF,UAAM,SAAS,MAAM,KAAK,eAAe;AACzC,UAAM,EAAE,MAAM,IAAI;AAElB,UAAM,OAAO,QAAQ,YAAY,GAAG;AACpC,UAAM,qBAAqB,QAAQ,wBAAwB,GAAG;AAC9D,UAAM,qBAAqB,QAAQ,yBAAyB,GAAG;AAC/D,UAAM,UAAU,QAAQ,WAAW,EAAE;AAErC,UAAM,aAAa,MAAM,eAAe;AACxC,UAAM,QAAQ,OAAO,eAAe,WAAW,aAAa;AAC5D,UAAM,YAAY,MAAM,WAAW,IAAI,IACnC,eACA,MAAM,WAAW,IAAI,IACrB,YACA;AAEJ,UAAM,QAAQ,MAAM;AAEpB,UAAM,aAAa,OAAO,OAAO,SAAS,SAAS,KAAK;AACxD,UAAM,gBAAgB,OAAO,OAAO,aAChC,IAAI,KAAK,OAAO,MAAM,aAAa,GAAI,EAAE,eAAe,SAAS;AAAA,MAC/D,KAAK;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,IACV,CAAC,IACD;AAEJ,UAAM,iBAAiB;AAAA,MACrB,YAAY,KAAK;AAAA,MACjB,OAAO,IAAI;AAAA,MACX,WAAW,UAAU;AAAA,MACrB,eAAe,aAAa;AAAA,MAC5B,aAAa,SAAS;AAAA,MACtB,OAAO,kBAAkB;AAAA,MACzB,QAAQ,kBAAkB;AAAA,IAC5B;AAEA,QAAI,SAAS;AACX,qBAAe,KAAK,WAAW,OAAO,EAAE;AAAA,IAC1C;AAEA,UAAM,YAAY,eAAe,KAAK,KAAK;AAE3C,WACE,4CAAC,cAAS,yBAAyB,EAAE,QAAQ,QAAQ,SAAS,OAAO,GAAG,0BAAwB,MAAC;AAAA,EAErG,QAAQ;AACN,WAAO,4CAAC,cAAS,yBAAyB,EAAE,QAAQ,wCAAwC,GAAG,0BAAwB,MAAC;AAAA,EAC1H;AACF;AAMO,SAAS,kBAAkB,EAAE,OAAO,GAAuB;AAChE,MAAI,CAAC,QAAQ,QAAQ;AACnB,WAAO;AAAA,EACT;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,yBAAyB;AAAA,QACvB,QAAQ,KAAK,UAAU,OAAO,MAAM,EAAE,QAAQ,MAAM,SAAS;AAAA,MAC/D;AAAA;AAAA,EACF;AAEJ;AAKO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AACF,GAGG;AACD,MAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,QAAQ,KAAK,IAAI,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,SACE,2EACG,eAAK,KAAK,IAAI,CAAC,OAAc,UAC5B;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA;AAAA;AAAA,IAFK,MAAM,OAAO;AAAA,EAGpB,CACD,GACH;AAEJ;AAKO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AACF,GAGG;AACD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,MAAM,YAAY,KAAK,MAAM,WAAW,MAAM,SAAS,IAAI;AAE7E,MAAI,WAAW;AACb,WAAO,4CAAC,aAAU,OAAc;AAAA,EAClC;AAEA,MAAI,KAAK,MAAM,2BAA2B;AACxC,WACE,6CAAC,SAAI,OAAO,EAAE,QAAQ,kBAAkB,SAAS,QAAQ,cAAc,QAAQ,iBAAiB,MAAM,GAAG;AAAA;AAAA,MAC7F,4CAAC,OAAG,gBAAM,WAAU;AAAA,MAAI;AAAA,OACpC;AAAA,EAEJ;AAEA,SAAO;AACT;AAoBO,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AACF,GAKG;AACD,MAAI,CAAC,MAAM,WAAW,CAAC,MAAM,QAAQ,KAAK,OAAO,GAAG;AAClD,WAAO;AAAA,EACT;AAEA,SACE,2EACG,eAAK,QAAQ,IAAI,CAAC,OAAc,UAC/B;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA;AAAA;AAAA,IAFK,MAAM,OAAO;AAAA,EAGpB,CACD,GACH;AAEJ;AAgBO,SAAS,eAAe,MAAoB;AACjD,iBAAe,UAAU,OAAoB;AAC3C,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,iBAAiB,KAAK;AAClD,WAAO,4CAAC,aAAU,MAAY,MAAY;AAAA,EAC5C;AACA,SAAO;AACT;AAcO,SAAS,0BAA0B,MAAoB;AAC5D,SAAO,OAAO,UAA0C;AACtD,UAAM,EAAE,KAAK,IAAI,MAAM,KAAK,iBAAiB,KAAK;AAElD,UAAM,OAAO,KAAK;AAClB,UAAM,QAAQ,MAAM,SAAS,KAAK,SAAS;AAC3C,UAAM,cAAc,MAAM,eAAe;AACzC,UAAM,QAAQ,MAAM,SAAS;AAE7B,UAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAC/D,UAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAE/D,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,QAC/B,MAAM;AAAA,MACR;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACF;AAcO,SAAS,8BAA8B,MAAoB;AAChE,SAAO,YAAY;AACjB,UAAM,MAAM,MAAM,KAAK,eAAe;AACtC,UAAM,QAAQ,IAAI,SAAS,CAAC;AAE5B,WAAO,MAAM,IAAI,CAAC,UAAkB;AAAA,MAClC,MAAM,SAAS,KAAK,SAAY,KAAK,MAAM,GAAG;AAAA,IAChD,EAAE;AAAA,EACJ;AACF;AAuBO,SAAS,iBACd,MACA,SAIA;AACA,QAAM,iBAAiB,2BAA2B,QAAQ,QAAQ;AAElE,iBAAe,YAAY,OAA6B;AACtD,UAAM,SAAS,MAAM,eAAe,KAAK;AAEzC,QAAI,QAAQ,QAAQ;AAClB,aAAO,QAAQ,OAAO,MAAM;AAAA,IAC9B;AAEA,WAAO,4CAAC,SAAK,iBAAO,QAAQ,cAAa;AAAA,EAC3C;AACA,SAAO;AACT;AAeO,SAAS,4BACd,MACA,SAGA;AACA,QAAM,iBAAiB,2BAA2B,QAAQ,QAAQ;AAElE,SAAO,OAAO,UAAmD;AAC/D,UAAM,SAAS,MAAM,eAAe,KAAK;AAEzC,UAAM,QAAQ,OAAO,QAAQ,gBAAgB;AAC7C,UAAM,cAAc,OAAO,QAAQ,iBAAiB;AACpD,UAAM,QAAQ,OAAO,QAAQ,gBAAgB;AAE7C,UAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAC/D,UAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAE/D,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,QAC/B,MAAM;AAAA,MACR;AAAA,MACA,SAAS;AAAA,QACP,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.tsx","../src/i18n.ts"],"sourcesContent":["import { cache } from 'react';\nimport type { Metadata, MetadataRoute } from 'next';\nimport { notFound } from 'next/navigation';\nimport { headers } from 'next/headers';\nimport {\n Page,\n Block,\n Entity,\n ConfigApi,\n ConfigResponse,\n Configuration,\n PagesApi,\n EntitiesApi,\n SitemapApi,\n SearchApi,\n type Translation\n} from '@flyo/nitro-typescript';\n\n// Re-export the framework-agnostic language-links helper + types so they are\n// available from `@flyo/nitro-next/server` alongside the rest of the server API.\n// (Also imported locally below, for the request-scoped switcher store.)\nimport { getLanguageLinks, type FlyoLanguageLink } from './i18n';\nexport { getLanguageLinks };\nexport type { FlyoLanguageLink };\nexport type { Translation } from '@flyo/nitro-typescript';\n\n/**\n * Read-only configuration state\n */\nexport interface NitroState {\n readonly accessToken: string;\n /** The single default language (back-compat). Prefer `defaultLocale` for i18n. */\n readonly lang: string | null;\n /** All locale shortcodes the site supports, e.g. `['de', 'en']`. Empty for single-language setups. */\n readonly locales: string[];\n /** The primary/default locale (no URL prefix). Matches `config.nitro.primary_language`. */\n readonly defaultLocale: string | null;\n readonly baseUrl: string | null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly components: Record<string, any>;\n readonly showMissingComponentAlert: boolean;\n readonly liveEdit: boolean;\n readonly serverCacheTtl: number;\n readonly clientCacheTtl: number;\n}\n\n/**\n * Route params type for Next.js catch-all routes\n */\ntype RouteParams = {\n params: Promise<{ slug?: string[] }>;\n};\n\n/**\n * Generic route params type for entity routes\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Entity resolver function type\n * Users provide this to resolve entities from their route params\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;\n\n/**\n * The Flyo instance returned by initNitro().\n * Carries all API access methods and configuration state.\n */\nexport interface FlyoInstance {\n /** Read-only configuration state */\n readonly state: NitroState;\n /**\n * Fetch and cache the Nitro CMS configuration (React-cached per requested locale).\n * When `lang` is omitted, the active request locale is used (from the `x-flyo-locale`\n * header set by the proxy), falling back to `defaultLocale`.\n */\n getNitroConfig(lang?: string): Promise<ConfigResponse>;\n /** Create a PagesApi client */\n getNitroPages(): PagesApi;\n /** Create an EntitiesApi client */\n getNitroEntities(): EntitiesApi;\n /** Create a SitemapApi client */\n getNitroSitemap(): SitemapApi;\n /** Create a SearchApi client */\n getNitroSearch(): SearchApi;\n /**\n * Resolve the active request locale from the `x-flyo-locale` header (set by the\n * proxy middleware), falling back to `defaultLocale`. Use it inside entity\n * resolvers or to set `<html lang>`.\n */\n getRequestLocale(): Promise<string | undefined>;\n /** Resolve a page from catch-all route params (React-cached per request) */\n pageResolveRoute(props: RouteParams): Promise<{ page: Page; path: string; lang: string | undefined; cfg: ConfigResponse }>;\n /** Generate a Next.js sitemap from Flyo Nitro content */\n sitemap(): Promise<MetadataRoute.Sitemap>;\n}\n\n/**\n * Initialize and return a FlyoInstance.\n *\n * Call once in your flyo.config.tsx and export the result.\n * The returned object carries all methods bound to the configuration,\n * eliminating the need for global state.\n *\n * @example\n * ```tsx\n * // flyo.config.tsx\n * import { initNitro } from '@flyo/nitro-next/server';\n *\n * export const flyo = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN || '',\n * lang: 'en',\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * components: { HeroBanner, Text },\n * });\n * ```\n */\nexport function initNitro({\n accessToken,\n lang,\n locales,\n defaultLocale,\n baseUrl,\n components,\n showMissingComponentAlert,\n liveEdit,\n serverCacheTtl,\n clientCacheTtl,\n}: {\n accessToken: string;\n lang?: string;\n /** All supported locale shortcodes, e.g. `['de', 'en']`. Enables per-request i18n. */\n locales?: string[];\n /** The primary/default locale (no URL prefix). Defaults to `lang`. Should match `config.nitro.primary_language`. */\n defaultLocale?: string;\n baseUrl?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components?: Record<string, any>;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): FlyoInstance {\n const configuration = new Configuration({ apiKey: accessToken });\n\n const resolvedDefaultLocale = defaultLocale ?? lang ?? null;\n const resolvedLocales = locales ?? (resolvedDefaultLocale ? [resolvedDefaultLocale] : []);\n\n const state: NitroState = {\n accessToken,\n lang: lang ?? null,\n locales: resolvedLocales,\n defaultLocale: resolvedDefaultLocale,\n baseUrl: baseUrl ?? null,\n components: components ?? {},\n showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,\n liveEdit: liveEdit ?? false,\n serverCacheTtl: serverCacheTtl ?? 1200,\n clientCacheTtl: clientCacheTtl ?? 900,\n };\n\n const getRequestLocale = async (): Promise<string | undefined> => {\n try {\n const requestHeaders = await headers();\n const headerLocale = requestHeaders.get('x-flyo-locale');\n if (headerLocale) {\n return headerLocale;\n }\n } catch {\n // `headers()` throws outside a request scope (build time, tests, …) — fall back below.\n }\n return state.defaultLocale ?? state.lang ?? undefined;\n };\n\n // React-cached per requested locale, so different languages don't collide on\n // one memoized value and the same locale is only fetched once per request.\n const fetchConfig = cache(async (lang?: string): Promise<ConfigResponse> => {\n const configApi = new ConfigApi(configuration);\n return configApi.config({ lang: lang ?? undefined });\n });\n\n const getNitroConfig = async (lang?: string): Promise<ConfigResponse> => {\n const useLang = lang ?? (await getRequestLocale());\n return fetchConfig(useLang);\n };\n\n const pageResolveRoute = cache(async ({ params }: RouteParams) => {\n const { slug } = await params;\n const segments = slug ?? [];\n // Pages are addressed by their full, globally-unique slug (locale prefix\n // included), which is exactly what the catch-all captures — pass it through.\n const path = segments.join('/');\n\n // Derive the active locale from the leading path segment when it is a\n // configured locale (matches the Laravel adapter's `request()->segment(1)`).\n const firstSegment = segments[0];\n const lang = firstSegment && state.locales.includes(firstSegment)\n ? firstSegment\n : (state.defaultLocale ?? undefined);\n\n const cfg = await getNitroConfig(lang);\n\n if (!cfg.pages?.includes(path)) {\n notFound();\n }\n\n const page = await new PagesApi(configuration)\n .page({ slug: path, lang })\n .catch((error: unknown) => {\n console.error('Error fetching page:', path, error);\n notFound();\n });\n\n if (!page) {\n notFound();\n }\n\n // Publish this page's switcher links so a switcher in shared chrome (e.g. a\n // footer in the root layout) can read them via `readLanguageLinks()`.\n publishLanguageLinks(getLanguageLinks(page.translation, { currentLang: lang, locales: state.locales }));\n\n return { page, path, lang, cfg };\n });\n\n const sitemap = async (): Promise<MetadataRoute.Sitemap> => {\n const sitemapApi = new SitemapApi(configuration);\n const sitemapLang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured. Please set it in initNitro().');\n }\n\n const items = await sitemapApi.sitemap({ lang: sitemapLang });\n const cleanBaseUrl = state.baseUrl.endsWith('/') ? state.baseUrl.slice(0, -1) : state.baseUrl;\n\n return items.map((item) => {\n let path = '';\n\n if (item.routes && typeof item.routes === 'object') {\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified: new Date(),\n };\n });\n };\n\n return {\n state,\n getNitroConfig,\n getRequestLocale,\n getNitroPages: () => new PagesApi(configuration),\n getNitroEntities: () => new EntitiesApi(configuration),\n getNitroSitemap: () => new SitemapApi(configuration),\n getNitroSearch: () => new SearchApi(configuration),\n pageResolveRoute,\n sitemap,\n };\n}\n\n// ─── Request-scoped language-switcher store ──────────────────────────────────\n//\n// A page's / entity's `translation[]` is resolved deep in the route tree (in the\n// catch-all page route or an entity detail route). A language switcher, however,\n// usually lives in *shared chrome* — a footer, say — that is rendered by the\n// **root layout**. In the App Router the root layout is an *ancestor* of the\n// page, so it cannot receive the page's data as props: data only flows down.\n//\n// This store bridges that gap. The page/entity route **publishes** its resolved\n// links; the switcher in the footer **reads** them. It is a per-request deferred,\n// created once per request via React `cache()` (so the layout and the page see\n// the *same* instance), holding a promise the footer awaits.\n//\n// Why the reader awaits a promise instead of reading a plain value: the root\n// layout (and therefore its footer) renders *above*, and concurrently with, the\n// page — so a synchronous read could run before the page has resolved. Awaiting\n// suspends the footer until the links are published, regardless of render order.\n\nexport interface LanguageLinksStore {\n /** Publish the links once. Later calls are ignored (first publish wins). */\n publish(links: FlyoLanguageLink[]): void;\n /** A promise that resolves when the links are published. */\n read(): Promise<FlyoLanguageLink[]>;\n}\n\n/**\n * Create a single deferred language-links channel: one writer, many readers,\n * order-independent. Low-level — most code uses {@link publishLanguageLinks} /\n * {@link readLanguageLinks}, which wrap this in a per-request `cache()`.\n */\nexport function createLanguageLinksStore(): LanguageLinksStore {\n let resolve!: (links: FlyoLanguageLink[]) => void;\n const promise = new Promise<FlyoLanguageLink[]>((r) => {\n resolve = r;\n });\n let settled = false;\n return {\n publish(links: FlyoLanguageLink[]) {\n // First publish wins — a route resolves once per request, and both the\n // render pass and `generateMetadata` share this store, so guard against a\n // double resolve.\n if (!settled) {\n settled = true;\n resolve(links);\n }\n },\n read() {\n return promise;\n },\n };\n}\n\n// `cache()` gives one store per request, shared across the whole server\n// component tree for that request. A fresh request gets a fresh store.\nconst languageLinksStore = cache(createLanguageLinksStore);\n\n/**\n * Publish the current route's language-switcher links into the request-scoped\n * store, so a switcher rendered in shared chrome (e.g. a footer in the root\n * layout) can read them via {@link readLanguageLinks}.\n *\n * The Flyo route helpers call this for you: `pageResolveRoute` (and therefore\n * `nitroPageRoute`) publishes the page's links, and `nitroEntityRoute` /\n * `nitroEntityGenerateMetadata` publish the entity's. You only need to call it\n * yourself on routes that render the shared switcher **without** going through\n * those helpers — a custom page, or `not-found.tsx` — where you would otherwise\n * leave the footer waiting forever. Pass a fallback there, e.g.\n * `publishLanguageLinks(getLanguageLinks(undefined, { currentLang, locales }))`.\n *\n * Only the first call per request takes effect.\n */\nexport function publishLanguageLinks(links: FlyoLanguageLink[]): void {\n languageLinksStore().publish(links);\n}\n\n/**\n * Await the current route's language-switcher links from shared chrome — a\n * footer, header, or any component in the root layout. Resolves with whatever\n * the active page/entity route passed to {@link publishLanguageLinks}.\n *\n * Because it awaits, the reader suspends until the links are published, so it\n * works no matter whether the layout or the page renders first. Wrap the reading\n * component in `<Suspense>` so the rest of the layout can stream while it waits.\n *\n * @example\n * ```tsx\n * // components/LanguageSwitcher.tsx (server component)\n * import { readLanguageLinks } from '@flyo/nitro-next/server';\n *\n * export async function LanguageSwitcher() {\n * const links = await readLanguageLinks();\n * return (\n * <nav aria-label=\"Language\">\n * {links.map((l) =>\n * l.exists\n * ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>\n * : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>,\n * )}\n * </nav>\n * );\n * }\n * ```\n */\nexport function readLanguageLinks(): Promise<FlyoLanguageLink[]> {\n return languageLinksStore().read();\n}\n\n// ─── Helper ──────────────────────────────────────────────────────────────────\n\n/**\n * Helper function to read environment variables with fallback\n */\nconst readEnv = (key: string, fallback = \"\"): string => {\n const value = process.env[key];\n if (value !== undefined && value !== \"\") {\n return String(value);\n }\n return fallback;\n};\n\n/**\n * Build Next.js `alternates` (hreflang + canonical) from a page's or entity's\n * `translation[]`. Returns `undefined` when there are no linked translations.\n */\nfunction buildLanguageAlternates(\n translations: Translation[] | undefined,\n currentLang?: string,\n): Metadata['alternates'] | undefined {\n const languages: Record<string, string> = {};\n let canonical: string | undefined;\n\n for (const t of translations ?? []) {\n const shortcode = t.language?.shortcode;\n if (shortcode && t.href) {\n languages[shortcode] = t.href;\n if (currentLang && shortcode === currentLang) {\n canonical = t.href;\n }\n }\n }\n\n if (Object.keys(languages).length === 0) {\n return undefined;\n }\n\n return canonical ? { canonical, languages } : { languages };\n}\n\n/**\n * Internal helper to wrap and cache entity resolvers.\n *\n * Also publishes the entity's language-switcher links into the request-scoped\n * store (see {@link publishLanguageLinks}) so a switcher in shared chrome can\n * read them via `readLanguageLinks()`.\n */\nfunction createCachedEntityResolver<T>(\n flyo: FlyoInstance,\n resolver: EntityResolver<T>\n): (props: EntityRouteParams<T>) => Promise<Entity> {\n return cache(async ({ params }: EntityRouteParams<T>) => {\n const entity = await resolver(params);\n\n if (!entity) {\n notFound();\n }\n\n publishLanguageLinks(\n getLanguageLinks(entity.translation, { currentLang: entity.language, locales: flyo.state.locales }),\n );\n\n return entity;\n });\n}\n\n// ─── Server Components ───────────────────────────────────────────────────────\n\n/**\n * NitroDebugInfo Component\n *\n * Async server component that outputs debug information as an HTML comment.\n * Resolves config internally from the flyo instance.\n *\n * @example\n * ```tsx\n * <NitroDebugInfo flyo={flyo} />\n * ```\n */\nexport async function NitroDebugInfo({ flyo }: { flyo: FlyoInstance }) {\n try {\n const config = await flyo.getNitroConfig();\n const { state } = flyo;\n\n const mode = readEnv(\"NODE_ENV\", \"-\");\n const vercelDeploymentId = readEnv(\"VERCEL_DEPLOYMENT_ID\", \"-\");\n const vercelGitCommitSha = readEnv(\"VERCEL_GIT_COMMIT_SHA\", \"-\");\n const version = readEnv(\"VERSION\", \"\");\n\n const tokenValue = state.accessToken || \"\";\n const token = typeof tokenValue === \"string\" ? tokenValue : \"\";\n const tokenType = token.startsWith(\"p-\")\n ? \"production\"\n : token.startsWith(\"d-\")\n ? \"develop\"\n : \"unknown\";\n\n const debug = state.liveEdit;\n\n const apiVersion = config.nitro?.version?.toString() || \"-\";\n const apiLastUpdate = config.nitro?.updated_at\n ? new Date(config.nitro.updated_at * 1000).toLocaleString(\"de-CH\", {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n })\n : \"-\";\n\n const debugInfoParts = [\n `liveedit:${debug}`,\n `env:${mode}`,\n `version:${apiVersion}`,\n `versiondate:${apiLastUpdate}`,\n `tokentype:${tokenType}`,\n `did:${vercelDeploymentId}`,\n `csha:${vercelGitCommitSha}`,\n ];\n\n if (version) {\n debugInfoParts.push(`release:${version}`);\n }\n\n const debugInfo = debugInfoParts.join(\" | \");\n\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch {\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Renders a JSON-LD structured data script tag from an Entity's jsonld field.\n * Safely escapes HTML to prevent XSS.\n */\nexport function NitroEntityJsonLd({ entity }: { entity: Entity }) {\n if (!entity?.jsonld) {\n return null;\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(entity.jsonld).replace(/</g, '\\\\u003c'),\n }}\n />\n );\n}\n\n/**\n * NitroPage renders all blocks from a Flyo page.\n */\nexport function NitroPage({\n page,\n flyo,\n}: {\n page: Page;\n flyo: FlyoInstance;\n}) {\n if (!page?.json || !Array.isArray(page.json)) {\n return null;\n }\n\n return (\n <>\n {page.json.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n/**\n * NitroBlock renders a single block using the registered component.\n */\nexport function NitroBlock({\n block,\n flyo,\n}: {\n block: Block;\n flyo: FlyoInstance;\n}) {\n if (!block) {\n return null;\n }\n\n const Component = block.component ? flyo.state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (flyo.state.showMissingComponentAlert) {\n return (\n <div style={{ border: '1px solid #fff', padding: '1rem', marginBottom: '1rem', backgroundColor: 'red' }}>\n Component <b>{block.component}</b> not found.\n </div>\n );\n }\n\n return null;\n}\n\n/**\n * NitroSlot renders nested blocks from a slot.\n * Used for recursive block rendering when blocks contain slots.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { NitroSlot } from '@flyo/nitro-next/server';\n *\n * export function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots?.content} flyo={flyo} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n flyo,\n}: {\n slot?: {\n content?: Block[];\n };\n flyo: FlyoInstance;\n}) {\n if (!slot?.content || !Array.isArray(slot.content)) {\n return null;\n }\n\n return (\n <>\n {slot.content.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n// ─── Factory Functions ───────────────────────────────────────────────────────\n\n/**\n * Create a page route handler for Nitro pages.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageRoute } from '@flyo/nitro-next/server';\n *\n * export default nitroPageRoute(flyo);\n * ```\n */\nexport function nitroPageRoute(flyo: FlyoInstance) {\n async function pageRoute(props: RouteParams) {\n const { page } = await flyo.pageResolveRoute(props);\n return <NitroPage page={page} flyo={flyo} />;\n }\n return pageRoute;\n}\n\n/**\n * Create a metadata generator for Nitro pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroPageGenerateMetadata(flyo);\n * ```\n */\nexport function nitroPageGenerateMetadata(flyo: FlyoInstance) {\n return async (props: RouteParams): Promise<Metadata> => {\n const { page, lang } = await flyo.pageResolveRoute(props);\n\n const meta = page.meta_json;\n const title = meta?.title || page.title || '';\n const description = meta?.description ?? '';\n const image = meta?.image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n const alternates = buildLanguageAlternates(page.translation, lang);\n\n return {\n title,\n description,\n ...(alternates ? { alternates } : {}),\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}\n\n/**\n * Create a static params generator for Nitro pages.\n * Returns a Next.js generateStaticParams function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateStaticParams } from '@flyo/nitro-next/server';\n *\n * export const generateStaticParams = nitroPageGenerateStaticParams(flyo);\n * ```\n */\nexport function nitroPageGenerateStaticParams(flyo: FlyoInstance) {\n return async () => {\n const cfg = await flyo.getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n };\n}\n\n/**\n * Create an entity route handler with a custom resolver.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityRoute } from '@flyo/nitro-next/server';\n *\n * const resolver = async (params) => {\n * const { slug } = await params;\n * return flyo.getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n *\n * export default nitroEntityRoute(flyo, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>,\n * });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(flyo, options.resolver);\n\n async function entityRoute(props: EntityRouteParams<T>) {\n const entity = await cachedResolver(props);\n\n if (options.render) {\n return options.render(entity);\n }\n\n return <div>{entity.entity?.entity_title}</div>;\n }\n return entityRoute;\n}\n\n/**\n * Create a metadata generator for entity detail pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroEntityGenerateMetadata(flyo, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityGenerateMetadata<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n }\n) {\n const cachedResolver = createCachedEntityResolver(flyo, options.resolver);\n\n return async (props: EntityRouteParams<T>): Promise<Metadata> => {\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title || '';\n const description = entity.entity?.entity_teaser ?? '';\n const image = entity.entity?.entity_image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n const alternates = buildLanguageAlternates(entity.translation, entity.language);\n\n return {\n title,\n description,\n ...(alternates ? { alternates } : {}),\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}","import type { Translation } from '@flyo/nitro-typescript';\n\n/**\n * A single language option for a language switcher, derived from a page's or\n * entity's `translation[]`. Framework-agnostic plain data — render it however\n * you like.\n */\nexport interface FlyoLanguageLink {\n /** Locale shortcode, e.g. `\"de\"`. */\n shortcode: string;\n /** Full language name from the CMS translation, e.g. `\"Deutsch\"` (when available). */\n name?: string;\n /** Fully-resolved localized href, or `null` when this locale has no linked translation. */\n href: string | null;\n /** Localized page/entity title, when available. */\n title?: string;\n /** `true` when this entry is the currently active locale. */\n isCurrent: boolean;\n /** `true` when a linked translation actually exists for this locale. */\n exists: boolean;\n}\n\n/**\n * Map a page's or entity's `translation[]` into a switcher-ready array of typed\n * links — the building block for a language switcher.\n *\n * Flyo only returns `translation` entries for languages that actually have a\n * translation. Pass `options.locales` (e.g. `flyo.state.locales`) to also get an\n * entry for every configured locale that is *missing* a translation — those come\n * back as `{ href: null, exists: false }` so you can render a fallback (a disabled\n * item, a link to the home page, …).\n *\n * Pure — no React or server-only APIs, so it is safe to call from server *or*\n * client components.\n *\n * This is a pure *data* helper — it maps a `translation[]` to switcher links and\n * renders nothing. **The Flyo route helpers already call it for you and publish\n * the result**, so page and entity routes need no switcher code at all; the\n * footer just reads the store (see `publishLanguageLinks` / `readLanguageLinks`\n * in `@flyo/nitro-next/server`). Call this directly only when you publish links\n * by hand from a raw `translation[]` — e.g. on a custom route Flyo doesn't\n * resolve:\n *\n * ```tsx\n * getLanguageLinks(translations, { currentLang, locales });\n * ```\n *\n * **Render each link as a native `<a>`, not `next/link`'s `<Link>`.** A language\n * switch must refresh the shared chrome — the localized nav, footer and\n * `<html lang>` — that lives in the root layout. In the Next.js App Router, soft\n * (client-side) navigation re-renders only the page segment, *not* shared\n * layouts, so a `<Link>` would leave that chrome in the previous language while\n * only the page body updates. A plain `<a>` triggers a full-document navigation,\n * forcing a fresh server render in the new locale so every part updates. (Regular\n * nav links can stay `<Link>` — the nav is identical within a language.)\n *\n * @example\n * ```tsx\n * const links = getLanguageLinks(page.translation, {\n * currentLang: lang,\n * locales: flyo.state.locales,\n * });\n * // Native <a> (full-document nav) — NOT next/link, so shared layout chrome\n * // re-renders in the new locale.\n * // links.map(l => l.exists\n * // ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>\n * // : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>)\n * ```\n */\nexport function getLanguageLinks(\n translations: Translation[] | undefined,\n options?: { currentLang?: string; locales?: string[] },\n): FlyoLanguageLink[] {\n const currentLang = options?.currentLang;\n\n const byShortcode = new Map<string, Translation>();\n for (const t of translations ?? []) {\n const shortcode = t.language?.shortcode;\n if (shortcode) {\n byShortcode.set(shortcode, t);\n }\n }\n\n const toLink = (shortcode: string, t: Translation | undefined): FlyoLanguageLink => ({\n shortcode,\n name: t?.language?.name,\n href: t?.href ?? null,\n title: t?.title,\n isCurrent: shortcode === currentLang,\n exists: t?.href != null,\n });\n\n // When the full set of locales is known, emit an entry for every one so\n // callers can render fallbacks for languages that have no translation.\n if (options?.locales && options.locales.length > 0) {\n return options.locales.map((shortcode) => toLink(shortcode, byShortcode.get(shortcode)));\n }\n\n // Otherwise, just surface the translations that exist.\n return (translations ?? [])\n .filter((t) => t.language?.shortcode)\n .map((t) => toLink(t.language!.shortcode!, t));\n}\n"],"mappings":"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","names":["lang"]}
|
package/dist/server.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/server.tsx
|
|
2
2
|
import { cache } from "react";
|
|
3
3
|
import { notFound } from "next/navigation";
|
|
4
|
+
import { headers } from "next/headers";
|
|
4
5
|
import {
|
|
5
6
|
ConfigApi,
|
|
6
7
|
Configuration,
|
|
@@ -9,10 +10,38 @@ import {
|
|
|
9
10
|
SitemapApi,
|
|
10
11
|
SearchApi
|
|
11
12
|
} from "@flyo/nitro-typescript";
|
|
13
|
+
|
|
14
|
+
// src/i18n.ts
|
|
15
|
+
function getLanguageLinks(translations, options) {
|
|
16
|
+
const currentLang = options?.currentLang;
|
|
17
|
+
const byShortcode = /* @__PURE__ */ new Map();
|
|
18
|
+
for (const t of translations ?? []) {
|
|
19
|
+
const shortcode = t.language?.shortcode;
|
|
20
|
+
if (shortcode) {
|
|
21
|
+
byShortcode.set(shortcode, t);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const toLink = (shortcode, t) => ({
|
|
25
|
+
shortcode,
|
|
26
|
+
name: t?.language?.name,
|
|
27
|
+
href: t?.href ?? null,
|
|
28
|
+
title: t?.title,
|
|
29
|
+
isCurrent: shortcode === currentLang,
|
|
30
|
+
exists: t?.href != null
|
|
31
|
+
});
|
|
32
|
+
if (options?.locales && options.locales.length > 0) {
|
|
33
|
+
return options.locales.map((shortcode) => toLink(shortcode, byShortcode.get(shortcode)));
|
|
34
|
+
}
|
|
35
|
+
return (translations ?? []).filter((t) => t.language?.shortcode).map((t) => toLink(t.language.shortcode, t));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/server.tsx
|
|
12
39
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
40
|
function initNitro({
|
|
14
41
|
accessToken,
|
|
15
42
|
lang,
|
|
43
|
+
locales,
|
|
44
|
+
defaultLocale,
|
|
16
45
|
baseUrl,
|
|
17
46
|
components,
|
|
18
47
|
showMissingComponentAlert,
|
|
@@ -21,9 +50,13 @@ function initNitro({
|
|
|
21
50
|
clientCacheTtl
|
|
22
51
|
}) {
|
|
23
52
|
const configuration = new Configuration({ apiKey: accessToken });
|
|
53
|
+
const resolvedDefaultLocale = defaultLocale ?? lang ?? null;
|
|
54
|
+
const resolvedLocales = locales ?? (resolvedDefaultLocale ? [resolvedDefaultLocale] : []);
|
|
24
55
|
const state = {
|
|
25
56
|
accessToken,
|
|
26
57
|
lang: lang ?? null,
|
|
58
|
+
locales: resolvedLocales,
|
|
59
|
+
defaultLocale: resolvedDefaultLocale,
|
|
27
60
|
baseUrl: baseUrl ?? null,
|
|
28
61
|
components: components ?? {},
|
|
29
62
|
showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,
|
|
@@ -31,26 +64,44 @@ function initNitro({
|
|
|
31
64
|
serverCacheTtl: serverCacheTtl ?? 1200,
|
|
32
65
|
clientCacheTtl: clientCacheTtl ?? 900
|
|
33
66
|
};
|
|
34
|
-
const
|
|
67
|
+
const getRequestLocale = async () => {
|
|
68
|
+
try {
|
|
69
|
+
const requestHeaders = await headers();
|
|
70
|
+
const headerLocale = requestHeaders.get("x-flyo-locale");
|
|
71
|
+
if (headerLocale) {
|
|
72
|
+
return headerLocale;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
return state.defaultLocale ?? state.lang ?? void 0;
|
|
77
|
+
};
|
|
78
|
+
const fetchConfig = cache(async (lang2) => {
|
|
35
79
|
const configApi = new ConfigApi(configuration);
|
|
36
|
-
|
|
37
|
-
return configApi.config({ lang: useLang });
|
|
80
|
+
return configApi.config({ lang: lang2 ?? void 0 });
|
|
38
81
|
});
|
|
82
|
+
const getNitroConfig = async (lang2) => {
|
|
83
|
+
const useLang = lang2 ?? await getRequestLocale();
|
|
84
|
+
return fetchConfig(useLang);
|
|
85
|
+
};
|
|
39
86
|
const pageResolveRoute = cache(async ({ params }) => {
|
|
40
87
|
const { slug } = await params;
|
|
41
|
-
const
|
|
42
|
-
const
|
|
88
|
+
const segments = slug ?? [];
|
|
89
|
+
const path = segments.join("/");
|
|
90
|
+
const firstSegment = segments[0];
|
|
91
|
+
const lang2 = firstSegment && state.locales.includes(firstSegment) ? firstSegment : state.defaultLocale ?? void 0;
|
|
92
|
+
const cfg = await getNitroConfig(lang2);
|
|
43
93
|
if (!cfg.pages?.includes(path)) {
|
|
44
94
|
notFound();
|
|
45
95
|
}
|
|
46
|
-
const page = await new PagesApi(configuration).page({ slug: path }).catch((error) => {
|
|
96
|
+
const page = await new PagesApi(configuration).page({ slug: path, lang: lang2 }).catch((error) => {
|
|
47
97
|
console.error("Error fetching page:", path, error);
|
|
48
98
|
notFound();
|
|
49
99
|
});
|
|
50
100
|
if (!page) {
|
|
51
101
|
notFound();
|
|
52
102
|
}
|
|
53
|
-
|
|
103
|
+
publishLanguageLinks(getLanguageLinks(page.translation, { currentLang: lang2, locales: state.locales }));
|
|
104
|
+
return { page, path, lang: lang2, cfg };
|
|
54
105
|
});
|
|
55
106
|
const sitemap = async () => {
|
|
56
107
|
const sitemapApi = new SitemapApi(configuration);
|
|
@@ -81,6 +132,7 @@ function initNitro({
|
|
|
81
132
|
return {
|
|
82
133
|
state,
|
|
83
134
|
getNitroConfig,
|
|
135
|
+
getRequestLocale,
|
|
84
136
|
getNitroPages: () => new PagesApi(configuration),
|
|
85
137
|
getNitroEntities: () => new EntitiesApi(configuration),
|
|
86
138
|
getNitroSitemap: () => new SitemapApi(configuration),
|
|
@@ -89,6 +141,31 @@ function initNitro({
|
|
|
89
141
|
sitemap
|
|
90
142
|
};
|
|
91
143
|
}
|
|
144
|
+
function createLanguageLinksStore() {
|
|
145
|
+
let resolve;
|
|
146
|
+
const promise = new Promise((r) => {
|
|
147
|
+
resolve = r;
|
|
148
|
+
});
|
|
149
|
+
let settled = false;
|
|
150
|
+
return {
|
|
151
|
+
publish(links) {
|
|
152
|
+
if (!settled) {
|
|
153
|
+
settled = true;
|
|
154
|
+
resolve(links);
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
read() {
|
|
158
|
+
return promise;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
var languageLinksStore = cache(createLanguageLinksStore);
|
|
163
|
+
function publishLanguageLinks(links) {
|
|
164
|
+
languageLinksStore().publish(links);
|
|
165
|
+
}
|
|
166
|
+
function readLanguageLinks() {
|
|
167
|
+
return languageLinksStore().read();
|
|
168
|
+
}
|
|
92
169
|
var readEnv = (key, fallback = "") => {
|
|
93
170
|
const value = process.env[key];
|
|
94
171
|
if (value !== void 0 && value !== "") {
|
|
@@ -96,12 +173,32 @@ var readEnv = (key, fallback = "") => {
|
|
|
96
173
|
}
|
|
97
174
|
return fallback;
|
|
98
175
|
};
|
|
99
|
-
function
|
|
176
|
+
function buildLanguageAlternates(translations, currentLang) {
|
|
177
|
+
const languages = {};
|
|
178
|
+
let canonical;
|
|
179
|
+
for (const t of translations ?? []) {
|
|
180
|
+
const shortcode = t.language?.shortcode;
|
|
181
|
+
if (shortcode && t.href) {
|
|
182
|
+
languages[shortcode] = t.href;
|
|
183
|
+
if (currentLang && shortcode === currentLang) {
|
|
184
|
+
canonical = t.href;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (Object.keys(languages).length === 0) {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
return canonical ? { canonical, languages } : { languages };
|
|
192
|
+
}
|
|
193
|
+
function createCachedEntityResolver(flyo, resolver) {
|
|
100
194
|
return cache(async ({ params }) => {
|
|
101
195
|
const entity = await resolver(params);
|
|
102
196
|
if (!entity) {
|
|
103
197
|
notFound();
|
|
104
198
|
}
|
|
199
|
+
publishLanguageLinks(
|
|
200
|
+
getLanguageLinks(entity.translation, { currentLang: entity.language, locales: flyo.state.locales })
|
|
201
|
+
);
|
|
105
202
|
return entity;
|
|
106
203
|
});
|
|
107
204
|
}
|
|
@@ -218,16 +315,18 @@ function nitroPageRoute(flyo) {
|
|
|
218
315
|
}
|
|
219
316
|
function nitroPageGenerateMetadata(flyo) {
|
|
220
317
|
return async (props) => {
|
|
221
|
-
const { page } = await flyo.pageResolveRoute(props);
|
|
318
|
+
const { page, lang } = await flyo.pageResolveRoute(props);
|
|
222
319
|
const meta = page.meta_json;
|
|
223
320
|
const title = meta?.title || page.title || "";
|
|
224
321
|
const description = meta?.description ?? "";
|
|
225
322
|
const image = meta?.image ?? "";
|
|
226
323
|
const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : void 0;
|
|
227
324
|
const twImage = image ? `${image}/thumb/1200x600?format=jpg` : void 0;
|
|
325
|
+
const alternates = buildLanguageAlternates(page.translation, lang);
|
|
228
326
|
return {
|
|
229
327
|
title,
|
|
230
328
|
description,
|
|
329
|
+
...alternates ? { alternates } : {},
|
|
231
330
|
openGraph: {
|
|
232
331
|
title,
|
|
233
332
|
description,
|
|
@@ -253,7 +352,7 @@ function nitroPageGenerateStaticParams(flyo) {
|
|
|
253
352
|
};
|
|
254
353
|
}
|
|
255
354
|
function nitroEntityRoute(flyo, options) {
|
|
256
|
-
const cachedResolver = createCachedEntityResolver(options.resolver);
|
|
355
|
+
const cachedResolver = createCachedEntityResolver(flyo, options.resolver);
|
|
257
356
|
async function entityRoute(props) {
|
|
258
357
|
const entity = await cachedResolver(props);
|
|
259
358
|
if (options.render) {
|
|
@@ -264,7 +363,7 @@ function nitroEntityRoute(flyo, options) {
|
|
|
264
363
|
return entityRoute;
|
|
265
364
|
}
|
|
266
365
|
function nitroEntityGenerateMetadata(flyo, options) {
|
|
267
|
-
const cachedResolver = createCachedEntityResolver(options.resolver);
|
|
366
|
+
const cachedResolver = createCachedEntityResolver(flyo, options.resolver);
|
|
268
367
|
return async (props) => {
|
|
269
368
|
const entity = await cachedResolver(props);
|
|
270
369
|
const title = entity.entity?.entity_title || "";
|
|
@@ -272,9 +371,11 @@ function nitroEntityGenerateMetadata(flyo, options) {
|
|
|
272
371
|
const image = entity.entity?.entity_image ?? "";
|
|
273
372
|
const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : void 0;
|
|
274
373
|
const twImage = image ? `${image}/thumb/1200x600?format=jpg` : void 0;
|
|
374
|
+
const alternates = buildLanguageAlternates(entity.translation, entity.language);
|
|
275
375
|
return {
|
|
276
376
|
title,
|
|
277
377
|
description,
|
|
378
|
+
...alternates ? { alternates } : {},
|
|
278
379
|
openGraph: {
|
|
279
380
|
title,
|
|
280
381
|
description,
|
|
@@ -296,11 +397,15 @@ export {
|
|
|
296
397
|
NitroEntityJsonLd,
|
|
297
398
|
NitroPage,
|
|
298
399
|
NitroSlot,
|
|
400
|
+
createLanguageLinksStore,
|
|
401
|
+
getLanguageLinks,
|
|
299
402
|
initNitro,
|
|
300
403
|
nitroEntityGenerateMetadata,
|
|
301
404
|
nitroEntityRoute,
|
|
302
405
|
nitroPageGenerateMetadata,
|
|
303
406
|
nitroPageGenerateStaticParams,
|
|
304
|
-
nitroPageRoute
|
|
407
|
+
nitroPageRoute,
|
|
408
|
+
publishLanguageLinks,
|
|
409
|
+
readLanguageLinks
|
|
305
410
|
};
|
|
306
411
|
//# sourceMappingURL=server.mjs.map
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.tsx"],"sourcesContent":["import { cache } from 'react';\nimport type { Metadata, MetadataRoute } from 'next';\nimport { notFound } from 'next/navigation';\nimport {\n Page,\n Block,\n Entity,\n ConfigApi,\n ConfigResponse,\n Configuration,\n PagesApi,\n EntitiesApi,\n SitemapApi,\n SearchApi\n} from '@flyo/nitro-typescript';\n\n/**\n * Read-only configuration state\n */\nexport interface NitroState {\n readonly accessToken: string;\n readonly lang: string | null;\n readonly baseUrl: string | null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly components: Record<string, any>;\n readonly showMissingComponentAlert: boolean;\n readonly liveEdit: boolean;\n readonly serverCacheTtl: number;\n readonly clientCacheTtl: number;\n}\n\n/**\n * Route params type for Next.js catch-all routes\n */\ntype RouteParams = {\n params: Promise<{ slug?: string[] }>;\n};\n\n/**\n * Generic route params type for entity routes\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Entity resolver function type\n * Users provide this to resolve entities from their route params\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;\n\n/**\n * The Flyo instance returned by initNitro().\n * Carries all API access methods and configuration state.\n */\nexport interface FlyoInstance {\n /** Read-only configuration state */\n readonly state: NitroState;\n /** Fetch and cache the Nitro CMS configuration (React-cached per request) */\n getNitroConfig(): Promise<ConfigResponse>;\n /** Create a PagesApi client */\n getNitroPages(): PagesApi;\n /** Create an EntitiesApi client */\n getNitroEntities(): EntitiesApi;\n /** Create a SitemapApi client */\n getNitroSitemap(): SitemapApi;\n /** Create a SearchApi client */\n getNitroSearch(): SearchApi;\n /** Resolve a page from catch-all route params (React-cached per request) */\n pageResolveRoute(props: RouteParams): Promise<{ page: Page; path: string; cfg: ConfigResponse }>;\n /** Generate a Next.js sitemap from Flyo Nitro content */\n sitemap(): Promise<MetadataRoute.Sitemap>;\n}\n\n/**\n * Initialize and return a FlyoInstance.\n *\n * Call once in your flyo.config.tsx and export the result.\n * The returned object carries all methods bound to the configuration,\n * eliminating the need for global state.\n *\n * @example\n * ```tsx\n * // flyo.config.tsx\n * import { initNitro } from '@flyo/nitro-next/server';\n *\n * export const flyo = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN || '',\n * lang: 'en',\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * components: { HeroBanner, Text },\n * });\n * ```\n */\nexport function initNitro({\n accessToken,\n lang,\n baseUrl,\n components,\n showMissingComponentAlert,\n liveEdit,\n serverCacheTtl,\n clientCacheTtl,\n}: {\n accessToken: string;\n lang?: string;\n baseUrl?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components?: Record<string, any>;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): FlyoInstance {\n const configuration = new Configuration({ apiKey: accessToken });\n\n const state: NitroState = {\n accessToken,\n lang: lang ?? null,\n baseUrl: baseUrl ?? null,\n components: components ?? {},\n showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,\n liveEdit: liveEdit ?? false,\n serverCacheTtl: serverCacheTtl ?? 1200,\n clientCacheTtl: clientCacheTtl ?? 900,\n };\n\n const getNitroConfig = cache(async (): Promise<ConfigResponse> => {\n const configApi = new ConfigApi(configuration);\n const useLang = state.lang ?? undefined;\n return configApi.config({ lang: useLang });\n });\n\n const pageResolveRoute = cache(async ({ params }: RouteParams) => {\n const { slug } = await params;\n const path = slug?.join('/') ?? '';\n\n const cfg = await getNitroConfig();\n\n if (!cfg.pages?.includes(path)) {\n notFound();\n }\n\n const page = await new PagesApi(configuration)\n .page({ slug: path })\n .catch((error: unknown) => {\n console.error('Error fetching page:', path, error);\n notFound();\n });\n\n if (!page) {\n notFound();\n }\n\n return { page, path, cfg };\n });\n\n const sitemap = async (): Promise<MetadataRoute.Sitemap> => {\n const sitemapApi = new SitemapApi(configuration);\n const sitemapLang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured. Please set it in initNitro().');\n }\n\n const items = await sitemapApi.sitemap({ lang: sitemapLang });\n const cleanBaseUrl = state.baseUrl.endsWith('/') ? state.baseUrl.slice(0, -1) : state.baseUrl;\n\n return items.map((item) => {\n let path = '';\n\n if (item.routes && typeof item.routes === 'object') {\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified: new Date(),\n };\n });\n };\n\n return {\n state,\n getNitroConfig,\n getNitroPages: () => new PagesApi(configuration),\n getNitroEntities: () => new EntitiesApi(configuration),\n getNitroSitemap: () => new SitemapApi(configuration),\n getNitroSearch: () => new SearchApi(configuration),\n pageResolveRoute,\n sitemap,\n };\n}\n\n// ─── Helper ──────────────────────────────────────────────────────────────────\n\n/**\n * Helper function to read environment variables with fallback\n */\nconst readEnv = (key: string, fallback = \"\"): string => {\n const value = process.env[key];\n if (value !== undefined && value !== \"\") {\n return String(value);\n }\n return fallback;\n};\n\n/**\n * Internal helper to wrap and cache entity resolvers\n */\nfunction createCachedEntityResolver<T>(\n resolver: EntityResolver<T>\n): (props: EntityRouteParams<T>) => Promise<Entity> {\n return cache(async ({ params }: EntityRouteParams<T>) => {\n const entity = await resolver(params);\n\n if (!entity) {\n notFound();\n }\n\n return entity;\n });\n}\n\n// ─── Server Components ───────────────────────────────────────────────────────\n\n/**\n * NitroDebugInfo Component\n *\n * Async server component that outputs debug information as an HTML comment.\n * Resolves config internally from the flyo instance.\n *\n * @example\n * ```tsx\n * <NitroDebugInfo flyo={flyo} />\n * ```\n */\nexport async function NitroDebugInfo({ flyo }: { flyo: FlyoInstance }) {\n try {\n const config = await flyo.getNitroConfig();\n const { state } = flyo;\n\n const mode = readEnv(\"NODE_ENV\", \"-\");\n const vercelDeploymentId = readEnv(\"VERCEL_DEPLOYMENT_ID\", \"-\");\n const vercelGitCommitSha = readEnv(\"VERCEL_GIT_COMMIT_SHA\", \"-\");\n const version = readEnv(\"VERSION\", \"\");\n\n const tokenValue = state.accessToken || \"\";\n const token = typeof tokenValue === \"string\" ? tokenValue : \"\";\n const tokenType = token.startsWith(\"p-\")\n ? \"production\"\n : token.startsWith(\"d-\")\n ? \"develop\"\n : \"unknown\";\n\n const debug = state.liveEdit;\n\n const apiVersion = config.nitro?.version?.toString() || \"-\";\n const apiLastUpdate = config.nitro?.updated_at\n ? new Date(config.nitro.updated_at * 1000).toLocaleString(\"de-CH\", {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n })\n : \"-\";\n\n const debugInfoParts = [\n `liveedit:${debug}`,\n `env:${mode}`,\n `version:${apiVersion}`,\n `versiondate:${apiLastUpdate}`,\n `tokentype:${tokenType}`,\n `did:${vercelDeploymentId}`,\n `csha:${vercelGitCommitSha}`,\n ];\n\n if (version) {\n debugInfoParts.push(`release:${version}`);\n }\n\n const debugInfo = debugInfoParts.join(\" | \");\n\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch {\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Renders a JSON-LD structured data script tag from an Entity's jsonld field.\n * Safely escapes HTML to prevent XSS.\n */\nexport function NitroEntityJsonLd({ entity }: { entity: Entity }) {\n if (!entity?.jsonld) {\n return null;\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(entity.jsonld).replace(/</g, '\\\\u003c'),\n }}\n />\n );\n}\n\n/**\n * NitroPage renders all blocks from a Flyo page.\n */\nexport function NitroPage({\n page,\n flyo,\n}: {\n page: Page;\n flyo: FlyoInstance;\n}) {\n if (!page?.json || !Array.isArray(page.json)) {\n return null;\n }\n\n return (\n <>\n {page.json.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n/**\n * NitroBlock renders a single block using the registered component.\n */\nexport function NitroBlock({\n block,\n flyo,\n}: {\n block: Block;\n flyo: FlyoInstance;\n}) {\n if (!block) {\n return null;\n }\n\n const Component = block.component ? flyo.state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (flyo.state.showMissingComponentAlert) {\n return (\n <div style={{ border: '1px solid #fff', padding: '1rem', marginBottom: '1rem', backgroundColor: 'red' }}>\n Component <b>{block.component}</b> not found.\n </div>\n );\n }\n\n return null;\n}\n\n/**\n * NitroSlot renders nested blocks from a slot.\n * Used for recursive block rendering when blocks contain slots.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { NitroSlot } from '@flyo/nitro-next/server';\n *\n * export function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots?.content} flyo={flyo} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n flyo,\n}: {\n slot?: {\n content?: Block[];\n };\n flyo: FlyoInstance;\n}) {\n if (!slot?.content || !Array.isArray(slot.content)) {\n return null;\n }\n\n return (\n <>\n {slot.content.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n// ─── Factory Functions ───────────────────────────────────────────────────────\n\n/**\n * Create a page route handler for Nitro pages.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageRoute } from '@flyo/nitro-next/server';\n *\n * export default nitroPageRoute(flyo);\n * ```\n */\nexport function nitroPageRoute(flyo: FlyoInstance) {\n async function pageRoute(props: RouteParams) {\n const { page } = await flyo.pageResolveRoute(props);\n return <NitroPage page={page} flyo={flyo} />;\n }\n return pageRoute;\n}\n\n/**\n * Create a metadata generator for Nitro pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroPageGenerateMetadata(flyo);\n * ```\n */\nexport function nitroPageGenerateMetadata(flyo: FlyoInstance) {\n return async (props: RouteParams): Promise<Metadata> => {\n const { page } = await flyo.pageResolveRoute(props);\n\n const meta = page.meta_json;\n const title = meta?.title || page.title || '';\n const description = meta?.description ?? '';\n const image = meta?.image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}\n\n/**\n * Create a static params generator for Nitro pages.\n * Returns a Next.js generateStaticParams function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateStaticParams } from '@flyo/nitro-next/server';\n *\n * export const generateStaticParams = nitroPageGenerateStaticParams(flyo);\n * ```\n */\nexport function nitroPageGenerateStaticParams(flyo: FlyoInstance) {\n return async () => {\n const cfg = await flyo.getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n };\n}\n\n/**\n * Create an entity route handler with a custom resolver.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityRoute } from '@flyo/nitro-next/server';\n *\n * const resolver = async (params) => {\n * const { slug } = await params;\n * return flyo.getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n *\n * export default nitroEntityRoute(flyo, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>,\n * });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n\n async function entityRoute(props: EntityRouteParams<T>) {\n const entity = await cachedResolver(props);\n\n if (options.render) {\n return options.render(entity);\n }\n\n return <div>{entity.entity?.entity_title}</div>;\n }\n return entityRoute;\n}\n\n/**\n * Create a metadata generator for entity detail pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroEntityGenerateMetadata(flyo, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityGenerateMetadata<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n\n return async (props: EntityRouteParams<T>): Promise<Metadata> => {\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title || '';\n const description = entity.entity?.entity_teaser ?? '';\n const image = entity.entity?.entity_image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n return {\n title,\n description,\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.tsx","../src/i18n.ts"],"sourcesContent":["import { cache } from 'react';\nimport type { Metadata, MetadataRoute } from 'next';\nimport { notFound } from 'next/navigation';\nimport { headers } from 'next/headers';\nimport {\n Page,\n Block,\n Entity,\n ConfigApi,\n ConfigResponse,\n Configuration,\n PagesApi,\n EntitiesApi,\n SitemapApi,\n SearchApi,\n type Translation\n} from '@flyo/nitro-typescript';\n\n// Re-export the framework-agnostic language-links helper + types so they are\n// available from `@flyo/nitro-next/server` alongside the rest of the server API.\n// (Also imported locally below, for the request-scoped switcher store.)\nimport { getLanguageLinks, type FlyoLanguageLink } from './i18n';\nexport { getLanguageLinks };\nexport type { FlyoLanguageLink };\nexport type { Translation } from '@flyo/nitro-typescript';\n\n/**\n * Read-only configuration state\n */\nexport interface NitroState {\n readonly accessToken: string;\n /** The single default language (back-compat). Prefer `defaultLocale` for i18n. */\n readonly lang: string | null;\n /** All locale shortcodes the site supports, e.g. `['de', 'en']`. Empty for single-language setups. */\n readonly locales: string[];\n /** The primary/default locale (no URL prefix). Matches `config.nitro.primary_language`. */\n readonly defaultLocale: string | null;\n readonly baseUrl: string | null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly components: Record<string, any>;\n readonly showMissingComponentAlert: boolean;\n readonly liveEdit: boolean;\n readonly serverCacheTtl: number;\n readonly clientCacheTtl: number;\n}\n\n/**\n * Route params type for Next.js catch-all routes\n */\ntype RouteParams = {\n params: Promise<{ slug?: string[] }>;\n};\n\n/**\n * Generic route params type for entity routes\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Entity resolver function type\n * Users provide this to resolve entities from their route params\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;\n\n/**\n * The Flyo instance returned by initNitro().\n * Carries all API access methods and configuration state.\n */\nexport interface FlyoInstance {\n /** Read-only configuration state */\n readonly state: NitroState;\n /**\n * Fetch and cache the Nitro CMS configuration (React-cached per requested locale).\n * When `lang` is omitted, the active request locale is used (from the `x-flyo-locale`\n * header set by the proxy), falling back to `defaultLocale`.\n */\n getNitroConfig(lang?: string): Promise<ConfigResponse>;\n /** Create a PagesApi client */\n getNitroPages(): PagesApi;\n /** Create an EntitiesApi client */\n getNitroEntities(): EntitiesApi;\n /** Create a SitemapApi client */\n getNitroSitemap(): SitemapApi;\n /** Create a SearchApi client */\n getNitroSearch(): SearchApi;\n /**\n * Resolve the active request locale from the `x-flyo-locale` header (set by the\n * proxy middleware), falling back to `defaultLocale`. Use it inside entity\n * resolvers or to set `<html lang>`.\n */\n getRequestLocale(): Promise<string | undefined>;\n /** Resolve a page from catch-all route params (React-cached per request) */\n pageResolveRoute(props: RouteParams): Promise<{ page: Page; path: string; lang: string | undefined; cfg: ConfigResponse }>;\n /** Generate a Next.js sitemap from Flyo Nitro content */\n sitemap(): Promise<MetadataRoute.Sitemap>;\n}\n\n/**\n * Initialize and return a FlyoInstance.\n *\n * Call once in your flyo.config.tsx and export the result.\n * The returned object carries all methods bound to the configuration,\n * eliminating the need for global state.\n *\n * @example\n * ```tsx\n * // flyo.config.tsx\n * import { initNitro } from '@flyo/nitro-next/server';\n *\n * export const flyo = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN || '',\n * lang: 'en',\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * components: { HeroBanner, Text },\n * });\n * ```\n */\nexport function initNitro({\n accessToken,\n lang,\n locales,\n defaultLocale,\n baseUrl,\n components,\n showMissingComponentAlert,\n liveEdit,\n serverCacheTtl,\n clientCacheTtl,\n}: {\n accessToken: string;\n lang?: string;\n /** All supported locale shortcodes, e.g. `['de', 'en']`. Enables per-request i18n. */\n locales?: string[];\n /** The primary/default locale (no URL prefix). Defaults to `lang`. Should match `config.nitro.primary_language`. */\n defaultLocale?: string;\n baseUrl?: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components?: Record<string, any>;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): FlyoInstance {\n const configuration = new Configuration({ apiKey: accessToken });\n\n const resolvedDefaultLocale = defaultLocale ?? lang ?? null;\n const resolvedLocales = locales ?? (resolvedDefaultLocale ? [resolvedDefaultLocale] : []);\n\n const state: NitroState = {\n accessToken,\n lang: lang ?? null,\n locales: resolvedLocales,\n defaultLocale: resolvedDefaultLocale,\n baseUrl: baseUrl ?? null,\n components: components ?? {},\n showMissingComponentAlert: showMissingComponentAlert ?? liveEdit ?? false,\n liveEdit: liveEdit ?? false,\n serverCacheTtl: serverCacheTtl ?? 1200,\n clientCacheTtl: clientCacheTtl ?? 900,\n };\n\n const getRequestLocale = async (): Promise<string | undefined> => {\n try {\n const requestHeaders = await headers();\n const headerLocale = requestHeaders.get('x-flyo-locale');\n if (headerLocale) {\n return headerLocale;\n }\n } catch {\n // `headers()` throws outside a request scope (build time, tests, …) — fall back below.\n }\n return state.defaultLocale ?? state.lang ?? undefined;\n };\n\n // React-cached per requested locale, so different languages don't collide on\n // one memoized value and the same locale is only fetched once per request.\n const fetchConfig = cache(async (lang?: string): Promise<ConfigResponse> => {\n const configApi = new ConfigApi(configuration);\n return configApi.config({ lang: lang ?? undefined });\n });\n\n const getNitroConfig = async (lang?: string): Promise<ConfigResponse> => {\n const useLang = lang ?? (await getRequestLocale());\n return fetchConfig(useLang);\n };\n\n const pageResolveRoute = cache(async ({ params }: RouteParams) => {\n const { slug } = await params;\n const segments = slug ?? [];\n // Pages are addressed by their full, globally-unique slug (locale prefix\n // included), which is exactly what the catch-all captures — pass it through.\n const path = segments.join('/');\n\n // Derive the active locale from the leading path segment when it is a\n // configured locale (matches the Laravel adapter's `request()->segment(1)`).\n const firstSegment = segments[0];\n const lang = firstSegment && state.locales.includes(firstSegment)\n ? firstSegment\n : (state.defaultLocale ?? undefined);\n\n const cfg = await getNitroConfig(lang);\n\n if (!cfg.pages?.includes(path)) {\n notFound();\n }\n\n const page = await new PagesApi(configuration)\n .page({ slug: path, lang })\n .catch((error: unknown) => {\n console.error('Error fetching page:', path, error);\n notFound();\n });\n\n if (!page) {\n notFound();\n }\n\n // Publish this page's switcher links so a switcher in shared chrome (e.g. a\n // footer in the root layout) can read them via `readLanguageLinks()`.\n publishLanguageLinks(getLanguageLinks(page.translation, { currentLang: lang, locales: state.locales }));\n\n return { page, path, lang, cfg };\n });\n\n const sitemap = async (): Promise<MetadataRoute.Sitemap> => {\n const sitemapApi = new SitemapApi(configuration);\n const sitemapLang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured. Please set it in initNitro().');\n }\n\n const items = await sitemapApi.sitemap({ lang: sitemapLang });\n const cleanBaseUrl = state.baseUrl.endsWith('/') ? state.baseUrl.slice(0, -1) : state.baseUrl;\n\n return items.map((item) => {\n let path = '';\n\n if (item.routes && typeof item.routes === 'object') {\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified: new Date(),\n };\n });\n };\n\n return {\n state,\n getNitroConfig,\n getRequestLocale,\n getNitroPages: () => new PagesApi(configuration),\n getNitroEntities: () => new EntitiesApi(configuration),\n getNitroSitemap: () => new SitemapApi(configuration),\n getNitroSearch: () => new SearchApi(configuration),\n pageResolveRoute,\n sitemap,\n };\n}\n\n// ─── Request-scoped language-switcher store ──────────────────────────────────\n//\n// A page's / entity's `translation[]` is resolved deep in the route tree (in the\n// catch-all page route or an entity detail route). A language switcher, however,\n// usually lives in *shared chrome* — a footer, say — that is rendered by the\n// **root layout**. In the App Router the root layout is an *ancestor* of the\n// page, so it cannot receive the page's data as props: data only flows down.\n//\n// This store bridges that gap. The page/entity route **publishes** its resolved\n// links; the switcher in the footer **reads** them. It is a per-request deferred,\n// created once per request via React `cache()` (so the layout and the page see\n// the *same* instance), holding a promise the footer awaits.\n//\n// Why the reader awaits a promise instead of reading a plain value: the root\n// layout (and therefore its footer) renders *above*, and concurrently with, the\n// page — so a synchronous read could run before the page has resolved. Awaiting\n// suspends the footer until the links are published, regardless of render order.\n\nexport interface LanguageLinksStore {\n /** Publish the links once. Later calls are ignored (first publish wins). */\n publish(links: FlyoLanguageLink[]): void;\n /** A promise that resolves when the links are published. */\n read(): Promise<FlyoLanguageLink[]>;\n}\n\n/**\n * Create a single deferred language-links channel: one writer, many readers,\n * order-independent. Low-level — most code uses {@link publishLanguageLinks} /\n * {@link readLanguageLinks}, which wrap this in a per-request `cache()`.\n */\nexport function createLanguageLinksStore(): LanguageLinksStore {\n let resolve!: (links: FlyoLanguageLink[]) => void;\n const promise = new Promise<FlyoLanguageLink[]>((r) => {\n resolve = r;\n });\n let settled = false;\n return {\n publish(links: FlyoLanguageLink[]) {\n // First publish wins — a route resolves once per request, and both the\n // render pass and `generateMetadata` share this store, so guard against a\n // double resolve.\n if (!settled) {\n settled = true;\n resolve(links);\n }\n },\n read() {\n return promise;\n },\n };\n}\n\n// `cache()` gives one store per request, shared across the whole server\n// component tree for that request. A fresh request gets a fresh store.\nconst languageLinksStore = cache(createLanguageLinksStore);\n\n/**\n * Publish the current route's language-switcher links into the request-scoped\n * store, so a switcher rendered in shared chrome (e.g. a footer in the root\n * layout) can read them via {@link readLanguageLinks}.\n *\n * The Flyo route helpers call this for you: `pageResolveRoute` (and therefore\n * `nitroPageRoute`) publishes the page's links, and `nitroEntityRoute` /\n * `nitroEntityGenerateMetadata` publish the entity's. You only need to call it\n * yourself on routes that render the shared switcher **without** going through\n * those helpers — a custom page, or `not-found.tsx` — where you would otherwise\n * leave the footer waiting forever. Pass a fallback there, e.g.\n * `publishLanguageLinks(getLanguageLinks(undefined, { currentLang, locales }))`.\n *\n * Only the first call per request takes effect.\n */\nexport function publishLanguageLinks(links: FlyoLanguageLink[]): void {\n languageLinksStore().publish(links);\n}\n\n/**\n * Await the current route's language-switcher links from shared chrome — a\n * footer, header, or any component in the root layout. Resolves with whatever\n * the active page/entity route passed to {@link publishLanguageLinks}.\n *\n * Because it awaits, the reader suspends until the links are published, so it\n * works no matter whether the layout or the page renders first. Wrap the reading\n * component in `<Suspense>` so the rest of the layout can stream while it waits.\n *\n * @example\n * ```tsx\n * // components/LanguageSwitcher.tsx (server component)\n * import { readLanguageLinks } from '@flyo/nitro-next/server';\n *\n * export async function LanguageSwitcher() {\n * const links = await readLanguageLinks();\n * return (\n * <nav aria-label=\"Language\">\n * {links.map((l) =>\n * l.exists\n * ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>\n * : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>,\n * )}\n * </nav>\n * );\n * }\n * ```\n */\nexport function readLanguageLinks(): Promise<FlyoLanguageLink[]> {\n return languageLinksStore().read();\n}\n\n// ─── Helper ──────────────────────────────────────────────────────────────────\n\n/**\n * Helper function to read environment variables with fallback\n */\nconst readEnv = (key: string, fallback = \"\"): string => {\n const value = process.env[key];\n if (value !== undefined && value !== \"\") {\n return String(value);\n }\n return fallback;\n};\n\n/**\n * Build Next.js `alternates` (hreflang + canonical) from a page's or entity's\n * `translation[]`. Returns `undefined` when there are no linked translations.\n */\nfunction buildLanguageAlternates(\n translations: Translation[] | undefined,\n currentLang?: string,\n): Metadata['alternates'] | undefined {\n const languages: Record<string, string> = {};\n let canonical: string | undefined;\n\n for (const t of translations ?? []) {\n const shortcode = t.language?.shortcode;\n if (shortcode && t.href) {\n languages[shortcode] = t.href;\n if (currentLang && shortcode === currentLang) {\n canonical = t.href;\n }\n }\n }\n\n if (Object.keys(languages).length === 0) {\n return undefined;\n }\n\n return canonical ? { canonical, languages } : { languages };\n}\n\n/**\n * Internal helper to wrap and cache entity resolvers.\n *\n * Also publishes the entity's language-switcher links into the request-scoped\n * store (see {@link publishLanguageLinks}) so a switcher in shared chrome can\n * read them via `readLanguageLinks()`.\n */\nfunction createCachedEntityResolver<T>(\n flyo: FlyoInstance,\n resolver: EntityResolver<T>\n): (props: EntityRouteParams<T>) => Promise<Entity> {\n return cache(async ({ params }: EntityRouteParams<T>) => {\n const entity = await resolver(params);\n\n if (!entity) {\n notFound();\n }\n\n publishLanguageLinks(\n getLanguageLinks(entity.translation, { currentLang: entity.language, locales: flyo.state.locales }),\n );\n\n return entity;\n });\n}\n\n// ─── Server Components ───────────────────────────────────────────────────────\n\n/**\n * NitroDebugInfo Component\n *\n * Async server component that outputs debug information as an HTML comment.\n * Resolves config internally from the flyo instance.\n *\n * @example\n * ```tsx\n * <NitroDebugInfo flyo={flyo} />\n * ```\n */\nexport async function NitroDebugInfo({ flyo }: { flyo: FlyoInstance }) {\n try {\n const config = await flyo.getNitroConfig();\n const { state } = flyo;\n\n const mode = readEnv(\"NODE_ENV\", \"-\");\n const vercelDeploymentId = readEnv(\"VERCEL_DEPLOYMENT_ID\", \"-\");\n const vercelGitCommitSha = readEnv(\"VERCEL_GIT_COMMIT_SHA\", \"-\");\n const version = readEnv(\"VERSION\", \"\");\n\n const tokenValue = state.accessToken || \"\";\n const token = typeof tokenValue === \"string\" ? tokenValue : \"\";\n const tokenType = token.startsWith(\"p-\")\n ? \"production\"\n : token.startsWith(\"d-\")\n ? \"develop\"\n : \"unknown\";\n\n const debug = state.liveEdit;\n\n const apiVersion = config.nitro?.version?.toString() || \"-\";\n const apiLastUpdate = config.nitro?.updated_at\n ? new Date(config.nitro.updated_at * 1000).toLocaleString(\"de-CH\", {\n day: \"2-digit\",\n month: \"2-digit\",\n year: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n })\n : \"-\";\n\n const debugInfoParts = [\n `liveedit:${debug}`,\n `env:${mode}`,\n `version:${apiVersion}`,\n `versiondate:${apiLastUpdate}`,\n `tokentype:${tokenType}`,\n `did:${vercelDeploymentId}`,\n `csha:${vercelGitCommitSha}`,\n ];\n\n if (version) {\n debugInfoParts.push(`release:${version}`);\n }\n\n const debugInfo = debugInfoParts.join(\" | \");\n\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch {\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Renders a JSON-LD structured data script tag from an Entity's jsonld field.\n * Safely escapes HTML to prevent XSS.\n */\nexport function NitroEntityJsonLd({ entity }: { entity: Entity }) {\n if (!entity?.jsonld) {\n return null;\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(entity.jsonld).replace(/</g, '\\\\u003c'),\n }}\n />\n );\n}\n\n/**\n * NitroPage renders all blocks from a Flyo page.\n */\nexport function NitroPage({\n page,\n flyo,\n}: {\n page: Page;\n flyo: FlyoInstance;\n}) {\n if (!page?.json || !Array.isArray(page.json)) {\n return null;\n }\n\n return (\n <>\n {page.json.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n/**\n * NitroBlock renders a single block using the registered component.\n */\nexport function NitroBlock({\n block,\n flyo,\n}: {\n block: Block;\n flyo: FlyoInstance;\n}) {\n if (!block) {\n return null;\n }\n\n const Component = block.component ? flyo.state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (flyo.state.showMissingComponentAlert) {\n return (\n <div style={{ border: '1px solid #fff', padding: '1rem', marginBottom: '1rem', backgroundColor: 'red' }}>\n Component <b>{block.component}</b> not found.\n </div>\n );\n }\n\n return null;\n}\n\n/**\n * NitroSlot renders nested blocks from a slot.\n * Used for recursive block rendering when blocks contain slots.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { NitroSlot } from '@flyo/nitro-next/server';\n *\n * export function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots?.content} flyo={flyo} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n flyo,\n}: {\n slot?: {\n content?: Block[];\n };\n flyo: FlyoInstance;\n}) {\n if (!slot?.content || !Array.isArray(slot.content)) {\n return null;\n }\n\n return (\n <>\n {slot.content.map((block: Block, index: number) => (\n <NitroBlock\n key={block.uid || index}\n block={block}\n flyo={flyo}\n />\n ))}\n </>\n );\n}\n\n// ─── Factory Functions ───────────────────────────────────────────────────────\n\n/**\n * Create a page route handler for Nitro pages.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageRoute } from '@flyo/nitro-next/server';\n *\n * export default nitroPageRoute(flyo);\n * ```\n */\nexport function nitroPageRoute(flyo: FlyoInstance) {\n async function pageRoute(props: RouteParams) {\n const { page } = await flyo.pageResolveRoute(props);\n return <NitroPage page={page} flyo={flyo} />;\n }\n return pageRoute;\n}\n\n/**\n * Create a metadata generator for Nitro pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroPageGenerateMetadata(flyo);\n * ```\n */\nexport function nitroPageGenerateMetadata(flyo: FlyoInstance) {\n return async (props: RouteParams): Promise<Metadata> => {\n const { page, lang } = await flyo.pageResolveRoute(props);\n\n const meta = page.meta_json;\n const title = meta?.title || page.title || '';\n const description = meta?.description ?? '';\n const image = meta?.image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n const alternates = buildLanguageAlternates(page.translation, lang);\n\n return {\n title,\n description,\n ...(alternates ? { alternates } : {}),\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}\n\n/**\n * Create a static params generator for Nitro pages.\n * Returns a Next.js generateStaticParams function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroPageGenerateStaticParams } from '@flyo/nitro-next/server';\n *\n * export const generateStaticParams = nitroPageGenerateStaticParams(flyo);\n * ```\n */\nexport function nitroPageGenerateStaticParams(flyo: FlyoInstance) {\n return async () => {\n const cfg = await flyo.getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n };\n}\n\n/**\n * Create an entity route handler with a custom resolver.\n * Returns a Next.js page component function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityRoute } from '@flyo/nitro-next/server';\n *\n * const resolver = async (params) => {\n * const { slug } = await params;\n * return flyo.getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n *\n * export default nitroEntityRoute(flyo, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>,\n * });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(flyo, options.resolver);\n\n async function entityRoute(props: EntityRouteParams<T>) {\n const entity = await cachedResolver(props);\n\n if (options.render) {\n return options.render(entity);\n }\n\n return <div>{entity.entity?.entity_title}</div>;\n }\n return entityRoute;\n}\n\n/**\n * Create a metadata generator for entity detail pages.\n * Returns a Next.js generateMetadata function.\n *\n * @example\n * ```tsx\n * import { flyo } from '@/flyo.config';\n * import { nitroEntityGenerateMetadata } from '@flyo/nitro-next/server';\n *\n * export const generateMetadata = nitroEntityGenerateMetadata(flyo, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityGenerateMetadata<T = any>(\n flyo: FlyoInstance,\n options: {\n resolver: EntityResolver<T>;\n }\n) {\n const cachedResolver = createCachedEntityResolver(flyo, options.resolver);\n\n return async (props: EntityRouteParams<T>): Promise<Metadata> => {\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title || '';\n const description = entity.entity?.entity_teaser ?? '';\n const image = entity.entity?.entity_image ?? '';\n\n const ogImage = image ? `${image}/thumb/1200x630?format=jpg` : undefined;\n const twImage = image ? `${image}/thumb/1200x600?format=jpg` : undefined;\n\n const alternates = buildLanguageAlternates(entity.translation, entity.language);\n\n return {\n title,\n description,\n ...(alternates ? { alternates } : {}),\n openGraph: {\n title,\n description,\n images: ogImage ? [ogImage] : [],\n type: 'website',\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n images: twImage ? [twImage] : [],\n },\n };\n };\n}","import type { Translation } from '@flyo/nitro-typescript';\n\n/**\n * A single language option for a language switcher, derived from a page's or\n * entity's `translation[]`. Framework-agnostic plain data — render it however\n * you like.\n */\nexport interface FlyoLanguageLink {\n /** Locale shortcode, e.g. `\"de\"`. */\n shortcode: string;\n /** Full language name from the CMS translation, e.g. `\"Deutsch\"` (when available). */\n name?: string;\n /** Fully-resolved localized href, or `null` when this locale has no linked translation. */\n href: string | null;\n /** Localized page/entity title, when available. */\n title?: string;\n /** `true` when this entry is the currently active locale. */\n isCurrent: boolean;\n /** `true` when a linked translation actually exists for this locale. */\n exists: boolean;\n}\n\n/**\n * Map a page's or entity's `translation[]` into a switcher-ready array of typed\n * links — the building block for a language switcher.\n *\n * Flyo only returns `translation` entries for languages that actually have a\n * translation. Pass `options.locales` (e.g. `flyo.state.locales`) to also get an\n * entry for every configured locale that is *missing* a translation — those come\n * back as `{ href: null, exists: false }` so you can render a fallback (a disabled\n * item, a link to the home page, …).\n *\n * Pure — no React or server-only APIs, so it is safe to call from server *or*\n * client components.\n *\n * This is a pure *data* helper — it maps a `translation[]` to switcher links and\n * renders nothing. **The Flyo route helpers already call it for you and publish\n * the result**, so page and entity routes need no switcher code at all; the\n * footer just reads the store (see `publishLanguageLinks` / `readLanguageLinks`\n * in `@flyo/nitro-next/server`). Call this directly only when you publish links\n * by hand from a raw `translation[]` — e.g. on a custom route Flyo doesn't\n * resolve:\n *\n * ```tsx\n * getLanguageLinks(translations, { currentLang, locales });\n * ```\n *\n * **Render each link as a native `<a>`, not `next/link`'s `<Link>`.** A language\n * switch must refresh the shared chrome — the localized nav, footer and\n * `<html lang>` — that lives in the root layout. In the Next.js App Router, soft\n * (client-side) navigation re-renders only the page segment, *not* shared\n * layouts, so a `<Link>` would leave that chrome in the previous language while\n * only the page body updates. A plain `<a>` triggers a full-document navigation,\n * forcing a fresh server render in the new locale so every part updates. (Regular\n * nav links can stay `<Link>` — the nav is identical within a language.)\n *\n * @example\n * ```tsx\n * const links = getLanguageLinks(page.translation, {\n * currentLang: lang,\n * locales: flyo.state.locales,\n * });\n * // Native <a> (full-document nav) — NOT next/link, so shared layout chrome\n * // re-renders in the new locale.\n * // links.map(l => l.exists\n * // ? <a key={l.shortcode} href={l.href!} aria-current={l.isCurrent || undefined}>{l.name ?? l.shortcode}</a>\n * // : <span key={l.shortcode} aria-disabled>{l.shortcode}</span>)\n * ```\n */\nexport function getLanguageLinks(\n translations: Translation[] | undefined,\n options?: { currentLang?: string; locales?: string[] },\n): FlyoLanguageLink[] {\n const currentLang = options?.currentLang;\n\n const byShortcode = new Map<string, Translation>();\n for (const t of translations ?? []) {\n const shortcode = t.language?.shortcode;\n if (shortcode) {\n byShortcode.set(shortcode, t);\n }\n }\n\n const toLink = (shortcode: string, t: Translation | undefined): FlyoLanguageLink => ({\n shortcode,\n name: t?.language?.name,\n href: t?.href ?? null,\n title: t?.title,\n isCurrent: shortcode === currentLang,\n exists: t?.href != null,\n });\n\n // When the full set of locales is known, emit an entry for every one so\n // callers can render fallbacks for languages that have no translation.\n if (options?.locales && options.locales.length > 0) {\n return options.locales.map((shortcode) => toLink(shortcode, byShortcode.get(shortcode)));\n }\n\n // Otherwise, just surface the translations that exist.\n return (translations ?? [])\n .filter((t) => t.language?.shortcode)\n .map((t) => toLink(t.language!.shortcode!, t));\n}\n"],"mappings":"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","names":["lang"]}
|