@fragno-dev/core 0.1.4 → 0.1.6

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 (69) hide show
  1. package/.turbo/turbo-build.log +49 -45
  2. package/CHANGELOG.md +53 -0
  3. package/dist/api/api.d.ts +2 -2
  4. package/dist/api/fragment-builder.d.ts +3 -2
  5. package/dist/api/fragment-instantiation.d.ts +4 -3
  6. package/dist/api/fragment-instantiation.js +3 -3
  7. package/dist/api/route.d.ts +3 -0
  8. package/dist/api/route.js +3 -0
  9. package/dist/{api-BX90b4-D.d.ts → api-CoCkNi6h.d.ts} +20 -7
  10. package/dist/api-CoCkNi6h.d.ts.map +1 -0
  11. package/dist/api-DngJDcmO.js.map +1 -1
  12. package/dist/client/client.d.ts +4 -3
  13. package/dist/client/client.js +3 -3
  14. package/dist/client/client.svelte.d.ts +3 -3
  15. package/dist/client/client.svelte.d.ts.map +1 -1
  16. package/dist/client/client.svelte.js +3 -3
  17. package/dist/client/react.d.ts +3 -3
  18. package/dist/client/react.d.ts.map +1 -1
  19. package/dist/client/react.js +3 -3
  20. package/dist/client/solid.d.ts +3 -3
  21. package/dist/client/solid.d.ts.map +1 -1
  22. package/dist/client/solid.js +3 -3
  23. package/dist/client/vanilla.d.ts +3 -3
  24. package/dist/client/vanilla.d.ts.map +1 -1
  25. package/dist/client/vanilla.js +3 -3
  26. package/dist/client/vue.d.ts +3 -3
  27. package/dist/client/vue.d.ts.map +1 -1
  28. package/dist/client/vue.js +3 -3
  29. package/dist/{client-C6LChM0Y.js → client-DJfCJiHK.js} +81 -7
  30. package/dist/client-DJfCJiHK.js.map +1 -0
  31. package/dist/{fragment-builder-BZr2JkuW.d.ts → fragment-builder-8-tiECi5.d.ts} +75 -38
  32. package/dist/fragment-builder-8-tiECi5.d.ts.map +1 -0
  33. package/dist/{fragment-instantiation-D74OQjbn.js → fragment-instantiation-C4wvwl6V.js} +129 -6
  34. package/dist/fragment-instantiation-C4wvwl6V.js.map +1 -0
  35. package/dist/mod.d.ts +3 -2
  36. package/dist/mod.js +3 -3
  37. package/dist/{route-D1MZR6JL.js → request-output-context-CdIjwmEN.js} +22 -33
  38. package/dist/request-output-context-CdIjwmEN.js.map +1 -0
  39. package/dist/route-C5Uryylh.js +21 -0
  40. package/dist/route-C5Uryylh.js.map +1 -0
  41. package/dist/route-mGLYSUvD.d.ts +26 -0
  42. package/dist/route-mGLYSUvD.d.ts.map +1 -0
  43. package/dist/test/test.d.ts +24 -70
  44. package/dist/test/test.d.ts.map +1 -1
  45. package/dist/test/test.js +27 -115
  46. package/dist/test/test.js.map +1 -1
  47. package/package.json +6 -1
  48. package/src/api/api.ts +1 -0
  49. package/src/api/fragment-instantiation.test.ts +460 -0
  50. package/src/api/fragment-instantiation.ts +157 -5
  51. package/src/api/fragno-response.ts +132 -0
  52. package/src/api/request-input-context.test.ts +37 -29
  53. package/src/api/request-input-context.ts +16 -14
  54. package/src/api/request-output-context.test.ts +10 -10
  55. package/src/api/request-output-context.ts +3 -3
  56. package/src/api/route-handler-input-options.ts +15 -0
  57. package/src/client/client.test.ts +264 -0
  58. package/src/client/client.ts +65 -3
  59. package/src/client/internal/fetcher-merge.ts +59 -0
  60. package/src/test/test.test.ts +110 -165
  61. package/src/test/test.ts +56 -266
  62. package/tsdown.config.ts +1 -0
  63. package/dist/api-BX90b4-D.d.ts.map +0 -1
  64. package/dist/client-C6LChM0Y.js.map +0 -1
  65. package/dist/fragment-builder-BZr2JkuW.d.ts.map +0 -1
  66. package/dist/fragment-instantiation-D74OQjbn.js.map +0 -1
  67. package/dist/route-CTxjMtGZ.js +0 -10
  68. package/dist/route-CTxjMtGZ.js.map +0 -1
  69. package/dist/route-D1MZR6JL.js.map +0 -1
@@ -1,5 +1,13 @@
1
1
  import { r as FragnoApiValidationError } from "./api-DngJDcmO.js";
2
2
 
3
+ //#region src/api/internal/route.ts
4
+ function getMountRoute(opts) {
5
+ const mountRoute = opts.mountRoute ?? `/api/${opts.name}`;
6
+ if (mountRoute.endsWith("/")) return mountRoute.slice(0, -1);
7
+ return mountRoute;
8
+ }
9
+
10
+ //#endregion
3
11
  //#region src/api/request-input-context.ts
