@netrojs/fnetro 0.2.21 → 0.3.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.js CHANGED
@@ -1,21 +1,71 @@
1
1
  // client.ts
2
- import { createSignal, createComponent } from "solid-js";
3
- import { hydrate } from "solid-js/web";
4
- import { Router, Route } from "@solidjs/router";
2
+ import {
3
+ createSSRApp,
4
+ defineAsyncComponent,
5
+ defineComponent,
6
+ h,
7
+ inject,
8
+ reactive,
9
+ readonly
10
+ } from "vue";
11
+ import {
12
+ createRouter,
13
+ createWebHistory,
14
+ RouterView
15
+ } from "vue-router";
16
+
17
+ // types.ts
18
+ var SPA_HEADER = "x-fnetro-spa";
19
+ var STATE_KEY = "__FNETRO_STATE__";
20
+ var PARAMS_KEY = "__FNETRO_PARAMS__";
21
+ var SEO_KEY = "__FNETRO_SEO__";
22
+ var DATA_KEY = /* @__PURE__ */ Symbol.for("fnetro:data");
5
23
 
6
24
  // core.ts
25
+ var VUE_BRANDS = ["__name", "__file", "__vccOpts", "setup", "render", "data", "components"];
26
+ function isAsyncLoader(c) {
27
+ if (typeof c !== "function") return false;
28
+ const f = c;
29
+ for (const brand of VUE_BRANDS) {
30
+ if (brand in f) return false;
31
+ }
32
+ return true;
33
+ }
7
34
  function definePage(def) {
8
35
  return { __type: "page", ...def };
9
36
  }
10
37
  function defineGroup(def) {
11
38
  return { __type: "group", ...def };
12
39
  }
13
- function defineLayout(Component) {
14
- return { __type: "layout", Component };
40
+ function defineLayout(component) {
41
+ return { __type: "layout", component };
15
42
  }
16
43
  function defineApiRoute(path, register) {
17
44
  return { __type: "api", path, register };
18
45
  }
46
+ function compilePath(path) {
47
+ const keys = [];
48
+ const src = path.replace(/\[\.\.\.([^\]]+)\]/g, (_, k) => {
49
+ keys.push(k);
50
+ return "(.*)";
51
+ }).replace(/\[([^\]]+)\]/g, (_, k) => {
52
+ keys.push(k);
53
+ return "([^/]+)";
54
+ }).replace(/\*/g, "(.*)");
55
+ return { re: new RegExp(`^${src}$`), keys };
56
+ }
57
+ function matchPath(cp, pathname) {
58
+ const m = pathname.match(cp.re);
59
+ if (!m) return null;
60
+ const params = {};
61
+ cp.keys.forEach((k, i) => {
62
+ params[k] = decodeURIComponent(m[i + 1] ?? "");
63
+ });
64
+ return params;
65
+ }
66
+ function toVueRouterPath(fnetroPath) {
67
+ return fnetroPath.replace(/\[\.\.\.([^\]]+)\]/g, ":$1(.*)*").replace(/\[([^\]]+)\]/g, ":$1");
68
+ }
19
69
  function resolveRoutes(routes, options = {}) {
20
70
  const pages = [];
21
71
  const apis = [];
@@ -26,7 +76,11 @@ function resolveRoutes(routes, options = {}) {
26
76
  const prefix = (options.prefix ?? "") + route.prefix;
27
77
  const mw = [...options.middleware ?? [], ...route.middleware ?? []];
28
78
  const layout = route.layout !== void 0 ? route.layout : options.layout;
29
- const sub = resolveRoutes(route.routes, { prefix, middleware: mw, layout });
79
+ const sub = resolveRoutes(route.routes, {
80
+ prefix,
81
+ middleware: mw,
82
+ ...layout !== void 0 && { layout }
83
+ });
30
84
  pages.push(...sub.pages);
31
85
  apis.push(...sub.apis);
32
86
  } else {
@@ -40,58 +94,14 @@ function resolveRoutes(routes, options = {}) {
40
94
  }
41
95
  return { pages, apis };
42
96
  }
43
- function compilePath(path) {
44
- const keys = [];
45
- const src = path.replace(/\[\.\.\.([^\]]+)\]/g, (_, k) => {
46
- keys.push(k);
47
- return "(.*)";
48
- }).replace(/\[([^\]]+)\]/g, (_, k) => {
49
- keys.push(k);
50
- return "([^/]+)";
51
- }).replace(/\*/g, "(.*)");
52
- return { re: new RegExp(`^${src}$`), keys };
53
- }
54
- function matchPath(compiled, pathname) {
55
- const m = pathname.match(compiled.re);
56
- if (!m) return null;
57
- const params = {};
58
- compiled.keys.forEach((k, i) => {
59
- params[k] = decodeURIComponent(m[i + 1] ?? "");
60
- });
61
- return params;
62
- }
63
- var SPA_HEADER = "x-fnetro-spa";
64
- var STATE_KEY = "__FNETRO_STATE__";
65
- var PARAMS_KEY = "__FNETRO_PARAMS__";
66
- var SEO_KEY = "__FNETRO_SEO__";
67
97
 
