@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.
Files changed (155) hide show
  1. package/.turbo/turbo-build.log +87 -69
  2. package/CHANGELOG.md +79 -0
  3. package/dist/api/api.d.ts +21 -2
  4. package/dist/api/api.d.ts.map +1 -1
  5. package/dist/api/api.js +2 -1
  6. package/dist/api/api.js.map +1 -1
  7. package/dist/api/bind-services.d.ts +0 -1
  8. package/dist/api/bind-services.d.ts.map +1 -1
  9. package/dist/api/bind-services.js.map +1 -1
  10. package/dist/api/error.d.ts.map +1 -1
  11. package/dist/api/error.js.map +1 -1
  12. package/dist/api/fragment-definition-builder.d.ts +32 -40
  13. package/dist/api/fragment-definition-builder.d.ts.map +1 -1
  14. package/dist/api/fragment-definition-builder.js +15 -21
  15. package/dist/api/fragment-definition-builder.js.map +1 -1
  16. package/dist/api/fragment-instantiator.d.ts +51 -30
  17. package/dist/api/fragment-instantiator.d.ts.map +1 -1
  18. package/dist/api/fragment-instantiator.js +201 -52
  19. package/dist/api/fragment-instantiator.js.map +1 -1
  20. package/dist/api/request-context-storage.d.ts +4 -0
  21. package/dist/api/request-context-storage.d.ts.map +1 -1
  22. package/dist/api/request-context-storage.js +6 -0
  23. package/dist/api/request-context-storage.js.map +1 -1
  24. package/dist/api/request-input-context.d.ts +57 -1
  25. package/dist/api/request-input-context.d.ts.map +1 -1
  26. package/dist/api/request-input-context.js +67 -0
  27. package/dist/api/request-input-context.js.map +1 -1
  28. package/dist/api/request-middleware.d.ts +2 -2
  29. package/dist/api/request-middleware.d.ts.map +1 -1
  30. package/dist/api/request-middleware.js.map +1 -1
  31. package/dist/api/request-output-context.d.ts +1 -1
  32. package/dist/api/request-output-context.d.ts.map +1 -1
  33. package/dist/api/request-output-context.js.map +1 -1
  34. package/dist/api/route-caller.d.ts +30 -0
  35. package/dist/api/route-caller.d.ts.map +1 -0
  36. package/dist/api/route-caller.js +63 -0
  37. package/dist/api/route-caller.js.map +1 -0
  38. package/dist/api/route-handler-input-options.d.ts.map +1 -1
  39. package/dist/api/route.d.ts +8 -8
  40. package/dist/api/route.d.ts.map +1 -1
  41. package/dist/api/route.js.map +1 -1
  42. package/dist/api/shared-types.d.ts.map +1 -1
  43. package/dist/client/client-error.d.ts.map +1 -1
  44. package/dist/client/client-error.js.map +1 -1
  45. package/dist/client/client.d.ts +90 -50
  46. package/dist/client/client.d.ts.map +1 -1
  47. package/dist/client/client.js +128 -16
  48. package/dist/client/client.js.map +1 -1
  49. package/dist/client/client.svelte.d.ts +6 -5
  50. package/dist/client/client.svelte.d.ts.map +1 -1
  51. package/dist/client/client.svelte.js +10 -2
  52. package/dist/client/client.svelte.js.map +1 -1
  53. package/dist/client/internal/ndjson-streaming.js.map +1 -1
  54. package/dist/client/react.d.ts +5 -4
  55. package/dist/client/react.d.ts.map +1 -1
  56. package/dist/client/react.js +104 -12
  57. package/dist/client/react.js.map +1 -1
  58. package/dist/client/solid.d.ts +7 -5
  59. package/dist/client/solid.d.ts.map +1 -1
  60. package/dist/client/solid.js +23 -9
  61. package/dist/client/solid.js.map +1 -1
  62. package/dist/client/vanilla.d.ts +16 -4
  63. package/dist/client/vanilla.d.ts.map +1 -1
  64. package/dist/client/vanilla.js +21 -1
  65. package/dist/client/vanilla.js.map +1 -1
  66. package/dist/client/vue.d.ts +10 -4
  67. package/dist/client/vue.d.ts.map +1 -1
  68. package/dist/client/vue.js +24 -1
  69. package/dist/client/vue.js.map +1 -1
  70. package/dist/id.d.ts +2 -0
  71. package/dist/id.js +3 -0
  72. package/dist/internal/cuid.d.ts +16 -0
  73. package/dist/internal/cuid.d.ts.map +1 -0
  74. package/dist/internal/cuid.js +82 -0
  75. package/dist/internal/cuid.js.map +1 -0
  76. package/dist/internal/trace-context.d.ts +23 -0
  77. package/dist/internal/trace-context.d.ts.map +1 -0
  78. package/dist/internal/trace-context.js +14 -0
  79. package/dist/internal/trace-context.js.map +1 -0
  80. package/dist/mod-client.d.ts +7 -20
  81. package/dist/mod-client.d.ts.map +1 -1
  82. package/dist/mod-client.js +25 -13
  83. package/dist/mod-client.js.map +1 -1
  84. package/dist/mod.d.ts +8 -6
  85. package/dist/mod.js +3 -1
  86. package/dist/runtime.d.ts +15 -0
  87. package/dist/runtime.d.ts.map +1 -0
  88. package/dist/runtime.js +33 -0
  89. package/dist/runtime.js.map +1 -0
  90. package/dist/test/test.d.ts +6 -6
  91. package/dist/test/test.d.ts.map +1 -1
  92. package/dist/test/test.js.map +1 -1
  93. package/dist/util/ssr.js.map +1 -1
  94. package/package.json +42 -52
  95. package/src/api/api.test.ts +3 -1
  96. package/src/api/api.ts +28 -0
  97. package/src/api/bind-services.ts +0 -5
  98. package/src/api/error.ts +1 -0
  99. package/src/api/fragment-definition-builder.extend.test.ts +2 -1
  100. package/src/api/fragment-definition-builder.test.ts +2 -1
  101. package/src/api/fragment-definition-builder.ts +56 -112
  102. package/src/api/fragment-instantiator.test.ts +311 -166
  103. package/src/api/fragment-instantiator.ts +470 -131
  104. package/src/api/fragment-services.test.ts +1 -0
  105. package/src/api/internal/path-runtime.test.ts +8 -0
  106. package/src/api/internal/path-type.test.ts +3 -1
  107. package/src/api/internal/route.test.ts +1 -0
  108. package/src/api/request-context-storage.ts +7 -0
  109. package/src/api/request-input-context.test.ts +156 -2
  110. package/src/api/request-input-context.ts +87 -1
  111. package/src/api/request-middleware.test.ts +43 -2
  112. package/src/api/request-middleware.ts +4 -3
  113. package/src/api/request-output-context.test.ts +3 -1
  114. package/src/api/request-output-context.ts +2 -1
  115. package/src/api/route-caller.test.ts +195 -0
  116. package/src/api/route-caller.ts +167 -0
  117. package/src/api/route-handler-input-options.ts +2 -1
  118. package/src/api/route.test.ts +4 -2
  119. package/src/api/route.ts +9 -3
  120. package/src/api/shared-types.ts +2 -1
  121. package/src/client/client-builder.test.ts +4 -2
  122. package/src/client/client-error.test.ts +2 -1
  123. package/src/client/client-error.ts +1 -1
  124. package/src/client/client-types.test.ts +19 -5
  125. package/src/client/client.ssr.test.ts +6 -4
  126. package/src/client/client.svelte.test.ts +18 -9
  127. package/src/client/client.svelte.ts +38 -13
  128. package/src/client/client.test.ts +244 -10
  129. package/src/client/client.ts +473 -148
  130. package/src/client/internal/ndjson-streaming.test.ts +6 -3
  131. package/src/client/internal/ndjson-streaming.ts +1 -0
  132. package/src/client/react.test.ts +176 -6
  133. package/src/client/react.ts +226 -31
  134. package/src/client/solid.test.ts +29 -5
  135. package/src/client/solid.ts +60 -22
  136. package/src/client/vanilla.test.ts +148 -6
  137. package/src/client/vanilla.ts +63 -9
  138. package/src/client/vue.test.ts +397 -8
  139. package/src/client/vue.ts +74 -4
  140. package/src/id.ts +1 -0
  141. package/src/internal/cuid.test.ts +164 -0
  142. package/src/internal/cuid.ts +133 -0
  143. package/src/internal/trace-context.ts +35 -0
  144. package/src/mod-client.ts +55 -9
  145. package/src/mod.ts +9 -3
  146. package/src/runtime.ts +48 -0
  147. package/src/test/test.test.ts +4 -2
  148. package/src/test/test.ts +14 -7
  149. package/src/util/async.test.ts +1 -0
  150. package/src/util/content-type.test.ts +1 -0
  151. package/src/util/nanostores.test.ts +3 -1
  152. package/src/util/ssr.ts +1 -0
  153. package/tsconfig.json +1 -1
  154. package/tsdown.config.ts +2 -0
  155. package/vitest.config.ts +2 -1
