Skip to content

Commit e4a991c

Browse files
committed
Add server function inspector
1 parent 42a18a3 commit e4a991c

35 files changed

Lines changed: 2021 additions & 72 deletions

apps/tests/src/routes/server-function-ping.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
11
import { createEffect, createSignal } from "solid-js";
22

3-
async function ping(value: string) {
3+
async function sleep(value: unknown, ms: number) {
4+
return new Promise((res) => {
5+
setTimeout(res, ms, value);
6+
})
7+
}
8+
9+
async function ping(value: Date) {
410
"use server";
511

6-
return await Promise.resolve(value);
12+
const current = [
13+
value,
14+
{
15+
name: 'example',
16+
async *[Symbol.asyncIterator]() {
17+
yield sleep('foo', 5000);
18+
yield sleep('bar', 5000);
19+
yield sleep('baz', 5000);
20+
}
21+
}
22+
];
23+
24+
return current;
725
}
826

927
export default function App() {
1028
const [output, setOutput] = createSignal<{ result?: boolean }>({});
1129

1230
createEffect(async () => {
13-
const value = `${Math.random() * 1000}`;
31+
const value = new Date();
1432
const result = await ping(value);
15-
setOutput(prev => ({ ...prev, result: value === result }));
33+
await ping(value);
34+
console.log(result);
35+
setOutput((prev) => ({ ...prev, result: value.toString() === result[0].toString() }));
1636
});
1737

1838
return (

packages/start/src/server/serialization.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export function serializeToJSONStream(value: any) {
109109
});
110110
}
111111

112-
class SerovalChunkReader {
112+
export class SerovalChunkReader {
113113
reader: ReadableStreamDefaultReader<Uint8Array>;
114114
buffer: Uint8Array;
115115
done: boolean;

packages/start/src/server/server-runtime.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { type Component } from "solid-js";
2+
import {
3+
pushRequest,
4+
pushResponse,
5+
} from "../shared/server-function-inspector/server-function-tracker";
26
import {
37
// serializeToJSONStream,
48
serializeToJSONString,
@@ -12,13 +16,13 @@ import {
1216

1317
let INSTANCE = 0;
1418

15-
function createRequest(
19+
async function createRequest(
1620
base: string,
1721
id: string,
1822
instance: string,
1923
options: RequestInit,
2024
) {
21-
return fetch(base, {
25+
const request = new Request(base, {
2226
method: "POST",
2327
...options,
2428
headers: {
@@ -27,6 +31,14 @@ function createRequest(
2731
"X-Server-Instance": instance,
2832
},
2933
});
34+
if (import.meta.env.DEV) {
35+
pushRequest(id, instance, request.clone());
36+
}
37+
const response = await fetch(request);
38+
if (import.meta.env.DEV) {
39+
pushResponse(id, instance, response.clone());
40+
}
41+
return response;
3042
}
3143

3244
async function initializeResponse(

packages/start/src/shared/ErrorBoundary.tsx

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,50 @@
11
// @refresh skip
2-
import { ErrorBoundary as DefaultErrorBoundary, catchError, type ParentProps } from "solid-js";
2+
import {
3+
catchError,
4+
ErrorBoundary as DefaultErrorBoundary,
5+
type ParentProps,
6+
} from "solid-js";
37
import { isServer } from "solid-js/web";
4-
import { HttpStatusCode } from "./HttpStatusCode.ts";
58
import { DevOverlay } from "./dev-overlay/index.tsx";
9+
import { HttpStatusCode } from "./HttpStatusCode.ts";
10+
import { ServerFunctionInspector } from "./server-function-inspector/index.tsx";
611

712
export const ErrorBoundary =
813
import.meta.env.DEV && import.meta.env.START_DEV_OVERLAY
9-
? (props: ParentProps) => <DevOverlay>{props.children}</DevOverlay>
14+
? (props: ParentProps) => (
15+
<DevOverlay>
16+
<ServerFunctionInspector />
17+
{props.children}
18+
</DevOverlay>
19+
)
1020
: (props: ParentProps) => {
11-
const message = isServer
12-
? "500 | Internal Server Error"
13-
: "Error | Uncaught Client Exception";
14-
return (
15-
<DefaultErrorBoundary
16-
fallback={error => {
17-
console.error(error);
18-
return (
19-
<>
20-
<span style="font-size:1.5em;text-align:center;position:fixed;left:0px;bottom:55%;width:100%;">
21-
{message}
22-
</span>
23-
<HttpStatusCode code={500} />
24-
</>
25-
);
26-
}}
27-
>
28-
{props.children}
29-
</DefaultErrorBoundary>
30-
);
31-
};
21+
const message = isServer
22+
? "500 | Internal Server Error"
23+
: "Error | Uncaught Client Exception";
24+
return (
25+
<DefaultErrorBoundary
26+
fallback={(error) => {
27+
console.error(error);
28+
return (
29+
<>
30+
<span style="font-size:1.5em;text-align:center;position:fixed;left:0px;bottom:55%;width:100%;">
31+
{message}
32+
</span>
33+
<HttpStatusCode code={500} />
34+
</>
35+
);
36+
}}
37+
>
38+
{props.children}
39+
</DefaultErrorBoundary>
40+
);
41+
};
3242

3343
export const TopErrorBoundary = (props: ParentProps) => {
3444
let isError = false;
3545
const res = catchError(
3646
() => props.children,
37-
err => {
47+
(err) => {
3848
console.error(err);
3949
isError = !!err;
4050
},

packages/start/src/shared/dev-overlay/DevOverlayDialog.tsx

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import * as htmlToImage from "html-to-image";
44
import type { JSX } from "solid-js";
55
import { createMemo, createSignal, ErrorBoundary, For, Show, Suspense } from "solid-js";
66
import { Portal } from "solid-js/web";
7-
// @ts-ignore - terracotta module resolution issue with NodeNext
8-
import { Dialog, DialogOverlay, DialogPanel, Select, SelectOption } from "terracotta";
97
import info from "../../../package.json" with { type: "json" };
8+
import IconButton from "../ui/IconButton.tsx";
9+
import { Select, SelectOption } from "../ui/Select.tsx";
10+
import { Dialog, DialogOverlay, DialogPanel } from "../ui/Dialog.tsx";
1011
import { CodeView } from "./CodeView.tsx";
1112
import { createStackFrame, type StackFrameSource } from "./createStackFrame.ts";
1213
import download from "./download.ts";
@@ -23,7 +24,9 @@ import {
2324
} from "./icons.tsx";
2425
import "./styles.css";
2526

26-
export function classNames(...classes: (string | boolean | undefined)[]): string {
27+
export function classNames(
28+
...classes: (string | boolean | undefined)[]
29+
): string {
2730
return classes.filter(Boolean).join(" ");
2831
}
2932

@@ -61,7 +64,9 @@ interface StackFramesContentProps {
6164

6265
function getFileName(source: string): string {
6366
try {
64-
const path = source.startsWith("/") ? new URL(source, "file://") : new URL(source);
67+
const path = source.startsWith("/")
68+
? new URL(source, "file://")
69+
: new URL(source);
6570
const paths = path.pathname.split("/");
6671
return paths[paths.length - 1]!;
6772
} catch (error) {
@@ -93,11 +98,14 @@ function StackFramesContent(props: StackFramesContentProps) {
9398
<div data-start-dev-overlay-stack-frames-code>
9499
<ErrorBoundary fallback={null}>
95100
{(() => {
96-
const data = createStackFrame(selectedFrame(), () => props.isCompiled);
101+
const data = createStackFrame(
102+
selectedFrame(),
103+
() => props.isCompiled,
104+
);
97105
return (
98106
<Suspense fallback={<CodeFallback />}>
99107
<Show when={data()} keyed fallback={<CodeFallback />}>
100-
{source => (
108+
{(source) => (
101109
<>
102110
<span data-start-dev-overlay-stack-frames-code-source>{source.source}</span>
103111
<div data-start-dev-overlay-stack-frames-code-container>
@@ -121,10 +129,14 @@ function StackFramesContent(props: StackFramesContentProps) {
121129
onChange={setSelectedFrame}
122130
>
123131
<For each={stackframes}>
124-
{current => (
132+
{(current) => (
125133
<ErrorBoundary
126134
fallback={
127-
<div data-start-dev-overlay-stack-frame>
135+
<SelectOption
136+
value={current}
137+
disabled
138+
data-start-dev-overlay-stack-frame
139+
>
128140
<span data-start-dev-overlay-stack-frame-function>
129141
{current.functionName ?? "<anonymous>"}
130142
</span>
@@ -137,7 +149,7 @@ function StackFramesContent(props: StackFramesContentProps) {
137149
name: current.getFunctionName(),
138150
})}
139151
</span>
140-
</div>
152+
</SelectOption>
141153
}
142154
>
143155
{(() => {
@@ -173,7 +185,9 @@ interface StackFramesProps {
173185
function StackFrames(props: StackFramesProps) {
174186
return (
175187
<Show when={props.error instanceof Error && props.error} keyed>
176-
{current => <StackFramesContent error={current} isCompiled={props.isCompiled} />}
188+
{(current) => (
189+
<StackFramesContent error={current} isCompiled={props.isCompiled} />
190+
)}
177191
</Show>
178192
);
179193
}
@@ -186,7 +200,9 @@ interface DevOverlayDialogProps {
186200
const ISSUE_THREAD = "https://github.com/solidjs/solid-start/issues/new";
187201
const DISCORD_INVITE = "https://discord.com/invite/solidjs";
188202

189-
export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Element {
203+
export default function DevOverlayDialog(
204+
props: DevOverlayDialogProps,
205+
): JSX.Element {
190206
const [currentPage, setCurrentPage] = createSignal(1);
191207
const [isCompiled, setIsCompiled] = createSignal(false);
192208
const length = createMemo(() => props.errors.length);
@@ -196,7 +212,7 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem
196212
});
197213

198214
function goPrev() {
199-
setCurrentPage(c => {
215+
setCurrentPage((c) => {
200216
if (c > 1) {
201217
return c - 1;
202218
}
@@ -205,7 +221,7 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem
205221
}
206222

207223
function goNext() {
208-
setCurrentPage(c => {
224+
setCurrentPage((c) => {
209225
if (c < length()) {
210226
return c + 1;
211227
}
@@ -214,7 +230,7 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem
214230
}
215231

216232
function toggleIsCompiled() {
217-
setIsCompiled(c => !c);
233+
setIsCompiled((c) => !c);
218234
}
219235

220236
const [panel, setPanel] = createSignal<HTMLElement>();
@@ -228,7 +244,7 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem
228244
transform: "scale(0.75)",
229245
},
230246
})
231-
.then(url => {
247+
.then((url) => {
232248
download(url, "start-screenshot.png");
233249
});
234250
}
@@ -239,7 +255,10 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem
239255
url.searchParams.append("labels", "bug");
240256
url.searchParams.append("labels", "needs+triage");
241257
url.searchParams.append("template", "bug.yml");
242-
url.searchParams.append("title", `[Bug?]:` + props.errors[truncated() - 1].toString());
258+
url.searchParams.append(
259+
"title",
260+
`[Bug?]:` + props.errors[truncated() - 1].toString(),
261+
);
243262
window.open(url, "_blank")!.focus();
244263
}
245264

@@ -264,39 +283,41 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem
264283
</div>
265284
<Show when={props.errors.length > 1}>
266285
<div data-start-dev-overlay-pagination>
267-
<button data-start-dev-overlay-button onClick={goPrev} type="button">
286+
<IconButton data-start-dev-overlay-button onClick={goPrev} type="button">
268287
<ArrowLeftIcon title="Go Previous" />
269-
</button>
288+
</IconButton>
270289
<div data-start-dev-overlay-page-counter>
271290
{`${truncated()} of ${props.errors.length}`}
272291
</div>
273-
<button data-start-dev-overlay-button onClick={goNext} type="button">
292+
<IconButton data-start-dev-overlay-button onClick={goNext} type="button">
274293
<ArrowRightIcon title="Go Next" />
275-
</button>
294+
</IconButton>
276295
</div>
277296
</Show>
278297
</div>
279298
<div data-start-dev-overlay-controls>
280-
<button data-start-dev-overlay-button onClick={redirectToGithub} type="button">
299+
<IconButton data-start-dev-overlay-button onClick={redirectToGithub} type="button">
281300
<GithubIcon title="Create an issue thread on Github" />
282-
</button>
283-
<button data-start-dev-overlay-button onClick={redirectToDiscord} type="button">
301+
</IconButton>
302+
<IconButton data-start-dev-overlay-button onClick={redirectToDiscord} type="button">
284303
<DiscordIcon title="Join our Discord Channel" />
285-
</button>
286-
<button data-start-dev-overlay-button onClick={downloadScreenshot} type="button">
304+
</IconButton>
305+
<IconButton data-start-dev-overlay-button onClick={downloadScreenshot} type="button">
287306
<CameraIcon title="Capture Error Overlay" />
288-
</button>
289-
<button data-start-dev-overlay-button onClick={toggleIsCompiled} type="button">
307+
</IconButton>
308+
<IconButton data-start-dev-overlay-button onClick={toggleIsCompiled} type="button">
290309
<Show
291310
when={isCompiled()}
292-
fallback={<ViewOriginalIcon title="View Original Source" />}
311+
fallback={
312+
<ViewOriginalIcon title="View Original Source" />
313+
}
293314
>
294315
<ViewCompiledIcon title="View Compiled Source" />
295316
</Show>
296-
</button>
297-
<button data-start-dev-overlay-button onClick={props.resetError} type="button">
317+
</IconButton>
318+
<IconButton data-start-dev-overlay-button onClick={props.resetError} type="button">
298319
<RefreshIcon title="Reset Error" />
299-
</button>
320+
</IconButton>
300321
</div>
301322
</div>
302323
<Show when={props.errors[truncated() - 1]} keyed>

0 commit comments

Comments
 (0)