@fragno-dev/core 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +45 -53
- package/CHANGELOG.md +6 -0
- package/dist/api/api.d.ts +2 -2
- package/dist/api/api.js +3 -2
- package/dist/api/fragment-builder.d.ts +2 -4
- package/dist/api/fragment-builder.js +1 -1
- package/dist/api/fragment-instantiation.d.ts +2 -4
- package/dist/api/fragment-instantiation.js +3 -5
- package/dist/api/route.d.ts +2 -3
- package/dist/api/route.js +1 -1
- package/dist/api-BFrUCIsF.d.ts +963 -0
- package/dist/api-BFrUCIsF.d.ts.map +1 -0
- package/dist/client/client.d.ts +1 -3
- package/dist/client/client.js +4 -5
- package/dist/client/client.svelte.d.ts +2 -3
- package/dist/client/client.svelte.d.ts.map +1 -1
- package/dist/client/client.svelte.js +4 -5
- package/dist/client/client.svelte.js.map +1 -1
- package/dist/client/react.d.ts +2 -3
- package/dist/client/react.d.ts.map +1 -1
- package/dist/client/react.js +4 -5
- package/dist/client/react.js.map +1 -1
- package/dist/client/solid.d.ts +2 -3
- package/dist/client/solid.d.ts.map +1 -1
- package/dist/client/solid.js +4 -5
- package/dist/client/solid.js.map +1 -1
- package/dist/client/vanilla.d.ts +2 -3
- package/dist/client/vanilla.d.ts.map +1 -1
- package/dist/client/vanilla.js +4 -5
- package/dist/client/vanilla.js.map +1 -1
- package/dist/client/vue.d.ts +2 -3
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +4 -5
- package/dist/client/vue.js.map +1 -1
- package/dist/{client-C5LsYHEI.js → client-DAFHcKqA.js} +4 -4
- package/dist/{client-C5LsYHEI.js.map → client-DAFHcKqA.js.map} +1 -1
- package/dist/fragment-builder-Boh2vNHq.js +108 -0
- package/dist/fragment-builder-Boh2vNHq.js.map +1 -0
- package/dist/fragment-instantiation-DUT-HLl1.js +898 -0
- package/dist/fragment-instantiation-DUT-HLl1.js.map +1 -0
- package/dist/integrations/react-ssr.js +1 -1
- package/dist/mod.d.ts +2 -4
- package/dist/mod.js +4 -6
- package/dist/{route-C5Uryylh.js → route-C4CyNHkC.js} +8 -3
- package/dist/route-C4CyNHkC.js.map +1 -0
- package/dist/{ssr-BByDVfFD.js → ssr-kyKI7pqH.js} +1 -1
- package/dist/{ssr-BByDVfFD.js.map → ssr-kyKI7pqH.js.map} +1 -1
- package/dist/test/test.d.ts +6 -7
- package/dist/test/test.d.ts.map +1 -1
- package/dist/test/test.js +9 -7
- package/dist/test/test.js.map +1 -1
- package/package.json +1 -1
- package/src/api/api.ts +45 -6
- package/src/api/fragment-builder.ts +463 -25
- package/src/api/fragment-instantiation.test.ts +249 -7
- package/src/api/fragment-instantiation.ts +283 -16
- package/src/api/fragment-services.test.ts +462 -0
- package/src/api/fragment.test.ts +65 -17
- package/src/api/request-middleware.test.ts +6 -3
- package/src/api/route.test.ts +111 -1
- package/src/api/route.ts +323 -14
- package/src/mod.ts +11 -1
- package/src/test/test.test.ts +20 -15
- package/src/test/test.ts +48 -9
- package/dist/api-BWN97TOr.d.ts +0 -377
- package/dist/api-BWN97TOr.d.ts.map +0 -1
- package/dist/api-DngJDcmO.js +0 -54
- package/dist/api-DngJDcmO.js.map +0 -1
- package/dist/fragment-builder-DOnCVBqc.js +0 -47
- package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
- package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
- package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
- package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
- package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
- package/dist/request-output-context-CdIjwmEN.js +0 -320
- package/dist/request-output-context-CdIjwmEN.js.map +0 -1
- package/dist/route-Bl9Zr1Yv.d.ts +0 -26
- package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
- package/dist/route-C5Uryylh.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fragment-instantiation-DUT-HLl1.js","names":["#status","#code","#issues","addRoute","#path","#method","#pathParams","#searchParams","#headers","#body","#parsedBody","#inputSchema","#shouldValidateInput","#validateInput","#aborted","#closed","#responseReadable","#writer","#encoder","#abortSubscribers","mergedHeaders","#outputSchema","#pathParams","#searchParams","#headers","#initialBody","#bodyOverride","#deps","#services","#options","#route","#state","route","providedServicesResolved: TProvidedInterfaces | undefined","middlewareHandler:\n | FragnoMiddlewareCallback<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps & TRequiredInterfaces,\n TServices & TProvidedInterfaces & TRequiredInterfaces\n >\n | undefined","fragment: FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps & TRequiredInterfaces,\n TServices & TProvidedInterfaces & TRequiredInterfaces,\n TAdditionalContext & TOptions\n >","response: Response","thisContext: RequestThisContext","requestBody: RequestBodyType","rawBody: string | undefined","#fragmentBuilder","#config","#routes","#options","#services"],"sources":["../src/api/error.ts","../src/api/api.ts","../src/api/internal/route.ts","../src/api/request-input-context.ts","../src/api/internal/response-stream.ts","../src/api/request-output-context.ts","../src/api/mutable-request-state.ts","../src/api/request-middleware.ts","../src/api/fragno-response.ts","../src/api/fragment-instantiation.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { StatusCode } from \"../http/http-status\";\n\nexport class FragnoApiError extends Error {\n readonly #status: StatusCode;\n readonly #code: string;\n\n constructor({ message, code }: { message: string; code: string }, status: StatusCode) {\n super(message);\n this.name = \"FragnoApiError\";\n this.#status = status;\n this.#code = code;\n }\n\n get status() {\n return this.#status;\n }\n\n get code() {\n return this.#code;\n }\n\n toResponse() {\n return Response.json({ message: this.message, code: this.code }, { status: this.status });\n }\n}\n\nexport class FragnoApiValidationError extends FragnoApiError {\n #issues: readonly StandardSchemaV1.Issue[];\n\n constructor(message: string, issues: readonly StandardSchemaV1.Issue[]) {\n super({ message, code: \"FRAGNO_VALIDATION_ERROR\" }, 400);\n this.name = \"FragnoApiValidationError\";\n this.#issues = issues;\n }\n\n get issues() {\n return this.#issues;\n }\n\n override toResponse() {\n return Response.json(\n { message: this.message, issues: this.#issues, code: this.code },\n { status: this.status },\n );\n }\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { RequestInputContext } from \"./request-input-context\";\nimport type { RequestOutputContext } from \"./request-output-context\";\n\nexport type HTTPMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\" | \"HEAD\" | \"OPTIONS\";\nexport type NonGetHTTPMethod = Exclude<HTTPMethod, \"GET\">;\n\n// Helper type to create branded error messages that are still assignable to string\ntype PathError<T extends string, TMessage extends string> = T & [`Error: ${TMessage}`];\n\n/**\n * A valid path string that:\n * - Is non-empty\n * - Starts with \"/\"\n * - Is not just \"/\"\n * - Does not end with \"/\"\n */\nexport type ValidPath<T extends string = string> = T extends `/${infer Rest}`\n ? Rest extends \"\"\n ? PathError<T, \"Path cannot be just '/'.\"> // Excludes \"/\"\n : T extends `${string}/`\n ? PathError<T, \"Path cannot end with '/'.\"> // Excludes paths ending with \"/\"\n : T\n : PathError<T, \"Path must start with '/'.\">; // Excludes paths not starting with \"/\"\n\n/**\n * Base ServiceContext interface. Can be augmented by packages like @fragno-dev/db.\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface RequestThisContext {}\n\nexport interface FragnoRouteConfig<\n TMethod extends HTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string = string,\n TQueryParameters extends string = string,\n TThisContext extends RequestThisContext = RequestThisContext,\n> {\n method: TMethod;\n path: TPath;\n inputSchema?: TInputSchema;\n outputSchema?: TOutputSchema;\n errorCodes?: readonly TErrorCode[];\n queryParameters?: readonly TQueryParameters[];\n handler(\n this: TThisContext,\n inputCtx: RequestInputContext<TPath, TInputSchema>,\n outputCtx: RequestOutputContext<TOutputSchema, TErrorCode>,\n ): Promise<Response>;\n}\n\n// Overload for routes without inputSchema\nexport function addRoute<\n TMethod extends HTTPMethod,\n TPath extends string,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string = string,\n TQueryParameters extends string = string,\n TThisContext extends RequestThisContext = RequestThisContext,\n>(\n route: FragnoRouteConfig<\n TMethod,\n ValidPath<TPath>,\n undefined,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n > & { inputSchema?: undefined },\n): FragnoRouteConfig<\n TMethod,\n TPath,\n undefined,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n>;\n\n// Overload for routes with inputSchema\nexport function addRoute<\n TMethod extends HTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string = string,\n TQueryParameters extends string = string,\n TThisContext extends RequestThisContext = RequestThisContext,\n>(\n route: FragnoRouteConfig<\n TMethod,\n ValidPath<TPath>,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n > & {\n inputSchema: TInputSchema;\n },\n): FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n>;\n\n// Implementation\nexport function addRoute<\n TMethod extends HTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string = string,\n TQueryParameters extends string = string,\n TThisContext extends RequestThisContext = RequestThisContext,\n>(\n route: FragnoRouteConfig<\n TMethod,\n ValidPath<TPath>,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n >,\n): FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n> {\n return route;\n}\n\nexport { FragnoApiError, FragnoApiValidationError } from \"./error\";\nexport type { RouteHandlerInputOptions } from \"./route-handler-input-options\";\nexport { instantiateFragment, FragmentInstantiationBuilder } from \"./fragment-instantiation\";\n","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","import type { RequestBodyType } from \"./request-input-context\";\n\n/**\n * Holds mutable request state that can be modified by middleware and consumed by handlers.\n *\n * This class provides a structural way for middleware to modify request data:\n * - Path parameters can be modified\n * - Query/search parameters can be modified\n * - Request body can be overridden\n * - Request headers can be modified\n *\n * @example\n * ```typescript\n * // In middleware\n * const state = new MutableRequestState({\n * pathParams: { id: \"123\" },\n * searchParams: new URLSearchParams(\"?role=user\"),\n * body: { name: \"John\" },\n * headers: new Headers()\n * });\n *\n * // Modify query parameters\n * state.searchParams.set(\"role\", \"admin\");\n *\n * // Override body\n * state.setBody({ name: \"Jane\" });\n *\n * // Modify headers\n * state.headers.set(\"X-Custom\", \"value\");\n * ```\n */\nexport class MutableRequestState {\n readonly #pathParams: Record<string, string>;\n readonly #searchParams: URLSearchParams;\n readonly #headers: Headers;\n // oxlint-disable-next-line no-unused-private-class-members False Positive?\n readonly #initialBody: RequestBodyType;\n #bodyOverride: RequestBodyType | undefined;\n\n constructor(config: {\n pathParams: Record<string, string>;\n searchParams: URLSearchParams;\n body: RequestBodyType;\n headers: Headers;\n }) {\n this.#pathParams = config.pathParams;\n this.#searchParams = config.searchParams;\n this.#headers = config.headers;\n this.#initialBody = config.body;\n this.#bodyOverride = undefined;\n }\n\n /**\n * Path parameters extracted from the route.\n * Can be modified directly (e.g., `state.pathParams.id = \"456\"`).\n */\n get pathParams(): Record<string, string> {\n return this.#pathParams;\n }\n\n /**\n * URLSearchParams for query parameters.\n * Can be modified using URLSearchParams API (e.g., `state.searchParams.set(\"key\", \"value\")`).\n */\n get searchParams(): URLSearchParams {\n return this.#searchParams;\n }\n\n /**\n * Request headers.\n * Can be modified using Headers API (e.g., `state.headers.set(\"X-Custom\", \"value\")`).\n */\n get headers(): Headers {\n return this.#headers;\n }\n\n /**\n * Get the current body value.\n * Returns the override if set, otherwise the initial body.\n */\n get body(): RequestBodyType {\n return this.#bodyOverride !== undefined ? this.#bodyOverride : this.#initialBody;\n }\n\n /**\n * Override the request body.\n * This allows middleware to replace the body that will be seen by the handler.\n *\n * @param body - The new body value\n *\n * @example\n * ```typescript\n * // In middleware\n * state.setBody({ modifiedField: \"new value\" });\n * ```\n */\n setBody(body: RequestBodyType): void {\n this.#bodyOverride = body;\n }\n\n /**\n * Check if the body has been overridden by middleware.\n */\n get hasBodyOverride(): boolean {\n return this.#bodyOverride !== undefined;\n }\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { ExtractRouteByPath, ExtractRoutePath } from \"../client/client\";\nimport type { HTTPMethod } from \"./api\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport type { AnyFragnoRouteConfig } from \"./route\";\nimport { RequestInputContext } from \"./request-input-context\";\nimport { OutputContext, RequestOutputContext } from \"./request-output-context\";\nimport { MutableRequestState } from \"./mutable-request-state\";\n\nexport type FragnoMiddlewareCallback<\n TRoutes extends readonly AnyFragnoRouteConfig[],\n TDeps,\n TServices extends Record<string, unknown>,\n> = (\n inputContext: RequestMiddlewareInputContext<TRoutes>,\n outputContext: RequestMiddlewareOutputContext<TDeps, TServices>,\n) => Promise<Response | undefined> | Response | undefined;\n\nexport interface RequestMiddlewareOptions {\n path: string;\n method: HTTPMethod;\n request: Request;\n state: MutableRequestState;\n}\n\nexport class RequestMiddlewareOutputContext<\n const TDeps,\n const TServices extends Record<string, unknown>,\n> extends OutputContext<unknown, string> {\n readonly #deps: TDeps;\n readonly #services: TServices;\n\n constructor(deps: TDeps, services: TServices) {\n super();\n this.#deps = deps;\n this.#services = services;\n }\n\n get deps(): TDeps {\n return this.#deps;\n }\n\n get services(): TServices {\n return this.#services;\n }\n}\n\nexport class RequestMiddlewareInputContext<const TRoutes extends readonly AnyFragnoRouteConfig[]> {\n readonly #options: RequestMiddlewareOptions;\n readonly #route: TRoutes[number];\n readonly #state: MutableRequestState;\n\n constructor(routes: TRoutes, options: RequestMiddlewareOptions) {\n this.#options = options;\n this.#state = options.state;\n\n const route = routes.find(\n (route) => route.path === options.path && route.method === options.method,\n );\n\n if (!route) {\n throw new Error(`Route not found: ${options.path} ${options.method}`);\n }\n\n this.#route = route;\n }\n\n get path(): string {\n return this.#options.path;\n }\n\n get method(): HTTPMethod {\n return this.#options.method;\n }\n\n get pathParams(): Record<string, string> {\n return this.#state.pathParams;\n }\n\n get queryParams(): URLSearchParams {\n return this.#state.searchParams;\n }\n\n get headers(): Headers {\n return this.#state.headers;\n }\n\n get inputSchema(): StandardSchemaV1 | undefined {\n return this.#route.inputSchema;\n }\n\n get outputSchema(): StandardSchemaV1 | undefined {\n return this.#route.outputSchema;\n }\n\n /**\n * Access to the mutable request state.\n * Use this to modify query parameters, path parameters, or request body.\n *\n * @example\n * ```typescript\n * // Modify body\n * requestState.setBody({ modified: true });\n *\n * // Query params are already accessible via queryParams getter\n * // Path params are already accessible via pathParams getter\n * ```\n */\n get requestState(): MutableRequestState {\n return this.#state;\n }\n\n // Defined as a field so that `this` reference stays in tact when destructuring\n ifMatchesRoute = async <\n const TMethod extends HTTPMethod,\n const TPath extends ExtractRoutePath<TRoutes>,\n const TRoute extends ExtractRouteByPath<TRoutes, TPath, TMethod> = ExtractRouteByPath<\n TRoutes,\n TPath,\n TMethod\n >,\n >(\n method: TMethod,\n path: TPath,\n handler: (\n ...args: Parameters<TRoute[\"handler\"]>\n ) => Promise<Response | undefined | void> | Response | undefined | void,\n ): Promise<Response | undefined> => {\n if (this.path !== path || this.method !== method) {\n return undefined;\n }\n\n // TODO(Wilco): We should support reading/modifying headers here.\n const inputContext = await RequestInputContext.fromRequest({\n request: this.#options.request,\n method: this.#options.method,\n path: path,\n pathParams: this.pathParams as ExtractPathParams<TPath>,\n inputSchema: this.#route.inputSchema,\n state: this.#state,\n });\n\n const outputContext = new RequestOutputContext(this.#route.outputSchema);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return await (handler as any)(inputContext, outputContext);\n };\n}\n","/**\n * Discriminated union representing all possible Fragno response types\n */\nexport type FragnoResponse<T> =\n | {\n type: \"empty\";\n status: number;\n headers: Headers;\n }\n | {\n type: \"error\";\n status: number;\n headers: Headers;\n error: { message: string; code: string };\n }\n | {\n type: \"json\";\n status: number;\n headers: Headers;\n data: T;\n }\n | {\n type: \"jsonStream\";\n status: number;\n headers: Headers;\n stream: AsyncGenerator<T extends unknown[] ? T[number] : T>;\n };\n\n/**\n * Parse a Response object into a FragnoResponse discriminated union\n */\nexport async function parseFragnoResponse<T>(response: Response): Promise<FragnoResponse<T>> {\n const status = response.status;\n const headers = response.headers;\n const contentType = headers.get(\"content-type\") || \"\";\n\n // Check for streaming response\n if (contentType.includes(\"application/x-ndjson\")) {\n return {\n type: \"jsonStream\",\n status,\n headers,\n stream: parseNDJSONStream<T>(response),\n };\n }\n\n // Parse JSON body\n const text = await response.text();\n\n // Empty response\n if (!text || text === \"null\") {\n return {\n type: \"empty\",\n status,\n headers,\n };\n }\n\n const data = JSON.parse(text);\n\n // Error response (has message and code, or error and code)\n if (data && typeof data === \"object\" && \"code\" in data) {\n if (\"message\" in data) {\n return {\n type: \"error\",\n status,\n headers,\n error: { message: data.message, code: data.code },\n };\n }\n if (\"error\" in data) {\n return {\n type: \"error\",\n status,\n headers,\n error: { message: data.error, code: data.code },\n };\n }\n }\n\n // JSON response\n return {\n type: \"json\",\n status,\n headers,\n data: data as T,\n };\n}\n\n/**\n * Parse an NDJSON stream into an async generator\n */\nasync function* parseNDJSONStream<T>(\n response: Response,\n): AsyncGenerator<T extends unknown[] ? T[number] : T> {\n if (!response.body) {\n return;\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n\n // Keep the last incomplete line in the buffer\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (line.trim()) {\n yield JSON.parse(line) as T extends unknown[] ? T[number] : T;\n }\n }\n }\n\n // Process any remaining data in the buffer\n if (buffer.trim()) {\n yield JSON.parse(buffer) as T extends unknown[] ? T[number] : T;\n }\n } finally {\n reader.releaseLock();\n }\n}\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { type FragnoRouteConfig, type HTTPMethod, type RequestThisContext } from \"./api\";\nimport { FragnoApiError } from \"./error\";\nimport { getMountRoute } from \"./internal/route\";\nimport { addRoute, createRouter, findRoute } from \"rou3\";\nimport { RequestInputContext, type RequestBodyType } from \"./request-input-context\";\nimport type { ExtractPathParams } from \"./internal/path\";\nimport { RequestOutputContext } from \"./request-output-context\";\nimport {\n type AnyFragnoRouteConfig,\n type AnyRouteOrFactory,\n type FlattenRouteFactories,\n resolveRouteFactories,\n} from \"./route\";\nimport {\n RequestMiddlewareInputContext,\n RequestMiddlewareOutputContext,\n type FragnoMiddlewareCallback,\n} from \"./request-middleware\";\nimport type { FragmentDefinition, RouteHandler } from \"./fragment-builder\";\nimport { MutableRequestState } from \"./mutable-request-state\";\nimport type { RouteHandlerInputOptions } from \"./route-handler-input-options\";\nimport type { ExtractRouteByPath, ExtractRoutePath } from \"../client/client\";\nimport { type FragnoResponse, parseFragnoResponse } from \"./fragno-response\";\nimport type { InferOrUnknown } from \"../util/types-util\";\n\nexport interface FragnoPublicConfig {\n mountRoute?: string;\n}\n\nexport type FetcherConfig =\n | { type: \"options\"; options: RequestInit }\n | { type: \"function\"; fetcher: typeof fetch };\n\nexport interface FragnoPublicClientConfig {\n mountRoute?: string;\n baseUrl?: string;\n fetcherConfig?: FetcherConfig;\n}\n\ntype AstroHandlers = {\n ALL: (req: Request) => Promise<Response>;\n};\n\ntype ReactRouterHandlers = {\n loader: (args: { request: Request }) => Promise<Response>;\n action: (args: { request: Request }) => Promise<Response>;\n};\n\ntype SolidStartHandlers = {\n GET: (args: { request: Request }) => Promise<Response>;\n POST: (args: { request: Request }) => Promise<Response>;\n PUT: (args: { request: Request }) => Promise<Response>;\n DELETE: (args: { request: Request }) => Promise<Response>;\n PATCH: (args: { request: Request }) => Promise<Response>;\n HEAD: (args: { request: Request }) => Promise<Response>;\n OPTIONS: (args: { request: Request }) => Promise<Response>;\n};\n\ntype TanStackStartHandlers = SolidStartHandlers;\n\ntype StandardHandlers = {\n GET: (req: Request) => Promise<Response>;\n POST: (req: Request) => Promise<Response>;\n PUT: (req: Request) => Promise<Response>;\n DELETE: (req: Request) => Promise<Response>;\n PATCH: (req: Request) => Promise<Response>;\n HEAD: (req: Request) => Promise<Response>;\n OPTIONS: (req: Request) => Promise<Response>;\n};\n\ntype HandlersByFramework = {\n astro: AstroHandlers;\n \"react-router\": ReactRouterHandlers;\n \"next-js\": StandardHandlers;\n \"svelte-kit\": StandardHandlers;\n \"solid-start\": SolidStartHandlers;\n \"tanstack-start\": TanStackStartHandlers;\n};\n\n// Not actually a symbol, since we might be dealing with multiple instances of this code.\nexport const instantiatedFragmentFakeSymbol = \"$fragno-instantiated-fragment\" as const;\n\ntype FullstackFrameworks = keyof HandlersByFramework;\n\nexport interface FragnoInstantiatedFragment<\n TRoutes extends readonly AnyFragnoRouteConfig[] = [],\n TDeps = {},\n TServices extends Record<string, unknown> = Record<string, unknown>,\n TAdditionalContext extends Record<string, unknown> = {},\n> {\n [instantiatedFragmentFakeSymbol]: typeof instantiatedFragmentFakeSymbol;\n\n config: FragnoFragmentSharedConfig<TRoutes>;\n deps: TDeps;\n services: TServices;\n additionalContext?: TAdditionalContext;\n handlersFor: <T extends FullstackFrameworks>(framework: T) => HandlersByFramework[T];\n handler: (req: Request) => Promise<Response>;\n mountRoute: string;\n callRoute: <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ) => Promise<\n FragnoResponse<\n InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>[\"outputSchema\"]>>\n >\n >;\n callRouteRaw: <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ) => Promise<Response>;\n withMiddleware: (\n handler: FragnoMiddlewareCallback<TRoutes, TDeps, TServices>,\n ) => FragnoInstantiatedFragment<TRoutes, TDeps, TServices, TAdditionalContext>;\n}\n\nexport interface FragnoFragmentSharedConfig<\n TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >[],\n> {\n name: string;\n routes: TRoutes;\n}\n\nexport type AnyFragnoFragmentSharedConfig = FragnoFragmentSharedConfig<\n readonly AnyFragnoRouteConfig[]\n>;\n\nexport function createFragment<\n const TConfig,\n const TDeps,\n const TServices extends Record<string, unknown>,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n const TAdditionalContext extends Record<string, unknown>,\n const TRequiredInterfaces extends Record<string, unknown>,\n const TProvidedInterfaces extends Record<string, unknown>,\n const TOptions extends FragnoPublicConfig,\n const TThisContext extends RequestThisContext = RequestThisContext,\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TRequiredInterfaces,\n TProvidedInterfaces,\n TThisContext\n >;\n $requiredOptions: TOptions;\n },\n config: TConfig,\n routesOrFactories: TRoutesOrFactories,\n options: TOptions,\n interfaceImplementations?: TRequiredInterfaces,\n): FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps & TRequiredInterfaces,\n TServices & TProvidedInterfaces & TRequiredInterfaces,\n TAdditionalContext\n> {\n type TRoutes = FlattenRouteFactories<TRoutesOrFactories>;\n\n const definition = fragmentBuilder.definition;\n\n // Validate required services are satisfied\n if (definition.usedServices) {\n for (const [serviceName, serviceMeta] of Object.entries(definition.usedServices)) {\n const implementation = interfaceImplementations?.[serviceName];\n if (serviceMeta.required && !implementation) {\n throw new Error(\n `Fragment '${definition.name}' requires service '${serviceMeta.name}' but it was not provided`,\n );\n }\n }\n }\n\n const dependencies = definition.dependencies?.(config, options) ?? ({} as TDeps);\n\n // Merge interface implementations into dependencies\n const depsWithInterfaces = {\n ...dependencies,\n ...interfaceImplementations,\n } as TDeps & TRequiredInterfaces;\n\n const servicesFromWithServices =\n definition.services?.(config, options, depsWithInterfaces) ?? ({} as TServices);\n\n // Handle providedServices - can be:\n // 1. A function that returns all services\n // 2. An object where each value is a factory function\n // 3. undefined\n let providedServicesResolved: TProvidedInterfaces | undefined;\n\n if (typeof definition.providedServices === \"function\") {\n // Case 1: It's a function, call it to get the services\n providedServicesResolved = definition.providedServices(config, options, depsWithInterfaces);\n } else if (definition.providedServices && typeof definition.providedServices === \"object\") {\n // Case 2: It's an object where each value might be a factory function\n providedServicesResolved = {} as TProvidedInterfaces;\n for (const [serviceName, serviceOrFactory] of Object.entries(definition.providedServices)) {\n if (typeof serviceOrFactory === \"function\") {\n // Call the factory function\n (providedServicesResolved as Record<string, unknown>)[serviceName] = serviceOrFactory(\n config,\n options,\n depsWithInterfaces,\n );\n } else {\n // It's already a resolved service\n (providedServicesResolved as Record<string, unknown>)[serviceName] = serviceOrFactory;\n }\n }\n }\n\n const services = {\n ...servicesFromWithServices,\n ...providedServicesResolved,\n ...interfaceImplementations,\n } as TServices & TProvidedInterfaces & TRequiredInterfaces;\n\n const context = { config, deps: depsWithInterfaces, services };\n const routes = resolveRouteFactories(context, routesOrFactories);\n\n const mountRoute = getMountRoute({\n name: definition.name,\n mountRoute: options.mountRoute,\n });\n\n const router =\n createRouter<\n FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string,\n RequestThisContext\n >\n >();\n\n let middlewareHandler:\n | FragnoMiddlewareCallback<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps & TRequiredInterfaces,\n TServices & TProvidedInterfaces & TRequiredInterfaces\n >\n | undefined;\n\n // Store the handler wrapper if provided (e.g., for database support)\n const handlerWrapper = definition.createHandlerWrapper?.(options);\n\n for (const routeConfig of routes) {\n addRoute(router, routeConfig.method.toUpperCase(), routeConfig.path, routeConfig);\n }\n\n const fragment: FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps & TRequiredInterfaces,\n TServices & TProvidedInterfaces & TRequiredInterfaces,\n TAdditionalContext & TOptions\n > = {\n [instantiatedFragmentFakeSymbol]: instantiatedFragmentFakeSymbol,\n mountRoute,\n config: {\n name: definition.name,\n routes,\n },\n services,\n deps: depsWithInterfaces,\n additionalContext: {\n ...definition.additionalContext,\n ...options,\n } as TAdditionalContext & TOptions,\n withMiddleware: (handler) => {\n if (middlewareHandler) {\n throw new Error(\"Middleware already set\");\n }\n\n middlewareHandler = handler;\n\n return fragment;\n },\n callRoute: async <TMethod extends HTTPMethod, TPath extends ExtractRoutePath<TRoutes, TMethod>>(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ): Promise<\n FragnoResponse<\n InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>[\"outputSchema\"]>>\n >\n > => {\n const response = await fragment.callRouteRaw(method, path, inputOptions);\n return parseFragnoResponse(response);\n },\n callRouteRaw: async <\n TMethod extends HTTPMethod,\n TPath extends ExtractRoutePath<TRoutes, TMethod>,\n >(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ): Promise<Response> => {\n // Find the route configuration\n const route = routes.find((r) => r.method === method && r.path === path);\n\n if (!route) {\n return Response.json(\n {\n error: `Route ${method} ${path} not found`,\n code: \"ROUTE_NOT_FOUND\",\n },\n { status: 404 },\n );\n }\n\n const {\n pathParams = {} as ExtractPathParams<TPath>,\n body,\n query,\n headers,\n } = inputOptions || {};\n\n // Convert query to URLSearchParams if needed\n const searchParams =\n query instanceof URLSearchParams\n ? query\n : query\n ? new URLSearchParams(query)\n : new URLSearchParams();\n\n // Convert headers to Headers if needed\n const requestHeaders =\n headers instanceof Headers ? headers : headers ? new Headers(headers) : new Headers();\n\n // Construct RequestInputContext\n const inputContext = new RequestInputContext({\n path: route.path,\n method: route.method,\n pathParams,\n searchParams,\n headers: requestHeaders,\n parsedBody: body,\n inputSchema: route.inputSchema,\n shouldValidateInput: true, // Enable validation for production use\n });\n\n // Construct RequestOutputContext\n const outputContext = new RequestOutputContext(route.outputSchema);\n\n // Call the route handler (wrap with handlerWrapper if provided)\n try {\n let response: Response;\n const thisContext: RequestThisContext = {};\n\n if (handlerWrapper) {\n // Wrapper handles binding the this context internally for database fragments\n // Safe: wrapper knows how to handle the specific this type (DatabaseRequestThisContext)\n const wrappedHandler = handlerWrapper(route.handler as unknown as RouteHandler);\n response = await wrappedHandler.call(thisContext, inputContext, outputContext);\n } else {\n // For standard fragments, bind to an empty RequestThisContext\n // Safe: we know route.handler expects RequestThisContext for standard fragments\n response = await (route.handler as RouteHandler).call(\n thisContext,\n inputContext,\n outputContext,\n );\n }\n return response;\n } catch (error) {\n console.error(\"Error in callRoute handler\", error);\n\n if (error instanceof FragnoApiError) {\n return error.toResponse();\n }\n\n return Response.json(\n { error: \"Internal server error\", code: \"INTERNAL_SERVER_ERROR\" },\n { status: 500 },\n );\n }\n },\n handlersFor: <T extends FullstackFrameworks>(framework: T): HandlersByFramework[T] => {\n const handler = fragment.handler;\n\n // LLMs hallucinate these values sometimes, solution isn't obvious so we throw this error\n // @ts-expect-error TS2367\n if (framework === \"h3\" || framework === \"nuxt\") {\n throw new Error(`To get handlers for h3, use the 'fromWebHandler' utility function:\n import { fromWebHandler } from \"h3\";\n export default fromWebHandler(myFragment().handler);`);\n }\n const allHandlers = {\n astro: { ALL: handler },\n \"react-router\": {\n loader: ({ request }: { request: Request }) => handler(request),\n action: ({ request }: { request: Request }) => handler(request),\n },\n \"next-js\": {\n GET: handler,\n POST: handler,\n PUT: handler,\n DELETE: handler,\n PATCH: handler,\n HEAD: handler,\n OPTIONS: handler,\n },\n \"svelte-kit\": {\n GET: handler,\n POST: handler,\n PUT: handler,\n DELETE: handler,\n PATCH: handler,\n HEAD: handler,\n OPTIONS: handler,\n },\n \"solid-start\": {\n GET: ({ request }: { request: Request }) => handler(request),\n POST: ({ request }: { request: Request }) => handler(request),\n PUT: ({ request }: { request: Request }) => handler(request),\n DELETE: ({ request }: { request: Request }) => handler(request),\n PATCH: ({ request }: { request: Request }) => handler(request),\n HEAD: ({ request }: { request: Request }) => handler(request),\n OPTIONS: ({ request }: { request: Request }) => handler(request),\n },\n \"tanstack-start\": {\n GET: ({ request }: { request: Request }) => handler(request),\n POST: ({ request }: { request: Request }) => handler(request),\n PUT: ({ request }: { request: Request }) => handler(request),\n DELETE: ({ request }: { request: Request }) => handler(request),\n PATCH: ({ request }: { request: Request }) => handler(request),\n HEAD: ({ request }: { request: Request }) => handler(request),\n OPTIONS: ({ request }: { request: Request }) => handler(request),\n },\n } satisfies HandlersByFramework;\n\n return allHandlers[framework];\n },\n handler: async (req: Request) => {\n const url = new URL(req.url);\n const pathname = url.pathname;\n\n const matchRoute = pathname.startsWith(mountRoute) ? pathname.slice(mountRoute.length) : null;\n\n if (matchRoute === null) {\n return Response.json(\n {\n error:\n `Fragno: Route for '${definition.name}' not found. Is the fragment mounted on the right route? ` +\n `Expecting: '${mountRoute}'.`,\n code: \"ROUTE_NOT_FOUND\",\n },\n { status: 404 },\n );\n }\n\n const route = findRoute(router, req.method, matchRoute);\n\n if (!route) {\n return Response.json(\n { error: `Fragno: Route for '${definition.name}' not found`, code: \"ROUTE_NOT_FOUND\" },\n { status: 404 },\n );\n }\n\n const { handler, inputSchema, outputSchema, path } = route.data;\n\n const outputContext = new RequestOutputContext(outputSchema);\n\n // Create mutable request state that can be modified by middleware\n // Clone the request to read body as both text and JSON without consuming original stream\n let requestBody: RequestBodyType = undefined;\n let rawBody: string | undefined = undefined;\n\n if (req.body instanceof ReadableStream) {\n // Clone request to make sure we don't consume body stream\n const clonedReq = req.clone();\n\n // Get raw text\n rawBody = await clonedReq.text();\n\n // Parse JSON if body is not empty\n if (rawBody) {\n try {\n requestBody = JSON.parse(rawBody);\n } catch {\n // If JSON parsing fails, keep body as undefined\n // This handles cases where body is not JSON\n requestBody = undefined;\n }\n }\n }\n\n const requestState = new MutableRequestState({\n pathParams: route.params ?? {},\n searchParams: url.searchParams,\n body: requestBody,\n headers: new Headers(req.headers),\n });\n\n if (middlewareHandler) {\n const middlewareInputContext = new RequestMiddlewareInputContext(routes, {\n method: req.method as HTTPMethod,\n path,\n request: req,\n state: requestState,\n });\n\n const middlewareOutputContext = new RequestMiddlewareOutputContext(\n depsWithInterfaces,\n services,\n );\n\n try {\n const middlewareResult = await middlewareHandler(\n middlewareInputContext,\n middlewareOutputContext,\n );\n if (middlewareResult !== undefined) {\n return middlewareResult;\n }\n } catch (error) {\n console.error(\"Error in middleware\", error);\n\n if (error instanceof FragnoApiError) {\n // TODO: If a validation error occurs in middleware (when calling `await input.valid()`)\n // the processing is short-circuited and a potential `catch` block around the call\n // to `input.valid()` in the actual handler will not be executed.\n return error.toResponse();\n }\n\n return Response.json(\n { error: \"Internal server error\", code: \"INTERNAL_SERVER_ERROR\" },\n { status: 500 },\n );\n }\n }\n\n const inputContext = await RequestInputContext.fromRequest({\n request: req,\n method: req.method,\n path,\n pathParams: (route.params ?? {}) as ExtractPathParams<typeof path>,\n inputSchema,\n state: requestState,\n rawBody,\n });\n\n try {\n // Apply handler wrapper if provided (e.g., for database support)\n // Safe cast: handler wrapper preserves handler signature\n const actualHandler = handlerWrapper\n ? (handlerWrapper(handler as RouteHandler) as typeof handler)\n : handler;\n\n // Create base this context (empty object for standard fragments)\n // Database fragments will provide their own context via handler wrapper\n const thisContext = {} as RequestThisContext;\n const result = await actualHandler.call(thisContext, inputContext, outputContext);\n return result;\n } catch (error) {\n console.error(\"Error in handler\", error);\n\n if (error instanceof FragnoApiError) {\n return error.toResponse();\n }\n\n return Response.json(\n { error: \"Internal server error\", code: \"INTERNAL_SERVER_ERROR\" },\n { status: 500 },\n );\n }\n },\n };\n\n return fragment;\n}\n\n/**\n * Builder class for fluent fragment instantiation API\n */\nexport class FragmentInstantiationBuilder<\n TConfig,\n TDeps,\n TServices extends Record<string, unknown>,\n TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n TAdditionalContext extends Record<string, unknown>,\n TRequiredInterfaces extends Record<string, unknown>,\n TProvidedInterfaces extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n TThisContext extends RequestThisContext,\n> {\n #fragmentBuilder: {\n definition: FragmentDefinition<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TRequiredInterfaces,\n TProvidedInterfaces,\n TThisContext\n >;\n $requiredOptions: TOptions;\n };\n #config?: TConfig;\n #routes?: TRoutesOrFactories;\n #options?: TOptions;\n #services?: TRequiredInterfaces;\n\n constructor(fragmentBuilder: {\n definition: FragmentDefinition<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TRequiredInterfaces,\n TProvidedInterfaces,\n TThisContext\n >;\n $requiredOptions: TOptions;\n }) {\n this.#fragmentBuilder = fragmentBuilder;\n }\n\n /**\n * Set the configuration for the fragment\n */\n withConfig(config: TConfig): this {\n this.#config = config;\n return this;\n }\n\n /**\n * Set the routes for the fragment\n */\n withRoutes<const TNewRoutes extends readonly AnyRouteOrFactory[]>(\n routes: TNewRoutes,\n ): FragmentInstantiationBuilder<\n TConfig,\n TDeps,\n TServices,\n TNewRoutes,\n TAdditionalContext,\n TRequiredInterfaces,\n TProvidedInterfaces,\n TOptions,\n TThisContext\n > {\n this.#routes = routes as unknown as TRoutesOrFactories;\n // Safe cast: We're changing the route type parameter\n return this as unknown as FragmentInstantiationBuilder<\n TConfig,\n TDeps,\n TServices,\n TNewRoutes,\n TAdditionalContext,\n TRequiredInterfaces,\n TProvidedInterfaces,\n TOptions,\n TThisContext\n >;\n }\n\n /**\n * Set the options for the fragment (e.g., mountRoute, databaseAdapter)\n */\n withOptions(options: TOptions): this {\n this.#options = options;\n return this;\n }\n\n /**\n * Provide implementations for services that this fragment uses\n */\n withServices(services: TRequiredInterfaces): this {\n this.#services = services;\n return this;\n }\n\n /**\n * Build and return the instantiated fragment\n */\n build(): FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps & TRequiredInterfaces,\n TServices & TProvidedInterfaces & TRequiredInterfaces,\n TAdditionalContext\n > {\n return createFragment(\n this.#fragmentBuilder,\n this.#config ?? ({} as TConfig),\n this.#routes ?? ([] as const as unknown as TRoutesOrFactories),\n this.#options ?? ({} as TOptions),\n this.#services,\n );\n }\n}\n\n/**\n * Create a fluent builder for instantiating a fragment\n *\n * @example\n * ```ts\n * const fragment = instantiateFragment(myFragmentBuilder)\n * .withConfig({ apiKey: \"key\" })\n * .withRoutes([route1, route2])\n * .withOptions({ mountRoute: \"/api\" })\n * .build();\n * ```\n */\nexport function instantiateFragment<\n TConfig,\n TDeps,\n TServices extends Record<string, unknown>,\n TAdditionalContext extends Record<string, unknown>,\n TRequiredInterfaces extends Record<string, unknown>,\n TProvidedInterfaces extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n TThisContext extends RequestThisContext = RequestThisContext,\n>(fragmentBuilder: {\n definition: FragmentDefinition<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TRequiredInterfaces,\n TProvidedInterfaces,\n TThisContext\n >;\n $requiredOptions: TOptions;\n}): FragmentInstantiationBuilder<\n TConfig,\n TDeps,\n TServices,\n readonly [],\n TAdditionalContext,\n TRequiredInterfaces,\n TProvidedInterfaces,\n TOptions,\n TThisContext\n> {\n return new FragmentInstantiationBuilder(fragmentBuilder);\n}\n"],"mappings":"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"}
|
package/dist/mod.d.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
import { t as FragnoRouteConfig } from "./api-
|
|
2
|
-
|
|
3
|
-
import { c as FragnoPublicClientConfig, l as FragnoPublicConfig, n as FragmentDefinition, o as FragnoFragmentSharedConfig, r as defineFragment, s as FragnoInstantiatedFragment, t as FragmentBuilder, u as createFragment } from "./fragment-builder-MGr68GNb.js";
|
|
4
|
-
export { type AnyRouteOrFactory, type FlattenRouteFactories, FragmentBuilder, type FragmentDefinition, type FragnoFragmentSharedConfig, type FragnoInstantiatedFragment, type FragnoPublicClientConfig, type FragnoPublicConfig, type FragnoRouteConfig, type RouteFactory, type RouteFactoryContext, createFragment, defineFragment, defineRoute, defineRoutes };
|
|
1
|
+
import { Y as AnyRouteOrFactory, ct as RouteHandler, d as FragnoInstantiatedFragment, et as FlattenRouteFactories, f as FragnoPublicClientConfig, gt as RequestInputContext, h as instantiateFragment, it as defineRoutes, l as FragmentInstantiationBuilder, lt as defineFragment, m as createFragment, nt as RouteFactoryContext, ot as FragmentBuilder, p as FragnoPublicConfig, pt as RequestOutputContext, rt as defineRoute, st as FragmentDefinition, t as FragnoRouteConfig, tt as RouteFactory, u as FragnoFragmentSharedConfig } from "./api-BFrUCIsF.js";
|
|
2
|
+
export { type AnyRouteOrFactory, type FlattenRouteFactories, FragmentBuilder, type FragmentDefinition, FragmentInstantiationBuilder, type FragnoFragmentSharedConfig, type FragnoInstantiatedFragment, type FragnoPublicClientConfig, type FragnoPublicConfig, type FragnoRouteConfig, RequestInputContext, RequestOutputContext, type RouteFactory, type RouteFactoryContext, type RouteHandler, createFragment, defineFragment, defineRoute, defineRoutes, instantiateFragment };
|
package/dist/mod.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { n as defineFragment, t as FragmentBuilder } from "./fragment-builder-
|
|
2
|
-
import "./
|
|
3
|
-
import "./
|
|
4
|
-
import { n as defineRoutes, t as defineRoute } from "./route-C5Uryylh.js";
|
|
5
|
-
import { t as createFragment } from "./fragment-instantiation-C4wvwl6V.js";
|
|
1
|
+
import { n as defineFragment, t as FragmentBuilder } from "./fragment-builder-Boh2vNHq.js";
|
|
2
|
+
import { a as RequestOutputContext, n as createFragment, o as RequestInputContext, r as instantiateFragment, t as FragmentInstantiationBuilder } from "./fragment-instantiation-DUT-HLl1.js";
|
|
3
|
+
import { n as defineRoutes, t as defineRoute } from "./route-C4CyNHkC.js";
|
|
6
4
|
|
|
7
|
-
export { FragmentBuilder, createFragment, defineFragment, defineRoute, defineRoutes };
|
|
5
|
+
export { FragmentBuilder, FragmentInstantiationBuilder, RequestInputContext, RequestOutputContext, createFragment, defineFragment, defineRoute, defineRoutes, instantiateFragment };
|
|
@@ -10,12 +10,17 @@ function resolveRouteFactories(context, routesOrFactories) {
|
|
|
10
10
|
function defineRoute(config) {
|
|
11
11
|
return config;
|
|
12
12
|
}
|
|
13
|
-
function defineRoutes() {
|
|
13
|
+
function defineRoutes(_fragmentBuilder) {
|
|
14
14
|
return { create: (fn) => {
|
|
15
|
-
return
|
|
15
|
+
return (ctx) => {
|
|
16
|
+
return fn({
|
|
17
|
+
...ctx,
|
|
18
|
+
defineRoute
|
|
19
|
+
});
|
|
20
|
+
};
|
|
16
21
|
} };
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
//#endregion
|
|
20
25
|
export { defineRoutes as n, resolveRouteFactories as r, defineRoute as t };
|
|
21
|
-
//# sourceMappingURL=route-
|
|
26
|
+
//# sourceMappingURL=route-C4CyNHkC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-C4CyNHkC.js","names":["routes: any[]"],"sources":["../src/api/route.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { FragnoRouteConfig, HTTPMethod, RequestThisContext } from \"./api\";\nimport type { FragmentDefinition } from \"./fragment-builder\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyFragnoRouteConfig = FragnoRouteConfig<HTTPMethod, string, any, any, any, any, any>;\n\nexport type AnyFragmentBuilder = {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly definition: FragmentDefinition<any, any, any, any, any, any, any>;\n};\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 RequestThisContext\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 const TThisContext extends RequestThisContext = RequestThisContext,\n>(\n config: FragnoRouteConfig<\n TMethod,\n TPath,\n undefined,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n > & { inputSchema?: undefined },\n): FragnoRouteConfig<\n TMethod,\n TPath,\n undefined,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n>;\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 const TThisContext extends RequestThisContext = RequestThisContext,\n>(\n config: FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n > & { inputSchema: TInputSchema },\n): FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n>;\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 const TThisContext extends RequestThisContext = RequestThisContext,\n>(\n config: FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n >,\n): FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n TThisContext\n> {\n return config;\n}\n\n// Type helpers to extract types from FragmentBuilder or DatabaseFragmentBuilder\n// DatabaseFragmentBuilder has 6 type parameters: TSchema, TConfig, TDeps, TServices, TUsedServices, TProvidedServices\n// FragmentBuilder has 6 type parameters: TConfig, TDeps, TServices, TAdditionalContext, TUsedServices, TProvidedServices\n\n// Helper to get the return type of the definition getter\n// Use T['definition'] to access the property type\ntype GetDefinition<T> = T extends { definition: unknown } ? T[\"definition\"] : never;\n\n// Extract config\nexport type ExtractFragmentConfig<T> =\n GetDefinition<T> extends FragmentDefinition<\n infer TConfig,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n >\n ? TConfig\n : never;\n\n// Extract deps\nexport type ExtractFragmentDeps<T> =\n GetDefinition<T> extends FragmentDefinition<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n infer TDeps,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n infer TUsedServices,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n >\n ? TDeps & TUsedServices\n : never;\n\n// Helper to recursively bind services (removes `this` parameter from methods)\ntype OmitThisParameter<T> = T extends (this: infer _This, ...args: infer A) => infer R\n ? (...args: A) => R\n : T;\n\ntype BoundServicesLocal<T> = {\n [K in keyof T]: T[K] extends (...args: never[]) => unknown\n ? OmitThisParameter<T[K]>\n : T[K] extends Record<string, unknown>\n ? BoundServicesLocal<T[K]>\n : T[K];\n};\n\n// Extract services (merges both withServices and providesService)\n// First try to extract from $types if available (for DatabaseFragmentBuilder)\n// Otherwise fall back to extracting from definition\nexport type ExtractFragmentServices<T> = T extends {\n $types: { services: infer S; providedServices: infer P };\n}\n ? BoundServicesLocal<S & P>\n : GetDefinition<T> extends FragmentDefinition<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n infer TServices,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n infer TProvidedServices\n >\n ? TServices & TProvidedServices\n : never;\n\n// Extract the this context type from the fragment builder\nexport type ExtractThisContext<T> =\n GetDefinition<T> extends FragmentDefinition<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any,\n infer TThisContext\n >\n ? TThisContext\n : RequestThisContext;\n\n// Overload that infers types from FragmentBuilder or DatabaseFragmentBuilder (runtime value)\nexport function defineRoutes<const TFragmentBuilder extends AnyFragmentBuilder>(\n fragmentBuilder: TFragmentBuilder,\n): {\n create: <\n const TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string,\n ExtractThisContext<TFragmentBuilder>\n >[],\n >(\n fn: (\n context: RouteFactoryContext<\n ExtractFragmentConfig<TFragmentBuilder>,\n ExtractFragmentDeps<TFragmentBuilder>,\n ExtractFragmentServices<TFragmentBuilder>\n > & {\n 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 ExtractThisContext<TFragmentBuilder>\n >,\n ) => FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n ExtractThisContext<TFragmentBuilder>\n >;\n },\n ) => TRoutes,\n ) => RouteFactory<\n ExtractFragmentConfig<TFragmentBuilder>,\n ExtractFragmentDeps<TFragmentBuilder>,\n ExtractFragmentServices<TFragmentBuilder>,\n TRoutes\n >;\n};\n\n// Overload that infers types from FragmentBuilder or DatabaseFragmentBuilder (type parameter)\nexport function defineRoutes<const TFragmentBuilder extends AnyFragmentBuilder>(): {\n create: <\n const TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string,\n ExtractThisContext<TFragmentBuilder>\n >[],\n >(\n fn: (\n context: RouteFactoryContext<\n ExtractFragmentConfig<TFragmentBuilder>,\n ExtractFragmentDeps<TFragmentBuilder>,\n ExtractFragmentServices<TFragmentBuilder>\n > & {\n 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 ExtractThisContext<TFragmentBuilder>\n >,\n ) => FragnoRouteConfig<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters,\n ExtractThisContext<TFragmentBuilder>\n >;\n },\n ) => TRoutes,\n ) => RouteFactory<\n ExtractFragmentConfig<TFragmentBuilder>,\n ExtractFragmentDeps<TFragmentBuilder>,\n ExtractFragmentServices<TFragmentBuilder>,\n TRoutes\n >;\n};\n\n// Overload that accepts manual type parameters\nexport function defineRoutes<TConfig = {}, TDeps = {}, TServices = {}>(): {\n create: <\n const TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string,\n RequestThisContext\n >[],\n >(\n fn: (\n context: RouteFactoryContext<TConfig, TDeps, TServices> & {\n defineRoute: typeof defineRoute;\n },\n ) => TRoutes,\n ) => RouteFactory<TConfig, TDeps, TServices, TRoutes>;\n};\n\n// Implementation\nexport function defineRoutes<\n const TConfig = {},\n const TDeps = {},\n const TServices = {},\n const TFragmentBuilder extends AnyFragmentBuilder | undefined = undefined,\n>(\n // Parameter is only used for type inference, not runtime\n _fragmentBuilder?: TFragmentBuilder,\n) {\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 RequestThisContext\n >[],\n >(\n fn: (\n context: RouteFactoryContext<\n TFragmentBuilder extends AnyFragmentBuilder\n ? ExtractFragmentConfig<TFragmentBuilder>\n : TConfig,\n TFragmentBuilder extends AnyFragmentBuilder\n ? ExtractFragmentDeps<TFragmentBuilder>\n : TDeps,\n TFragmentBuilder extends AnyFragmentBuilder\n ? ExtractFragmentServices<TFragmentBuilder>\n : TServices\n > & {\n defineRoute: typeof defineRoute;\n },\n ) => TRoutes,\n ): RouteFactory<\n TFragmentBuilder extends AnyFragmentBuilder\n ? ExtractFragmentConfig<TFragmentBuilder>\n : TConfig,\n TFragmentBuilder extends AnyFragmentBuilder ? ExtractFragmentDeps<TFragmentBuilder> : TDeps,\n TFragmentBuilder extends AnyFragmentBuilder\n ? ExtractFragmentServices<TFragmentBuilder>\n : TServices,\n TRoutes\n > => {\n // Create a wrapper around the callback that adds the defineRoute function\n return (ctx: RouteFactoryContext<unknown, unknown, unknown>) => {\n const extendedCtx = {\n ...ctx,\n defineRoute,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return fn(extendedCtx as any);\n };\n },\n };\n}\n"],"mappings":";AA+CA,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;;AAgET,SAAgB,YASd,QAiBA;AACA,QAAO;;AA2OT,SAAgB,aAOd,kBACA;AACA,QAAO,EACL,SAWE,OAwBG;AAEH,UAAQ,QAAwD;AAM9D,UAAO,GALa;IAClB,GAAG;IACH;IACD,CAE4B;;IAGlC"}
|
|
@@ -45,4 +45,4 @@ async function getFinalStoreValues() {
|
|
|
45
45
|
|
|
46
46
|
//#endregion
|
|
47
47
|
export { getInitialData as a, getFinalStoreValues as i, addStore as n, hydrateFromWindow as o, cleanStores as r, SSR_ENABLED as t };
|
|
48
|
-
//# sourceMappingURL=ssr-
|
|
48
|
+
//# sourceMappingURL=ssr-kyKI7pqH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-
|
|
1
|
+
{"version":3,"file":"ssr-kyKI7pqH.js","names":["stores: FetcherStore[]","clientInitialData: Map<string, unknown> | undefined","stores"],"sources":["../src/util/ssr.ts"],"sourcesContent":["import { allTasks } from \"nanostores\";\nimport type { FetcherStore } from \"@nanostores/query\";\n\nlet stores: FetcherStore[] = [];\n\nexport const SSR_ENABLED = false;\n\nexport function getStores() {\n return stores;\n}\n\nexport function addStore(store: FetcherStore) {\n stores.push(store);\n}\n\nexport function cleanStores() {\n stores = [];\n}\n\n// Client side\ndeclare global {\n interface Window {\n __FRAGNO_INITIAL_DATA__?: [string, unknown][];\n }\n}\n\nlet clientInitialData: Map<string, unknown> | undefined;\n\nexport function hydrateFromWindow() {\n if (typeof window !== \"undefined\" && window.__FRAGNO_INITIAL_DATA__) {\n clientInitialData = new Map(window.__FRAGNO_INITIAL_DATA__);\n delete window.__FRAGNO_INITIAL_DATA__;\n console.warn(\"hydrateFromWindow\", {\n clientInitialData: Array.from(clientInitialData.entries()),\n });\n }\n}\n\nexport function getInitialData(key: string): unknown | undefined {\n if (clientInitialData?.has(key)) {\n const data = clientInitialData.get(key);\n clientInitialData.delete(key);\n return data;\n }\n return undefined;\n}\n\nfunction listenToStores(): void {\n for (const store of getStores()) {\n // By calling `listen`, we trigger the fetcher function of the store.\n // This will start the data fetching process on the server.\n // We don't need to do anything with the return value of `listen`, as we\n // are only interested in starting the data fetching.\n store.listen(() => {});\n }\n}\n\n// Server side\nexport async function getFinalStoreValues(): Promise<Map<string, unknown>> {\n listenToStores();\n await allTasks();\n\n const stores = getStores();\n const storesInitialValue = new Map<string, unknown>();\n\n for (const store of stores) {\n const value = store.get();\n if (!value || !store.key || value.loading) {\n continue;\n }\n storesInitialValue.set(store.key, value.data);\n }\n\n return storesInitialValue;\n}\n"],"mappings":";;;AAGA,IAAIA,SAAyB,EAAE;AAE/B,MAAa,cAAc;AAE3B,SAAgB,YAAY;AAC1B,QAAO;;AAGT,SAAgB,SAAS,OAAqB;AAC5C,QAAO,KAAK,MAAM;;AAGpB,SAAgB,cAAc;AAC5B,UAAS,EAAE;;AAUb,IAAIC;AAEJ,SAAgB,oBAAoB;AAClC,KAAI,OAAO,WAAW,eAAe,OAAO,yBAAyB;AACnE,sBAAoB,IAAI,IAAI,OAAO,wBAAwB;AAC3D,SAAO,OAAO;AACd,UAAQ,KAAK,qBAAqB,EAChC,mBAAmB,MAAM,KAAK,kBAAkB,SAAS,CAAC,EAC3D,CAAC;;;AAIN,SAAgB,eAAe,KAAkC;AAC/D,KAAI,mBAAmB,IAAI,IAAI,EAAE;EAC/B,MAAM,OAAO,kBAAkB,IAAI,IAAI;AACvC,oBAAkB,OAAO,IAAI;AAC7B,SAAO;;;AAKX,SAAS,iBAAuB;AAC9B,MAAK,MAAM,SAAS,WAAW,CAK7B,OAAM,aAAa,GAAG;;AAK1B,eAAsB,sBAAqD;AACzE,iBAAgB;AAChB,OAAM,UAAU;CAEhB,MAAMC,WAAS,WAAW;CAC1B,MAAM,qCAAqB,IAAI,KAAsB;AAErD,MAAK,MAAM,SAASA,UAAQ;EAC1B,MAAM,QAAQ,MAAM,KAAK;AACzB,MAAI,CAAC,SAAS,CAAC,MAAM,OAAO,MAAM,QAChC;AAEF,qBAAmB,IAAI,MAAM,KAAK,MAAM,KAAK;;AAG/C,QAAO"}
|
package/dist/test/test.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as AnyRouteOrFactory, r as FlattenRouteFactories } from "../route-Bl9Zr1Yv.js";
|
|
3
|
-
import { b as ExtractRouteByPath, f as FragnoResponse, l as FragnoPublicConfig, n as FragmentDefinition, x as ExtractRoutePath } from "../fragment-builder-MGr68GNb.js";
|
|
1
|
+
import { E as ExtractRoutePath, T as ExtractRouteByPath, Y as AnyRouteOrFactory, _ as FragnoResponse, et as FlattenRouteFactories, ht as InferOrUnknown, n as HTTPMethod, p as FragnoPublicConfig, st as FragmentDefinition, t as FragnoRouteConfig, ut as RouteHandlerInputOptions } from "../api-BFrUCIsF.js";
|
|
4
2
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
5
3
|
|
|
6
4
|
//#region src/test/test.d.ts
|
|
@@ -8,12 +6,13 @@ import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
|
8
6
|
/**
|
|
9
7
|
* Options for creating a test fragment
|
|
10
8
|
*/
|
|
11
|
-
interface CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig> {
|
|
9
|
+
interface CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig, TRequiredInterfaces extends Record<string, unknown> = {}> {
|
|
12
10
|
config: TConfig;
|
|
13
11
|
options?: Partial<TOptions>;
|
|
14
12
|
deps?: Partial<TDeps>;
|
|
15
13
|
services?: Partial<TServices>;
|
|
16
14
|
additionalContext?: Partial<TAdditionalContext>;
|
|
15
|
+
interfaceImplementations?: TRequiredInterfaces;
|
|
17
16
|
}
|
|
18
17
|
/**
|
|
19
18
|
* Fragment test instance with type-safe callRoute method
|
|
@@ -57,10 +56,10 @@ interface FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext extends
|
|
|
57
56
|
* }
|
|
58
57
|
* ```
|
|
59
58
|
*/
|
|
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: {
|
|
61
|
-
definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;
|
|
59
|
+
declare function createFragmentForTest<TConfig, TDeps, TServices extends Record<string, unknown>, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig, TRequiredInterfaces extends Record<string, unknown>, TProvidedInterfaces extends Record<string, unknown>, const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(fragmentBuilder: {
|
|
60
|
+
definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext, TRequiredInterfaces, TProvidedInterfaces>;
|
|
62
61
|
$requiredOptions: TOptions;
|
|
63
|
-
}, routesOrFactories: TRoutesOrFactories, options: CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>): FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions, FlattenRouteFactories<TRoutesOrFactories>>;
|
|
62
|
+
}, routesOrFactories: TRoutesOrFactories, options: CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions, TRequiredInterfaces>): FragmentForTest<TConfig, TDeps & TRequiredInterfaces, TServices & TProvidedInterfaces, TAdditionalContext, TOptions, FlattenRouteFactories<TRoutesOrFactories>>;
|
|
64
63
|
//#endregion
|
|
65
64
|
export { CreateFragmentForTestOptions, FragmentForTest, type FragnoResponse, type RouteHandlerInputOptions, createFragmentForTest };
|
|
66
65
|
//# sourceMappingURL=test.d.ts.map
|
package/dist/test/test.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.d.ts","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"test.d.ts","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA8Ba,UAXI,4BAWJ,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BAPgB,MAOhB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBANM,kBAMN,EAAA,4BALiB,MAKjB,CAAA,MAAA,EAAA,OAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA;EACiB,MAAA,EAJpB,OAIoB;EAAR,OAAA,CAAA,EAHV,OAGU,CAHF,QAGE,CAAA;EACO,IAAA,CAAA,EAHpB,OAGoB,CAHZ,KAGY,CAAA;EAAmB,QAAA,CAAA,EAFnC,OAEmC,CAF3B,SAE2B,CAAA;EAM/B,iBAAA,CAAe,EAPV,OAOU,CAPF,kBAOE,CAAA;EAIH,wBAAA,CAAA,EAVA,mBAUA;;;;;AAEF,UANV,eAMU,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BAFE,MAEF,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBADR,kBACQ,EAAA,gBAAA,SAAA,iBAAA,CACvB,UADuB,EAAA,MAAA,EAGvB,gBAHuB,GAAA,SAAA,EAIvB,gBAJuB,GAAA,SAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,CAAA,CAAA;EASjB,MAAA,EAAA,OAAA;EACF,IAAA,EAAA,KAAA;EACI,QAAA,EAAA,SAAA;EACS,iBAAA,EAAA,kBAAA,GAAqB,QAArB;EAAqB,SAAA,EAAA,CAAA,sBAEhB,UAFgB,EAAA,oBAGlB,gBAHkB,CAGD,OAHC,EAGQ,OAHR,CAAA,CAAA,CAAA,MAAA,EAK9B,OAL8B,EAAA,IAAA,EAMhC,KANgC,EAAA,YAAA,CAAA,EAOvB,wBAPuB,CAQpC,KARoC,EASpC,kBAToC,CASjB,OATiB,EASR,KATQ,EASD,OATC,CAAA,CAAA,aAAA,CAAA,CAAA,EAAA,GAWnC,OAXmC,CAYtC,cAZsC,CAapC,cAboC,CAarB,WAbqB,CAaT,kBAbS,CAaU,OAbV,EAamB,KAbnB,EAa0B,OAb1B,CAAA,CAAA,cAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;AAkD1C;;;;;;;;;;;;AAiBM,iBAjBU,qBAiBV,CAAA,OAAA,EAAA,KAAA,EAAA,kBAdc,MAcd,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,2BAbuB,MAavB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBAZa,kBAYb,EAAA,4BAXwB,MAWxB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,4BAVwB,MAUxB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iCAAA,SATsC,iBAStC,EAAA,CAAA,CAAA,eAAA,EAAA;EANU,UAAA,EAAA,kBAAA,CACV,OADU,EAEV,KAFU,EAGV,SAHU,EAIV,kBAJU,EAKV,mBALU,EAMV,mBANU,CAAA;EAQM,gBAAA,EAAA,QAAA;CAED,EAAA,iBAAA,EAAA,kBAAA,EAAA,OAAA,EACV,4BADU,CAEjB,OAFiB,EAGjB,KAHiB,EAIjB,SAJiB,EAKjB,kBALiB,EAMjB,QANiB,EAOjB,mBAPiB,CAAA,CAAA,EASlB,eATkB,CAUnB,OAVmB,EAWnB,KAXmB,GAWX,mBAXW,EAYnB,SAZmB,GAYP,mBAZO,EAanB,kBAbmB,EAcnB,QAdmB,EAenB,qBAfmB,CAeG,kBAfH,CAAA,CAAA"}
|
package/dist/test/test.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import "../
|
|
3
|
-
import "../route-C5Uryylh.js";
|
|
4
|
-
import { t as createFragment } from "../fragment-instantiation-C4wvwl6V.js";
|
|
1
|
+
import { n as createFragment } from "../fragment-instantiation-DUT-HLl1.js";
|
|
2
|
+
import "../route-C4CyNHkC.js";
|
|
5
3
|
|
|
6
4
|
//#region src/test/test.ts
|
|
7
5
|
/**
|
|
@@ -37,14 +35,18 @@ import { t as createFragment } from "../fragment-instantiation-C4wvwl6V.js";
|
|
|
37
35
|
* ```
|
|
38
36
|
*/
|
|
39
37
|
function createFragmentForTest(fragmentBuilder, routesOrFactories, options) {
|
|
40
|
-
const { config, options: fragmentOptions = {}, deps: depsOverride, services: servicesOverride, additionalContext: additionalContextOverride } = options;
|
|
38
|
+
const { config, options: fragmentOptions = {}, deps: depsOverride, services: servicesOverride, additionalContext: additionalContextOverride, interfaceImplementations } = options;
|
|
41
39
|
const definition = fragmentBuilder.definition;
|
|
42
40
|
const deps = {
|
|
43
41
|
...definition.dependencies ? definition.dependencies(config, fragmentOptions) : {},
|
|
42
|
+
...interfaceImplementations,
|
|
44
43
|
...depsOverride
|
|
45
44
|
};
|
|
45
|
+
const baseServices = definition.services ? definition.services(config, fragmentOptions, deps) : {};
|
|
46
|
+
const providedServicesResolved = typeof definition.providedServices === "function" ? definition.providedServices(config, fragmentOptions, deps) : definition.providedServices;
|
|
46
47
|
const services = {
|
|
47
|
-
...
|
|
48
|
+
...baseServices,
|
|
49
|
+
...providedServicesResolved,
|
|
48
50
|
...servicesOverride
|
|
49
51
|
};
|
|
50
52
|
const additionalContext = {
|
|
@@ -52,7 +54,7 @@ function createFragmentForTest(fragmentBuilder, routesOrFactories, options) {
|
|
|
52
54
|
...fragmentOptions,
|
|
53
55
|
...additionalContextOverride
|
|
54
56
|
};
|
|
55
|
-
const fragment = createFragment(fragmentBuilder, config, routesOrFactories, fragmentOptions);
|
|
57
|
+
const fragment = createFragment(fragmentBuilder, config, routesOrFactories, fragmentOptions, interfaceImplementations);
|
|
56
58
|
return {
|
|
57
59
|
config,
|
|
58
60
|
deps,
|
package/dist/test/test.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
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 TRequiredInterfaces extends Record<string, unknown> = {},\n> {\n config: TConfig;\n options?: Partial<TOptions>;\n deps?: Partial<TDeps>;\n services?: Partial<TServices>;\n additionalContext?: Partial<TAdditionalContext>;\n interfaceImplementations?: TRequiredInterfaces;\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 TRequiredInterfaces extends Record<string, unknown>,\n TProvidedInterfaces extends Record<string, unknown>,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TRequiredInterfaces,\n TProvidedInterfaces\n >;\n $requiredOptions: TOptions;\n },\n routesOrFactories: TRoutesOrFactories,\n options: CreateFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions,\n TRequiredInterfaces\n >,\n): FragmentForTest<\n TConfig,\n TDeps & TRequiredInterfaces,\n TServices & TProvidedInterfaces,\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 interfaceImplementations,\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 and interface implementations\n const deps = {\n ...baseDeps,\n ...interfaceImplementations,\n ...depsOverride,\n } as TDeps & TRequiredInterfaces;\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 // Handle providedServices - can be either a factory function or a direct object\n const providedServicesResolved =\n typeof definition.providedServices === \"function\"\n ? definition.providedServices(config, fragmentOptions, deps)\n : definition.providedServices;\n\n // Merge services with provided services and overrides\n const services = {\n ...baseServices,\n ...providedServicesResolved,\n ...servicesOverride,\n } as TServices & TProvidedInterfaces;\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(\n fragmentBuilder,\n config,\n routesOrFactories,\n fragmentOptions,\n interfaceImplementations,\n );\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0GA,SAAgB,sBAUd,iBAWA,mBACA,SAeA;CACA,MAAM,EACJ,QACA,SAAS,kBAAkB,EAAE,EAC7B,MAAM,cACN,UAAU,kBACV,mBAAmB,2BACnB,6BACE;CAGJ,MAAM,aAAa,gBAAgB;CAMnC,MAAM,OAAO;EACX,GANe,WAAW,eACxB,WAAW,aAAa,QAAQ,gBAAgB,GAC/C,EAAE;EAKL,GAAG;EACH,GAAG;EACJ;CAGD,MAAM,eAAe,WAAW,WAC5B,WAAW,SAAS,QAAQ,iBAAiB,KAAK,GACjD,EAAE;CAGP,MAAM,2BACJ,OAAO,WAAW,qBAAqB,aACnC,WAAW,iBAAiB,QAAQ,iBAAiB,KAAK,GAC1D,WAAW;CAGjB,MAAM,WAAW;EACf,GAAG;EACH,GAAG;EACH,GAAG;EACJ;CAGD,MAAM,oBAAoB;EACxB,GAAG,WAAW;EACd,GAAG;EACH,GAAG;EACJ;CAGD,MAAM,WAAW,eACf,iBACA,QACA,mBACA,iBACA,yBACD;AAED,QAAO;EACL;EACA;EACA;EACA;EACA,YAAY,QAAQ,MAAM,iBAAiB,SAAS,UAAU,QAAQ,MAAM,aAAa;EAC1F"}
|
package/package.json
CHANGED
package/src/api/api.ts
CHANGED
|
@@ -23,6 +23,12 @@ export type ValidPath<T extends string = string> = T extends `/${infer Rest}`
|
|
|
23
23
|
: T
|
|
24
24
|
: PathError<T, "Path must start with '/'.">; // Excludes paths not starting with "/"
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Base ServiceContext interface. Can be augmented by packages like @fragno-dev/db.
|
|
28
|
+
*/
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
30
|
+
export interface RequestThisContext {}
|
|
31
|
+
|
|
26
32
|
export interface FragnoRouteConfig<
|
|
27
33
|
TMethod extends HTTPMethod,
|
|
28
34
|
TPath extends string,
|
|
@@ -30,6 +36,7 @@ export interface FragnoRouteConfig<
|
|
|
30
36
|
TOutputSchema extends StandardSchemaV1 | undefined,
|
|
31
37
|
TErrorCode extends string = string,
|
|
32
38
|
TQueryParameters extends string = string,
|
|
39
|
+
TThisContext extends RequestThisContext = RequestThisContext,
|
|
33
40
|
> {
|
|
34
41
|
method: TMethod;
|
|
35
42
|
path: TPath;
|
|
@@ -38,6 +45,7 @@ export interface FragnoRouteConfig<
|
|
|
38
45
|
errorCodes?: readonly TErrorCode[];
|
|
39
46
|
queryParameters?: readonly TQueryParameters[];
|
|
40
47
|
handler(
|
|
48
|
+
this: TThisContext,
|
|
41
49
|
inputCtx: RequestInputContext<TPath, TInputSchema>,
|
|
42
50
|
outputCtx: RequestOutputContext<TOutputSchema, TErrorCode>,
|
|
43
51
|
): Promise<Response>;
|
|
@@ -50,6 +58,7 @@ export function addRoute<
|
|
|
50
58
|
TOutputSchema extends StandardSchemaV1 | undefined,
|
|
51
59
|
TErrorCode extends string = string,
|
|
52
60
|
TQueryParameters extends string = string,
|
|
61
|
+
TThisContext extends RequestThisContext = RequestThisContext,
|
|
53
62
|
>(
|
|
54
63
|
route: FragnoRouteConfig<
|
|
55
64
|
TMethod,
|
|
@@ -57,9 +66,18 @@ export function addRoute<
|
|
|
57
66
|
undefined,
|
|
58
67
|
TOutputSchema,
|
|
59
68
|
TErrorCode,
|
|
60
|
-
TQueryParameters
|
|
69
|
+
TQueryParameters,
|
|
70
|
+
TThisContext
|
|
61
71
|
> & { inputSchema?: undefined },
|
|
62
|
-
): FragnoRouteConfig<
|
|
72
|
+
): FragnoRouteConfig<
|
|
73
|
+
TMethod,
|
|
74
|
+
TPath,
|
|
75
|
+
undefined,
|
|
76
|
+
TOutputSchema,
|
|
77
|
+
TErrorCode,
|
|
78
|
+
TQueryParameters,
|
|
79
|
+
TThisContext
|
|
80
|
+
>;
|
|
63
81
|
|
|
64
82
|
// Overload for routes with inputSchema
|
|
65
83
|
export function addRoute<
|
|
@@ -69,6 +87,7 @@ export function addRoute<
|
|
|
69
87
|
TOutputSchema extends StandardSchemaV1 | undefined,
|
|
70
88
|
TErrorCode extends string = string,
|
|
71
89
|
TQueryParameters extends string = string,
|
|
90
|
+
TThisContext extends RequestThisContext = RequestThisContext,
|
|
72
91
|
>(
|
|
73
92
|
route: FragnoRouteConfig<
|
|
74
93
|
TMethod,
|
|
@@ -76,11 +95,20 @@ export function addRoute<
|
|
|
76
95
|
TInputSchema,
|
|
77
96
|
TOutputSchema,
|
|
78
97
|
TErrorCode,
|
|
79
|
-
TQueryParameters
|
|
98
|
+
TQueryParameters,
|
|
99
|
+
TThisContext
|
|
80
100
|
> & {
|
|
81
101
|
inputSchema: TInputSchema;
|
|
82
102
|
},
|
|
83
|
-
): FragnoRouteConfig<
|
|
103
|
+
): FragnoRouteConfig<
|
|
104
|
+
TMethod,
|
|
105
|
+
TPath,
|
|
106
|
+
TInputSchema,
|
|
107
|
+
TOutputSchema,
|
|
108
|
+
TErrorCode,
|
|
109
|
+
TQueryParameters,
|
|
110
|
+
TThisContext
|
|
111
|
+
>;
|
|
84
112
|
|
|
85
113
|
// Implementation
|
|
86
114
|
export function addRoute<
|
|
@@ -90,6 +118,7 @@ export function addRoute<
|
|
|
90
118
|
TOutputSchema extends StandardSchemaV1 | undefined,
|
|
91
119
|
TErrorCode extends string = string,
|
|
92
120
|
TQueryParameters extends string = string,
|
|
121
|
+
TThisContext extends RequestThisContext = RequestThisContext,
|
|
93
122
|
>(
|
|
94
123
|
route: FragnoRouteConfig<
|
|
95
124
|
TMethod,
|
|
@@ -97,11 +126,21 @@ export function addRoute<
|
|
|
97
126
|
TInputSchema,
|
|
98
127
|
TOutputSchema,
|
|
99
128
|
TErrorCode,
|
|
100
|
-
TQueryParameters
|
|
129
|
+
TQueryParameters,
|
|
130
|
+
TThisContext
|
|
101
131
|
>,
|
|
102
|
-
): FragnoRouteConfig<
|
|
132
|
+
): FragnoRouteConfig<
|
|
133
|
+
TMethod,
|
|
134
|
+
TPath,
|
|
135
|
+
TInputSchema,
|
|
136
|
+
TOutputSchema,
|
|
137
|
+
TErrorCode,
|
|
138
|
+
TQueryParameters,
|
|
139
|
+
TThisContext
|
|
140
|
+
> {
|
|
103
141
|
return route;
|
|
104
142
|
}
|
|
105
143
|
|
|
106
144
|
export { FragnoApiError, FragnoApiValidationError } from "./error";
|
|
107
145
|
export type { RouteHandlerInputOptions } from "./route-handler-input-options";
|
|
146
|
+
export { instantiateFragment, FragmentInstantiationBuilder } from "./fragment-instantiation";
|