@@ -1,10 +1,26 @@
1
+ import { addRoute, createRouter, findRoute } from "rou3";
2
+
1
3
  import type { StandardSchemaV1 } from "@standard-schema/spec";
4
+
5
+ import type { ExtractRouteByPath, ExtractRoutePath } from "../client/client";
6
+ import { instantiatedFragmentFakeSymbol } from "../internal/symbols";
7
+ import { recordTraceEvent } from "../internal/trace-context";
8
+ import type { InferOrUnknown } from "../util/types-util";
2
9
  import { type FragnoRouteConfig, type HTTPMethod, type RequestThisContext } from "./api";
10
+ import { bindServicesToContext, type BoundServices } from "./bind-services";
3
11
  import { FragnoApiError } from "./error";
12
+ import type { FragmentDefinition } from "./fragment-definition-builder";
13
+ import { type FragnoResponse, parseFragnoResponse } from "./fragno-response";
14
+ import type { ExtractPathParams } from "./internal/path";
4
15
  import { getMountRoute } from "./internal/route";
5
- import { addRoute, createRouter, findRoute } from "rou3";
16
+ import { MutableRequestState } from "./mutable-request-state";
17
+ import { RequestContextStorage } from "./request-context-storage";
6
18
  import { RequestInputContext, type RequestBodyType } from "./request-input-context";
7
- import type { ExtractPathParams } from "./internal/path";
19
+ import {
20
+ RequestMiddlewareInputContext,
21
+ RequestMiddlewareOutputContext,
22
+ type FragnoMiddlewareCallback,
23
+ } from "./request-middleware";
8
24
  import { RequestOutputContext } from "./request-output-context";
