@fragno-dev/core 0.1.7 → 0.1.8
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/.turbo/turbo-build.log +45 -53
- package/CHANGELOG.md +6 -0
- package/dist/api/api.d.ts +2 -2
- package/dist/api/api.js +3 -2
- package/dist/api/fragment-builder.d.ts +2 -4
- package/dist/api/fragment-builder.js +1 -1
- package/dist/api/fragment-instantiation.d.ts +2 -4
- package/dist/api/fragment-instantiation.js +3 -5
- package/dist/api/route.d.ts +2 -3
- package/dist/api/route.js +1 -1
- package/dist/api-BFrUCIsF.d.ts +963 -0
- package/dist/api-BFrUCIsF.d.ts.map +1 -0
- package/dist/client/client.d.ts +1 -3
- package/dist/client/client.js +4 -5
- package/dist/client/client.svelte.d.ts +2 -3
- package/dist/client/client.svelte.d.ts.map +1 -1
- package/dist/client/client.svelte.js +4 -5
- package/dist/client/client.svelte.js.map +1 -1
- package/dist/client/react.d.ts +2 -3
- package/dist/client/react.d.ts.map +1 -1
- package/dist/client/react.js +4 -5
- package/dist/client/react.js.map +1 -1
- package/dist/client/solid.d.ts +2 -3
- package/dist/client/solid.d.ts.map +1 -1
- package/dist/client/solid.js +4 -5
- package/dist/client/solid.js.map +1 -1
- package/dist/client/vanilla.d.ts +2 -3
- package/dist/client/vanilla.d.ts.map +1 -1
- package/dist/client/vanilla.js +4 -5
- package/dist/client/vanilla.js.map +1 -1
- package/dist/client/vue.d.ts +2 -3
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +4 -5
- package/dist/client/vue.js.map +1 -1
- package/dist/{client-C5LsYHEI.js → client-DAFHcKqA.js} +4 -4
- package/dist/{client-C5LsYHEI.js.map → client-DAFHcKqA.js.map} +1 -1
- package/dist/fragment-builder-Boh2vNHq.js +108 -0
- package/dist/fragment-builder-Boh2vNHq.js.map +1 -0
- package/dist/fragment-instantiation-DUT-HLl1.js +898 -0
- package/dist/fragment-instantiation-DUT-HLl1.js.map +1 -0
- package/dist/integrations/react-ssr.js +1 -1
- package/dist/mod.d.ts +2 -4
- package/dist/mod.js +4 -6
- package/dist/{route-C5Uryylh.js → route-C4CyNHkC.js} +8 -3
- package/dist/route-C4CyNHkC.js.map +1 -0
- package/dist/{ssr-BByDVfFD.js → ssr-kyKI7pqH.js} +1 -1
- package/dist/{ssr-BByDVfFD.js.map → ssr-kyKI7pqH.js.map} +1 -1
- package/dist/test/test.d.ts +6 -7
- package/dist/test/test.d.ts.map +1 -1
- package/dist/test/test.js +9 -7
- package/dist/test/test.js.map +1 -1
- package/package.json +1 -1
- package/src/api/api.ts +45 -6
- package/src/api/fragment-builder.ts +463 -25
- package/src/api/fragment-instantiation.test.ts +249 -7
- package/src/api/fragment-instantiation.ts +283 -16
- package/src/api/fragment-services.test.ts +462 -0
- package/src/api/fragment.test.ts +65 -17
- package/src/api/request-middleware.test.ts +6 -3
- package/src/api/route.test.ts +111 -1
- package/src/api/route.ts +323 -14
- package/src/mod.ts +11 -1
- package/src/test/test.test.ts +20 -15
- package/src/test/test.ts +48 -9
- package/dist/api-BWN97TOr.d.ts +0 -377
- package/dist/api-BWN97TOr.d.ts.map +0 -1
- package/dist/api-DngJDcmO.js +0 -54
- package/dist/api-DngJDcmO.js.map +0 -1
- package/dist/fragment-builder-DOnCVBqc.js +0 -47
- package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
- package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
- package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
- package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
- package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
- package/dist/request-output-context-CdIjwmEN.js +0 -320
- package/dist/request-output-context-CdIjwmEN.js.map +0 -1
- package/dist/route-Bl9Zr1Yv.d.ts +0 -26
- package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
- package/dist/route-C5Uryylh.js.map +0 -1
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
import { n as FragnoApiError } from "./api-DngJDcmO.js";
|
|
2
|
-
import { i as getMountRoute, n as RequestOutputContext, r as RequestInputContext, t as OutputContext } from "./request-output-context-CdIjwmEN.js";
|
|
3
|
-
import { r as resolveRouteFactories } from "./route-C5Uryylh.js";
|
|
4
|
-
import { addRoute, createRouter, findRoute } from "rou3";
|
|
5
|
-
|
|
6
|
-
//#region src/api/mutable-request-state.ts
|
|
7
|
-
/**
|
|
8
|
-
* Holds mutable request state that can be modified by middleware and consumed by handlers.
|
|
9
|
-
*
|
|
10
|
-
* This class provides a structural way for middleware to modify request data:
|
|
11
|
-
* - Path parameters can be modified
|
|
12
|
-
* - Query/search parameters can be modified
|
|
13
|
-
* - Request body can be overridden
|
|
14
|
-
* - Request headers can be modified
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```typescript
|
|
18
|
-
* // In middleware
|
|
19
|
-
* const state = new MutableRequestState({
|
|
20
|
-
* pathParams: { id: "123" },
|
|
21
|
-
* searchParams: new URLSearchParams("?role=user"),
|
|
22
|
-
* body: { name: "John" },
|
|
23
|
-
* headers: new Headers()
|
|
24
|
-
* });
|
|
25
|
-
*
|
|
26
|
-
* // Modify query parameters
|
|
27
|
-
* state.searchParams.set("role", "admin");
|
|
28
|
-
*
|
|
29
|
-
* // Override body
|
|
30
|
-
* state.setBody({ name: "Jane" });
|
|
31
|
-
*
|
|
32
|
-
* // Modify headers
|
|
33
|
-
* state.headers.set("X-Custom", "value");
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
var MutableRequestState = class {
|
|
37
|
-
#pathParams;
|
|
38
|
-
#searchParams;
|
|
39
|
-
#headers;
|
|
40
|
-
#initialBody;
|
|
41
|
-
#bodyOverride;
|
|
42
|
-
constructor(config) {
|
|
43
|
-
this.#pathParams = config.pathParams;
|
|
44
|
-
this.#searchParams = config.searchParams;
|
|
45
|
-
this.#headers = config.headers;
|
|
46
|
-
this.#initialBody = config.body;
|
|
47
|
-
this.#bodyOverride = void 0;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Path parameters extracted from the route.
|
|
51
|
-
* Can be modified directly (e.g., `state.pathParams.id = "456"`).
|
|
52
|
-
*/
|
|
53
|
-
get pathParams() {
|
|
54
|
-
return this.#pathParams;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* URLSearchParams for query parameters.
|
|
58
|
-
* Can be modified using URLSearchParams API (e.g., `state.searchParams.set("key", "value")`).
|
|
59
|
-
*/
|
|
60
|
-
get searchParams() {
|
|
61
|
-
return this.#searchParams;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Request headers.
|
|
65
|
-
* Can be modified using Headers API (e.g., `state.headers.set("X-Custom", "value")`).
|
|
66
|
-
*/
|
|
67
|
-
get headers() {
|
|
68
|
-
return this.#headers;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Get the current body value.
|
|
72
|
-
* Returns the override if set, otherwise the initial body.
|
|
73
|
-
*/
|
|
74
|
-
get body() {
|
|
75
|
-
return this.#bodyOverride !== void 0 ? this.#bodyOverride : this.#initialBody;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Override the request body.
|
|
79
|
-
* This allows middleware to replace the body that will be seen by the handler.
|
|
80
|
-
*
|
|
81
|
-
* @param body - The new body value
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* ```typescript
|
|
85
|
-
* // In middleware
|
|
86
|
-
* state.setBody({ modifiedField: "new value" });
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
setBody(body) {
|
|
90
|
-
this.#bodyOverride = body;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Check if the body has been overridden by middleware.
|
|
94
|
-
*/
|
|
95
|
-
get hasBodyOverride() {
|
|
96
|
-
return this.#bodyOverride !== void 0;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
//#endregion
|
|
101
|
-
//#region src/api/request-middleware.ts
|
|
102
|
-
var RequestMiddlewareOutputContext = class extends OutputContext {
|
|
103
|
-
#deps;
|
|
104
|
-
#services;
|
|
105
|
-
constructor(deps, services) {
|
|
106
|
-
super();
|
|
107
|
-
this.#deps = deps;
|
|
108
|
-
this.#services = services;
|
|
109
|
-
}
|
|
110
|
-
get deps() {
|
|
111
|
-
return this.#deps;
|
|
112
|
-
}
|
|
113
|
-
get services() {
|
|
114
|
-
return this.#services;
|
|
115
|
-
}
|
|
116
|
-
};
|
|
117
|
-
var RequestMiddlewareInputContext = class {
|
|
118
|
-
#options;
|
|
119
|
-
#route;
|
|
120
|
-
#state;
|
|
121
|
-
constructor(routes, options) {
|
|
122
|
-
this.#options = options;
|
|
123
|
-
this.#state = options.state;
|
|
124
|
-
const route = routes.find((route$1) => route$1.path === options.path && route$1.method === options.method);
|
|
125
|
-
if (!route) throw new Error(`Route not found: ${options.path} ${options.method}`);
|
|
126
|
-
this.#route = route;
|
|
127
|
-
}
|
|
128
|
-
get path() {
|
|
129
|
-
return this.#options.path;
|
|
130
|
-
}
|
|
131
|
-
get method() {
|
|
132
|
-
return this.#options.method;
|
|
133
|
-
}
|
|
134
|
-
get pathParams() {
|
|
135
|
-
return this.#state.pathParams;
|
|
136
|
-
}
|
|
137
|
-
get queryParams() {
|
|
138
|
-
return this.#state.searchParams;
|
|
139
|
-
}
|
|
140
|
-
get headers() {
|
|
141
|
-
return this.#state.headers;
|
|
142
|
-
}
|
|
143
|
-
get inputSchema() {
|
|
144
|
-
return this.#route.inputSchema;
|
|
145
|
-
}
|
|
146
|
-
get outputSchema() {
|
|
147
|
-
return this.#route.outputSchema;
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Access to the mutable request state.
|
|
151
|
-
* Use this to modify query parameters, path parameters, or request body.
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```typescript
|
|
155
|
-
* // Modify body
|
|
156
|
-
* requestState.setBody({ modified: true });
|
|
157
|
-
*
|
|
158
|
-
* // Query params are already accessible via queryParams getter
|
|
159
|
-
* // Path params are already accessible via pathParams getter
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
get requestState() {
|
|
163
|
-
return this.#state;
|
|
164
|
-
}
|
|
165
|
-
ifMatchesRoute = async (method, path, handler) => {
|
|
166
|
-
if (this.path !== path || this.method !== method) return;
|
|
167
|
-
return await handler(await RequestInputContext.fromRequest({
|
|
168
|
-
request: this.#options.request,
|
|
169
|
-
method: this.#options.method,
|
|
170
|
-
path,
|
|
171
|
-
pathParams: this.pathParams,
|
|
172
|
-
inputSchema: this.#route.inputSchema,
|
|
173
|
-
state: this.#state
|
|
174
|
-
}), new RequestOutputContext(this.#route.outputSchema));
|
|
175
|
-
};
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
//#endregion
|
|
179
|
-
//#region src/api/fragno-response.ts
|
|
180
|
-
/**
|
|
181
|
-
* Parse a Response object into a FragnoResponse discriminated union
|
|
182
|
-
*/
|
|
183
|
-
async function parseFragnoResponse(response) {
|
|
184
|
-
const status = response.status;
|
|
185
|
-
const headers = response.headers;
|
|
186
|
-
if ((headers.get("content-type") || "").includes("application/x-ndjson")) return {
|
|
187
|
-
type: "jsonStream",
|
|
188
|
-
status,
|
|
189
|
-
headers,
|
|
190
|
-
stream: parseNDJSONStream(response)
|
|
191
|
-
};
|
|
192
|
-
const text = await response.text();
|
|
193
|
-
if (!text || text === "null") return {
|
|
194
|
-
type: "empty",
|
|
195
|
-
status,
|
|
196
|
-
headers
|
|
197
|
-
};
|
|
198
|
-
const data = JSON.parse(text);
|
|
199
|
-
if (data && typeof data === "object" && "code" in data) {
|
|
200
|
-
if ("message" in data) return {
|
|
201
|
-
type: "error",
|
|
202
|
-
status,
|
|
203
|
-
headers,
|
|
204
|
-
error: {
|
|
205
|
-
message: data.message,
|
|
206
|
-
code: data.code
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
if ("error" in data) return {
|
|
210
|
-
type: "error",
|
|
211
|
-
status,
|
|
212
|
-
headers,
|
|
213
|
-
error: {
|
|
214
|
-
message: data.error,
|
|
215
|
-
code: data.code
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
return {
|
|
220
|
-
type: "json",
|
|
221
|
-
status,
|
|
222
|
-
headers,
|
|
223
|
-
data
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Parse an NDJSON stream into an async generator
|
|
228
|
-
*/
|
|
229
|
-
async function* parseNDJSONStream(response) {
|
|
230
|
-
if (!response.body) return;
|
|
231
|
-
const reader = response.body.getReader();
|
|
232
|
-
const decoder = new TextDecoder();
|
|
233
|
-
let buffer = "";
|
|
234
|
-
try {
|
|
235
|
-
while (true) {
|
|
236
|
-
const { done, value } = await reader.read();
|
|
237
|
-
if (done) break;
|
|
238
|
-
buffer += decoder.decode(value, { stream: true });
|
|
239
|
-
const lines = buffer.split("\n");
|
|
240
|
-
buffer = lines.pop() || "";
|
|
241
|
-
for (const line of lines) if (line.trim()) yield JSON.parse(line);
|
|
242
|
-
}
|
|
243
|
-
if (buffer.trim()) yield JSON.parse(buffer);
|
|
244
|
-
} finally {
|
|
245
|
-
reader.releaseLock();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
//#endregion
|
|
250
|
-
//#region src/api/fragment-instantiation.ts
|
|
251
|
-
const instantiatedFragmentFakeSymbol = "$fragno-instantiated-fragment";
|
|
252
|
-
function createFragment(fragmentBuilder, config, routesOrFactories, options) {
|
|
253
|
-
const definition = fragmentBuilder.definition;
|
|
254
|
-
const dependencies = definition.dependencies?.(config, options) ?? {};
|
|
255
|
-
const services = definition.services?.(config, options, dependencies) ?? {};
|
|
256
|
-
const routes = resolveRouteFactories({
|
|
257
|
-
config,
|
|
258
|
-
deps: dependencies,
|
|
259
|
-
services
|
|
260
|
-
}, routesOrFactories);
|
|
261
|
-
const mountRoute = getMountRoute({
|
|
262
|
-
name: definition.name,
|
|
263
|
-
mountRoute: options.mountRoute
|
|
264
|
-
});
|
|
265
|
-
const router = createRouter();
|
|
266
|
-
let middlewareHandler;
|
|
267
|
-
for (const routeConfig of routes) addRoute(router, routeConfig.method.toUpperCase(), routeConfig.path, routeConfig);
|
|
268
|
-
const fragment = {
|
|
269
|
-
[instantiatedFragmentFakeSymbol]: instantiatedFragmentFakeSymbol,
|
|
270
|
-
mountRoute,
|
|
271
|
-
config: {
|
|
272
|
-
name: definition.name,
|
|
273
|
-
routes
|
|
274
|
-
},
|
|
275
|
-
services,
|
|
276
|
-
deps: dependencies,
|
|
277
|
-
additionalContext: {
|
|
278
|
-
...definition.additionalContext,
|
|
279
|
-
...options
|
|
280
|
-
},
|
|
281
|
-
withMiddleware: (handler) => {
|
|
282
|
-
if (middlewareHandler) throw new Error("Middleware already set");
|
|
283
|
-
middlewareHandler = handler;
|
|
284
|
-
return fragment;
|
|
285
|
-
},
|
|
286
|
-
callRoute: async (method, path, inputOptions) => {
|
|
287
|
-
return parseFragnoResponse(await fragment.callRouteRaw(method, path, inputOptions));
|
|
288
|
-
},
|
|
289
|
-
callRouteRaw: async (method, path, inputOptions) => {
|
|
290
|
-
const route = routes.find((r) => r.method === method && r.path === path);
|
|
291
|
-
if (!route) return Response.json({
|
|
292
|
-
error: `Route ${method} ${path} not found`,
|
|
293
|
-
code: "ROUTE_NOT_FOUND"
|
|
294
|
-
}, { status: 404 });
|
|
295
|
-
const { pathParams = {}, body, query, headers } = inputOptions || {};
|
|
296
|
-
const searchParams = query instanceof URLSearchParams ? query : query ? new URLSearchParams(query) : new URLSearchParams();
|
|
297
|
-
const requestHeaders = headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();
|
|
298
|
-
const inputContext = new RequestInputContext({
|
|
299
|
-
path: route.path,
|
|
300
|
-
method: route.method,
|
|
301
|
-
pathParams,
|
|
302
|
-
searchParams,
|
|
303
|
-
headers: requestHeaders,
|
|
304
|
-
parsedBody: body,
|
|
305
|
-
inputSchema: route.inputSchema,
|
|
306
|
-
shouldValidateInput: true
|
|
307
|
-
});
|
|
308
|
-
const outputContext = new RequestOutputContext(route.outputSchema);
|
|
309
|
-
try {
|
|
310
|
-
return await route.handler(inputContext, outputContext);
|
|
311
|
-
} catch (error) {
|
|
312
|
-
console.error("Error in callRoute handler", error);
|
|
313
|
-
if (error instanceof FragnoApiError) return error.toResponse();
|
|
314
|
-
return Response.json({
|
|
315
|
-
error: "Internal server error",
|
|
316
|
-
code: "INTERNAL_SERVER_ERROR"
|
|
317
|
-
}, { status: 500 });
|
|
318
|
-
}
|
|
319
|
-
},
|
|
320
|
-
handlersFor: (framework) => {
|
|
321
|
-
const handler = fragment.handler;
|
|
322
|
-
if (framework === "h3" || framework === "nuxt") throw new Error(`To get handlers for h3, use the 'fromWebHandler' utility function:
|
|
323
|
-
import { fromWebHandler } from "h3";
|
|
324
|
-
export default fromWebHandler(myFragment().handler);`);
|
|
325
|
-
return {
|
|
326
|
-
astro: { ALL: handler },
|
|
327
|
-
"react-router": {
|
|
328
|
-
loader: ({ request }) => handler(request),
|
|
329
|
-
action: ({ request }) => handler(request)
|
|
330
|
-
},
|
|
331
|
-
"next-js": {
|
|
332
|
-
GET: handler,
|
|
333
|
-
POST: handler,
|
|
334
|
-
PUT: handler,
|
|
335
|
-
DELETE: handler,
|
|
336
|
-
PATCH: handler,
|
|
337
|
-
HEAD: handler,
|
|
338
|
-
OPTIONS: handler
|
|
339
|
-
},
|
|
340
|
-
"svelte-kit": {
|
|
341
|
-
GET: handler,
|
|
342
|
-
POST: handler,
|
|
343
|
-
PUT: handler,
|
|
344
|
-
DELETE: handler,
|
|
345
|
-
PATCH: handler,
|
|
346
|
-
HEAD: handler,
|
|
347
|
-
OPTIONS: handler
|
|
348
|
-
},
|
|
349
|
-
"solid-start": {
|
|
350
|
-
GET: ({ request }) => handler(request),
|
|
351
|
-
POST: ({ request }) => handler(request),
|
|
352
|
-
PUT: ({ request }) => handler(request),
|
|
353
|
-
DELETE: ({ request }) => handler(request),
|
|
354
|
-
PATCH: ({ request }) => handler(request),
|
|
355
|
-
HEAD: ({ request }) => handler(request),
|
|
356
|
-
OPTIONS: ({ request }) => handler(request)
|
|
357
|
-
},
|
|
358
|
-
"tanstack-start": {
|
|
359
|
-
GET: ({ request }) => handler(request),
|
|
360
|
-
POST: ({ request }) => handler(request),
|
|
361
|
-
PUT: ({ request }) => handler(request),
|
|
362
|
-
DELETE: ({ request }) => handler(request),
|
|
363
|
-
PATCH: ({ request }) => handler(request),
|
|
364
|
-
HEAD: ({ request }) => handler(request),
|
|
365
|
-
OPTIONS: ({ request }) => handler(request)
|
|
366
|
-
}
|
|
367
|
-
}[framework];
|
|
368
|
-
},
|
|
369
|
-
handler: async (req) => {
|
|
370
|
-
const url = new URL(req.url);
|
|
371
|
-
const pathname = url.pathname;
|
|
372
|
-
const matchRoute = pathname.startsWith(mountRoute) ? pathname.slice(mountRoute.length) : null;
|
|
373
|
-
if (matchRoute === null) return Response.json({
|
|
374
|
-
error: `Fragno: Route for '${definition.name}' not found. Is the fragment mounted on the right route? Expecting: '${mountRoute}'.`,
|
|
375
|
-
code: "ROUTE_NOT_FOUND"
|
|
376
|
-
}, { status: 404 });
|
|
377
|
-
const route = findRoute(router, req.method, matchRoute);
|
|
378
|
-
if (!route) return Response.json({
|
|
379
|
-
error: `Fragno: Route for '${definition.name}' not found`,
|
|
380
|
-
code: "ROUTE_NOT_FOUND"
|
|
381
|
-
}, { status: 404 });
|
|
382
|
-
const { handler, inputSchema, outputSchema, path } = route.data;
|
|
383
|
-
const outputContext = new RequestOutputContext(outputSchema);
|
|
384
|
-
let requestBody = void 0;
|
|
385
|
-
let rawBody = void 0;
|
|
386
|
-
if (req.body instanceof ReadableStream) {
|
|
387
|
-
rawBody = await req.clone().text();
|
|
388
|
-
if (rawBody) try {
|
|
389
|
-
requestBody = JSON.parse(rawBody);
|
|
390
|
-
} catch {
|
|
391
|
-
requestBody = void 0;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
const requestState = new MutableRequestState({
|
|
395
|
-
pathParams: route.params ?? {},
|
|
396
|
-
searchParams: url.searchParams,
|
|
397
|
-
body: requestBody,
|
|
398
|
-
headers: new Headers(req.headers)
|
|
399
|
-
});
|
|
400
|
-
if (middlewareHandler) {
|
|
401
|
-
const middlewareInputContext = new RequestMiddlewareInputContext(routes, {
|
|
402
|
-
method: req.method,
|
|
403
|
-
path,
|
|
404
|
-
request: req,
|
|
405
|
-
state: requestState
|
|
406
|
-
});
|
|
407
|
-
const middlewareOutputContext = new RequestMiddlewareOutputContext(dependencies, services);
|
|
408
|
-
try {
|
|
409
|
-
const middlewareResult = await middlewareHandler(middlewareInputContext, middlewareOutputContext);
|
|
410
|
-
if (middlewareResult !== void 0) return middlewareResult;
|
|
411
|
-
} catch (error) {
|
|
412
|
-
console.error("Error in middleware", error);
|
|
413
|
-
if (error instanceof FragnoApiError) return error.toResponse();
|
|
414
|
-
return Response.json({
|
|
415
|
-
error: "Internal server error",
|
|
416
|
-
code: "INTERNAL_SERVER_ERROR"
|
|
417
|
-
}, { status: 500 });
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
const inputContext = await RequestInputContext.fromRequest({
|
|
421
|
-
request: req,
|
|
422
|
-
method: req.method,
|
|
423
|
-
path,
|
|
424
|
-
pathParams: route.params ?? {},
|
|
425
|
-
inputSchema,
|
|
426
|
-
state: requestState,
|
|
427
|
-
rawBody
|
|
428
|
-
});
|
|
429
|
-
try {
|
|
430
|
-
return await handler(inputContext, outputContext);
|
|
431
|
-
} catch (error) {
|
|
432
|
-
console.error("Error in handler", error);
|
|
433
|
-
if (error instanceof FragnoApiError) return error.toResponse();
|
|
434
|
-
return Response.json({
|
|
435
|
-
error: "Internal server error",
|
|
436
|
-
code: "INTERNAL_SERVER_ERROR"
|
|
437
|
-
}, { status: 500 });
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
return fragment;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
//#endregion
|
|
445
|
-
export { instantiatedFragmentFakeSymbol as n, createFragment as t };
|
|
446
|
-
//# sourceMappingURL=fragment-instantiation-C4wvwl6V.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fragment-instantiation-C4wvwl6V.js","names":["#pathParams","#searchParams","#headers","#initialBody","#bodyOverride","#deps","#services","#options","#route","#state","route","middlewareHandler:\n | FragnoMiddlewareCallback<FlattenRouteFactories<TRoutesOrFactories>, TDeps, TServices>\n | undefined","fragment: FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps,\n TServices,\n TAdditionalContext & TOptions\n >","requestBody: RequestBodyType","rawBody: string | undefined"],"sources":["../src/api/mutable-request-state.ts","../src/api/request-middleware.ts","../src/api/fragno-response.ts","../src/api/fragment-instantiation.ts"],"sourcesContent":["import type { RequestBodyType } from \"./request-input-context\";\n\n/**\n * Holds mutable request state that can be modified by middleware and consumed by handlers.\n *\n * This class provides a structural way for middleware to modify request data:\n * - Path parameters can be modified\n * - Query/search parameters can be modified\n * - Request body can be overridden\n * - Request headers can be modified\n *\n * @example\n * ```typescript\n * // In middleware\n * const state = new MutableRequestState({\n * pathParams: { id: \"123\" },\n * searchParams: new URLSearchParams(\"?role=user\"),\n * body: { name: \"John\" },\n * headers: new Headers()\n * });\n *\n * // Modify query parameters\n * state.searchParams.set(\"role\", \"admin\");\n *\n * // Override body\n * state.setBody({ name: \"Jane\" });\n *\n * // Modify headers\n * state.headers.set(\"X-Custom\", \"value\");\n * ```\n */\nexport class MutableRequestState {\n readonly #pathParams: Record<string, string>;\n readonly #searchParams: URLSearchParams;\n readonly #headers: Headers;\n // oxlint-disable-next-line no-unused-private-class-members False Positive?\n readonly #initialBody: RequestBodyType;\n #bodyOverride: RequestBodyType | undefined;\n\n constructor(config: {\n pathParams: Record<string, string>;\n searchParams: URLSearchParams;\n body: RequestBodyType;\n headers: Headers;\n }) {\n this.#pathParams = config.pathParams;\n this.#searchParams = config.searchParams;\n this.#headers = config.headers;\n this.#initialBody = config.body;\n this.#bodyOverride = undefined;\n }\n\n /**\n * Path parameters extracted from the route.\n * Can be modified directly (e.g., `state.pathParams.id = \"456\"`).\n */\n get pathParams(): Record<string, string> {\n return this.#pathParams;\n }\n\n /**\n * URLSearchParams for query parameters.\n * Can be modified using URLSearchParams API (e.g., `state.searchParams.set(\"key\", \"value\")`).\n */\n get searchParams(): URLSearchParams {\n return this.#searchParams;\n }\n\n /**\n * Request headers.\n * Can be modified using Headers API (e.g., `state.headers.set(\"X-Custom\", \"value\")`).\n */\n get headers(): Headers {\n return this.#headers;\n }\n\n /**\n * Get the current body value.\n * Returns the override if set, otherwise the initial body.\n */\n get body(): RequestBodyType {\n return this.#bodyOverride !== undefined ? this.#bodyOverride : this.#initialBody;\n }\n\n /**\n * Override the request body.\n * This allows middleware to replace the body that will be seen by the handler.\n *\n * @param body - The new body value\n *\n * @example\n * ```typescript\n * // In middleware\n * state.setBody({ modifiedField: \"new value\" });\n * ```\n */\n setBody(body: RequestBodyType): void {\n this.#bodyOverride = body;\n }\n\n /**\n * Check if the body has been overridden by middleware.\n */\n get hasBodyOverride(): boolean {\n return this.#bodyOverride !== undefined;\n }\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ExtractRouteByPath, ExtractRoutePath } from \"../client/client\";\nimport type { HTTPMethod } from \"./api\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport type { AnyFragnoRouteConfig } from \"./route\";\nimport { RequestInputContext } from \"./request-input-context\";\nimport { OutputContext, RequestOutputContext } from \"./request-output-context\";\nimport { MutableRequestState } from \"./mutable-request-state\";\n\nexport type FragnoMiddlewareCallback<\n TRoutes extends readonly AnyFragnoRouteConfig[],\n TDeps,\n TServices extends Record<string, unknown>,\n> = (\n inputContext: RequestMiddlewareInputContext<TRoutes>,\n outputContext: RequestMiddlewareOutputContext<TDeps, TServices>,\n) => Promise<Response | undefined> | Response | undefined;\n\nexport interface RequestMiddlewareOptions {\n path: string;\n method: HTTPMethod;\n request: Request;\n state: MutableRequestState;\n}\n\nexport class RequestMiddlewareOutputContext<\n const TDeps,\n const TServices extends Record<string, unknown>,\n> extends OutputContext<unknown, string> {\n readonly #deps: TDeps;\n readonly #services: TServices;\n\n constructor(deps: TDeps, services: TServices) {\n super();\n this.#deps = deps;\n this.#services = services;\n }\n\n get deps(): TDeps {\n return this.#deps;\n }\n\n get services(): TServices {\n return this.#services;\n }\n}\n\nexport class RequestMiddlewareInputContext<const TRoutes extends readonly AnyFragnoRouteConfig[]> {\n readonly #options: RequestMiddlewareOptions;\n readonly #route: TRoutes[number];\n readonly #state: MutableRequestState;\n\n constructor(routes: TRoutes, options: RequestMiddlewareOptions) {\n this.#options = options;\n this.#state = options.state;\n\n const route = routes.find(\n (route) => route.path === options.path && route.method === options.method,\n );\n\n if (!route) {\n throw new Error(`Route not found: ${options.path} ${options.method}`);\n }\n\n this.#route = route;\n }\n\n get path(): string {\n return this.#options.path;\n }\n\n get method(): HTTPMethod {\n return this.#options.method;\n }\n\n get pathParams(): Record<string, string> {\n return this.#state.pathParams;\n }\n\n get queryParams(): URLSearchParams {\n return this.#state.searchParams;\n }\n\n get headers(): Headers {\n return this.#state.headers;\n }\n\n get inputSchema(): StandardSchemaV1 | undefined {\n return this.#route.inputSchema;\n }\n\n get outputSchema(): StandardSchemaV1 | undefined {\n return this.#route.outputSchema;\n }\n\n /**\n * Access to the mutable request state.\n * Use this to modify query parameters, path parameters, or request body.\n *\n * @example\n * ```typescript\n * // Modify body\n * requestState.setBody({ modified: true });\n *\n * // Query params are already accessible via queryParams getter\n * // Path params are already accessible via pathParams getter\n * ```\n */\n get requestState(): MutableRequestState {\n return this.#state;\n }\n\n // Defined as a field so that `this` reference stays in tact when destructuring\n ifMatchesRoute = async <\n const TMethod extends HTTPMethod,\n const TPath extends ExtractRoutePath<TRoutes>,\n const TRoute extends ExtractRouteByPath<TRoutes, TPath, TMethod> = ExtractRouteByPath<\n TRoutes,\n TPath,\n TMethod\n >,\n >(\n method: TMethod,\n path: TPath,\n handler: (\n ...args: Parameters<TRoute[\"handler\"]>\n ) => Promise<Response | undefined | void> | Response | undefined | void,\n ): Promise<Response | undefined> => {\n if (this.path !== path || this.method !== method) {\n return undefined;\n }\n\n // TODO(Wilco): We should support reading/modifying headers here.\n const inputContext = await RequestInputContext.fromRequest({\n request: this.#options.request,\n method: this.#options.method,\n path: path,\n pathParams: this.pathParams as ExtractPathParams<TPath>,\n inputSchema: this.#route.inputSchema,\n state: this.#state,\n });\n\n const outputContext = new RequestOutputContext(this.#route.outputSchema);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return await (handler as any)(inputContext, outputContext);\n };\n}\n","/**\n * Discriminated union representing all possible Fragno response types\n */\nexport type FragnoResponse<T> =\n | {\n type: \"empty\";\n status: number;\n headers: Headers;\n }\n | {\n type: \"error\";\n status: number;\n headers: Headers;\n error: { message: string; code: string };\n }\n | {\n type: \"json\";\n status: number;\n headers: Headers;\n data: T;\n }\n | {\n type: \"jsonStream\";\n status: number;\n headers: Headers;\n stream: AsyncGenerator<T extends unknown[] ? T[number] : T>;\n };\n\n/**\n * Parse a Response object into a FragnoResponse discriminated union\n */\nexport async function parseFragnoResponse<T>(response: Response): Promise<FragnoResponse<T>> {\n const status = response.status;\n const headers = response.headers;\n const contentType = headers.get(\"content-type\") || \"\";\n\n // Check for streaming response\n if (contentType.includes(\"application/x-ndjson\")) {\n return {\n type: \"jsonStream\",\n status,\n headers,\n stream: parseNDJSONStream<T>(response),\n };\n }\n\n // Parse JSON body\n const text = await response.text();\n\n // Empty response\n if (!text || text === \"null\") {\n return {\n type: \"empty\",\n status,\n headers,\n };\n }\n\n const data = JSON.parse(text);\n\n // Error response (has message and code, or error and code)\n if (data && typeof data === \"object\" && \"code\" in data) {\n if (\"message\" in data) {\n return {\n type: \"error\",\n status,\n headers,\n error: { message: data.message, code: data.code },\n };\n }\n if (\"error\" in data) {\n return {\n type: \"error\",\n status,\n headers,\n error: { message: data.error, code: data.code },\n };\n }\n }\n\n // JSON response\n return {\n type: \"json\",\n status,\n headers,\n data: data as T,\n };\n}\n\n/**\n * Parse an NDJSON stream into an async generator\n */\nasync function* parseNDJSONStream<T>(\n response: Response,\n): AsyncGenerator<T extends unknown[] ? T[number] : T> {\n if (!response.body) {\n return;\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (line.trim()) {\n yield JSON.parse(line) as T extends unknown[] ? T[number] : T;\n }\n }\n }\n\n // Process any remaining data in the buffer\n if (buffer.trim()) {\n yield JSON.parse(buffer) as T extends unknown[] ? T[number] : T;\n }\n } finally {\n reader.releaseLock();\n }\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { type FragnoRouteConfig, type HTTPMethod } from \"./api\";\nimport { FragnoApiError } from \"./error\";\nimport { getMountRoute } from \"./internal/route\";\nimport { addRoute, createRouter, findRoute } from \"rou3\";\nimport { RequestInputContext, type RequestBodyType } from \"./request-input-context\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport { RequestOutputContext } from \"./request-output-context\";\nimport {\n type AnyFragnoRouteConfig,\n type AnyRouteOrFactory,\n type FlattenRouteFactories,\n resolveRouteFactories,\n} from \"./route\";\nimport {\n RequestMiddlewareInputContext,\n RequestMiddlewareOutputContext,\n type FragnoMiddlewareCallback,\n} from \"./request-middleware\";\nimport type { FragmentDefinition } from \"./fragment-builder\";\nimport { MutableRequestState } from \"./mutable-request-state\";\nimport type { RouteHandlerInputOptions } from \"./route-handler-input-options\";\nimport type { ExtractRouteByPath, ExtractRoutePath } from \"../client/client\";\nimport { type FragnoResponse, parseFragnoResponse } from \"./fragno-response\";\nimport type { InferOrUnknown } from \"../util/types-util\";\n\nexport interface FragnoPublicConfig {\n mountRoute?: string;\n}\n\nexport type FetcherConfig =\n | { type: \"options\"; options: RequestInit }\n | { type: \"function\"; fetcher: typeof fetch };\n\nexport interface FragnoPublicClientConfig {\n mountRoute?: string;\n baseUrl?: string;\n fetcherConfig?: FetcherConfig;\n}\n\ntype AstroHandlers = {\n ALL: (req: Request) => Promise<Response>;\n};\n\ntype ReactRouterHandlers = {\n loader: (args: { request: Request }) => Promise<Response>;\n action: (args: { request: Request }) => Promise<Response>;\n};\n\ntype SolidStartHandlers = {\n GET: (args: { request: Request }) => Promise<Response>;\n POST: (args: { request: Request }) => Promise<Response>;\n PUT: (args: { request: Request }) => Promise<Response>;\n DELETE: (args: { request: Request }) => Promise<Response>;\n PATCH: (args: { request: Request }) => Promise<Response>;\n HEAD: (args: { request: Request }) => Promise<Response>;\n OPTIONS: (args: { request: Request }) => Promise<Response>;\n};\n\ntype TanStackStartHandlers = SolidStartHandlers;\n\ntype StandardHandlers = {\n GET: (req: Request) => Promise<Response>;\n POST: (req: Request) => Promise<Response>;\n PUT: (req: Request) => Promise<Response>;\n DELETE: (req: Request) => Promise<Response>;\n PATCH: (req: Request) => Promise<Response>;\n HEAD: (req: Request) => Promise<Response>;\n OPTIONS: (req: Request) => Promise<Response>;\n};\n\ntype HandlersByFramework = {\n astro: AstroHandlers;\n \"react-router\": ReactRouterHandlers;\n \"next-js\": StandardHandlers;\n \"svelte-kit\": StandardHandlers;\n \"solid-start\": SolidStartHandlers;\n \"tanstack-start\": TanStackStartHandlers;\n};\n\n// Not actually a symbol, since we might be dealing with multiple instances of this code.\nexport const instantiatedFragmentFakeSymbol = \"$fragno-instantiated-fragment\" as const;\n\ntype FullstackFrameworks = keyof HandlersByFramework;\n\nexport interface FragnoInstantiatedFragment<\n TRoutes extends readonly AnyFragnoRouteConfig[] = [],\n TDeps = {},\n TServices extends Record<string, unknown> = Record<string, unknown>,\n TAdditionalContext extends Record<string, unknown> = {},\n> {\n [instantiatedFragmentFakeSymbol]: typeof instantiatedFragmentFakeSymbol;\n\n config: FragnoFragmentSharedConfig<TRoutes>;\n deps: TDeps;\n services: TServices;\n additionalContext?: TAdditionalContext;\n handlersFor: <T extends FullstackFrameworks>(framework: T) => HandlersByFramework[T];\n handler: (req: Request) => Promise<Response>;\n mountRoute: string;\n callRoute: <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ) => Promise<\n FragnoResponse<\n InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>[\"outputSchema\"]>>\n >\n >;\n callRouteRaw: <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ) => Promise<Response>;\n withMiddleware: (\n handler: FragnoMiddlewareCallback<TRoutes, TDeps, TServices>,\n ) => FragnoInstantiatedFragment<TRoutes, TDeps, TServices, TAdditionalContext>;\n}\n\nexport interface FragnoFragmentSharedConfig<\n TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >[],\n> {\n name: string;\n routes: TRoutes;\n}\n\nexport type AnyFragnoFragmentSharedConfig = FragnoFragmentSharedConfig<\n readonly AnyFragnoRouteConfig[]\n>;\n\nexport function createFragment<\n const TConfig,\n const TDeps,\n const TServices extends Record<string, unknown>,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n const TAdditionalContext extends Record<string, unknown>,\n const TOptions extends FragnoPublicConfig,\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;\n $requiredOptions: TOptions;\n },\n config: TConfig,\n routesOrFactories: TRoutesOrFactories,\n options: TOptions,\n): FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps,\n TServices,\n TAdditionalContext\n> {\n type TRoutes = FlattenRouteFactories<TRoutesOrFactories>;\n\n const definition = fragmentBuilder.definition;\n\n const dependencies = definition.dependencies?.(config, options) ?? ({} as TDeps);\n const services = definition.services?.(config, options, dependencies) ?? ({} as TServices);\n\n const context = { config, deps: dependencies, services };\n const routes = resolveRouteFactories(context, routesOrFactories);\n\n const mountRoute = getMountRoute({\n name: definition.name,\n mountRoute: options.mountRoute,\n });\n\n const router =\n createRouter<\n FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >\n >();\n\n let middlewareHandler:\n | FragnoMiddlewareCallback<FlattenRouteFactories<TRoutesOrFactories>, TDeps, TServices>\n | undefined;\n\n for (const routeConfig of routes) {\n addRoute(router, routeConfig.method.toUpperCase(), routeConfig.path, routeConfig);\n }\n\n const fragment: FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps,\n TServices,\n TAdditionalContext & TOptions\n > = {\n [instantiatedFragmentFakeSymbol]: instantiatedFragmentFakeSymbol,\n mountRoute,\n config: {\n name: definition.name,\n routes,\n },\n services,\n deps: dependencies,\n additionalContext: {\n ...definition.additionalContext,\n ...options,\n } as TAdditionalContext & TOptions,\n withMiddleware: (handler) => {\n if (middlewareHandler) {\n throw new Error(\"Middleware already set\");\n }\n\n middlewareHandler = handler;\n\n return fragment;\n },\n callRoute: async <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ): Promise<\n FragnoResponse<\n InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>[\"outputSchema\"]>>\n >\n > => {\n const response = await fragment.callRouteRaw(method, path, inputOptions);\n return parseFragnoResponse(response);\n },\n callRouteRaw: async <\n TMethod extends HTTPMethod,\n TPath extends ExtractRoutePath<TRoutes, TMethod>,\n >(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ): Promise<Response> => {\n // Find the route configuration\n const route = routes.find((r) => r.method === method && r.path === path);\n\n if (!route) {\n return Response.json(\n {\n error: `Route ${method} ${path} not found`,\n code: \"ROUTE_NOT_FOUND\",\n },\n { status: 404 },\n );\n }\n\n const {\n pathParams = {} as ExtractPathParams<TPath>,\n body,\n query,\n headers,\n } = inputOptions || {};\n\n // Convert query to URLSearchParams if needed\n const searchParams =\n query instanceof URLSearchParams\n ? query\n : query\n ? new URLSearchParams(query)\n : new URLSearchParams();\n\n // Convert headers to Headers if needed\n const requestHeaders =\n headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();\n\n // Construct RequestInputContext\n const inputContext = new RequestInputContext({\n path: route.path,\n method: route.method,\n pathParams,\n searchParams,\n headers: requestHeaders,\n parsedBody: body,\n inputSchema: route.inputSchema,\n shouldValidateInput: true, // Enable validation for production use\n });\n\n // Construct RequestOutputContext\n const outputContext = new RequestOutputContext(route.outputSchema);\n\n // Call the route handler\n try {\n const response = await route.handler(inputContext, outputContext);\n return response;\n } catch (error) {\n console.error(\"Error in callRoute handler\", error);\n\n if (error instanceof FragnoApiError) {\n return error.toResponse();\n }\n\n return Response.json(\n { error: \"Internal server error\", code: \"INTERNAL_SERVER_ERROR\" },\n { status: 500 },\n );\n }\n },\n handlersFor: <T extends FullstackFrameworks>(framework: T): HandlersByFramework[T] => {\n const handler = fragment.handler;\n\n // LLMs hallucinate these values sometimes, solution isn't obvious so we throw this error\n // @ts-expect-error TS2367\n if (framework === \"h3\" || framework === \"nuxt\") {\n throw new Error(`To get handlers for h3, use the 'fromWebHandler' utility function:\n import { fromWebHandler } from \"h3\";\n export default fromWebHandler(myFragment().handler);`);\n }\n const allHandlers = {\n astro: { ALL: handler },\n \"react-router\": {\n loader: ({ request }: { request: Request }) => handler(request),\n action: ({ request }: { request: Request }) => handler(request),\n },\n \"next-js\": {\n GET: handler,\n POST: handler,\n PUT: handler,\n DELETE: handler,\n PATCH: handler,\n HEAD: handler,\n OPTIONS: handler,\n },\n \"svelte-kit\": {\n GET: handler,\n POST: handler,\n PUT: handler,\n DELETE: handler,\n PATCH: handler,\n HEAD: handler,\n OPTIONS: handler,\n },\n \"solid-start\": {\n GET: ({ request }: { request: Request }) => handler(request),\n POST: ({ request }: { request: Request }) => handler(request),\n PUT: ({ request }: { request: Request }) => handler(request),\n DELETE: ({ request }: { request: Request }) => handler(request),\n PATCH: ({ request }: { request: Request }) => handler(request),\n HEAD: ({ request }: { request: Request }) => handler(request),\n OPTIONS: ({ request }: { request: Request }) => handler(request),\n },\n \"tanstack-start\": {\n GET: ({ request }: { request: Request }) => handler(request),\n POST: ({ request }: { request: Request }) => handler(request),\n PUT: ({ request }: { request: Request }) => handler(request),\n DELETE: ({ request }: { request: Request }) => handler(request),\n PATCH: ({ request }: { request: Request }) => handler(request),\n HEAD: ({ request }: { request: Request }) => handler(request),\n OPTIONS: ({ request }: { request: Request }) => handler(request),\n },\n } satisfies HandlersByFramework;\n\n return allHandlers[framework];\n },\n handler: async (req: Request) => {\n const url = new URL(req.url);\n const pathname = url.pathname;\n\n const matchRoute = pathname.startsWith(mountRoute) ? pathname.slice(mountRoute.length) : null;\n\n if (matchRoute === null) {\n return Response.json(\n {\n error:\n `Fragno: Route for '${definition.name}' not found. Is the fragment mounted on the right route? ` +\n `Expecting: '${mountRoute}'.`,\n code: \"ROUTE_NOT_FOUND\",\n },\n { status: 404 },\n );\n }\n\n const route = findRoute(router, req.method, matchRoute);\n\n if (!route) {\n return Response.json(\n { error: `Fragno: Route for '${definition.name}' not found`, code: \"ROUTE_NOT_FOUND\" },\n { status: 404 },\n );\n }\n\n const { handler, inputSchema, outputSchema, path } = route.data;\n\n const outputContext = new RequestOutputContext(outputSchema);\n\n // Create mutable request state that can be modified by middleware\n // Clone the request to read body as both text and JSON without consuming original stream\n let requestBody: RequestBodyType = undefined;\n let rawBody: string | undefined = undefined;\n\n if (req.body instanceof ReadableStream) {\n // Clone request to make sure we don't consume body stream\n const clonedReq = req.clone();\n\n // Get raw text\n rawBody = await clonedReq.text();\n\n // Parse JSON if body is not empty\n if (rawBody) {\n try {\n requestBody = JSON.parse(rawBody);\n } catch {\n // If JSON parsing fails, keep body as undefined\n // This handles cases where body is not JSON\n requestBody = undefined;\n }\n }\n }\n\n const requestState = new MutableRequestState({\n pathParams: route.params ?? {},\n searchParams: url.searchParams,\n body: requestBody,\n headers: new Headers(req.headers),\n });\n\n if (middlewareHandler) {\n const middlewareInputContext = new RequestMiddlewareInputContext(routes, {\n method: req.method as HTTPMethod,\n path,\n request: req,\n state: requestState,\n });\n\n const middlewareOutputContext = new RequestMiddlewareOutputContext(dependencies, services);\n\n try {\n const middlewareResult = await middlewareHandler(\n middlewareInputContext,\n middlewareOutputContext,\n );\n if (middlewareResult !== undefined) {\n return middlewareResult;\n }\n } catch (error) {\n console.error(\"Error in middleware\", error);\n\n if (error instanceof FragnoApiError) {\n // TODO: If a validation error occurs in middleware (when calling `await input.valid()`)\n // the processing is short-circuited and a potential `catch` block around the call\n // to `input.valid()` in the actual handler will not be executed.\n return error.toResponse();\n }\n\n return Response.json(\n { error: \"Internal server error\", code: \"INTERNAL_SERVER_ERROR\" },\n { status: 500 },\n );\n }\n }\n\n const inputContext = await RequestInputContext.fromRequest({\n request: req,\n method: req.method,\n path,\n pathParams: (route.params ?? {}) as ExtractPathParams<typeof path>,\n inputSchema,\n state: requestState,\n rawBody,\n });\n\n try {\n const result = await handler(inputContext, outputContext);\n return result;\n } catch (error) {\n console.error(\"Error in handler\", error);\n\n if (error instanceof FragnoApiError) {\n return error.toResponse();\n }\n\n return Response.json(\n { error: \"Internal server error\", code: \"INTERNAL_SERVER_ERROR\" },\n { status: 500 },\n );\n }\n },\n };\n\n return fragment;\n}\n"],"mappings":"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"}
|