@solidjs/start 1.2.0 → 2.0.0-alpha.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 (184) hide show
  1. package/README.md +88 -8
  2. package/dist/client/StartClient.d.ts +0 -1
  3. package/dist/client/StartClient.jsx +2 -4
  4. package/dist/client/index.d.ts +2 -3
  5. package/dist/client/index.jsx +2 -4
  6. package/dist/client/mount.js +10 -2
  7. package/dist/client/spa/index.d.ts +1 -2
  8. package/dist/client/spa/index.jsx +1 -2
  9. package/dist/config/constants.d.ts +16 -0
  10. package/dist/config/constants.js +16 -0
  11. package/dist/config/dev-server.d.ts +2 -0
  12. package/dist/config/dev-server.js +108 -0
  13. package/dist/config/fs-router.d.ts +37 -0
  14. package/dist/config/fs-router.js +153 -0
  15. package/dist/config/fs-routes/fs-watcher.d.ts +3 -0
  16. package/dist/config/fs-routes/fs-watcher.js +49 -0
  17. package/dist/config/fs-routes/index.d.ts +7 -0
  18. package/dist/config/fs-routes/index.js +109 -0
  19. package/dist/config/fs-routes/router.d.ts +37 -0
  20. package/dist/config/fs-routes/router.js +136 -0
  21. package/dist/config/fs-routes/tree-shake.d.ts +2 -0
  22. package/dist/config/fs-routes/tree-shake.js +357 -0
  23. package/dist/config/index.d.ts +10 -0
  24. package/dist/config/index.js +217 -0
  25. package/dist/config/lazy.d.ts +3 -0
  26. package/dist/config/lazy.js +82 -0
  27. package/dist/config/manifest.d.ts +3 -0
  28. package/dist/config/manifest.js +74 -0
  29. package/dist/config/utils.d.ts +4 -0
  30. package/dist/config/utils.js +6 -0
  31. package/dist/config/vite-utils.d.ts +16 -0
  32. package/dist/config/vite-utils.js +81 -0
  33. package/dist/http/index.d.ts +84 -0
  34. package/dist/http/index.js +131 -0
  35. package/dist/index.d.ts +6 -5
  36. package/dist/index.jsx +5 -5
  37. package/dist/middleware/index.d.ts +6 -6
  38. package/dist/middleware/index.js +39 -0
  39. package/dist/{router/FileRoutes.js → router.jsx} +5 -6
  40. package/dist/server/StartServer.d.ts +3 -3
  41. package/dist/server/StartServer.jsx +9 -63
  42. package/dist/server/assets/index.d.ts +2 -0
  43. package/dist/server/assets/index.js +52 -0
  44. package/dist/server/assets/render.d.ts +19 -0
  45. package/dist/server/{renderAsset.jsx → assets/render.jsx} +1 -1
  46. package/dist/server/collect-styles.d.ts +3 -0
  47. package/dist/server/collect-styles.js +69 -0
  48. package/dist/server/fetchEvent.d.ts +4 -3
  49. package/dist/server/fetchEvent.js +12 -74
  50. package/dist/server/fetchEvent.spec.js +18 -197
  51. package/dist/server/handler.d.ts +6 -12
  52. package/dist/server/handler.js +180 -97
  53. package/dist/server/index.d.ts +8 -6
  54. package/dist/server/index.jsx +14 -6
  55. package/dist/server/manifest/client-manifest.d.ts +9 -0
  56. package/dist/server/manifest/client-manifest.js +6 -0
  57. package/dist/server/manifest/dev-client-manifest.d.ts +4 -0
  58. package/dist/server/manifest/dev-client-manifest.js +24 -0
  59. package/dist/server/manifest/dev-ssr-manifest.d.ts +5 -0
  60. package/dist/server/manifest/dev-ssr-manifest.js +23 -0
  61. package/dist/server/manifest/prod-client-manifest.d.ts +5 -0
  62. package/dist/server/manifest/prod-client-manifest.js +26 -0
  63. package/dist/server/manifest/prod-ssr-manifest.d.ts +6 -0
  64. package/dist/server/manifest/prod-ssr-manifest.js +90 -0
  65. package/dist/server/manifest/ssr-manifest.d.ts +4 -0
  66. package/dist/server/manifest/ssr-manifest.js +6 -0
  67. package/dist/{router → server}/routes.d.ts +5 -10
  68. package/dist/{router → server}/routes.js +35 -36
  69. package/dist/{runtime → server}/server-fns-runtime.d.ts +1 -1
  70. package/dist/{runtime → server}/server-fns-runtime.js +7 -6
  71. package/dist/server/server-functions-handler.d.ts +2 -0
  72. package/dist/{runtime/server-handler.js → server/server-functions-handler.js} +65 -100
  73. package/dist/server/server-runtime.d.ts +3 -0
  74. package/dist/{runtime → server}/server-runtime.js +9 -16
  75. package/dist/server/spa/StartServer.d.ts +1 -1
  76. package/dist/server/spa/StartServer.jsx +7 -9
  77. package/dist/server/spa/handler.d.ts +3 -3
  78. package/dist/server/spa/handler.js +9 -11
  79. package/dist/server/spa/index.d.ts +4 -3
  80. package/dist/server/spa/index.jsx +4 -3
  81. package/dist/server/types.d.ts +7 -9
  82. package/dist/server/util.d.ts +2 -0
  83. package/dist/server/util.js +8 -0
  84. package/dist/shared/ErrorBoundary.jsx +2 -2
  85. package/dist/shared/HttpHeader.jsx +4 -2
  86. package/dist/shared/{HttpStatusCode.jsx → HttpStatusCode.js} +3 -1
  87. package/dist/shared/dev-overlay/DevOverlayDialog.d.ts +2 -2
  88. package/dist/shared/dev-overlay/DevOverlayDialog.jsx +75 -66
  89. package/dist/shared/dev-overlay/createStackFrame.js +3 -3
  90. package/dist/shared/dev-overlay/index.jsx +5 -8
  91. package/dist/shared/lazy.d.ts +3 -0
  92. package/dist/shared/lazy.js +40 -0
  93. package/dist/shared/serverFunction.d.ts +1 -1
  94. package/package.json +45 -67
  95. package/scripts/build.js +15 -0
  96. package/scripts/validate-imports.js +316 -0
  97. package/src/client/StartClient.tsx +34 -0
  98. package/src/client/index.tsx +2 -0
  99. package/src/client/mount.ts +114 -0
  100. package/src/client/spa/index.tsx +9 -0
  101. package/src/config/constants.ts +19 -0
  102. package/src/config/dev-server.ts +149 -0
  103. package/src/config/fs-router.ts +177 -0
  104. package/src/config/fs-routes/fs-watcher.ts +81 -0
  105. package/src/config/fs-routes/index.ts +138 -0
  106. package/src/config/fs-routes/router.ts +169 -0
  107. package/src/config/fs-routes/tree-shake.ts +392 -0
  108. package/src/config/index.ts +236 -0
  109. package/src/config/lazy.ts +100 -0
  110. package/src/config/manifest.ts +84 -0
  111. package/src/config/utils.ts +5 -0
  112. package/src/config/vite-utils.ts +113 -0
  113. package/src/env.d.ts +35 -0
  114. package/src/http/index.ts +253 -0
  115. package/src/index.tsx +19 -0
  116. package/src/internal.d.ts +14 -0
  117. package/src/middleware/index.ts +82 -0
  118. package/src/router.tsx +36 -0
  119. package/src/server/StartServer.tsx +62 -0
  120. package/src/server/assets/index.ts +70 -0
  121. package/src/server/assets/render.tsx +42 -0
  122. package/src/server/collect-styles.ts +94 -0
  123. package/src/server/fetchEvent.spec.ts +89 -0
  124. package/src/server/fetchEvent.ts +36 -0
  125. package/src/server/handler.ts +250 -0
  126. package/src/server/index.tsx +16 -0
  127. package/src/server/manifest/client-manifest.ts +8 -0
  128. package/src/server/manifest/dev-client-manifest.ts +19 -0
  129. package/src/server/manifest/dev-ssr-manifest.ts +21 -0
  130. package/src/server/manifest/prod-client-manifest.ts +18 -0
  131. package/src/server/manifest/prod-ssr-manifest.ts +125 -0
  132. package/src/server/manifest/ssr-manifest.ts +8 -0
  133. package/src/server/routes.ts +138 -0
  134. package/src/server/server-fns-runtime.ts +31 -0
  135. package/src/server/server-functions-handler.ts +364 -0
  136. package/src/server/server-runtime.ts +240 -0
  137. package/src/server/spa/StartServer.tsx +40 -0
  138. package/src/server/spa/handler.ts +29 -0
  139. package/src/server/spa/index.tsx +5 -0
  140. package/src/server/types.ts +82 -0
  141. package/src/server/util.ts +12 -0
  142. package/src/shared/ErrorBoundary.tsx +52 -0
  143. package/src/shared/GET.ts +8 -0
  144. package/src/shared/HttpHeader.tsx +42 -0
  145. package/src/shared/HttpStatusCode.ts +27 -0
  146. package/src/shared/clientOnly.ts +44 -0
  147. package/src/shared/dev-overlay/CodeView.tsx +95 -0
  148. package/src/shared/dev-overlay/DevOverlayDialog.tsx +316 -0
  149. package/src/shared/dev-overlay/createStackFrame.ts +77 -0
  150. package/src/shared/dev-overlay/download.ts +6 -0
  151. package/src/shared/dev-overlay/env.d.ts +4 -0
  152. package/src/shared/dev-overlay/get-source-map.ts +35 -0
  153. package/src/shared/dev-overlay/icons.tsx +288 -0
  154. package/src/shared/dev-overlay/index.tsx +63 -0
  155. package/src/shared/dev-overlay/styles.css +292 -0
  156. package/src/shared/lazy.ts +51 -0
  157. package/src/shared/serverFunction.ts +10 -0
  158. package/src/virtual.d.ts +27 -0
  159. package/tsconfig.json +23 -0
  160. package/vitest.config.ts +9 -0
  161. package/config/fs-router.js +0 -142
  162. package/config/index.d.ts +0 -40
  163. package/config/index.js +0 -304
  164. package/config/server-components.js +0 -106
  165. package/dist/client/islands.d.ts +0 -7
  166. package/dist/client/islands.jsx +0 -10
  167. package/dist/middleware/index.jsx +0 -35
  168. package/dist/router/index.d.ts +0 -1
  169. package/dist/router/index.jsx +0 -2
  170. package/dist/router/lazyRoute.d.ts +0 -6
  171. package/dist/router/lazyRoute.js +0 -53
  172. package/dist/runtime/server-handler.d.ts +0 -2
  173. package/dist/runtime/server-runtime.d.ts +0 -3
  174. package/dist/server/islands/index.d.ts +0 -19
  175. package/dist/server/islands/index.jsx +0 -62
  176. package/dist/server/islands/utils.d.ts +0 -1
  177. package/dist/server/islands/utils.jsx +0 -85
  178. package/dist/server/pageEvent.d.ts +0 -2
  179. package/dist/server/pageEvent.js +0 -56
  180. package/dist/server/renderAsset.d.ts +0 -2
  181. package/env.d.ts +0 -26
  182. /package/dist/{router/FileRoutes.d.ts → router.d.ts} +0 -0
  183. /package/dist/shared/{clientOnly.jsx → clientOnly.js} +0 -0
  184. /package/dist/shared/{serverFunction.jsx → serverFunction.js} +0 -0