9
25
  import {
10
26
  type AnyFragnoRouteConfig,
@@ -12,25 +28,98 @@ import {
12
28
  type FlattenRouteFactories,
13
29
  resolveRouteFactories,
14
30
  } from "./route";
15
- import {
16
- RequestMiddlewareInputContext,
17
- RequestMiddlewareOutputContext,
18
- type FragnoMiddlewareCallback,
19
- } from "./request-middleware";
20
- import { MutableRequestState } from "./mutable-request-state";
21
31
  import type { RouteHandlerInputOptions } from "./route-handler-input-options";
22
- import type { ExtractRouteByPath, ExtractRoutePath } from "../client/client";
23
- import { type FragnoResponse, parseFragnoResponse } from "./fragno-response";
24
- import type { InferOrUnknown } from "../util/types-util";
25
- import type { FragmentDefinition } from "./fragment-definition-builder";
26
32
  import type { FragnoPublicConfig } from "./shared-types";
27
- import { RequestContextStorage } from "./request-context-storage";
28
- import { bindServicesToContext, type BoundServices } from "./bind-services";
29
- import { instantiatedFragmentFakeSymbol } from "../internal/symbols";
30
33
 
31
34
  // Re-export types needed by consumers
32
35
  export type { BoundServices };
33
36
 
37
+ type CallRoutePath<TRoutes extends readonly AnyFragnoRouteConfig[], TMethod extends HTTPMethod> = [
38
+ ExtractRoutePath<TRoutes, TMethod>,
39
+ ] extends [never]
40
+ ? string
41
+ : ExtractRoutePath<TRoutes, TMethod>;
42
+
43
+ type CallRouteMatch<
44
+ TRoutes extends readonly AnyFragnoRouteConfig[],
45
+ TMethod extends HTTPMethod,
46
+ TPath extends string,
47
+ > = [ExtractRouteByPath<TRoutes, TPath, TMethod>] extends [never]
48
+ ? AnyFragnoRouteConfig
49
+ : ExtractRouteByPath<TRoutes, TPath, TMethod>;
50
+
51
+ const requestSourceSymbol = Symbol.for("fragno-request-source");
52
+ const requestRouteSymbol = Symbol.for("fragno-request-route");
53
+ const requestWaitUntilSymbol = Symbol.for("fragno-request-wait-until");
54
+
55
+ type RequestRouteInfo = {
56
+ method: HTTPMethod;
57
+ path: string;
58
+ mountRoute?: string;
59
+ fullPath?: string;
60
+ };
61
+ type RequestSource = "route" | "context";
62
+
63
+ export type FragnoRequestLifecycleContext = {
64
+ waitUntil?: (promise: Promise<unknown>) => void;
65
+ };
66
+
67
+ type InternalRoutePrefix = "/_internal";
68
+
69
+ type JoinInternalRoutePath<TPath extends string> = TPath extends "" | "/"
70
+ ? InternalRoutePrefix
71
+ : TPath extends `/${string}`
72
+ ? `${InternalRoutePrefix}${TPath}`
73
+ : `${InternalRoutePrefix}/${TPath}`;
74
+
75
+ type PrefixInternalRoute<TRoute> =
76
+ TRoute extends FragnoRouteConfig<
77
+ infer TMethod,
78
+ infer TPath,
79
+ infer TInputSchema,
80
+ infer TOutputSchema,
81
+ infer TErrorCode,
82
+ infer TQueryParameters,
83
+ infer TThisContext
84
+ >
85
+ ? FragnoRouteConfig<
86
+ TMethod,
87
+ JoinInternalRoutePath<TPath>,
88
+ TInputSchema,
89
+ TOutputSchema,
90
+ TErrorCode,
91
+ TQueryParameters,
92
+ TThisContext
93
+ >
94
+ : never;
95
+
96
+ type PrefixInternalRoutes<TRoutes extends readonly AnyFragnoRouteConfig[]> =
97
+ TRoutes extends readonly [...infer TRoutesTuple]
98
+ ? { [K in keyof TRoutesTuple]: PrefixInternalRoute<TRoutesTuple[K]> }
99
+ : readonly AnyFragnoRouteConfig[];
100
+
101
+ type InternalRoutesFromDefinition<TInternalRoutes extends readonly AnyRouteOrFactory[]> =
102
+ PrefixInternalRoutes<FlattenRouteFactories<TInternalRoutes>>;
103
+
104
+ export type RoutesWithInternal<
105
+ TRoutes extends readonly AnyFragnoRouteConfig[],
106
+ TInternalRoutes extends readonly AnyRouteOrFactory[],
107
+ > = readonly [...TRoutes, ...InternalRoutesFromDefinition<TInternalRoutes>];
108
+
109
+ type ExtractServiceCallResult<T> = T extends undefined
110
+ ? undefined
111
+ : T extends { _internal: { finalResult?: infer R } }
112
+ ? R
113
+ : Awaited<T>;
114
+
115
+ type ExtractServiceCallResults<T extends readonly unknown[]> = {
116
+ [K in keyof T]: ExtractServiceCallResult<T[K]>;
117
+ };
118
+
119
+ type ExtractServiceCallResultsOrSingle<T> = T extends readonly unknown[]
120
+ ? ExtractServiceCallResults<T>
121
+ : ExtractServiceCallResult<T>;
122
+
34
123
  /**
35
124
  * Helper type to extract the instantiated fragment type from a fragment definition.
36
125
  * This is useful for typing functions that accept instantiated fragments based on their definition.
@@ -56,17 +145,16 @@ export type InstantiatedFragmentFromDefinition<
56
145
  infer TServiceThisContext,
57
146
  infer THandlerThisContext,
58
147
  infer TRequestStorage,
59
- infer TLinkedFragments
148
+ infer TInternalRoutes
60
149
  >
61
150
  ? FragnoInstantiatedFragment<
62
- readonly AnyFragnoRouteConfig[], // Routes are dynamic, so we use a generic array
151
+ RoutesWithInternal<readonly AnyFragnoRouteConfig[], TInternalRoutes>,
63
152
  TDeps,
64
153
  BoundServices<TBaseServices & TServices>,
65
154
  TServiceThisContext,
66
155
  THandlerThisContext,
67
156
  TRequestStorage,
68
- TOptions,
69
- TLinkedFragments
157
+ TOptions
70
158
  >
71
159
  : never;
72
160
 
@@ -79,6 +167,34 @@ type ReactRouterHandlers = {
79
167
  action: (args: { request: Request }) => Promise<Response>;
80
168
  };
81
169
 
170
+ const serializeHeadersForTrace = (headers: Headers): [string, string][] =>
171
+ Array.from(headers.entries()).sort(([a], [b]) => a.localeCompare(b));
172
+
173
+ const serializeQueryForTrace = (query: URLSearchParams): [string, string][] =>
174
+ Array.from(query.entries()).sort(([a], [b]) => a.localeCompare(b));
175
+
176
+ const serializeBodyForTrace = (body: RequestBodyType): unknown => {
177
+ if (body instanceof FormData) {
178
+ const entries = Array.from(body.entries()).map(([key, value]) => {
179
+ if (value instanceof Blob) {
180
+ return [key, { type: "blob", size: value.size, mime: value.type }] as const;
181
+ }
182
+ return [key, value] as const;
183
+ });
184
+ return { type: "form-data", entries };
185
+ }
186
+
187
+ if (body instanceof Blob) {
188
+ return { type: "blob", size: body.size, mime: body.type };
189
+ }
190
+
191
+ if (body instanceof ReadableStream) {
192
+ return { type: "stream" };
193
+ }
194
+
195
+ return body;
196
+ };
197
+
82
198
  type SolidStartHandlers = {
83
199
  GET: (args: { request: Request }) => Promise<Response>;
84
200
  POST: (args: { request: Request }) => Promise<Response>;
@@ -126,11 +242,27 @@ export type AnyFragnoInstantiatedFragment = FragnoInstantiatedFragment<
126
242
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
243
  any,
128
244
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
129
- any,
130
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
245
  any
132
246
  >;
133
247
 
248
+ const INTERNAL_ROUTE_PREFIX = "/_internal";
249
+
250
+ function normalizeRoutePrefix(prefix: string): string {
251
+ if (!prefix.startsWith("/")) {
252
+ prefix = `/${prefix}`;
253
+ }
254
+ return prefix.endsWith("/") && prefix.length > 1 ? prefix.slice(0, -1) : prefix;
255
+ }
256
+
257
+ function joinRoutePath(prefix: string, path: string): string {
258
+ const normalizedPrefix = normalizeRoutePrefix(prefix);
259
+ if (!path || path === "/") {
260
+ return normalizedPrefix;
261
+ }
262
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
263
+ return `${normalizedPrefix}${normalizedPath}`;
264
+ }
265
+
134
266
  export interface FragnoFragmentSharedConfig<
135
267
  TRoutes extends readonly FragnoRouteConfig<
136
268
  HTTPMethod,
@@ -157,9 +289,7 @@ export class FragnoInstantiatedFragment<
157
289
  THandlerThisContext extends RequestThisContext,
158
290
  TRequestStorage = {},
159
291
  TOptions extends FragnoPublicConfig = FragnoPublicConfig,
160
- TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment> = {},
161
- > implements IFragnoInstantiatedFragment
162
- {
292
+ > implements IFragnoInstantiatedFragment {
163
293
  readonly [instantiatedFragmentFakeSymbol] = instantiatedFragmentFakeSymbol;
164
294
 
165
295
  // Private fields
@@ -175,7 +305,7 @@ export class FragnoInstantiatedFragment<
175
305
  #contextStorage: RequestContextStorage<TRequestStorage>;
176
306
  #createRequestStorage?: () => TRequestStorage;
177
307
  #options: TOptions;
178
- #linkedFragments: TLinkedFragments;
308
+ #internalData: Record<string, unknown>;
179
309
 
180
310
  constructor(params: {
181
311
  name: string;
@@ -188,7 +318,7 @@ export class FragnoInstantiatedFragment<
188
318
  storage: RequestContextStorage<TRequestStorage>;
189
319
  createRequestStorage?: () => TRequestStorage;
190
320
  options: TOptions;
191
- linkedFragments?: TLinkedFragments;
321
+ internalData?: Record<string, unknown>;
192
322
  }) {
193
323
  this.#name = params.name;
194
324
  this.#routes = params.routes;
@@ -200,7 +330,7 @@ export class FragnoInstantiatedFragment<
200
330
  this.#contextStorage = params.storage;
201
331
  this.#createRequestStorage = params.createRequestStorage;
202
332
  this.#options = params.options;
203
- this.#linkedFragments = params.linkedFragments ?? ({} as TLinkedFragments);
333
+ this.#internalData = params.internalData ?? {};
204
334
 
205
335
  // Build router
206
336
  this.#router =
@@ -248,7 +378,7 @@ export class FragnoInstantiatedFragment<
248
378
  return {
249
379
  deps: this.#deps,
250
380
  options: this.#options,
251
- linkedFragments: this.#linkedFragments,
381
+ ...this.#internalData,
252
382
  };
253
383
  }
254
384
 
@@ -269,9 +399,24 @@ export class FragnoInstantiatedFragment<
269
399
  * This is a shared helper used by inContext(), handler(), and callRouteRaw().
270
400
  * @private
271
401
  */
272
- #withRequestStorage<T>(callback: () => T): T;
273
- #withRequestStorage<T>(callback: () => Promise<T>): Promise<T>;
274
- #withRequestStorage<T>(callback: () => T | Promise<T>): T | Promise<T> {
402
+ #withRequestStorage<T>(
403
+ callback: () => T,
404
+ source?: RequestSource,
405
+ routeInfo?: RequestRouteInfo,
406
+ lifecycleContext?: FragnoRequestLifecycleContext,
407
+ ): T;
408
+ #withRequestStorage<T>(
409
+ callback: () => Promise<T>,
410
+ source?: RequestSource,
411
+ routeInfo?: RequestRouteInfo,
412
+ lifecycleContext?: FragnoRequestLifecycleContext,
413
+ ): Promise<T>;
414
+ #withRequestStorage<T>(
415
+ callback: () => T | Promise<T>,
416
+ source: RequestSource = "context",
417
+ routeInfo?: RequestRouteInfo,
418
+ lifecycleContext?: FragnoRequestLifecycleContext,
419
+ ): T | Promise<T> {
275
420
  if (!this.#serviceThisContext && !this.#handlerThisContext) {
276
421
  // No request context configured - just run callback directly
277
422
  return callback();
@@ -281,6 +426,16 @@ export class FragnoInstantiatedFragment<
281
426
  const storageData = this.#createRequestStorage
282
427
  ? this.#createRequestStorage()
283
428
  : ({} as TRequestStorage);
429
+ if (storageData && typeof storageData === "object") {
430
+ const metadataTarget = storageData as Record<symbol, unknown>;
431
+ metadataTarget[requestSourceSymbol] = source;
432
+ if (routeInfo) {
433
+ metadataTarget[requestRouteSymbol] = routeInfo;
434
+ }
435
+ if (lifecycleContext?.waitUntil) {
436
+ metadataTarget[requestWaitUntilSymbol] = lifecycleContext.waitUntil;
437
+ }
438
+ }
284
439
  return this.#contextStorage.run(storageData, callback);
285
440
  }
286
441
 
@@ -308,9 +463,55 @@ export class FragnoInstantiatedFragment<
308
463
  // Always use handler context for inContext - it has full capabilities
309
464
  if (this.#handlerThisContext) {
310
465
  const boundCallback = callback.bind(this.#handlerThisContext);
311
- return this.#withRequestStorage(boundCallback);
466
+ return this.#withRequestStorage(boundCallback, "context");
467
+ }
468
+ return this.#withRequestStorage(callback, "context");
469
+ }
470
+
471
+ /**
472
+ * Execute multiple service calls within a handler context.
473
+ * If called outside a request context, it will create one automatically.
474
+ * Pass a factory so service calls are created inside the active context.
475
+ * Primarily used by database fragments (handlerTx).
476
+ */
477
+ async callServices<TServiceCalls>(
478
+ serviceCalls: () => TServiceCalls,
479
+ ): Promise<ExtractServiceCallResultsOrSingle<TServiceCalls>> {
480
+ const handlerContext = this.#handlerThisContext as
481
+ | {
482
+ handlerTx?: () => {
483
+ withServiceCalls: (fn: () => readonly unknown[]) => { execute: () => Promise<unknown> };
484
+ };
485
+ }
486
+ | undefined;
487
+
488
+ if (!handlerContext?.handlerTx) {
489
+ throw new Error(
490
+ "callServices is only supported for fragments with handlerTx (database fragments).",
491
+ );
492
+ }
493
+
494
+ let callWasArray = false;
495
+ const execute = () => {
496
+ return handlerContext.handlerTx!()
497
+ .withServiceCalls(() => {
498
+ const calls = serviceCalls();
499
+ callWasArray = Array.isArray(calls);
500
+ return (callWasArray ? calls : [calls]) as readonly unknown[];
501
+ })
502
+ .execute();
503
+ };
504
+
505
+ const result = this.#contextStorage.hasStore()
506
+ ? await execute()
507
+ : await this.#withRequestStorage(execute, "context");
508
+
509
+ if (callWasArray) {
510
+ return result as ExtractServiceCallResultsOrSingle<TServiceCalls>;
312
511
  }
313
- return this.#withRequestStorage(callback);
512
+
513
+ const [first] = result as unknown[];
514
+ return first as ExtractServiceCallResultsOrSingle<TServiceCalls>;
314
515
  }
315
516
 
316
517
  /**
@@ -379,7 +580,7 @@ export class FragnoInstantiatedFragment<
379
580
  * Main request handler for this fragment.
380
581
  * Handles routing, middleware, and error handling.
381
582
  */
382
- async handler(req: Request): Promise<Response> {
583
+ async handler(req: Request, lifecycleContext?: FragnoRequestLifecycleContext): Promise<Response> {
383
584
  const url = new URL(req.url);
384
585
  const pathname = url.pathname;
385
586
 
@@ -409,44 +610,112 @@ export class FragnoInstantiatedFragment<
409
610
  );
410
611
  }
411
612
 
412
- // Parse request body
613
+ // Get the expected content type from route config (default: application/json)
614
+ const routeConfig = route.data as AnyFragnoRouteConfig;
615
+ const expectedContentType = routeConfig.contentType ?? "application/json";
616
+
617
+ // Parse request body based on route's expected content type
413
618
  let requestBody: RequestBodyType = undefined;
414
619
  let rawBody: string | undefined = undefined;
415
620
 
416
621
  if (req.body instanceof ReadableStream) {
417
- // Clone request to make sure we don't consume body stream
418
- const clonedReq = req.clone();
419
-
420
- // Get raw text
421
- rawBody = await clonedReq.text();
622
+ const requestContentType = (req.headers.get("content-type") ?? "").toLowerCase();
623
+
624
+ if (expectedContentType === "multipart/form-data") {
625
+ // Route expects FormData (file uploads)
626
+ if (!requestContentType.includes("multipart/form-data")) {
627
+ return Response.json(
628
+ {
629
+ error: `This endpoint expects multipart/form-data, but received: ${requestContentType || "no content-type"}`,
630
+ code: "UNSUPPORTED_MEDIA_TYPE",
631
+ },
632
+ { status: 415 },
633
+ );
634
+ }
422
635
 
423
- // Parse JSON if body is not empty
424
- if (rawBody) {
425
636
  try {
426
- requestBody = JSON.parse(rawBody);
637
+ requestBody = await req.formData();
427
638
  } catch {
428
- // If JSON parsing fails, keep body as undefined
429
- // This handles cases where body is not JSON
430
- requestBody = undefined;
639
+ return Response.json(
640
+ { error: "Failed to parse multipart form data", code: "INVALID_REQUEST_BODY" },
641
+ { status: 400 },
642
+ );
643
+ }
644
+ } else if (expectedContentType === "application/octet-stream") {
645
+ if (!requestContentType.includes("application/octet-stream")) {
646
+ return Response.json(
647
+ {
648
+ error: `This endpoint expects application/octet-stream, but received: ${requestContentType || "no content-type"}`,
649
+ code: "UNSUPPORTED_MEDIA_TYPE",
650
+ },
651
+ { status: 415 },
652
+ );
653
+ }
654
+
655
+ requestBody = req.body ?? new ReadableStream<Uint8Array>();
656
+ } else {
657
+ // Route expects JSON (default)
658
+ // Note: We're lenient here - we accept requests without Content-Type header
659
+ // or with application/json. We reject multipart/form-data for JSON routes.
660
+ if (requestContentType.includes("multipart/form-data")) {
661
+ return Response.json(
662
+ {
663
+ error: `This endpoint expects JSON, but received multipart/form-data. Use a route with contentType: "multipart/form-data" for file uploads.`,
664
+ code: "UNSUPPORTED_MEDIA_TYPE",
665
+ },
666
+ { status: 415 },
667
+ );
668
+ }
669
+
670
+ // Clone request to make sure we don't consume body stream
671
+ const clonedReq = req.clone();
672
+
673
+ // Get raw text
674
+ rawBody = await clonedReq.text();
675
+
676
+ // Parse JSON if body is not empty
677
+ if (rawBody) {
678
+ try {
679
+ requestBody = JSON.parse(rawBody);
680
+ } catch {
681
+ // If JSON parsing fails, keep body as undefined
682
+ // This handles cases where body is not JSON
683
+ requestBody = undefined;
684
+ }
431
685
  }
432
686
  }
433
687
  }
434
688
 
689
+ // URL decode path params from rou3 (which returns encoded values)
690
+ const decodedRouteParams: Record<string, string> = {};
691
+ for (const [key, value] of Object.entries(route.params ?? {})) {
692
+ decodedRouteParams[key] = decodeURIComponent(value);
693
+ }
694
+
435
695
  const requestState = new MutableRequestState({
436
- pathParams: route.params ?? {},
696
+ pathParams: decodedRouteParams,
437
697
  searchParams: url.searchParams,
438
698
  body: requestBody,
439
699
  headers: new Headers(req.headers),
440
700
  });
441
701
 
702
+ const fullRoutePath =
703
+ this.#mountRoute && this.#mountRoute !== "/"
704
+ ? `${this.#mountRoute}${routeConfig.path}`
705
+ : routeConfig.path;
706
+ const routeInfo: RequestRouteInfo = {
707
+ method: routeConfig.method,
708
+ path: routeConfig.path,
709
+ mountRoute: this.#mountRoute,
710
+ fullPath: fullRoutePath,
711
+ };
712
+
442
713
  // Execute middleware and handler
443
714
  const executeRequest = async (): Promise<Response> => {
444
- // Middleware execution (if present)
445
- if (this.#middlewareHandler) {
446
- const middlewareResult = await this.#executeMiddleware(req, route, requestState);
447
- if (middlewareResult !== undefined) {
448
- return middlewareResult;
449
- }
715
+ // Parent middleware execution
716
+ const middlewareResult = await this.#executeMiddleware(req, route, requestState);
717
+ if (middlewareResult !== undefined) {
718
+ return middlewareResult;
450
719
  }
451
720
 
452
721
  // Handler execution
@@ -454,23 +723,23 @@ export class FragnoInstantiatedFragment<
454
723
  };
455
724
 
456
725
  // Wrap with request storage context if provided
457
- return this.#withRequestStorage(executeRequest);
726
+ return this.#withRequestStorage(executeRequest, "route", routeInfo, lifecycleContext);
458
727
  }
459
728
 
460
729
  /**
461
730
  * Call a route directly with typed inputs and outputs.
462
731
  * Useful for testing and server-side route calls.
463
732
  */
464
- async callRoute<TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(
733
+ async callRoute<TMethod extends HTTPMethod, TPath extends CallRoutePath<TRoutes, TMethod>>(
465
734
  method: TMethod,
466
735
  path: TPath,
467
736
  inputOptions?: RouteHandlerInputOptions<
468
737
  TPath,
469
- ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
738
+ CallRouteMatch<TRoutes, TMethod, TPath>["inputSchema"]
470
739
  >,
471
740
  ): Promise<
472
741
  FragnoResponse<
473
- InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>["outputSchema"]>>
742
+ InferOrUnknown<NonNullable<CallRouteMatch<TRoutes, TMethod, TPath>["outputSchema"]>>
474
743
  >
475
744
  > {
476
745
  const response = await this.callRouteRaw(method, path, inputOptions);
@@ -481,12 +750,12 @@ export class FragnoInstantiatedFragment<
481
750
  * Call a route directly and get the raw Response object.
482
751
  * Useful for testing and server-side route calls.
483
752
  */
484
- async callRouteRaw<TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(
753
+ async callRouteRaw<TMethod extends HTTPMethod, TPath extends CallRoutePath<TRoutes, TMethod>>(
485
754
  method: TMethod,
486
755
  path: TPath,
487
756
  inputOptions?: RouteHandlerInputOptions<
488
757
  TPath,
489
- ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]
758
+ CallRouteMatch<TRoutes, TMethod, TPath>["inputSchema"]
490
759
  >,
491
760
  ): Promise<Response> {
492
761
  // Find route in this.#routes
@@ -502,7 +771,8 @@ export class FragnoInstantiatedFragment<
502
771
  );
503
772
  }
504
773
 
505
- const { pathParams = {}, body, query, headers } = inputOptions || {};
774
+ const { pathParams = {}, query, headers } = inputOptions || {};
775
+ const body = inputOptions && "body" in inputOptions ? inputOptions.body : undefined;
506
776
 
507
777
  // Convert query to URLSearchParams if needed
508
778
  const searchParams =
@@ -527,9 +797,30 @@ export class FragnoInstantiatedFragment<
527
797
  shouldValidateInput: true, // Enable validation for production use
528
798
  });
529
799
 
800
+ recordTraceEvent({
801
+ type: "route-input",
802
+ method: route.method,
803
+ path: route.path,
804
+ pathParams: (pathParams ?? {}) as Record<string, string>,
805
+ queryParams: serializeQueryForTrace(searchParams),
806
+ headers: serializeHeadersForTrace(requestHeaders),
807
+ body: serializeBodyForTrace(body),
808
+ });
809
+
530
810
  // Construct RequestOutputContext
531
811
  const outputContext = new RequestOutputContext(route.outputSchema);
532
812
 
813
+ const fullRoutePath =
814
+ this.#mountRoute && this.#mountRoute !== "/"
815
+ ? `${this.#mountRoute}${route.path}`
816
+ : route.path;
817
+ const routeInfo: RequestRouteInfo = {
818
+ method: route.method,
819
+ path: route.path,
820
+ mountRoute: this.#mountRoute,
821
+ fullPath: fullRoutePath,
822
+ };
823
+
533
824
  // Execute handler
534
825
  const executeHandler = async (): Promise<Response> => {
535
826
  try {
@@ -551,7 +842,7 @@ export class FragnoInstantiatedFragment<
551
842
  };
552
843
 
553
844
  // Wrap with request storage context if provided
554
- return this.#withRequestStorage(executeHandler);
845
+ return this.#withRequestStorage(executeHandler, "route", routeInfo);
555
846
  }
556
847
 
557
848
  /**
@@ -563,32 +854,73 @@ export class FragnoInstantiatedFragment<
563
854
  route: ReturnType<typeof findRoute>,
564
855
  requestState: MutableRequestState,
565
856
  ): Promise<Response | undefined> {
566
- if (!this.#middlewareHandler || !route) {
857
+ if (!route) {
567
858
  return undefined;
568
859
  }
569
860
 
570
861
  const { path } = route.data as AnyFragnoRouteConfig;
571
-
572
- const middlewareInputContext = new RequestMiddlewareInputContext(this.#routes, {
862
+ return FragnoInstantiatedFragment.#runMiddlewareForFragment(this, {
863
+ req,
573
864
  method: req.method as HTTPMethod,
574
865
  path,
575
- request: req,
576
- state: requestState,
866
+ requestState,
577
867
  });
868
+ }
869
+
870
+ static async #runMiddlewareForFragment(
871
+ fragment: AnyFragnoInstantiatedFragment,
872
+ options: {
873
+ req: Request;
874
+ method: HTTPMethod;
875
+ path: string;
876
+ requestState: MutableRequestState;
877
+ routes?: readonly AnyFragnoRouteConfig[];
878
+ },
879
+ ): Promise<Response | undefined> {
880
+ if (!fragment.#middlewareHandler) {
881
+ return undefined;
882
+ }
578
883
 
579
- const middlewareOutputContext = new RequestMiddlewareOutputContext(this.#deps, this.#services);
884
+ const middlewareInputContext = new RequestMiddlewareInputContext(
885
+ (options.routes ?? fragment.#routes) as readonly AnyFragnoRouteConfig[],
886
+ {
887
+ method: options.method,
888
+ path: options.path,
889
+ request: options.req,
890
+ state: options.requestState,
891
+ },
892
+ );
893
+
894
+ const middlewareOutputContext = new RequestMiddlewareOutputContext(
895
+ fragment.#deps,
896
+ fragment.#services,
897
+ );
580
898
 
581
899
  try {
582
- const middlewareResult = await this.#middlewareHandler(
900
+ const middlewareResult = await fragment.#middlewareHandler(
583
901
  middlewareInputContext,
584
902
  middlewareOutputContext,
585
903
  );
904
+ recordTraceEvent({
905
+ type: "middleware-decision",
906
+ method: options.method,
907
+ path: options.path,
908
+ outcome: middlewareResult ? "deny" : "allow",
909
+ status: middlewareResult?.status,
910
+ });
586
911
  if (middlewareResult !== undefined) {
587
912
  return middlewareResult;
588
913
  }
589
914
  } catch (error) {
590
915
  console.error("Error in middleware", error);
591
916
 
917
+ recordTraceEvent({
918
+ type: "middleware-decision",
919
+ method: options.method,
920
+ path: options.path,
921
+ outcome: "deny",
922
+ status: error instanceof FragnoApiError ? error.status : 500,
923
+ });
592
924
  if (error instanceof FragnoApiError) {
593
925
  return error.toResponse();
594
926
  }
@@ -627,6 +959,16 @@ export class FragnoInstantiatedFragment<
627
959
  rawBody,
628
960
  });
629
961
 
962
+ recordTraceEvent({
963
+ type: "route-input",
964
+ method: req.method,
965
+ path,
966
+ pathParams: inputContext.pathParams as Record<string, string>,
967
+ queryParams: serializeQueryForTrace(requestState.searchParams),
968
+ headers: serializeHeadersForTrace(requestState.headers),
969
+ body: serializeBodyForTrace(requestState.body),
970
+ });
971
+
630
972
  const outputContext = new RequestOutputContext(outputSchema);
631
973
 
632
974
  try {
@@ -679,7 +1021,7 @@ export function instantiateFragment<
679
1021
  const THandlerThisContext extends RequestThisContext,
680
1022
  const TRequestStorage,
681
1023
  const TRoutesOrFactories extends readonly AnyRouteOrFactory[],
682
- const TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment>,
1024
+ const TInternalRoutes extends readonly AnyRouteOrFactory[],
683
1025
  >(
684
1026
  definition: FragmentDefinition<
685
1027
  TConfig,
@@ -692,7 +1034,7 @@ export function instantiateFragment<
692
1034
  TServiceThisContext,
693
1035
  THandlerThisContext,
694
1036
  TRequestStorage,
695
- TLinkedFragments
1037
+ TInternalRoutes
696
1038
  >,
697
1039
  config: TConfig,
698
1040
  routesOrFactories: TRoutesOrFactories,
@@ -700,14 +1042,13 @@ export function instantiateFragment<
700
1042
  serviceImplementations?: TServiceDependencies,
701
1043
  instantiationOptions?: InstantiationOptions,
702
1044
  ): FragnoInstantiatedFragment<
703
- FlattenRouteFactories<TRoutesOrFactories>,
1045
+ RoutesWithInternal<FlattenRouteFactories<TRoutesOrFactories>, TInternalRoutes>,
704
1046
  TDeps,
705
1047
  BoundServices<TBaseServices & TServices>,
706
1048
  TServiceThisContext,
707
1049
  THandlerThisContext,
708
1050
  TRequestStorage,
709
- TOptions,
710
- TLinkedFragments
1051
+ TOptions
711
1052
  > {
712
1053
  const { dryRun = false } = instantiationOptions ?? {};
713
1054
 
@@ -742,36 +1083,18 @@ export function instantiateFragment<
742
1083
  }
743
1084
  }
744
1085
 
745
- // 3. Instantiate linked fragments FIRST (before any services)
746
- // Their services will be merged into private services
747
- const linkedFragmentInstances = {} as TLinkedFragments;
748
- const linkedFragmentServices: Record<string, unknown> = {};
749
-
750
- if (definition.linkedFragments) {
751
- for (const [name, callback] of Object.entries(definition.linkedFragments)) {
752
- const linkedFragment = callback({
753
- config,
754
- options,
755
- serviceDependencies: serviceImplementations,
756
- });
757
- (linkedFragmentInstances as Record<string, AnyFragnoInstantiatedFragment>)[name] =
758
- linkedFragment;
759
-
760
- // Merge all services from linked fragment into private services directly by their service name
761
- const services = linkedFragment.services as Record<string, unknown>;
762
- for (const [serviceName, service] of Object.entries(services)) {
763
- linkedFragmentServices[serviceName] = service;
764
- }
765
- }
766
- }
1086
+ // 3. Calculate mount route early so internal routes can reference it
1087
+ const mountRoute = getMountRoute({
1088
+ name: definition.name,
1089
+ mountRoute: options.mountRoute,
1090
+ });
767
1091
 
768
- // Identity function for service definition (used to set 'this' context)
1092
+ // 4. Identity function for service definition (used to set 'this' context)
769
1093
  const defineService = <T>(services: T & ThisType<TServiceThisContext>): T => services;
770
1094
 
771
- // 4. Call privateServices factories
1095
+ // 5. Call privateServices factories
772
1096
  // Private services are instantiated in order, so earlier ones are available to later ones
773
- // Start with linked fragment services, then add explicitly defined private services
774
- const privateServices = { ...linkedFragmentServices } as TPrivateServices;
1097
+ const privateServices = {} as TPrivateServices;
775
1098
  if (definition.privateServices) {
776
1099
  for (const [serviceName, factory] of Object.entries(definition.privateServices)) {
777
1100
  const serviceFactory = factory as (context: {
@@ -806,7 +1129,7 @@ export function instantiateFragment<
806
1129
  }
807
1130
  }
808
1131
 
809
- // 5. Call baseServices callback (with access to private services including linked fragment services)
1132
+ // 6. Call baseServices callback (with access to private services)
810
1133
  let baseServices: TBaseServices;
811
1134
  try {
812
1135
  baseServices =
@@ -830,7 +1153,7 @@ export function instantiateFragment<
830
1153
  }
831
1154
  }
832
1155
 
833
- // 6. Call namedServices factories (with access to private services including linked fragment services)
1156
+ // 7. Call namedServices factories (with access to private services)
834
1157
  const namedServices = {} as TServices;
835
1158
  if (definition.namedServices) {
836
1159
  for (const [serviceName, factory] of Object.entries(definition.namedServices)) {
@@ -866,13 +1189,13 @@ export function instantiateFragment<
866
1189
  }
867
1190
  }
868
1191
 
869
- // 7. Merge public services (NOT including private services)
1192
+ // 8. Merge public services (NOT including private services)
870
1193
  const services = {
871
1194
  ...baseServices,
872
1195
  ...namedServices,
873
1196
  };
874
1197
 
875
- // 8. Create request context storage and both service & handler contexts
1198
+ // 9. Create request context storage and both service & handler contexts
876
1199
  // Use external storage if provided, otherwise create new storage
877
1200
  const storage = definition.getExternalStorage
878
1201
  ? definition.getExternalStorage({ config, options, deps })
@@ -889,24 +1212,37 @@ export function instantiateFragment<
889
1212
  const serviceContext = contexts?.serviceContext;
890
1213
  const handlerContext = contexts?.handlerContext;
891
1214
 
892
- // 9. Bind services to serviceContext (restricted)
1215
+ // 10. Bind services to serviceContext (restricted)
893
1216
  // Services get the restricted context (for database fragments, this excludes execute methods)
894
1217
  const boundServices = serviceContext ? bindServicesToContext(services, serviceContext) : services;
895
-
896
- // 10. Resolve routes with bound services
1218
+ const internalData =
1219
+ definition.internalDataFactory?.({
1220
+ config,
1221
+ options,
1222
+ deps,
1223
+ services: boundServices as BoundServices<TBaseServices & TServices>,
1224
+ serviceDeps: (serviceImplementations ?? {}) as TServiceDependencies,
1225
+ }) ?? {};
1226
+
1227
+ // 11. Resolve routes with bound services
897
1228
  const context = {
898
1229
  config,
899
1230
  deps,
900
1231
  services: boundServices,
901
1232
  serviceDeps: serviceImplementations ?? ({} as TServiceDependencies),
902
1233
  };
903
- const routes = resolveRouteFactories(context, routesOrFactories);
904
-
905
- // 11. Calculate mount route
906
- const mountRoute = getMountRoute({
907
- name: definition.name,
908
- mountRoute: options.mountRoute,
909
- });
1234
+ const routes = resolveRouteFactories(context, routesOrFactories) as AnyFragnoRouteConfig[];
1235
+ const internalRoutes = definition.internalRoutes
1236
+ ? (resolveRouteFactories(context, definition.internalRoutes) as readonly AnyFragnoRouteConfig[])
1237
+ : [];
1238
+ const prefixedInternalRoutes = internalRoutes.map((route) => ({
1239
+ ...route,
1240
+ path: joinRoutePath(INTERNAL_ROUTE_PREFIX, route.path),
1241
+ }));
1242
+ const finalRoutes =
1243
+ prefixedInternalRoutes.length > 0
1244
+ ? [...routes, ...prefixedInternalRoutes]
1245
+ : (routes as AnyFragnoRouteConfig[]);
910
1246
 
911
1247
  // 12. Wrap createRequestStorage to capture context
912
1248
  const createRequestStorageWithContext = definition.createRequestStorage
@@ -918,7 +1254,10 @@ export function instantiateFragment<
918
1254
  // Handlers get handlerContext which may have more capabilities than serviceContext
919
1255
  return new FragnoInstantiatedFragment({
920
1256
  name: definition.name,
921
- routes,
1257
+ routes: finalRoutes as unknown as RoutesWithInternal<
1258
+ FlattenRouteFactories<TRoutesOrFactories>,
1259
+ TInternalRoutes
1260
+ >,
922
1261
  deps,
923
1262
  services: boundServices as BoundServices<TBaseServices & TServices>,
924
1263
  mountRoute,
@@ -927,7 +1266,7 @@ export function instantiateFragment<
927
1266
  storage,
928
1267
  createRequestStorage: createRequestStorageWithContext,
929
1268
  options,
930
- linkedFragments: linkedFragmentInstances,
1269
+ internalData: internalData as Record<string, unknown>,
931
1270
  });
932
1271
  }
933
1272
 
@@ -1003,8 +1342,7 @@ interface IFragnoInstantiatedFragment {
1003
1342
  get $internal(): {
1004
1343
  deps: unknown;
1005
1344
  options: unknown;
1006
- linkedFragments: unknown;
1007
- };
1345
+ } & Record<string, unknown>;
1008
1346
 
1009
1347
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1010
1348
  withMiddleware(handler: any): this;
@@ -1014,10 +1352,13 @@ interface IFragnoInstantiatedFragment {
1014
1352
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1015
1353
  inContext<T>(callback: any): Promise<T>;
1016
1354
 
1355
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1356
+ callServices(serviceCalls: () => any): Promise<any>;
1357
+
1017
1358
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1018
1359
  handlersFor(framework: FullstackFrameworks): any;
1019
1360
 
1020
- handler(req: Request): Promise<Response>;
1361
+ handler(req: Request, lifecycleContext?: FragnoRequestLifecycleContext): Promise<Response>;
1021
1362
 
1022
1363
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1023
1364
  callRoute(method: HTTPMethod, path: string, inputOptions?: any): Promise<any>;
@@ -1042,9 +1383,8 @@ export class FragmentInstantiationBuilder<
1042
1383
  THandlerThisContext extends RequestThisContext,
1043
1384
  TRequestStorage,
1044
1385
  TRoutesOrFactories extends readonly AnyRouteOrFactory[],
1045
- TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment>,
1046
- > implements IFragmentInstantiationBuilder
1047
- {
1386
+ TInternalRoutes extends readonly AnyRouteOrFactory[],
1387
+ > implements IFragmentInstantiationBuilder {
1048
1388
  #definition: FragmentDefinition<
1049
1389
  TConfig,
1050
1390
  TOptions,
@@ -1056,7 +1396,7 @@ export class FragmentInstantiationBuilder<
1056
1396
  TServiceThisContext,
1057
1397
  THandlerThisContext,
1058
1398
  TRequestStorage,
1059
- TLinkedFragments
1399
+ TInternalRoutes
1060
1400
  >;
1061
1401
  #config?: TConfig;
1062
1402
  #routes?: TRoutesOrFactories;
@@ -1075,7 +1415,7 @@ export class FragmentInstantiationBuilder<
1075
1415
  TServiceThisContext,
1076
1416
  THandlerThisContext,
1077
1417
  TRequestStorage,
1078
- TLinkedFragments
1418
+ TInternalRoutes
1079
1419
  >,
1080
1420
  routes?: TRoutesOrFactories,
1081
1421
  ) {
@@ -1097,7 +1437,7 @@ export class FragmentInstantiationBuilder<
1097
1437
  TServiceThisContext,
1098
1438
  THandlerThisContext,
1099
1439
  TRequestStorage,
1100
- TLinkedFragments
1440
+ TInternalRoutes
1101
1441
  > {
1102
1442
  return this.#definition;
1103
1443
  }
@@ -1148,7 +1488,7 @@ export class FragmentInstantiationBuilder<
1148
1488
  THandlerThisContext,
1149
1489
  TRequestStorage,
1150
1490
  TNewRoutes,
1151
- TLinkedFragments
1491
+ TInternalRoutes
1152
1492
  > {
1153
1493
  const newBuilder = new FragmentInstantiationBuilder(this.#definition, routes);
1154
1494
  // Preserve config, options, and services from the current instance
@@ -1178,14 +1518,13 @@ export class FragmentInstantiationBuilder<
1178
1518
  * Build and return the instantiated fragment
1179
1519
  */
1180
1520
  build(): FragnoInstantiatedFragment<
1181
- FlattenRouteFactories<TRoutesOrFactories>,
1521
+ RoutesWithInternal<FlattenRouteFactories<TRoutesOrFactories>, TInternalRoutes>,
1182
1522
  TDeps,
1183
1523
  BoundServices<TBaseServices & TServices>,
1184
1524
  TServiceThisContext,
1185
1525
  THandlerThisContext,
1186
1526
  TRequestStorage,
1187
- TOptions,
1188
- TLinkedFragments
1527
+ TOptions
1189
1528
  > {
1190
1529
  // This variable is set by the frango-cli when extracting database schemas
1191
1530
  const dryRun = process.env["FRAGNO_INIT_DRY_RUN"] === "true";
@@ -1224,7 +1563,7 @@ export function instantiate<
1224
1563
  TServiceThisContext extends RequestThisContext,
1225
1564
  THandlerThisContext extends RequestThisContext,
1226
1565
  TRequestStorage,
1227
- TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment>,
1566
+ TInternalRoutes extends readonly AnyRouteOrFactory[],
1228
1567
  >(
1229
1568
  definition: FragmentDefinition<
1230
1569
  TConfig,
@@ -1237,7 +1576,7 @@ export function instantiate<
1237
1576
  TServiceThisContext,
1238
1577
  THandlerThisContext,
1239
1578
  TRequestStorage,
1240
- TLinkedFragments
1579
+ TInternalRoutes
1241
1580
  >,
1242
1581
  ): FragmentInstantiationBuilder<
1243
1582
  TConfig,
@@ -1251,7 +1590,7 @@ export function instantiate<
1251
1590
  THandlerThisContext,
1252
1591
  TRequestStorage,
1253
1592
  readonly [],
1254
- TLinkedFragments
1593
+ TInternalRoutes
1255
1594
  > {
1256
1595
  return new FragmentInstantiationBuilder(definition);
1257
1596
  }