@flyo/nitro-next 1.8.1 → 1.10.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 +11 -2
- package/dist/client.d.ts +11 -2
- package/dist/client.js +23 -4
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +23 -4
- package/dist/client.mjs.map +1 -1
- package/dist/proxy.js +2 -0
- package/dist/proxy.js.map +1 -1
- package/dist/proxy.mjs +2 -0
- package/dist/proxy.mjs.map +1 -1
- package/dist/server.d.mts +23 -1
- package/dist/server.d.ts +23 -1
- package/dist/server.js +16 -0
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +15 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +2 -5
package/dist/client.d.mts
CHANGED
|
@@ -36,6 +36,13 @@ declare function FlyoClientWrapper({ children, }: {
|
|
|
36
36
|
/**
|
|
37
37
|
* WYSIWYG component for rendering ProseMirror/TipTap JSON content
|
|
38
38
|
*
|
|
39
|
+
* Uses the `wysiwyg()` function from `@flyo/nitro-js-bridge` to convert
|
|
40
|
+
* nodes to HTML. All consecutive non-custom nodes are joined into a single
|
|
41
|
+
* HTML string so no extra wrapper `<div>` elements are added around each node.
|
|
42
|
+
*
|
|
43
|
+
* The component wraps all output in a single `<div>` with an optional
|
|
44
|
+
* `className` (defaults to `"wysiwyg"`).
|
|
45
|
+
*
|
|
39
46
|
* @example
|
|
40
47
|
* ```tsx
|
|
41
48
|
* import { FlyoWysiwyg } from '@flyo/nitro-next/client';
|
|
@@ -45,6 +52,7 @@ declare function FlyoClientWrapper({ children, }: {
|
|
|
45
52
|
* return (
|
|
46
53
|
* <FlyoWysiwyg
|
|
47
54
|
* json={block.content.json}
|
|
55
|
+
* className="wysiwyg"
|
|
48
56
|
* components={{
|
|
49
57
|
* image: CustomImage
|
|
50
58
|
* }}
|
|
@@ -53,8 +61,9 @@ declare function FlyoClientWrapper({ children, }: {
|
|
|
53
61
|
* }
|
|
54
62
|
* ```
|
|
55
63
|
*/
|
|
56
|
-
declare function FlyoWysiwyg({ json, components, }: {
|
|
64
|
+
declare function FlyoWysiwyg({ json, className, components, }: {
|
|
57
65
|
json: WysiwygJson;
|
|
66
|
+
className?: string;
|
|
58
67
|
components?: Record<string, React.ComponentType<{
|
|
59
68
|
node: WysiwygNode;
|
|
60
69
|
}>>;
|
|
@@ -111,4 +120,4 @@ declare function FlyoMetric({ entity }: {
|
|
|
111
120
|
entity: Entity;
|
|
112
121
|
}): null;
|
|
113
122
|
|
|
114
|
-
export { FlyoCdnLoader, FlyoClientWrapper, FlyoMetric, FlyoWysiwyg, editable, isProd };
|
|
123
|
+
export { FlyoCdnLoader, FlyoClientWrapper, FlyoMetric, FlyoWysiwyg, type WysiwygJson, type WysiwygNode, editable, isProd };
|
package/dist/client.d.ts
CHANGED
|
@@ -36,6 +36,13 @@ declare function FlyoClientWrapper({ children, }: {
|
|
|
36
36
|
/**
|
|
37
37
|
* WYSIWYG component for rendering ProseMirror/TipTap JSON content
|
|
38
38
|
*
|
|
39
|
+
* Uses the `wysiwyg()` function from `@flyo/nitro-js-bridge` to convert
|
|
40
|
+
* nodes to HTML. All consecutive non-custom nodes are joined into a single
|
|
41
|
+
* HTML string so no extra wrapper `<div>` elements are added around each node.
|
|
42
|
+
*
|
|
43
|
+
* The component wraps all output in a single `<div>` with an optional
|
|
44
|
+
* `className` (defaults to `"wysiwyg"`).
|
|
45
|
+
*
|
|
39
46
|
* @example
|
|
40
47
|
* ```tsx
|
|
41
48
|
* import { FlyoWysiwyg } from '@flyo/nitro-next/client';
|
|
@@ -45,6 +52,7 @@ declare function FlyoClientWrapper({ children, }: {
|
|
|
45
52
|
* return (
|
|
46
53
|
* <FlyoWysiwyg
|
|
47
54
|
* json={block.content.json}
|
|
55
|
+
* className="wysiwyg"
|
|
48
56
|
* components={{
|
|
49
57
|
* image: CustomImage
|
|
50
58
|
* }}
|
|
@@ -53,8 +61,9 @@ declare function FlyoClientWrapper({ children, }: {
|
|
|
53
61
|
* }
|
|
54
62
|
* ```
|
|
55
63
|
*/
|
|
56
|
-
declare function FlyoWysiwyg({ json, components, }: {
|
|
64
|
+
declare function FlyoWysiwyg({ json, className, components, }: {
|
|
57
65
|
json: WysiwygJson;
|
|
66
|
+
className?: string;
|
|
58
67
|
components?: Record<string, React.ComponentType<{
|
|
59
68
|
node: WysiwygNode;
|
|
60
69
|
}>>;
|
|
@@ -111,4 +120,4 @@ declare function FlyoMetric({ entity }: {
|
|
|
111
120
|
entity: Entity;
|
|
112
121
|
}): null;
|
|
113
122
|
|
|
114
|
-
export { FlyoCdnLoader, FlyoClientWrapper, FlyoMetric, FlyoWysiwyg, editable, isProd };
|
|
123
|
+
export { FlyoCdnLoader, FlyoClientWrapper, FlyoMetric, FlyoWysiwyg, type WysiwygJson, type WysiwygNode, editable, isProd };
|
package/dist/client.js
CHANGED
|
@@ -85,6 +85,7 @@ function FlyoClientWrapper({
|
|
|
85
85
|
}
|
|
86
86
|
function FlyoWysiwyg({
|
|
87
87
|
json,
|
|
88
|
+
className = "wysiwyg",
|
|
88
89
|
components = {}
|
|
89
90
|
}) {
|
|
90
91
|
let nodes = [];
|
|
@@ -97,13 +98,31 @@ function FlyoWysiwyg({
|
|
|
97
98
|
nodes = [json];
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
|
-
|
|
101
|
+
const hasCustomComponents = nodes.some((node) => components[node.type]);
|
|
102
|
+
if (!hasCustomComponents) {
|
|
103
|
+
const html = nodes.map((node) => (0, import_nitro_js_bridge.wysiwyg)(node)).join("");
|
|
104
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, dangerouslySetInnerHTML: { __html: html } });
|
|
105
|
+
}
|
|
106
|
+
const groups = [];
|
|
107
|
+
for (const node of nodes) {
|
|
101
108
|
const Component = components[node.type];
|
|
102
109
|
if (Component) {
|
|
103
|
-
|
|
110
|
+
groups.push({ type: "custom", component: Component, node });
|
|
111
|
+
} else {
|
|
112
|
+
const html = (0, import_nitro_js_bridge.wysiwyg)(node);
|
|
113
|
+
const last = groups[groups.length - 1];
|
|
114
|
+
if (last && last.type === "html") {
|
|
115
|
+
last.html += html;
|
|
116
|
+
} else {
|
|
117
|
+
groups.push({ type: "html", html });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: groups.map((group, index) => {
|
|
122
|
+
if (group.type === "custom") {
|
|
123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(group.component, { node: group.node }, index);
|
|
104
124
|
}
|
|
105
|
-
|
|
106
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: html } }, index);
|
|
125
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: group.html } }, index);
|
|
107
126
|
}) });
|
|
108
127
|
}
|
|
109
128
|
function FlyoCdnLoader({ src, width }) {
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { highlightAndClick, wysiwyg, reload} from '@flyo/nitro-js-bridge';\nimport { Block, Entity } from \"@flyo/nitro-typescript\";\nimport type { ImageLoaderProps } from 'next/image';\n\nconst FLYO_CDN_HOST = 'storage.flyo.cloud';\n\n/**\n * Check if running in production environment\n */\nexport const isProd = process.env.NODE_ENV === 'production';\n\n/**\n * Type for WYSIWYG node structure\n */\
|
|
1
|
+
{"version":3,"sources":["../src/client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { highlightAndClick, wysiwyg, reload} from '@flyo/nitro-js-bridge';\nimport { Block, Entity } from \"@flyo/nitro-typescript\";\nimport type { ImageLoaderProps } from 'next/image';\n\nconst FLYO_CDN_HOST = 'storage.flyo.cloud';\n\n/**\n * Check if running in production environment\n */\nexport const isProd = process.env.NODE_ENV === 'production';\n\n/**\n * Type for WYSIWYG node structure\n */\nexport interface WysiwygNode {\n type: string;\n content?: WysiwygNode[];\n [key: string]: unknown;\n}\n\n/**\n * Type for WYSIWYG JSON that can be a node, array of nodes, or doc structure\n */\nexport type WysiwygJson = WysiwygNode | WysiwygNode[] | { type: 'doc'; content: WysiwygNode[] };\n\n/**\n * Helper function to get editable props\n */\nexport function editable(block: Block): { 'data-flyo-uid'?: string } {\n if (typeof block.uid === 'string' && block.uid.trim() !== '') {\n return { 'data-flyo-uid': block.uid };\n }\n return {};\n}\n\n/**\n * Internal client component that sets up live editing functionality\n */\nexport function FlyoClientWrapper({ \n children,\n}: { \n children: React.ReactNode;\n}) {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n reload();\n\n const wireAll = () => {\n const elements = document.querySelectorAll('[data-flyo-uid]');\n elements.forEach((el) => {\n const uid = el.getAttribute('data-flyo-uid');\n if (uid && el instanceof HTMLElement) {\n highlightAndClick(uid, el);\n }\n });\n };\n\n wireAll();\n\n const observer = new MutationObserver((mutations) => {\n const hasRelevantChanges = mutations.some(mutation => \n Array.from(mutation.addedNodes).some(node => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element;\n return element.hasAttribute('data-flyo-uid') || \n element.querySelector('[data-flyo-uid]');\n }\n return false;\n })\n );\n\n if (hasRelevantChanges) {\n wireAll();\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n return () => {\n observer.disconnect();\n };\n }, []);\n\n return <>{children}</>;\n}\n\n/**\n * WYSIWYG component for rendering ProseMirror/TipTap JSON content\n * \n * Uses the `wysiwyg()` function from `@flyo/nitro-js-bridge` to convert\n * nodes to HTML. All consecutive non-custom nodes are joined into a single\n * HTML string so no extra wrapper `<div>` elements are added around each node.\n * \n * The component wraps all output in a single `<div>` with an optional\n * `className` (defaults to `\"wysiwyg\"`).\n * \n * @example\n * ```tsx\n * import { FlyoWysiwyg } from '@flyo/nitro-next/client';\n * import CustomImage from './CustomImage';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <FlyoWysiwyg \n * json={block.content.json} \n * className=\"wysiwyg\"\n * components={{\n * image: CustomImage\n * }} \n * />\n * );\n * }\n * ```\n */\nexport function FlyoWysiwyg({\n json,\n className = 'wysiwyg',\n components = {},\n}: {\n json: WysiwygJson;\n className?: string;\n components?: Record<string, React.ComponentType<{ node: WysiwygNode }>>;\n}) {\n let nodes: WysiwygNode[] = [];\n\n if (json) {\n if (Array.isArray(json)) {\n nodes = json;\n } else if ('type' in json && json.type === 'doc' && Array.isArray(json.content)) {\n nodes = json.content;\n } else {\n nodes = [json as WysiwygNode];\n }\n }\n\n // If no custom components are provided, render all nodes as a single HTML block\n const hasCustomComponents = nodes.some((node) => components[node.type]);\n\n if (!hasCustomComponents) {\n const html = nodes.map((node) => wysiwyg(node)).join('');\n return <div className={className} dangerouslySetInnerHTML={{ __html: html }} />;\n }\n\n // When custom components are used, group consecutive non-custom nodes\n // into single HTML blocks to avoid extra wrapper elements.\n const groups: ({ type: 'custom'; component: React.ComponentType<{ node: WysiwygNode }>; node: WysiwygNode } | { type: 'html'; html: string })[] = [];\n\n for (const node of nodes) {\n const Component = components[node.type];\n if (Component) {\n groups.push({ type: 'custom', component: Component, node });\n } else {\n const html = wysiwyg(node);\n const last = groups[groups.length - 1];\n if (last && last.type === 'html') {\n last.html += html;\n } else {\n groups.push({ type: 'html', html });\n }\n }\n }\n\n return (\n <div className={className}>\n {groups.map((group, index) => {\n if (group.type === 'custom') {\n return <group.component key={index} node={group.node} />;\n }\n return <div key={index} dangerouslySetInnerHTML={{ __html: group.html }} />;\n })}\n </div>\n );\n}\n\n/**\n * Image loader for Flyo CDN that automatically handles image transformations.\n * Adds Flyo CDN host if not already present and applies width transformations.\n * \n * @param src - The image source URL (relative or absolute)\n * @param width - The desired width for the image\n * @returns Transformed image URL with Flyo CDN parameters\n * \n * @example\n * ```tsx\n * <Image\n * loader={FlyoCdnLoader}\n * src=\"me.png\"\n * alt=\"Picture\"\n * width={500}\n * height={500}\n * />\n * ```\n */\nexport function FlyoCdnLoader({ src, width }: ImageLoaderProps): string {\n let imageUrl = src;\n\n // If src doesn't contain the Flyo CDN host, prefix it\n if (!src.includes(FLYO_CDN_HOST)) {\n // Remove leading slash if present to avoid double slashes\n const cleanSrc = src.startsWith('/') ? src.slice(1) : src;\n imageUrl = `https://${FLYO_CDN_HOST}/${cleanSrc}`;\n }\n\n // Append Flyo CDN transformation parameters\n return `${imageUrl}/thumb/${width}xnull?format=webp`;\n}\n\n/**\n * FlyoMetric component for tracking entity metrics in production\n * \n * Automatically sends a metric tracking request to the Flyo API when:\n * - The environment is production (NODE_ENV === 'production')\n * - The entity has a metric API URL configured\n * \n * @param entity - The entity object containing entity_metric.api\n * \n * @example\n * ```tsx\n * import { FlyoMetric } from '@flyo/nitro-next/client';\n * \n * export default function BlogPost(props: RouteParams) {\n * return nitroEntityRoute(props, {\n * resolver,\n * render: (entity: Entity) => (\n * <>\n * <FlyoMetric entity={entity} />\n * <article>\n * <h1>{entity.entity?.entity_title}</h1>\n * </article>\n * </>\n * )\n * });\n * }\n * ```\n */\nexport function FlyoMetric({ entity }: { entity: Entity }) {\n useEffect(() => {\n // Only track metrics in production and if API URL is available\n if (isProd && entity?.entity?.entity_metric?.api) {\n fetch(entity.entity.entity_metric.api);\n }\n }, [entity]);\n\n // This component doesn't render anything\n return null;\n}\n"],"mappings":"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","names":[]}
|
package/dist/client.mjs
CHANGED
|
@@ -57,6 +57,7 @@ function FlyoClientWrapper({
|
|
|
57
57
|
}
|
|
58
58
|
function FlyoWysiwyg({
|
|
59
59
|
json,
|
|
60
|
+
className = "wysiwyg",
|
|
60
61
|
components = {}
|
|
61
62
|
}) {
|
|
62
63
|
let nodes = [];
|
|
@@ -69,13 +70,31 @@ function FlyoWysiwyg({
|
|
|
69
70
|
nodes = [json];
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
const hasCustomComponents = nodes.some((node) => components[node.type]);
|
|
74
|
+
if (!hasCustomComponents) {
|
|
75
|
+
const html = nodes.map((node) => wysiwyg(node)).join("");
|
|
76
|
+
return /* @__PURE__ */ jsx("div", { className, dangerouslySetInnerHTML: { __html: html } });
|
|
77
|
+
}
|
|
78
|
+
const groups = [];
|
|
79
|
+
for (const node of nodes) {
|
|
73
80
|
const Component = components[node.type];
|
|
74
81
|
if (Component) {
|
|
75
|
-
|
|
82
|
+
groups.push({ type: "custom", component: Component, node });
|
|
83
|
+
} else {
|
|
84
|
+
const html = wysiwyg(node);
|
|
85
|
+
const last = groups[groups.length - 1];
|
|
86
|
+
if (last && last.type === "html") {
|
|
87
|
+
last.html += html;
|
|
88
|
+
} else {
|
|
89
|
+
groups.push({ type: "html", html });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return /* @__PURE__ */ jsx("div", { className, children: groups.map((group, index) => {
|
|
94
|
+
if (group.type === "custom") {
|
|
95
|
+
return /* @__PURE__ */ jsx(group.component, { node: group.node }, index);
|
|
76
96
|
}
|
|
77
|
-
|
|
78
|
-
return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: html } }, index);
|
|
97
|
+
return /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: group.html } }, index);
|
|
79
98
|
}) });
|
|
80
99
|
}
|
|
81
100
|
function FlyoCdnLoader({ src, width }) {
|
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { highlightAndClick, wysiwyg, reload} from '@flyo/nitro-js-bridge';\nimport { Block, Entity } from \"@flyo/nitro-typescript\";\nimport type { ImageLoaderProps } from 'next/image';\n\nconst FLYO_CDN_HOST = 'storage.flyo.cloud';\n\n/**\n * Check if running in production environment\n */\nexport const isProd = process.env.NODE_ENV === 'production';\n\n/**\n * Type for WYSIWYG node structure\n */\
|
|
1
|
+
{"version":3,"sources":["../src/client.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\nimport { highlightAndClick, wysiwyg, reload} from '@flyo/nitro-js-bridge';\nimport { Block, Entity } from \"@flyo/nitro-typescript\";\nimport type { ImageLoaderProps } from 'next/image';\n\nconst FLYO_CDN_HOST = 'storage.flyo.cloud';\n\n/**\n * Check if running in production environment\n */\nexport const isProd = process.env.NODE_ENV === 'production';\n\n/**\n * Type for WYSIWYG node structure\n */\nexport interface WysiwygNode {\n type: string;\n content?: WysiwygNode[];\n [key: string]: unknown;\n}\n\n/**\n * Type for WYSIWYG JSON that can be a node, array of nodes, or doc structure\n */\nexport type WysiwygJson = WysiwygNode | WysiwygNode[] | { type: 'doc'; content: WysiwygNode[] };\n\n/**\n * Helper function to get editable props\n */\nexport function editable(block: Block): { 'data-flyo-uid'?: string } {\n if (typeof block.uid === 'string' && block.uid.trim() !== '') {\n return { 'data-flyo-uid': block.uid };\n }\n return {};\n}\n\n/**\n * Internal client component that sets up live editing functionality\n */\nexport function FlyoClientWrapper({ \n children,\n}: { \n children: React.ReactNode;\n}) {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n reload();\n\n const wireAll = () => {\n const elements = document.querySelectorAll('[data-flyo-uid]');\n elements.forEach((el) => {\n const uid = el.getAttribute('data-flyo-uid');\n if (uid && el instanceof HTMLElement) {\n highlightAndClick(uid, el);\n }\n });\n };\n\n wireAll();\n\n const observer = new MutationObserver((mutations) => {\n const hasRelevantChanges = mutations.some(mutation => \n Array.from(mutation.addedNodes).some(node => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const element = node as Element;\n return element.hasAttribute('data-flyo-uid') || \n element.querySelector('[data-flyo-uid]');\n }\n return false;\n })\n );\n\n if (hasRelevantChanges) {\n wireAll();\n }\n });\n\n observer.observe(document.body, {\n childList: true,\n subtree: true,\n });\n\n return () => {\n observer.disconnect();\n };\n }, []);\n\n return <>{children}</>;\n}\n\n/**\n * WYSIWYG component for rendering ProseMirror/TipTap JSON content\n * \n * Uses the `wysiwyg()` function from `@flyo/nitro-js-bridge` to convert\n * nodes to HTML. All consecutive non-custom nodes are joined into a single\n * HTML string so no extra wrapper `<div>` elements are added around each node.\n * \n * The component wraps all output in a single `<div>` with an optional\n * `className` (defaults to `\"wysiwyg\"`).\n * \n * @example\n * ```tsx\n * import { FlyoWysiwyg } from '@flyo/nitro-next/client';\n * import CustomImage from './CustomImage';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <FlyoWysiwyg \n * json={block.content.json} \n * className=\"wysiwyg\"\n * components={{\n * image: CustomImage\n * }} \n * />\n * );\n * }\n * ```\n */\nexport function FlyoWysiwyg({\n json,\n className = 'wysiwyg',\n components = {},\n}: {\n json: WysiwygJson;\n className?: string;\n components?: Record<string, React.ComponentType<{ node: WysiwygNode }>>;\n}) {\n let nodes: WysiwygNode[] = [];\n\n if (json) {\n if (Array.isArray(json)) {\n nodes = json;\n } else if ('type' in json && json.type === 'doc' && Array.isArray(json.content)) {\n nodes = json.content;\n } else {\n nodes = [json as WysiwygNode];\n }\n }\n\n // If no custom components are provided, render all nodes as a single HTML block\n const hasCustomComponents = nodes.some((node) => components[node.type]);\n\n if (!hasCustomComponents) {\n const html = nodes.map((node) => wysiwyg(node)).join('');\n return <div className={className} dangerouslySetInnerHTML={{ __html: html }} />;\n }\n\n // When custom components are used, group consecutive non-custom nodes\n // into single HTML blocks to avoid extra wrapper elements.\n const groups: ({ type: 'custom'; component: React.ComponentType<{ node: WysiwygNode }>; node: WysiwygNode } | { type: 'html'; html: string })[] = [];\n\n for (const node of nodes) {\n const Component = components[node.type];\n if (Component) {\n groups.push({ type: 'custom', component: Component, node });\n } else {\n const html = wysiwyg(node);\n const last = groups[groups.length - 1];\n if (last && last.type === 'html') {\n last.html += html;\n } else {\n groups.push({ type: 'html', html });\n }\n }\n }\n\n return (\n <div className={className}>\n {groups.map((group, index) => {\n if (group.type === 'custom') {\n return <group.component key={index} node={group.node} />;\n }\n return <div key={index} dangerouslySetInnerHTML={{ __html: group.html }} />;\n })}\n </div>\n );\n}\n\n/**\n * Image loader for Flyo CDN that automatically handles image transformations.\n * Adds Flyo CDN host if not already present and applies width transformations.\n * \n * @param src - The image source URL (relative or absolute)\n * @param width - The desired width for the image\n * @returns Transformed image URL with Flyo CDN parameters\n * \n * @example\n * ```tsx\n * <Image\n * loader={FlyoCdnLoader}\n * src=\"me.png\"\n * alt=\"Picture\"\n * width={500}\n * height={500}\n * />\n * ```\n */\nexport function FlyoCdnLoader({ src, width }: ImageLoaderProps): string {\n let imageUrl = src;\n\n // If src doesn't contain the Flyo CDN host, prefix it\n if (!src.includes(FLYO_CDN_HOST)) {\n // Remove leading slash if present to avoid double slashes\n const cleanSrc = src.startsWith('/') ? src.slice(1) : src;\n imageUrl = `https://${FLYO_CDN_HOST}/${cleanSrc}`;\n }\n\n // Append Flyo CDN transformation parameters\n return `${imageUrl}/thumb/${width}xnull?format=webp`;\n}\n\n/**\n * FlyoMetric component for tracking entity metrics in production\n * \n * Automatically sends a metric tracking request to the Flyo API when:\n * - The environment is production (NODE_ENV === 'production')\n * - The entity has a metric API URL configured\n * \n * @param entity - The entity object containing entity_metric.api\n * \n * @example\n * ```tsx\n * import { FlyoMetric } from '@flyo/nitro-next/client';\n * \n * export default function BlogPost(props: RouteParams) {\n * return nitroEntityRoute(props, {\n * resolver,\n * render: (entity: Entity) => (\n * <>\n * <FlyoMetric entity={entity} />\n * <article>\n * <h1>{entity.entity?.entity_title}</h1>\n * </article>\n * </>\n * )\n * });\n * }\n * ```\n */\nexport function FlyoMetric({ entity }: { entity: Entity }) {\n useEffect(() => {\n // Only track metrics in production and if API URL is available\n if (isProd && entity?.entity?.entity_metric?.api) {\n fetch(entity.entity.entity_metric.api);\n }\n }, [entity]);\n\n // This component doesn't render anything\n return null;\n}\n"],"mappings":"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","names":[]}
|
package/dist/proxy.js
CHANGED
|
@@ -38,6 +38,8 @@ function createProxy(state) {
|
|
|
38
38
|
res.headers.set("CDN-Cache-Control", cdn);
|
|
39
39
|
if (state.clientCacheTtl > 0) {
|
|
40
40
|
res.headers.set("Cache-Control", `max-age=${state.clientCacheTtl}`);
|
|
41
|
+
} else {
|
|
42
|
+
res.headers.set("Cache-Control", "no-store");
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
return res;
|
package/dist/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/proxy.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\nimport type { NitroState } from './server';\n\n/**\n * Nitro Next.js Proxy Factory\n * \n * Creates a Next.js middleware that handles cache control headers.\n * Uses cache TTL values from the Nitro configuration state.\n * \n * @param state The Nitro state containing cache configuration\n * @returns Next.js middleware function\n * \n * @example\n * ```ts\n * // src/middleware.ts\n * import { createProxy } from '@flyo/nitro-next/proxy';\n * import { flyoConfig } from './flyo.config';\n * \n * export default createProxy(flyoConfig());\n * \n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * };\n * ```\n * \n * @example\n * ```ts\n * // flyo.config.tsx\n * export const flyoConfig = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN!,\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * serverCacheTtl: 1200, // 20 minutes\n * clientCacheTtl: 900, // 15 minutes\n * });\n * ```\n */\nexport function createProxy(state: NitroState) {\n return function proxy() {\n const res = NextResponse.next();\n\n if (state.liveEdit) {\n // Development or live edit mode - no caching\n res.headers.set('Vercel-CDN-Cache-Control', 'no-store');\n res.headers.set('CDN-Cache-Control', 'no-store');\n res.headers.set('Cache-Control', 'no-store');\n } else {\n // Production with caching enabled\n const cdn = state.serverCacheTtl > 0 ? `max-age=${state.serverCacheTtl}` : 'no-store';\n res.headers.set('Vercel-CDN-Cache-Control', cdn);\n res.headers.set('CDN-Cache-Control', cdn);\n\n if (state.clientCacheTtl > 0) {\n res.headers.set('Cache-Control', `max-age=${state.clientCacheTtl}`);\n }\n }\n\n return res;\n };\n}\n\n\n/**\n * Proxy matcher configuration\n * Applies to all routes except Next.js internal routes\n */\nexport const config = {\n matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAqCtB,SAAS,YAAY,OAAmB;AAC7C,SAAO,SAAS,QAAQ;AACtB,UAAM,MAAM,2BAAa,KAAK;AAE9B,QAAI,MAAM,UAAU;AAElB,UAAI,QAAQ,IAAI,4BAA4B,UAAU;AACtD,UAAI,QAAQ,IAAI,qBAAqB,UAAU;AAC/C,UAAI,QAAQ,IAAI,iBAAiB,UAAU;AAAA,IAC7C,OAAO;AAEL,YAAM,MAAM,MAAM,iBAAiB,IAAI,WAAW,MAAM,cAAc,KAAK;AAC3E,UAAI,QAAQ,IAAI,4BAA4B,GAAG;AAC/C,UAAI,QAAQ,IAAI,qBAAqB,GAAG;AAExC,UAAI,MAAM,iBAAiB,GAAG;AAC5B,YAAI,QAAQ,IAAI,iBAAiB,WAAW,MAAM,cAAc,EAAE;AAAA,MACpE;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAOO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,+CAA+C;AAC3D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/proxy.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\nimport type { NitroState } from './server';\n\n/**\n * Nitro Next.js Proxy Factory\n * \n * Creates a Next.js middleware that handles cache control headers.\n * Uses cache TTL values from the Nitro configuration state.\n * \n * @param state The Nitro state containing cache configuration\n * @returns Next.js middleware function\n * \n * @example\n * ```ts\n * // src/middleware.ts\n * import { createProxy } from '@flyo/nitro-next/proxy';\n * import { flyoConfig } from './flyo.config';\n * \n * export default createProxy(flyoConfig());\n * \n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * };\n * ```\n * \n * @example\n * ```ts\n * // flyo.config.tsx\n * export const flyoConfig = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN!,\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * serverCacheTtl: 1200, // 20 minutes\n * clientCacheTtl: 900, // 15 minutes\n * });\n * ```\n */\nexport function createProxy(state: NitroState) {\n return function proxy() {\n const res = NextResponse.next();\n\n if (state.liveEdit) {\n // Development or live edit mode - no caching\n res.headers.set('Vercel-CDN-Cache-Control', 'no-store');\n res.headers.set('CDN-Cache-Control', 'no-store');\n res.headers.set('Cache-Control', 'no-store');\n } else {\n // Production with caching enabled\n const cdn = state.serverCacheTtl > 0 ? `max-age=${state.serverCacheTtl}` : 'no-store';\n res.headers.set('Vercel-CDN-Cache-Control', cdn);\n res.headers.set('CDN-Cache-Control', cdn);\n\n if (state.clientCacheTtl > 0) {\n res.headers.set('Cache-Control', `max-age=${state.clientCacheTtl}`);\n } else {\n res.headers.set('Cache-Control', 'no-store');\n }\n }\n\n return res;\n };\n}\n\n\n/**\n * Proxy matcher configuration\n * Applies to all routes except Next.js internal routes\n */\nexport const config = {\n matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA6B;AAqCtB,SAAS,YAAY,OAAmB;AAC7C,SAAO,SAAS,QAAQ;AACtB,UAAM,MAAM,2BAAa,KAAK;AAE9B,QAAI,MAAM,UAAU;AAElB,UAAI,QAAQ,IAAI,4BAA4B,UAAU;AACtD,UAAI,QAAQ,IAAI,qBAAqB,UAAU;AAC/C,UAAI,QAAQ,IAAI,iBAAiB,UAAU;AAAA,IAC7C,OAAO;AAEL,YAAM,MAAM,MAAM,iBAAiB,IAAI,WAAW,MAAM,cAAc,KAAK;AAC3E,UAAI,QAAQ,IAAI,4BAA4B,GAAG;AAC/C,UAAI,QAAQ,IAAI,qBAAqB,GAAG;AAExC,UAAI,MAAM,iBAAiB,GAAG;AAC5B,YAAI,QAAQ,IAAI,iBAAiB,WAAW,MAAM,cAAc,EAAE;AAAA,MACpE,OAAO;AACL,YAAI,QAAQ,IAAI,iBAAiB,UAAU;AAAA,MAC7C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAOO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,+CAA+C;AAC3D;","names":[]}
|
package/dist/proxy.mjs
CHANGED
|
@@ -13,6 +13,8 @@ function createProxy(state) {
|
|
|
13
13
|
res.headers.set("CDN-Cache-Control", cdn);
|
|
14
14
|
if (state.clientCacheTtl > 0) {
|
|
15
15
|
res.headers.set("Cache-Control", `max-age=${state.clientCacheTtl}`);
|
|
16
|
+
} else {
|
|
17
|
+
res.headers.set("Cache-Control", "no-store");
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
return res;
|
package/dist/proxy.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/proxy.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\nimport type { NitroState } from './server';\n\n/**\n * Nitro Next.js Proxy Factory\n * \n * Creates a Next.js middleware that handles cache control headers.\n * Uses cache TTL values from the Nitro configuration state.\n * \n * @param state The Nitro state containing cache configuration\n * @returns Next.js middleware function\n * \n * @example\n * ```ts\n * // src/middleware.ts\n * import { createProxy } from '@flyo/nitro-next/proxy';\n * import { flyoConfig } from './flyo.config';\n * \n * export default createProxy(flyoConfig());\n * \n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * };\n * ```\n * \n * @example\n * ```ts\n * // flyo.config.tsx\n * export const flyoConfig = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN!,\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * serverCacheTtl: 1200, // 20 minutes\n * clientCacheTtl: 900, // 15 minutes\n * });\n * ```\n */\nexport function createProxy(state: NitroState) {\n return function proxy() {\n const res = NextResponse.next();\n\n if (state.liveEdit) {\n // Development or live edit mode - no caching\n res.headers.set('Vercel-CDN-Cache-Control', 'no-store');\n res.headers.set('CDN-Cache-Control', 'no-store');\n res.headers.set('Cache-Control', 'no-store');\n } else {\n // Production with caching enabled\n const cdn = state.serverCacheTtl > 0 ? `max-age=${state.serverCacheTtl}` : 'no-store';\n res.headers.set('Vercel-CDN-Cache-Control', cdn);\n res.headers.set('CDN-Cache-Control', cdn);\n\n if (state.clientCacheTtl > 0) {\n res.headers.set('Cache-Control', `max-age=${state.clientCacheTtl}`);\n }\n }\n\n return res;\n };\n}\n\n\n/**\n * Proxy matcher configuration\n * Applies to all routes except Next.js internal routes\n */\nexport const config = {\n matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n};\n"],"mappings":";AAAA,SAAS,oBAAoB;AAqCtB,SAAS,YAAY,OAAmB;AAC7C,SAAO,SAAS,QAAQ;AACtB,UAAM,MAAM,aAAa,KAAK;AAE9B,QAAI,MAAM,UAAU;AAElB,UAAI,QAAQ,IAAI,4BAA4B,UAAU;AACtD,UAAI,QAAQ,IAAI,qBAAqB,UAAU;AAC/C,UAAI,QAAQ,IAAI,iBAAiB,UAAU;AAAA,IAC7C,OAAO;AAEL,YAAM,MAAM,MAAM,iBAAiB,IAAI,WAAW,MAAM,cAAc,KAAK;AAC3E,UAAI,QAAQ,IAAI,4BAA4B,GAAG;AAC/C,UAAI,QAAQ,IAAI,qBAAqB,GAAG;AAExC,UAAI,MAAM,iBAAiB,GAAG;AAC5B,YAAI,QAAQ,IAAI,iBAAiB,WAAW,MAAM,cAAc,EAAE;AAAA,MACpE;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAOO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,+CAA+C;AAC3D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/proxy.ts"],"sourcesContent":["import { NextResponse } from 'next/server';\nimport type { NitroState } from './server';\n\n/**\n * Nitro Next.js Proxy Factory\n * \n * Creates a Next.js middleware that handles cache control headers.\n * Uses cache TTL values from the Nitro configuration state.\n * \n * @param state The Nitro state containing cache configuration\n * @returns Next.js middleware function\n * \n * @example\n * ```ts\n * // src/middleware.ts\n * import { createProxy } from '@flyo/nitro-next/proxy';\n * import { flyoConfig } from './flyo.config';\n * \n * export default createProxy(flyoConfig());\n * \n * export const config = {\n * matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n * };\n * ```\n * \n * @example\n * ```ts\n * // flyo.config.tsx\n * export const flyoConfig = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN!,\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * liveEdit: process.env.FLYO_LIVE_EDIT === 'true',\n * serverCacheTtl: 1200, // 20 minutes\n * clientCacheTtl: 900, // 15 minutes\n * });\n * ```\n */\nexport function createProxy(state: NitroState) {\n return function proxy() {\n const res = NextResponse.next();\n\n if (state.liveEdit) {\n // Development or live edit mode - no caching\n res.headers.set('Vercel-CDN-Cache-Control', 'no-store');\n res.headers.set('CDN-Cache-Control', 'no-store');\n res.headers.set('Cache-Control', 'no-store');\n } else {\n // Production with caching enabled\n const cdn = state.serverCacheTtl > 0 ? `max-age=${state.serverCacheTtl}` : 'no-store';\n res.headers.set('Vercel-CDN-Cache-Control', cdn);\n res.headers.set('CDN-Cache-Control', cdn);\n\n if (state.clientCacheTtl > 0) {\n res.headers.set('Cache-Control', `max-age=${state.clientCacheTtl}`);\n } else {\n res.headers.set('Cache-Control', 'no-store');\n }\n }\n\n return res;\n };\n}\n\n\n/**\n * Proxy matcher configuration\n * Applies to all routes except Next.js internal routes\n */\nexport const config = {\n matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n};\n"],"mappings":";AAAA,SAAS,oBAAoB;AAqCtB,SAAS,YAAY,OAAmB;AAC7C,SAAO,SAAS,QAAQ;AACtB,UAAM,MAAM,aAAa,KAAK;AAE9B,QAAI,MAAM,UAAU;AAElB,UAAI,QAAQ,IAAI,4BAA4B,UAAU;AACtD,UAAI,QAAQ,IAAI,qBAAqB,UAAU;AAC/C,UAAI,QAAQ,IAAI,iBAAiB,UAAU;AAAA,IAC7C,OAAO;AAEL,YAAM,MAAM,MAAM,iBAAiB,IAAI,WAAW,MAAM,cAAc,KAAK;AAC3E,UAAI,QAAQ,IAAI,4BAA4B,GAAG;AAC/C,UAAI,QAAQ,IAAI,qBAAqB,GAAG;AAExC,UAAI,MAAM,iBAAiB,GAAG;AAC5B,YAAI,QAAQ,IAAI,iBAAiB,WAAW,MAAM,cAAc,EAAE;AAAA,MACpE,OAAO;AACL,YAAI,QAAQ,IAAI,iBAAiB,UAAU;AAAA,MAC7C;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;AAOO,IAAM,SAAS;AAAA,EACpB,SAAS,CAAC,+CAA+C;AAC3D;","names":[]}
|
package/dist/server.d.mts
CHANGED
|
@@ -81,6 +81,28 @@ type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;
|
|
|
81
81
|
declare function NitroDebugInfo({ config }: {
|
|
82
82
|
config: ConfigResponse;
|
|
83
83
|
}): react_jsx_runtime.JSX.Element;
|
|
84
|
+
/**
|
|
85
|
+
* Renders a JSON-LD structured data script tag from an Entity's jsonld field.
|
|
86
|
+
* Safely escapes HTML entities to prevent XSS attacks.
|
|
87
|
+
* Returns null if the entity has no jsonld data.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* import { NitroEntityJsonLd } from '@flyo/nitro-next/server';
|
|
92
|
+
*
|
|
93
|
+
* export default function BlogPost({ entity }: { entity: Entity }) {
|
|
94
|
+
* return (
|
|
95
|
+
* <>
|
|
96
|
+
* <NitroEntityJsonLd entity={entity} />
|
|
97
|
+
* <h1>{entity.entity?.entity_title}</h1>
|
|
98
|
+
* </>
|
|
99
|
+
* );
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare function NitroEntityJsonLd({ entity }: {
|
|
104
|
+
entity: Entity;
|
|
105
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
84
106
|
/**
|
|
85
107
|
* NitroPage component renders all blocks from a Flyo page
|
|
86
108
|
*/
|
|
@@ -242,4 +264,4 @@ declare function nitroEntityGenerateMetadata<T = any>(props: EntityRouteParams<T
|
|
|
242
264
|
*/
|
|
243
265
|
declare function nitroSitemap(state: NitroState): Promise<MetadataRoute.Sitemap>;
|
|
244
266
|
|
|
245
|
-
export { type EntityResolver, NitroBlock, NitroDebugInfo, NitroPage, NitroSlot, type NitroState, getNitro, getNitroConfig, getNitroEntities, getNitroPages, getNitroSearch, getNitroSitemap, globalNitroState, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute, nitroSitemap };
|
|
267
|
+
export { type EntityResolver, NitroBlock, NitroDebugInfo, NitroEntityJsonLd, NitroPage, NitroSlot, type NitroState, getNitro, getNitroConfig, getNitroEntities, getNitroPages, getNitroSearch, getNitroSitemap, globalNitroState, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute, nitroSitemap };
|
package/dist/server.d.ts
CHANGED
|
@@ -81,6 +81,28 @@ type EntityResolver<T = any> = (params: Promise<T>) => Promise<Entity>;
|
|
|
81
81
|
declare function NitroDebugInfo({ config }: {
|
|
82
82
|
config: ConfigResponse;
|
|
83
83
|
}): react_jsx_runtime.JSX.Element;
|
|
84
|
+
/**
|
|
85
|
+
* Renders a JSON-LD structured data script tag from an Entity's jsonld field.
|
|
86
|
+
* Safely escapes HTML entities to prevent XSS attacks.
|
|
87
|
+
* Returns null if the entity has no jsonld data.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* import { NitroEntityJsonLd } from '@flyo/nitro-next/server';
|
|
92
|
+
*
|
|
93
|
+
* export default function BlogPost({ entity }: { entity: Entity }) {
|
|
94
|
+
* return (
|
|
95
|
+
* <>
|
|
96
|
+
* <NitroEntityJsonLd entity={entity} />
|
|
97
|
+
* <h1>{entity.entity?.entity_title}</h1>
|
|
98
|
+
* </>
|
|
99
|
+
* );
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare function NitroEntityJsonLd({ entity }: {
|
|
104
|
+
entity: Entity;
|
|
105
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
84
106
|
/**
|
|
85
107
|
* NitroPage component renders all blocks from a Flyo page
|
|
86
108
|
*/
|
|
@@ -242,4 +264,4 @@ declare function nitroEntityGenerateMetadata<T = any>(props: EntityRouteParams<T
|
|
|
242
264
|
*/
|
|
243
265
|
declare function nitroSitemap(state: NitroState): Promise<MetadataRoute.Sitemap>;
|
|
244
266
|
|
|
245
|
-
export { type EntityResolver, NitroBlock, NitroDebugInfo, NitroPage, NitroSlot, type NitroState, getNitro, getNitroConfig, getNitroEntities, getNitroPages, getNitroSearch, getNitroSitemap, globalNitroState, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute, nitroSitemap };
|
|
267
|
+
export { type EntityResolver, NitroBlock, NitroDebugInfo, NitroEntityJsonLd, NitroPage, NitroSlot, type NitroState, getNitro, getNitroConfig, getNitroEntities, getNitroPages, getNitroSearch, getNitroSitemap, globalNitroState, initNitro, nitroEntityGenerateMetadata, nitroEntityRoute, nitroPageGenerateMetadata, nitroPageGenerateStaticParams, nitroPageRoute, nitroSitemap };
|
package/dist/server.js
CHANGED
|
@@ -22,6 +22,7 @@ var server_exports = {};
|
|
|
22
22
|
__export(server_exports, {
|
|
23
23
|
NitroBlock: () => NitroBlock,
|
|
24
24
|
NitroDebugInfo: () => NitroDebugInfo,
|
|
25
|
+
NitroEntityJsonLd: () => NitroEntityJsonLd,
|
|
25
26
|
NitroPage: () => NitroPage,
|
|
26
27
|
NitroSlot: () => NitroSlot,
|
|
27
28
|
getNitro: () => getNitro,
|
|
@@ -174,6 +175,20 @@ function createCachedEntityResolver(resolver) {
|
|
|
174
175
|
return entity;
|
|
175
176
|
});
|
|
176
177
|
}
|
|
178
|
+
function NitroEntityJsonLd({ entity }) {
|
|
179
|
+
if (!entity?.jsonld) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
183
|
+
"script",
|
|
184
|
+
{
|
|
185
|
+
type: "application/ld+json",
|
|
186
|
+
dangerouslySetInnerHTML: {
|
|
187
|
+
__html: JSON.stringify(entity.jsonld).replace(/</g, "\\u003c")
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
}
|
|
177
192
|
function NitroPage({
|
|
178
193
|
page
|
|
179
194
|
}) {
|
|
@@ -325,6 +340,7 @@ async function nitroSitemap(state) {
|
|
|
325
340
|
0 && (module.exports = {
|
|
326
341
|
NitroBlock,
|
|
327
342
|
NitroDebugInfo,
|
|
343
|
+
NitroEntityJsonLd,
|
|
328
344
|
NitroPage,
|
|
329
345
|
NitroSlot,
|
|
330
346
|
getNitro,
|
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 * Interface for Nitro configuration state\n */\nexport interface NitroState {\n configuration: Configuration | null;\n accessToken: string | null;\n lang: string | null;\n baseUrl: string | null;\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}\n\n/**\n * Global Nitro state - shared across server and middleware\n */\nexport const globalNitroState: NitroState = {\n configuration: null,\n accessToken: null,\n lang: null,\n baseUrl: null,\n components: {},\n showMissingComponentAlert: false,\n liveEdit: false,\n serverCacheTtl: 1200,\n clientCacheTtl: 900\n};\n\n/**\n * Access the Nitro configuration state\n * Can be used anywhere: server components, middlewares, API routes, etc.\n * Must be called after initNitro() has been initialized.\n * \n * @throws {Error} If Nitro has not been initialized with initNitro()\n * \n * @example\n * ```ts\n * const state = getNitro();\n * const { configuration, lang, components } = state;\n * ```\n */\nexport function getNitro(): NitroState {\n if (!globalNitroState.configuration) {\n throw new Error('Nitro has not been initialized. Make sure to call initNitro() first.');\n }\n return globalNitroState;\n}\n\nexport const 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 components?: object;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): ( () => NitroState ) => {\n\n if (!globalNitroState.configuration) {\n globalNitroState.configuration = new Configuration({\n apiKey: accessToken,\n });\n }\n\n globalNitroState.accessToken = accessToken;\n globalNitroState.lang = lang ?? null;\n globalNitroState.baseUrl = baseUrl ?? null;\n globalNitroState.components = components ?? {};\n globalNitroState.showMissingComponentAlert = showMissingComponentAlert ?? liveEdit ?? false;\n globalNitroState.liveEdit = liveEdit ?? false;\n globalNitroState.serverCacheTtl = serverCacheTtl ?? 1200;\n globalNitroState.clientCacheTtl = clientCacheTtl ?? 900;\n\n return () => globalNitroState;\n}\n\nexport const getNitroConfig = cache(async (): Promise<ConfigResponse> => {\n const state = getNitro();\n\n const configApi = new ConfigApi(state.configuration!);\n const useLang = state.lang ?? undefined;\n\n const config = await configApi.config({ lang: useLang });\n \n return config;\n});\n\nexport function getNitroPages(): PagesApi {\n return new PagesApi(getNitro().configuration!);\n}\n\nexport function getNitroEntities(): EntitiesApi {\n return new EntitiesApi(getNitro().configuration!);\n}\n\nexport function getNitroSitemap(): SitemapApi {\n return new SitemapApi(getNitro().configuration!);\n}\n\nexport function getNitroSearch(): SearchApi {\n return new SearchApi(getNitro().configuration!);\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 * Allows any param structure from Next.js app router\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Internal helper to resolve Nitro page from route params\n * Uses React cache to avoid duplicate fetching\n */\nconst resolveNitroRoute = 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 getNitroPages()\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/**\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 * Helper function to read environment variables with fallback\n * Checks process.env for server-side environment variables\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 * NitroDebugInfo Component\n * \n * Outputs debug information about the current Nitro/Flyo setup as an HTML comment.\n * This includes environment info, API version, token type, deployment details, etc.\n * \n * Usage: Add <NitroDebugInfo config={config} /> to your layout to include debug info in the HTML output.\n */\nexport function NitroDebugInfo({ config }: { config: ConfigResponse }) {\n try {\n // Get Nitro state\n const state = getNitro();\n\n // Get environment variables\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 // Get token from configuration and determine type\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 // Get live edit / debug status\n const debug = state.liveEdit;\n\n // Get API version from config.nitro\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 // Build debug info parts\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 just the HTML comment as a real HTML comment (not text)\n // React requires dangerouslySetInnerHTML for raw HTML, so we use an empty template element\n // which is semantic and doesn't render in the DOM tree\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch (error) {\n // If Nitro is not initialized or there's an error, return empty comment\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Internal helper to wrap and cache entity resolvers\n * Ensures the resolver is only called once per unique params\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\n/**\n * NitroPage component renders all blocks from a Flyo page\n */\nexport function NitroPage({\n page,\n}: {\n page: Page\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 />\n ))}\n </>\n );\n}\n\nexport function NitroBlock({\n block,\n}: {\n block: Block\n}) {\n if (!block) {\n return null;\n }\n\n const state = getNitro();\n const Component = block.component ? state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (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 component renders nested blocks from a slot\n * Used for recursive block rendering when blocks contain slots\n * \n * @example\n * ```tsx\n * import { NitroSlot } from '@flyo/nitro-next/server';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots.mysuperslotname} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n}: {\n slot?: {\n content?: Block[];\n };\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 />\n ))}\n </>\n );\n}\n\n/**\n * Default page route handler for Nitro pages\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageRoute as default } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageRoute(props: RouteParams) {\n const { page } = await resolveNitroRoute(props);\n return <NitroPage page={page} />;\n}\n\n/**\n * Generate metadata for Nitro pages\n * Provides basic meta tags based on Flyo page data\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageGenerateMetadata as generateMetadata } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageGenerateMetadata(\n props: RouteParams\n): Promise<Metadata> {\n const { page } = await resolveNitroRoute(props);\n\n // Extract meta information from page\n const meta = page.meta_json;\n \n const title = meta?.title ?? page.title ?? 'Page';\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 * Generate static params for all Nitro pages\n * Enables static site generation (SSG) for all pages\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageGenerateStaticParams as generateStaticParams } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageGenerateStaticParams() {\n const cfg = await getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n}\n\n/**\n * Default entity route handler with custom resolver\n * Flexible solution that works with any route param structure\n * \n * @example\n * ```ts\n * // app/blog/[slug]/page.tsx\n * const resolver = async (params: Promise<{ slug: string }>) => {\n * const { slug } = await params;\n * return getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n * \n * export default (props) => nitroEntityRoute(props, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>\n * });\n * ```\n * \n * @example\n * ```ts\n * // app/items/[uniqueid]/page.tsx\n * const resolver = async (params: Promise<{ uniqueid: string }>) => {\n * const { uniqueid } = await params;\n * return getNitroEntities().entityByUniqueid({ uniqueid });\n * };\n * \n * export default (props) => nitroEntityRoute(props, { resolver });\n * ```\n * \n * @example\n * ```ts\n * // app/custom/[whatever]/page.tsx\n * const resolver = async (params: Promise<{ whatever: string }>) => {\n * const { whatever } = await params;\n * return getNitroEntities().entityBySlug({ slug: whatever });\n * };\n * \n * export default (props) => nitroEntityRoute(props, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n props: EntityRouteParams<T>,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n \n return (async () => {\n const entity = await cachedResolver(props);\n \n if (options.render) {\n return options.render(entity);\n }\n\n // Default simple render - users should provide their own render function\n return <div>{entity.entity?.entity_title}</div>;\n })();\n}\n\n/**\n * Generate metadata for Nitro entities with custom resolver\n * Works with any route param structure\n * \n * @example\n * ```ts\n * // app/blog/[slug]/page.tsx\n * const resolver = async (params: Promise<{ slug: string }>) => {\n * const { slug } = await params;\n * return getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n * \n * export const generateMetadata = (props) => nitroEntityGenerateMetadata(props, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function nitroEntityGenerateMetadata<T = any>(\n props: EntityRouteParams<T>,\n options: {\n resolver: EntityResolver<T>;\n }\n): Promise<Metadata> {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title ?? 'Entity';\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\n/**\n * Generate sitemap for Next.js from Flyo Nitro\n * Fetches all pages and entities from the sitemap endpoint\n * Uses the baseUrl from the Nitro configuration state\n * \n * @param state The Nitro state containing configuration and baseUrl\n * @returns Promise resolving to Next.js MetadataRoute.Sitemap format\n * \n * @example\n * ```ts\n * // app/sitemap.ts\n * import { nitroSitemap } from '@flyo/nitro-next/server';\n * import { flyoConfig } from '../flyo.config';\n * \n * export default async function sitemap() {\n * return nitroSitemap(flyoConfig());\n * }\n * ```\n * \n * @example\n * ```ts\n * // flyo.config.tsx\n * export const flyoConfig = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN!,\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * lang: 'en',\n * });\n * ```\n */\nexport async function nitroSitemap(state: NitroState): Promise<MetadataRoute.Sitemap> {\n const sitemapApi = getNitroSitemap();\n const lang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured in Nitro state. Please set it in initNitro().');\n }\n\n // Fetch all sitemap entries from Flyo Nitro\n const items = await sitemapApi.sitemap({ lang });\n\n const baseUrl = state.baseUrl;\n \n // Remove trailing slash from baseUrl for consistency\n const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n return items.map((item) => {\n // Prefer routes object if available, otherwise use entity_slug\n let path = '';\n \n if (item.routes && typeof item.routes === 'object') {\n // Use the first available route from the routes object\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n \n // Fallback to entity_slug if no routes found\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n // Ensure path starts with /\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n // Convert Unix timestamp to Date if available\n const lastModified = new Date();\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified,\n };\n });\n}"],"mappings":"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","names":[]}
|
|
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 * Interface for Nitro configuration state\n */\nexport interface NitroState {\n configuration: Configuration | null;\n accessToken: string | null;\n lang: string | null;\n baseUrl: string | null;\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}\n\n/**\n * Global Nitro state - shared across server and middleware\n */\nexport const globalNitroState: NitroState = {\n configuration: null,\n accessToken: null,\n lang: null,\n baseUrl: null,\n components: {},\n showMissingComponentAlert: false,\n liveEdit: false,\n serverCacheTtl: 1200,\n clientCacheTtl: 900\n};\n\n/**\n * Access the Nitro configuration state\n * Can be used anywhere: server components, middlewares, API routes, etc.\n * Must be called after initNitro() has been initialized.\n * \n * @throws {Error} If Nitro has not been initialized with initNitro()\n * \n * @example\n * ```ts\n * const state = getNitro();\n * const { configuration, lang, components } = state;\n * ```\n */\nexport function getNitro(): NitroState {\n if (!globalNitroState.configuration) {\n throw new Error('Nitro has not been initialized. Make sure to call initNitro() first.');\n }\n return globalNitroState;\n}\n\nexport const 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 components?: object;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): ( () => NitroState ) => {\n\n if (!globalNitroState.configuration) {\n globalNitroState.configuration = new Configuration({\n apiKey: accessToken,\n });\n }\n\n globalNitroState.accessToken = accessToken;\n globalNitroState.lang = lang ?? null;\n globalNitroState.baseUrl = baseUrl ?? null;\n globalNitroState.components = components ?? {};\n globalNitroState.showMissingComponentAlert = showMissingComponentAlert ?? liveEdit ?? false;\n globalNitroState.liveEdit = liveEdit ?? false;\n globalNitroState.serverCacheTtl = serverCacheTtl ?? 1200;\n globalNitroState.clientCacheTtl = clientCacheTtl ?? 900;\n\n return () => globalNitroState;\n}\n\nexport const getNitroConfig = cache(async (): Promise<ConfigResponse> => {\n const state = getNitro();\n\n const configApi = new ConfigApi(state.configuration!);\n const useLang = state.lang ?? undefined;\n\n const config = await configApi.config({ lang: useLang });\n \n return config;\n});\n\nexport function getNitroPages(): PagesApi {\n return new PagesApi(getNitro().configuration!);\n}\n\nexport function getNitroEntities(): EntitiesApi {\n return new EntitiesApi(getNitro().configuration!);\n}\n\nexport function getNitroSitemap(): SitemapApi {\n return new SitemapApi(getNitro().configuration!);\n}\n\nexport function getNitroSearch(): SearchApi {\n return new SearchApi(getNitro().configuration!);\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 * Allows any param structure from Next.js app router\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Internal helper to resolve Nitro page from route params\n * Uses React cache to avoid duplicate fetching\n */\nconst resolveNitroRoute = 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 getNitroPages()\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/**\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 * Helper function to read environment variables with fallback\n * Checks process.env for server-side environment variables\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 * NitroDebugInfo Component\n * \n * Outputs debug information about the current Nitro/Flyo setup as an HTML comment.\n * This includes environment info, API version, token type, deployment details, etc.\n * \n * Usage: Add <NitroDebugInfo config={config} /> to your layout to include debug info in the HTML output.\n */\nexport function NitroDebugInfo({ config }: { config: ConfigResponse }) {\n try {\n // Get Nitro state\n const state = getNitro();\n\n // Get environment variables\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 // Get token from configuration and determine type\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 // Get live edit / debug status\n const debug = state.liveEdit;\n\n // Get API version from config.nitro\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 // Build debug info parts\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 just the HTML comment as a real HTML comment (not text)\n // React requires dangerouslySetInnerHTML for raw HTML, so we use an empty template element\n // which is semantic and doesn't render in the DOM tree\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch (error) {\n // If Nitro is not initialized or there's an error, return empty comment\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Internal helper to wrap and cache entity resolvers\n * Ensures the resolver is only called once per unique params\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\n/**\n * Renders a JSON-LD structured data script tag from an Entity's jsonld field.\n * Safely escapes HTML entities to prevent XSS attacks.\n * Returns null if the entity has no jsonld data.\n * \n * @example\n * ```tsx\n * import { NitroEntityJsonLd } from '@flyo/nitro-next/server';\n * \n * export default function BlogPost({ entity }: { entity: Entity }) {\n * return (\n * <>\n * <NitroEntityJsonLd entity={entity} />\n * <h1>{entity.entity?.entity_title}</h1>\n * </>\n * );\n * }\n * ```\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 component renders all blocks from a Flyo page\n */\nexport function NitroPage({\n page,\n}: {\n page: Page\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 />\n ))}\n </>\n );\n}\n\nexport function NitroBlock({\n block,\n}: {\n block: Block\n}) {\n if (!block) {\n return null;\n }\n\n const state = getNitro();\n const Component = block.component ? state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (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 component renders nested blocks from a slot\n * Used for recursive block rendering when blocks contain slots\n * \n * @example\n * ```tsx\n * import { NitroSlot } from '@flyo/nitro-next/server';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots.mysuperslotname} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n}: {\n slot?: {\n content?: Block[];\n };\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 />\n ))}\n </>\n );\n}\n\n/**\n * Default page route handler for Nitro pages\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageRoute as default } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageRoute(props: RouteParams) {\n const { page } = await resolveNitroRoute(props);\n return <NitroPage page={page} />;\n}\n\n/**\n * Generate metadata for Nitro pages\n * Provides basic meta tags based on Flyo page data\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageGenerateMetadata as generateMetadata } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageGenerateMetadata(\n props: RouteParams\n): Promise<Metadata> {\n const { page } = await resolveNitroRoute(props);\n\n // Extract meta information from page\n const meta = page.meta_json;\n \n const title = meta?.title ?? page.title ?? 'Page';\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 * Generate static params for all Nitro pages\n * Enables static site generation (SSG) for all pages\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageGenerateStaticParams as generateStaticParams } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageGenerateStaticParams() {\n const cfg = await getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n}\n\n/**\n * Default entity route handler with custom resolver\n * Flexible solution that works with any route param structure\n * \n * @example\n * ```ts\n * // app/blog/[slug]/page.tsx\n * const resolver = async (params: Promise<{ slug: string }>) => {\n * const { slug } = await params;\n * return getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n * \n * export default (props) => nitroEntityRoute(props, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>\n * });\n * ```\n * \n * @example\n * ```ts\n * // app/items/[uniqueid]/page.tsx\n * const resolver = async (params: Promise<{ uniqueid: string }>) => {\n * const { uniqueid } = await params;\n * return getNitroEntities().entityByUniqueid({ uniqueid });\n * };\n * \n * export default (props) => nitroEntityRoute(props, { resolver });\n * ```\n * \n * @example\n * ```ts\n * // app/custom/[whatever]/page.tsx\n * const resolver = async (params: Promise<{ whatever: string }>) => {\n * const { whatever } = await params;\n * return getNitroEntities().entityBySlug({ slug: whatever });\n * };\n * \n * export default (props) => nitroEntityRoute(props, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n props: EntityRouteParams<T>,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n \n return (async () => {\n const entity = await cachedResolver(props);\n \n if (options.render) {\n return options.render(entity);\n }\n\n // Default simple render - users should provide their own render function\n return <div>{entity.entity?.entity_title}</div>;\n })();\n}\n\n/**\n * Generate metadata for Nitro entities with custom resolver\n * Works with any route param structure\n * \n * @example\n * ```ts\n * // app/blog/[slug]/page.tsx\n * const resolver = async (params: Promise<{ slug: string }>) => {\n * const { slug } = await params;\n * return getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n * \n * export const generateMetadata = (props) => nitroEntityGenerateMetadata(props, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function nitroEntityGenerateMetadata<T = any>(\n props: EntityRouteParams<T>,\n options: {\n resolver: EntityResolver<T>;\n }\n): Promise<Metadata> {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title ?? 'Entity';\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\n/**\n * Generate sitemap for Next.js from Flyo Nitro\n * Fetches all pages and entities from the sitemap endpoint\n * Uses the baseUrl from the Nitro configuration state\n * \n * @param state The Nitro state containing configuration and baseUrl\n * @returns Promise resolving to Next.js MetadataRoute.Sitemap format\n * \n * @example\n * ```ts\n * // app/sitemap.ts\n * import { nitroSitemap } from '@flyo/nitro-next/server';\n * import { flyoConfig } from '../flyo.config';\n * \n * export default async function sitemap() {\n * return nitroSitemap(flyoConfig());\n * }\n * ```\n * \n * @example\n * ```ts\n * // flyo.config.tsx\n * export const flyoConfig = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN!,\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * lang: 'en',\n * });\n * ```\n */\nexport async function nitroSitemap(state: NitroState): Promise<MetadataRoute.Sitemap> {\n const sitemapApi = getNitroSitemap();\n const lang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured in Nitro state. Please set it in initNitro().');\n }\n\n // Fetch all sitemap entries from Flyo Nitro\n const items = await sitemapApi.sitemap({ lang });\n\n const baseUrl = state.baseUrl;\n \n // Remove trailing slash from baseUrl for consistency\n const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n return items.map((item) => {\n // Prefer routes object if available, otherwise use entity_slug\n let path = '';\n \n if (item.routes && typeof item.routes === 'object') {\n // Use the first available route from the routes object\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n \n // Fallback to entity_slug if no routes found\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n // Ensure path starts with /\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n // Convert Unix timestamp to Date if available\n const lastModified = new Date();\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified,\n };\n });\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAAsB;AAEtB,wBAAyB;AACzB,8BAWO;AAqPD;AAhOC,IAAM,mBAA+B;AAAA,EAC1C,eAAe;AAAA,EACf,aAAa;AAAA,EACb,MAAM;AAAA,EACN,SAAS;AAAA,EACT,YAAY,CAAC;AAAA,EACb,2BAA2B;AAAA,EAC3B,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,gBAAgB;AAClB;AAeO,SAAS,WAAuB;AACrC,MAAI,CAAC,iBAAiB,eAAe;AACnC,UAAM,IAAI,MAAM,sEAAsE;AAAA,EACxF;AACA,SAAO;AACT;AAEO,IAAM,YAAY,CAAC;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAS8B;AAE1B,MAAI,CAAC,iBAAiB,eAAe;AACnC,qBAAiB,gBAAgB,IAAI,sCAAc;AAAA,MACjD,QAAQ;AAAA,IACV,CAAC;AAAA,EACH;AAEA,mBAAiB,cAAc;AAC/B,mBAAiB,OAAO,QAAQ;AAChC,mBAAiB,UAAU,WAAW;AACtC,mBAAiB,aAAa,cAAc,CAAC;AAC7C,mBAAiB,4BAA4B,6BAA6B,YAAY;AACtF,mBAAiB,WAAW,YAAY;AACxC,mBAAiB,iBAAiB,kBAAkB;AACpD,mBAAiB,iBAAiB,kBAAkB;AAEpD,SAAO,MAAM;AACjB;AAEO,IAAM,qBAAiB,oBAAM,YAAqC;AACrE,QAAM,QAAQ,SAAS;AAEvB,QAAM,YAAY,IAAI,kCAAU,MAAM,aAAc;AACpD,QAAM,UAAU,MAAM,QAAQ;AAE9B,QAAM,SAAS,MAAM,UAAU,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEvD,SAAO;AACX,CAAC;AAEM,SAAS,gBAA0B;AACxC,SAAO,IAAI,iCAAS,SAAS,EAAE,aAAc;AAC/C;AAEO,SAAS,mBAAgC;AAC9C,SAAO,IAAI,oCAAY,SAAS,EAAE,aAAc;AAClD;AAEO,SAAS,kBAA8B;AAC5C,SAAO,IAAI,mCAAW,SAAS,EAAE,aAAc;AACjD;AAEO,SAAS,iBAA4B;AAC1C,SAAO,IAAI,kCAAU,SAAS,EAAE,aAAc;AAChD;AAsBA,IAAM,wBAAoB,oBAAM,OAAO,EAAE,OAAO,MAAmB;AACjE,QAAM,EAAE,KAAK,IAAI,MAAM;AACvB,QAAM,OAAO,MAAM,KAAK,GAAG,KAAK;AAEhC,QAAM,MAAM,MAAM,eAAe;AAEjC,MAAI,CAAC,IAAI,OAAO,SAAS,IAAI,GAAG;AAC9B,oCAAS;AAAA,EACX;AAEA,QAAM,OAAO,MAAM,cAAc,EAC9B,KAAK,EAAE,MAAM,KAAK,CAAC,EACnB,MAAM,CAAC,UAAmB;AACzB,YAAQ,MAAM,wBAAwB,MAAM,KAAK;AACjD,oCAAS;AAAA,EACX,CAAC;AAEH,MAAI,CAAC,MAAM;AACT,oCAAS;AAAA,EACX;AAEA,SAAO,EAAE,MAAM,MAAM,IAAI;AAC3B,CAAC;AAaD,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;AAUO,SAAS,eAAe,EAAE,OAAO,GAA+B;AACrE,MAAI;AAEF,UAAM,QAAQ,SAAS;AAGvB,UAAM,OAAO,QAAQ,YAAY,GAAG;AACpC,UAAM,qBAAqB,QAAQ,wBAAwB,GAAG;AAC9D,UAAM,qBAAqB,QAAQ,yBAAyB,GAAG;AAC/D,UAAM,UAAU,QAAQ,WAAW,EAAE;AAGrC,UAAM,aAAa,MAAM,eAAe;AACxC,UAAM,QAAQ,OAAO,eAAe,WAAW,aAAa;AAC5D,UAAM,YAAY,MAAM,WAAW,IAAI,IACnC,eACA,MAAM,WAAW,IAAI,IACrB,YACA;AAGJ,UAAM,QAAQ,MAAM;AAGpB,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;AAGJ,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;AAK3C,WACE,4CAAC,cAAS,yBAAyB,EAAE,QAAQ,QAAQ,SAAS,OAAO,GAAG,0BAAwB,MAAC;AAAA,EAErG,SAAS,OAAO;AAEd,WAAO,4CAAC,cAAS,yBAAyB,EAAE,QAAQ,wCAAwC,GAAG,0BAAwB,MAAC;AAAA,EAC1H;AACF;AAMA,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;AAsBO,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;AACF,GAEG;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;AAAA,IADK,MAAM,OAAO;AAAA,EAEpB,CACD,GACH;AAEJ;AAEO,SAAS,WAAW;AAAA,EACzB;AACF,GAEG;AACD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,SAAS;AACvB,QAAM,YAAY,MAAM,YAAY,MAAM,WAAW,MAAM,SAAS,IAAI;AAExE,MAAI,WAAW;AACb,WAAO,4CAAC,aAAU,OAAc;AAAA,EAClC;AAEA,MAAI,MAAM,2BAA2B;AACnC,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;AAmBO,SAAS,UAAU;AAAA,EACxB;AACF,GAIG;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;AAAA,IADK,MAAM,OAAO;AAAA,EAEpB,CACD,GACH;AAEJ;AAYA,eAAsB,eAAe,OAAoB;AACvD,QAAM,EAAE,KAAK,IAAI,MAAM,kBAAkB,KAAK;AAC9C,SAAO,4CAAC,aAAU,MAAY;AAChC;AAaA,eAAsB,0BACpB,OACmB;AACnB,QAAM,EAAE,KAAK,IAAI,MAAM,kBAAkB,KAAK;AAG9C,QAAM,OAAO,KAAK;AAElB,QAAM,QAAQ,MAAM,SAAS,KAAK,SAAS;AAC3C,QAAM,cAAc,MAAM,eAAe;AACzC,QAAM,QAAQ,MAAM,SAAS;AAE7B,QAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAC/D,QAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAE/D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,MAC/B,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,IACjC;AAAA,EACF;AACF;AAaA,eAAsB,gCAAgC;AACpD,QAAM,MAAM,MAAM,eAAe;AACjC,QAAM,QAAQ,IAAI,SAAS,CAAC;AAE5B,SAAO,MAAM,IAAI,CAAC,UAAkB;AAAA,IAClC,MAAM,SAAS,KAAK,SAAY,KAAK,MAAM,GAAG;AAAA,EAChD,EAAE;AACJ;AA2CO,SAAS,iBACd,OACA,SAIA;AACA,QAAM,iBAAiB,2BAA2B,QAAQ,QAAQ;AAElE,UAAQ,YAAY;AAClB,UAAM,SAAS,MAAM,eAAe,KAAK;AAEzC,QAAI,QAAQ,QAAQ;AAClB,aAAO,QAAQ,OAAO,MAAM;AAAA,IAC9B;AAGA,WAAO,4CAAC,SAAK,iBAAO,QAAQ,cAAa;AAAA,EAC3C,GAAG;AACL;AAkBA,eAAsB,4BACpB,OACA,SAGmB;AACnB,QAAM,iBAAiB,2BAA2B,QAAQ,QAAQ;AAClE,QAAM,SAAS,MAAM,eAAe,KAAK;AAEzC,QAAM,QAAQ,OAAO,QAAQ,gBAAgB;AAC7C,QAAM,cAAc,OAAO,QAAQ,iBAAiB;AACpD,QAAM,QAAQ,OAAO,QAAQ,gBAAgB;AAE7C,QAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAC/D,QAAM,UAAU,QAAQ,GAAG,KAAK,+BAA+B;AAE/D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,MAC/B,MAAM;AAAA,IACR;AAAA,IACA,SAAS;AAAA,MACP,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,CAAC,OAAO,IAAI,CAAC;AAAA,IACjC;AAAA,EACF;AACF;AA+BA,eAAsB,aAAa,OAAmD;AACpF,QAAM,aAAa,gBAAgB;AACnC,QAAM,OAAO,MAAM,QAAQ;AAE3B,MAAI,CAAC,MAAM,SAAS;AAClB,UAAM,IAAI,MAAM,yEAAyE;AAAA,EAC3F;AAGA,QAAM,QAAQ,MAAM,WAAW,QAAQ,EAAE,KAAK,CAAC;AAE/C,QAAM,UAAU,MAAM;AAGtB,QAAM,eAAe,QAAQ,SAAS,GAAG,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI;AAEpE,SAAO,MAAM,IAAI,CAAC,SAAS;AAEzB,QAAI,OAAO;AAEX,QAAI,KAAK,UAAU,OAAO,KAAK,WAAW,UAAU;AAElD,YAAM,cAAc,OAAO,OAAO,KAAK,MAAM;AAC7C,UAAI,YAAY,SAAS,GAAG;AAC1B,eAAO,YAAY,CAAC;AAAA,MACtB;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ,KAAK,aAAa;AAC7B,aAAO,KAAK;AAAA,IACd;AAGA,UAAM,YAAY,QAAQ,CAAC,KAAK,WAAW,GAAG,IAAI,IAAI,IAAI,KAAK;AAG/D,UAAM,eAAe,oBAAI,KAAK;AAE9B,WAAO;AAAA,MACL,KAAK,GAAG,YAAY,GAAG,SAAS;AAAA,MAChC;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/server.mjs
CHANGED
|
@@ -140,6 +140,20 @@ function createCachedEntityResolver(resolver) {
|
|
|
140
140
|
return entity;
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
|
+
function NitroEntityJsonLd({ entity }) {
|
|
144
|
+
if (!entity?.jsonld) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return /* @__PURE__ */ jsx(
|
|
148
|
+
"script",
|
|
149
|
+
{
|
|
150
|
+
type: "application/ld+json",
|
|
151
|
+
dangerouslySetInnerHTML: {
|
|
152
|
+
__html: JSON.stringify(entity.jsonld).replace(/</g, "\\u003c")
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
}
|
|
143
157
|
function NitroPage({
|
|
144
158
|
page
|
|
145
159
|
}) {
|
|
@@ -290,6 +304,7 @@ async function nitroSitemap(state) {
|
|
|
290
304
|
export {
|
|
291
305
|
NitroBlock,
|
|
292
306
|
NitroDebugInfo,
|
|
307
|
+
NitroEntityJsonLd,
|
|
293
308
|
NitroPage,
|
|
294
309
|
NitroSlot,
|
|
295
310
|
getNitro,
|
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 * Interface for Nitro configuration state\n */\nexport interface NitroState {\n configuration: Configuration | null;\n accessToken: string | null;\n lang: string | null;\n baseUrl: string | null;\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}\n\n/**\n * Global Nitro state - shared across server and middleware\n */\nexport const globalNitroState: NitroState = {\n configuration: null,\n accessToken: null,\n lang: null,\n baseUrl: null,\n components: {},\n showMissingComponentAlert: false,\n liveEdit: false,\n serverCacheTtl: 1200,\n clientCacheTtl: 900\n};\n\n/**\n * Access the Nitro configuration state\n * Can be used anywhere: server components, middlewares, API routes, etc.\n * Must be called after initNitro() has been initialized.\n * \n * @throws {Error} If Nitro has not been initialized with initNitro()\n * \n * @example\n * ```ts\n * const state = getNitro();\n * const { configuration, lang, components } = state;\n * ```\n */\nexport function getNitro(): NitroState {\n if (!globalNitroState.configuration) {\n throw new Error('Nitro has not been initialized. Make sure to call initNitro() first.');\n }\n return globalNitroState;\n}\n\nexport const 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 components?: object;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): ( () => NitroState ) => {\n\n if (!globalNitroState.configuration) {\n globalNitroState.configuration = new Configuration({\n apiKey: accessToken,\n });\n }\n\n globalNitroState.accessToken = accessToken;\n globalNitroState.lang = lang ?? null;\n globalNitroState.baseUrl = baseUrl ?? null;\n globalNitroState.components = components ?? {};\n globalNitroState.showMissingComponentAlert = showMissingComponentAlert ?? liveEdit ?? false;\n globalNitroState.liveEdit = liveEdit ?? false;\n globalNitroState.serverCacheTtl = serverCacheTtl ?? 1200;\n globalNitroState.clientCacheTtl = clientCacheTtl ?? 900;\n\n return () => globalNitroState;\n}\n\nexport const getNitroConfig = cache(async (): Promise<ConfigResponse> => {\n const state = getNitro();\n\n const configApi = new ConfigApi(state.configuration!);\n const useLang = state.lang ?? undefined;\n\n const config = await configApi.config({ lang: useLang });\n \n return config;\n});\n\nexport function getNitroPages(): PagesApi {\n return new PagesApi(getNitro().configuration!);\n}\n\nexport function getNitroEntities(): EntitiesApi {\n return new EntitiesApi(getNitro().configuration!);\n}\n\nexport function getNitroSitemap(): SitemapApi {\n return new SitemapApi(getNitro().configuration!);\n}\n\nexport function getNitroSearch(): SearchApi {\n return new SearchApi(getNitro().configuration!);\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 * Allows any param structure from Next.js app router\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Internal helper to resolve Nitro page from route params\n * Uses React cache to avoid duplicate fetching\n */\nconst resolveNitroRoute = 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 getNitroPages()\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/**\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 * Helper function to read environment variables with fallback\n * Checks process.env for server-side environment variables\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 * NitroDebugInfo Component\n * \n * Outputs debug information about the current Nitro/Flyo setup as an HTML comment.\n * This includes environment info, API version, token type, deployment details, etc.\n * \n * Usage: Add <NitroDebugInfo config={config} /> to your layout to include debug info in the HTML output.\n */\nexport function NitroDebugInfo({ config }: { config: ConfigResponse }) {\n try {\n // Get Nitro state\n const state = getNitro();\n\n // Get environment variables\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 // Get token from configuration and determine type\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 // Get live edit / debug status\n const debug = state.liveEdit;\n\n // Get API version from config.nitro\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 // Build debug info parts\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 just the HTML comment as a real HTML comment (not text)\n // React requires dangerouslySetInnerHTML for raw HTML, so we use an empty template element\n // which is semantic and doesn't render in the DOM tree\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch (error) {\n // If Nitro is not initialized or there's an error, return empty comment\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Internal helper to wrap and cache entity resolvers\n * Ensures the resolver is only called once per unique params\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\n/**\n * NitroPage component renders all blocks from a Flyo page\n */\nexport function NitroPage({\n page,\n}: {\n page: Page\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 />\n ))}\n </>\n );\n}\n\nexport function NitroBlock({\n block,\n}: {\n block: Block\n}) {\n if (!block) {\n return null;\n }\n\n const state = getNitro();\n const Component = block.component ? state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (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 component renders nested blocks from a slot\n * Used for recursive block rendering when blocks contain slots\n * \n * @example\n * ```tsx\n * import { NitroSlot } from '@flyo/nitro-next/server';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots.mysuperslotname} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n}: {\n slot?: {\n content?: Block[];\n };\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 />\n ))}\n </>\n );\n}\n\n/**\n * Default page route handler for Nitro pages\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageRoute as default } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageRoute(props: RouteParams) {\n const { page } = await resolveNitroRoute(props);\n return <NitroPage page={page} />;\n}\n\n/**\n * Generate metadata for Nitro pages\n * Provides basic meta tags based on Flyo page data\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageGenerateMetadata as generateMetadata } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageGenerateMetadata(\n props: RouteParams\n): Promise<Metadata> {\n const { page } = await resolveNitroRoute(props);\n\n // Extract meta information from page\n const meta = page.meta_json;\n \n const title = meta?.title ?? page.title ?? 'Page';\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 * Generate static params for all Nitro pages\n * Enables static site generation (SSG) for all pages\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageGenerateStaticParams as generateStaticParams } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageGenerateStaticParams() {\n const cfg = await getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n}\n\n/**\n * Default entity route handler with custom resolver\n * Flexible solution that works with any route param structure\n * \n * @example\n * ```ts\n * // app/blog/[slug]/page.tsx\n * const resolver = async (params: Promise<{ slug: string }>) => {\n * const { slug } = await params;\n * return getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n * \n * export default (props) => nitroEntityRoute(props, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>\n * });\n * ```\n * \n * @example\n * ```ts\n * // app/items/[uniqueid]/page.tsx\n * const resolver = async (params: Promise<{ uniqueid: string }>) => {\n * const { uniqueid } = await params;\n * return getNitroEntities().entityByUniqueid({ uniqueid });\n * };\n * \n * export default (props) => nitroEntityRoute(props, { resolver });\n * ```\n * \n * @example\n * ```ts\n * // app/custom/[whatever]/page.tsx\n * const resolver = async (params: Promise<{ whatever: string }>) => {\n * const { whatever } = await params;\n * return getNitroEntities().entityBySlug({ slug: whatever });\n * };\n * \n * export default (props) => nitroEntityRoute(props, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n props: EntityRouteParams<T>,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n \n return (async () => {\n const entity = await cachedResolver(props);\n \n if (options.render) {\n return options.render(entity);\n }\n\n // Default simple render - users should provide their own render function\n return <div>{entity.entity?.entity_title}</div>;\n })();\n}\n\n/**\n * Generate metadata for Nitro entities with custom resolver\n * Works with any route param structure\n * \n * @example\n * ```ts\n * // app/blog/[slug]/page.tsx\n * const resolver = async (params: Promise<{ slug: string }>) => {\n * const { slug } = await params;\n * return getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n * \n * export const generateMetadata = (props) => nitroEntityGenerateMetadata(props, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function nitroEntityGenerateMetadata<T = any>(\n props: EntityRouteParams<T>,\n options: {\n resolver: EntityResolver<T>;\n }\n): Promise<Metadata> {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title ?? 'Entity';\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\n/**\n * Generate sitemap for Next.js from Flyo Nitro\n * Fetches all pages and entities from the sitemap endpoint\n * Uses the baseUrl from the Nitro configuration state\n * \n * @param state The Nitro state containing configuration and baseUrl\n * @returns Promise resolving to Next.js MetadataRoute.Sitemap format\n * \n * @example\n * ```ts\n * // app/sitemap.ts\n * import { nitroSitemap } from '@flyo/nitro-next/server';\n * import { flyoConfig } from '../flyo.config';\n * \n * export default async function sitemap() {\n * return nitroSitemap(flyoConfig());\n * }\n * ```\n * \n * @example\n * ```ts\n * // flyo.config.tsx\n * export const flyoConfig = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN!,\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * lang: 'en',\n * });\n * ```\n */\nexport async function nitroSitemap(state: NitroState): Promise<MetadataRoute.Sitemap> {\n const sitemapApi = getNitroSitemap();\n const lang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured in Nitro state. Please set it in initNitro().');\n }\n\n // Fetch all sitemap entries from Flyo Nitro\n const items = await sitemapApi.sitemap({ lang });\n\n const baseUrl = state.baseUrl;\n \n // Remove trailing slash from baseUrl for consistency\n const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n return items.map((item) => {\n // Prefer routes object if available, otherwise use entity_slug\n let path = '';\n \n if (item.routes && typeof item.routes === 'object') {\n // Use the first available route from the routes object\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n \n // Fallback to entity_slug if no routes found\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n // Ensure path starts with /\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n // Convert Unix timestamp to Date if available\n const lastModified = new Date();\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified,\n };\n });\n}"],"mappings":"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","names":[]}
|
|
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 * Interface for Nitro configuration state\n */\nexport interface NitroState {\n configuration: Configuration | null;\n accessToken: string | null;\n lang: string | null;\n baseUrl: string | null;\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}\n\n/**\n * Global Nitro state - shared across server and middleware\n */\nexport const globalNitroState: NitroState = {\n configuration: null,\n accessToken: null,\n lang: null,\n baseUrl: null,\n components: {},\n showMissingComponentAlert: false,\n liveEdit: false,\n serverCacheTtl: 1200,\n clientCacheTtl: 900\n};\n\n/**\n * Access the Nitro configuration state\n * Can be used anywhere: server components, middlewares, API routes, etc.\n * Must be called after initNitro() has been initialized.\n * \n * @throws {Error} If Nitro has not been initialized with initNitro()\n * \n * @example\n * ```ts\n * const state = getNitro();\n * const { configuration, lang, components } = state;\n * ```\n */\nexport function getNitro(): NitroState {\n if (!globalNitroState.configuration) {\n throw new Error('Nitro has not been initialized. Make sure to call initNitro() first.');\n }\n return globalNitroState;\n}\n\nexport const 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 components?: object;\n showMissingComponentAlert?: boolean;\n liveEdit?: boolean;\n serverCacheTtl?: number;\n clientCacheTtl?: number;\n}): ( () => NitroState ) => {\n\n if (!globalNitroState.configuration) {\n globalNitroState.configuration = new Configuration({\n apiKey: accessToken,\n });\n }\n\n globalNitroState.accessToken = accessToken;\n globalNitroState.lang = lang ?? null;\n globalNitroState.baseUrl = baseUrl ?? null;\n globalNitroState.components = components ?? {};\n globalNitroState.showMissingComponentAlert = showMissingComponentAlert ?? liveEdit ?? false;\n globalNitroState.liveEdit = liveEdit ?? false;\n globalNitroState.serverCacheTtl = serverCacheTtl ?? 1200;\n globalNitroState.clientCacheTtl = clientCacheTtl ?? 900;\n\n return () => globalNitroState;\n}\n\nexport const getNitroConfig = cache(async (): Promise<ConfigResponse> => {\n const state = getNitro();\n\n const configApi = new ConfigApi(state.configuration!);\n const useLang = state.lang ?? undefined;\n\n const config = await configApi.config({ lang: useLang });\n \n return config;\n});\n\nexport function getNitroPages(): PagesApi {\n return new PagesApi(getNitro().configuration!);\n}\n\nexport function getNitroEntities(): EntitiesApi {\n return new EntitiesApi(getNitro().configuration!);\n}\n\nexport function getNitroSitemap(): SitemapApi {\n return new SitemapApi(getNitro().configuration!);\n}\n\nexport function getNitroSearch(): SearchApi {\n return new SearchApi(getNitro().configuration!);\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 * Allows any param structure from Next.js app router\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype EntityRouteParams<T = any> = {\n params: Promise<T>;\n};\n\n/**\n * Internal helper to resolve Nitro page from route params\n * Uses React cache to avoid duplicate fetching\n */\nconst resolveNitroRoute = 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 getNitroPages()\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/**\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 * Helper function to read environment variables with fallback\n * Checks process.env for server-side environment variables\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 * NitroDebugInfo Component\n * \n * Outputs debug information about the current Nitro/Flyo setup as an HTML comment.\n * This includes environment info, API version, token type, deployment details, etc.\n * \n * Usage: Add <NitroDebugInfo config={config} /> to your layout to include debug info in the HTML output.\n */\nexport function NitroDebugInfo({ config }: { config: ConfigResponse }) {\n try {\n // Get Nitro state\n const state = getNitro();\n\n // Get environment variables\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 // Get token from configuration and determine type\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 // Get live edit / debug status\n const debug = state.liveEdit;\n\n // Get API version from config.nitro\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 // Build debug info parts\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 just the HTML comment as a real HTML comment (not text)\n // React requires dangerouslySetInnerHTML for raw HTML, so we use an empty template element\n // which is semantic and doesn't render in the DOM tree\n return (\n <template dangerouslySetInnerHTML={{ __html: `<!-- ${debugInfo} -->` }} suppressHydrationWarning />\n );\n } catch (error) {\n // If Nitro is not initialized or there's an error, return empty comment\n return <template dangerouslySetInnerHTML={{ __html: `<!-- nitro-debug: not initialized -->` }} suppressHydrationWarning />;\n }\n}\n\n/**\n * Internal helper to wrap and cache entity resolvers\n * Ensures the resolver is only called once per unique params\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\n/**\n * Renders a JSON-LD structured data script tag from an Entity's jsonld field.\n * Safely escapes HTML entities to prevent XSS attacks.\n * Returns null if the entity has no jsonld data.\n * \n * @example\n * ```tsx\n * import { NitroEntityJsonLd } from '@flyo/nitro-next/server';\n * \n * export default function BlogPost({ entity }: { entity: Entity }) {\n * return (\n * <>\n * <NitroEntityJsonLd entity={entity} />\n * <h1>{entity.entity?.entity_title}</h1>\n * </>\n * );\n * }\n * ```\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 component renders all blocks from a Flyo page\n */\nexport function NitroPage({\n page,\n}: {\n page: Page\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 />\n ))}\n </>\n );\n}\n\nexport function NitroBlock({\n block,\n}: {\n block: Block\n}) {\n if (!block) {\n return null;\n }\n\n const state = getNitro();\n const Component = block.component ? state.components[block.component] : undefined;\n\n if (Component) {\n return <Component block={block} />;\n }\n\n if (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 component renders nested blocks from a slot\n * Used for recursive block rendering when blocks contain slots\n * \n * @example\n * ```tsx\n * import { NitroSlot } from '@flyo/nitro-next/server';\n * \n * export default function MyComponent({ block }) {\n * return (\n * <div>\n * <NitroSlot slot={block.slots.mysuperslotname} />\n * </div>\n * );\n * }\n * ```\n */\nexport function NitroSlot({\n slot,\n}: {\n slot?: {\n content?: Block[];\n };\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 />\n ))}\n </>\n );\n}\n\n/**\n * Default page route handler for Nitro pages\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageRoute as default } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageRoute(props: RouteParams) {\n const { page } = await resolveNitroRoute(props);\n return <NitroPage page={page} />;\n}\n\n/**\n * Generate metadata for Nitro pages\n * Provides basic meta tags based on Flyo page data\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageGenerateMetadata as generateMetadata } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageGenerateMetadata(\n props: RouteParams\n): Promise<Metadata> {\n const { page } = await resolveNitroRoute(props);\n\n // Extract meta information from page\n const meta = page.meta_json;\n \n const title = meta?.title ?? page.title ?? 'Page';\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 * Generate static params for all Nitro pages\n * Enables static site generation (SSG) for all pages\n * Can be re-exported directly from Next.js app routes\n * \n * @example\n * ```ts\n * // app/[[...slug]]/page.tsx\n * export { nitroPageGenerateStaticParams as generateStaticParams } from '@flyo/nitro-next/server';\n * ```\n */\nexport async function nitroPageGenerateStaticParams() {\n const cfg = await getNitroConfig();\n const pages = cfg.pages ?? [];\n\n return pages.map((path: string) => ({\n slug: path === '' ? undefined : path.split('/'),\n }));\n}\n\n/**\n * Default entity route handler with custom resolver\n * Flexible solution that works with any route param structure\n * \n * @example\n * ```ts\n * // app/blog/[slug]/page.tsx\n * const resolver = async (params: Promise<{ slug: string }>) => {\n * const { slug } = await params;\n * return getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n * \n * export default (props) => nitroEntityRoute(props, {\n * resolver,\n * render: (entity) => <h1>{entity.entity?.entity_title}</h1>\n * });\n * ```\n * \n * @example\n * ```ts\n * // app/items/[uniqueid]/page.tsx\n * const resolver = async (params: Promise<{ uniqueid: string }>) => {\n * const { uniqueid } = await params;\n * return getNitroEntities().entityByUniqueid({ uniqueid });\n * };\n * \n * export default (props) => nitroEntityRoute(props, { resolver });\n * ```\n * \n * @example\n * ```ts\n * // app/custom/[whatever]/page.tsx\n * const resolver = async (params: Promise<{ whatever: string }>) => {\n * const { whatever } = await params;\n * return getNitroEntities().entityBySlug({ slug: whatever });\n * };\n * \n * export default (props) => nitroEntityRoute(props, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function nitroEntityRoute<T = any>(\n props: EntityRouteParams<T>,\n options: {\n resolver: EntityResolver<T>;\n render?: (entity: Entity) => React.ReactNode;\n }\n) {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n \n return (async () => {\n const entity = await cachedResolver(props);\n \n if (options.render) {\n return options.render(entity);\n }\n\n // Default simple render - users should provide their own render function\n return <div>{entity.entity?.entity_title}</div>;\n })();\n}\n\n/**\n * Generate metadata for Nitro entities with custom resolver\n * Works with any route param structure\n * \n * @example\n * ```ts\n * // app/blog/[slug]/page.tsx\n * const resolver = async (params: Promise<{ slug: string }>) => {\n * const { slug } = await params;\n * return getNitroEntities().entityBySlug({ slug, typeId: 123 });\n * };\n * \n * export const generateMetadata = (props) => nitroEntityGenerateMetadata(props, { resolver });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function nitroEntityGenerateMetadata<T = any>(\n props: EntityRouteParams<T>,\n options: {\n resolver: EntityResolver<T>;\n }\n): Promise<Metadata> {\n const cachedResolver = createCachedEntityResolver(options.resolver);\n const entity = await cachedResolver(props);\n\n const title = entity.entity?.entity_title ?? 'Entity';\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\n/**\n * Generate sitemap for Next.js from Flyo Nitro\n * Fetches all pages and entities from the sitemap endpoint\n * Uses the baseUrl from the Nitro configuration state\n * \n * @param state The Nitro state containing configuration and baseUrl\n * @returns Promise resolving to Next.js MetadataRoute.Sitemap format\n * \n * @example\n * ```ts\n * // app/sitemap.ts\n * import { nitroSitemap } from '@flyo/nitro-next/server';\n * import { flyoConfig } from '../flyo.config';\n * \n * export default async function sitemap() {\n * return nitroSitemap(flyoConfig());\n * }\n * ```\n * \n * @example\n * ```ts\n * // flyo.config.tsx\n * export const flyoConfig = initNitro({\n * accessToken: process.env.FLYO_ACCESS_TOKEN!,\n * baseUrl: process.env.SITE_URL || 'http://localhost:3000',\n * lang: 'en',\n * });\n * ```\n */\nexport async function nitroSitemap(state: NitroState): Promise<MetadataRoute.Sitemap> {\n const sitemapApi = getNitroSitemap();\n const lang = state.lang ?? undefined;\n\n if (!state.baseUrl) {\n throw new Error('baseUrl is not configured in Nitro state. Please set it in initNitro().');\n }\n\n // Fetch all sitemap entries from Flyo Nitro\n const items = await sitemapApi.sitemap({ lang });\n\n const baseUrl = state.baseUrl;\n \n // Remove trailing slash from baseUrl for consistency\n const cleanBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n return items.map((item) => {\n // Prefer routes object if available, otherwise use entity_slug\n let path = '';\n \n if (item.routes && typeof item.routes === 'object') {\n // Use the first available route from the routes object\n const routeValues = Object.values(item.routes);\n if (routeValues.length > 0) {\n path = routeValues[0];\n }\n }\n \n // Fallback to entity_slug if no routes found\n if (!path && item.entity_slug) {\n path = item.entity_slug;\n }\n\n // Ensure path starts with /\n const cleanPath = path && !path.startsWith('/') ? `/${path}` : path;\n\n // Convert Unix timestamp to Date if available\n const lastModified = new Date();\n\n return {\n url: `${cleanBaseUrl}${cleanPath}`,\n lastModified,\n };\n });\n}"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flyo/nitro-next",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Connecting Flyo Headless Content Hub into your Next.js project.",
|
|
5
5
|
"homepage": "https://dev.flyo.cloud/nitro",
|
|
6
6
|
"keywords": [
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@testing-library/jest-dom": "^6.1.5",
|
|
59
|
-
"@testing-library/react": "^
|
|
59
|
+
"@testing-library/react": "^16.0.0",
|
|
60
60
|
"@types/jest": "^29.5.11",
|
|
61
61
|
"@types/node": "^20.10.0",
|
|
62
62
|
"@types/react": "^19.2.1",
|
|
@@ -68,9 +68,6 @@
|
|
|
68
68
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
69
69
|
"jest": "^29.7.0",
|
|
70
70
|
"jest-environment-jsdom": "^29.7.0",
|
|
71
|
-
"next": "^14.2.25",
|
|
72
|
-
"react": "^18.2.0",
|
|
73
|
-
"react-dom": "^18.2.0",
|
|
74
71
|
"ts-jest": "^29.1.1",
|
|
75
72
|
"tsup": "^8.0.0",
|
|
76
73
|
"typescript": "^5.3.0"
|