@@ -1,62 +1,35 @@
1
+ import * as h3 from "h3";
1
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
- import { cloneEvent, createFetchEvent, getFetchEvent, mergeResponseHeaders } from "./fetchEvent";
3
- vi.mock("vinxi/http", () => ({
4
- getWebRequest: vi.fn(),
5
- getRequestIP: vi.fn(),
6
- getResponseStatus: vi.fn(),
7
- setResponseStatus: vi.fn(),
8
- getResponseStatusText: vi.fn(),
9
- getResponseHeader: vi.fn(),
10
- getResponseHeaders: vi.fn(),
11
- setResponseHeader: vi.fn(),
12
- appendResponseHeader: vi.fn(),
13
- removeResponseHeader: vi.fn()
14
- }));
15
- import * as vinxiHttp from "vinxi/http";
16
- const mockedVinxiHttp = vi.mocked(vinxiHttp);
17
- const createMockH3Event = () => {
18
- const mockRequest = new Request("http://localhost/test");
19
- const mockStatus = 200;
20
- const mockStatusText = "OK";
3
+ import { createFetchEvent, getFetchEvent, mergeResponseHeaders, } from "./fetchEvent.js";
4
+ vi.mock(import("h3"), async (mod) => {
21
5
  return {
22
- node: {
23
- req: {},
24
- res: {
25
- statusCode: mockStatus,
26
- statusMessage: mockStatusText
27
- }
28
- },
29
- context: {},
30
- web: {
31
- request: mockRequest
32
- }
6
+ ...(await mod()),
7
+ getRequestIP: vi.fn(),
33
8
  };
9
+ });
10
+ const mockedH3 = vi.mocked(h3);
11
+ const createMockH3Event = () => {
12
+ const event = new h3.H3Event(new Request("http://localhost/test"));
13
+ event.res.status = 200;
14
+ event.res.statusText = "OK";
15
+ return event;
34
16
  };
35
17
  describe("fetchEvent", () => {
36
18
  let mockH3Event;
37
19
  beforeEach(() => {
38
20
  mockH3Event = createMockH3Event();
39
21
  vi.clearAllMocks();
40
- mockedVinxiHttp.getWebRequest.mockReturnValue(mockH3Event.web?.request);
41
- mockedVinxiHttp.getRequestIP.mockReturnValue("127.0.0.1");
42
- mockedVinxiHttp.getResponseStatus.mockReturnValue(200);
43
- mockedVinxiHttp.setResponseStatus.mockImplementation(() => { });
44
- mockedVinxiHttp.getResponseStatusText.mockReturnValue("OK");
45
- mockedVinxiHttp.getResponseHeader.mockReturnValue(undefined);
46
- mockedVinxiHttp.getResponseHeaders.mockReturnValue({});
47
- mockedVinxiHttp.setResponseHeader.mockImplementation(() => { });
48
- mockedVinxiHttp.appendResponseHeader.mockImplementation(() => { });
49
- mockedVinxiHttp.removeResponseHeader.mockImplementation(() => { });
22
+ mockedH3.getRequestIP.mockReturnValue("127.0.0.1");
50
23
  });
51
24
  describe("createFetchEvent", () => {
52
25
  it("should create a FetchEvent from H3Event", () => {
53
26
  const fetchEvent = createFetchEvent(mockH3Event);
54
27
  expect(fetchEvent).toEqual({
55
- request: mockH3Event.web?.request,
28
+ request: mockH3Event.req,
56
29
  response: expect.any(Object),
57
30
  clientAddress: "127.0.0.1",
58
31
  locals: {},
59
- nativeEvent: mockH3Event
32
+ nativeEvent: mockH3Event,
60
33
  });
61
34
  });
62
35
  it("should create response stub with correct properties", () => {
@@ -66,16 +39,6 @@ describe("fetchEvent", () => {
66
39
  expect(fetchEvent.response).toHaveProperty("headers");
67
40
  });
68
41
  });
69
- describe("cloneEvent", () => {
70
- it("should create a shallow copy of FetchEvent", () => {
71
- const original = createFetchEvent(mockH3Event);
72
- const cloned = cloneEvent(original);
73
- expect(cloned).toEqual(original);
74
- expect(cloned).not.toBe(original);
75
- expect(cloned.request).toBe(original.request);
76
- expect(cloned.response).toBe(original.response);
77
- });
78
- });
79
42
  describe("getFetchEvent", () => {
80
43
  it("should create and cache FetchEvent on first call", () => {
81
44
  const fetchEvent = getFetchEvent(mockH3Event);
@@ -92,153 +55,11 @@ describe("fetchEvent", () => {
92
55
  it("should merge headers from Headers object to H3Event", () => {
93
56
  const headers = new Headers({
94
57
  "content-type": "application/json",
95
- "x-custom": "value"
58
+ "x-custom": "value",
96
59
  });
97
60
  mergeResponseHeaders(mockH3Event, headers);
98
- expect(mockedVinxiHttp.appendResponseHeader).toHaveBeenCalledWith(mockH3Event, "content-type", "application/json");
99
- expect(mockedVinxiHttp.appendResponseHeader).toHaveBeenCalledWith(mockH3Event, "x-custom", "value");
100
- });
101
- });
102
- describe("ResponseStub", () => {
103
- let fetchEvent;
104
- beforeEach(() => {
105
- fetchEvent = createFetchEvent(mockH3Event);
106
- });
107
- describe("status", () => {
108
- it("should get status from H3Event", () => {
109
- expect(fetchEvent.response.status).toBe(200);
110
- });
111
- it("should set status on H3Event", () => {
112
- fetchEvent.response.status = 404;
113
- expect(mockedVinxiHttp.setResponseStatus).toHaveBeenCalledWith(mockH3Event, 404);
114
- });
115
- });
116
- describe("statusText", () => {
117
- it("should get statusText from H3Event", () => {
118
- expect(fetchEvent.response.statusText).toBe("OK");
119
- });
120
- it("should set statusText on H3Event", () => {
121
- fetchEvent.response.statusText = "Not Found";
122
- expect(mockedVinxiHttp.setResponseStatus).toHaveBeenCalledWith(mockH3Event, 200, "Not Found");
123
- });
124
- });
125
- });
126
- describe("HeaderProxy", () => {
127
- let fetchEvent;
128
- beforeEach(() => {
129
- fetchEvent = createFetchEvent(mockH3Event);
130
- });
131
- describe("get", () => {
132
- it("should return null for non-existent header", () => {
133
- expect(fetchEvent.response.headers.get("non-existent")).toBe(null);
134
- });
135
- it("should return string value for single header", () => {
136
- mockedVinxiHttp.getResponseHeader.mockReturnValue("application/json");
137
- expect(fetchEvent.response.headers.get("content-type")).toBe("application/json");
138
- });
139
- it("should join array values with comma", () => {
140
- mockedVinxiHttp.getResponseHeader.mockReturnValue(["text/html", "application/json"]);
141
- expect(fetchEvent.response.headers.get("accept")).toBe("text/html, application/json");
142
- });
143
- });
144
- describe("has", () => {
145
- it("should return false for non-existent header", () => {
146
- mockedVinxiHttp.getResponseHeader.mockReturnValue(undefined);
147
- expect(fetchEvent.response.headers.has("non-existent")).toBe(false);
148
- });
149
- it("should return true for existing header", () => {
150
- mockedVinxiHttp.getResponseHeader.mockReturnValue("application/json");
151
- expect(fetchEvent.response.headers.has("content-type")).toBe(true);
152
- });
153
- });
154
- describe("set", () => {
155
- it("should set header value", () => {
156
- fetchEvent.response.headers.set("content-type", "application/json");
157
- expect(mockedVinxiHttp.setResponseHeader).toHaveBeenCalledWith(mockH3Event, "content-type", "application/json");
158
- });
159
- });
160
- describe("delete", () => {
161
- it("should remove header", () => {
162
- fetchEvent.response.headers.delete("content-type");
163
- expect(mockedVinxiHttp.removeResponseHeader).toHaveBeenCalledWith(mockH3Event, "content-type");
164
- });
165
- });
166
- describe("append", () => {
167
- it("should append header value", () => {
168
- fetchEvent.response.headers.append("x-custom", "value");
169
- expect(mockedVinxiHttp.appendResponseHeader).toHaveBeenCalledWith(mockH3Event, "x-custom", "value");
170
- });
171
- });
172
- describe("getSetCookie", () => {
173
- it("should return array for single cookie", () => {
174
- mockedVinxiHttp.getResponseHeader.mockReturnValue("session=abc123");
175
- expect(fetchEvent.response.headers.getSetCookie()).toEqual(["session=abc123"]);
176
- });
177
- it("should return array for multiple cookies", () => {
178
- mockedVinxiHttp.getResponseHeader.mockReturnValue(["session=abc123", "theme=dark"]);
179
- expect(fetchEvent.response.headers.getSetCookie()).toEqual([
180
- "session=abc123",
181
- "theme=dark"
182
- ]);
183
- });
184
- });
185
- describe("forEach", () => {
186
- it("should iterate over headers", () => {
187
- mockedVinxiHttp.getResponseHeaders.mockReturnValue({
188
- "content-type": "application/json",
189
- "x-custom": ["value1", "value2"]
190
- });
191
- const callback = vi.fn();
192
- fetchEvent.response.headers.forEach(callback);
193
- expect(callback).toHaveBeenCalledWith("application/json", "content-type", expect.any(Object));
194
- expect(callback).toHaveBeenCalledWith("value1, value2", "x-custom", expect.any(Object));
195
- });
196
- });
197
- describe("entries", () => {
198
- it("should return iterator of header entries", () => {
199
- mockedVinxiHttp.getResponseHeaders.mockReturnValue({
200
- "content-type": "application/json",
201
- "x-custom": ["value1", "value2"]
202
- });
203
- const entries = Array.from(fetchEvent.response.headers.entries());
204
- expect(entries).toEqual([
205
- ["content-type", "application/json"],
206
- ["x-custom", "value1, value2"]
207
- ]);
208
- });
209
- });
210
- describe("keys", () => {
211
- it("should return iterator of header keys", () => {
212
- mockedVinxiHttp.getResponseHeaders.mockReturnValue({
213
- "content-type": "application/json",
214
- "x-custom": "value"
215
- });
216
- const keys = Array.from(fetchEvent.response.headers.keys());
217
- expect(keys).toEqual(["content-type", "x-custom"]);
218
- });
219
- });
220
- describe("values", () => {
221
- it("should return iterator of header values", () => {
222
- mockedVinxiHttp.getResponseHeaders.mockReturnValue({
223
- "content-type": "application/json",
224
- "x-custom": ["value1", "value2"]
225
- });
226
- const values = Array.from(fetchEvent.response.headers.values());
227
- expect(values).toEqual(["application/json", "value1, value2"]);
228
- });
229
- });
230
- describe("Symbol.iterator", () => {
231
- it("should be iterable", () => {
232
- mockedVinxiHttp.getResponseHeaders.mockReturnValue({
233
- "content-type": "application/json",
234
- "x-custom": "value"
235
- });
236
- const entries = Array.from(fetchEvent.response.headers);
237
- expect(entries).toEqual([
238
- ["content-type", "application/json"],
239
- ["x-custom", "value"]
240
- ]);
241
- });
61
+ expect(headers.get("content-type")).toBe("application/json");
62
+ expect(headers.get("x-custom")).toBe("value");
242
63
  });
243
64
  });
244
65
  });
@@ -1,12 +1,6 @@
1
- import type { FetchEvent, HandlerOptions, PageEvent, ResponseStub } from "./types";
2
- /**
3
- * Checks if user has set a redirect status in the response.
4
- * If not, falls back to the 302 (temporary redirect)
5
- */
6
- export declare function getExpectedRedirectStatus(response: ResponseStub): number;
7
- export declare function createBaseHandler(fn: (context: PageEvent) => unknown, createPageEvent: (event: FetchEvent) => Promise<PageEvent>, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>), routerLoad?: (event: FetchEvent) => Promise<void>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
8
- /**
9
- *
10
- * Read more: https://docs.solidjs.com/solid-start/reference/server/create-handler
11
- */
12
- export declare function createHandler(fn: (context: PageEvent) => unknown, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>), routerLoad?: (event: FetchEvent) => Promise<void>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
1
+ import { H3 } from "h3";
2
+ import type { JSX } from "solid-js";
3
+ import type { FetchEvent, HandlerOptions, PageEvent } from "./types.ts";
4
+ export declare function createBaseHandler(createPageEvent: (e: FetchEvent) => Promise<PageEvent>, fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>)): H3;
5
+ export declare function createHandler(fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>)): H3;
6
+ export declare function createPageEvent(ctx: FetchEvent): Promise<PageEvent>;
@@ -1,108 +1,182 @@
1
+ import middleware from "solid-start:middleware";
2
+ import { defineHandler, getCookie, H3, redirect, setCookie } from "h3";
3
+ import { join } from "pathe";
1
4
  import { sharedConfig } from "solid-js";
2
- import { renderToStream, renderToString } from "solid-js/web";
3
- import { provideRequestEvent } from "solid-js/web/storage";
4
- import { eventHandler, sendRedirect, setHeader, setResponseStatus } from "vinxi/http";
5
- import { matchAPIRoute } from "../router/routes";
6
- import { getFetchEvent } from "./fetchEvent";
7
- import { createPageEvent } from "./pageEvent";
8
- // according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#redirection_messages
9
- const validRedirectStatuses = new Set([301, 302, 303, 307, 308]);
10
- /**
11
- * Checks if user has set a redirect status in the response.
12
- * If not, falls back to the 302 (temporary redirect)
13
- */
14
- export function getExpectedRedirectStatus(response) {
15
- if (response.status && validRedirectStatuses.has(response.status)) {
16
- return response.status;
17
- }
18
- return 302;
19
- }
20
- export function createBaseHandler(fn, createPageEvent, options = {}, routerLoad) {
21
- return eventHandler({
22
- handler: (e) => {
23
- const event = getFetchEvent(e);
24
- return provideRequestEvent(event, async () => {
25
- if (routerLoad) {
26
- await routerLoad(event);
27
- }
28
- // api
29
- const match = matchAPIRoute(new URL(event.request.url).pathname, event.request.method);
30
- if (match) {
31
- const mod = await match.handler.import();
32
- const fn = event.request.method === "HEAD" ? mod["HEAD"] || mod["GET"] : mod[event.request.method];
33
- event.params = match.params || {};
34
- // @ts-ignore
35
- sharedConfig.context = { event };
36
- const res = await fn(event);
37
- if (res !== undefined)
38
- return res;
39
- if (event.request.method !== "GET") {
40
- throw new Error(`API handler for ${event.request.method} "${event.request.url}" did not return a response.`);
41
- }
42
- if (!match.isPage)
43
- return;
44
- }
45
- // render
46
- const context = await createPageEvent(event);
47
- const resolvedOptions = typeof options == "function" ? await options(context) : { ...options };
48
- const mode = resolvedOptions.mode || "stream";
49
- // @ts-ignore
50
- if (resolvedOptions.nonce)
51
- context.nonce = resolvedOptions.nonce;
52
- if (mode === "sync" || !import.meta.env.START_SSR) {
53
- const html = renderToString(() => {
54
- sharedConfig.context.event = context;
55
- return fn(context);
56
- }, resolvedOptions);
57
- context.complete = true;
58
- if (context.response && context.response.headers.get("Location")) {
59
- const status = getExpectedRedirectStatus(context.response);
60
- return sendRedirect(e, context.response.headers.get("Location"), status);
61
- }
62
- return html;
5
+ import { getRequestEvent, renderToStream, renderToString } from "solid-js/web";
6
+ import { createRoutes } from "../router.jsx";
7
+ import { decorateHandler, decorateMiddleware } from "./fetchEvent.js";
8
+ import { getSsrManifest } from "./manifest/ssr-manifest.js";
9
+ import { matchAPIRoute } from "./routes.js";
10
+ import { handleServerFunction } from "./server-functions-handler.js";
11
+ import { getExpectedRedirectStatus } from "./util.js";
12
+ const SERVER_FN_BASE = "/_server";
13
+ export function createBaseHandler(createPageEvent, fn, options = {}) {
14
+ const handler = defineHandler({
15
+ middleware: middleware.length ? middleware.map(decorateMiddleware) : undefined,
16
+ handler: decorateHandler(async (e) => {
17
+ const event = getRequestEvent();
18
+ const url = new URL(event.request.url);
19
+ const pathname = url.pathname;
20
+ const serverFunctionTest = join(import.meta.env.BASE_URL, SERVER_FN_BASE);
21
+ if (pathname.startsWith(serverFunctionTest)) {
22
+ const serverFnResponse = await handleServerFunction(e);
23
+ if (serverFnResponse instanceof Response)
24
+ return produceResponseWithEventHeaders(serverFnResponse);
25
+ return new Response(serverFnResponse, {
26
+ headers: e.res.headers,
27
+ });
28
+ }
29
+ const match = matchAPIRoute(pathname, event.request.method);
30
+ if (match) {
31
+ const mod = await match.handler.import();
32
+ const fn = event.request.method === "HEAD"
33
+ ? mod["HEAD"] || mod["GET"]
34
+ : mod[event.request.method];
35
+ event.params = match.params || {};
36
+ // @ts-expect-error
37
+ sharedConfig.context = { event };
38
+ const res = await fn(event);
39
+ if (res !== undefined) {
40
+ if (res instanceof Response)
41
+ return produceResponseWithEventHeaders(res);
42
+ return res;
63
43
  }
64
- if (resolvedOptions.onCompleteAll) {
65
- const og = resolvedOptions.onCompleteAll;
66
- resolvedOptions.onCompleteAll = options => {
67
- handleStreamCompleteRedirect(context)(options);
68
- og(options);
69
- };
44
+ if (event.request.method !== "GET") {
45
+ throw new Error(`API handler for ${event.request.method} "${event.request.url}" did not return a response.`);
70
46
  }
71
- else
72
- resolvedOptions.onCompleteAll = handleStreamCompleteRedirect(context);
73
- if (resolvedOptions.onCompleteShell) {
74
- const og = resolvedOptions.onCompleteShell;
75
- resolvedOptions.onCompleteShell = options => {
76
- handleShellCompleteRedirect(context, e)();
77
- og(options);
78
- };
79
- }
80
- else
81
- resolvedOptions.onCompleteShell = handleShellCompleteRedirect(context, e);
82
- const stream = renderToStream(() => {
47
+ if (!match.isPage)
48
+ return;
49
+ }
50
+ const context = await createPageEvent(event);
51
+ const resolvedOptions = typeof options === "function" ? await options(context) : { ...options };
52
+ const mode = resolvedOptions.mode || "stream";
53
+ if (resolvedOptions.nonce)
54
+ context.nonce = resolvedOptions.nonce;
55
+ if (mode === "sync" || !import.meta.env.START_SSR) {
56
+ const html = renderToString(() => {
83
57
  sharedConfig.context.event = context;
84
58
  return fn(context);
85
- }, resolvedOptions);
59
+ });
60
+ context.complete = true;
86
61
  if (context.response && context.response.headers.get("Location")) {
87
62
  const status = getExpectedRedirectStatus(context.response);
88
- return sendRedirect(e, context.response.headers.get("Location"), status);
63
+ return redirect(context.response.headers.get("Location"), status);
89
64
  }
90
- if (mode === "async")
91
- return stream;
92
- // fix cloudflare streaming
93
- const { writable, readable } = new TransformStream();
94
- stream.pipeTo(writable);
95
- return readable;
96
- });
97
- }
65
+ return html;
66
+ }
67
+ if (resolvedOptions.onCompleteAll) {
68
+ const og = resolvedOptions.onCompleteAll;
69
+ resolvedOptions.onCompleteAll = options => {
70
+ handleStreamCompleteRedirect(context)(options);
71
+ og(options);
72
+ };
73
+ }
74
+ else
75
+ resolvedOptions.onCompleteAll = handleStreamCompleteRedirect(context);
76
+ if (resolvedOptions.onCompleteShell) {
77
+ const og = resolvedOptions.onCompleteShell;
78
+ resolvedOptions.onCompleteShell = options => {
79
+ handleShellCompleteRedirect(context, e)();
80
+ og(options);
81
+ };
82
+ }
83
+ else
84
+ resolvedOptions.onCompleteShell = handleShellCompleteRedirect(context, e);
85
+ const _stream = renderToStream(() => {
86
+ sharedConfig.context.event = context;
87
+ return fn(context);
88
+ }, resolvedOptions);
89
+ const stream = _stream; // stream has a hidden 'then' method
90
+ if (context.response && context.response.headers.get("Location")) {
91
+ const status = getExpectedRedirectStatus(context.response);
92
+ return redirect(context.response.headers.get("Location"), status);
93
+ }
94
+ if (mode === "async")
95
+ return await stream;
96
+ delete stream.then;
97
+ // using TransformStream in dev can cause solid-start-dev-server to crash
98
+ // when stream is cancelled
99
+ if (globalThis.USING_SOLID_START_DEV_SERVER)
100
+ return stream;
101
+ // returning stream directly breaks cloudflare workers
102
+ const { writable, readable } = new TransformStream();
103
+ stream.pipeTo(writable);
104
+ return readable;
105
+ }),
98
106
  });
107
+ const app = new H3();
108
+ app.use(handler);
109
+ return app;
110
+ }
111
+ export function createHandler(fn, options = {}) {
112
+ return createBaseHandler(createPageEvent, fn, options);
113
+ }
114
+ export async function createPageEvent(ctx) {
115
+ ctx.response.headers.set("Content-Type", "text/html");
116
+ // const prevPath = ctx.request.headers.get("x-solid-referrer");
117
+ // const mutation = ctx.request.headers.get("x-solid-mutation") === "true";
118
+ const manifest = getSsrManifest(import.meta.env.SSR && import.meta.env.DEV ? "ssr" : "client");
119
+ // Handle Vite build.cssCodeSplit
120
+ // When build.cssCodeSplit is false, a single CSS file is generated with the key style.css
121
+ const mergedCSS = import.meta.env.PROD ? await manifest.getAssets('style.css') : [];
122
+ const assets = [
123
+ ...mergedCSS,
124
+ ...(await manifest.getAssets(import.meta.env.START_CLIENT_ENTRY)),
125
+ ...(await manifest.getAssets(import.meta.env.START_APP_ENTRY)),
126
+ // ...(import.meta.env.START_ISLANDS
127
+ // ? (await serverManifest.inputs[serverManifest.handler]!.assets()).filter(
128
+ // s => (s as any).attrs.rel !== "modulepreload"
129
+ // )
130
+ // : [])
131
+ ];
132
+ const pageEvent = Object.assign(ctx, {
133
+ assets,
134
+ router: {
135
+ submission: initFromFlash(ctx),
136
+ },
137
+ routes: createRoutes(),
138
+ // prevUrl: prevPath || "",
139
+ // mutation: mutation,
140
+ // $type: FETCH_EVENT,
141
+ complete: false,
142
+ $islands: new Set(),
143
+ });
144
+ return pageEvent;
145
+ }
146
+ function initFromFlash(ctx) {
147
+ const flash = getCookie(ctx.nativeEvent, "flash");
148
+ if (!flash)
149
+ return;
150
+ try {
151
+ const param = JSON.parse(flash);
152
+ if (!param || !param.result)
153
+ return;
154
+ const input = [
155
+ ...param.input.slice(0, -1),
156
+ new Map(param.input[param.input.length - 1]),
157
+ ];
158
+ const result = param.error ? new Error(param.result) : param.result;
159
+ return {
160
+ input,
161
+ url: param.url,
162
+ pending: false,
163
+ result: param.thrown ? undefined : result,
164
+ error: param.thrown ? result : undefined,
165
+ };
166
+ }
167
+ catch (e) {
168
+ console.error(e);
169
+ }
170
+ finally {
171
+ setCookie(ctx.nativeEvent, "flash", "", { maxAge: 0 });
172
+ }
99
173
  }
100
174
  function handleShellCompleteRedirect(context, e) {
101
175
  return () => {
102
176
  if (context.response && context.response.headers.get("Location")) {
103
177
  const status = getExpectedRedirectStatus(context.response);
104
- setResponseStatus(e, status);
105
- setHeader(e, "Location", context.response.headers.get("Location"));
178
+ e.res.status = status;
179
+ e.res.headers.set("Location", context.response.headers.get("Location"));
106
180
  }
107
181
  };
108
182
  }
@@ -113,10 +187,19 @@ function handleStreamCompleteRedirect(context) {
113
187
  to && write(`<script>window.location="${to}"</script>`);
114
188
  };
115
189
  }
116
- /**
117
- *
118
- * Read more: https://docs.solidjs.com/solid-start/reference/server/create-handler
119
- */
120
- export function createHandler(fn, options, routerLoad) {
121
- return createBaseHandler(fn, createPageEvent, options, routerLoad);
190
+ function produceResponseWithEventHeaders(res) {
191
+ const event = getRequestEvent();
192
+ let ret = res;
193
+ // Response.redirect returns an immutable value, so we clone on any redirect just in case
194
+ if (300 <= res.status && res.status < 400) {
195
+ ret = new Response(res.body, {
196
+ status: res.status,
197
+ statusText: res.statusText,
198
+ headers: Object.fromEntries(res.headers.entries())
199
+ });
200
+ }
201
+ for (const [name, value] of event.response.headers) {
202
+ ret.headers.set(name, value);
203
+ }
204
+ return ret;
122
205
  }
@@ -1,6 +1,8 @@
1
- export { createHandler } from "./handler";
2
- export { StartServer } from "./StartServer";
3
- export type { APIEvent, APIHandler, Asset, ContextMatches, DocumentComponentProps, FetchEvent, HandlerOptions, PageEvent, ResponseStub, ServerFunctionMeta } from "./types";
4
- import { getServerFunctionMeta as getServerFunctionMeta_ } from "../shared/serverFunction";
5
- /** @deprecated */
6
- export declare const getServerFunctionMeta: typeof getServerFunctionMeta_;
1
+ export { default as lazy } from "../shared/lazy.ts";
2
+ export { getServerFunctionMeta } from "../shared/serverFunction.ts";
3
+ export { StartServer } from "./StartServer.tsx";
4
+ export { createHandler } from "./handler.ts";
5
+ /**
6
+ * Checks if user has set a redirect status in the response.
7
+ * If not, falls back to the 302 (temporary redirect)
8
+ */
@@ -1,6 +1,14 @@
1
- // @refresh skip
2
- export { createHandler } from "./handler";
3
- export { StartServer } from "./StartServer";
4
- import { getServerFunctionMeta as getServerFunctionMeta_ } from "../shared/serverFunction";
5
- /** @deprecated */
6
- export const getServerFunctionMeta = getServerFunctionMeta_;
1
+ export { default as lazy } from "../shared/lazy.js";
2
+ export { getServerFunctionMeta } from "../shared/serverFunction.js";
3
+ export { StartServer } from "./StartServer.jsx";
4
+ export { createHandler } from "./handler.js";
5
+ /**
6
+ * Checks if user has set a redirect status in the response.
7
+ * If not, falls back to the 302 (temporary redirect)
8
+ */
9
+ // export function getExpectedRedirectStatus(response: ResponseStub): number {
10
+ // if (response.status && validRedirectStatuses.has(response.status)) {
11
+ // return response.status;
12
+ // }
13
+ // return 302;
14
+ // }
@@ -0,0 +1,9 @@
1
+ export declare function getClientManifest(): {
2
+ import(id: string): Promise<any>;
3
+ getAssets(id: string): Promise<any[]>;
4
+ } | {
5
+ import(id: string): Promise<any>;
6
+ getAssets(id: string): Promise<any>;
7
+ json(): Promise<any>;
8
+ };
9
+ export { getClientManifest as getManifest };
@@ -0,0 +1,6 @@
1
+ import { getClientDevManifest } from "./dev-client-manifest.js";
2
+ import { getClientProdManifest } from "./prod-client-manifest.js";
3
+ export function getClientManifest() {
4
+ return import.meta.env.DEV ? getClientDevManifest() : getClientProdManifest();
5
+ }
6
+ export { getClientManifest as getManifest };
@@ -0,0 +1,4 @@
1
+ export declare function getClientDevManifest(): {
2
+ import(id: string): Promise<any>;
3
+ getAssets(id: string): Promise<any[]>;
4
+ };