@fragno-dev/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.turbo/turbo-build.log +61 -0
  2. package/.turbo/turbo-types$colon$check.log +2 -0
  3. package/dist/api/api.d.ts +2 -0
  4. package/dist/api/api.js +3 -0
  5. package/dist/api-CBDGZiLC.d.ts +278 -0
  6. package/dist/api-CBDGZiLC.d.ts.map +1 -0
  7. package/dist/api-DgHfYjq2.js +54 -0
  8. package/dist/api-DgHfYjq2.js.map +1 -0
  9. package/dist/client/client.d.ts +3 -0
  10. package/dist/client/client.js +6 -0
  11. package/dist/client/client.svelte.d.ts +33 -0
  12. package/dist/client/client.svelte.d.ts.map +1 -0
  13. package/dist/client/client.svelte.js +123 -0
  14. package/dist/client/client.svelte.js.map +1 -0
  15. package/dist/client/react.d.ts +58 -0
  16. package/dist/client/react.d.ts.map +1 -0
  17. package/dist/client/react.js +80 -0
  18. package/dist/client/react.js.map +1 -0
  19. package/dist/client/vanilla.d.ts +61 -0
  20. package/dist/client/vanilla.d.ts.map +1 -0
  21. package/dist/client/vanilla.js +136 -0
  22. package/dist/client/vanilla.js.map +1 -0
  23. package/dist/client/vue.d.ts +39 -0
  24. package/dist/client/vue.d.ts.map +1 -0
  25. package/dist/client/vue.js +108 -0
  26. package/dist/client/vue.js.map +1 -0
  27. package/dist/client-DWjxKDnE.js +703 -0
  28. package/dist/client-DWjxKDnE.js.map +1 -0
  29. package/dist/client-XFdAy-IQ.d.ts +287 -0
  30. package/dist/client-XFdAy-IQ.d.ts.map +1 -0
  31. package/dist/integrations/astro.d.ts +18 -0
  32. package/dist/integrations/astro.d.ts.map +1 -0
  33. package/dist/integrations/astro.js +16 -0
  34. package/dist/integrations/astro.js.map +1 -0
  35. package/dist/integrations/next-js.d.ts +15 -0
  36. package/dist/integrations/next-js.d.ts.map +1 -0
  37. package/dist/integrations/next-js.js +17 -0
  38. package/dist/integrations/next-js.js.map +1 -0
  39. package/dist/integrations/react-ssr.d.ts +19 -0
  40. package/dist/integrations/react-ssr.d.ts.map +1 -0
  41. package/dist/integrations/react-ssr.js +38 -0
  42. package/dist/integrations/react-ssr.js.map +1 -0
  43. package/dist/integrations/svelte-kit.d.ts +21 -0
  44. package/dist/integrations/svelte-kit.d.ts.map +1 -0
  45. package/dist/integrations/svelte-kit.js +18 -0
  46. package/dist/integrations/svelte-kit.js.map +1 -0
  47. package/dist/mod.d.ts +3 -0
  48. package/dist/mod.js +177 -0
  49. package/dist/mod.js.map +1 -0
  50. package/dist/route-Bp6eByhz.js +331 -0
  51. package/dist/route-Bp6eByhz.js.map +1 -0
  52. package/dist/ssr-tJHqcNSw.js +48 -0
  53. package/dist/ssr-tJHqcNSw.js.map +1 -0
  54. package/package.json +127 -0
  55. package/src/api/api.test.ts +140 -0
  56. package/src/api/api.ts +106 -0
  57. package/src/api/error.ts +47 -0
  58. package/src/api/fragment.test.ts +509 -0
  59. package/src/api/fragment.ts +277 -0
  60. package/src/api/internal/path-runtime.test.ts +121 -0
  61. package/src/api/internal/path-type.test.ts +602 -0
  62. package/src/api/internal/path.ts +322 -0
  63. package/src/api/internal/response-stream.ts +118 -0
  64. package/src/api/internal/route.test.ts +56 -0
  65. package/src/api/internal/route.ts +9 -0
  66. package/src/api/request-input-context.test.ts +437 -0
  67. package/src/api/request-input-context.ts +201 -0
  68. package/src/api/request-middleware.test.ts +544 -0
  69. package/src/api/request-middleware.ts +126 -0
  70. package/src/api/request-output-context.test.ts +626 -0
  71. package/src/api/request-output-context.ts +175 -0
  72. package/src/api/route.test.ts +176 -0
  73. package/src/api/route.ts +152 -0
  74. package/src/client/client-builder.test.ts +264 -0
  75. package/src/client/client-error.test.ts +15 -0
  76. package/src/client/client-error.ts +141 -0
  77. package/src/client/client-types.test.ts +493 -0
  78. package/src/client/client.ssr.test.ts +173 -0
  79. package/src/client/client.svelte.test.ts +837 -0
  80. package/src/client/client.svelte.ts +278 -0
  81. package/src/client/client.test.ts +1690 -0
  82. package/src/client/client.ts +1035 -0
  83. package/src/client/component.test.svelte +21 -0
  84. package/src/client/internal/ndjson-streaming.test.ts +457 -0
  85. package/src/client/internal/ndjson-streaming.ts +248 -0
  86. package/src/client/react.test.ts +947 -0
  87. package/src/client/react.ts +241 -0
  88. package/src/client/vanilla.test.ts +867 -0
  89. package/src/client/vanilla.ts +265 -0
  90. package/src/client/vue.test.ts +754 -0
  91. package/src/client/vue.ts +242 -0
  92. package/src/http/http-status.ts +60 -0
  93. package/src/integrations/astro.ts +17 -0
  94. package/src/integrations/next-js.ts +31 -0
  95. package/src/integrations/react-ssr.ts +40 -0
  96. package/src/integrations/svelte-kit.ts +41 -0
  97. package/src/mod.ts +20 -0
  98. package/src/util/async.test.ts +85 -0
  99. package/src/util/async.ts +96 -0
  100. package/src/util/content-type.test.ts +136 -0
  101. package/src/util/content-type.ts +84 -0
  102. package/src/util/nanostores.test.ts +28 -0
  103. package/src/util/nanostores.ts +65 -0
  104. package/src/util/ssr.ts +75 -0
  105. package/src/util/types-util.ts +16 -0
  106. package/tsconfig.json +10 -0
  107. package/tsdown.config.ts +21 -0
  108. package/vitest.config.ts +10 -0
