@fragno-dev/core 0.1.7 → 0.1.9

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 (183) hide show
  1. package/.turbo/turbo-build.log +131 -64
  2. package/CHANGELOG.md +19 -0
  3. package/dist/api/api.d.ts +38 -2
  4. package/dist/api/api.d.ts.map +1 -0
  5. package/dist/api/api.js +9 -2
  6. package/dist/api/api.js.map +1 -0
  7. package/dist/api/bind-services.d.ts +6 -0
  8. package/dist/api/bind-services.d.ts.map +1 -0
  9. package/dist/api/bind-services.js +20 -0
  10. package/dist/api/bind-services.js.map +1 -0
  11. package/dist/api/error.d.ts +26 -0
  12. package/dist/api/error.d.ts.map +1 -0
  13. package/dist/{api-DngJDcmO.js → api/error.js} +2 -8
  14. package/dist/api/error.js.map +1 -0
  15. package/dist/api/fragment-definition-builder.d.ts +313 -0
  16. package/dist/api/fragment-definition-builder.d.ts.map +1 -0
  17. package/dist/api/fragment-definition-builder.js +326 -0
  18. package/dist/api/fragment-definition-builder.js.map +1 -0
  19. package/dist/api/fragment-instantiator.d.ts +216 -0
  20. package/dist/api/fragment-instantiator.d.ts.map +1 -0
  21. package/dist/api/fragment-instantiator.js +487 -0
  22. package/dist/api/fragment-instantiator.js.map +1 -0
  23. package/dist/api/fragno-response.d.ts +30 -0
  24. package/dist/api/fragno-response.d.ts.map +1 -0
  25. package/dist/api/fragno-response.js +73 -0
  26. package/dist/api/fragno-response.js.map +1 -0
  27. package/dist/api/internal/path.d.ts +50 -0
  28. package/dist/api/internal/path.d.ts.map +1 -0
  29. package/dist/api/internal/path.js +76 -0
  30. package/dist/api/internal/path.js.map +1 -0
  31. package/dist/api/internal/response-stream.d.ts +43 -0
  32. package/dist/api/internal/response-stream.d.ts.map +1 -0
  33. package/dist/api/internal/response-stream.js +81 -0
  34. package/dist/api/internal/response-stream.js.map +1 -0
  35. package/dist/api/internal/route.js +10 -0
  36. package/dist/api/internal/route.js.map +1 -0
  37. package/dist/api/mutable-request-state.d.ts +82 -0
  38. package/dist/api/mutable-request-state.d.ts.map +1 -0
  39. package/dist/api/mutable-request-state.js +97 -0
  40. package/dist/api/mutable-request-state.js.map +1 -0
  41. package/dist/api/request-context-storage.d.ts +42 -0
  42. package/dist/api/request-context-storage.d.ts.map +1 -0
  43. package/dist/api/request-context-storage.js +43 -0
  44. package/dist/api/request-context-storage.js.map +1 -0
  45. package/dist/api/request-input-context.d.ts +89 -0
  46. package/dist/api/request-input-context.d.ts.map +1 -0
  47. package/dist/api/request-input-context.js +118 -0
  48. package/dist/api/request-input-context.js.map +1 -0
  49. package/dist/api/request-middleware.d.ts +50 -0
  50. package/dist/api/request-middleware.d.ts.map +1 -0
  51. package/dist/api/request-middleware.js +83 -0
  52. package/dist/api/request-middleware.js.map +1 -0
  53. package/dist/api/request-output-context.d.ts +41 -0
  54. package/dist/api/request-output-context.d.ts.map +1 -0
  55. package/dist/api/request-output-context.js +119 -0
  56. package/dist/api/request-output-context.js.map +1 -0
  57. package/dist/api/route-handler-input-options.d.ts +21 -0
  58. package/dist/api/route-handler-input-options.d.ts.map +1 -0
  59. package/dist/api/route.d.ts +54 -3
  60. package/dist/api/route.d.ts.map +1 -0
  61. package/dist/api/route.js +29 -2
  62. package/dist/api/route.js.map +1 -0
  63. package/dist/api/shared-types.d.ts +47 -0
  64. package/dist/api/shared-types.d.ts.map +1 -0
  65. package/dist/api/shared-types.js +1 -0
  66. package/dist/client/client-error.d.ts +60 -0
  67. package/dist/client/client-error.d.ts.map +1 -0
  68. package/dist/client/client-error.js +92 -0
  69. package/dist/client/client-error.js.map +1 -0
  70. package/dist/client/client.d.ts +210 -4
  71. package/dist/client/client.d.ts.map +1 -0
  72. package/dist/client/client.js +397 -6
  73. package/dist/client/client.js.map +1 -0
  74. package/dist/client/client.svelte.d.ts +5 -3
  75. package/dist/client/client.svelte.d.ts.map +1 -1
  76. package/dist/client/client.svelte.js +1 -5
  77. package/dist/client/client.svelte.js.map +1 -1
  78. package/dist/client/internal/fetcher-merge.js +36 -0
  79. package/dist/client/internal/fetcher-merge.js.map +1 -0
  80. package/dist/client/internal/ndjson-streaming.js +139 -0
  81. package/dist/client/internal/ndjson-streaming.js.map +1 -0
  82. package/dist/client/react.d.ts +5 -3
  83. package/dist/client/react.d.ts.map +1 -1
  84. package/dist/client/react.js +3 -5
  85. package/dist/client/react.js.map +1 -1
  86. package/dist/client/solid.d.ts +5 -3
  87. package/dist/client/solid.d.ts.map +1 -1
  88. package/dist/client/solid.js +2 -5
  89. package/dist/client/solid.js.map +1 -1
  90. package/dist/client/vanilla.d.ts +5 -3
  91. package/dist/client/vanilla.d.ts.map +1 -1
  92. package/dist/client/vanilla.js +2 -43
  93. package/dist/client/vanilla.js.map +1 -1
  94. package/dist/client/vue.d.ts +5 -3
  95. package/dist/client/vue.d.ts.map +1 -1
  96. package/dist/client/vue.js +1 -5
  97. package/dist/client/vue.js.map +1 -1
  98. package/dist/http/http-status.d.ts +26 -0
  99. package/dist/http/http-status.d.ts.map +1 -0
  100. package/dist/integrations/react-ssr.js +1 -1
  101. package/dist/internal/symbols.d.ts +9 -0
  102. package/dist/internal/symbols.d.ts.map +1 -0
  103. package/dist/internal/symbols.js +10 -0
  104. package/dist/internal/symbols.js.map +1 -0
  105. package/dist/mod-client.d.ts +36 -0
  106. package/dist/mod-client.d.ts.map +1 -0
  107. package/dist/mod-client.js +21 -0
  108. package/dist/mod-client.js.map +1 -0
  109. package/dist/mod.d.ts +7 -4
  110. package/dist/mod.js +4 -6
  111. package/dist/request/request.d.ts +4 -0
  112. package/dist/request/request.js +5 -0
  113. package/dist/test/test.d.ts +62 -35
  114. package/dist/test/test.d.ts.map +1 -1
  115. package/dist/test/test.js +75 -40
  116. package/dist/test/test.js.map +1 -1
  117. package/dist/util/async.js +40 -0
  118. package/dist/util/async.js.map +1 -0
  119. package/dist/util/content-type.js +49 -0
  120. package/dist/util/content-type.js.map +1 -0
  121. package/dist/util/nanostores.js +31 -0
  122. package/dist/util/nanostores.js.map +1 -0
  123. package/dist/{ssr-BByDVfFD.js → util/ssr.js} +2 -2
  124. package/dist/util/ssr.js.map +1 -0
  125. package/dist/util/types-util.d.ts +8 -0
  126. package/dist/util/types-util.d.ts.map +1 -0
  127. package/package.json +19 -12
  128. package/src/api/api.ts +41 -6
  129. package/src/api/bind-services.ts +42 -0
  130. package/src/api/fragment-definition-builder.extend.test.ts +810 -0
  131. package/src/api/fragment-definition-builder.test.ts +499 -0
  132. package/src/api/fragment-definition-builder.ts +1088 -0
  133. package/src/api/fragment-instantiator.test.ts +1488 -0
  134. package/src/api/fragment-instantiator.ts +1053 -0
  135. package/src/api/fragment-services.test.ts +727 -0
  136. package/src/api/request-context-storage.ts +64 -0
  137. package/src/api/request-middleware.test.ts +301 -225
  138. package/src/api/route.test.ts +87 -1
  139. package/src/api/route.ts +345 -24
  140. package/src/api/shared-types.ts +43 -0
  141. package/src/client/client-builder.test.ts +23 -23
  142. package/src/client/client.ssr.test.ts +3 -3
  143. package/src/client/client.svelte.test.ts +15 -15
  144. package/src/client/client.test.ts +22 -22
  145. package/src/client/client.ts +72 -12
  146. package/src/client/internal/fetcher-merge.ts +1 -1
  147. package/src/client/react.test.ts +2 -2
  148. package/src/client/solid.test.ts +2 -2
  149. package/src/client/vanilla.test.ts +2 -2
  150. package/src/client/vue.test.ts +2 -2
  151. package/src/internal/symbols.ts +5 -0
  152. package/src/mod-client.ts +59 -0
  153. package/src/mod.ts +26 -9
  154. package/src/request/request.ts +8 -0
  155. package/src/test/test.test.ts +200 -381
  156. package/src/test/test.ts +190 -117
  157. package/tsdown.config.ts +8 -5
  158. package/dist/api/fragment-builder.d.ts +0 -4
  159. package/dist/api/fragment-builder.js +0 -3
  160. package/dist/api/fragment-instantiation.d.ts +0 -4
  161. package/dist/api/fragment-instantiation.js +0 -6
  162. package/dist/api-BWN97TOr.d.ts +0 -377
  163. package/dist/api-BWN97TOr.d.ts.map +0 -1
  164. package/dist/api-DngJDcmO.js.map +0 -1
  165. package/dist/client-C5LsYHEI.js +0 -782
  166. package/dist/client-C5LsYHEI.js.map +0 -1
  167. package/dist/fragment-builder-DOnCVBqc.js +0 -47
  168. package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
  169. package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
  170. package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
  171. package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
  172. package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
  173. package/dist/request-output-context-CdIjwmEN.js +0 -320
  174. package/dist/request-output-context-CdIjwmEN.js.map +0 -1
  175. package/dist/route-Bl9Zr1Yv.d.ts +0 -26
  176. package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
  177. package/dist/route-C5Uryylh.js +0 -21
  178. package/dist/route-C5Uryylh.js.map +0 -1
  179. package/dist/ssr-BByDVfFD.js.map +0 -1
  180. package/src/api/fragment-builder.ts +0 -80
  181. package/src/api/fragment-instantiation.test.ts +0 -460
  182. package/src/api/fragment-instantiation.ts +0 -499
  183. package/src/api/fragment.test.ts +0 -537
