hono-preact 0.2.0 → 0.4.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/iso/action-result-context.d.ts +22 -0
- package/dist/iso/action-result-context.js +2 -0
- package/dist/iso/action.d.ts +52 -13
- package/dist/iso/action.js +204 -88
- package/dist/iso/cache.d.ts +9 -0
- package/dist/iso/cache.js +26 -0
- package/dist/iso/define-app.d.ts +7 -0
- package/dist/iso/define-loader.d.ts +12 -0
- package/dist/iso/define-loader.js +26 -16
- package/dist/iso/form.d.ts +13 -4
- package/dist/iso/form.js +115 -33
- package/dist/iso/index.d.ts +13 -4
- package/dist/iso/index.js +14 -2
- package/dist/iso/internal/action-envelope.d.ts +37 -0
- package/dist/iso/internal/action-envelope.js +47 -0
- package/dist/iso/internal/action-result-store.d.ts +28 -0
- package/dist/iso/internal/action-result-store.js +35 -0
- package/dist/iso/internal/envelope.js +1 -2
- package/dist/iso/internal/form-submit-store.d.ts +9 -0
- package/dist/iso/internal/form-submit-store.js +32 -0
- package/dist/iso/internal/history-shim.d.ts +7 -0
- package/dist/iso/internal/history-shim.js +79 -0
- package/dist/iso/internal/loader-fetch.js +65 -34
- package/dist/iso/internal/loader.d.ts +3 -3
- package/dist/iso/internal/merge-refs.d.ts +4 -0
- package/dist/iso/internal/merge-refs.js +14 -0
- package/dist/iso/internal/persist-registry.d.ts +10 -0
- package/dist/iso/internal/persist-registry.js +24 -0
- package/dist/iso/internal/route-boundary.d.ts +4 -4
- package/dist/iso/internal/route-change.d.ts +8 -2
- package/dist/iso/internal/route-change.js +107 -12
- package/dist/iso/internal/safe-redirect.d.ts +7 -0
- package/dist/iso/internal/safe-redirect.js +27 -0
- package/dist/iso/internal/sse-decoder.d.ts +1 -1
- package/dist/iso/internal/sse-decoder.js +40 -26
- package/dist/iso/internal/use-render.d.ts +11 -0
- package/dist/iso/internal/use-render.js +47 -0
- package/dist/iso/internal/view-transition-event.d.ts +23 -0
- package/dist/iso/internal/view-transition-event.js +25 -0
- package/dist/iso/internal.d.ts +12 -1
- package/dist/iso/internal.js +13 -1
- package/dist/iso/optimistic-action.d.ts +10 -1
- package/dist/iso/optimistic-action.js +11 -3
- package/dist/iso/optimistic.d.ts +10 -1
- package/dist/iso/optimistic.js +45 -5
- package/dist/iso/outcomes.d.ts +14 -2
- package/dist/iso/outcomes.js +14 -3
- package/dist/iso/persist.d.ts +14 -0
- package/dist/iso/persist.js +56 -0
- package/dist/iso/use-action-result.d.ts +25 -0
- package/dist/iso/use-action-result.js +39 -0
- package/dist/iso/use-form-status.d.ts +5 -0
- package/dist/iso/use-form-status.js +13 -0
- package/dist/iso/view-transition-lifecycle.d.ts +9 -0
- package/dist/iso/view-transition-lifecycle.js +18 -0
- package/dist/iso/view-transition-name.d.ts +17 -0
- package/dist/iso/view-transition-name.js +79 -0
- package/dist/iso/view-transition-types.d.ts +8 -0
- package/dist/iso/view-transition-types.js +21 -0
- package/dist/server/actions-handler.d.ts +7 -0
- package/dist/server/actions-handler.js +42 -9
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +2 -1
- package/dist/server/loaders-handler.d.ts +8 -0
- package/dist/server/loaders-handler.js +37 -4
- package/dist/server/page-action-handler.d.ts +63 -0
- package/dist/server/page-action-handler.js +274 -0
- package/dist/server/page-action-resolvers.d.ts +28 -0
- package/dist/server/page-action-resolvers.js +147 -0
- package/dist/server/render.js +136 -55
- package/dist/server/route-server-modules.d.ts +7 -8
- package/dist/server/route-server-modules.js +7 -8
- package/dist/server/speculation-rules.d.ts +3 -0
- package/dist/server/speculation-rules.js +8 -0
- package/dist/server/sse.d.ts +43 -28
- package/dist/server/sse.js +113 -88
- package/dist/vite/client-entry.js +12 -3
- package/dist/vite/server-entry.js +10 -2
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readSSE } from './sse-decoder.js';
|
|
2
|
+
import { TimeoutError } from '../action.js';
|
|
2
3
|
/**
|
|
3
4
|
* POST to /__loaders and consume the response.
|
|
4
5
|
*
|
|
@@ -18,6 +19,9 @@ export async function fetchLoaderData(moduleKey, loaderName, location, signal, c
|
|
|
18
19
|
// Try to parse a deny outcome envelope first; it carries `message`
|
|
19
20
|
// rather than `error`. Fall back to the legacy `{ error }` shape.
|
|
20
21
|
const body = (await res.json().catch(() => ({})));
|
|
22
|
+
if (body.__outcome === 'timeout' && typeof body.timeoutMs === 'number') {
|
|
23
|
+
throw new TimeoutError(body.timeoutMs);
|
|
24
|
+
}
|
|
21
25
|
if (body.__outcome === 'deny') {
|
|
22
26
|
// The `deny()` constructor defaults `message` for first-party
|
|
23
27
|
// callers, but a hand-rolled envelope from custom server middleware
|
|
@@ -74,40 +78,7 @@ export async function fetchLoaderData(moduleKey, loaderName, location, signal, c
|
|
|
74
78
|
// SSE: read the first message event synchronously (await first chunk),
|
|
75
79
|
// then kick off an async loop that pushes subsequent chunks to callbacks.
|
|
76
80
|
const iter = readSSE(res.body);
|
|
77
|
-
|
|
78
|
-
while (true) {
|
|
79
|
-
const step = await iter.next();
|
|
80
|
-
if (step.done) {
|
|
81
|
-
// Stream closed before any data event: error
|
|
82
|
-
throw new Error('Streaming loader closed before emitting any data');
|
|
83
|
-
}
|
|
84
|
-
const ev = step.value;
|
|
85
|
-
if (ev.event === 'message') {
|
|
86
|
-
try {
|
|
87
|
-
firstChunk = JSON.parse(ev.data);
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
throw new Error('Malformed first chunk in streaming loader');
|
|
91
|
-
}
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
if (ev.event === 'error') {
|
|
95
|
-
try {
|
|
96
|
-
const parsed = JSON.parse(ev.data);
|
|
97
|
-
const err = new Error(parsed.message ?? 'Streamed error');
|
|
98
|
-
if (parsed.name)
|
|
99
|
-
err.name = parsed.name;
|
|
100
|
-
throw err;
|
|
101
|
-
}
|
|
102
|
-
catch (e) {
|
|
103
|
-
if (e instanceof Error && e.message.startsWith('Malformed')) {
|
|
104
|
-
throw new Error('Malformed error event in streaming loader');
|
|
105
|
-
}
|
|
106
|
-
throw e;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
// Other events (result, etc.): ignore for loaders
|
|
110
|
-
}
|
|
81
|
+
const firstChunk = await readFirstChunk(iter);
|
|
111
82
|
// Continue consuming chunks in the background. Each subsequent message
|
|
112
83
|
// pushes a value via onChunk. Errors fire onError. End fires onEnd.
|
|
113
84
|
(async () => {
|
|
@@ -127,6 +98,16 @@ export async function fetchLoaderData(moduleKey, loaderName, location, signal, c
|
|
|
127
98
|
// malformed mid-stream chunk: skip
|
|
128
99
|
}
|
|
129
100
|
}
|
|
101
|
+
else if (ev.event === 'timeout') {
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(ev.data);
|
|
104
|
+
callbacks.onError(new TimeoutError(parsed.timeoutMs ?? 0));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
callbacks.onError(new Error('Malformed timeout event in streaming loader'));
|
|
108
|
+
}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
130
111
|
else if (ev.event === 'error') {
|
|
131
112
|
try {
|
|
132
113
|
const parsed = JSON.parse(ev.data);
|
|
@@ -151,3 +132,53 @@ export async function fetchLoaderData(moduleKey, loaderName, location, signal, c
|
|
|
151
132
|
})();
|
|
152
133
|
return firstChunk;
|
|
153
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Drain the SSE stream until the first `message` event and parse it as `T`.
|
|
137
|
+
* `timeout` / `error` events before the first message reject with the
|
|
138
|
+
* appropriate error. Other event types are ignored.
|
|
139
|
+
*/
|
|
140
|
+
async function readFirstChunk(iter) {
|
|
141
|
+
while (true) {
|
|
142
|
+
const step = await iter.next();
|
|
143
|
+
if (step.done) {
|
|
144
|
+
throw new Error('Streaming loader closed before emitting any data');
|
|
145
|
+
}
|
|
146
|
+
const ev = step.value;
|
|
147
|
+
if (ev.event === 'message') {
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(ev.data);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
throw new Error('Malformed first chunk in streaming loader');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (ev.event === 'timeout') {
|
|
156
|
+
let timeoutMs = 0;
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(ev.data);
|
|
159
|
+
timeoutMs = parsed.timeoutMs ?? 0;
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
throw new Error(`Malformed timeout event in streaming loader: ${e instanceof Error ? e.message : String(e)}`);
|
|
163
|
+
}
|
|
164
|
+
throw new TimeoutError(timeoutMs);
|
|
165
|
+
}
|
|
166
|
+
if (ev.event === 'error') {
|
|
167
|
+
let message = 'Streamed error';
|
|
168
|
+
let name;
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(ev.data);
|
|
171
|
+
message = parsed.message ?? message;
|
|
172
|
+
name = parsed.name;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
throw new Error('Malformed error event in streaming loader');
|
|
176
|
+
}
|
|
177
|
+
const err = new Error(message);
|
|
178
|
+
if (name)
|
|
179
|
+
err.name = name;
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
// Other events (result, etc.): ignore for loaders
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { ComponentChildren
|
|
1
|
+
import type { ComponentChildren } from 'preact';
|
|
2
2
|
import type { RouteHook } from 'preact-iso';
|
|
3
3
|
import type { LoaderRef } from '../define-loader.js';
|
|
4
4
|
export { serializeLocationForCache } from './cache-key.js';
|
|
5
5
|
type LoaderHostProps<T> = {
|
|
6
6
|
loader: LoaderRef<T>;
|
|
7
7
|
location?: RouteHook;
|
|
8
|
-
fallback?:
|
|
8
|
+
fallback?: ComponentChildren;
|
|
9
9
|
errorFallback?: ComponentChildren | ((err: Error, reset: () => void) => ComponentChildren);
|
|
10
10
|
children: ComponentChildren;
|
|
11
11
|
};
|
|
12
|
-
export declare function LoaderHost<T>({ loader: loaderRef, location: locationProp, fallback, errorFallback, children, }: LoaderHostProps<T>): JSX.Element;
|
|
12
|
+
export declare function LoaderHost<T>({ loader: loaderRef, location: locationProp, fallback, errorFallback, children, }: LoaderHostProps<T>): import("preact").JSX.Element;
|
|
13
13
|
export { LoaderHost as Loader };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComponentChildren } from 'preact';
|
|
2
|
+
export interface PersistEntry {
|
|
3
|
+
children: ComponentChildren;
|
|
4
|
+
viewTransitionName: string | undefined;
|
|
5
|
+
}
|
|
6
|
+
export declare function __persistRegistryWrite(id: string, entry: PersistEntry): void;
|
|
7
|
+
export declare function __persistRegistryRead(): ReadonlyMap<string, PersistEntry>;
|
|
8
|
+
export declare function __persistRegistrySubscribe(sub: () => void): () => void;
|
|
9
|
+
/** Test-only reset. Do not call from production code. */
|
|
10
|
+
export declare function __persistRegistryResetForTesting(): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
let map = new Map();
|
|
2
|
+
const subs = new Set();
|
|
3
|
+
export function __persistRegistryWrite(id, entry) {
|
|
4
|
+
// Replace the map reference so consumers using identity-checks can detect.
|
|
5
|
+
const next = new Map(map);
|
|
6
|
+
next.set(id, entry);
|
|
7
|
+
map = next;
|
|
8
|
+
for (const sub of subs)
|
|
9
|
+
sub();
|
|
10
|
+
}
|
|
11
|
+
export function __persistRegistryRead() {
|
|
12
|
+
return map;
|
|
13
|
+
}
|
|
14
|
+
export function __persistRegistrySubscribe(sub) {
|
|
15
|
+
subs.add(sub);
|
|
16
|
+
return () => {
|
|
17
|
+
subs.delete(sub);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Test-only reset. Do not call from production code. */
|
|
21
|
+
export function __persistRegistryResetForTesting() {
|
|
22
|
+
map = new Map();
|
|
23
|
+
subs.clear();
|
|
24
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component } from 'preact';
|
|
2
|
-
import type { ComponentChildren, FunctionComponent
|
|
3
|
-
type ErrorFallback =
|
|
2
|
+
import type { ComponentChildren, FunctionComponent } from 'preact';
|
|
3
|
+
type ErrorFallback = ComponentChildren | ((error: Error, reset: () => void) => ComponentChildren);
|
|
4
4
|
type ErrorBoundaryProps = {
|
|
5
5
|
fallback?: ErrorFallback;
|
|
6
6
|
children: ComponentChildren;
|
|
@@ -16,10 +16,10 @@ export declare class ErrorBoundary extends Component<ErrorBoundaryProps, {
|
|
|
16
16
|
};
|
|
17
17
|
componentDidCatch(error: unknown): void;
|
|
18
18
|
reset: () => void;
|
|
19
|
-
render():
|
|
19
|
+
render(): any;
|
|
20
20
|
}
|
|
21
21
|
export declare const RouteBoundary: FunctionComponent<{
|
|
22
|
-
fallback?:
|
|
22
|
+
fallback?: ComponentChildren;
|
|
23
23
|
errorFallback?: ErrorFallback;
|
|
24
24
|
children: ComponentChildren;
|
|
25
25
|
}>;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { ViewTransitionEvent } from './view-transition-event.js';
|
|
2
|
+
export type PhaseName = 'beforeTransition' | 'beforeSwap' | 'afterSwap' | 'afterTransition';
|
|
3
|
+
type PhaseSub = (event: ViewTransitionEvent) => void | Promise<void>;
|
|
4
|
+
type LegacySub = (to: string, from: string | undefined) => void;
|
|
5
|
+
export declare function __subscribePhase(phase: PhaseName, sub: PhaseSub): () => void;
|
|
6
|
+
export declare function __subscribeRouteChange(sub: LegacySub): () => void;
|
|
2
7
|
export declare function __dispatchRouteChange(to: string, from: string | undefined): void;
|
|
3
|
-
|
|
8
|
+
/** @internal Test-only reset for default-types installer. */
|
|
9
|
+
export declare function resetDefaultTypesForTesting(): void;
|
|
4
10
|
export {};
|
|
@@ -1,18 +1,113 @@
|
|
|
1
1
|
import { flushSync } from 'preact/compat';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
import { ViewTransitionEvent, } from './view-transition-event.js';
|
|
3
|
+
import { getNavDirection } from './history-shim.js';
|
|
4
|
+
const phaseSubs = {
|
|
5
|
+
beforeTransition: new Set(),
|
|
6
|
+
beforeSwap: new Set(),
|
|
7
|
+
afterSwap: new Set(),
|
|
8
|
+
afterTransition: new Set(),
|
|
9
|
+
};
|
|
10
|
+
const legacySubs = new Set();
|
|
11
|
+
export function __subscribePhase(phase, sub) {
|
|
12
|
+
phaseSubs[phase].add(sub);
|
|
13
|
+
return () => {
|
|
14
|
+
phaseSubs[phase].delete(sub);
|
|
15
|
+
};
|
|
12
16
|
}
|
|
13
17
|
export function __subscribeRouteChange(sub) {
|
|
14
|
-
|
|
18
|
+
legacySubs.add(sub);
|
|
15
19
|
return () => {
|
|
16
|
-
|
|
20
|
+
legacySubs.delete(sub);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function fireLegacy(to, from) {
|
|
24
|
+
for (const sub of legacySubs)
|
|
25
|
+
sub(to, from);
|
|
26
|
+
}
|
|
27
|
+
function getStartViewTransition() {
|
|
28
|
+
if (typeof document === 'undefined')
|
|
29
|
+
return undefined;
|
|
30
|
+
const fn = document.startViewTransition;
|
|
31
|
+
return typeof fn === 'function' ? fn.bind(document) : undefined;
|
|
32
|
+
}
|
|
33
|
+
export function __dispatchRouteChange(to, from) {
|
|
34
|
+
ensureDefaultTypes();
|
|
35
|
+
const direction = getNavDirection();
|
|
36
|
+
const event = new ViewTransitionEvent({ to, from, direction });
|
|
37
|
+
for (const sub of phaseSubs.beforeTransition)
|
|
38
|
+
sub(event);
|
|
39
|
+
const fireAfterSwap = () => {
|
|
40
|
+
for (const sub of phaseSubs.afterSwap)
|
|
41
|
+
sub(event);
|
|
42
|
+
// Legacy subscribers fire at the afterSwap slot: after the DOM swap,
|
|
43
|
+
// before the browser begins animating the new frame.
|
|
44
|
+
fireLegacy(to, from);
|
|
17
45
|
};
|
|
46
|
+
const fireAfterTransition = (reason) => {
|
|
47
|
+
if (reason !== undefined)
|
|
48
|
+
event.reason = reason;
|
|
49
|
+
for (const sub of phaseSubs.afterTransition)
|
|
50
|
+
sub(event);
|
|
51
|
+
};
|
|
52
|
+
if (event._skipped) {
|
|
53
|
+
flushSync(() => { });
|
|
54
|
+
fireAfterSwap();
|
|
55
|
+
fireAfterTransition('skipped');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const start = getStartViewTransition();
|
|
59
|
+
if (!start) {
|
|
60
|
+
flushSync(() => { });
|
|
61
|
+
fireAfterSwap();
|
|
62
|
+
fireAfterTransition('unsupported');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const transition = start(() => {
|
|
66
|
+
flushSync(() => { });
|
|
67
|
+
});
|
|
68
|
+
// Set event.transition before firing beforeSwap so all subsequent phase
|
|
69
|
+
// subscribers see a non-null transition. In real browsers startViewTransition
|
|
70
|
+
// invokes the callback asynchronously, meaning transition is set here before
|
|
71
|
+
// the browser calls the update function. In synchronous test mocks the
|
|
72
|
+
// callback returns before start() does, so we set transition here (after
|
|
73
|
+
// start() returns) and then fire the post-swap phases manually.
|
|
74
|
+
event.transition = transition;
|
|
75
|
+
for (const sub of phaseSubs.beforeSwap)
|
|
76
|
+
sub(event);
|
|
77
|
+
fireAfterSwap();
|
|
78
|
+
// Apply types accumulated across all phases. beforeTransition and beforeSwap
|
|
79
|
+
// have both run by this point, so the full set of types is available here.
|
|
80
|
+
const vtTypes = transition.types;
|
|
81
|
+
if (vtTypes && typeof vtTypes.add === 'function') {
|
|
82
|
+
for (const t of event.types)
|
|
83
|
+
vtTypes.add(t);
|
|
84
|
+
}
|
|
85
|
+
transition.finished.then(() => fireAfterTransition(), () => fireAfterTransition('aborted'));
|
|
86
|
+
}
|
|
87
|
+
let defaultTypesInstalled = false;
|
|
88
|
+
let firstDispatchSeen = false;
|
|
89
|
+
let defaultTypeUnsubscriber = null;
|
|
90
|
+
function ensureDefaultTypes() {
|
|
91
|
+
if (defaultTypesInstalled)
|
|
92
|
+
return;
|
|
93
|
+
defaultTypesInstalled = true;
|
|
94
|
+
defaultTypeUnsubscriber = __subscribePhase('beforeTransition', (event) => {
|
|
95
|
+
if (!firstDispatchSeen) {
|
|
96
|
+
event.types.push('nav-initial');
|
|
97
|
+
firstDispatchSeen = true;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
event.types.push(`nav-${event.direction}`);
|
|
101
|
+
}
|
|
102
|
+
event.types.push('nav-same-origin');
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** @internal Test-only reset for default-types installer. */
|
|
106
|
+
export function resetDefaultTypesForTesting() {
|
|
107
|
+
if (defaultTypeUnsubscriber) {
|
|
108
|
+
defaultTypeUnsubscriber();
|
|
109
|
+
}
|
|
110
|
+
defaultTypesInstalled = false;
|
|
111
|
+
firstDispatchSeen = false;
|
|
112
|
+
defaultTypeUnsubscriber = null;
|
|
18
113
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function isSameOrigin(target: string): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Navigate to `target` only when it resolves same-origin against the current
|
|
4
|
+
* window. Cross-origin targets log a console error and are not followed.
|
|
5
|
+
* Returns true when the navigation was issued.
|
|
6
|
+
*/
|
|
7
|
+
export declare function assignSafeRedirect(target: string): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function isSameOrigin(target) {
|
|
2
|
+
if (typeof window === 'undefined')
|
|
3
|
+
return true;
|
|
4
|
+
try {
|
|
5
|
+
// Relative URLs resolve against the current origin and are always same-origin.
|
|
6
|
+
const resolved = new URL(target, window.location.href);
|
|
7
|
+
return resolved.origin === window.location.origin;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Navigate to `target` only when it resolves same-origin against the current
|
|
15
|
+
* window. Cross-origin targets log a console error and are not followed.
|
|
16
|
+
* Returns true when the navigation was issued.
|
|
17
|
+
*/
|
|
18
|
+
export function assignSafeRedirect(target) {
|
|
19
|
+
if (typeof window === 'undefined')
|
|
20
|
+
return false;
|
|
21
|
+
if (!isSameOrigin(target)) {
|
|
22
|
+
console.error(`[hono-preact] refusing to navigate to cross-origin redirect target: ${target}`);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
window.location.assign(target);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
@@ -2,4 +2,4 @@ export type SSEEvent = {
|
|
|
2
2
|
event: string;
|
|
3
3
|
data: string;
|
|
4
4
|
};
|
|
5
|
-
export declare function readSSE(stream: ReadableStream<
|
|
5
|
+
export declare function readSSE(stream: ReadableStream<BufferSource>): AsyncGenerator<SSEEvent, void, unknown>;
|
|
@@ -1,39 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
const reader = stream.getReader();
|
|
3
|
-
const decoder = new TextDecoder();
|
|
1
|
+
function lineSplitTransform() {
|
|
4
2
|
let buffer = '';
|
|
3
|
+
return new TransformStream({
|
|
4
|
+
transform(chunk, controller) {
|
|
5
|
+
buffer += chunk;
|
|
6
|
+
let nl;
|
|
7
|
+
while ((nl = buffer.indexOf('\n')) !== -1) {
|
|
8
|
+
controller.enqueue(buffer.slice(0, nl).replace(/\r$/, ''));
|
|
9
|
+
buffer = buffer.slice(nl + 1);
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
flush(controller) {
|
|
13
|
+
if (buffer.length) {
|
|
14
|
+
controller.enqueue(buffer.replace(/\r$/, ''));
|
|
15
|
+
buffer = '';
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export async function* readSSE(stream) {
|
|
21
|
+
const lines = stream
|
|
22
|
+
.pipeThrough(new TextDecoderStream())
|
|
23
|
+
.pipeThrough(lineSplitTransform());
|
|
5
24
|
let event = 'message';
|
|
6
25
|
let dataLines = [];
|
|
26
|
+
const reader = lines.getReader();
|
|
7
27
|
try {
|
|
8
28
|
while (true) {
|
|
9
|
-
const { done, value } = await reader.read();
|
|
29
|
+
const { done, value: line } = await reader.read();
|
|
10
30
|
if (done) {
|
|
11
|
-
|
|
12
|
-
if (dataLines.length)
|
|
31
|
+
if (dataLines.length) {
|
|
13
32
|
yield { event, data: dataLines.join('\n') };
|
|
33
|
+
}
|
|
14
34
|
return;
|
|
15
35
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const line = buffer.slice(0, nl).replace(/\r$/, '');
|
|
20
|
-
buffer = buffer.slice(nl + 1);
|
|
21
|
-
if (line === '') {
|
|
22
|
-
if (dataLines.length) {
|
|
23
|
-
yield { event, data: dataLines.join('\n') };
|
|
24
|
-
}
|
|
25
|
-
event = 'message';
|
|
26
|
-
dataLines = [];
|
|
27
|
-
}
|
|
28
|
-
else if (line.startsWith(':')) {
|
|
29
|
-
// SSE comment / keepalive, ignore
|
|
30
|
-
}
|
|
31
|
-
else if (line.startsWith('event:')) {
|
|
32
|
-
event = line.slice(6).trim();
|
|
33
|
-
}
|
|
34
|
-
else if (line.startsWith('data:')) {
|
|
35
|
-
dataLines.push(line.slice(5).replace(/^ /, ''));
|
|
36
|
+
if (line === '') {
|
|
37
|
+
if (dataLines.length) {
|
|
38
|
+
yield { event, data: dataLines.join('\n') };
|
|
36
39
|
}
|
|
40
|
+
event = 'message';
|
|
41
|
+
dataLines = [];
|
|
42
|
+
}
|
|
43
|
+
else if (line.startsWith(':')) {
|
|
44
|
+
// SSE comment / keepalive, ignore
|
|
45
|
+
}
|
|
46
|
+
else if (line.startsWith('event:')) {
|
|
47
|
+
event = line.slice(6).trim();
|
|
48
|
+
}
|
|
49
|
+
else if (line.startsWith('data:')) {
|
|
50
|
+
dataLines.push(line.slice(5).replace(/^ /, ''));
|
|
37
51
|
}
|
|
38
52
|
}
|
|
39
53
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ComponentChildren, type VNode } from 'preact';
|
|
2
|
+
type Props = Record<string, unknown>;
|
|
3
|
+
export type UseRenderRender = VNode | string | ((props: Props) => VNode) | undefined;
|
|
4
|
+
interface UseRenderOptions {
|
|
5
|
+
render?: UseRenderRender;
|
|
6
|
+
defaultTag: string;
|
|
7
|
+
props: Props;
|
|
8
|
+
children?: ComponentChildren;
|
|
9
|
+
}
|
|
10
|
+
export declare function useRender(opts: UseRenderOptions): VNode;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { cloneElement, h, } from 'preact';
|
|
2
|
+
import { mergeRefs } from './merge-refs.js';
|
|
3
|
+
function joinClass(a, b) {
|
|
4
|
+
const parts = [];
|
|
5
|
+
if (typeof a === 'string' && a.length > 0)
|
|
6
|
+
parts.push(a);
|
|
7
|
+
if (typeof b === 'string' && b.length > 0)
|
|
8
|
+
parts.push(b);
|
|
9
|
+
if (parts.length === 0)
|
|
10
|
+
return undefined;
|
|
11
|
+
return parts.join(' ');
|
|
12
|
+
}
|
|
13
|
+
function mergeProps(user, framework) {
|
|
14
|
+
const out = { ...user };
|
|
15
|
+
for (const key of Object.keys(framework)) {
|
|
16
|
+
if (key === 'class' || key === 'className') {
|
|
17
|
+
const userClass = (user.class ?? user.className);
|
|
18
|
+
const merged = joinClass(userClass, framework[key]);
|
|
19
|
+
if (merged !== undefined)
|
|
20
|
+
out.class = merged;
|
|
21
|
+
delete out.className;
|
|
22
|
+
}
|
|
23
|
+
else if (key === 'ref') {
|
|
24
|
+
out.ref = mergeRefs(user.ref, framework.ref);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
out[key] = framework[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
export function useRender(opts) {
|
|
33
|
+
const { render, defaultTag, props, children } = opts;
|
|
34
|
+
if (typeof render === 'function') {
|
|
35
|
+
return render(mergeProps({}, props));
|
|
36
|
+
}
|
|
37
|
+
if (render && typeof render === 'object' && 'type' in render) {
|
|
38
|
+
const merged = mergeProps((render.props ?? {}), props);
|
|
39
|
+
const mergedChildren = children !== undefined
|
|
40
|
+
? children
|
|
41
|
+
: (render.props?.children ??
|
|
42
|
+
null);
|
|
43
|
+
return cloneElement(render, merged, mergedChildren);
|
|
44
|
+
}
|
|
45
|
+
const tag = typeof render === 'string' ? render : defaultTag;
|
|
46
|
+
return h(tag, props, children);
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type NavDirection = 'initial' | 'push' | 'replace' | 'back' | 'forward';
|
|
2
|
+
export type ViewTransitionReason = 'skipped' | 'unsupported' | 'aborted';
|
|
3
|
+
interface ViewTransitionEventInit {
|
|
4
|
+
to: string;
|
|
5
|
+
from: string | undefined;
|
|
6
|
+
direction: NavDirection;
|
|
7
|
+
}
|
|
8
|
+
export declare class ViewTransitionEvent {
|
|
9
|
+
readonly to: string;
|
|
10
|
+
readonly from: string | undefined;
|
|
11
|
+
readonly direction: NavDirection;
|
|
12
|
+
readonly types: string[];
|
|
13
|
+
transition: ViewTransition | null;
|
|
14
|
+
reason: ViewTransitionReason | undefined;
|
|
15
|
+
/** @internal */
|
|
16
|
+
_skipped: boolean;
|
|
17
|
+
private readonly stash;
|
|
18
|
+
constructor(init: ViewTransitionEventInit);
|
|
19
|
+
skip(): void;
|
|
20
|
+
set(key: unknown, value: unknown): void;
|
|
21
|
+
get<T = unknown>(key: unknown): T | undefined;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class ViewTransitionEvent {
|
|
2
|
+
to;
|
|
3
|
+
from;
|
|
4
|
+
direction;
|
|
5
|
+
types = [];
|
|
6
|
+
transition = null;
|
|
7
|
+
reason = undefined;
|
|
8
|
+
/** @internal */
|
|
9
|
+
_skipped = false;
|
|
10
|
+
stash = new Map();
|
|
11
|
+
constructor(init) {
|
|
12
|
+
this.to = init.to;
|
|
13
|
+
this.from = init.from;
|
|
14
|
+
this.direction = init.direction;
|
|
15
|
+
}
|
|
16
|
+
skip() {
|
|
17
|
+
this._skipped = true;
|
|
18
|
+
}
|
|
19
|
+
set(key, value) {
|
|
20
|
+
this.stash.set(key, value);
|
|
21
|
+
}
|
|
22
|
+
get(key) {
|
|
23
|
+
return this.stash.get(key);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/iso/internal.d.ts
CHANGED
|
@@ -2,19 +2,30 @@ export { Loader } from './internal/loader.js';
|
|
|
2
2
|
export { Envelope } from './internal/envelope.js';
|
|
3
3
|
export { RouteBoundary } from './internal/route-boundary.js';
|
|
4
4
|
export { OptimisticOverlay } from './internal/optimistic-overlay.js';
|
|
5
|
+
export { serializeActionOutcome, type ActionEnvelope, type ActionResolution, type SerializedEnvelope, } from './internal/action-envelope.js';
|
|
5
6
|
export { LoaderIdContext, LoaderDataContext } from './internal/contexts.js';
|
|
6
7
|
export { ReloadContext } from './reload-context.js';
|
|
7
8
|
export { RouteLocationsContext, RouteLocationsProvider, } from './internal/route-locations.js';
|
|
8
9
|
export { getPreloadedData, deletePreloadedData } from './internal/preload.js';
|
|
9
|
-
export { runRequestScope, getRequestStore, captureRequestScope, } from './cache.js';
|
|
10
|
+
export { runRequestScope, getRequestStore, captureRequestScope, getActionResultSlot, setActionResultSlot, type ActionResultSlot, } from './cache.js';
|
|
10
11
|
export { default as wrapPromise } from './internal/wrap-promise.js';
|
|
11
12
|
export { HonoRequestContext } from './internal/contexts.js';
|
|
12
13
|
export { PageMiddlewareHost } from './internal/page-middleware-host.js';
|
|
13
14
|
export { __dispatchRouteChange, __subscribeRouteChange, } from './internal/route-change.js';
|
|
15
|
+
export { installHistoryShim, getNavDirection, } from './internal/history-shim.js';
|
|
16
|
+
export { __subscribePhase, type PhaseName } from './internal/route-change.js';
|
|
17
|
+
export { ViewTransitionEvent, type NavDirection, type ViewTransitionReason, } from './internal/view-transition-event.js';
|
|
18
|
+
export { useRender, type UseRenderRender } from './internal/use-render.js';
|
|
19
|
+
export { mergeRefs } from './internal/merge-refs.js';
|
|
14
20
|
export { installStreamRegistry, subscribeToLoaderStream, } from './internal/stream-registry.js';
|
|
15
21
|
export { registerServerStreamingLoader, takeServerStreamingLoaders, } from './internal/streaming-ssr.js';
|
|
16
22
|
export type { ServerLoaderStream } from './internal/streaming-ssr.js';
|
|
23
|
+
export { beginSubmit, endSubmit, isPending, subscribe as subscribeFormSubmit, } from './internal/form-submit-store.js';
|
|
24
|
+
export { assignSafeRedirect, isSameOrigin } from './internal/safe-redirect.js';
|
|
25
|
+
export { setLastActionResult, clearLastActionResult, getLastActionResult, subscribeActionResults, type StoredActionResult, } from './internal/action-result-store.js';
|
|
17
26
|
export { dispatchServer, dispatchClient, type DispatchResult, } from './internal/middleware-runner.js';
|
|
18
27
|
export { partitionUse } from './internal/use-partitioner.js';
|
|
19
28
|
export { fanStart, fanChunk, fanEnd, fanError, fanAbort, } from './internal/stream-observer-runner.js';
|
|
20
29
|
export { __$createLoaderStub_hpiso } from './internal/loader-stub.js';
|
|
30
|
+
export { readSSE } from './internal/sse-decoder.js';
|
|
31
|
+
export type { SSEEvent } from './internal/sse-decoder.js';
|