68
98
  // client.ts
69
- import { useNavigate, useParams, useLocation, A, useSearchParams } from "@solidjs/router";
70
- var _routes = [];
71
- var _appLayout;
72
- function findRoute(pathname) {
73
- for (const { route, cp } of _routes) {
74
- const params = matchPath(cp, pathname);
75
- if (params !== null) return { route, params };
76
- }
77
- return null;
78
- }
79
- var _mw = [];
80
- function useClientMiddleware(mw) {
81
- _mw.push(mw);
82
- }
83
- async function runMiddleware(url, done) {
84
- const chain = [..._mw, async (_u, next) => {
85
- await done();
86
- await next();
87
- }];
88
- let i = 0;
89
- const run = async () => {
90
- const fn = chain[i++];
91
- if (fn) await fn(url, run);
92
- };
93
- await run();
94
- }
99
+ import {
100
+ useRoute,
101
+ useRouter,
102
+ RouterLink,
103
+ RouterView as RouterView2
104
+ } from "vue-router";
95
105
  function setMeta(selector, attr, val) {
96
106
  if (!val) {
97
107
  document.querySelector(selector)?.remove();
@@ -100,8 +110,8 @@ function setMeta(selector, attr, val) {
100
110
  let el = document.querySelector(selector);
101
111
  if (!el) {
102
112
  el = document.createElement("meta");
103
- const m = /\[(\w+[:-]?\w*)="([^"]+)"\]/.exec(selector);
104
- if (m) el.setAttribute(m[1], m[2]);
113
+ const [, attrName = "", attrVal = ""] = /\[([^=]+)="([^"]+)"\]/.exec(selector) ?? [];
114
+ if (attrName) el.setAttribute(attrName, attrVal);
105
115
  document.head.appendChild(el);
106
116
  }
107
117
  el.setAttribute(attr, val);
@@ -121,156 +131,140 @@ function syncSEO(seo) {
121
131
  setMeta('[name="twitter:title"]', "content", seo.twitterTitle);
122
132
  setMeta('[name="twitter:description"]', "content", seo.twitterDescription);
123
133
  setMeta('[name="twitter:image"]', "content", seo.twitterImage);
124
- const canon = seo.canonical;
125
- let linkEl = document.querySelector('link[rel="canonical"]');
126
- if (canon) {
127
- if (!linkEl) {
128
- linkEl = document.createElement("link");
129
- linkEl.rel = "canonical";
130
- document.head.appendChild(linkEl);
134
+ let link = document.querySelector('link[rel="canonical"]');
135
+ if (seo.canonical) {
136
+ if (!link) {
137
+ link = document.createElement("link");
138
+ link.rel = "canonical";
139
+ document.head.appendChild(link);
131
140
  }
132
- linkEl.href = canon;
141
+ link.href = seo.canonical;
133
142
  } else {
134
- linkEl?.remove();
143
+ link?.remove();
135
144
  }
136
145
  }
137
- var _cache = /* @__PURE__ */ new Map();
138
- function fetchPayload(href) {
139
- if (!_cache.has(href)) {
140
- _cache.set(
146
+ var _fetchCache = /* @__PURE__ */ new Map();
147
+ function fetchSPA(href) {
148
+ if (!_fetchCache.has(href)) {
149
+ _fetchCache.set(
141
150
  href,
142
151
  fetch(href, { headers: { [SPA_HEADER]: "1" } }).then((r) => {
143
- if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
152
+ if (!r.ok) throw new Error(`[fnetro] ${r.status} ${r.statusText} \u2014 ${href}`);
144
153
  return r.json();
145
154
  })
146
155
  );
147
156
  }
148
- return _cache.get(href);
157
+ return _fetchCache.get(href);
149
158
  }
150
159
  function prefetch(url) {
151
160
  try {
152
161
  const u = new URL(url, location.origin);
153
- if (u.origin !== location.origin || !findRoute(u.pathname)) return;
154
- fetchPayload(u.toString());
162
+ if (u.origin === location.origin) fetchSPA(u.toString());
155
163
  } catch {
156
164
  }
157
165
  }
158
- function makeRouteComponent(route, appLayout, initialState, initialParams, initialSeo, prefetchOnHover) {
159
- return function FNetroRouteComponent(routerProps) {
160
- const routeParams = routerProps.params ?? {};
161
- const pathname = routerProps.location?.pathname ?? location.pathname;
162
- const serverData = initialState[pathname];
163
- const [data, setData] = createSignal(serverData ?? {});
164
- const [params, setParams] = createSignal(serverData ? initialParams : routeParams);
165
- if (!serverData) {
166
- const url = new URL(pathname, location.origin).toString();
167
- fetchPayload(url).then((payload) => {
168
- setData(payload.state ?? {});
169
- setParams(payload.params ?? {});
170
- syncSEO(payload.seo ?? {});
171
- }).catch((err) => {
172
- console.error("[fnetro] Failed to load route data:", err);
173
- });
174
- } else {
175
- syncSEO(initialSeo);
166
+ var _mw = [];
167
+ function useClientMiddleware(mw) {
168
+ _mw.push(mw);
169
+ }
170
+ async function runMw(url, done) {
171
+ const chain = [
172
+ ..._mw,
173
+ async (_, next) => {
174
+ await done();
175
+ await next();
176
176
  }
177
- const layout = route.layout !== void 0 ? route.layout : appLayout;
178
- const pageEl = () => createComponent(route.page.Page, {
179
- ...data(),
180
- url: pathname,
181
- params: params()
182
- });
183
- if (!layout) return pageEl();
184
- return createComponent(layout.Component, {
185
- url: pathname,
186
- params: params(),
187
- get children() {
188
- return pageEl();
189
- }
190
- });
177
+ ];
178
+ let i = 0;
179
+ const run = async () => {
180
+ const fn = chain[i++];
181
+ if (fn) await fn(url, run);
191
182
  };
183
+ await run();
192
184
  }
193
- async function navigate(to, opts = {}) {
194
- const u = new URL(to, location.origin);
195
- if (u.origin !== location.origin) {
196
- location.href = to;
197
- return;
185
+ var _pageData = reactive({});
186
+ function updatePageData(newData) {
187
+ for (const k of Object.keys(_pageData)) {
188
+ if (!(k in newData)) delete _pageData[k];
198
189
  }
199
- if (!findRoute(u.pathname)) {
200
- location.href = to;
201
- return;
190
+ Object.assign(_pageData, newData);
191
+ }
192
+ function usePageData() {
193
+ const data = inject(DATA_KEY);
194
+ if (data === void 0) {
195
+ throw new Error("[fnetro] usePageData() must be called inside a component setup().");
202
196
  }
203
- await runMiddleware(u.pathname, async () => {
204
- try {
205
- const payload = await fetchPayload(u.toString());
206
- history[opts.replace ? "replaceState" : "pushState"](
207
- { url: u.pathname },
208
- "",
209
- u.pathname
210
- );
211
- if (opts.scroll !== false) window.scrollTo(0, 0);
212
- syncSEO(payload.seo ?? {});
213
- window.dispatchEvent(new PopStateEvent("popstate", { state: history.state }));
214
- } catch (err) {
215
- console.error("[fnetro] Navigation error:", err);
216
- location.href = to;
217
- }
218
- });
197
+ return data;
219
198
  }
220
199
  async function boot(options) {
200
+ const container = document.getElementById("fnetro-app");
201
+ if (!container) {
202
+ console.error("[fnetro] #fnetro-app not found \u2014 aborting hydration.");
203
+ return;
204
+ }
221
205
  const { pages } = resolveRoutes(options.routes, {
222
- layout: options.layout,
206
+ ...options.layout !== void 0 && { layout: options.layout },
223
207
  middleware: []
224
208
  });
225
- _routes = pages.map((r) => ({ route: r, cp: compilePath(r.fullPath) }));
226
- _appLayout = options.layout;
227
- const pathname = location.pathname;
228
- if (!findRoute(pathname)) {
229
- console.warn(`[fnetro] No route matched "${pathname}" \u2014 skipping hydration`);
230
- return;
231
- }
232
209
  const stateMap = window[STATE_KEY] ?? {};
233
- const paramsMap = window[PARAMS_KEY] ?? {};
234
210
  const seoData = window[SEO_KEY] ?? {};
235
- const container = document.getElementById("fnetro-app");
236
- if (!container) {
237
- console.error("[fnetro] #fnetro-app not found \u2014 aborting hydration");
238
- return;
211
+ const pathname = location.pathname;
212
+ updatePageData(stateMap[pathname] ?? {});
213
+ syncSEO(seoData);
214
+ const vueRoutes = pages.map((r) => {
215
+ const layout = r.layout !== void 0 ? r.layout : options.layout;
216
+ const comp = r.page.component;
217
+ const PageComp = isAsyncLoader(comp) ? defineAsyncComponent(comp) : comp;
218
+ const routeComp = layout ? defineComponent({
219
+ name: "FNetroRoute",
220
+ setup: () => () => h(layout.component, null, {
221
+ default: () => h(PageComp)
222
+ })
223
+ }) : PageComp;
224
+ return { path: toVueRouterPath(r.fullPath), component: routeComp };
225
+ });
226
+ const currentRoute = pages.find((r) => matchPath(compilePath(r.fullPath), pathname) !== null);
227
+ if (currentRoute && isAsyncLoader(currentRoute.page.component)) {
228
+ await currentRoute.page.component();
239
229
  }
240
- const prefetchOnHover = options.prefetchOnHover !== false;
241
- const routeElements = pages.map(
242
- (route) => createComponent(Route, {
243
- path: route.fullPath,
244
- component: makeRouteComponent(
245
- route,
246
- _appLayout,
247
- stateMap,
248
- paramsMap,
249
- seoData,
250
- prefetchOnHover
251
- )
252
- })
253
- );
254
- hydrate(
255
- () => createComponent(Router, {
256
- get children() {
257
- return routeElements;
258
- }
259
- }),
260
- container
261
- );
262
- if (prefetchOnHover) {
230
+ const app = createSSRApp({ name: "FNetroApp", render: () => h(RouterView) });
231
+ app.provide(DATA_KEY, readonly(_pageData));
232
+ const router = createRouter({ history: createWebHistory(), routes: vueRoutes });
233
+ let isInitialNav = true;
234
+ router.beforeEach(async (to, _from, next) => {
235
+ if (isInitialNav) {
236
+ isInitialNav = false;
237
+ return next();
238
+ }
239
+ const href = new URL(to.fullPath, location.origin).toString();
240
+ try {
241
+ await runMw(to.fullPath, async () => {
242
+ const payload = await fetchSPA(href);
243
+ updatePageData(payload.state ?? {});
244
+ syncSEO(payload.seo ?? {});
245
+ window.scrollTo(0, 0);
246
+ });
247
+ next();
248
+ } catch (err) {
249
+ console.error("[fnetro] Navigation error:", err);
250
+ location.href = to.fullPath;
251
+ }
252
+ });
253
+ app.use(router);
254
+ await router.isReady();
255
+ app.mount(container);
256
+ if (options.prefetchOnHover !== false) {
263
257
  document.addEventListener("mouseover", (e) => {
264
- const a = e.composedPath().find(
265
- (el) => el instanceof HTMLAnchorElement
266
- );
258
+ const a = e.composedPath().find((el) => el instanceof HTMLAnchorElement);
267
259
  if (a?.href) prefetch(a.href);
268
260
  });
269
261
  }
270
262
  }
271
263
  export {
272
- A,
264
+ DATA_KEY,
273
265
  PARAMS_KEY,
266
+ RouterLink,
267
+ RouterView2 as RouterView,
274
268
  SEO_KEY,
275
269
  SPA_HEADER,
276
270
  STATE_KEY,
@@ -280,15 +274,14 @@ export {
280
274
  defineGroup,
281
275
  defineLayout,
282
276
  definePage,
283
- fetchPayload,
277
+ isAsyncLoader,
284
278
  matchPath,
285
- navigate,
286
279
  prefetch,
287
280
  resolveRoutes,
288
281
  syncSEO,
282
+ toVueRouterPath,
289
283
  useClientMiddleware,
290
- useLocation,
291
- useNavigate,
292
- useParams,
293
- useSearchParams
284
+ usePageData,
285
+ useRoute,
286
+ useRouter
294
287
  };
package/dist/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { Component } from 'vue';
1
2
  import { Hono, MiddlewareHandler, Context } from 'hono';
2
- import { Component, JSX } from 'solid-js';
3
3
 
4
4
  type HonoMiddleware = MiddlewareHandler;
5
5
  type LoaderCtx = Context;
@@ -15,44 +15,33 @@ interface SEOMeta {
15
15
  ogDescription?: string;
16
16
  ogImage?: string;
17
17
  ogImageAlt?: string;
18
- ogImageWidth?: string;
19
- ogImageHeight?: string;
20
18
  ogUrl?: string;
21
19
  ogType?: string;
22
20
  ogSiteName?: string;
23
- ogLocale?: string;
24
21
  twitterCard?: 'summary' | 'summary_large_image' | 'app' | 'player';
25
22
  twitterSite?: string;
26
- twitterCreator?: string;
27
23
  twitterTitle?: string;
28
24
  twitterDescription?: string;
29
25
  twitterImage?: string;
30
- twitterImageAlt?: string;
26
+ /** Structured data injected as <script type="application/ld+json">. */
31
27
  jsonLd?: Record<string, unknown> | Record<string, unknown>[];
32
- extra?: Array<{
33
- name?: string;
34
- property?: string;
35
- httpEquiv?: string;
36
- content: string;
37
- }>;
38
28
  }
39
- type PageProps<TData extends object = {}> = TData & {
40
- url: string;
41
- params: Record<string, string>;
42
- };
43
- interface LayoutProps {
44
- children: JSX.Element;
45
- url: string;
46
- params: Record<string, string>;
47
- }
48
- interface PageDef<TData extends object = {}> {
29
+ type AsyncLoader = () => Promise<{
30
+ default: Component;
31
+ } | Component>;
32
+ interface PageDef<TData extends object = Record<string, never>> {
49
33
  readonly __type: 'page';
50
34
  path: string;
51
35
  middleware?: HonoMiddleware[];
52
36
  loader?: (c: LoaderCtx) => TData | Promise<TData>;
53
37
  seo?: SEOMeta | ((data: TData, params: Record<string, string>) => SEOMeta);
38
+ /** Override or disable the app-level layout for this route. */
54
39
  layout?: LayoutDef | false;
55
- Page: Component<PageProps<TData>>;
40
+ /**
41
+ * The Vue component to render for this route.
42
+ * Use () => import('./Page.vue') for automatic code splitting.
43
+ */
44
+ component: Component | AsyncLoader;
56
45
  }
57
46
  interface GroupDef {
58
47
  readonly __type: 'group';
@@ -63,7 +52,8 @@ interface GroupDef {
63
52
  }
64
53
  interface LayoutDef {
65
54
  readonly __type: 'layout';
66
- Component: Component<LayoutProps>;
55
+ /** Vue layout component — must contain <slot /> for page content. */
56
+ component: Component;
67
57
  }
68
58
  interface ApiRouteDef {
69
59
  readonly __type: 'api';
@@ -78,36 +68,63 @@ interface AppConfig {
78
68
  routes: Route[];
79
69
  notFound?: Component;
80
70
  htmlAttrs?: Record<string, string>;
71
+ /** Extra HTML injected into <head> (e.g. font preloads). */
81
72
  head?: string;
82
73
  }
83
- type ClientMiddleware = (url: string, next: () => Promise<void>) => Promise<void>;
84
- declare function definePage<TData extends object = {}>(def: Omit<PageDef<TData>, '__type'>): PageDef<TData>;
85
- declare function defineGroup(def: Omit<GroupDef, '__type'>): GroupDef;
86
- declare function defineLayout(Component: Component<LayoutProps>): LayoutDef;
87
- declare function defineApiRoute(path: string, register: ApiRouteDef['register']): ApiRouteDef;
88
74
  interface ResolvedRoute {
89
75
  fullPath: string;
90
76
  page: PageDef<any>;
91
77
  layout: LayoutDef | false | undefined;
92
78
  middleware: HonoMiddleware[];
93
79
  }
94
- declare function resolveRoutes(routes: Route[], options?: {
95
- prefix?: string;
96
- middleware?: HonoMiddleware[];
97
- layout?: LayoutDef | false;
98
- }): {
99
- pages: ResolvedRoute[];
100
- apis: ApiRouteDef[];
101
- };
102
80
  interface CompiledPath {
103
81
  re: RegExp;
104
82
  keys: string[];
105
83
  }
106
- declare function compilePath(path: string): CompiledPath;
107
- declare function matchPath(compiled: CompiledPath, pathname: string): Record<string, string> | null;
84
+ type ClientMiddleware = (url: string, next: () => Promise<void>) => Promise<void>;
85
+ /** Custom request header that identifies an SPA navigation (JSON payload). */
108
86
  declare const SPA_HEADER = "x-fnetro-spa";
87
+ /** window key for SSR-injected per-page loader data. */
109
88
  declare const STATE_KEY = "__FNETRO_STATE__";
89
+ /** window key for SSR-injected URL params. */
110
90
  declare const PARAMS_KEY = "__FNETRO_PARAMS__";
91
+ /** window key for SSR-injected SEO meta. */
111
92
  declare const SEO_KEY = "__FNETRO_SEO__";
93
+ /**
94
+ * Vue provide/inject key for the reactive page-data object.
95
+ * Symbol.for() ensures the same reference across module instances (SSR safe).
96
+ */
97
+ declare const DATA_KEY: unique symbol;
98
+
99
+ /**
100
+ * Returns true when `c` is an async factory function (i.e. `() => import(...)`)
101
+ * rather than a resolved Vue component object.
102
+ *
103
+ * Used by both server.ts (to resolve the import before SSR) and client.ts
104
+ * (to wrap with defineAsyncComponent for lazy hydration).
105
+ */
106
+ declare function isAsyncLoader(c: unknown): c is AsyncLoader;
107
+ declare function definePage<TData extends object = Record<string, never>>(def: Omit<PageDef<TData>, '__type'>): PageDef<TData>;
108
+ declare function defineGroup(def: Omit<GroupDef, '__type'>): GroupDef;
109
+ /** Wrap a Vue layout component (must render <slot />) as a FNetro layout. */
110
+ declare function defineLayout(component: Component): LayoutDef;
111
+ declare function defineApiRoute(path: string, register: ApiRouteDef['register']): ApiRouteDef;
112
+ declare function compilePath(path: string): CompiledPath;
113
+ declare function matchPath(cp: CompiledPath, pathname: string): Record<string, string> | null;
114
+ /**
115
+ * Convert FNetro `[param]` syntax to Vue Router `:param` syntax.
116
+ *
117
+ * `/posts/[slug]` → `/posts/:slug`
118
+ * `/files/[...path]` → `/files/:path(.*)*`
119
+ */
120
+ declare function toVueRouterPath(fnetroPath: string): string;
121
+ declare function resolveRoutes(routes: Route[], options?: {
122
+ prefix?: string;
123
+ middleware?: HonoMiddleware[];
124
+ layout?: LayoutDef | false;
125
+ }): {
126
+ pages: ResolvedRoute[];
127
+ apis: ApiRouteDef[];
128
+ };
112
129
 
113
- export { type ApiRouteDef, type AppConfig, type ClientMiddleware, type CompiledPath, type GroupDef, type HonoMiddleware, type LayoutDef, type LayoutProps, type LoaderCtx, PARAMS_KEY, type PageDef, type PageProps, type ResolvedRoute, type Route, type SEOMeta, SEO_KEY, SPA_HEADER, STATE_KEY, compilePath, defineApiRoute, defineGroup, defineLayout, definePage, matchPath, resolveRoutes };
130
+ export { type ApiRouteDef, type AppConfig, type AsyncLoader, type ClientMiddleware, type CompiledPath, DATA_KEY, type GroupDef, type HonoMiddleware, type LayoutDef, type LoaderCtx, PARAMS_KEY, type PageDef, type ResolvedRoute, type Route, type SEOMeta, SEO_KEY, SPA_HEADER, STATE_KEY, compilePath, defineApiRoute, defineGroup, defineLayout, definePage, isAsyncLoader, matchPath, resolveRoutes, toVueRouterPath };