@@ -1,499 +0,0 @@
1
- import type { StandardSchemaV1 } from "@standard-schema/spec";
2
- import { type FragnoRouteConfig, type HTTPMethod } from "./api";
3
- import { FragnoApiError } from "./error";
4
- import { getMountRoute } from "./internal/route";
5
- import { addRoute, createRouter, findRoute } from "rou3";
6
- import { RequestInputContext, type RequestBodyType } from "./request-input-context";
7
- import type { ExtractPathParams } from "./internal/path";
8
- import { RequestOutputContext } from "./request-output-context";
9
- import {
10
- type AnyFragnoRouteConfig,
11
- type AnyRouteOrFactory,
12
- type FlattenRouteFactories,
13
- resolveRouteFactories,
14
- } from "./route";
15
- import {
16
- RequestMiddlewareInputContext,
17
- RequestMiddlewareOutputContext,
18
- type FragnoMiddlewareCallback,
19
- } from "./request-middleware";
20
- import type { FragmentDefinition } from "./fragment-builder";
21
- import { MutableRequestState } from "./mutable-request-state";
22
- import type { RouteHandlerInputOptions } from "./route-handler-input-options";
23
- import type { ExtractRouteByPath, ExtractRoutePath } from "../client/client";
24
- import { type FragnoResponse, parseFragnoResponse } from "./fragno-response";
25
- import type { InferOrUnknown } from "../util/types-util";
26
-
27
- export interface FragnoPublicConfig {
28
- mountRoute?: string;
29
- }
30
-
31
- export type FetcherConfig =
32
- | { type: "options"; options: RequestInit }
33
- | { type: "function"; fetcher: typeof fetch };
34
-
35
- export interface FragnoPublicClientConfig {
36
- mountRoute?: string;
37
- baseUrl?: string;
38
- fetcherConfig?: FetcherConfig;
39
- }
40
-
41
- type AstroHandlers = {
42
- ALL: (req: Request) => Promise<Response>;
43
- };
44
-
45
- type ReactRouterHandlers = {
46
- loader: (args: { request: Request }) => Promise<Response>;
47
- action: (args: { request: Request }) => Promise<Response>;
48
- };
49
-
50
- type SolidStartHandlers = {
51
- GET: (args: { request: Request }) => Promise<Response>;
52
- POST: (args: { request: Request }) => Promise<Response>;
53
- PUT: (args: { request: Request }) => Promise<Response>;
54
- DELETE: (args: { request: Request }) => Promise<Response>;
55
- PATCH: (args: { request: Request }) => Promise<Response>;
56
- HEAD: (args: { request: Request }) => Promise<Response>;
57
- OPTIONS: (args: { request: Request }) => Promise<Response>;
58
- };
59
-
60
- type TanStackStartHandlers = SolidStartHandlers;
61
-
62
- type StandardHandlers = {
63
- GET: (req: Request) => Promise<Response>;
64
- POST: (req: Request) => Promise<Response>;
65
- PUT: (req: Request) => Promise<Response>;
66
- DELETE: (req: Request) => Promise<Response>;
67
- PATCH: (req: Request) => Promise<Response>;
68
- HEAD: (req: Request) => Promise<Response>;
69
- OPTIONS: (req: Request) => Promise<Response>;
70
- };
71
-
72
- type HandlersByFramework = {
73
- astro: AstroHandlers;
74
- "react-router": ReactRouterHandlers;
75
- "next-js": StandardHandlers;
76
- "svelte-kit": StandardHandlers;
77
- "solid-start": SolidStartHandlers;
78
- "tanstack-start": TanStackStartHandlers;
79
- };
80
-
81
- // Not actually a symbol, since we might be dealing with multiple instances of this code.
82
- export const instantiatedFragmentFakeSymbol = "$fragno-instantiated-fragment" as const;
83
-
84
- type FullstackFrameworks = keyof HandlersByFramework;
85
-
86
- export interface FragnoInstantiatedFragment<
87
- TRoutes extends readonly AnyFragnoRouteConfig[] = [],
88
- TDeps = {},
89
- TServices extends Record<string, unknown> = Record<string, unknown>,
90
- TAdditionalContext extends Record<string, unknown> = {},
91
- > {
92
- [instantiatedFragmentFakeSymbol]: typeof instantiatedFragmentFakeSymbol;
93
-
94
- config: FragnoFragmentSharedConfig<TRoutes>;
95
- deps: TDeps;
96
- services: TServices;
97
- additionalContext?: TAdditionalContext;
98
- handlersFor: <T extends FullstackFrameworks>(framework: T) => HandlersByFramework[T];
99
- handler: (req: Request) => Promise<Response>;
100
- mountRoute: string;
101
- callRoute: <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(
102
- method: TMethod,
103
- path: TPath,
104
- inputOptions?: RouteHandlerInputOptions<
105
- TPath,
106
- ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
107
- >,
108
- ) => Promise<
109
- FragnoResponse<
110
- InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>["outputSchema"]>>
111
- >
112
- >;
113
- callRouteRaw: <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(
114
- method: TMethod,
115
- path: TPath,
116
- inputOptions?: RouteHandlerInputOptions<
117
- TPath,
118
- ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
119
- >,
120
- ) => Promise<Response>;
121
- withMiddleware: (
122
- handler: FragnoMiddlewareCallback<TRoutes, TDeps, TServices>,
123
- ) => FragnoInstantiatedFragment<TRoutes, TDeps, TServices, TAdditionalContext>;
124
- }
125
-
126
- export interface FragnoFragmentSharedConfig<
127
- TRoutes extends readonly FragnoRouteConfig<
128
- HTTPMethod,
129
- string,
130
- StandardSchemaV1 | undefined,
131
- StandardSchemaV1 | undefined,
132
- string,
133
- string
134
- >[],
135
- > {
136
- name: string;
137
- routes: TRoutes;
138
- }
139
-
140
- export type AnyFragnoFragmentSharedConfig = FragnoFragmentSharedConfig<
141
- readonly AnyFragnoRouteConfig[]
142
- >;
143
-
144
- export function createFragment<
145
- const TConfig,
146
- const TDeps,
147
- const TServices extends Record<string, unknown>,
148
- const TRoutesOrFactories extends readonly AnyRouteOrFactory[],
149
- const TAdditionalContext extends Record<string, unknown>,
150
- const TOptions extends FragnoPublicConfig,
151
- >(
152
- fragmentBuilder: {
153
- definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;
154
- $requiredOptions: TOptions;
155
- },
156
- config: TConfig,
157
- routesOrFactories: TRoutesOrFactories,
158
- options: TOptions,
159
- ): FragnoInstantiatedFragment<
160
- FlattenRouteFactories<TRoutesOrFactories>,
161
- TDeps,
162
- TServices,
163
- TAdditionalContext
164
- > {
165
- type TRoutes = FlattenRouteFactories<TRoutesOrFactories>;
166
-
167
- const definition = fragmentBuilder.definition;
168
-
169
- const dependencies = definition.dependencies?.(config, options) ?? ({} as TDeps);
170
- const services = definition.services?.(config, options, dependencies) ?? ({} as TServices);
171
-
172
- const context = { config, deps: dependencies, services };
173
- const routes = resolveRouteFactories(context, routesOrFactories);
174
-
175
- const mountRoute = getMountRoute({
176
- name: definition.name,
177
- mountRoute: options.mountRoute,
178
- });
179
-
180
- const router =
181
- createRouter<
182
- FragnoRouteConfig<
183
- HTTPMethod,
184
- string,
185
- StandardSchemaV1 | undefined,
186
- StandardSchemaV1 | undefined,
187
- string,
188
- string
189
- >
190
- >();
191
-
192
- let middlewareHandler:
193
- | FragnoMiddlewareCallback<FlattenRouteFactories<TRoutesOrFactories>, TDeps, TServices>
194
- | undefined;
195
-
196
- for (const routeConfig of routes) {
197
- addRoute(router, routeConfig.method.toUpperCase(), routeConfig.path, routeConfig);
198
- }
199
-
200
- const fragment: FragnoInstantiatedFragment<
201
- FlattenRouteFactories<TRoutesOrFactories>,
202
- TDeps,
203
- TServices,
204
- TAdditionalContext & TOptions
205
- > = {
206
- [instantiatedFragmentFakeSymbol]: instantiatedFragmentFakeSymbol,
207
- mountRoute,
208
- config: {
209
- name: definition.name,
210
- routes,
211
- },
212
- services,
213
- deps: dependencies,
214
- additionalContext: {
215
- ...definition.additionalContext,
216
- ...options,
217
- } as TAdditionalContext & TOptions,
218
- withMiddleware: (handler) => {
219
- if (middlewareHandler) {
220
- throw new Error("Middleware already set");
221
- }
222
-
223
- middlewareHandler = handler;
224
-
225
- return fragment;
226
- },
227
- callRoute: async <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(
228
- method: TMethod,
229
- path: TPath,
230
- inputOptions?: RouteHandlerInputOptions<
231
- TPath,
232
- ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
233
- >,
234
- ): Promise<
235
- FragnoResponse<
236
- InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>["outputSchema"]>>
237
- >
238
- > => {
239
- const response = await fragment.callRouteRaw(method, path, inputOptions);
240
- return parseFragnoResponse(response);
241
- },
242
- callRouteRaw: async <
243
- TMethod extends HTTPMethod,
244
- TPath extends ExtractRoutePath<TRoutes, TMethod>,
245
- >(
246
- method: TMethod,
247
- path: TPath,
248
- inputOptions?: RouteHandlerInputOptions<
249
- TPath,
250
- ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
251
- >,
252
- ): Promise<Response> => {
253
- // Find the route configuration
254
- const route = routes.find((r) => r.method === method && r.path === path);
255
-
256
- if (!route) {
257
- return Response.json(
258
- {
259
- error: `Route ${method} ${path} not found`,
260
- code: "ROUTE_NOT_FOUND",
261
- },
262
- { status: 404 },
263
- );
264
- }
265
-
266
- const {
267
- pathParams = {} as ExtractPathParams<TPath>,
268
- body,
269
- query,
270
- headers,
271
- } = inputOptions || {};
272
-
273
- // Convert query to URLSearchParams if needed
274
- const searchParams =
275
- query instanceof URLSearchParams
276
- ? query
277
- : query
278
- ? new URLSearchParams(query)
279
- : new URLSearchParams();
280
-
281
- // Convert headers to Headers if needed
282
- const requestHeaders =
283
- headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();
284
-
285
- // Construct RequestInputContext
286
- const inputContext = new RequestInputContext({
287
- path: route.path,
288
- method: route.method,
289
- pathParams,
290
- searchParams,
291
- headers: requestHeaders,
292
- parsedBody: body,
293
- inputSchema: route.inputSchema,
294
- shouldValidateInput: true, // Enable validation for production use
295
- });
296
-
297
- // Construct RequestOutputContext
298
- const outputContext = new RequestOutputContext(route.outputSchema);
299
-
300
- // Call the route handler
301
- try {
302
- const response = await route.handler(inputContext, outputContext);
303
- return response;
304
- } catch (error) {
305
- console.error("Error in callRoute handler", error);
306
-
307
- if (error instanceof FragnoApiError) {
308
- return error.toResponse();
309
- }
310
-
311
- return Response.json(
312
- { error: "Internal server error", code: "INTERNAL_SERVER_ERROR" },
313
- { status: 500 },
314
- );
315
- }
316
- },
317
- handlersFor: <T extends FullstackFrameworks>(framework: T): HandlersByFramework[T] => {
318
- const handler = fragment.handler;
319
-
320
- // LLMs hallucinate these values sometimes, solution isn't obvious so we throw this error
321
- // @ts-expect-error TS2367
322
- if (framework === "h3" || framework === "nuxt") {
323
- throw new Error(`To get handlers for h3, use the 'fromWebHandler' utility function:
324
- import { fromWebHandler } from "h3";
325
- export default fromWebHandler(myFragment().handler);`);
326
- }
327
- const allHandlers = {
328
- astro: { ALL: handler },
329
- "react-router": {
330
- loader: ({ request }: { request: Request }) => handler(request),
331
- action: ({ request }: { request: Request }) => handler(request),
332
- },
333
- "next-js": {
334
- GET: handler,
335
- POST: handler,
336
- PUT: handler,
337
- DELETE: handler,
338
- PATCH: handler,
339
- HEAD: handler,
340
- OPTIONS: handler,
341
- },
342
- "svelte-kit": {
343
- GET: handler,
344
- POST: handler,
345
- PUT: handler,
346
- DELETE: handler,
347
- PATCH: handler,
348
- HEAD: handler,
349
- OPTIONS: handler,
350
- },
351
- "solid-start": {
352
- GET: ({ request }: { request: Request }) => handler(request),
353
- POST: ({ request }: { request: Request }) => handler(request),
354
- PUT: ({ request }: { request: Request }) => handler(request),
355
- DELETE: ({ request }: { request: Request }) => handler(request),
356
- PATCH: ({ request }: { request: Request }) => handler(request),
357
- HEAD: ({ request }: { request: Request }) => handler(request),
358
- OPTIONS: ({ request }: { request: Request }) => handler(request),
359
- },
360
- "tanstack-start": {
361
- GET: ({ request }: { request: Request }) => handler(request),
362
- POST: ({ request }: { request: Request }) => handler(request),
363
- PUT: ({ request }: { request: Request }) => handler(request),
364
- DELETE: ({ request }: { request: Request }) => handler(request),
365
- PATCH: ({ request }: { request: Request }) => handler(request),
366
- HEAD: ({ request }: { request: Request }) => handler(request),
367
- OPTIONS: ({ request }: { request: Request }) => handler(request),
368
- },
369
- } satisfies HandlersByFramework;
370
-
371
- return allHandlers[framework];
372
- },
373
- handler: async (req: Request) => {
374
- const url = new URL(req.url);
375
- const pathname = url.pathname;
376
-
377
- const matchRoute = pathname.startsWith(mountRoute) ? pathname.slice(mountRoute.length) : null;
378
-
379
- if (matchRoute === null) {
380
- return Response.json(
381
- {
382
- error:
383
- `Fragno: Route for '${definition.name}' not found. Is the fragment mounted on the right route? ` +
384
- `Expecting: '${mountRoute}'.`,
385
- code: "ROUTE_NOT_FOUND",
386
- },
387
- { status: 404 },
388
- );
389
- }
390
-
391
- const route = findRoute(router, req.method, matchRoute);
392
-
393
- if (!route) {
394
- return Response.json(
395
- { error: `Fragno: Route for '${definition.name}' not found`, code: "ROUTE_NOT_FOUND" },
396
- { status: 404 },
397
- );
398
- }
399
-
400
- const { handler, inputSchema, outputSchema, path } = route.data;
401
-
402
- const outputContext = new RequestOutputContext(outputSchema);
403
-
404
- // Create mutable request state that can be modified by middleware
405
- // Clone the request to read body as both text and JSON without consuming original stream
406
- let requestBody: RequestBodyType = undefined;
407
- let rawBody: string | undefined = undefined;
408
-
409
- if (req.body instanceof ReadableStream) {
410
- // Clone request to make sure we don't consume body stream
411
- const clonedReq = req.clone();
412
-
413
- // Get raw text
414
- rawBody = await clonedReq.text();
415
-
416
- // Parse JSON if body is not empty
417
- if (rawBody) {
418
- try {
419
- requestBody = JSON.parse(rawBody);
420
- } catch {
421
- // If JSON parsing fails, keep body as undefined
422
- // This handles cases where body is not JSON
423
- requestBody = undefined;
424
- }
425
- }
426
- }
427
-
428
- const requestState = new MutableRequestState({
429
- pathParams: route.params ?? {},
430
- searchParams: url.searchParams,
431
- body: requestBody,
432
- headers: new Headers(req.headers),
433
- });
434
-
435
- if (middlewareHandler) {
436
- const middlewareInputContext = new RequestMiddlewareInputContext(routes, {
437
- method: req.method as HTTPMethod,
438
- path,
439
- request: req,
440
- state: requestState,
441
- });
442
-
443
- const middlewareOutputContext = new RequestMiddlewareOutputContext(dependencies, services);
444
-
445
- try {
446
- const middlewareResult = await middlewareHandler(
447
- middlewareInputContext,
448
- middlewareOutputContext,
449
- );
450
- if (middlewareResult !== undefined) {
451
- return middlewareResult;
452
- }
453
- } catch (error) {
454
- console.error("Error in middleware", error);
455
-
456
- if (error instanceof FragnoApiError) {
457
- // TODO: If a validation error occurs in middleware (when calling `await input.valid()`)
458
- // the processing is short-circuited and a potential `catch` block around the call
459
- // to `input.valid()` in the actual handler will not be executed.
460
- return error.toResponse();
461
- }
462
-
463
- return Response.json(
464
- { error: "Internal server error", code: "INTERNAL_SERVER_ERROR" },
465
- { status: 500 },
466
- );
467
- }
468
- }
469
-
470
- const inputContext = await RequestInputContext.fromRequest({
471
- request: req,
472
- method: req.method,
473
- path,
474
- pathParams: (route.params ?? {}) as ExtractPathParams<typeof path>,
475
- inputSchema,
476
- state: requestState,
477
- rawBody,
478
- });
479
-
480
- try {
481
- const result = await handler(inputContext, outputContext);
482
- return result;
483
- } catch (error) {
484
- console.error("Error in handler", error);
485
-
486
- if (error instanceof FragnoApiError) {
487
- return error.toResponse();
488
- }
489
-
490
- return Response.json(
491
- { error: "Internal server error", code: "INTERNAL_SERVER_ERROR" },
492
- { status: 500 },
493
- );
494
- }
495
- },
496
- };
497
-
498
- return fragment;
499
- }