@fragno-dev/core 0.1.11 → 0.2.2
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 +87 -69
- package/CHANGELOG.md +79 -0
- package/dist/api/api.d.ts +21 -2
- package/dist/api/api.d.ts.map +1 -1
- package/dist/api/api.js +2 -1
- package/dist/api/api.js.map +1 -1
- package/dist/api/bind-services.d.ts +0 -1
- package/dist/api/bind-services.d.ts.map +1 -1
- package/dist/api/bind-services.js.map +1 -1
- package/dist/api/error.d.ts.map +1 -1
- package/dist/api/error.js.map +1 -1
- package/dist/api/fragment-definition-builder.d.ts +32 -40
- package/dist/api/fragment-definition-builder.d.ts.map +1 -1
- package/dist/api/fragment-definition-builder.js +15 -21
- package/dist/api/fragment-definition-builder.js.map +1 -1
- package/dist/api/fragment-instantiator.d.ts +51 -30
- package/dist/api/fragment-instantiator.d.ts.map +1 -1
- package/dist/api/fragment-instantiator.js +201 -52
- package/dist/api/fragment-instantiator.js.map +1 -1
- package/dist/api/request-context-storage.d.ts +4 -0
- package/dist/api/request-context-storage.d.ts.map +1 -1
- package/dist/api/request-context-storage.js +6 -0
- package/dist/api/request-context-storage.js.map +1 -1
- package/dist/api/request-input-context.d.ts +57 -1
- package/dist/api/request-input-context.d.ts.map +1 -1
- package/dist/api/request-input-context.js +67 -0
- package/dist/api/request-input-context.js.map +1 -1
- package/dist/api/request-middleware.d.ts +2 -2
- package/dist/api/request-middleware.d.ts.map +1 -1
- package/dist/api/request-middleware.js.map +1 -1
- package/dist/api/request-output-context.d.ts +1 -1
- package/dist/api/request-output-context.d.ts.map +1 -1
- package/dist/api/request-output-context.js.map +1 -1
- package/dist/api/route-caller.d.ts +30 -0
- package/dist/api/route-caller.d.ts.map +1 -0
- package/dist/api/route-caller.js +63 -0
- package/dist/api/route-caller.js.map +1 -0
- package/dist/api/route-handler-input-options.d.ts.map +1 -1
- package/dist/api/route.d.ts +8 -8
- package/dist/api/route.d.ts.map +1 -1
- package/dist/api/route.js.map +1 -1
- package/dist/api/shared-types.d.ts.map +1 -1
- package/dist/client/client-error.d.ts.map +1 -1
- package/dist/client/client-error.js.map +1 -1
- package/dist/client/client.d.ts +90 -50
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +128 -16
- package/dist/client/client.js.map +1 -1
- package/dist/client/client.svelte.d.ts +6 -5
- package/dist/client/client.svelte.d.ts.map +1 -1
- package/dist/client/client.svelte.js +10 -2
- package/dist/client/client.svelte.js.map +1 -1
- package/dist/client/internal/ndjson-streaming.js.map +1 -1
- package/dist/client/react.d.ts +5 -4
- package/dist/client/react.d.ts.map +1 -1
- package/dist/client/react.js +104 -12
- package/dist/client/react.js.map +1 -1
- package/dist/client/solid.d.ts +7 -5
- package/dist/client/solid.d.ts.map +1 -1
- package/dist/client/solid.js +23 -9
- package/dist/client/solid.js.map +1 -1
- package/dist/client/vanilla.d.ts +16 -4
- package/dist/client/vanilla.d.ts.map +1 -1
- package/dist/client/vanilla.js +21 -1
- package/dist/client/vanilla.js.map +1 -1
- package/dist/client/vue.d.ts +10 -4
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +24 -1
- package/dist/client/vue.js.map +1 -1
- package/dist/id.d.ts +2 -0
- package/dist/id.js +3 -0
- package/dist/internal/cuid.d.ts +16 -0
- package/dist/internal/cuid.d.ts.map +1 -0
- package/dist/internal/cuid.js +82 -0
- package/dist/internal/cuid.js.map +1 -0
- package/dist/internal/trace-context.d.ts +23 -0
- package/dist/internal/trace-context.d.ts.map +1 -0
- package/dist/internal/trace-context.js +14 -0
- package/dist/internal/trace-context.js.map +1 -0
- package/dist/mod-client.d.ts +7 -20
- package/dist/mod-client.d.ts.map +1 -1
- package/dist/mod-client.js +25 -13
- package/dist/mod-client.js.map +1 -1
- package/dist/mod.d.ts +8 -6
- package/dist/mod.js +3 -1
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +33 -0
- package/dist/runtime.js.map +1 -0
- package/dist/test/test.d.ts +6 -6
- package/dist/test/test.d.ts.map +1 -1
- package/dist/test/test.js.map +1 -1
- package/dist/util/ssr.js.map +1 -1
- package/package.json +42 -52
- package/src/api/api.test.ts +3 -1
- package/src/api/api.ts +28 -0
- package/src/api/bind-services.ts +0 -5
- package/src/api/error.ts +1 -0
- package/src/api/fragment-definition-builder.extend.test.ts +2 -1
- package/src/api/fragment-definition-builder.test.ts +2 -1
- package/src/api/fragment-definition-builder.ts +56 -112
- package/src/api/fragment-instantiator.test.ts +311 -166
- package/src/api/fragment-instantiator.ts +470 -131
- package/src/api/fragment-services.test.ts +1 -0
- package/src/api/internal/path-runtime.test.ts +8 -0
- package/src/api/internal/path-type.test.ts +3 -1
- package/src/api/internal/route.test.ts +1 -0
- package/src/api/request-context-storage.ts +7 -0
- package/src/api/request-input-context.test.ts +156 -2
- package/src/api/request-input-context.ts +87 -1
- package/src/api/request-middleware.test.ts +43 -2
- package/src/api/request-middleware.ts +4 -3
- package/src/api/request-output-context.test.ts +3 -1
- package/src/api/request-output-context.ts +2 -1
- package/src/api/route-caller.test.ts +195 -0
- package/src/api/route-caller.ts +167 -0
- package/src/api/route-handler-input-options.ts +2 -1
- package/src/api/route.test.ts +4 -2
- package/src/api/route.ts +9 -3
- package/src/api/shared-types.ts +2 -1
- package/src/client/client-builder.test.ts +4 -2
- package/src/client/client-error.test.ts +2 -1
- package/src/client/client-error.ts +1 -1
- package/src/client/client-types.test.ts +19 -5
- package/src/client/client.ssr.test.ts +6 -4
- package/src/client/client.svelte.test.ts +18 -9
- package/src/client/client.svelte.ts +38 -13
- package/src/client/client.test.ts +244 -10
- package/src/client/client.ts +473 -148
- package/src/client/internal/ndjson-streaming.test.ts +6 -3
- package/src/client/internal/ndjson-streaming.ts +1 -0
- package/src/client/react.test.ts +176 -6
- package/src/client/react.ts +226 -31
- package/src/client/solid.test.ts +29 -5
- package/src/client/solid.ts +60 -22
- package/src/client/vanilla.test.ts +148 -6
- package/src/client/vanilla.ts +63 -9
- package/src/client/vue.test.ts +397 -8
- package/src/client/vue.ts +74 -4
- package/src/id.ts +1 -0
- package/src/internal/cuid.test.ts +164 -0
- package/src/internal/cuid.ts +133 -0
- package/src/internal/trace-context.ts +35 -0
- package/src/mod-client.ts +55 -9
- package/src/mod.ts +9 -3
- package/src/runtime.ts +48 -0
- package/src/test/test.test.ts +4 -2
- package/src/test/test.ts +14 -7
- package/src/util/async.test.ts +1 -0
- package/src/util/content-type.test.ts +1 -0
- package/src/util/nanostores.test.ts +3 -1
- package/src/util/ssr.ts +1 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +2 -0
- package/vitest.config.ts +2 -1
|
@@ -1,22 +1,58 @@
|
|
|
1
1
|
import { instantiatedFragmentFakeSymbol } from "../internal/symbols.js";
|
|
2
2
|
import { resolveRouteFactories } from "./route.js";
|
|
3
|
+
import { recordTraceEvent } from "../internal/trace-context.js";
|
|
3
4
|
import { FragnoApiError } from "./error.js";
|
|
5
|
+
import { parseFragnoResponse } from "./fragno-response.js";
|
|
6
|
+
import { bindServicesToContext } from "./bind-services.js";
|
|
4
7
|
import { getMountRoute } from "./internal/route.js";
|
|
8
|
+
import { MutableRequestState } from "./mutable-request-state.js";
|
|
9
|
+
import { RequestContextStorage } from "./request-context-storage.js";
|
|
5
10
|
import { RequestInputContext } from "./request-input-context.js";
|
|
6
11
|
import { RequestOutputContext } from "./request-output-context.js";
|
|
7
|
-
import { MutableRequestState } from "./mutable-request-state.js";
|
|
8
12
|
import { RequestMiddlewareInputContext, RequestMiddlewareOutputContext } from "./request-middleware.js";
|
|
9
|
-
import { parseFragnoResponse } from "./fragno-response.js";
|
|
10
|
-
import { RequestContextStorage } from "./request-context-storage.js";
|
|
11
|
-
import { bindServicesToContext } from "./bind-services.js";
|
|
12
13
|
import { addRoute, createRouter, findRoute } from "rou3";
|
|
13
14
|
|
|
14
15
|
//#region src/api/fragment-instantiator.ts
|
|
16
|
+
const requestSourceSymbol = Symbol.for("fragno-request-source");
|
|
17
|
+
const requestRouteSymbol = Symbol.for("fragno-request-route");
|
|
18
|
+
const requestWaitUntilSymbol = Symbol.for("fragno-request-wait-until");
|
|
19
|
+
const serializeHeadersForTrace = (headers) => Array.from(headers.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
20
|
+
const serializeQueryForTrace = (query) => Array.from(query.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
21
|
+
const serializeBodyForTrace = (body) => {
|
|
22
|
+
if (body instanceof FormData) return {
|
|
23
|
+
type: "form-data",
|
|
24
|
+
entries: Array.from(body.entries()).map(([key, value]) => {
|
|
25
|
+
if (value instanceof Blob) return [key, {
|
|
26
|
+
type: "blob",
|
|
27
|
+
size: value.size,
|
|
28
|
+
mime: value.type
|
|
29
|
+
}];
|
|
30
|
+
return [key, value];
|
|
31
|
+
})
|
|
32
|
+
};
|
|
33
|
+
if (body instanceof Blob) return {
|
|
34
|
+
type: "blob",
|
|
35
|
+
size: body.size,
|
|
36
|
+
mime: body.type
|
|
37
|
+
};
|
|
38
|
+
if (body instanceof ReadableStream) return { type: "stream" };
|
|
39
|
+
return body;
|
|
40
|
+
};
|
|
41
|
+
const INTERNAL_ROUTE_PREFIX = "/_internal";
|
|
42
|
+
function normalizeRoutePrefix(prefix) {
|
|
43
|
+
if (!prefix.startsWith("/")) prefix = `/${prefix}`;
|
|
44
|
+
return prefix.endsWith("/") && prefix.length > 1 ? prefix.slice(0, -1) : prefix;
|
|
45
|
+
}
|
|
46
|
+
function joinRoutePath(prefix, path) {
|
|
47
|
+
const normalizedPrefix = normalizeRoutePrefix(prefix);
|
|
48
|
+
if (!path || path === "/") return normalizedPrefix;
|
|
49
|
+
return `${normalizedPrefix}${path.startsWith("/") ? path : `/${path}`}`;
|
|
50
|
+
}
|
|
15
51
|
/**
|
|
16
52
|
* Instantiated fragment class with encapsulated state.
|
|
17
53
|
* Provides the same public API as the old FragnoInstantiatedFragment but with better encapsulation.
|
|
18
54
|
*/
|
|
19
|
-
var FragnoInstantiatedFragment = class {
|
|
55
|
+
var FragnoInstantiatedFragment = class FragnoInstantiatedFragment {
|
|
20
56
|
[instantiatedFragmentFakeSymbol] = instantiatedFragmentFakeSymbol;
|
|
21
57
|
#name;
|
|
22
58
|
#routes;
|
|
@@ -30,7 +66,7 @@ var FragnoInstantiatedFragment = class {
|
|
|
30
66
|
#contextStorage;
|
|
31
67
|
#createRequestStorage;
|
|
32
68
|
#options;
|
|
33
|
-
#
|
|
69
|
+
#internalData;
|
|
34
70
|
constructor(params) {
|
|
35
71
|
this.#name = params.name;
|
|
36
72
|
this.#routes = params.routes;
|
|
@@ -42,7 +78,7 @@ var FragnoInstantiatedFragment = class {
|
|
|
42
78
|
this.#contextStorage = params.storage;
|
|
43
79
|
this.#createRequestStorage = params.createRequestStorage;
|
|
44
80
|
this.#options = params.options;
|
|
45
|
-
this.#
|
|
81
|
+
this.#internalData = params.internalData ?? {};
|
|
46
82
|
this.#router = createRouter();
|
|
47
83
|
for (const routeConfig of this.#routes) addRoute(this.#router, routeConfig.method.toUpperCase(), routeConfig.path, routeConfig);
|
|
48
84
|
this.handler = this.handler.bind(this);
|
|
@@ -66,7 +102,7 @@ var FragnoInstantiatedFragment = class {
|
|
|
66
102
|
return {
|
|
67
103
|
deps: this.#deps,
|
|
68
104
|
options: this.#options,
|
|
69
|
-
|
|
105
|
+
...this.#internalData
|
|
70
106
|
};
|
|
71
107
|
}
|
|
72
108
|
/**
|
|
@@ -78,17 +114,45 @@ var FragnoInstantiatedFragment = class {
|
|
|
78
114
|
this.#middlewareHandler = handler;
|
|
79
115
|
return this;
|
|
80
116
|
}
|
|
81
|
-
#withRequestStorage(callback) {
|
|
117
|
+
#withRequestStorage(callback, source = "context", routeInfo, lifecycleContext) {
|
|
82
118
|
if (!this.#serviceThisContext && !this.#handlerThisContext) return callback();
|
|
83
119
|
const storageData = this.#createRequestStorage ? this.#createRequestStorage() : {};
|
|
120
|
+
if (storageData && typeof storageData === "object") {
|
|
121
|
+
const metadataTarget = storageData;
|
|
122
|
+
metadataTarget[requestSourceSymbol] = source;
|
|
123
|
+
if (routeInfo) metadataTarget[requestRouteSymbol] = routeInfo;
|
|
124
|
+
if (lifecycleContext?.waitUntil) metadataTarget[requestWaitUntilSymbol] = lifecycleContext.waitUntil;
|
|
125
|
+
}
|
|
84
126
|
return this.#contextStorage.run(storageData, callback);
|
|
85
127
|
}
|
|
86
128
|
inContext(callback) {
|
|
87
129
|
if (this.#handlerThisContext) {
|
|
88
130
|
const boundCallback = callback.bind(this.#handlerThisContext);
|
|
89
|
-
return this.#withRequestStorage(boundCallback);
|
|
131
|
+
return this.#withRequestStorage(boundCallback, "context");
|
|
90
132
|
}
|
|
91
|
-
return this.#withRequestStorage(callback);
|
|
133
|
+
return this.#withRequestStorage(callback, "context");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Execute multiple service calls within a handler context.
|
|
137
|
+
* If called outside a request context, it will create one automatically.
|
|
138
|
+
* Pass a factory so service calls are created inside the active context.
|
|
139
|
+
* Primarily used by database fragments (handlerTx).
|
|
140
|
+
*/
|
|
141
|
+
async callServices(serviceCalls) {
|
|
142
|
+
const handlerContext = this.#handlerThisContext;
|
|
143
|
+
if (!handlerContext?.handlerTx) throw new Error("callServices is only supported for fragments with handlerTx (database fragments).");
|
|
144
|
+
let callWasArray = false;
|
|
145
|
+
const execute = () => {
|
|
146
|
+
return handlerContext.handlerTx().withServiceCalls(() => {
|
|
147
|
+
const calls = serviceCalls();
|
|
148
|
+
callWasArray = Array.isArray(calls);
|
|
149
|
+
return callWasArray ? calls : [calls];
|
|
150
|
+
}).execute();
|
|
151
|
+
};
|
|
152
|
+
const result = this.#contextStorage.hasStore() ? await execute() : await this.#withRequestStorage(execute, "context");
|
|
153
|
+
if (callWasArray) return result;
|
|
154
|
+
const [first] = result;
|
|
155
|
+
return first;
|
|
92
156
|
}
|
|
93
157
|
/**
|
|
94
158
|
* Get framework-specific handlers for this fragment.
|
|
@@ -147,7 +211,7 @@ var FragnoInstantiatedFragment = class {
|
|
|
147
211
|
* Main request handler for this fragment.
|
|
148
212
|
* Handles routing, middleware, and error handling.
|
|
149
213
|
*/
|
|
150
|
-
async handler(req) {
|
|
214
|
+
async handler(req, lifecycleContext) {
|
|
151
215
|
const url = new URL(req.url);
|
|
152
216
|
const pathname = url.pathname;
|
|
153
217
|
const matchRoute = pathname.startsWith(this.#mountRoute) ? pathname.slice(this.#mountRoute.length) : null;
|
|
@@ -160,30 +224,65 @@ var FragnoInstantiatedFragment = class {
|
|
|
160
224
|
error: `Fragno: Route for '${this.#name}' not found`,
|
|
161
225
|
code: "ROUTE_NOT_FOUND"
|
|
162
226
|
}, { status: 404 });
|
|
227
|
+
const routeConfig = route.data;
|
|
228
|
+
const expectedContentType = routeConfig.contentType ?? "application/json";
|
|
163
229
|
let requestBody = void 0;
|
|
164
230
|
let rawBody = void 0;
|
|
165
231
|
if (req.body instanceof ReadableStream) {
|
|
166
|
-
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
232
|
+
const requestContentType = (req.headers.get("content-type") ?? "").toLowerCase();
|
|
233
|
+
if (expectedContentType === "multipart/form-data") {
|
|
234
|
+
if (!requestContentType.includes("multipart/form-data")) return Response.json({
|
|
235
|
+
error: `This endpoint expects multipart/form-data, but received: ${requestContentType || "no content-type"}`,
|
|
236
|
+
code: "UNSUPPORTED_MEDIA_TYPE"
|
|
237
|
+
}, { status: 415 });
|
|
238
|
+
try {
|
|
239
|
+
requestBody = await req.formData();
|
|
240
|
+
} catch {
|
|
241
|
+
return Response.json({
|
|
242
|
+
error: "Failed to parse multipart form data",
|
|
243
|
+
code: "INVALID_REQUEST_BODY"
|
|
244
|
+
}, { status: 400 });
|
|
245
|
+
}
|
|
246
|
+
} else if (expectedContentType === "application/octet-stream") {
|
|
247
|
+
if (!requestContentType.includes("application/octet-stream")) return Response.json({
|
|
248
|
+
error: `This endpoint expects application/octet-stream, but received: ${requestContentType || "no content-type"}`,
|
|
249
|
+
code: "UNSUPPORTED_MEDIA_TYPE"
|
|
250
|
+
}, { status: 415 });
|
|
251
|
+
requestBody = req.body ?? new ReadableStream();
|
|
252
|
+
} else {
|
|
253
|
+
if (requestContentType.includes("multipart/form-data")) return Response.json({
|
|
254
|
+
error: `This endpoint expects JSON, but received multipart/form-data. Use a route with contentType: "multipart/form-data" for file uploads.`,
|
|
255
|
+
code: "UNSUPPORTED_MEDIA_TYPE"
|
|
256
|
+
}, { status: 415 });
|
|
257
|
+
rawBody = await req.clone().text();
|
|
258
|
+
if (rawBody) try {
|
|
259
|
+
requestBody = JSON.parse(rawBody);
|
|
260
|
+
} catch {
|
|
261
|
+
requestBody = void 0;
|
|
262
|
+
}
|
|
171
263
|
}
|
|
172
264
|
}
|
|
265
|
+
const decodedRouteParams = {};
|
|
266
|
+
for (const [key, value] of Object.entries(route.params ?? {})) decodedRouteParams[key] = decodeURIComponent(value);
|
|
173
267
|
const requestState = new MutableRequestState({
|
|
174
|
-
pathParams:
|
|
268
|
+
pathParams: decodedRouteParams,
|
|
175
269
|
searchParams: url.searchParams,
|
|
176
270
|
body: requestBody,
|
|
177
271
|
headers: new Headers(req.headers)
|
|
178
272
|
});
|
|
273
|
+
const fullRoutePath = this.#mountRoute && this.#mountRoute !== "/" ? `${this.#mountRoute}${routeConfig.path}` : routeConfig.path;
|
|
274
|
+
const routeInfo = {
|
|
275
|
+
method: routeConfig.method,
|
|
276
|
+
path: routeConfig.path,
|
|
277
|
+
mountRoute: this.#mountRoute,
|
|
278
|
+
fullPath: fullRoutePath
|
|
279
|
+
};
|
|
179
280
|
const executeRequest = async () => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (middlewareResult !== void 0) return middlewareResult;
|
|
183
|
-
}
|
|
281
|
+
const middlewareResult = await this.#executeMiddleware(req, route, requestState);
|
|
282
|
+
if (middlewareResult !== void 0) return middlewareResult;
|
|
184
283
|
return this.#executeHandler(req, route, requestState, rawBody);
|
|
185
284
|
};
|
|
186
|
-
return this.#withRequestStorage(executeRequest);
|
|
285
|
+
return this.#withRequestStorage(executeRequest, "route", routeInfo, lifecycleContext);
|
|
187
286
|
}
|
|
188
287
|
/**
|
|
189
288
|
* Call a route directly with typed inputs and outputs.
|
|
@@ -202,7 +301,8 @@ var FragnoInstantiatedFragment = class {
|
|
|
202
301
|
error: `Route ${method} ${path} not found`,
|
|
203
302
|
code: "ROUTE_NOT_FOUND"
|
|
204
303
|
}, { status: 404 });
|
|
205
|
-
const { pathParams = {},
|
|
304
|
+
const { pathParams = {}, query, headers } = inputOptions || {};
|
|
305
|
+
const body = inputOptions && "body" in inputOptions ? inputOptions.body : void 0;
|
|
206
306
|
const searchParams = query instanceof URLSearchParams ? query : query ? new URLSearchParams(query) : new URLSearchParams();
|
|
207
307
|
const requestHeaders = headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();
|
|
208
308
|
const inputContext = new RequestInputContext({
|
|
@@ -215,7 +315,23 @@ var FragnoInstantiatedFragment = class {
|
|
|
215
315
|
inputSchema: route.inputSchema,
|
|
216
316
|
shouldValidateInput: true
|
|
217
317
|
});
|
|
318
|
+
recordTraceEvent({
|
|
319
|
+
type: "route-input",
|
|
320
|
+
method: route.method,
|
|
321
|
+
path: route.path,
|
|
322
|
+
pathParams: pathParams ?? {},
|
|
323
|
+
queryParams: serializeQueryForTrace(searchParams),
|
|
324
|
+
headers: serializeHeadersForTrace(requestHeaders),
|
|
325
|
+
body: serializeBodyForTrace(body)
|
|
326
|
+
});
|
|
218
327
|
const outputContext = new RequestOutputContext(route.outputSchema);
|
|
328
|
+
const fullRoutePath = this.#mountRoute && this.#mountRoute !== "/" ? `${this.#mountRoute}${route.path}` : route.path;
|
|
329
|
+
const routeInfo = {
|
|
330
|
+
method: route.method,
|
|
331
|
+
path: route.path,
|
|
332
|
+
mountRoute: this.#mountRoute,
|
|
333
|
+
fullPath: fullRoutePath
|
|
334
|
+
};
|
|
219
335
|
const executeHandler = async () => {
|
|
220
336
|
try {
|
|
221
337
|
const thisContext = this.#handlerThisContext ?? {};
|
|
@@ -229,27 +345,50 @@ var FragnoInstantiatedFragment = class {
|
|
|
229
345
|
}, { status: 500 });
|
|
230
346
|
}
|
|
231
347
|
};
|
|
232
|
-
return this.#withRequestStorage(executeHandler);
|
|
348
|
+
return this.#withRequestStorage(executeHandler, "route", routeInfo);
|
|
233
349
|
}
|
|
234
350
|
/**
|
|
235
351
|
* Execute middleware for a request.
|
|
236
352
|
* Returns undefined if middleware allows the request to continue to the handler.
|
|
237
353
|
*/
|
|
238
354
|
async #executeMiddleware(req, route, requestState) {
|
|
239
|
-
if (!
|
|
355
|
+
if (!route) return;
|
|
240
356
|
const { path } = route.data;
|
|
241
|
-
|
|
357
|
+
return FragnoInstantiatedFragment.#runMiddlewareForFragment(this, {
|
|
358
|
+
req,
|
|
242
359
|
method: req.method,
|
|
243
360
|
path,
|
|
244
|
-
|
|
245
|
-
|
|
361
|
+
requestState
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
static async #runMiddlewareForFragment(fragment, options) {
|
|
365
|
+
if (!fragment.#middlewareHandler) return;
|
|
366
|
+
const middlewareInputContext = new RequestMiddlewareInputContext(options.routes ?? fragment.#routes, {
|
|
367
|
+
method: options.method,
|
|
368
|
+
path: options.path,
|
|
369
|
+
request: options.req,
|
|
370
|
+
state: options.requestState
|
|
246
371
|
});
|
|
247
|
-
const middlewareOutputContext = new RequestMiddlewareOutputContext(
|
|
372
|
+
const middlewareOutputContext = new RequestMiddlewareOutputContext(fragment.#deps, fragment.#services);
|
|
248
373
|
try {
|
|
249
|
-
const middlewareResult = await
|
|
374
|
+
const middlewareResult = await fragment.#middlewareHandler(middlewareInputContext, middlewareOutputContext);
|
|
375
|
+
recordTraceEvent({
|
|
376
|
+
type: "middleware-decision",
|
|
377
|
+
method: options.method,
|
|
378
|
+
path: options.path,
|
|
379
|
+
outcome: middlewareResult ? "deny" : "allow",
|
|
380
|
+
status: middlewareResult?.status
|
|
381
|
+
});
|
|
250
382
|
if (middlewareResult !== void 0) return middlewareResult;
|
|
251
383
|
} catch (error) {
|
|
252
384
|
console.error("Error in middleware", error);
|
|
385
|
+
recordTraceEvent({
|
|
386
|
+
type: "middleware-decision",
|
|
387
|
+
method: options.method,
|
|
388
|
+
path: options.path,
|
|
389
|
+
outcome: "deny",
|
|
390
|
+
status: error instanceof FragnoApiError ? error.status : 500
|
|
391
|
+
});
|
|
253
392
|
if (error instanceof FragnoApiError) return error.toResponse();
|
|
254
393
|
return Response.json({
|
|
255
394
|
error: "Internal server error",
|
|
@@ -275,6 +414,15 @@ var FragnoInstantiatedFragment = class {
|
|
|
275
414
|
state: requestState,
|
|
276
415
|
rawBody
|
|
277
416
|
});
|
|
417
|
+
recordTraceEvent({
|
|
418
|
+
type: "route-input",
|
|
419
|
+
method: req.method,
|
|
420
|
+
path,
|
|
421
|
+
pathParams: inputContext.pathParams,
|
|
422
|
+
queryParams: serializeQueryForTrace(requestState.searchParams),
|
|
423
|
+
headers: serializeHeadersForTrace(requestState.headers),
|
|
424
|
+
body: serializeBodyForTrace(requestState.body)
|
|
425
|
+
});
|
|
278
426
|
const outputContext = new RequestOutputContext(outputSchema);
|
|
279
427
|
try {
|
|
280
428
|
const contextForHandler = this.#handlerThisContext ?? {};
|
|
@@ -313,20 +461,12 @@ function instantiateFragment(definition, config, routesOrFactories, options, ser
|
|
|
313
461
|
deps = {};
|
|
314
462
|
} else throw error;
|
|
315
463
|
}
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
config,
|
|
321
|
-
options,
|
|
322
|
-
serviceDependencies: serviceImplementations
|
|
323
|
-
});
|
|
324
|
-
linkedFragmentInstances[name] = linkedFragment;
|
|
325
|
-
const services$1 = linkedFragment.services;
|
|
326
|
-
for (const [serviceName, service] of Object.entries(services$1)) linkedFragmentServices[serviceName] = service;
|
|
327
|
-
}
|
|
464
|
+
const mountRoute = getMountRoute({
|
|
465
|
+
name: definition.name,
|
|
466
|
+
mountRoute: options.mountRoute
|
|
467
|
+
});
|
|
328
468
|
const defineService = (services$1) => services$1;
|
|
329
|
-
const privateServices = {
|
|
469
|
+
const privateServices = {};
|
|
330
470
|
if (definition.privateServices) for (const [serviceName, factory] of Object.entries(definition.privateServices)) {
|
|
331
471
|
const serviceFactory = factory;
|
|
332
472
|
try {
|
|
@@ -398,16 +538,25 @@ function instantiateFragment(definition, config, routesOrFactories, options, ser
|
|
|
398
538
|
const serviceContext = contexts?.serviceContext;
|
|
399
539
|
const handlerContext = contexts?.handlerContext;
|
|
400
540
|
const boundServices = serviceContext ? bindServicesToContext(services, serviceContext) : services;
|
|
401
|
-
const
|
|
541
|
+
const internalData = definition.internalDataFactory?.({
|
|
402
542
|
config,
|
|
543
|
+
options,
|
|
403
544
|
deps,
|
|
404
545
|
services: boundServices,
|
|
405
546
|
serviceDeps: serviceImplementations ?? {}
|
|
406
|
-
}
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
547
|
+
}) ?? {};
|
|
548
|
+
const context = {
|
|
549
|
+
config,
|
|
550
|
+
deps,
|
|
551
|
+
services: boundServices,
|
|
552
|
+
serviceDeps: serviceImplementations ?? {}
|
|
553
|
+
};
|
|
554
|
+
const routes = resolveRouteFactories(context, routesOrFactories);
|
|
555
|
+
const prefixedInternalRoutes = (definition.internalRoutes ? resolveRouteFactories(context, definition.internalRoutes) : []).map((route) => ({
|
|
556
|
+
...route,
|
|
557
|
+
path: joinRoutePath(INTERNAL_ROUTE_PREFIX, route.path)
|
|
558
|
+
}));
|
|
559
|
+
const finalRoutes = prefixedInternalRoutes.length > 0 ? [...routes, ...prefixedInternalRoutes] : routes;
|
|
411
560
|
const createRequestStorageWithContext = definition.createRequestStorage ? () => definition.createRequestStorage({
|
|
412
561
|
config,
|
|
413
562
|
options,
|
|
@@ -415,7 +564,7 @@ function instantiateFragment(definition, config, routesOrFactories, options, ser
|
|
|
415
564
|
}) : void 0;
|
|
416
565
|
return new FragnoInstantiatedFragment({
|
|
417
566
|
name: definition.name,
|
|
418
|
-
routes,
|
|
567
|
+
routes: finalRoutes,
|
|
419
568
|
deps,
|
|
420
569
|
services: boundServices,
|
|
421
570
|
mountRoute,
|
|
@@ -424,7 +573,7 @@ function instantiateFragment(definition, config, routesOrFactories, options, ser
|
|
|
424
573
|
storage,
|
|
425
574
|
createRequestStorage: createRequestStorageWithContext,
|
|
426
575
|
options,
|
|
427
|
-
|
|
576
|
+
internalData
|
|
428
577
|
});
|
|
429
578
|
}
|
|
430
579
|
/**
|