@@ -0,0 +1,175 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import type { ContentlessStatusCode, StatusCode } from "../http/http-status";
3
+ import { ResponseStream } from "./internal/response-stream";
4
+ import type { InferOrUnknown } from "../util/types-util";
5
+
6
+ export type ResponseData = string | ArrayBuffer | ReadableStream | Uint8Array<ArrayBuffer>;
7
+
8
+ interface ResponseInit<T extends StatusCode = StatusCode> {
9
+ headers?: HeadersInit;
10
+ status?: T;
11
+ statusText?: string;
12
+ }
13
+
14
+ /**
15
+ * Utility function to merge headers from multiple sources.
16
+ * Later headers override earlier ones.
17
+ */
18
+ function mergeHeaders(...headerSources: (HeadersInit | undefined)[]): HeadersInit | undefined {
19
+ const mergedHeaders = new Headers();
20
+
21
+ for (const headerSource of headerSources) {
22
+ if (!headerSource) continue;
23
+
24
+ if (headerSource instanceof Headers) {
25
+ for (const [key, value] of headerSource.entries()) {
26
+ mergedHeaders.set(key, value);
27
+ }
28
+ } else if (Array.isArray(headerSource)) {
29
+ for (const [key, value] of headerSource) {
30
+ mergedHeaders.set(key, value);
31
+ }
32
+ } else {
33
+ for (const [key, value] of Object.entries(headerSource)) {
34
+ mergedHeaders.set(key, value);
35
+ }
36
+ }
37
+ }
38
+
39
+ return mergedHeaders;
40
+ }
41
+
42
+ export abstract class OutputContext<const TOutput, const TErrorCode extends string> {
43
+ /**
44
+ * Creates an error response.
45
+ *
46
+ * Shortcut for `throw new FragnoApiError(...)`
47
+ */
48
+ error(
49
+ { message, code }: { message: string; code: TErrorCode },
50
+ initOrStatus?: ResponseInit | StatusCode,
51
+ headers?: HeadersInit,
52
+ ): Response {
53
+ if (typeof initOrStatus === "undefined") {
54
+ return Response.json({ error: message, code }, { status: 500, headers });
55
+ }
56
+
57
+ if (typeof initOrStatus === "number") {
58
+ return Response.json({ error: message, code }, { status: initOrStatus, headers });
59
+ }
60
+
61
+ const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
62
+ return Response.json(
63
+ { error: message, code },
64
+ { status: initOrStatus.status, headers: mergedHeaders },
65
+ );
66
+ }
67
+
68
+ empty(
69
+ initOrStatus?: ResponseInit<ContentlessStatusCode> | ContentlessStatusCode,
70
+ headers?: HeadersInit,
71
+ ): Response {
72
+ const defaultHeaders = {};
73
+
74
+ if (typeof initOrStatus === "undefined") {
75
+ const mergedHeaders = mergeHeaders(defaultHeaders, headers);
76
+ return Response.json(null, {
77
+ status: 201,
78
+ headers: mergedHeaders,
79
+ });
80
+ }
81
+
82
+ if (typeof initOrStatus === "number") {
83
+ const mergedHeaders = mergeHeaders(defaultHeaders, headers);
84
+ return Response.json(null, {
85
+ status: initOrStatus,
86
+ headers: mergedHeaders,
87
+ });
88
+ }
89
+
90
+ const mergedHeaders = mergeHeaders(defaultHeaders, initOrStatus.headers, headers);
91
+ return Response.json(null, {
92
+ status: initOrStatus.status,
93
+ headers: mergedHeaders,
94
+ });
95
+ }
96
+
97
+ json(object: TOutput, initOrStatus?: ResponseInit | StatusCode, headers?: HeadersInit): Response {
98
+ if (typeof initOrStatus === "undefined") {
99
+ return Response.json(object, {
100
+ status: 200,
101
+ headers,
102
+ });
103
+ }
104
+
105
+ if (typeof initOrStatus === "number") {
106
+ return Response.json(object, {
107
+ status: initOrStatus,
108
+ headers,
109
+ });
110
+ }
111
+
112
+ const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
113
+ return Response.json(object, {
114
+ status: initOrStatus.status,
115
+ headers: mergedHeaders,
116
+ });
117
+ }
118
+
119
+ jsonStream = (
120
+ cb: (stream: ResponseStream<TOutput>) => void | Promise<void>,
121
+ {
122
+ onError,
123
+ headers,
124
+ }: {
125
+ onError?: (error: Error, stream: ResponseStream<TOutput>) => void | Promise<void>;
126
+ headers?: HeadersInit;
127
+ } = {},
128
+ ): Response => {
129
+ // Note: this is intentionally an arrow function (=>) to keep `this` context.
130
+ const defaultHeaders = {
131
+ "content-type": "application/x-ndjson; charset=utf-8",
132
+ "transfer-encoding": "chunked",
133
+ "cache-control": "no-cache",
134
+ };
135
+
136
+ const { readable, writable } = new TransformStream();
137
+ const stream = new ResponseStream(writable, readable);
138
+
139
+ (async () => {
140
+ try {
141
+ await cb(stream);
142
+ } catch (e) {
143
+ if (e === undefined) {
144
+ // If reading is canceled without a reason value (e.g. by StreamingApi)
145
+ // then the .pipeTo() promise will reject with undefined.
146
+ // In this case, do nothing because the stream is already closed.
147
+ } else if (e instanceof Error && onError) {
148
+ await onError(e, stream);
149
+ } else {
150
+ console.error(e);
151
+ }
152
+ } finally {
153
+ stream.close();
154
+ }
155
+ })();
156
+
157
+ return new Response(stream.responseReadable, {
158
+ status: 200,
159
+ headers: mergeHeaders(defaultHeaders, headers),
160
+ });
161
+ };
162
+ }
163
+
164
+ export class RequestOutputContext<
165
+ const TOutputSchema extends StandardSchemaV1 | undefined = undefined,
166
+ const TErrorCode extends string = string,
167
+ > extends OutputContext<InferOrUnknown<TOutputSchema>, TErrorCode> {
168
+ // eslint-disable-next-line no-unused-private-class-members
169
+ #outputSchema?: TOutputSchema;
170
+
171
+ constructor(outputSchema?: TOutputSchema) {
172
+ super();
173
+ this.#outputSchema = outputSchema;
174
+ }
175
+ }
@@ -0,0 +1,176 @@
1
+ import { test, expect, expectTypeOf, describe } from "vitest";
2
+ import { defineRoute } from "./route";
3
+ import { z } from "zod";
4
+
5
+ describe("defineRoute", () => {
6
+ test("defineRoute no inputSchema", () => {
7
+ const route = defineRoute({
8
+ method: "GET",
9
+ path: "/thing/**:path",
10
+ handler: async ({ path, pathParams }, { empty }) => {
11
+ expect(path).toEqual("/thing/**:path");
12
+ expectTypeOf<typeof path>().toEqualTypeOf<"/thing/**:path">();
13
+
14
+ expect(pathParams).toEqual({ path: "test" });
15
+ expectTypeOf<typeof pathParams>().toEqualTypeOf<{ path: string }>();
16
+ return empty();
17
+ },
18
+ });
19
+
20
+ expect(route.method).toBe("GET");
21
+ expect(route.path).toBe("/thing/**:path");
22
+ expect(route.handler).toBeDefined();
23
+ });
24
+
25
+ test("defineRoute with inputSchema", () => {
26
+ const route = defineRoute({
27
+ method: "GET" as const,
28
+ path: "/thing/**:path" as const,
29
+ inputSchema: z.object({
30
+ path: z.string(),
31
+ }),
32
+ handler: async ({ path, pathParams, input }, { empty }) => {
33
+ expect(path).toEqual("/thing/**:path");
34
+ expectTypeOf<typeof path>().toEqualTypeOf<"/thing/**:path">();
35
+
36
+ expect(pathParams).toEqual({ path: "test" });
37
+ expectTypeOf<typeof pathParams>().toEqualTypeOf<{ path: string }>();
38
+
39
+ expect(input).toBeTruthy();
40
+ if (input) {
41
+ expectTypeOf<typeof input.schema>().toEqualTypeOf<z.ZodObject<{ path: z.ZodString }>>();
42
+ expectTypeOf<typeof input.valid>().toEqualTypeOf<() => Promise<{ path: string }>>();
43
+
44
+ const _valid = await input.valid();
45
+ expectTypeOf<typeof _valid>().toEqualTypeOf<{ path: string }>();
46
+ }
47
+ return empty();
48
+ },
49
+ });
50
+
51
+ expect(route.method).toBe("GET");
52
+ expect(route.path).toBe("/thing/**:path");
53
+ expect(route.inputSchema).toBeDefined();
54
+ expect(route.handler).toBeDefined();
55
+ });
56
+
57
+ test("HTTPMethod DELETE without inputSchema or outputSchema", () => {
58
+ const route = defineRoute({
59
+ method: "DELETE",
60
+ path: "/thing",
61
+ handler: async ({ input }, { empty, json }) => {
62
+ // FIXME: Would be nicer if input was not on the object at all
63
+ expect(input).toBeUndefined();
64
+ expectTypeOf<typeof input>().toEqualTypeOf<undefined>();
65
+
66
+ // FIXME: Would be nicer if parameter of json was never, or not have json as field at all.
67
+ expect(json).toBeDefined();
68
+ expectTypeOf<Parameters<typeof json>[0]>().toEqualTypeOf<unknown>();
69
+
70
+ return empty();
71
+ },
72
+ });
73
+
74
+ expect(route.method).toBe("DELETE");
75
+ expect(route.path).toBe("/thing");
76
+ expect(route.handler).toBeDefined();
77
+ });
78
+
79
+ test("defineRoute with outputSchema", () => {
80
+ const route = defineRoute({
81
+ method: "GET",
82
+ path: "/users",
83
+ outputSchema: z.array(z.object({ id: z.number(), name: z.string() })),
84
+ handler: async (_ctx, { json }) => {
85
+ return json([{ id: 1, name: "John" }]);
86
+ },
87
+ });
88
+
89
+ expect(route.method).toBe("GET");
90
+ expect(route.path).toBe("/users");
91
+ expect(route.outputSchema).toBeDefined();
92
+ expect(route.handler).toBeDefined();
93
+ });
94
+
95
+ test("defineRoute with both inputSchema and outputSchema", () => {
96
+ const route = defineRoute({
97
+ method: "POST",
98
+ path: "/users",
99
+ inputSchema: z.object({ name: z.string(), email: z.string() }),
100
+ outputSchema: z.object({ id: z.number(), name: z.string(), email: z.string() }),
101
+ handler: async ({ input }, { json }) => {
102
+ expect(input).toBeTruthy();
103
+ if (input) {
104
+ const data = await input.valid();
105
+ return json({ id: 1, name: data.name, email: data.email });
106
+ }
107
+ return json({ id: 1, name: "", email: "" });
108
+ },
109
+ });
110
+
111
+ expect(route.method).toBe("POST");
112
+ expect(route.path).toBe("/users");
113
+ expect(route.inputSchema).toBeDefined();
114
+ expect(route.outputSchema).toBeDefined();
115
+ expect(route.handler).toBeDefined();
116
+ });
117
+
118
+ test("defineRoute with path parameters", () => {
119
+ const route = defineRoute({
120
+ method: "GET",
121
+ path: "/users/:id",
122
+ outputSchema: z.object({ id: z.number(), name: z.string() }),
123
+ handler: async ({ pathParams }, { json }) => {
124
+ expectTypeOf<typeof pathParams>().toEqualTypeOf<{ id: string }>();
125
+ return json({ id: Number(pathParams.id), name: "John" });
126
+ },
127
+ });
128
+
129
+ expect(route.method).toBe("GET");
130
+ expect(route.path).toBe("/users/:id");
131
+ expect(route.outputSchema).toBeDefined();
132
+ expect(route.handler).toBeDefined();
133
+ });
134
+
135
+ test("defineRoute with multiple path parameters", () => {
136
+ const route = defineRoute({
137
+ method: "GET",
138
+ path: "/organizations/:orgId/users/:userId",
139
+ outputSchema: z.object({ orgId: z.number(), userId: z.number() }),
140
+ handler: async ({ pathParams }, { json }) => {
141
+ expectTypeOf<typeof pathParams>().toEqualTypeOf<{ orgId: string; userId: string }>();
142
+ return json({ orgId: Number(pathParams.orgId), userId: Number(pathParams.userId) });
143
+ },
144
+ });
145
+
146
+ expect(route.method).toBe("GET");
147
+ expect(route.path).toBe("/organizations/:orgId/users/:userId");
148
+ expect(route.outputSchema).toBeDefined();
149
+ expect(route.handler).toBeDefined();
150
+ });
151
+
152
+ test("defineRoute returns the same config object", () => {
153
+ const config = {
154
+ method: "GET" as const,
155
+ path: "/test" as const,
156
+ handler: async (_ctx: unknown, { empty }: { empty: () => Response }) => empty(),
157
+ };
158
+
159
+ const route = defineRoute(config);
160
+ expect(route).toBe(config);
161
+ });
162
+
163
+ test("defineRoute with ValidPath type checking", () => {
164
+ // Valid path
165
+ const validRoute = defineRoute({
166
+ method: "GET",
167
+ path: "/api/users",
168
+ handler: async (_ctx, { empty }) => empty(),
169
+ });
170
+
171
+ expectTypeOf(validRoute.path).toEqualTypeOf<"/api/users">();
172
+
173
+ // TODO: Once ValidPath is integrated with defineRoute, add tests for invalid paths
174
+ // Currently defineRoute doesn't enforce ValidPath constraints
175
+ });
176
+ });
@@ -0,0 +1,152 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import type { FragnoRouteConfig, HTTPMethod } from "./api";
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ export type AnyFragnoRouteConfig = FragnoRouteConfig<HTTPMethod, string, any, any, any, any>;
6
+
7
+ export interface RouteFactoryContext<TConfig, TDeps, TServices> {
8
+ config: TConfig;
9
+ deps: TDeps;
10
+ services: TServices;
11
+ }
12
+
13
+ export type RouteFactory<
14
+ TConfig,
15
+ TDeps,
16
+ TServices,
17
+ TRoutes extends readonly FragnoRouteConfig<
18
+ HTTPMethod,
19
+ string,
20
+ StandardSchemaV1 | undefined,
21
+ StandardSchemaV1 | undefined,
22
+ string,
23
+ string
24
+ >[],
25
+ > = (context: RouteFactoryContext<TConfig, TDeps, TServices>) => TRoutes;
26
+
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ export type AnyRouteOrFactory = AnyFragnoRouteConfig | RouteFactory<any, any, any, any>;
29
+
30
+ export type FlattenRouteFactories<T extends readonly AnyRouteOrFactory[]> = T extends readonly [
31
+ infer First,
32
+ ...infer Rest extends readonly AnyRouteOrFactory[],
33
+ ]
34
+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ First extends RouteFactory<any, any, any, infer TRoutes>
36
+ ? [...TRoutes, ...FlattenRouteFactories<Rest>]
37
+ : [First, ...FlattenRouteFactories<Rest>]
38
+ : [];
39
+
40
+ // Helper to resolve route factories into routes
41
+ export function resolveRouteFactories<
42
+ TConfig,
43
+ TDeps,
44
+ TServices,
45
+ const TRoutesOrFactories extends readonly AnyRouteOrFactory[],
46
+ >(
47
+ context: RouteFactoryContext<TConfig, TDeps, TServices>,
48
+ routesOrFactories: TRoutesOrFactories,
49
+ ): FlattenRouteFactories<TRoutesOrFactories> {
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ const routes: any[] = [];
52
+
53
+ for (const item of routesOrFactories) {
54
+ if (typeof item === "function") {
55
+ // It's a route factory
56
+ const factoryRoutes = item(context);
57
+ routes.push(...factoryRoutes);
58
+ } else {
59
+ // It's a direct route
60
+ routes.push(item);
61
+ }
62
+ }
63
+
64
+ return routes as FlattenRouteFactories<TRoutesOrFactories>;
65
+ }
66
+
67
+ // TODO(Wilco): Do these overloads actually do anything?
68
+ // TODO(Wilco): ValidPath<T> should be added back in here.
69
+
70
+ // Overload for routes without inputSchema
71
+ export function defineRoute<
72
+ const TMethod extends HTTPMethod,
73
+ const TPath extends string,
74
+ const TOutputSchema extends StandardSchemaV1 | undefined,
75
+ const TErrorCode extends string = string,
76
+ const TQueryParameters extends string = string,
77
+ >(
78
+ config: FragnoRouteConfig<
79
+ TMethod,
80
+ TPath,
81
+ undefined,
82
+ TOutputSchema,
83
+ TErrorCode,
84
+ TQueryParameters
85
+ > & { inputSchema?: undefined },
86
+ ): FragnoRouteConfig<TMethod, TPath, undefined, TOutputSchema, TErrorCode, TQueryParameters>;
87
+
88
+ // Overload for routes with inputSchema
89
+ export function defineRoute<
90
+ const TMethod extends HTTPMethod,
91
+ const TPath extends string,
92
+ const TInputSchema extends StandardSchemaV1,
93
+ const TOutputSchema extends StandardSchemaV1 | undefined,
94
+ const TErrorCode extends string = string,
95
+ const TQueryParameters extends string = string,
96
+ >(
97
+ config: FragnoRouteConfig<
98
+ TMethod,
99
+ TPath,
100
+ TInputSchema,
101
+ TOutputSchema,
102
+ TErrorCode,
103
+ TQueryParameters
104
+ > & { inputSchema: TInputSchema },
105
+ ): FragnoRouteConfig<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters>;
106
+
107
+ // implementation
108
+ export function defineRoute<
109
+ const TMethod extends HTTPMethod,
110
+ const TPath extends string,
111
+ const TInputSchema extends StandardSchemaV1 | undefined,
112
+ const TOutputSchema extends StandardSchemaV1 | undefined,
113
+ const TErrorCode extends string = string,
114
+ const TQueryParameters extends string = string,
115
+ >(
116
+ config: FragnoRouteConfig<
117
+ TMethod,
118
+ TPath,
119
+ TInputSchema,
120
+ TOutputSchema,
121
+ TErrorCode,
122
+ TQueryParameters
123
+ >,
124
+ ): FragnoRouteConfig<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters> {
125
+ return config;
126
+ }
127
+
128
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
129
+ export type EmptyObject = {}; //Record<string, never>;
130
+
131
+ export function defineRoutes<
132
+ TConfig = EmptyObject,
133
+ TDeps = EmptyObject,
134
+ TServices = EmptyObject,
135
+ >() {
136
+ return {
137
+ create: <
138
+ const TRoutes extends readonly FragnoRouteConfig<
139
+ HTTPMethod,
140
+ string,
141
+ StandardSchemaV1 | undefined,
142
+ StandardSchemaV1 | undefined,
143
+ string,
144
+ string
145
+ >[],
146
+ >(
147
+ fn: (context: RouteFactoryContext<TConfig, TDeps, TServices>) => TRoutes,
148
+ ): RouteFactory<TConfig, TDeps, TServices, TRoutes> => {
149
+ return fn;
150
+ },
151
+ };
152
+ }