hono-preact 0.1.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.
Files changed (130) hide show
  1. package/README.md +47 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/internal.d.ts +1 -0
  6. package/dist/internal.d.ts.map +1 -0
  7. package/dist/internal.js +1 -0
  8. package/dist/iso/action.d.ts +78 -0
  9. package/dist/iso/action.js +189 -0
  10. package/dist/iso/cache.d.ts +17 -0
  11. package/dist/iso/cache.js +122 -0
  12. package/dist/iso/client-script.d.ts +2 -0
  13. package/dist/iso/client-script.js +13 -0
  14. package/dist/iso/define-loader.d.ts +47 -0
  15. package/dist/iso/define-loader.js +118 -0
  16. package/dist/iso/define-page.d.ts +10 -0
  17. package/dist/iso/define-page.js +7 -0
  18. package/dist/iso/define-routes.d.ts +34 -0
  19. package/dist/iso/define-routes.js +251 -0
  20. package/dist/iso/form.d.ts +7 -0
  21. package/dist/iso/form.js +40 -0
  22. package/dist/iso/guard.d.ts +33 -0
  23. package/dist/iso/guard.js +32 -0
  24. package/dist/iso/head.d.ts +6 -0
  25. package/dist/iso/head.js +4 -0
  26. package/dist/iso/index.d.ts +30 -0
  27. package/dist/iso/index.js +29 -0
  28. package/dist/iso/internal/cache-key.d.ts +2 -0
  29. package/dist/iso/internal/cache-key.js +8 -0
  30. package/dist/iso/internal/contexts.d.ts +12 -0
  31. package/dist/iso/internal/contexts.js +7 -0
  32. package/dist/iso/internal/envelope.d.ts +8 -0
  33. package/dist/iso/internal/envelope.js +21 -0
  34. package/dist/iso/internal/guard-noop.d.ts +7 -0
  35. package/dist/iso/internal/guard-noop.js +6 -0
  36. package/dist/iso/internal/guards.d.ts +14 -0
  37. package/dist/iso/internal/guards.js +54 -0
  38. package/dist/iso/internal/loader-fetch.d.ts +20 -0
  39. package/dist/iso/internal/loader-fetch.js +123 -0
  40. package/dist/iso/internal/loader-runner.d.ts +15 -0
  41. package/dist/iso/internal/loader-runner.js +59 -0
  42. package/dist/iso/internal/loader-stub.d.ts +8 -0
  43. package/dist/iso/internal/loader-stub.js +19 -0
  44. package/dist/iso/internal/loader.d.ts +13 -0
  45. package/dist/iso/internal/loader.js +31 -0
  46. package/dist/iso/internal/optimistic-overlay.d.ts +10 -0
  47. package/dist/iso/internal/optimistic-overlay.js +11 -0
  48. package/dist/iso/internal/preload.d.ts +15 -0
  49. package/dist/iso/internal/preload.js +36 -0
  50. package/dist/iso/internal/route-boundary.d.ts +25 -0
  51. package/dist/iso/internal/route-boundary.js +24 -0
  52. package/dist/iso/internal/route-change.d.ts +4 -0
  53. package/dist/iso/internal/route-change.js +18 -0
  54. package/dist/iso/internal/route-locations.d.ts +11 -0
  55. package/dist/iso/internal/route-locations.js +15 -0
  56. package/dist/iso/internal/sse-decoder.d.ts +5 -0
  57. package/dist/iso/internal/sse-decoder.js +43 -0
  58. package/dist/iso/internal/stream-registry.d.ts +60 -0
  59. package/dist/iso/internal/stream-registry.js +98 -0
  60. package/dist/iso/internal/streaming-ssr.d.ts +17 -0
  61. package/dist/iso/internal/streaming-ssr.js +32 -0
  62. package/dist/iso/internal/use-loader-runner.d.ts +12 -0
  63. package/dist/iso/internal/use-loader-runner.js +185 -0
  64. package/dist/iso/internal/wrap-promise.d.ts +4 -0
  65. package/dist/iso/internal/wrap-promise.js +24 -0
  66. package/dist/iso/internal.d.ts +19 -0
  67. package/dist/iso/internal.js +49 -0
  68. package/dist/iso/is-browser.d.ts +4 -0
  69. package/dist/iso/is-browser.js +6 -0
  70. package/dist/iso/optimistic-action.d.ts +19 -0
  71. package/dist/iso/optimistic-action.js +25 -0
  72. package/dist/iso/optimistic.d.ts +5 -0
  73. package/dist/iso/optimistic.js +31 -0
  74. package/dist/iso/page.d.ts +16 -0
  75. package/dist/iso/page.js +10 -0
  76. package/dist/iso/prefetch.d.ts +22 -0
  77. package/dist/iso/prefetch.js +78 -0
  78. package/dist/iso/reload-context.d.ts +6 -0
  79. package/dist/iso/reload-context.js +9 -0
  80. package/dist/iso/route-change.d.ts +2 -0
  81. package/dist/iso/route-change.js +10 -0
  82. package/dist/iso/view-transitions.d.ts +1 -0
  83. package/dist/iso/view-transitions.js +6 -0
  84. package/dist/server/actions-handler.d.ts +33 -0
  85. package/dist/server/actions-handler.js +159 -0
  86. package/dist/server/context.d.ts +6 -0
  87. package/dist/server/context.js +6 -0
  88. package/dist/server/index.d.ts +5 -0
  89. package/dist/server/index.js +5 -0
  90. package/dist/server/loaders-handler.d.ts +30 -0
  91. package/dist/server/loaders-handler.js +117 -0
  92. package/dist/server/middleware/location.d.ts +1 -0
  93. package/dist/server/middleware/location.js +10 -0
  94. package/dist/server/render.d.ts +5 -0
  95. package/dist/server/render.js +203 -0
  96. package/dist/server/route-server-modules.d.ts +12 -0
  97. package/dist/server/route-server-modules.js +13 -0
  98. package/dist/server/sse.d.ts +22 -0
  99. package/dist/server/sse.js +83 -0
  100. package/dist/server.d.ts +1 -0
  101. package/dist/server.d.ts.map +1 -0
  102. package/dist/server.js +1 -0
  103. package/dist/vite/client-entry.d.ts +10 -0
  104. package/dist/vite/client-entry.js +47 -0
  105. package/dist/vite/client-shim.d.ts +12 -0
  106. package/dist/vite/client-shim.js +62 -0
  107. package/dist/vite/guard-strip.d.ts +2 -0
  108. package/dist/vite/guard-strip.js +96 -0
  109. package/dist/vite/hono-preact.d.ts +12 -0
  110. package/dist/vite/hono-preact.js +111 -0
  111. package/dist/vite/index.d.ts +7 -0
  112. package/dist/vite/index.js +7 -0
  113. package/dist/vite/module-key-plugin.d.ts +12 -0
  114. package/dist/vite/module-key-plugin.js +114 -0
  115. package/dist/vite/module-key.d.ts +11 -0
  116. package/dist/vite/module-key.js +20 -0
  117. package/dist/vite/parser-options.d.ts +16 -0
  118. package/dist/vite/parser-options.js +22 -0
  119. package/dist/vite/server-entry.d.ts +26 -0
  120. package/dist/vite/server-entry.js +201 -0
  121. package/dist/vite/server-loader-validation.d.ts +2 -0
  122. package/dist/vite/server-loader-validation.js +73 -0
  123. package/dist/vite/server-loaders-parser.d.ts +22 -0
  124. package/dist/vite/server-loaders-parser.js +64 -0
  125. package/dist/vite/server-only.d.ts +3 -0
  126. package/dist/vite/server-only.js +244 -0
  127. package/dist/vite.d.ts +1 -0
  128. package/dist/vite.d.ts.map +1 -0
  129. package/dist/vite.js +1 -0
  130. package/package.json +78 -0
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "preact/jsx-runtime";
2
+ import { Page } from './page.js';
3
+ export function definePage(Component, bindings) {
4
+ const PageRoute = (location) => (_jsx(Page, { Wrapper: bindings?.Wrapper, errorFallback: bindings?.errorFallback, guards: bindings?.guards, location: location, children: _jsx(Component, {}) }));
5
+ PageRoute.displayName = `definePage(${Component.displayName ?? Component.name ?? 'Anonymous'})`;
6
+ return PageRoute;
7
+ }
@@ -0,0 +1,34 @@
1
+ import type { ComponentChildren, ComponentType } from 'preact';
2
+ import type { RouteHook } from 'preact-iso';
3
+ export type LayoutProps = {
4
+ children: ComponentChildren;
5
+ };
6
+ export type ViewProps = RouteHook;
7
+ type LazyImport<T> = () => Promise<{
8
+ default: T;
9
+ }>;
10
+ type LazyServerImport = () => Promise<unknown>;
11
+ export type RouteDef = {
12
+ path: string;
13
+ view?: LazyImport<ComponentType<ViewProps>>;
14
+ layout?: LazyImport<ComponentType<LayoutProps>>;
15
+ server?: LazyServerImport;
16
+ children?: RouteDef[];
17
+ };
18
+ export type FlatRoute = {
19
+ path: string;
20
+ component: ComponentType<ViewProps>;
21
+ key: string;
22
+ };
23
+ export type RoutesManifest = {
24
+ tree: ReadonlyArray<RouteDef>;
25
+ flat: ReadonlyArray<FlatRoute>;
26
+ serverImports: ReadonlyArray<LazyServerImport>;
27
+ };
28
+ export declare function defineRoutes(tree: RouteDef[]): RoutesManifest;
29
+ export type RoutesProps = {
30
+ routes: RoutesManifest;
31
+ onRouteChange?: (url: string) => void;
32
+ };
33
+ export declare const Routes: ComponentType<RoutesProps>;
34
+ export {};
@@ -0,0 +1,251 @@
1
+ import { h } from 'preact';
2
+ import { lazy, Route, Router, useLocation } from 'preact-iso';
3
+ import { RouteLocationsProvider } from './internal/route-locations.js';
4
+ function wrapWithRouteLocations(serverMod, location, node) {
5
+ const moduleKey = serverMod
6
+ ?.__moduleKey;
7
+ return moduleKey
8
+ ? h(RouteLocationsProvider, { moduleKey, location }, node)
9
+ : node;
10
+ }
11
+ // preact-iso's `Route<P>` and `Router` carry strict generics that reject our
12
+ // heterogeneous route components (leaves take `ViewProps`, layout-group
13
+ // wrappers take `()`). We erase the generic at every `h(Route, ...)` /
14
+ // `h(Router, ...)` call site through this single helper so the rationale lives
15
+ // in one place.
16
+ const asRouteComponent = (c) => c;
17
+ // preact-iso's `lazy()` returns a wrapper whose component-type generic is
18
+ // loose. We know the underlying default export conforms to `ViewProps` (or to
19
+ // our layout-group wrapper which renders an inner Router); assert that here so
20
+ // `FlatRoute.component` stays strongly typed.
21
+ const asViewComponent = (c) => c;
22
+ function validate(routes, parentPath = '', context = 'top') {
23
+ for (const r of routes) {
24
+ const here = parentPath + (r.path.startsWith('/') ? r.path : '/' + r.path);
25
+ const hasView = !!r.view;
26
+ const hasLayout = !!r.layout;
27
+ const hasChildren = !!(r.children && r.children.length > 0);
28
+ if (hasView && hasLayout) {
29
+ throw new Error(`Route ${here}: cannot declare both \`view\` and \`layout\`.`);
30
+ }
31
+ if (hasView && hasChildren) {
32
+ throw new Error(`Route ${here}: \`view\` route cannot have \`children\`.`);
33
+ }
34
+ if (hasLayout && !hasChildren) {
35
+ throw new Error(`Route ${here}: \`layout\` requires \`children\`.`);
36
+ }
37
+ if (!hasView && !hasLayout && !hasChildren) {
38
+ throw new Error(`Route ${here}: must declare \`view\`, \`layout\`+\`children\`, or \`children\`.`);
39
+ }
40
+ if (parentPath !== '' && r.path.startsWith('/')) {
41
+ throw new Error(`Route ${here}: child path must not start with \`/\`.`);
42
+ }
43
+ if (context === 'layout-grouping' && (hasLayout || hasChildren)) {
44
+ throw new Error(`Route ${here}: a path-grouping inside a layout group may only contain view leaves at v0.1. ` +
45
+ `Move this route up a level (direct child of the layout group) or restructure as its own layout group.`);
46
+ }
47
+ if (hasChildren) {
48
+ const childContext = hasLayout
49
+ ? 'layout'
50
+ : context === 'layout'
51
+ ? 'layout-grouping'
52
+ : 'top';
53
+ validate(r.children, here === '/' ? '' : here, childContext);
54
+ }
55
+ }
56
+ }
57
+ function collectServerImports(routes) {
58
+ const out = [];
59
+ const walk = (rs) => {
60
+ for (const r of rs) {
61
+ if (r.server)
62
+ out.push(r.server);
63
+ if (r.children)
64
+ walk(r.children);
65
+ }
66
+ };
67
+ walk(routes);
68
+ return out;
69
+ }
70
+ /**
71
+ * Memoize `lazy(view)` per view-thunk identity. When the same `view` thunk is
72
+ * referenced by multiple route registrations (e.g. `/docs` and `/docs/*`),
73
+ * they should share one component reference so preact-iso's Router does not
74
+ * treat the navigation as a route change and remount the layout.
75
+ *
76
+ * When `server` is provided, the loaded view is wrapped in a
77
+ * RouteLocationsProvider so that loaders in the server module can read the
78
+ * route's location from context.
79
+ */
80
+ function getOrCreateLazyView(view, server, cache) {
81
+ let component = cache.get(view);
82
+ if (!component) {
83
+ if (!server) {
84
+ component = asViewComponent(lazy(view));
85
+ }
86
+ else {
87
+ component = asViewComponent(lazy(async () => {
88
+ const [{ default: View }, serverMod] = await Promise.all([
89
+ view(),
90
+ server(),
91
+ ]);
92
+ // `location` from the inner Router has a relative path (e.g. `/123`
93
+ // when the route is nested inside a layout at `/movies/*`). Use
94
+ // `useLocation()` to get the full window path and searchParams so the
95
+ // stored location reflects the actual URL the loader runs against.
96
+ const Wrapped = (location) => {
97
+ const { path, searchParams } = useLocation();
98
+ const fullLocation = { ...location, path, searchParams };
99
+ return wrapWithRouteLocations(serverMod, fullLocation, h(View, location));
100
+ };
101
+ return { default: Wrapped };
102
+ }));
103
+ }
104
+ cache.set(view, component);
105
+ }
106
+ return component;
107
+ }
108
+ /**
109
+ * Build the component for a layout group: <Layout><Router>{childRoutes}</Router></Layout>.
110
+ * Returned via preact-iso's lazy so the layout module loads only when matched.
111
+ * Children are themselves wrapped in preact-iso's lazy via their own `view`/`layout`,
112
+ * so each child remains a separate code-split chunk.
113
+ *
114
+ * When the layout declares a `server` module, a RouteLocationsProvider is
115
+ * installed around the layout with the layout's own matched location
116
+ * (i.e. the path up to and including the layout's own segments, not the
117
+ * inner wildcard/child segments).
118
+ */
119
+ function makeLayoutGroupComponent(layoutImport, server, layoutPathPattern, children, viewCache) {
120
+ return asViewComponent(lazy(async () => {
121
+ const [{ default: Layout }, serverMod] = await Promise.all([
122
+ layoutImport(),
123
+ server ? server() : Promise.resolve(undefined),
124
+ ]);
125
+ const inner = buildInnerRoutes(children, viewCache);
126
+ const Wrapper = (location) => {
127
+ const layoutLocation = deriveLayoutLocation(location, layoutPathPattern);
128
+ const layoutNode = h(Layout, null, h(Router, null, ...inner));
129
+ return wrapWithRouteLocations(serverMod, layoutLocation, layoutNode);
130
+ };
131
+ return { default: Wrapper };
132
+ }));
133
+ }
134
+ /**
135
+ * Derive the layout's own matched location from the active (inner) RouteHook.
136
+ *
137
+ * When a layout matches `/movies/*`, the wildcard portion (`rest` or `0`) is
138
+ * the child segment. The layout's location should be the path up to and
139
+ * including the layout's own segments, with the wildcard stripped.
140
+ */
141
+ function deriveLayoutLocation(active, layoutPathPattern) {
142
+ const params = active.pathParams ?? {};
143
+ const path = layoutPathPattern
144
+ .split('/')
145
+ .map((seg) => seg.startsWith(':')
146
+ ? String(params[seg.slice(1)] ?? '')
147
+ : seg.startsWith('*')
148
+ ? ''
149
+ : seg)
150
+ .filter(Boolean)
151
+ .join('/');
152
+ const finalPath = '/' + path;
153
+ const filteredParams = {};
154
+ for (const k of Object.keys(params)) {
155
+ if (k !== 'rest' && k !== '0')
156
+ filteredParams[k] = params[k];
157
+ }
158
+ return {
159
+ ...active,
160
+ path: finalPath === '/' ? '/' : finalPath,
161
+ pathParams: filteredParams,
162
+ };
163
+ }
164
+ /**
165
+ * Build the inner <Route> children for a layout group's <Router>. Each child
166
+ * is either a leaf (registered under its relative path) or another layout
167
+ * group (registered under bare + wildcard paths within the inner router).
168
+ */
169
+ function buildInnerRoutes(children, viewCache) {
170
+ const nodes = [];
171
+ for (const child of children) {
172
+ if (child.view) {
173
+ nodes.push(h(Route, {
174
+ path: child.path,
175
+ component: asRouteComponent(getOrCreateLazyView(child.view, child.server, viewCache)),
176
+ }));
177
+ }
178
+ else if (child.layout && child.children) {
179
+ const Group = makeLayoutGroupComponent(child.layout, child.server, child.path, child.children, viewCache);
180
+ // Same shared-component trick at this nesting level.
181
+ nodes.push(h(Route, { path: child.path, component: asRouteComponent(Group) }));
182
+ nodes.push(h(Route, {
183
+ path: child.path + '/*',
184
+ component: asRouteComponent(Group),
185
+ }));
186
+ }
187
+ else if (child.children) {
188
+ // Path-grouping inside a layout. `validate()` already enforces that all
189
+ // descendants here are view leaves, so we only inline grandchild views.
190
+ for (const grand of child.children) {
191
+ const joined = child.path === '' ? grand.path : child.path + '/' + grand.path;
192
+ if (grand.view) {
193
+ nodes.push(h(Route, {
194
+ path: joined,
195
+ component: asRouteComponent(getOrCreateLazyView(grand.view, grand.server, viewCache)),
196
+ }));
197
+ }
198
+ }
199
+ }
200
+ }
201
+ return nodes;
202
+ }
203
+ function flattenTree(routes, viewCache, keyCache, parentPath = '') {
204
+ const keyFor = (c) => {
205
+ let k = keyCache.get(c);
206
+ if (!k) {
207
+ k = `r${keyCache.size}`;
208
+ keyCache.set(c, k);
209
+ }
210
+ return k;
211
+ };
212
+ const out = [];
213
+ for (const r of routes) {
214
+ const here = parentPath === ''
215
+ ? r.path
216
+ : parentPath + (r.path === '' ? '' : '/' + r.path);
217
+ if (r.view) {
218
+ const component = getOrCreateLazyView(r.view, r.server, viewCache);
219
+ out.push({ path: here, component, key: keyFor(component) });
220
+ }
221
+ else if (r.layout && r.children) {
222
+ const Group = makeLayoutGroupComponent(r.layout, r.server, here, r.children, viewCache);
223
+ const key = keyFor(Group);
224
+ out.push({ path: here, component: Group, key });
225
+ out.push({ path: here + '/*', component: Group, key });
226
+ }
227
+ else if (r.children) {
228
+ // Path-grouping at top level: recurse with the prefix.
229
+ const childParent = here === '/' ? '' : here;
230
+ out.push(...flattenTree(r.children, viewCache, keyCache, childParent));
231
+ }
232
+ }
233
+ return out;
234
+ }
235
+ export function defineRoutes(tree) {
236
+ validate(tree);
237
+ const viewCache = new Map();
238
+ const keyCache = new Map();
239
+ return {
240
+ tree,
241
+ flat: flattenTree(tree, viewCache, keyCache),
242
+ serverImports: collectServerImports(tree),
243
+ };
244
+ }
245
+ export const Routes = ({ routes, onRouteChange, }) => {
246
+ return h(asRouteComponent(Router), onRouteChange ? { onRouteChange } : null, ...routes.flat.map((r) => h(Route, {
247
+ key: r.key,
248
+ path: r.path,
249
+ component: asRouteComponent(r.component),
250
+ })));
251
+ };
@@ -0,0 +1,7 @@
1
+ import type { JSX, ComponentChildren } from 'preact';
2
+ export type FormProps<TPayload extends Record<string, unknown>> = Omit<JSX.HTMLAttributes<HTMLFormElement>, 'onSubmit'> & {
3
+ mutate: (payload: TPayload) => Promise<unknown> | unknown;
4
+ pending?: boolean;
5
+ children?: ComponentChildren;
6
+ };
7
+ export declare function Form<TPayload extends Record<string, unknown>>({ mutate, pending, children, ...rest }: FormProps<TPayload>): JSX.Element;
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx } from "preact/jsx-runtime";
2
+ /**
3
+ * Collect a FormData into a plain payload object.
4
+ *
5
+ * Repeated field names (checkboxes sharing a name, multi-select, multiple
6
+ * `<input type="file" multiple>` entries) collect into an array. The old
7
+ * `Object.fromEntries(fd)` produced the LAST value only, which was silent
8
+ * data loss: a four-checkbox group would submit one value with no warning.
9
+ *
10
+ * Single-value fields stay scalar. Files survive as `File` instances.
11
+ *
12
+ * Consumers should type their `defineAction<TPayload, ...>` to match:
13
+ * `tags: string[]`, `photos: File[]`, etc. for fields that may have multiple
14
+ * values; scalar types for fields that won't.
15
+ */
16
+ function collectFormData(fd) {
17
+ const payload = {};
18
+ for (const [key, value] of fd.entries()) {
19
+ if (key in payload) {
20
+ const existing = payload[key];
21
+ payload[key] = Array.isArray(existing)
22
+ ? [...existing, value]
23
+ : [existing, value];
24
+ }
25
+ else {
26
+ payload[key] = value;
27
+ }
28
+ }
29
+ return payload;
30
+ }
31
+ export function Form({ mutate, pending, children, ...rest }) {
32
+ const handleSubmit = (e) => {
33
+ e.preventDefault();
34
+ const formEl = e.currentTarget;
35
+ const formData = new FormData(formEl);
36
+ const payload = collectFormData(formData);
37
+ void mutate(payload);
38
+ };
39
+ return (_jsx("form", { ...rest, onSubmit: handleSubmit, children: _jsx("fieldset", { disabled: pending, class: "hp-form-fieldset", children: children }) }));
40
+ }
@@ -0,0 +1,33 @@
1
+ import { type FunctionComponent } from 'preact';
2
+ import type { Context } from 'hono';
3
+ import { type RouteHook } from 'preact-iso';
4
+ export type GuardRunsOn = 'server' | 'client';
5
+ export type GuardResult = {
6
+ redirect: string;
7
+ } | {
8
+ render: FunctionComponent;
9
+ } | void;
10
+ export type ServerGuardContext = {
11
+ c: Context;
12
+ location: RouteHook;
13
+ };
14
+ export type ClientGuardContext = {
15
+ location: RouteHook;
16
+ };
17
+ export type ServerGuardFn = {
18
+ readonly runs: 'server';
19
+ readonly fn: (ctx: ServerGuardContext, next: () => Promise<GuardResult>) => GuardResult | Promise<GuardResult>;
20
+ };
21
+ export type ClientGuardFn = {
22
+ readonly runs: 'client';
23
+ readonly fn: (ctx: ClientGuardContext, next: () => Promise<GuardResult>) => GuardResult | Promise<GuardResult>;
24
+ };
25
+ export type GuardFn = ServerGuardFn | ClientGuardFn;
26
+ export declare const defineServerGuard: (fn: ServerGuardFn["fn"]) => ServerGuardFn;
27
+ export declare const defineClientGuard: (fn: ClientGuardFn["fn"]) => ClientGuardFn;
28
+ export declare const runServerGuards: (guards: ServerGuardFn[], ctx: ServerGuardContext) => Promise<GuardResult>;
29
+ export declare const runClientGuards: (guards: ClientGuardFn[], ctx: ClientGuardContext) => Promise<GuardResult>;
30
+ export declare class GuardRedirect extends Error {
31
+ readonly location: string;
32
+ constructor(location: string);
33
+ }
@@ -0,0 +1,32 @@
1
+ export const defineServerGuard = (fn) => ({
2
+ runs: 'server',
3
+ fn,
4
+ });
5
+ export const defineClientGuard = (fn) => ({
6
+ runs: 'client',
7
+ fn,
8
+ });
9
+ export const runServerGuards = async (guards, ctx) => {
10
+ const run = async (index) => {
11
+ if (index >= guards.length)
12
+ return;
13
+ return guards[index].fn(ctx, () => run(index + 1));
14
+ };
15
+ return run(0);
16
+ };
17
+ export const runClientGuards = async (guards, ctx) => {
18
+ const run = async (index) => {
19
+ if (index >= guards.length)
20
+ return;
21
+ return guards[index].fn(ctx, () => run(index + 1));
22
+ };
23
+ return run(0);
24
+ };
25
+ export class GuardRedirect extends Error {
26
+ location;
27
+ constructor(location) {
28
+ super(`Guard redirect to ${location}`);
29
+ this.location = location;
30
+ this.name = 'GuardRedirect';
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ import type { ComponentChildren, VNode } from 'preact';
2
+ export interface HeadProps {
3
+ defaultTitle?: string;
4
+ children?: ComponentChildren;
5
+ }
6
+ export declare function Head({ defaultTitle, children }: HeadProps): VNode;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ export function Head({ defaultTitle, children }) {
3
+ return (_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width,initial-scale=1.0" }), _jsx("title", { children: defaultTitle ?? '' }), children] }));
4
+ }
@@ -0,0 +1,30 @@
1
+ export { Page } from './page.js';
2
+ export type { PageProps, WrapperProps } from './page.js';
3
+ export { definePage } from './define-page.js';
4
+ export type { PageBindings } from './define-page.js';
5
+ export { Route, Router, lazy, useLocation, useRoute } from 'preact-iso';
6
+ export { defineRoutes, Routes } from './define-routes.js';
7
+ export type { RouteDef, RoutesManifest, FlatRoute, LayoutProps, ViewProps, } from './define-routes.js';
8
+ export { defineLoader } from './define-loader.js';
9
+ export type { LoaderRef, LoaderCtx, Loader as LoaderFn, } from './define-loader.js';
10
+ export { defineAction, useAction } from './action.js';
11
+ export type { ActionStub, UseActionOptions, UseActionResult, MutateResult, ActionGuardContext, ActionGuardFn, } from './action.js';
12
+ export { ActionGuardError, defineActionGuard } from './action.js';
13
+ export type { ContentfulStatusCode } from 'hono/utils/http-status';
14
+ export { useReload } from './reload-context.js';
15
+ export { useOptimistic } from './optimistic.js';
16
+ export type { OptimisticHandle } from './optimistic.js';
17
+ export { useOptimisticAction } from './optimistic-action.js';
18
+ export type { UseOptimisticActionOptions, UseOptimisticActionResult, } from './optimistic-action.js';
19
+ export { Form } from './form.js';
20
+ export { createCache } from './cache.js';
21
+ export type { LoaderCache } from './cache.js';
22
+ export { defineServerGuard, defineClientGuard, GuardRedirect, runServerGuards, runClientGuards, } from './guard.js';
23
+ export type { GuardFn, ServerGuardFn, ClientGuardFn, GuardResult, ServerGuardContext, ClientGuardContext, GuardRunsOn, } from './guard.js';
24
+ export { prefetch } from './prefetch.js';
25
+ export { isBrowser, env } from './is-browser.js';
26
+ export { useRouteChange } from './route-change.js';
27
+ export type { RouteChangeHandler } from './route-change.js';
28
+ export { Head } from './head.js';
29
+ export type { HeadProps } from './head.js';
30
+ export { ClientScript } from './client-script.js';
@@ -0,0 +1,29 @@
1
+ // Page declaration and the <Page> escape hatch.
2
+ export { Page } from './page.js';
3
+ export { definePage } from './define-page.js';
4
+ // Routing primitives — trivial re-exports of preact-iso. Listed here so
5
+ // consumers have a single import surface for everything they need.
6
+ export { Route, Router, lazy, useLocation, useRoute } from 'preact-iso';
7
+ // Declarative route tree.
8
+ export { defineRoutes, Routes } from './define-routes.js';
9
+ // Server bindings.
10
+ export { defineLoader } from './define-loader.js';
11
+ export { defineAction, useAction } from './action.js';
12
+ export { ActionGuardError, defineActionGuard } from './action.js';
13
+ // Hooks.
14
+ export { useReload } from './reload-context.js';
15
+ export { useOptimistic } from './optimistic.js';
16
+ export { useOptimisticAction } from './optimistic-action.js';
17
+ // Forms.
18
+ export { Form } from './form.js';
19
+ // Cache + invalidation.
20
+ export { createCache } from './cache.js';
21
+ // Guards.
22
+ export { defineServerGuard, defineClientGuard, GuardRedirect, runServerGuards, runClientGuards, } from './guard.js';
23
+ // Utilities.
24
+ export { prefetch } from './prefetch.js';
25
+ export { isBrowser, env } from './is-browser.js';
26
+ // Client entry primitives (item 4 of v0.1).
27
+ export { useRouteChange } from './route-change.js';
28
+ export { Head } from './head.js';
29
+ export { ClientScript } from './client-script.js';
@@ -0,0 +1,2 @@
1
+ import type { RouteHook } from 'preact-iso';
2
+ export declare function serializeLocationForCache(loc: RouteHook, params: string[] | '*'): string;
@@ -0,0 +1,8 @@
1
+ export function serializeLocationForCache(loc, params) {
2
+ const sp = (loc.searchParams ?? {});
3
+ const keys = params === '*'
4
+ ? Object.keys(sp).sort()
5
+ : params.filter((k) => k in sp).sort();
6
+ const sortedSearch = keys.map((k) => `${k}=${sp[k]}`).join('&');
7
+ return `${loc.path}?${sortedSearch}`;
8
+ }
@@ -0,0 +1,12 @@
1
+ import type { Context } from 'hono';
2
+ import type { GuardResult } from '../guard.js';
3
+ export declare const HonoRequestContext: import("preact").Context<{
4
+ context?: Context;
5
+ }>;
6
+ export declare const LoaderIdContext: import("preact").Context<string | null>;
7
+ export declare const LoaderDataContext: import("preact").Context<{
8
+ data: unknown;
9
+ } | null>;
10
+ export declare const GuardResultContext: import("preact").Context<GuardResult | null>;
11
+ export declare const ActiveLoaderIdContext: import("preact").Context<symbol | null>;
12
+ export declare const LoaderErrorContext: import("preact").Context<Error | null>;
@@ -0,0 +1,7 @@
1
+ import { createContext } from 'preact';
2
+ export const HonoRequestContext = createContext({});
3
+ export const LoaderIdContext = createContext(null);
4
+ export const LoaderDataContext = createContext(null);
5
+ export const GuardResultContext = createContext(null);
6
+ export const ActiveLoaderIdContext = createContext(null);
7
+ export const LoaderErrorContext = createContext(null);
@@ -0,0 +1,8 @@
1
+ import type { ComponentChildren, ComponentType, FunctionComponent, JSX } from 'preact';
2
+ import type { WrapperProps } from '../page.js';
3
+ type EnvelopeProps = {
4
+ as?: ComponentType<WrapperProps> | keyof JSX.IntrinsicElements;
5
+ children: ComponentChildren;
6
+ };
7
+ export declare const Envelope: FunctionComponent<EnvelopeProps>;
8
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "preact/jsx-runtime";
2
+ import { useContext } from 'preact/hooks';
3
+ import { isBrowser } from '../is-browser.js';
4
+ import { LoaderDataContext, LoaderIdContext } from './contexts.js';
5
+ export const Envelope = ({ as = 'section', children, }) => {
6
+ const id = useContext(LoaderIdContext);
7
+ const ctx = useContext(LoaderDataContext);
8
+ if (!id || !ctx)
9
+ throw new Error('<Envelope> must be inside a <Loader>');
10
+ // Coerce undefined → null so JSON.stringify(undefined) (which returns
11
+ // undefined and serializes as the literal string "undefined") never
12
+ // reaches the wire. Loaders that return undefined should hydrate to null.
13
+ const dataLoader = isBrowser() ? 'null' : JSON.stringify(ctx.data ?? null);
14
+ if (typeof as === 'string') {
15
+ const Tag = as;
16
+ const props = { id, 'data-loader': dataLoader };
17
+ return (_jsx(Tag, { ...props, children: children }));
18
+ }
19
+ const Wrapper = as;
20
+ return (_jsx(Wrapper, { id: id, "data-loader": dataLoader, children: children }));
21
+ };
@@ -0,0 +1,7 @@
1
+ import type { GuardResult } from '../guard.js';
2
+ /**
3
+ * Passthrough guard body used by the Vite `guardStripPlugin` to replace
4
+ * opposite-env guard bodies at build time. Importing this in user code is
5
+ * supported but unnecessary; the plugin handles the substitution.
6
+ */
7
+ export declare const __$guardNoop_hpiso: (_ctx: unknown, next: () => Promise<GuardResult>) => Promise<GuardResult>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Passthrough guard body used by the Vite `guardStripPlugin` to replace
3
+ * opposite-env guard bodies at build time. Importing this in user code is
4
+ * supported but unnecessary; the plugin handles the substitution.
5
+ */
6
+ export const __$guardNoop_hpiso = (_ctx, next) => next();
@@ -0,0 +1,14 @@
1
+ import type { ComponentChildren, FunctionComponent, JSX } from 'preact';
2
+ import { type RouteHook } from 'preact-iso';
3
+ import { type GuardFn, type GuardResult } from '../guard.js';
4
+ export declare function useGuardResult(): GuardResult | null;
5
+ export declare const GuardGate: FunctionComponent<{
6
+ result: GuardResult | null;
7
+ children: ComponentChildren;
8
+ }>;
9
+ export declare const Guards: FunctionComponent<{
10
+ guards?: GuardFn[];
11
+ location: RouteHook;
12
+ fallback?: JSX.Element;
13
+ children: ComponentChildren;
14
+ }>;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "preact/jsx-runtime";
2
+ import { useLocation } from 'preact-iso';
3
+ import { Suspense } from 'preact/compat';
4
+ import { useContext, useRef } from 'preact/hooks';
5
+ import { GuardRedirect, runServerGuards, runClientGuards, } from '../guard.js';
6
+ import { isBrowser } from '../is-browser.js';
7
+ import wrapPromise from './wrap-promise.js';
8
+ import { GuardResultContext, HonoRequestContext } from './contexts.js';
9
+ export function useGuardResult() {
10
+ return useContext(GuardResultContext);
11
+ }
12
+ export const GuardGate = ({ result, children }) => {
13
+ const { route } = useLocation();
14
+ if (result && 'redirect' in result) {
15
+ if (isBrowser()) {
16
+ route(result.redirect);
17
+ return null;
18
+ }
19
+ throw new GuardRedirect(result.redirect);
20
+ }
21
+ if (result && 'render' in result) {
22
+ const Fallback = result.render;
23
+ return _jsx(Fallback, {});
24
+ }
25
+ return _jsx(_Fragment, { children: children });
26
+ };
27
+ function GuardConsumer({ guardRef, children, }) {
28
+ const result = (guardRef.current.read() ?? null);
29
+ return (_jsx(GuardResultContext.Provider, { value: result, children: _jsx(GuardGate, { result: result, children: children }) }));
30
+ }
31
+ function startGuardChain(guards, location, honoCtx) {
32
+ if (isBrowser()) {
33
+ const active = guards.filter((g) => g.runs === 'client');
34
+ return runClientGuards(active, { location });
35
+ }
36
+ const active = guards.filter((g) => g.runs === 'server');
37
+ if (active.length === 0)
38
+ return Promise.resolve(undefined);
39
+ if (!honoCtx) {
40
+ throw new Error('<Guards> rendered server-side without a HonoContext.Provider. ' +
41
+ 'renderPage must wrap the prerendered tree in <HonoContext.Provider value={{ context: c }}>.');
42
+ }
43
+ return runServerGuards(active, { c: honoCtx, location });
44
+ }
45
+ export const Guards = ({ guards = [], location, fallback, children }) => {
46
+ const honoCtx = useContext(HonoRequestContext).context;
47
+ const prevPath = useRef(location.path);
48
+ const guardRef = useRef(wrapPromise(startGuardChain(guards, location, honoCtx)));
49
+ if (prevPath.current !== location.path) {
50
+ prevPath.current = location.path;
51
+ guardRef.current = wrapPromise(startGuardChain(guards, location, honoCtx));
52
+ }
53
+ return (_jsx(Suspense, { fallback: fallback, children: _jsx(GuardConsumer, { guardRef: guardRef, children: children }) }));
54
+ };
@@ -0,0 +1,20 @@
1
+ export type LoaderFetchCallbacks<T> = {
2
+ onChunk: (value: T) => void;
3
+ onError: (err: Error) => void;
4
+ onEnd: () => void;
5
+ };
6
+ type SerializedLocation = {
7
+ path: string;
8
+ pathParams: Record<string, string>;
9
+ searchParams: Record<string, string>;
10
+ };
11
+ /**
12
+ * POST to /__loaders and consume the response.
13
+ *
14
+ * Static loaders return JSON; the parsed value resolves the returned promise.
15
+ * Streaming loaders return SSE; the first chunk resolves the promise, and
16
+ * subsequent chunks fire callbacks.onChunk. Stream errors after the first
17
+ * chunk fire callbacks.onError. Stream end fires callbacks.onEnd.
18
+ */
19
+ export declare function fetchLoaderData<T>(moduleKey: string, loaderName: string, location: SerializedLocation, signal: AbortSignal, callbacks: LoaderFetchCallbacks<T>): Promise<T>;
20
+ export {};