4
12
  var RequestInputContext = class RequestInputContext {
5
13
  #path;
@@ -8,6 +16,7 @@ var RequestInputContext = class RequestInputContext {
8
16
  #searchParams;
9
17
  #headers;
10
18
  #body;
19
+ #parsedBody;
11
20
  #inputSchema;
12
21
  #shouldValidateInput;
13
22
  constructor(config) {
@@ -16,7 +25,8 @@ var RequestInputContext = class RequestInputContext {
16
25
  this.#pathParams = config.pathParams;
17
26
  this.#searchParams = config.searchParams;
18
27
  this.#headers = config.headers;
19
- this.#body = config.body;
28
+ this.#body = config.rawBody;
29
+ this.#parsedBody = config.parsedBody;
20
30
  this.#inputSchema = config.inputSchema;
21
31
  this.#shouldValidateInput = config.shouldValidateInput ?? true;
22
32
  }
@@ -30,7 +40,8 @@ var RequestInputContext = class RequestInputContext {
30
40
  pathParams: config.state.pathParams,
31
41
  searchParams: config.state.searchParams,
32
42
  headers: config.state.headers,
33
- body: config.state.body,
43
+ parsedBody: config.state.body,
44
+ rawBody: config.rawBody,
34
45
  inputSchema: config.inputSchema,
35
46
  shouldValidateInput: config.shouldValidateInput
36
47
  });
@@ -45,7 +56,7 @@ var RequestInputContext = class RequestInputContext {
45
56
  pathParams: config.pathParams,
46
57
  searchParams: config.searchParams ?? new URLSearchParams(),
47
58
  headers: config.headers ?? new Headers(),
48
- body: "body" in config ? config.body : void 0,
59
+ parsedBody: "body" in config ? config.body : void 0,
49
60
  inputSchema: "inputSchema" in config ? config.inputSchema : void 0,
50
61
  shouldValidateInput: false
51
62
  });
@@ -84,9 +95,6 @@ var RequestInputContext = class RequestInputContext {
84
95
  get headers() {
85
96
  return this.#headers;
86
97
  }
87
- /**
88
- * @internal
89
- */
90
98
  get rawBody() {
91
99
  return this.#body;
92
100
  }
@@ -99,15 +107,15 @@ var RequestInputContext = class RequestInputContext {
99
107
  return {
100
108
  schema: this.#inputSchema,
101
109
  valid: async () => {
102
- if (!this.#shouldValidateInput) return this.#body;
110
+ if (!this.#shouldValidateInput) return this.#parsedBody;
103
111
  return this.#validateInput();
104
112
  }
105
113
  };
106
114
  }
107
115
  async #validateInput() {
108
116
  if (!this.#inputSchema) throw new Error("No input schema defined for this route");
109
- if (this.#body instanceof FormData || this.#body instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
110
- const result = await this.#inputSchema["~standard"].validate(this.#body);
117
+ if (this.#parsedBody instanceof FormData || this.#parsedBody instanceof Blob) throw new Error("Schema validation is only supported for JSON data, not FormData or Blob");
118
+ const result = await this.#inputSchema["~standard"].validate(this.#parsedBody);
111
119
  if (result.issues) throw new FragnoApiValidationError("Validation failed", result.issues);
112
120
  return result.value;
113
121
  }
@@ -242,20 +250,20 @@ var OutputContext = class {
242
250
  const defaultHeaders = {};
243
251
  if (typeof initOrStatus === "undefined") {
244
252
  const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
245
- return Response.json(null, {
253
+ return new Response(null, {
246
254
  status: 201,
247
255
  headers: mergedHeaders$1
248
256
  });
249
257
  }
250
258
  if (typeof initOrStatus === "number") {
251
259
  const mergedHeaders$1 = mergeHeaders(defaultHeaders, headers);
252
- return Response.json(null, {
260
+ return new Response(null, {
253
261
  status: initOrStatus,
254
262
  headers: mergedHeaders$1
255
263
  });
256
264
  }
257
265
  const mergedHeaders = mergeHeaders(defaultHeaders, initOrStatus.headers, headers);
258
- return Response.json(null, {
266
+ return new Response(null, {
259
267
  status: initOrStatus.status,
260
268
  headers: mergedHeaders
261
269
  });
@@ -308,24 +316,5 @@ var RequestOutputContext = class extends OutputContext {
308
316
  };
309
317
 
310
318
  //#endregion
311
- //#region src/api/route.ts
312
- function resolveRouteFactories(context, routesOrFactories) {
313
- const routes = [];
314
- for (const item of routesOrFactories) if (typeof item === "function") {
315
- const factoryRoutes = item(context);
316
- routes.push(...factoryRoutes);
317
- } else routes.push(item);
318
- return routes;
319
- }
320
- function defineRoute(config) {
321
- return config;
322
- }
323
- function defineRoutes() {
324
- return { create: (fn) => {
325
- return fn;
326
- } };
327
- }
328
-
329
- //#endregion
330
- export { RequestOutputContext as a, OutputContext as i, defineRoutes as n, RequestInputContext as o, resolveRouteFactories as r, defineRoute as t };
331
- //# sourceMappingURL=route-D1MZR6JL.js.map
319
+ export { getMountRoute as i, RequestOutputContext as n, RequestInputContext as r, OutputContext as t };
320
+ //# sourceMappingURL=request-output-context-CdIjwmEN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-output-context-CdIjwmEN.js","names":["#path","#method","#pathParams","#searchParams","#headers","#body","#parsedBody","#inputSchema","#shouldValidateInput","#validateInput","#aborted","#closed","#responseReadable","#writer","#encoder","#abortSubscribers","mergedHeaders","#outputSchema"],"sources":["../src/api/internal/route.ts","../src/api/request-input-context.ts","../src/api/internal/response-stream.ts","../src/api/request-output-context.ts"],"sourcesContent":["export function getMountRoute(opts: { mountRoute?: string; name: string }) {\n const mountRoute = opts.mountRoute ?? `/api/${opts.name}`;\n\n if (mountRoute.endsWith(\"/\")) {\n return mountRoute.slice(0, -1);\n }\n\n return mountRoute;\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport { FragnoApiValidationError, type HTTPMethod } from \"./api\";\nimport type { MutableRequestState } from \"./mutable-request-state\";\n\nexport type RequestBodyType =\n | unknown // JSON\n | FormData\n | Blob\n | null\n | undefined;\n\nexport class RequestInputContext<\n TPath extends string = string,\n TInputSchema extends StandardSchemaV1 | undefined = undefined,\n> {\n readonly #path: TPath;\n readonly #method: string;\n readonly #pathParams: ExtractPathParams<TPath>;\n readonly #searchParams: URLSearchParams;\n readonly #headers: Headers;\n readonly #body: string | undefined;\n readonly #parsedBody: RequestBodyType;\n readonly #inputSchema: TInputSchema | undefined;\n readonly #shouldValidateInput: boolean;\n\n constructor(config: {\n path: TPath;\n method: string;\n pathParams: ExtractPathParams<TPath>;\n searchParams: URLSearchParams;\n parsedBody: RequestBodyType;\n rawBody?: string;\n headers: Headers;\n request?: Request;\n inputSchema?: TInputSchema;\n shouldValidateInput?: boolean;\n }) {\n this.#path = config.path;\n this.#method = config.method;\n this.#pathParams = config.pathParams;\n this.#searchParams = config.searchParams;\n this.#headers = config.headers;\n this.#body = config.rawBody;\n this.#parsedBody = config.parsedBody;\n this.#inputSchema = config.inputSchema;\n this.#shouldValidateInput = config.shouldValidateInput ?? true;\n }\n\n /**\n * Create a RequestContext from a Request object for server-side handling\n */\n static async fromRequest<\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined = undefined,\n >(config: {\n request: Request;\n method: string;\n path: TPath;\n pathParams: ExtractPathParams<TPath>;\n inputSchema?: TInputSchema;\n shouldValidateInput?: boolean;\n state: MutableRequestState;\n rawBody?: string;\n }): Promise<RequestInputContext<TPath, TInputSchema>> {\n // Use the mutable state (potentially modified by middleware)\n return new RequestInputContext({\n method: config.method,\n path: config.path,\n pathParams: config.state.pathParams as ExtractPathParams<TPath>,\n searchParams: config.state.searchParams,\n headers: config.state.headers,\n parsedBody: config.state.body,\n rawBody: config.rawBody,\n inputSchema: config.inputSchema,\n shouldValidateInput: config.shouldValidateInput,\n });\n }\n\n /**\n * Create a RequestContext for server-side rendering contexts (no Request object)\n */\n static fromSSRContext<\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined = undefined,\n >(\n config:\n | {\n method: \"GET\";\n path: TPath;\n pathParams: ExtractPathParams<TPath>;\n searchParams?: URLSearchParams;\n headers?: Headers;\n }\n | {\n method: Exclude<HTTPMethod, \"GET\">;\n path: TPath;\n pathParams: ExtractPathParams<TPath>;\n searchParams?: URLSearchParams;\n headers?: Headers;\n body: RequestBodyType;\n inputSchema?: TInputSchema;\n },\n ): RequestInputContext<TPath, TInputSchema> {\n return new RequestInputContext({\n method: config.method,\n path: config.path,\n pathParams: config.pathParams,\n searchParams: config.searchParams ?? new URLSearchParams(),\n headers: config.headers ?? new Headers(),\n parsedBody: \"body\" in config ? config.body : undefined,\n inputSchema: \"inputSchema\" in config ? config.inputSchema : undefined,\n shouldValidateInput: false, // No input validation in SSR context\n });\n }\n\n /**\n * The HTTP method as string (e.g., `GET`, `POST`)\n */\n get method(): string {\n return this.#method;\n }\n /**\n * The matched route path (e.g., `/users/:id`)\n * @remarks `string`\n */\n get path(): TPath {\n return this.#path;\n }\n /**\n * Extracted path parameters as object (e.g., `{ id: '123' }`)\n * @remarks `Record<string, string>`\n */\n get pathParams(): ExtractPathParams<TPath> {\n return this.#pathParams;\n }\n /**\n * [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object for query parameters\n * @remarks `URLSearchParams`\n */\n get query(): URLSearchParams {\n return this.#searchParams;\n }\n /**\n * [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) object for request headers\n * @remarks `Headers`\n */\n get headers(): Headers {\n return this.#headers;\n }\n\n get rawBody(): string | undefined {\n return this.#body;\n }\n\n /**\n * Input validation context (only if inputSchema is defined)\n * @remarks `InputContext`\n */\n get input(): TInputSchema extends undefined\n ? undefined\n : {\n schema: TInputSchema;\n valid: () => Promise<\n TInputSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TInputSchema>\n : unknown\n >;\n } {\n if (!this.#inputSchema) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return undefined as any;\n }\n\n return {\n schema: this.#inputSchema,\n valid: async () => {\n if (!this.#shouldValidateInput) {\n // In SSR context, return the body directly without validation\n return this.#parsedBody;\n }\n\n return this.#validateInput();\n },\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } as any;\n }\n\n async #validateInput(): Promise<\n TInputSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TInputSchema> : never\n > {\n if (!this.#inputSchema) {\n throw new Error(\"No input schema defined for this route\");\n }\n\n if (this.#parsedBody instanceof FormData || this.#parsedBody instanceof Blob) {\n throw new Error(\"Schema validation is only supported for JSON data, not FormData or Blob\");\n }\n\n const result = await this.#inputSchema[\"~standard\"].validate(this.#parsedBody);\n\n if (result.issues) {\n throw new FragnoApiValidationError(\"Validation failed\", result.issues);\n }\n\n return result.value as TInputSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TInputSchema>\n : never;\n }\n}\n","/**\n * @module\n * Stream utility.\n *\n * Modified from honojs/hono\n * Original source: https://github.com/honojs/hono/blob/0e3db674ad3f40be215a55a18062dd8e387ce525/src/utils/stream.ts\n * License: MIT\n * Date obtained: August 28 2025\n * Copyright (c) 2021-present Yusuke Wada and Hono contributors\n */\n\ntype Error<Message extends string> = { __errorMessage: Message };\n\nexport class ResponseStream<TArray> {\n #writer: WritableStreamDefaultWriter<Uint8Array>;\n #encoder: TextEncoder;\n #abortSubscribers: (() => void | Promise<void>)[] = [];\n #responseReadable: ReadableStream;\n\n #aborted: boolean = false;\n #closed: boolean = false;\n\n /**\n * Whether the stream has been aborted.\n */\n get aborted(): boolean {\n return this.#aborted;\n }\n\n /**\n * Whether the stream has been closed normally.\n */\n get closed(): boolean {\n return this.#closed;\n }\n\n /**\n * The readable stream that the response is piped to.\n */\n get responseReadable(): ReadableStream {\n return this.#responseReadable;\n }\n\n constructor(writable: WritableStream, readable: ReadableStream) {\n this.#writer = writable.getWriter();\n this.#encoder = new TextEncoder();\n const reader = readable.getReader();\n\n // in case the user disconnects, let the reader know to cancel\n // this in-turn results in responseReadable being closed\n // and writeSSE method no longer blocks indefinitely\n this.#abortSubscribers.push(async () => {\n await reader.cancel();\n });\n\n this.#responseReadable = new ReadableStream({\n async pull(controller) {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n } else {\n controller.enqueue(value);\n }\n },\n cancel: () => {\n this.abort();\n },\n });\n }\n\n async writeRaw(input: Uint8Array | string): Promise<void> {\n try {\n if (typeof input === \"string\") {\n input = this.#encoder.encode(input);\n }\n await this.#writer.write(input);\n } catch {\n // Do nothing.\n }\n }\n\n write(\n input: TArray extends (infer U)[]\n ? U\n : Error<\"To use a streaming response, outputSchema must be an array.\">,\n ): Promise<void> {\n return this.writeRaw(JSON.stringify(input) + \"\\n\");\n }\n\n sleep(ms: number): Promise<unknown> {\n return new Promise((res) => setTimeout(res, ms));\n }\n\n async close() {\n try {\n await this.#writer.close();\n } catch {\n // Do nothing. If you want to handle errors, create a stream by yourself.\n } finally {\n this.#closed = true;\n }\n }\n\n onAbort(listener: () => void | Promise<void>) {\n this.#abortSubscribers.push(listener);\n }\n\n /**\n * Abort the stream.\n * You can call this method when stream is aborted by external event.\n */\n abort() {\n if (!this.aborted) {\n this.#aborted = true;\n this.#abortSubscribers.forEach((subscriber) => subscriber());\n }\n }\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ContentlessStatusCode, StatusCode } from \"../http/http-status\";\nimport { ResponseStream } from \"./internal/response-stream\";\nimport type { InferOrUnknown } from \"../util/types-util\";\n\nexport type ResponseData = string | ArrayBuffer | ReadableStream | Uint8Array<ArrayBuffer>;\n\ninterface ResponseInit<T extends StatusCode = StatusCode> {\n headers?: HeadersInit;\n status?: T;\n statusText?: string;\n}\n\n/**\n * Utility function to merge headers from multiple sources.\n * Later headers override earlier ones.\n */\nfunction mergeHeaders(...headerSources: (HeadersInit | undefined)[]): HeadersInit | undefined {\n const mergedHeaders = new Headers();\n\n for (const headerSource of headerSources) {\n if (!headerSource) {\n continue;\n }\n\n if (headerSource instanceof Headers) {\n for (const [key, value] of headerSource.entries()) {\n mergedHeaders.set(key, value);\n }\n } else if (Array.isArray(headerSource)) {\n for (const [key, value] of headerSource) {\n mergedHeaders.set(key, value);\n }\n } else {\n for (const [key, value] of Object.entries(headerSource)) {\n mergedHeaders.set(key, value);\n }\n }\n }\n\n return mergedHeaders;\n}\n\nexport abstract class OutputContext<const TOutput, const TErrorCode extends string> {\n /**\n * Creates an error response.\n *\n * Shortcut for `throw new FragnoApiError(...)`\n */\n error = (\n { message, code }: { message: string; code: TErrorCode },\n initOrStatus?: ResponseInit | StatusCode,\n headers?: HeadersInit,\n ): Response => {\n if (typeof initOrStatus === \"undefined\") {\n return Response.json({ message: message, code }, { status: 500, headers });\n }\n\n if (typeof initOrStatus === \"number\") {\n return Response.json({ message: message, code }, { status: initOrStatus, headers });\n }\n\n const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);\n return Response.json(\n { message: message, code },\n { status: initOrStatus.status, headers: mergedHeaders },\n );\n };\n\n empty = (\n initOrStatus?: ResponseInit<ContentlessStatusCode> | ContentlessStatusCode,\n headers?: HeadersInit,\n ): Response => {\n const defaultHeaders = {};\n\n if (typeof initOrStatus === \"undefined\") {\n const mergedHeaders = mergeHeaders(defaultHeaders, headers);\n return new Response(null, {\n status: 201,\n headers: mergedHeaders,\n });\n }\n\n if (typeof initOrStatus === \"number\") {\n const mergedHeaders = mergeHeaders(defaultHeaders, headers);\n return new Response(null, {\n status: initOrStatus,\n headers: mergedHeaders,\n });\n }\n\n const mergedHeaders = mergeHeaders(defaultHeaders, initOrStatus.headers, headers);\n return new Response(null, {\n status: initOrStatus.status,\n headers: mergedHeaders,\n });\n };\n\n json = (\n object: TOutput,\n initOrStatus?: ResponseInit | StatusCode,\n headers?: HeadersInit,\n ): Response => {\n if (typeof initOrStatus === \"undefined\") {\n return Response.json(object, {\n status: 200,\n headers,\n });\n }\n\n if (typeof initOrStatus === \"number\") {\n return Response.json(object, {\n status: initOrStatus,\n headers,\n });\n }\n\n const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);\n return Response.json(object, {\n status: initOrStatus.status,\n headers: mergedHeaders,\n });\n };\n\n jsonStream = (\n cb: (stream: ResponseStream<TOutput>) => void | Promise<void>,\n {\n onError,\n headers,\n }: {\n onError?: (error: Error, stream: ResponseStream<TOutput>) => void | Promise<void>;\n headers?: HeadersInit;\n } = {},\n ): Response => {\n // Note: this is intentionally an arrow function (=>) to keep `this` context.\n const defaultHeaders = {\n \"content-type\": \"application/x-ndjson; charset=utf-8\",\n \"transfer-encoding\": \"chunked\",\n \"cache-control\": \"no-cache\",\n };\n\n const { readable, writable } = new TransformStream();\n const stream = new ResponseStream(writable, readable);\n\n (async () => {\n try {\n await cb(stream);\n } catch (e) {\n if (e === undefined) {\n // If reading is canceled without a reason value (e.g. by StreamingApi)\n // then the .pipeTo() promise will reject with undefined.\n // In this case, do nothing because the stream is already closed.\n } else if (e instanceof Error && onError) {\n await onError(e, stream);\n } else {\n console.error(e);\n }\n } finally {\n stream.close();\n }\n })();\n\n return new Response(stream.responseReadable, {\n status: 200,\n headers: mergeHeaders(defaultHeaders, headers),\n });\n };\n}\n\nexport class RequestOutputContext<\n const TOutputSchema extends StandardSchemaV1 | undefined = undefined,\n const TErrorCode extends string = string,\n> extends OutputContext<InferOrUnknown<TOutputSchema>, TErrorCode> {\n // eslint-disable-next-line no-unused-private-class-members\n #outputSchema?: TOutputSchema;\n\n constructor(outputSchema?: TOutputSchema) {\n super();\n this.#outputSchema = outputSchema;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ //#region src/api/route.ts
2
+ function resolveRouteFactories(context, routesOrFactories) {
3
+ const routes = [];
4
+ for (const item of routesOrFactories) if (typeof item === "function") {
5
+ const factoryRoutes = item(context);
6
+ routes.push(...factoryRoutes);
7
+ } else routes.push(item);
8
+ return routes;
9
+ }
10
+ function defineRoute(config) {
11
+ return config;
12
+ }
13
+ function defineRoutes() {
14
+ return { create: (fn) => {
15
+ return fn;
16
+ } };
17
+ }
18
+
19
+ //#endregion
20
+ export { defineRoutes as n, resolveRouteFactories as r, defineRoute as t };
21
+ //# sourceMappingURL=route-C5Uryylh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-C5Uryylh.js","names":["routes: any[]"],"sources":["../src/api/route.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { FragnoRouteConfig, HTTPMethod } from \"./api\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyFragnoRouteConfig = FragnoRouteConfig<HTTPMethod, string, any, any, any, any>;\n\nexport interface RouteFactoryContext<TConfig, TDeps, TServices> {\n config: TConfig;\n deps: TDeps;\n services: TServices;\n}\n\nexport type RouteFactory<\n TConfig,\n TDeps,\n TServices,\n TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >[],\n> = (context: RouteFactoryContext<TConfig, TDeps, TServices>) => TRoutes;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyRouteOrFactory = AnyFragnoRouteConfig | RouteFactory<any, any, any, any>;\n\nexport type FlattenRouteFactories<T extends readonly AnyRouteOrFactory[]> = T extends readonly [\n infer First,\n ...infer Rest extends readonly AnyRouteOrFactory[],\n]\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n First extends RouteFactory<any, any, any, infer TRoutes>\n ? [...TRoutes, ...FlattenRouteFactories<Rest>]\n : [First, ...FlattenRouteFactories<Rest>]\n : [];\n\n// Helper to resolve route factories into routes\nexport function resolveRouteFactories<\n TConfig,\n TDeps,\n TServices,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n>(\n context: RouteFactoryContext<TConfig, TDeps, TServices>,\n routesOrFactories: TRoutesOrFactories,\n): FlattenRouteFactories<TRoutesOrFactories> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const routes: any[] = [];\n\n for (const item of routesOrFactories) {\n if (typeof item === \"function\") {\n // It's a route factory\n const factoryRoutes = item(context);\n routes.push(...factoryRoutes);\n } else {\n // It's a direct route\n routes.push(item);\n }\n }\n\n return routes as FlattenRouteFactories<TRoutesOrFactories>;\n}\n\n// TODO(Wilco): Do these overloads actually do anything?\n// TODO(Wilco): ValidPath<T> should be added back in here.\n\n// Overload for routes without inputSchema\nexport function defineRoute<\n const TMethod extends HTTPMethod,\n const TPath extends string,\n const TOutputSchema extends StandardSchemaV1 | undefined,\n const TErrorCode extends string = string,\n const TQueryParameters extends string = string,\n>(\n config: FragnoRouteConfig<\n TMethod,\n TPath,\n undefined,\n TOutputSchema,\n TErrorCode,\n TQueryParameters\n > & { inputSchema?: undefined },\n): FragnoRouteConfig<TMethod, TPath, undefined, TOutputSchema, TErrorCode, TQueryParameters>;\n\n// Overload for routes with inputSchema\nexport function defineRoute<\n const TMethod extends HTTPMethod,\n const TPath extends string,\n const TInputSchema extends StandardSchemaV1,\n const TOutputSchema extends StandardSchemaV1 | undefined,\n const TErrorCode extends string = string,\n const TQueryParameters extends string = string,\n>(\n config: FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters\n > & { inputSchema: TInputSchema },\n): FragnoRouteConfig<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters>;\n\n// implementation\nexport function defineRoute<\n const TMethod extends HTTPMethod,\n const TPath extends string,\n const TInputSchema extends StandardSchemaV1 | undefined,\n const TOutputSchema extends StandardSchemaV1 | undefined,\n const TErrorCode extends string = string,\n const TQueryParameters extends string = string,\n>(\n config: FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters\n >,\n): FragnoRouteConfig<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters> {\n return config;\n}\n\nexport function defineRoutes<TConfig = {}, TDeps = {}, TServices = {}>() {\n return {\n create: <\n const TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >[],\n >(\n fn: (context: RouteFactoryContext<TConfig, TDeps, TServices>) => TRoutes,\n ): RouteFactory<TConfig, TDeps, TServices, TRoutes> => {\n return fn;\n },\n };\n}\n"],"mappings":";AAwCA,SAAgB,sBAMd,SACA,mBAC2C;CAE3C,MAAMA,SAAgB,EAAE;AAExB,MAAK,MAAM,QAAQ,kBACjB,KAAI,OAAO,SAAS,YAAY;EAE9B,MAAM,gBAAgB,KAAK,QAAQ;AACnC,SAAO,KAAK,GAAG,cAAc;OAG7B,QAAO,KAAK,KAAK;AAIrB,QAAO;;AA4CT,SAAgB,YAQd,QAQ8F;AAC9F,QAAO;;AAGT,SAAgB,eAAyD;AACvE,QAAO,EACL,SAUE,OACqD;AACrD,SAAO;IAEV"}
@@ -0,0 +1,26 @@
1
+ import { n as HTTPMethod, t as FragnoRouteConfig } from "./api-CoCkNi6h.js";
2
+ import { StandardSchemaV1 } from "@standard-schema/spec";
3
+
4
+ //#region src/api/route.d.ts
5
+ type AnyFragnoRouteConfig = FragnoRouteConfig<HTTPMethod, string, any, any, any, any>;
6
+ interface RouteFactoryContext<TConfig, TDeps, TServices> {
7
+ config: TConfig;
8
+ deps: TDeps;
9
+ services: TServices;
10
+ }
11
+ type RouteFactory<TConfig, TDeps, TServices, TRoutes$1 extends readonly FragnoRouteConfig<HTTPMethod, string, StandardSchemaV1 | undefined, StandardSchemaV1 | undefined, string, string>[]> = (context: RouteFactoryContext<TConfig, TDeps, TServices>) => TRoutes$1;
12
+ type AnyRouteOrFactory = AnyFragnoRouteConfig | RouteFactory<any, any, any, any>;
13
+ type FlattenRouteFactories<T extends readonly AnyRouteOrFactory[]> = T extends readonly [infer First, ...infer Rest extends readonly AnyRouteOrFactory[]] ? First extends RouteFactory<any, any, any, infer TRoutes> ? [...TRoutes, ...FlattenRouteFactories<Rest>] : [First, ...FlattenRouteFactories<Rest>] : [];
14
+ declare function resolveRouteFactories<TConfig, TDeps, TServices, const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(context: RouteFactoryContext<TConfig, TDeps, TServices>, routesOrFactories: TRoutesOrFactories): FlattenRouteFactories<TRoutesOrFactories>;
15
+ declare function defineRoute<const TMethod extends HTTPMethod, const TPath extends string, const TOutputSchema extends StandardSchemaV1 | undefined, const TErrorCode extends string = string, const TQueryParameters extends string = string>(config: FragnoRouteConfig<TMethod, TPath, undefined, TOutputSchema, TErrorCode, TQueryParameters> & {
16
+ inputSchema?: undefined;
17
+ }): FragnoRouteConfig<TMethod, TPath, undefined, TOutputSchema, TErrorCode, TQueryParameters>;
18
+ declare function defineRoute<const TMethod extends HTTPMethod, const TPath extends string, const TInputSchema extends StandardSchemaV1, const TOutputSchema extends StandardSchemaV1 | undefined, const TErrorCode extends string = string, const TQueryParameters extends string = string>(config: FragnoRouteConfig<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters> & {
19
+ inputSchema: TInputSchema;
20
+ }): FragnoRouteConfig<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters>;
21
+ declare function defineRoutes<TConfig = {}, TDeps = {}, TServices = {}>(): {
22
+ create: <const TRoutes$1 extends readonly FragnoRouteConfig<HTTPMethod, string, StandardSchemaV1 | undefined, StandardSchemaV1 | undefined, string, string>[]>(fn: (context: RouteFactoryContext<TConfig, TDeps, TServices>) => TRoutes$1) => RouteFactory<TConfig, TDeps, TServices, TRoutes$1>;
23
+ };
24
+ //#endregion
25
+ export { RouteFactoryContext as a, resolveRouteFactories as c, RouteFactory as i, AnyRouteOrFactory as n, defineRoute as o, FlattenRouteFactories as r, defineRoutes as s, AnyFragnoRouteConfig as t };
26
+ //# sourceMappingURL=route-mGLYSUvD.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-mGLYSUvD.d.ts","names":[],"sources":["../src/api/route.ts"],"sourcesContent":[],"mappings":"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"}
@@ -1,35 +1,10 @@
1
- import { f as ExtractPathParams, n as HTTPMethod, t as FragnoRouteConfig } from "../api-BX90b4-D.js";
2
- import { B as AnyRouteOrFactory, V as FlattenRouteFactories, c as FragnoPublicConfig, n as FragmentDefinition } from "../fragment-builder-BZr2JkuW.js";
1
+ import { d as InferOrUnknown, n as HTTPMethod, o as RouteHandlerInputOptions, t as FragnoRouteConfig } from "../api-CoCkNi6h.js";
2
+ import { n as AnyRouteOrFactory, r as FlattenRouteFactories } from "../route-mGLYSUvD.js";
3
+ import { b as ExtractRouteByPath, f as FragnoResponse, l as FragnoPublicConfig, n as FragmentDefinition, x as ExtractRoutePath } from "../fragment-builder-8-tiECi5.js";
3
4
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
5
 
5
6
  //#region src/test/test.d.ts
6
7
 
7
- /**
8
- * Discriminated union representing all possible test response types
9
- */
10
- type TestResponse<T> = {
11
- type: "empty";
12
- status: number;
13
- headers: Headers;
14
- } | {
15
- type: "error";
16
- status: number;
17
- headers: Headers;
18
- error: {
19
- message: string;
20
- code: string;
21
- };
22
- } | {
23
- type: "json";
24
- status: number;
25
- headers: Headers;
26
- data: T;
27
- } | {
28
- type: "jsonStream";
29
- status: number;
30
- headers: Headers;
31
- stream: AsyncGenerator<T>;
32
- };
33
8
  /**
34
9
  * Options for creating a test fragment
35
10
  */
@@ -41,61 +16,40 @@ interface CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalCon
41
16
  additionalContext?: Partial<TAdditionalContext>;
42
17
  }
43
18
  /**
44
- * Options for calling a route handler
45
- */
46
- interface RouteHandlerInputOptions<TPath extends string, TInputSchema extends StandardSchemaV1 | undefined> {
47
- pathParams?: ExtractPathParams<TPath>;
48
- body?: TInputSchema extends StandardSchemaV1 ? StandardSchemaV1.InferInput<TInputSchema> : never;
49
- query?: URLSearchParams | Record<string, string>;
50
- headers?: Headers | Record<string, string>;
51
- }
52
- /**
53
- * Options for overriding config/deps/services when initializing routes
54
- */
55
- interface InitRoutesOverrides<TConfig, TDeps, TServices> {
56
- config?: Partial<TConfig>;
57
- deps?: Partial<TDeps>;
58
- services?: Partial<TServices>;
59
- }
60
- /**
61
- * Fragment test instance with type-safe handler method
19
+ * Fragment test instance with type-safe callRoute method
62
20
  */
63
- interface FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig> {
21
+ interface FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig, TRoutes extends readonly FragnoRouteConfig<HTTPMethod, string, StandardSchemaV1 | undefined, StandardSchemaV1 | undefined, string, string>[]> {
64
22
  config: TConfig;
65
23
  deps: TDeps;
66
24
  services: TServices;
67
25
  additionalContext: TAdditionalContext & TOptions;
68
- handler: <TMethod extends HTTPMethod, TPath extends string, TInputSchema extends StandardSchemaV1 | undefined, TOutputSchema extends StandardSchemaV1 | undefined, TErrorCode extends string, TQueryParameters extends string>(route: FragnoRouteConfig<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters>, inputOptions?: RouteHandlerInputOptions<TPath, TInputSchema>) => Promise<TestResponse<TOutputSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TOutputSchema> : unknown>>;
69
- initRoutes: <const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(routesOrFactories: TRoutesOrFactories, overrides?: InitRoutesOverrides<TConfig, TDeps, TServices>) => FlattenRouteFactories<TRoutesOrFactories>;
26
+ callRoute: <const TMethod extends HTTPMethod, const TPath extends ExtractRoutePath<TRoutes, TMethod>>(method: TMethod, path: TPath, inputOptions?: RouteHandlerInputOptions<TPath, ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]>) => Promise<FragnoResponse<InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>["outputSchema"]>>>>;
70
27
  }
71
28
  /**
72
29
  * Create a fragment instance for testing with optional dependency and service overrides
73
30
  *
74
31
  * @param fragmentBuilder - The fragment builder with definition and required options
32
+ * @param routesOrFactories - Route configurations or route factories
75
33
  * @param options - Configuration and optional overrides for deps/services
76
- * @returns A fragment test instance with a type-safe handler method
34
+ * @returns A fragment test instance with a type-safe callRoute method
77
35
  *
78
36
  * @example
79
37
  * ```typescript
80
- * const fragment = createFragmentForTest(chatnoDefinition, {
81
- * config: { openaiApiKey: "test-key" },
82
- * options: { mountRoute: "/api/chatno" },
83
- * services: {
84
- * generateStreamMessages: mockGenerator
38
+ * const fragment = createFragmentForTest(
39
+ * chatnoDefinition,
40
+ * [routesFactory],
41
+ * {
42
+ * config: { openaiApiKey: "test-key" },
43
+ * options: { mountRoute: "/api/chatno" },
44
+ * services: {
45
+ * generateStreamMessages: mockGenerator
46
+ * }
85
47
  * }
86
- * });
87
- *
88
- * // Initialize routes with fragment's config/deps/services
89
- * const [route] = fragment.initRoutes(routes);
90
- *
91
- * // Or override specific config/deps/services for certain routes
92
- * const [route] = fragment.initRoutes(routes, {
93
- * services: { mockService: mockImplementation }
94
- * });
48
+ * );
95
49
  *
96
- * const response = await fragment.handler(route, {
97
- * pathParams: { id: "123" },
98
- * body: { message: "Hello" }
50
+ * // Call routes directly by method and path
51
+ * const response = await fragment.callRoute("POST", "/login", {
52
+ * body: { username: "test", password: "test123" }
99
53
  * });
100
54
  *
101
55
  * if (response.type === 'json') {
@@ -103,10 +57,10 @@ interface FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext extends
103
57
  * }
104
58
  * ```
105
59
  */
106
- declare function createFragmentForTest<TConfig, TDeps, TServices extends Record<string, unknown>, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig>(fragmentBuilder: {
60
+ declare function createFragmentForTest<TConfig, TDeps, TServices extends Record<string, unknown>, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig, const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(fragmentBuilder: {
107
61
  definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;
108
62
  $requiredOptions: TOptions;
109
- }, options: CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>): FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions>;
63
+ }, routesOrFactories: TRoutesOrFactories, options: CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>): FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions, FlattenRouteFactories<TRoutesOrFactories>>;
110
64
  //#endregion
111
- export { CreateFragmentForTestOptions, FragmentForTest, InitRoutesOverrides, RouteHandlerInputOptions, TestResponse, createFragmentForTest };
65
+ export { CreateFragmentForTestOptions, FragmentForTest, type FragnoResponse, type RouteHandlerInputOptions, createFragmentForTest };
112
66
  //# sourceMappingURL=test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test.d.ts","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"test.d.ts","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA6BqB,UAVJ,4BAUI,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BANQ,MAMR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBALF,kBAKE,CAAA,CAAA;EAAR,MAAA,EAHH,OAGG;EACiB,OAAA,CAAA,EAHlB,OAGkB,CAHV,QAGU,CAAA;EAAR,IAAA,CAAA,EAFb,OAEa,CAFL,KAEK,CAAA;EAAO,QAAA,CAAA,EADhB,OACgB,CADR,SACQ,CAAA;EAMZ,iBAAA,CAAe,EANV,OAMU,CANF,kBAME,CAAA;;;;;AAU5B,UAVa,eAUb,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BANyB,MAMzB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBALe,kBAKf,EAAA,gBAAA,SAJuB,iBAIvB,CAHA,UAGA,EAAA,MAAA,EADA,gBACA,GAAA,SAAA,EAAA,gBAAA,GAAA,SAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,CAAA,CAAA;EAJuB,MAAA,EASjB,OATiB;EASjB,IAAA,EACF,KADE;EACF,QAAA,EACI,SADJ;EACI,iBAAA,EACS,kBADT,GAC8B,QAD9B;EACS,SAAA,EAAA,CAAA,sBAEK,UAFL,EAAA,oBAGG,gBAHH,CAGoB,OAHpB,EAG6B,OAH7B,CAAA,CAAA,CAAA,MAAA,EAKT,OALS,EAAA,IAAA,EAMX,KANW,EAAA,YAAA,CAAA,EAOF,wBAPE,CAQf,KARe,EASf,kBATe,CASI,OATJ,EASa,KATb,EASoB,OATpB,CAAA,CAAA,aAAA,CAAA,CAAA,EAAA,GAWd,OAXc,CAYjB,cAZiB,CAaf,cAbe,CAaA,WAbA,CAaY,kBAbZ,CAa+B,OAb/B,EAawC,KAbxC,EAa+C,OAb/C,CAAA,CAAA,cAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;AAkDrB;;;;;;;;;;;AAYqB,iBAZL,qBAYK,CAAA,OAAA,EAAA,KAAA,EAAA,kBATD,MASC,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,2BARQ,MAQR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBAPF,kBAOE,EAAA,iCAAA,SANuB,iBAMvB,EAAA,CAAA,CAAA,eAAA,EAAA;EACmB,UAAA,EAJxB,kBAIwB,CAJL,OAIK,EAJI,KAIJ,EAJW,SAIX,EAJsB,kBAItB,CAAA;EAAS,gBAAA,EAH3B,QAG2B;CAAO,EAAA,iBAAA,EADnC,kBACmC,EAAA,OAAA,EAA7C,4BAA6C,CAAhB,OAAgB,EAAP,KAAO,EAAA,SAAA,EAAW,kBAAX,EAA+B,QAA/B,CAAA,CAAA,EACrD,eADqD,CAEtD,OAFsD,EAGtD,KAHsD,EAItD,SAJsD,EAKtD,kBALsD,EAMtD,QANsD,EAOtD,qBAPsD,CAOhC,kBAPgC,CAAA,CAAA"}
package/dist/test/test.js CHANGED
@@ -1,92 +1,34 @@
1
1
  import "../api-DngJDcmO.js";
2
- import { a as RequestOutputContext, o as RequestInputContext, r as resolveRouteFactories } from "../route-D1MZR6JL.js";
2
+ import "../request-output-context-CdIjwmEN.js";
3
+ import "../route-C5Uryylh.js";
4
+ import { t as createFragment } from "../fragment-instantiation-C4wvwl6V.js";
3
5
 
4
6
  //#region src/test/test.ts
5
7
  /**
6
- * Parse a Response object into a TestResponse discriminated union
7
- */
8
- async function parseResponse(response) {
9
- const status = response.status;
10
- const headers = response.headers;
11
- if ((headers.get("content-type") || "").includes("application/x-ndjson")) return {
12
- type: "jsonStream",
13
- status,
14
- headers,
15
- stream: parseNDJSONStream(response)
16
- };
17
- const text = await response.text();
18
- if (!text || text === "null") return {
19
- type: "empty",
20
- status,
21
- headers
22
- };
23
- const data = JSON.parse(text);
24
- if (data && typeof data === "object" && "message" in data && "code" in data) return {
25
- type: "error",
26
- status,
27
- headers,
28
- error: {
29
- message: data.message,
30
- code: data.code
31
- }
32
- };
33
- return {
34
- type: "json",
35
- status,
36
- headers,
37
- data
38
- };
39
- }
40
- /**
41
- * Parse an NDJSON stream into an async generator
42
- */
43
- async function* parseNDJSONStream(response) {
44
- if (!response.body) return;
45
- const reader = response.body.getReader();
46
- const decoder = new TextDecoder();
47
- let buffer = "";
48
- try {
49
- while (true) {
50
- const { done, value } = await reader.read();
51
- if (done) break;
52
- buffer += decoder.decode(value, { stream: true });
53
- const lines = buffer.split("\n");
54
- buffer = lines.pop() || "";
55
- for (const line of lines) if (line.trim()) yield JSON.parse(line);
56
- }
57
- if (buffer.trim()) yield JSON.parse(buffer);
58
- } finally {
59
- reader.releaseLock();
60
- }
61
- }
62
- /**
63
8
  * Create a fragment instance for testing with optional dependency and service overrides
64
9
  *
65
10
  * @param fragmentBuilder - The fragment builder with definition and required options
11
+ * @param routesOrFactories - Route configurations or route factories
66
12
  * @param options - Configuration and optional overrides for deps/services
67
- * @returns A fragment test instance with a type-safe handler method
13
+ * @returns A fragment test instance with a type-safe callRoute method
68
14
  *
69
15
  * @example
70
16
  * ```typescript
71
- * const fragment = createFragmentForTest(chatnoDefinition, {
72
- * config: { openaiApiKey: "test-key" },
73
- * options: { mountRoute: "/api/chatno" },
74
- * services: {
75
- * generateStreamMessages: mockGenerator
17
+ * const fragment = createFragmentForTest(
18
+ * chatnoDefinition,
19
+ * [routesFactory],
20
+ * {
21
+ * config: { openaiApiKey: "test-key" },
22
+ * options: { mountRoute: "/api/chatno" },
23
+ * services: {
24
+ * generateStreamMessages: mockGenerator
25
+ * }
76
26
  * }
77
- * });
27
+ * );
78
28
  *
79
- * // Initialize routes with fragment's config/deps/services
80
- * const [route] = fragment.initRoutes(routes);
81
- *
82
- * // Or override specific config/deps/services for certain routes
83
- * const [route] = fragment.initRoutes(routes, {
84
- * services: { mockService: mockImplementation }
85
- * });
86
- *
87
- * const response = await fragment.handler(route, {
88
- * pathParams: { id: "123" },
89
- * body: { message: "Hello" }
29
+ * // Call routes directly by method and path
30
+ * const response = await fragment.callRoute("POST", "/login", {
31
+ * body: { username: "test", password: "test123" }
90
32
  * });
91
33
  *
92
34
  * if (response.type === 'json') {
@@ -94,7 +36,7 @@ async function* parseNDJSONStream(response) {
94
36
  * }
95
37
  * ```
96
38
  */
97
- function createFragmentForTest(fragmentBuilder, options) {
39
+ function createFragmentForTest(fragmentBuilder, routesOrFactories, options) {
98
40
  const { config, options: fragmentOptions = {}, deps: depsOverride, services: servicesOverride, additionalContext: additionalContextOverride } = options;
99
41
  const definition = fragmentBuilder.definition;
100
42
  const deps = {
@@ -105,48 +47,18 @@ function createFragmentForTest(fragmentBuilder, options) {
105
47
  ...definition.services ? definition.services(config, fragmentOptions, deps) : {},
106
48
  ...servicesOverride
107
49
  };
50
+ const additionalContext = {
51
+ ...definition.additionalContext,
52
+ ...fragmentOptions,
53
+ ...additionalContextOverride
54
+ };
55
+ const fragment = createFragment(fragmentBuilder, config, routesOrFactories, fragmentOptions);
108
56
  return {
109
57
  config,
110
58
  deps,
111
59
  services,
112
- additionalContext: {
113
- ...definition.additionalContext,
114
- ...fragmentOptions,
115
- ...additionalContextOverride
116
- },
117
- initRoutes: (routesOrFactories, overrides) => {
118
- return resolveRouteFactories({
119
- config: {
120
- ...config,
121
- ...overrides?.config
122
- },
123
- deps: {
124
- ...deps,
125
- ...overrides?.deps
126
- },
127
- services: {
128
- ...services,
129
- ...overrides?.services
130
- }
131
- }, routesOrFactories);
132
- },
133
- handler: async (route, inputOptions) => {
134
- const { pathParams = {}, body, query, headers } = inputOptions || {};
135
- const searchParams = query instanceof URLSearchParams ? query : query ? new URLSearchParams(query) : new URLSearchParams();
136
- const requestHeaders = headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();
137
- const inputContext = new RequestInputContext({
138
- path: route.path,
139
- method: route.method,
140
- pathParams,
141
- searchParams,
142
- headers: requestHeaders,
143
- body,
144
- inputSchema: route.inputSchema,
145
- shouldValidateInput: false
146
- });
147
- const outputContext = new RequestOutputContext(route.outputSchema);
148
- return parseResponse(await route.handler(inputContext, outputContext));
149
- }
60
+ additionalContext,
61
+ callRoute: (method, path, inputOptions) => fragment.callRoute(method, path, inputOptions)
150
62
  };
151
63
  }
152
64
 
@@ -1 +1 @@
1
- {"version":3,"file":"test.js","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { FragmentDefinition } from \"../api/fragment-builder\";\nimport type { FragnoRouteConfig, HTTPMethod } from \"../api/api\";\nimport type { ExtractPathParams } from \"../api/internal/path\";\nimport { RequestInputContext } from \"../api/request-input-context\";\nimport { RequestOutputContext } from \"../api/request-output-context\";\nimport type { AnyRouteOrFactory, FlattenRouteFactories } from \"../api/route\";\nimport { resolveRouteFactories } from \"../api/route\";\nimport type { FragnoPublicConfig } from \"../api/fragment-instantiation\";\n\n/**\n * Discriminated union representing all possible test response types\n */\nexport type TestResponse<T> =\n | {\n type: \"empty\";\n status: number;\n headers: Headers;\n }\n | {\n type: \"error\";\n status: number;\n headers: Headers;\n error: { message: string; code: string };\n }\n | {\n type: \"json\";\n status: number;\n headers: Headers;\n data: T;\n }\n | {\n type: \"jsonStream\";\n status: number;\n headers: Headers;\n stream: AsyncGenerator<T>;\n };\n\n/**\n * Parse a Response object into a TestResponse discriminated union\n */\nasync function parseResponse<T>(response: Response): Promise<TestResponse<T>> {\n const status = response.status;\n const headers = response.headers;\n const contentType = headers.get(\"content-type\") || \"\";\n\n // Check for streaming response\n if (contentType.includes(\"application/x-ndjson\")) {\n return {\n type: \"jsonStream\",\n status,\n headers,\n stream: parseNDJSONStream<T>(response),\n };\n }\n\n // Parse JSON body\n const text = await response.text();\n\n // Empty response\n if (!text || text === \"null\") {\n return {\n type: \"empty\",\n status,\n headers,\n };\n }\n\n const data = JSON.parse(text);\n\n // Error response (has message and code)\n if (data && typeof data === \"object\" && \"message\" in data && \"code\" in data) {\n return {\n type: \"error\",\n status,\n headers,\n error: { message: data.message, code: data.code },\n };\n }\n\n // JSON response\n return {\n type: \"json\",\n status,\n headers,\n data: data as T,\n };\n}\n\n/**\n * Parse an NDJSON stream into an async generator\n */\nasync function* parseNDJSONStream<T>(response: Response): AsyncGenerator<T> {\n if (!response.body) {\n return;\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (line.trim()) {\n yield JSON.parse(line) as T;\n }\n }\n }\n\n // Process any remaining data in the buffer\n if (buffer.trim()) {\n yield JSON.parse(buffer) as T;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n/**\n * Options for creating a test fragment\n */\nexport interface CreateFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n> {\n config: TConfig;\n options?: Partial<TOptions>;\n deps?: Partial<TDeps>;\n services?: Partial<TServices>;\n additionalContext?: Partial<TAdditionalContext>;\n}\n\n/**\n * Options for calling a route handler\n */\nexport interface RouteHandlerInputOptions<\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n> {\n pathParams?: ExtractPathParams<TPath>;\n body?: TInputSchema extends StandardSchemaV1 ? StandardSchemaV1.InferInput<TInputSchema> : never;\n query?: URLSearchParams | Record<string, string>;\n headers?: Headers | Record<string, string>;\n}\n\n/**\n * Options for overriding config/deps/services when initializing routes\n */\nexport interface InitRoutesOverrides<TConfig, TDeps, TServices> {\n config?: Partial<TConfig>;\n deps?: Partial<TDeps>;\n services?: Partial<TServices>;\n}\n\n/**\n * Fragment test instance with type-safe handler method\n */\nexport interface FragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n> {\n config: TConfig;\n deps: TDeps;\n services: TServices;\n additionalContext: TAdditionalContext & TOptions;\n handler: <\n TMethod extends HTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string,\n TQueryParameters extends string,\n >(\n route: FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters\n >,\n inputOptions?: RouteHandlerInputOptions<TPath, TInputSchema>,\n ) => Promise<\n TestResponse<\n TOutputSchema extends StandardSchemaV1 ? StandardSchemaV1.InferOutput<TOutputSchema> : unknown\n >\n >;\n initRoutes: <const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(\n routesOrFactories: TRoutesOrFactories,\n overrides?: InitRoutesOverrides<TConfig, TDeps, TServices>,\n ) => FlattenRouteFactories<TRoutesOrFactories>;\n}\n\n/**\n * Create a fragment instance for testing with optional dependency and service overrides\n *\n * @param fragmentBuilder - The fragment builder with definition and required options\n * @param options - Configuration and optional overrides for deps/services\n * @returns A fragment test instance with a type-safe handler method\n *\n * @example\n * ```typescript\n * const fragment = createFragmentForTest(chatnoDefinition, {\n * config: { openaiApiKey: \"test-key\" },\n * options: { mountRoute: \"/api/chatno\" },\n * services: {\n * generateStreamMessages: mockGenerator\n * }\n * });\n *\n * // Initialize routes with fragment's config/deps/services\n * const [route] = fragment.initRoutes(routes);\n *\n * // Or override specific config/deps/services for certain routes\n * const [route] = fragment.initRoutes(routes, {\n * services: { mockService: mockImplementation }\n * });\n *\n * const response = await fragment.handler(route, {\n * pathParams: { id: \"123\" },\n * body: { message: \"Hello\" }\n * });\n *\n * if (response.type === 'json') {\n * expect(response.data).toEqual({...});\n * }\n * ```\n */\nexport function createFragmentForTest<\n TConfig,\n TDeps,\n TServices extends Record<string, unknown>,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;\n $requiredOptions: TOptions;\n },\n options: CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>,\n): FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions> {\n const {\n config,\n options: fragmentOptions = {} as TOptions,\n deps: depsOverride,\n services: servicesOverride,\n additionalContext: additionalContextOverride,\n } = options;\n\n // Create deps from definition or use empty object\n const definition = fragmentBuilder.definition;\n const baseDeps = definition.dependencies\n ? definition.dependencies(config, fragmentOptions)\n : ({} as TDeps);\n\n // Merge deps with overrides\n const deps = { ...baseDeps, ...depsOverride } as TDeps;\n\n // Create services from definition or use empty object\n const baseServices = definition.services\n ? definition.services(config, fragmentOptions, deps)\n : ({} as TServices);\n\n // Merge services with overrides\n const services = { ...baseServices, ...servicesOverride } as TServices;\n\n // Merge additional context with options\n const additionalContext = {\n ...definition.additionalContext,\n ...fragmentOptions,\n ...additionalContextOverride,\n } as TAdditionalContext & TOptions;\n\n return {\n config,\n deps,\n services,\n additionalContext,\n initRoutes: <const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(\n routesOrFactories: TRoutesOrFactories,\n overrides?: InitRoutesOverrides<TConfig, TDeps, TServices>,\n ): FlattenRouteFactories<TRoutesOrFactories> => {\n // Merge overrides with base config/deps/services\n const routeContext = {\n config: { ...config, ...overrides?.config } as TConfig,\n deps: { ...deps, ...overrides?.deps } as TDeps,\n services: { ...services, ...overrides?.services } as TServices,\n };\n return resolveRouteFactories(routeContext, routesOrFactories);\n },\n handler: async <\n TMethod extends HTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string,\n TQueryParameters extends string,\n >(\n route: FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters\n >,\n inputOptions?: RouteHandlerInputOptions<TPath, TInputSchema>,\n ): Promise<\n TestResponse<\n TOutputSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TOutputSchema>\n : unknown\n >\n > => {\n const {\n pathParams = {} as ExtractPathParams<TPath>,\n body,\n query,\n headers,\n } = inputOptions || {};\n\n // Convert query to URLSearchParams if needed\n const searchParams =\n query instanceof URLSearchParams\n ? query\n : query\n ? new URLSearchParams(query)\n : new URLSearchParams();\n\n // Convert headers to Headers if needed\n const requestHeaders =\n headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();\n\n // Construct RequestInputContext\n const inputContext = new RequestInputContext<TPath, TInputSchema>({\n path: route.path,\n method: route.method,\n pathParams,\n searchParams,\n headers: requestHeaders,\n body,\n inputSchema: route.inputSchema,\n shouldValidateInput: false, // Skip validation in tests\n });\n\n // Construct RequestOutputContext\n const outputContext = new RequestOutputContext(route.outputSchema);\n\n // Call the route handler\n const response = await route.handler(inputContext, outputContext);\n\n // Parse and return the response\n return parseResponse<\n TOutputSchema extends StandardSchemaV1\n ? StandardSchemaV1.InferOutput<TOutputSchema>\n : unknown\n >(response);\n },\n };\n}\n"],"mappings":";;;;;;;AAyCA,eAAe,cAAiB,UAA8C;CAC5E,MAAM,SAAS,SAAS;CACxB,MAAM,UAAU,SAAS;AAIzB,MAHoB,QAAQ,IAAI,eAAe,IAAI,IAGnC,SAAS,uBAAuB,CAC9C,QAAO;EACL,MAAM;EACN;EACA;EACA,QAAQ,kBAAqB,SAAS;EACvC;CAIH,MAAM,OAAO,MAAM,SAAS,MAAM;AAGlC,KAAI,CAAC,QAAQ,SAAS,OACpB,QAAO;EACL,MAAM;EACN;EACA;EACD;CAGH,MAAM,OAAO,KAAK,MAAM,KAAK;AAG7B,KAAI,QAAQ,OAAO,SAAS,YAAY,aAAa,QAAQ,UAAU,KACrE,QAAO;EACL,MAAM;EACN;EACA;EACA,OAAO;GAAE,SAAS,KAAK;GAAS,MAAM,KAAK;GAAM;EAClD;AAIH,QAAO;EACL,MAAM;EACN;EACA;EACM;EACP;;;;;AAMH,gBAAgB,kBAAqB,UAAuC;AAC1E,KAAI,CAAC,SAAS,KACZ;CAGF,MAAM,SAAS,SAAS,KAAK,WAAW;CACxC,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,SAAS;AAEb,KAAI;AACF,SAAO,MAAM;GACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAE3C,OAAI,KACF;AAGF,aAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;GACjD,MAAM,QAAQ,OAAO,MAAM,KAAK;AAGhC,YAAS,MAAM,KAAK,IAAI;AAExB,QAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,MAAM,CACb,OAAM,KAAK,MAAM,KAAK;;AAM5B,MAAI,OAAO,MAAM,CACf,OAAM,KAAK,MAAM,OAAO;WAElB;AACR,SAAO,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwHxB,SAAgB,sBAOd,iBAIA,SAC0E;CAC1E,MAAM,EACJ,QACA,SAAS,kBAAkB,EAAE,EAC7B,MAAM,cACN,UAAU,kBACV,mBAAmB,8BACjB;CAGJ,MAAM,aAAa,gBAAgB;CAMnC,MAAM,OAAO;EAAE,GALE,WAAW,eACxB,WAAW,aAAa,QAAQ,gBAAgB,GAC/C,EAAE;EAGqB,GAAG;EAAc;CAQ7C,MAAM,WAAW;EAAE,GALE,WAAW,WAC5B,WAAW,SAAS,QAAQ,iBAAiB,KAAK,GACjD,EAAE;EAG6B,GAAG;EAAkB;AASzD,QAAO;EACL;EACA;EACA;EACA,mBAVwB;GACxB,GAAG,WAAW;GACd,GAAG;GACH,GAAG;GACJ;EAOC,aACE,mBACA,cAC8C;AAO9C,UAAO,sBALc;IACnB,QAAQ;KAAE,GAAG;KAAQ,GAAG,WAAW;KAAQ;IAC3C,MAAM;KAAE,GAAG;KAAM,GAAG,WAAW;KAAM;IACrC,UAAU;KAAE,GAAG;KAAU,GAAG,WAAW;KAAU;IAClD,EAC0C,kBAAkB;;EAE/D,SAAS,OAQP,OAQA,iBAOG;GACH,MAAM,EACJ,aAAa,EAAE,EACf,MACA,OACA,YACE,gBAAgB,EAAE;GAGtB,MAAM,eACJ,iBAAiB,kBACb,QACA,QACE,IAAI,gBAAgB,MAAM,GAC1B,IAAI,iBAAiB;GAG7B,MAAM,iBACJ,mBAAmB,UAAU,UAAU,UAAU,IAAI,QAAQ,QAAQ,GAAG,IAAI,SAAS;GAGvF,MAAM,eAAe,IAAI,oBAAyC;IAChE,MAAM,MAAM;IACZ,QAAQ,MAAM;IACd;IACA;IACA,SAAS;IACT;IACA,aAAa,MAAM;IACnB,qBAAqB;IACtB,CAAC;GAGF,MAAM,gBAAgB,IAAI,qBAAqB,MAAM,aAAa;AAMlE,UAAO,cAHU,MAAM,MAAM,QAAQ,cAAc,cAAc,CAOtD;;EAEd"}
1
+ {"version":3,"file":"test.js","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":["import type { FragmentDefinition } from \"../api/fragment-builder\";\nimport type { FragnoRouteConfig, HTTPMethod } from \"../api/api\";\nimport type { AnyRouteOrFactory, FlattenRouteFactories } from \"../api/route\";\nimport type { FragnoPublicConfig } from \"../api/fragment-instantiation\";\nimport { createFragment } from \"../api/fragment-instantiation\";\nimport type { RouteHandlerInputOptions } from \"../api/route-handler-input-options\";\nimport type { ExtractRouteByPath, ExtractRoutePath } from \"../client/client\";\nimport type { InferOrUnknown } from \"../util/types-util\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { FragnoResponse } from \"../api/fragno-response\";\n\n// Re-export for convenience\nexport type { RouteHandlerInputOptions };\n\nexport type { FragnoResponse };\n\n/**\n * Options for creating a test fragment\n */\nexport interface CreateFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n> {\n config: TConfig;\n options?: Partial<TOptions>;\n deps?: Partial<TDeps>;\n services?: Partial<TServices>;\n additionalContext?: Partial<TAdditionalContext>;\n}\n\n/**\n * Fragment test instance with type-safe callRoute method\n */\nexport interface FragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >[],\n> {\n config: TConfig;\n deps: TDeps;\n services: TServices;\n additionalContext: TAdditionalContext & TOptions;\n callRoute: <\n const TMethod extends HTTPMethod,\n const TPath extends ExtractRoutePath<TRoutes, TMethod>,\n >(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ) => Promise<\n FragnoResponse<\n InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>[\"outputSchema\"]>>\n >\n >;\n}\n\n/**\n * Create a fragment instance for testing with optional dependency and service overrides\n *\n * @param fragmentBuilder - The fragment builder with definition and required options\n * @param routesOrFactories - Route configurations or route factories\n * @param options - Configuration and optional overrides for deps/services\n * @returns A fragment test instance with a type-safe callRoute method\n *\n * @example\n * ```typescript\n * const fragment = createFragmentForTest(\n * chatnoDefinition,\n * [routesFactory],\n * {\n * config: { openaiApiKey: \"test-key\" },\n * options: { mountRoute: \"/api/chatno\" },\n * services: {\n * generateStreamMessages: mockGenerator\n * }\n * }\n * );\n *\n * // Call routes directly by method and path\n * const response = await fragment.callRoute(\"POST\", \"/login\", {\n * body: { username: \"test\", password: \"test123\" }\n * });\n *\n * if (response.type === 'json') {\n * expect(response.data).toEqual({...});\n * }\n * ```\n */\nexport function createFragmentForTest<\n TConfig,\n TDeps,\n TServices extends Record<string, unknown>,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;\n $requiredOptions: TOptions;\n },\n routesOrFactories: TRoutesOrFactories,\n options: CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>,\n): FragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions,\n FlattenRouteFactories<TRoutesOrFactories>\n> {\n const {\n config,\n options: fragmentOptions = {} as TOptions,\n deps: depsOverride,\n services: servicesOverride,\n additionalContext: additionalContextOverride,\n } = options;\n\n // Create deps from definition or use empty object\n const definition = fragmentBuilder.definition;\n const baseDeps = definition.dependencies\n ? definition.dependencies(config, fragmentOptions)\n : ({} as TDeps);\n\n // Merge deps with overrides\n const deps = { ...baseDeps, ...depsOverride } as TDeps;\n\n // Create services from definition or use empty object\n const baseServices = definition.services\n ? definition.services(config, fragmentOptions, deps)\n : ({} as TServices);\n\n // Merge services with overrides\n const services = { ...baseServices, ...servicesOverride } as TServices;\n\n // Merge additional context with options\n const additionalContext = {\n ...definition.additionalContext,\n ...fragmentOptions,\n ...additionalContextOverride,\n } as TAdditionalContext & TOptions;\n\n // Create the actual fragment using createFragment\n const fragment = createFragment(fragmentBuilder, config, routesOrFactories, fragmentOptions);\n\n return {\n config,\n deps,\n services,\n additionalContext,\n callRoute: (method, path, inputOptions) => fragment.callRoute(method, path, inputOptions),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwGA,SAAgB,sBAQd,iBAIA,mBACA,SAQA;CACA,MAAM,EACJ,QACA,SAAS,kBAAkB,EAAE,EAC7B,MAAM,cACN,UAAU,kBACV,mBAAmB,8BACjB;CAGJ,MAAM,aAAa,gBAAgB;CAMnC,MAAM,OAAO;EAAE,GALE,WAAW,eACxB,WAAW,aAAa,QAAQ,gBAAgB,GAC/C,EAAE;EAGqB,GAAG;EAAc;CAQ7C,MAAM,WAAW;EAAE,GALE,WAAW,WAC5B,WAAW,SAAS,QAAQ,iBAAiB,KAAK,GACjD,EAAE;EAG6B,GAAG;EAAkB;CAGzD,MAAM,oBAAoB;EACxB,GAAG,WAAW;EACd,GAAG;EACH,GAAG;EACJ;CAGD,MAAM,WAAW,eAAe,iBAAiB,QAAQ,mBAAmB,gBAAgB;AAE5F,QAAO;EACL;EACA;EACA;EACA;EACA,YAAY,QAAQ,MAAM,iBAAiB,SAAS,UAAU,QAAQ,MAAM,aAAa;EAC1F"}