@fragno-dev/core 0.1.10 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +139 -131
- package/CHANGELOG.md +63 -0
- package/dist/api/api.d.ts +23 -5
- package/dist/api/api.d.ts.map +1 -1
- package/dist/api/api.js.map +1 -1
- package/dist/api/fragment-definition-builder.d.ts +17 -7
- package/dist/api/fragment-definition-builder.d.ts.map +1 -1
- package/dist/api/fragment-definition-builder.js +3 -2
- package/dist/api/fragment-definition-builder.js.map +1 -1
- package/dist/api/fragment-instantiator.d.ts +129 -32
- package/dist/api/fragment-instantiator.d.ts.map +1 -1
- package/dist/api/fragment-instantiator.js +232 -50
- package/dist/api/fragment-instantiator.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 +1 -1
- package/dist/api/request-middleware.d.ts.map +1 -1
- package/dist/api/request-middleware.js.map +1 -1
- package/dist/api/route.d.ts +7 -7
- package/dist/api/route.d.ts.map +1 -1
- package/dist/api/route.js.map +1 -1
- package/dist/client/client.d.ts +4 -3
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +103 -7
- package/dist/client/client.js.map +1 -1
- package/dist/client/vue.d.ts +7 -3
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +16 -1
- package/dist/client/vue.js.map +1 -1
- 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 +5 -27
- package/dist/mod-client.d.ts.map +1 -1
- package/dist/mod-client.js +50 -13
- package/dist/mod-client.js.map +1 -1
- package/dist/mod.d.ts +4 -3
- package/dist/mod.js +2 -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 +2 -2
- package/dist/test/test.d.ts.map +1 -1
- package/dist/test/test.js.map +1 -1
- package/package.json +31 -18
- package/src/api/api.ts +24 -0
- package/src/api/fragment-definition-builder.ts +36 -17
- package/src/api/fragment-instantiator.test.ts +429 -1
- package/src/api/fragment-instantiator.ts +572 -58
- package/src/api/internal/path-runtime.test.ts +7 -0
- package/src/api/request-input-context.test.ts +152 -0
- package/src/api/request-input-context.ts +85 -0
- package/src/api/request-middleware.test.ts +47 -1
- package/src/api/request-middleware.ts +1 -1
- package/src/api/route.ts +7 -2
- package/src/client/client.test.ts +195 -0
- package/src/client/client.ts +185 -10
- package/src/client/vue.test.ts +253 -3
- package/src/client/vue.ts +44 -1
- package/src/internal/trace-context.ts +35 -0
- package/src/mod-client.ts +89 -9
- package/src/mod.ts +7 -1
- package/src/runtime.ts +48 -0
- package/src/test/test.ts +13 -4
- package/tsdown.config.ts +1 -0
|
@@ -27,10 +27,112 @@ import type { FragnoPublicConfig } from "./shared-types";
|
|
|
27
27
|
import { RequestContextStorage } from "./request-context-storage";
|
|
28
28
|
import { bindServicesToContext, type BoundServices } from "./bind-services";
|
|
29
29
|
import { instantiatedFragmentFakeSymbol } from "../internal/symbols";
|
|
30
|
+
import { recordTraceEvent } from "../internal/trace-context";
|
|
30
31
|
|
|
31
32
|
// Re-export types needed by consumers
|
|
32
33
|
export type { BoundServices };
|
|
33
34
|
|
|
35
|
+
type InternalRoutePrefix = "/_internal";
|
|
36
|
+
|
|
37
|
+
type JoinInternalRoutePath<TPath extends string> = TPath extends "" | "/"
|
|
38
|
+
? InternalRoutePrefix
|
|
39
|
+
: TPath extends `/${string}`
|
|
40
|
+
? `${InternalRoutePrefix}${TPath}`
|
|
41
|
+
: `${InternalRoutePrefix}/${TPath}`;
|
|
42
|
+
|
|
43
|
+
type PrefixInternalRoute<TRoute> =
|
|
44
|
+
TRoute extends FragnoRouteConfig<
|
|
45
|
+
infer TMethod,
|
|
46
|
+
infer TPath,
|
|
47
|
+
infer TInputSchema,
|
|
48
|
+
infer TOutputSchema,
|
|
49
|
+
infer TErrorCode,
|
|
50
|
+
infer TQueryParameters,
|
|
51
|
+
infer TThisContext
|
|
52
|
+
>
|
|
53
|
+
? FragnoRouteConfig<
|
|
54
|
+
TMethod,
|
|
55
|
+
JoinInternalRoutePath<TPath>,
|
|
56
|
+
TInputSchema,
|
|
57
|
+
TOutputSchema,
|
|
58
|
+
TErrorCode,
|
|
59
|
+
TQueryParameters,
|
|
60
|
+
TThisContext
|
|
61
|
+
>
|
|
62
|
+
: never;
|
|
63
|
+
|
|
64
|
+
type PrefixInternalRoutes<TRoutes extends readonly AnyFragnoRouteConfig[]> =
|
|
65
|
+
TRoutes extends readonly [...infer TRoutesTuple]
|
|
66
|
+
? { [K in keyof TRoutesTuple]: PrefixInternalRoute<TRoutesTuple[K]> }
|
|
67
|
+
: readonly AnyFragnoRouteConfig[];
|
|
68
|
+
|
|
69
|
+
type ExtractRoutesFromFragment<T> =
|
|
70
|
+
T extends FragnoInstantiatedFragment<
|
|
71
|
+
infer TRoutes,
|
|
72
|
+
infer _TDeps,
|
|
73
|
+
infer _TServices,
|
|
74
|
+
infer _TServiceThisContext,
|
|
75
|
+
infer _THandlerThisContext,
|
|
76
|
+
infer _TRequestStorage,
|
|
77
|
+
infer _TOptions,
|
|
78
|
+
infer _TLinkedFragments
|
|
79
|
+
>
|
|
80
|
+
? TRoutes
|
|
81
|
+
: never;
|
|
82
|
+
|
|
83
|
+
type InternalLinkedRoutes<TLinkedFragments> =
|
|
84
|
+
TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment>
|
|
85
|
+
? TLinkedFragments extends { _fragno_internal: infer TInternal }
|
|
86
|
+
? ExtractRoutesFromFragment<TInternal> extends readonly AnyFragnoRouteConfig[]
|
|
87
|
+
? PrefixInternalRoutes<ExtractRoutesFromFragment<TInternal>>
|
|
88
|
+
: readonly []
|
|
89
|
+
: readonly []
|
|
90
|
+
: readonly [];
|
|
91
|
+
|
|
92
|
+
export type RoutesWithInternal<
|
|
93
|
+
TRoutes extends readonly AnyFragnoRouteConfig[],
|
|
94
|
+
TLinkedFragments,
|
|
95
|
+
> = readonly [...TRoutes, ...InternalLinkedRoutes<TLinkedFragments>];
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Helper type to extract the instantiated fragment type from a fragment definition.
|
|
99
|
+
* This is useful for typing functions that accept instantiated fragments based on their definition.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* const myFragmentDef = defineFragment("my-fragment").build();
|
|
104
|
+
* type MyInstantiatedFragment = InstantiatedFragmentFromDefinition<typeof myFragmentDef>;
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export type InstantiatedFragmentFromDefinition<
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
|
+
TDef extends FragmentDefinition<any, any, any, any, any, any, any, any, any, any, any>,
|
|
110
|
+
> =
|
|
111
|
+
TDef extends FragmentDefinition<
|
|
112
|
+
infer _TConfig,
|
|
113
|
+
infer TOptions,
|
|
114
|
+
infer TDeps,
|
|
115
|
+
infer TBaseServices,
|
|
116
|
+
infer TServices,
|
|
117
|
+
infer _TServiceDependencies,
|
|
118
|
+
infer _TPrivateServices,
|
|
119
|
+
infer TServiceThisContext,
|
|
120
|
+
infer THandlerThisContext,
|
|
121
|
+
infer TRequestStorage,
|
|
122
|
+
infer TLinkedFragments
|
|
123
|
+
>
|
|
124
|
+
? FragnoInstantiatedFragment<
|
|
125
|
+
RoutesWithInternal<readonly AnyFragnoRouteConfig[], TLinkedFragments>,
|
|
126
|
+
TDeps,
|
|
127
|
+
BoundServices<TBaseServices & TServices>,
|
|
128
|
+
TServiceThisContext,
|
|
129
|
+
THandlerThisContext,
|
|
130
|
+
TRequestStorage,
|
|
131
|
+
TOptions,
|
|
132
|
+
TLinkedFragments
|
|
133
|
+
>
|
|
134
|
+
: never;
|
|
135
|
+
|
|
34
136
|
type AstroHandlers = {
|
|
35
137
|
ALL: (req: Request) => Promise<Response>;
|
|
36
138
|
};
|
|
@@ -40,6 +142,34 @@ type ReactRouterHandlers = {
|
|
|
40
142
|
action: (args: { request: Request }) => Promise<Response>;
|
|
41
143
|
};
|
|
42
144
|
|
|
145
|
+
const serializeHeadersForTrace = (headers: Headers): [string, string][] =>
|
|
146
|
+
Array.from(headers.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
147
|
+
|
|
148
|
+
const serializeQueryForTrace = (query: URLSearchParams): [string, string][] =>
|
|
149
|
+
Array.from(query.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
150
|
+
|
|
151
|
+
const serializeBodyForTrace = (body: RequestBodyType): unknown => {
|
|
152
|
+
if (body instanceof FormData) {
|
|
153
|
+
const entries = Array.from(body.entries()).map(([key, value]) => {
|
|
154
|
+
if (value instanceof Blob) {
|
|
155
|
+
return [key, { type: "blob", size: value.size, mime: value.type }] as const;
|
|
156
|
+
}
|
|
157
|
+
return [key, value] as const;
|
|
158
|
+
});
|
|
159
|
+
return { type: "form-data", entries };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (body instanceof Blob) {
|
|
163
|
+
return { type: "blob", size: body.size, mime: body.type };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (body instanceof ReadableStream) {
|
|
167
|
+
return { type: "stream" };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return body;
|
|
171
|
+
};
|
|
172
|
+
|
|
43
173
|
type SolidStartHandlers = {
|
|
44
174
|
GET: (args: { request: Request }) => Promise<Response>;
|
|
45
175
|
POST: (args: { request: Request }) => Promise<Response>;
|
|
@@ -73,8 +203,7 @@ type HandlersByFramework = {
|
|
|
73
203
|
|
|
74
204
|
type FullstackFrameworks = keyof HandlersByFramework;
|
|
75
205
|
|
|
76
|
-
|
|
77
|
-
type AnyFragnoInstantiatedFragment = FragnoInstantiatedFragment<
|
|
206
|
+
export type AnyFragnoInstantiatedFragment = FragnoInstantiatedFragment<
|
|
78
207
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
208
|
any,
|
|
80
209
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -93,7 +222,65 @@ type AnyFragnoInstantiatedFragment = FragnoInstantiatedFragment<
|
|
|
93
222
|
any
|
|
94
223
|
>;
|
|
95
224
|
|
|
96
|
-
|
|
225
|
+
const INTERNAL_LINKED_FRAGMENT_NAME = "_fragno_internal";
|
|
226
|
+
const INTERNAL_ROUTE_PREFIX = "/_internal";
|
|
227
|
+
|
|
228
|
+
type InternalLinkedRouteMeta = {
|
|
229
|
+
fragment: AnyFragnoInstantiatedFragment;
|
|
230
|
+
originalPath: string;
|
|
231
|
+
routes: readonly AnyFragnoRouteConfig[];
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
type InternalLinkedRouteConfig = AnyFragnoRouteConfig & {
|
|
235
|
+
__internal?: InternalLinkedRouteMeta;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
function normalizeRoutePrefix(prefix: string): string {
|
|
239
|
+
if (!prefix.startsWith("/")) {
|
|
240
|
+
prefix = `/${prefix}`;
|
|
241
|
+
}
|
|
242
|
+
return prefix.endsWith("/") && prefix.length > 1 ? prefix.slice(0, -1) : prefix;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function joinRoutePath(prefix: string, path: string): string {
|
|
246
|
+
const normalizedPrefix = normalizeRoutePrefix(prefix);
|
|
247
|
+
if (!path || path === "/") {
|
|
248
|
+
return normalizedPrefix;
|
|
249
|
+
}
|
|
250
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
251
|
+
return `${normalizedPrefix}${normalizedPath}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function collectLinkedFragmentRoutes(
|
|
255
|
+
linkedFragments: Record<string, AnyFragnoInstantiatedFragment>,
|
|
256
|
+
): InternalLinkedRouteConfig[] {
|
|
257
|
+
const linkedRoutes: InternalLinkedRouteConfig[] = [];
|
|
258
|
+
|
|
259
|
+
for (const [name, fragment] of Object.entries(linkedFragments)) {
|
|
260
|
+
if (name !== INTERNAL_LINKED_FRAGMENT_NAME) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const internalRoutes = (fragment.routes ?? []) as readonly AnyFragnoRouteConfig[];
|
|
265
|
+
if (internalRoutes.length === 0) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
for (const route of internalRoutes) {
|
|
270
|
+
linkedRoutes.push({
|
|
271
|
+
...route,
|
|
272
|
+
path: joinRoutePath(INTERNAL_ROUTE_PREFIX, route.path),
|
|
273
|
+
__internal: {
|
|
274
|
+
fragment,
|
|
275
|
+
originalPath: route.path,
|
|
276
|
+
routes: internalRoutes,
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return linkedRoutes;
|
|
283
|
+
}
|
|
97
284
|
|
|
98
285
|
export interface FragnoFragmentSharedConfig<
|
|
99
286
|
TRoutes extends readonly FragnoRouteConfig<
|
|
@@ -122,7 +309,8 @@ export class FragnoInstantiatedFragment<
|
|
|
122
309
|
TRequestStorage = {},
|
|
123
310
|
TOptions extends FragnoPublicConfig = FragnoPublicConfig,
|
|
124
311
|
TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment> = {},
|
|
125
|
-
>
|
|
312
|
+
> implements IFragnoInstantiatedFragment
|
|
313
|
+
{
|
|
126
314
|
readonly [instantiatedFragmentFakeSymbol] = instantiatedFragmentFakeSymbol;
|
|
127
315
|
|
|
128
316
|
// Private fields
|
|
@@ -139,6 +327,7 @@ export class FragnoInstantiatedFragment<
|
|
|
139
327
|
#createRequestStorage?: () => TRequestStorage;
|
|
140
328
|
#options: TOptions;
|
|
141
329
|
#linkedFragments: TLinkedFragments;
|
|
330
|
+
#internalData: Record<string, unknown>;
|
|
142
331
|
|
|
143
332
|
constructor(params: {
|
|
144
333
|
name: string;
|
|
@@ -152,6 +341,7 @@ export class FragnoInstantiatedFragment<
|
|
|
152
341
|
createRequestStorage?: () => TRequestStorage;
|
|
153
342
|
options: TOptions;
|
|
154
343
|
linkedFragments?: TLinkedFragments;
|
|
344
|
+
internalData?: Record<string, unknown>;
|
|
155
345
|
}) {
|
|
156
346
|
this.#name = params.name;
|
|
157
347
|
this.#routes = params.routes;
|
|
@@ -164,6 +354,7 @@ export class FragnoInstantiatedFragment<
|
|
|
164
354
|
this.#createRequestStorage = params.createRequestStorage;
|
|
165
355
|
this.#options = params.options;
|
|
166
356
|
this.#linkedFragments = params.linkedFragments ?? ({} as TLinkedFragments);
|
|
357
|
+
this.#internalData = params.internalData ?? {};
|
|
167
358
|
|
|
168
359
|
// Build router
|
|
169
360
|
this.#router =
|
|
@@ -212,6 +403,7 @@ export class FragnoInstantiatedFragment<
|
|
|
212
403
|
deps: this.#deps,
|
|
213
404
|
options: this.#options,
|
|
214
405
|
linkedFragments: this.#linkedFragments,
|
|
406
|
+
...this.#internalData,
|
|
215
407
|
};
|
|
216
408
|
}
|
|
217
409
|
|
|
@@ -372,31 +564,90 @@ export class FragnoInstantiatedFragment<
|
|
|
372
564
|
);
|
|
373
565
|
}
|
|
374
566
|
|
|
375
|
-
//
|
|
567
|
+
// Get the expected content type from route config (default: application/json)
|
|
568
|
+
const routeConfig = route.data as InternalLinkedRouteConfig;
|
|
569
|
+
const expectedContentType = routeConfig.contentType ?? "application/json";
|
|
570
|
+
|
|
571
|
+
// Parse request body based on route's expected content type
|
|
376
572
|
let requestBody: RequestBodyType = undefined;
|
|
377
573
|
let rawBody: string | undefined = undefined;
|
|
378
574
|
|
|
379
575
|
if (req.body instanceof ReadableStream) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
576
|
+
const requestContentType = (req.headers.get("content-type") ?? "").toLowerCase();
|
|
577
|
+
|
|
578
|
+
if (expectedContentType === "multipart/form-data") {
|
|
579
|
+
// Route expects FormData (file uploads)
|
|
580
|
+
if (!requestContentType.includes("multipart/form-data")) {
|
|
581
|
+
return Response.json(
|
|
582
|
+
{
|
|
583
|
+
error: `This endpoint expects multipart/form-data, but received: ${requestContentType || "no content-type"}`,
|
|
584
|
+
code: "UNSUPPORTED_MEDIA_TYPE",
|
|
585
|
+
},
|
|
586
|
+
{ status: 415 },
|
|
587
|
+
);
|
|
588
|
+
}
|
|
385
589
|
|
|
386
|
-
// Parse JSON if body is not empty
|
|
387
|
-
if (rawBody) {
|
|
388
590
|
try {
|
|
389
|
-
requestBody =
|
|
591
|
+
requestBody = await req.formData();
|
|
390
592
|
} catch {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
593
|
+
return Response.json(
|
|
594
|
+
{ error: "Failed to parse multipart form data", code: "INVALID_REQUEST_BODY" },
|
|
595
|
+
{ status: 400 },
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
} else if (expectedContentType === "application/octet-stream") {
|
|
599
|
+
if (!requestContentType.includes("application/octet-stream")) {
|
|
600
|
+
return Response.json(
|
|
601
|
+
{
|
|
602
|
+
error: `This endpoint expects application/octet-stream, but received: ${requestContentType || "no content-type"}`,
|
|
603
|
+
code: "UNSUPPORTED_MEDIA_TYPE",
|
|
604
|
+
},
|
|
605
|
+
{ status: 415 },
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
requestBody = req.body ?? new ReadableStream<Uint8Array>();
|
|
610
|
+
} else {
|
|
611
|
+
// Route expects JSON (default)
|
|
612
|
+
// Note: We're lenient here - we accept requests without Content-Type header
|
|
613
|
+
// or with application/json. We reject multipart/form-data for JSON routes.
|
|
614
|
+
if (requestContentType.includes("multipart/form-data")) {
|
|
615
|
+
return Response.json(
|
|
616
|
+
{
|
|
617
|
+
error: `This endpoint expects JSON, but received multipart/form-data. Use a route with contentType: "multipart/form-data" for file uploads.`,
|
|
618
|
+
code: "UNSUPPORTED_MEDIA_TYPE",
|
|
619
|
+
},
|
|
620
|
+
{ status: 415 },
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Clone request to make sure we don't consume body stream
|
|
625
|
+
const clonedReq = req.clone();
|
|
626
|
+
|
|
627
|
+
// Get raw text
|
|
628
|
+
rawBody = await clonedReq.text();
|
|
629
|
+
|
|
630
|
+
// Parse JSON if body is not empty
|
|
631
|
+
if (rawBody) {
|
|
632
|
+
try {
|
|
633
|
+
requestBody = JSON.parse(rawBody);
|
|
634
|
+
} catch {
|
|
635
|
+
// If JSON parsing fails, keep body as undefined
|
|
636
|
+
// This handles cases where body is not JSON
|
|
637
|
+
requestBody = undefined;
|
|
638
|
+
}
|
|
394
639
|
}
|
|
395
640
|
}
|
|
396
641
|
}
|
|
397
642
|
|
|
643
|
+
// URL decode path params from rou3 (which returns encoded values)
|
|
644
|
+
const decodedRouteParams: Record<string, string> = {};
|
|
645
|
+
for (const [key, value] of Object.entries(route.params ?? {})) {
|
|
646
|
+
decodedRouteParams[key] = decodeURIComponent(value);
|
|
647
|
+
}
|
|
648
|
+
|
|
398
649
|
const requestState = new MutableRequestState({
|
|
399
|
-
pathParams:
|
|
650
|
+
pathParams: decodedRouteParams,
|
|
400
651
|
searchParams: url.searchParams,
|
|
401
652
|
body: requestBody,
|
|
402
653
|
headers: new Headers(req.headers),
|
|
@@ -404,11 +655,27 @@ export class FragnoInstantiatedFragment<
|
|
|
404
655
|
|
|
405
656
|
// Execute middleware and handler
|
|
406
657
|
const executeRequest = async (): Promise<Response> => {
|
|
407
|
-
//
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
658
|
+
// Parent middleware execution
|
|
659
|
+
const middlewareResult = await this.#executeMiddleware(req, route, requestState);
|
|
660
|
+
if (middlewareResult !== undefined) {
|
|
661
|
+
return middlewareResult;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Internal fragment middleware execution (if linked)
|
|
665
|
+
const internalMeta = routeConfig.__internal;
|
|
666
|
+
if (internalMeta) {
|
|
667
|
+
const internalResult = await FragnoInstantiatedFragment.#runMiddlewareForFragment(
|
|
668
|
+
internalMeta.fragment as AnyFragnoInstantiatedFragment,
|
|
669
|
+
{
|
|
670
|
+
req,
|
|
671
|
+
method: routeConfig.method,
|
|
672
|
+
path: internalMeta.originalPath,
|
|
673
|
+
requestState,
|
|
674
|
+
routes: internalMeta.routes,
|
|
675
|
+
},
|
|
676
|
+
);
|
|
677
|
+
if (internalResult !== undefined) {
|
|
678
|
+
return internalResult;
|
|
412
679
|
}
|
|
413
680
|
}
|
|
414
681
|
|
|
@@ -475,7 +742,6 @@ export class FragnoInstantiatedFragment<
|
|
|
475
742
|
? new URLSearchParams(query)
|
|
476
743
|
: new URLSearchParams();
|
|
477
744
|
|
|
478
|
-
// Convert headers to Headers if needed
|
|
479
745
|
const requestHeaders =
|
|
480
746
|
headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();
|
|
481
747
|
|
|
@@ -491,6 +757,16 @@ export class FragnoInstantiatedFragment<
|
|
|
491
757
|
shouldValidateInput: true, // Enable validation for production use
|
|
492
758
|
});
|
|
493
759
|
|
|
760
|
+
recordTraceEvent({
|
|
761
|
+
type: "route-input",
|
|
762
|
+
method: route.method,
|
|
763
|
+
path: route.path,
|
|
764
|
+
pathParams: (pathParams ?? {}) as Record<string, string>,
|
|
765
|
+
queryParams: serializeQueryForTrace(searchParams),
|
|
766
|
+
headers: serializeHeadersForTrace(requestHeaders),
|
|
767
|
+
body: serializeBodyForTrace(body),
|
|
768
|
+
});
|
|
769
|
+
|
|
494
770
|
// Construct RequestOutputContext
|
|
495
771
|
const outputContext = new RequestOutputContext(route.outputSchema);
|
|
496
772
|
|
|
@@ -527,32 +803,73 @@ export class FragnoInstantiatedFragment<
|
|
|
527
803
|
route: ReturnType<typeof findRoute>,
|
|
528
804
|
requestState: MutableRequestState,
|
|
529
805
|
): Promise<Response | undefined> {
|
|
530
|
-
if (!
|
|
806
|
+
if (!route) {
|
|
531
807
|
return undefined;
|
|
532
808
|
}
|
|
533
809
|
|
|
534
810
|
const { path } = route.data as AnyFragnoRouteConfig;
|
|
535
|
-
|
|
536
|
-
|
|
811
|
+
return FragnoInstantiatedFragment.#runMiddlewareForFragment(this, {
|
|
812
|
+
req,
|
|
537
813
|
method: req.method as HTTPMethod,
|
|
538
814
|
path,
|
|
539
|
-
|
|
540
|
-
state: requestState,
|
|
815
|
+
requestState,
|
|
541
816
|
});
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
static async #runMiddlewareForFragment(
|
|
820
|
+
fragment: AnyFragnoInstantiatedFragment,
|
|
821
|
+
options: {
|
|
822
|
+
req: Request;
|
|
823
|
+
method: HTTPMethod;
|
|
824
|
+
path: string;
|
|
825
|
+
requestState: MutableRequestState;
|
|
826
|
+
routes?: readonly AnyFragnoRouteConfig[];
|
|
827
|
+
},
|
|
828
|
+
): Promise<Response | undefined> {
|
|
829
|
+
if (!fragment.#middlewareHandler) {
|
|
830
|
+
return undefined;
|
|
831
|
+
}
|
|
542
832
|
|
|
543
|
-
const
|
|
833
|
+
const middlewareInputContext = new RequestMiddlewareInputContext(
|
|
834
|
+
(options.routes ?? fragment.#routes) as readonly AnyFragnoRouteConfig[],
|
|
835
|
+
{
|
|
836
|
+
method: options.method,
|
|
837
|
+
path: options.path,
|
|
838
|
+
request: options.req,
|
|
839
|
+
state: options.requestState,
|
|
840
|
+
},
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
const middlewareOutputContext = new RequestMiddlewareOutputContext(
|
|
844
|
+
fragment.#deps,
|
|
845
|
+
fragment.#services,
|
|
846
|
+
);
|
|
544
847
|
|
|
545
848
|
try {
|
|
546
|
-
const middlewareResult = await
|
|
849
|
+
const middlewareResult = await fragment.#middlewareHandler(
|
|
547
850
|
middlewareInputContext,
|
|
548
851
|
middlewareOutputContext,
|
|
549
852
|
);
|
|
853
|
+
recordTraceEvent({
|
|
854
|
+
type: "middleware-decision",
|
|
855
|
+
method: options.method,
|
|
856
|
+
path: options.path,
|
|
857
|
+
outcome: middlewareResult ? "deny" : "allow",
|
|
858
|
+
status: middlewareResult?.status,
|
|
859
|
+
});
|
|
550
860
|
if (middlewareResult !== undefined) {
|
|
551
861
|
return middlewareResult;
|
|
552
862
|
}
|
|
553
863
|
} catch (error) {
|
|
554
864
|
console.error("Error in middleware", error);
|
|
555
865
|
|
|
866
|
+
recordTraceEvent({
|
|
867
|
+
type: "middleware-decision",
|
|
868
|
+
method: options.method,
|
|
869
|
+
path: options.path,
|
|
870
|
+
outcome: "deny",
|
|
871
|
+
status: error instanceof FragnoApiError ? error.status : 500,
|
|
872
|
+
});
|
|
556
873
|
if (error instanceof FragnoApiError) {
|
|
557
874
|
return error.toResponse();
|
|
558
875
|
}
|
|
@@ -591,6 +908,16 @@ export class FragnoInstantiatedFragment<
|
|
|
591
908
|
rawBody,
|
|
592
909
|
});
|
|
593
910
|
|
|
911
|
+
recordTraceEvent({
|
|
912
|
+
type: "route-input",
|
|
913
|
+
method: req.method,
|
|
914
|
+
path,
|
|
915
|
+
pathParams: inputContext.pathParams as Record<string, string>,
|
|
916
|
+
queryParams: serializeQueryForTrace(requestState.searchParams),
|
|
917
|
+
headers: serializeHeadersForTrace(requestState.headers),
|
|
918
|
+
body: serializeBodyForTrace(requestState.body),
|
|
919
|
+
});
|
|
920
|
+
|
|
594
921
|
const outputContext = new RequestOutputContext(outputSchema);
|
|
595
922
|
|
|
596
923
|
try {
|
|
@@ -615,6 +942,18 @@ export class FragnoInstantiatedFragment<
|
|
|
615
942
|
}
|
|
616
943
|
}
|
|
617
944
|
|
|
945
|
+
/**
|
|
946
|
+
* Options for fragment instantiation.
|
|
947
|
+
*/
|
|
948
|
+
export interface InstantiationOptions {
|
|
949
|
+
/**
|
|
950
|
+
* If true, catches errors during initialization and returns stub implementations.
|
|
951
|
+
* This is useful for CLI tools that need to extract metadata (like database schemas)
|
|
952
|
+
* without requiring all dependencies to be fully initialized.
|
|
953
|
+
*/
|
|
954
|
+
dryRun?: boolean;
|
|
955
|
+
}
|
|
956
|
+
|
|
618
957
|
/**
|
|
619
958
|
* Core instantiation function that creates a fragment instance from a definition.
|
|
620
959
|
* This function validates dependencies, calls all callbacks, and wires everything together.
|
|
@@ -650,8 +989,9 @@ export function instantiateFragment<
|
|
|
650
989
|
routesOrFactories: TRoutesOrFactories,
|
|
651
990
|
options: TOptions,
|
|
652
991
|
serviceImplementations?: TServiceDependencies,
|
|
992
|
+
instantiationOptions?: InstantiationOptions,
|
|
653
993
|
): FragnoInstantiatedFragment<
|
|
654
|
-
FlattenRouteFactories<TRoutesOrFactories>,
|
|
994
|
+
RoutesWithInternal<FlattenRouteFactories<TRoutesOrFactories>, TLinkedFragments>,
|
|
655
995
|
TDeps,
|
|
656
996
|
BoundServices<TBaseServices & TServices>,
|
|
657
997
|
TServiceThisContext,
|
|
@@ -660,6 +1000,8 @@ export function instantiateFragment<
|
|
|
660
1000
|
TOptions,
|
|
661
1001
|
TLinkedFragments
|
|
662
1002
|
> {
|
|
1003
|
+
const { dryRun = false } = instantiationOptions ?? {};
|
|
1004
|
+
|
|
663
1005
|
// 1. Validate service dependencies
|
|
664
1006
|
const serviceDependencies = definition.serviceDependencies;
|
|
665
1007
|
if (serviceDependencies) {
|
|
@@ -675,7 +1017,21 @@ export function instantiateFragment<
|
|
|
675
1017
|
}
|
|
676
1018
|
|
|
677
1019
|
// 2. Call dependencies callback
|
|
678
|
-
|
|
1020
|
+
let deps: TDeps;
|
|
1021
|
+
try {
|
|
1022
|
+
deps = definition.dependencies?.({ config, options }) ?? ({} as TDeps);
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
if (dryRun) {
|
|
1025
|
+
console.warn(
|
|
1026
|
+
"Warning: Failed to initialize dependencies in dry run mode:",
|
|
1027
|
+
error instanceof Error ? error.message : String(error),
|
|
1028
|
+
);
|
|
1029
|
+
// Return empty deps - database fragments will add implicit deps later
|
|
1030
|
+
deps = {} as TDeps;
|
|
1031
|
+
} else {
|
|
1032
|
+
throw error;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
679
1035
|
|
|
680
1036
|
// 3. Instantiate linked fragments FIRST (before any services)
|
|
681
1037
|
// Their services will be merged into private services
|
|
@@ -717,28 +1073,54 @@ export function instantiateFragment<
|
|
|
717
1073
|
privateServices: TPrivateServices;
|
|
718
1074
|
defineService: <T>(svc: T & ThisType<TServiceThisContext>) => T;
|
|
719
1075
|
}) => unknown;
|
|
720
|
-
|
|
1076
|
+
|
|
1077
|
+
try {
|
|
1078
|
+
(privateServices as Record<string, unknown>)[serviceName] = serviceFactory({
|
|
1079
|
+
config,
|
|
1080
|
+
options,
|
|
1081
|
+
deps,
|
|
1082
|
+
serviceDeps: (serviceImplementations ?? {}) as TServiceDependencies,
|
|
1083
|
+
privateServices, // Pass the current state of private services (earlier ones are available)
|
|
1084
|
+
defineService,
|
|
1085
|
+
});
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
if (dryRun) {
|
|
1088
|
+
console.warn(
|
|
1089
|
+
`Warning: Failed to initialize private service '${serviceName}' in dry run mode:`,
|
|
1090
|
+
error instanceof Error ? error.message : String(error),
|
|
1091
|
+
);
|
|
1092
|
+
(privateServices as Record<string, unknown>)[serviceName] = {};
|
|
1093
|
+
} else {
|
|
1094
|
+
throw error;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// 5. Call baseServices callback (with access to private services including linked fragment services)
|
|
1101
|
+
let baseServices: TBaseServices;
|
|
1102
|
+
try {
|
|
1103
|
+
baseServices =
|
|
1104
|
+
definition.baseServices?.({
|
|
721
1105
|
config,
|
|
722
1106
|
options,
|
|
723
1107
|
deps,
|
|
724
1108
|
serviceDeps: (serviceImplementations ?? {}) as TServiceDependencies,
|
|
725
|
-
privateServices,
|
|
1109
|
+
privateServices,
|
|
726
1110
|
defineService,
|
|
727
|
-
});
|
|
1111
|
+
}) ?? ({} as TBaseServices);
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
if (dryRun) {
|
|
1114
|
+
console.warn(
|
|
1115
|
+
"Warning: Failed to initialize base services in dry run mode:",
|
|
1116
|
+
error instanceof Error ? error.message : String(error),
|
|
1117
|
+
);
|
|
1118
|
+
baseServices = {} as TBaseServices;
|
|
1119
|
+
} else {
|
|
1120
|
+
throw error;
|
|
728
1121
|
}
|
|
729
1122
|
}
|
|
730
1123
|
|
|
731
|
-
// 5. Call baseServices callback (with access to private services including linked fragment services)
|
|
732
|
-
const baseServices =
|
|
733
|
-
definition.baseServices?.({
|
|
734
|
-
config,
|
|
735
|
-
options,
|
|
736
|
-
deps,
|
|
737
|
-
serviceDeps: (serviceImplementations ?? {}) as TServiceDependencies,
|
|
738
|
-
privateServices,
|
|
739
|
-
defineService,
|
|
740
|
-
}) ?? ({} as TBaseServices);
|
|
741
|
-
|
|
742
1124
|
// 6. Call namedServices factories (with access to private services including linked fragment services)
|
|
743
1125
|
const namedServices = {} as TServices;
|
|
744
1126
|
if (definition.namedServices) {
|
|
@@ -751,14 +1133,27 @@ export function instantiateFragment<
|
|
|
751
1133
|
privateServices: TPrivateServices;
|
|
752
1134
|
defineService: <T>(svc: T & ThisType<TServiceThisContext>) => T;
|
|
753
1135
|
}) => unknown;
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1136
|
+
|
|
1137
|
+
try {
|
|
1138
|
+
(namedServices as Record<string, unknown>)[serviceName] = serviceFactory({
|
|
1139
|
+
config,
|
|
1140
|
+
options,
|
|
1141
|
+
deps,
|
|
1142
|
+
serviceDeps: (serviceImplementations ?? {}) as TServiceDependencies,
|
|
1143
|
+
privateServices,
|
|
1144
|
+
defineService,
|
|
1145
|
+
});
|
|
1146
|
+
} catch (error) {
|
|
1147
|
+
if (dryRun) {
|
|
1148
|
+
console.warn(
|
|
1149
|
+
`Warning: Failed to initialize service '${serviceName}' in dry run mode:`,
|
|
1150
|
+
error instanceof Error ? error.message : String(error),
|
|
1151
|
+
);
|
|
1152
|
+
(namedServices as Record<string, unknown>)[serviceName] = {};
|
|
1153
|
+
} else {
|
|
1154
|
+
throw error;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
762
1157
|
}
|
|
763
1158
|
}
|
|
764
1159
|
|
|
@@ -784,6 +1179,13 @@ export function instantiateFragment<
|
|
|
784
1179
|
|
|
785
1180
|
const serviceContext = contexts?.serviceContext;
|
|
786
1181
|
const handlerContext = contexts?.handlerContext;
|
|
1182
|
+
const internalData =
|
|
1183
|
+
definition.internalDataFactory?.({
|
|
1184
|
+
config,
|
|
1185
|
+
options,
|
|
1186
|
+
deps,
|
|
1187
|
+
linkedFragments: linkedFragmentInstances,
|
|
1188
|
+
}) ?? {};
|
|
787
1189
|
|
|
788
1190
|
// 9. Bind services to serviceContext (restricted)
|
|
789
1191
|
// Services get the restricted context (for database fragments, this excludes execute methods)
|
|
@@ -796,7 +1198,12 @@ export function instantiateFragment<
|
|
|
796
1198
|
services: boundServices,
|
|
797
1199
|
serviceDeps: serviceImplementations ?? ({} as TServiceDependencies),
|
|
798
1200
|
};
|
|
799
|
-
const routes = resolveRouteFactories(context, routesOrFactories);
|
|
1201
|
+
const routes = resolveRouteFactories(context, routesOrFactories) as AnyFragnoRouteConfig[];
|
|
1202
|
+
const linkedRoutes = collectLinkedFragmentRoutes(
|
|
1203
|
+
linkedFragmentInstances as Record<string, AnyFragnoInstantiatedFragment>,
|
|
1204
|
+
);
|
|
1205
|
+
const finalRoutes =
|
|
1206
|
+
linkedRoutes.length > 0 ? [...routes, ...linkedRoutes] : (routes as AnyFragnoRouteConfig[]);
|
|
800
1207
|
|
|
801
1208
|
// 11. Calculate mount route
|
|
802
1209
|
const mountRoute = getMountRoute({
|
|
@@ -814,7 +1221,10 @@ export function instantiateFragment<
|
|
|
814
1221
|
// Handlers get handlerContext which may have more capabilities than serviceContext
|
|
815
1222
|
return new FragnoInstantiatedFragment({
|
|
816
1223
|
name: definition.name,
|
|
817
|
-
routes
|
|
1224
|
+
routes: finalRoutes as unknown as RoutesWithInternal<
|
|
1225
|
+
FlattenRouteFactories<TRoutesOrFactories>,
|
|
1226
|
+
TLinkedFragments
|
|
1227
|
+
>,
|
|
818
1228
|
deps,
|
|
819
1229
|
services: boundServices as BoundServices<TBaseServices & TServices>,
|
|
820
1230
|
mountRoute,
|
|
@@ -824,9 +1234,105 @@ export function instantiateFragment<
|
|
|
824
1234
|
createRequestStorage: createRequestStorageWithContext,
|
|
825
1235
|
options,
|
|
826
1236
|
linkedFragments: linkedFragmentInstances,
|
|
1237
|
+
internalData: internalData as Record<string, unknown>,
|
|
827
1238
|
});
|
|
828
1239
|
}
|
|
829
1240
|
|
|
1241
|
+
/**
|
|
1242
|
+
* Interface that defines the public API for a fragment instantiation builder.
|
|
1243
|
+
* Used to ensure consistency between real implementations and stubs.
|
|
1244
|
+
*/
|
|
1245
|
+
interface IFragmentInstantiationBuilder {
|
|
1246
|
+
/**
|
|
1247
|
+
* Get the fragment definition
|
|
1248
|
+
*/
|
|
1249
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1250
|
+
get definition(): any;
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Get the configured routes
|
|
1254
|
+
*/
|
|
1255
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1256
|
+
get routes(): any;
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* Get the configuration
|
|
1260
|
+
*/
|
|
1261
|
+
get config(): unknown;
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Get the options
|
|
1265
|
+
*/
|
|
1266
|
+
get options(): unknown;
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Set the configuration for the fragment
|
|
1270
|
+
*/
|
|
1271
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1272
|
+
withConfig(config: any): unknown;
|
|
1273
|
+
|
|
1274
|
+
/**
|
|
1275
|
+
* Set the routes for the fragment
|
|
1276
|
+
*/
|
|
1277
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1278
|
+
withRoutes(routes: any): unknown;
|
|
1279
|
+
|
|
1280
|
+
/**
|
|
1281
|
+
* Set the options for the fragment (e.g., mountRoute, databaseAdapter)
|
|
1282
|
+
*/
|
|
1283
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1284
|
+
withOptions(options: any): unknown;
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Provide implementations for services that this fragment uses
|
|
1288
|
+
*/
|
|
1289
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1290
|
+
withServices(services: any): unknown;
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Build and return the instantiated fragment
|
|
1294
|
+
*/
|
|
1295
|
+
build(): IFragnoInstantiatedFragment;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* Interface that defines the public API for an instantiated fragment.
|
|
1300
|
+
* Used to ensure consistency between real implementations and stubs.
|
|
1301
|
+
*/
|
|
1302
|
+
interface IFragnoInstantiatedFragment {
|
|
1303
|
+
readonly [instantiatedFragmentFakeSymbol]: typeof instantiatedFragmentFakeSymbol;
|
|
1304
|
+
|
|
1305
|
+
get name(): string;
|
|
1306
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1307
|
+
get routes(): any;
|
|
1308
|
+
get services(): Record<string, unknown>;
|
|
1309
|
+
get mountRoute(): string;
|
|
1310
|
+
get $internal(): {
|
|
1311
|
+
deps: unknown;
|
|
1312
|
+
options: unknown;
|
|
1313
|
+
linkedFragments: unknown;
|
|
1314
|
+
} & Record<string, unknown>;
|
|
1315
|
+
|
|
1316
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1317
|
+
withMiddleware(handler: any): this;
|
|
1318
|
+
|
|
1319
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1320
|
+
inContext<T>(callback: any): T;
|
|
1321
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1322
|
+
inContext<T>(callback: any): Promise<T>;
|
|
1323
|
+
|
|
1324
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1325
|
+
handlersFor(framework: FullstackFrameworks): any;
|
|
1326
|
+
|
|
1327
|
+
handler(req: Request): Promise<Response>;
|
|
1328
|
+
|
|
1329
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1330
|
+
callRoute(method: HTTPMethod, path: string, inputOptions?: any): Promise<any>;
|
|
1331
|
+
|
|
1332
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1333
|
+
callRouteRaw(method: HTTPMethod, path: string, inputOptions?: any): Promise<Response>;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
830
1336
|
/**
|
|
831
1337
|
* Fluent builder for instantiating fragments.
|
|
832
1338
|
* Provides a type-safe API for configuring and building fragment instances.
|
|
@@ -844,7 +1350,8 @@ export class FragmentInstantiationBuilder<
|
|
|
844
1350
|
TRequestStorage,
|
|
845
1351
|
TRoutesOrFactories extends readonly AnyRouteOrFactory[],
|
|
846
1352
|
TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment>,
|
|
847
|
-
>
|
|
1353
|
+
> implements IFragmentInstantiationBuilder
|
|
1354
|
+
{
|
|
848
1355
|
#definition: FragmentDefinition<
|
|
849
1356
|
TConfig,
|
|
850
1357
|
TOptions,
|
|
@@ -978,7 +1485,7 @@ export class FragmentInstantiationBuilder<
|
|
|
978
1485
|
* Build and return the instantiated fragment
|
|
979
1486
|
*/
|
|
980
1487
|
build(): FragnoInstantiatedFragment<
|
|
981
|
-
FlattenRouteFactories<TRoutesOrFactories>,
|
|
1488
|
+
RoutesWithInternal<FlattenRouteFactories<TRoutesOrFactories>, TLinkedFragments>,
|
|
982
1489
|
TDeps,
|
|
983
1490
|
BoundServices<TBaseServices & TServices>,
|
|
984
1491
|
TServiceThisContext,
|
|
@@ -987,12 +1494,16 @@ export class FragmentInstantiationBuilder<
|
|
|
987
1494
|
TOptions,
|
|
988
1495
|
TLinkedFragments
|
|
989
1496
|
> {
|
|
1497
|
+
// This variable is set by the frango-cli when extracting database schemas
|
|
1498
|
+
const dryRun = process.env["FRAGNO_INIT_DRY_RUN"] === "true";
|
|
1499
|
+
|
|
990
1500
|
return instantiateFragment(
|
|
991
1501
|
this.#definition,
|
|
992
1502
|
this.#config ?? ({} as TConfig),
|
|
993
1503
|
this.#routes ?? ([] as const as unknown as TRoutesOrFactories),
|
|
994
1504
|
this.#options ?? ({} as TOptions),
|
|
995
1505
|
this.#services,
|
|
1506
|
+
{ dryRun },
|
|
996
1507
|
);
|
|
997
1508
|
}
|
|
998
1509
|
}
|
|
@@ -1051,3 +1562,6 @@ export function instantiate<
|
|
|
1051
1562
|
> {
|
|
1052
1563
|
return new FragmentInstantiationBuilder(definition);
|
|
1053
1564
|
}
|
|
1565
|
+
|
|
1566
|
+
// Export interfaces for stub implementations
|
|
1567
|
+
export type { IFragmentInstantiationBuilder, IFragnoInstantiatedFragment };
|