@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
package/src/api/route.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { test, expect, expectTypeOf, describe } from "vitest";
|
|
2
|
-
import { defineRoute } from "./route";
|
|
2
|
+
import { defineRoute, defineRoutes, type ExtractFragmentServices } from "./route";
|
|
3
|
+
import { defineFragment } from "./fragment-builder";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
|
|
5
6
|
describe("defineRoute", () => {
|
|
@@ -174,3 +175,112 @@ describe("defineRoute", () => {
|
|
|
174
175
|
// Currently defineRoute doesn't enforce ValidPath constraints
|
|
175
176
|
});
|
|
176
177
|
});
|
|
178
|
+
|
|
179
|
+
describe("ExtractFragmentServices", () => {
|
|
180
|
+
test("extracts services from fragment with .providesService()", () => {
|
|
181
|
+
const fragment = defineFragment<{}>("test-fragment")
|
|
182
|
+
.withDependencies(() => ({ dep: "value" }))
|
|
183
|
+
.providesService(({ defineService }) =>
|
|
184
|
+
defineService({
|
|
185
|
+
getUserById: async (id: string) => ({ id, name: "John" }),
|
|
186
|
+
createUser: async (name: string) => ({ id: "123", name }),
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
type Services = ExtractFragmentServices<typeof fragment>;
|
|
191
|
+
|
|
192
|
+
expectTypeOf<Services>().toMatchObjectType<{
|
|
193
|
+
getUserById: (id: string) => Promise<{ id: string; name: string }>;
|
|
194
|
+
createUser: (name: string) => Promise<{ id: string; name: string }>;
|
|
195
|
+
}>();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("ExtractFragmentServices returns never for fragment without services", () => {
|
|
199
|
+
const fragment = defineFragment<{}>("test-fragment").withDependencies(() => ({ dep: "value" }));
|
|
200
|
+
|
|
201
|
+
type Services = ExtractFragmentServices<typeof fragment>;
|
|
202
|
+
|
|
203
|
+
// Fragment with no services should have empty services object
|
|
204
|
+
expectTypeOf<Services>().toEqualTypeOf<{}>();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("defineRoutes", () => {
|
|
209
|
+
test("defineRoutes extracts services correctly for route factory", () => {
|
|
210
|
+
const fragment = defineFragment<{}>("test-fragment").providesService(({ defineService }) =>
|
|
211
|
+
defineService({
|
|
212
|
+
getUserById: async (id: string) => ({ id, name: "John" }),
|
|
213
|
+
createUser: async (name: string) => ({ id: "123", name }),
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const routeFactory = defineRoutes(fragment).create(({ services, config, deps }) => {
|
|
218
|
+
// Type check that services are properly extracted
|
|
219
|
+
expectTypeOf(services).toMatchObjectType<{
|
|
220
|
+
getUserById: (id: string) => Promise<{ id: string; name: string }>;
|
|
221
|
+
createUser: (name: string) => Promise<{ id: string; name: string }>;
|
|
222
|
+
}>();
|
|
223
|
+
|
|
224
|
+
expectTypeOf(config).toEqualTypeOf<{}>();
|
|
225
|
+
expectTypeOf(deps).toEqualTypeOf<{}>();
|
|
226
|
+
|
|
227
|
+
return [
|
|
228
|
+
defineRoute({
|
|
229
|
+
method: "GET",
|
|
230
|
+
path: "/users/:id",
|
|
231
|
+
outputSchema: z.object({ id: z.string(), name: z.string() }),
|
|
232
|
+
handler: async ({ pathParams }, { json }) => {
|
|
233
|
+
// Services should be accessible here via closure
|
|
234
|
+
const user = await services.getUserById(pathParams.id);
|
|
235
|
+
return json(user);
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
];
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// routeFactory is a function that returns routes when called
|
|
242
|
+
expect(routeFactory).toBeDefined();
|
|
243
|
+
expect(typeof routeFactory).toBe("function");
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("defineRoutes with dependencies and services", () => {
|
|
247
|
+
const fragment = defineFragment<{ apiKey: string }>("auth-fragment")
|
|
248
|
+
.withDependencies(({ config }) => ({
|
|
249
|
+
authClient: { apiKey: config.apiKey },
|
|
250
|
+
}))
|
|
251
|
+
.providesService(({ deps, defineService }) =>
|
|
252
|
+
defineService({
|
|
253
|
+
validateToken: async (_token: string) => {
|
|
254
|
+
return { valid: true, apiKey: deps.authClient.apiKey };
|
|
255
|
+
},
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const routeFactory = defineRoutes(fragment).create(({ services, config, deps }) => {
|
|
260
|
+
// Type check all context properties
|
|
261
|
+
expectTypeOf(config).toEqualTypeOf<{ apiKey: string }>();
|
|
262
|
+
expectTypeOf(deps).toMatchObjectType<{ authClient: { apiKey: string } }>();
|
|
263
|
+
expectTypeOf(services).toMatchObjectType<{
|
|
264
|
+
validateToken: (token: string) => Promise<{ valid: boolean; apiKey: string }>;
|
|
265
|
+
}>();
|
|
266
|
+
|
|
267
|
+
return [
|
|
268
|
+
defineRoute({
|
|
269
|
+
method: "POST",
|
|
270
|
+
path: "/auth/validate",
|
|
271
|
+
inputSchema: z.object({ token: z.string() }),
|
|
272
|
+
outputSchema: z.object({ valid: z.boolean() }),
|
|
273
|
+
handler: async ({ input }, { json }) => {
|
|
274
|
+
const { token } = await input!.valid();
|
|
275
|
+
const result = await services.validateToken(token);
|
|
276
|
+
return json({ valid: result.valid });
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
];
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// routeFactory is a function that returns routes when called
|
|
283
|
+
expect(routeFactory).toBeDefined();
|
|
284
|
+
expect(typeof routeFactory).toBe("function");
|
|
285
|
+
});
|
|
286
|
+
});
|
package/src/api/route.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
-
import type { FragnoRouteConfig, HTTPMethod } from "./api";
|
|
2
|
+
import type { FragnoRouteConfig, HTTPMethod, RequestThisContext } from "./api";
|
|
3
|
+
import type { FragmentDefinition } from "./fragment-builder";
|
|
3
4
|
|
|
4
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
-
export type AnyFragnoRouteConfig = FragnoRouteConfig<HTTPMethod, string, any, any, any, any>;
|
|
6
|
+
export type AnyFragnoRouteConfig = FragnoRouteConfig<HTTPMethod, string, any, any, any, any, any>;
|
|
7
|
+
|
|
8
|
+
export type AnyFragmentBuilder = {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
readonly definition: FragmentDefinition<any, any, any, any, any, any, any>;
|
|
11
|
+
};
|
|
6
12
|
|
|
7
13
|
export interface RouteFactoryContext<TConfig, TDeps, TServices> {
|
|
8
14
|
config: TConfig;
|
|
@@ -20,7 +26,8 @@ export type RouteFactory<
|
|
|
20
26
|
StandardSchemaV1 | undefined,
|
|
21
27
|
StandardSchemaV1 | undefined,
|
|
22
28
|
string,
|
|
23
|
-
string
|
|
29
|
+
string,
|
|
30
|
+
RequestThisContext
|
|
24
31
|
>[],
|
|
25
32
|
> = (context: RouteFactoryContext<TConfig, TDeps, TServices>) => TRoutes;
|
|
26
33
|
|
|
@@ -74,6 +81,7 @@ export function defineRoute<
|
|
|
74
81
|
const TOutputSchema extends StandardSchemaV1 | undefined,
|
|
75
82
|
const TErrorCode extends string = string,
|
|
76
83
|
const TQueryParameters extends string = string,
|
|
84
|
+
const TThisContext extends RequestThisContext = RequestThisContext,
|
|
77
85
|
>(
|
|
78
86
|
config: FragnoRouteConfig<
|
|
79
87
|
TMethod,
|
|
@@ -81,9 +89,18 @@ export function defineRoute<
|
|
|
81
89
|
undefined,
|
|
82
90
|
TOutputSchema,
|
|
83
91
|
TErrorCode,
|
|
84
|
-
TQueryParameters
|
|
92
|
+
TQueryParameters,
|
|
93
|
+
TThisContext
|
|
85
94
|
> & { inputSchema?: undefined },
|
|
86
|
-
): FragnoRouteConfig<
|
|
95
|
+
): FragnoRouteConfig<
|
|
96
|
+
TMethod,
|
|
97
|
+
TPath,
|
|
98
|
+
undefined,
|
|
99
|
+
TOutputSchema,
|
|
100
|
+
TErrorCode,
|
|
101
|
+
TQueryParameters,
|
|
102
|
+
TThisContext
|
|
103
|
+
>;
|
|
87
104
|
|
|
88
105
|
// Overload for routes with inputSchema
|
|
89
106
|
export function defineRoute<
|
|
@@ -93,6 +110,7 @@ export function defineRoute<
|
|
|
93
110
|
const TOutputSchema extends StandardSchemaV1 | undefined,
|
|
94
111
|
const TErrorCode extends string = string,
|
|
95
112
|
const TQueryParameters extends string = string,
|
|
113
|
+
const TThisContext extends RequestThisContext = RequestThisContext,
|
|
96
114
|
>(
|
|
97
115
|
config: FragnoRouteConfig<
|
|
98
116
|
TMethod,
|
|
@@ -100,9 +118,18 @@ export function defineRoute<
|
|
|
100
118
|
TInputSchema,
|
|
101
119
|
TOutputSchema,
|
|
102
120
|
TErrorCode,
|
|
103
|
-
TQueryParameters
|
|
121
|
+
TQueryParameters,
|
|
122
|
+
TThisContext
|
|
104
123
|
> & { inputSchema: TInputSchema },
|
|
105
|
-
): FragnoRouteConfig<
|
|
124
|
+
): FragnoRouteConfig<
|
|
125
|
+
TMethod,
|
|
126
|
+
TPath,
|
|
127
|
+
TInputSchema,
|
|
128
|
+
TOutputSchema,
|
|
129
|
+
TErrorCode,
|
|
130
|
+
TQueryParameters,
|
|
131
|
+
TThisContext
|
|
132
|
+
>;
|
|
106
133
|
|
|
107
134
|
// implementation
|
|
108
135
|
export function defineRoute<
|
|
@@ -112,6 +139,7 @@ export function defineRoute<
|
|
|
112
139
|
const TOutputSchema extends StandardSchemaV1 | undefined,
|
|
113
140
|
const TErrorCode extends string = string,
|
|
114
141
|
const TQueryParameters extends string = string,
|
|
142
|
+
const TThisContext extends RequestThisContext = RequestThisContext,
|
|
115
143
|
>(
|
|
116
144
|
config: FragnoRouteConfig<
|
|
117
145
|
TMethod,
|
|
@@ -119,13 +147,262 @@ export function defineRoute<
|
|
|
119
147
|
TInputSchema,
|
|
120
148
|
TOutputSchema,
|
|
121
149
|
TErrorCode,
|
|
122
|
-
TQueryParameters
|
|
150
|
+
TQueryParameters,
|
|
151
|
+
TThisContext
|
|
123
152
|
>,
|
|
124
|
-
): FragnoRouteConfig<
|
|
153
|
+
): FragnoRouteConfig<
|
|
154
|
+
TMethod,
|
|
155
|
+
TPath,
|
|
156
|
+
TInputSchema,
|
|
157
|
+
TOutputSchema,
|
|
158
|
+
TErrorCode,
|
|
159
|
+
TQueryParameters,
|
|
160
|
+
TThisContext
|
|
161
|
+
> {
|
|
125
162
|
return config;
|
|
126
163
|
}
|
|
127
164
|
|
|
128
|
-
|
|
165
|
+
// Type helpers to extract types from FragmentBuilder or DatabaseFragmentBuilder
|
|
166
|
+
// DatabaseFragmentBuilder has 6 type parameters: TSchema, TConfig, TDeps, TServices, TUsedServices, TProvidedServices
|
|
167
|
+
// FragmentBuilder has 6 type parameters: TConfig, TDeps, TServices, TAdditionalContext, TUsedServices, TProvidedServices
|
|
168
|
+
|
|
169
|
+
// Helper to get the return type of the definition getter
|
|
170
|
+
// Use T['definition'] to access the property type
|
|
171
|
+
type GetDefinition<T> = T extends { definition: unknown } ? T["definition"] : never;
|
|
172
|
+
|
|
173
|
+
// Extract config
|
|
174
|
+
export type ExtractFragmentConfig<T> =
|
|
175
|
+
GetDefinition<T> extends FragmentDefinition<
|
|
176
|
+
infer TConfig,
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
178
|
+
any,
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
+
any,
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
+
any,
|
|
183
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
184
|
+
any,
|
|
185
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
186
|
+
any
|
|
187
|
+
>
|
|
188
|
+
? TConfig
|
|
189
|
+
: never;
|
|
190
|
+
|
|
191
|
+
// Extract deps
|
|
192
|
+
export type ExtractFragmentDeps<T> =
|
|
193
|
+
GetDefinition<T> extends FragmentDefinition<
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
195
|
+
any,
|
|
196
|
+
infer TDeps,
|
|
197
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
|
+
any,
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
200
|
+
any,
|
|
201
|
+
infer TUsedServices,
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
203
|
+
any
|
|
204
|
+
>
|
|
205
|
+
? TDeps & TUsedServices
|
|
206
|
+
: never;
|
|
207
|
+
|
|
208
|
+
// Helper to recursively bind services (removes `this` parameter from methods)
|
|
209
|
+
type OmitThisParameter<T> = T extends (this: infer _This, ...args: infer A) => infer R
|
|
210
|
+
? (...args: A) => R
|
|
211
|
+
: T;
|
|
212
|
+
|
|
213
|
+
type BoundServicesLocal<T> = {
|
|
214
|
+
[K in keyof T]: T[K] extends (...args: never[]) => unknown
|
|
215
|
+
? OmitThisParameter<T[K]>
|
|
216
|
+
: T[K] extends Record<string, unknown>
|
|
217
|
+
? BoundServicesLocal<T[K]>
|
|
218
|
+
: T[K];
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Extract services (merges both withServices and providesService)
|
|
222
|
+
// First try to extract from $types if available (for DatabaseFragmentBuilder)
|
|
223
|
+
// Otherwise fall back to extracting from definition
|
|
224
|
+
export type ExtractFragmentServices<T> = T extends {
|
|
225
|
+
$types: { services: infer S; providedServices: infer P };
|
|
226
|
+
}
|
|
227
|
+
? BoundServicesLocal<S & P>
|
|
228
|
+
: GetDefinition<T> extends FragmentDefinition<
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
230
|
+
any,
|
|
231
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
232
|
+
any,
|
|
233
|
+
infer TServices,
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
235
|
+
any,
|
|
236
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
237
|
+
any,
|
|
238
|
+
infer TProvidedServices
|
|
239
|
+
>
|
|
240
|
+
? TServices & TProvidedServices
|
|
241
|
+
: never;
|
|
242
|
+
|
|
243
|
+
// Extract the this context type from the fragment builder
|
|
244
|
+
export type ExtractThisContext<T> =
|
|
245
|
+
GetDefinition<T> extends FragmentDefinition<
|
|
246
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
|
+
any,
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
249
|
+
any,
|
|
250
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
251
|
+
any,
|
|
252
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
253
|
+
any,
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
255
|
+
any,
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
257
|
+
any,
|
|
258
|
+
infer TThisContext
|
|
259
|
+
>
|
|
260
|
+
? TThisContext
|
|
261
|
+
: RequestThisContext;
|
|
262
|
+
|
|
263
|
+
// Overload that infers types from FragmentBuilder or DatabaseFragmentBuilder (runtime value)
|
|
264
|
+
export function defineRoutes<const TFragmentBuilder extends AnyFragmentBuilder>(
|
|
265
|
+
fragmentBuilder: TFragmentBuilder,
|
|
266
|
+
): {
|
|
267
|
+
create: <
|
|
268
|
+
const TRoutes extends readonly FragnoRouteConfig<
|
|
269
|
+
HTTPMethod,
|
|
270
|
+
string,
|
|
271
|
+
StandardSchemaV1 | undefined,
|
|
272
|
+
StandardSchemaV1 | undefined,
|
|
273
|
+
string,
|
|
274
|
+
string,
|
|
275
|
+
ExtractThisContext<TFragmentBuilder>
|
|
276
|
+
>[],
|
|
277
|
+
>(
|
|
278
|
+
fn: (
|
|
279
|
+
context: RouteFactoryContext<
|
|
280
|
+
ExtractFragmentConfig<TFragmentBuilder>,
|
|
281
|
+
ExtractFragmentDeps<TFragmentBuilder>,
|
|
282
|
+
ExtractFragmentServices<TFragmentBuilder>
|
|
283
|
+
> & {
|
|
284
|
+
defineRoute: <
|
|
285
|
+
const TMethod extends HTTPMethod,
|
|
286
|
+
const TPath extends string,
|
|
287
|
+
const TInputSchema extends StandardSchemaV1 | undefined,
|
|
288
|
+
const TOutputSchema extends StandardSchemaV1 | undefined,
|
|
289
|
+
const TErrorCode extends string = string,
|
|
290
|
+
const TQueryParameters extends string = string,
|
|
291
|
+
>(
|
|
292
|
+
config: FragnoRouteConfig<
|
|
293
|
+
TMethod,
|
|
294
|
+
TPath,
|
|
295
|
+
TInputSchema,
|
|
296
|
+
TOutputSchema,
|
|
297
|
+
TErrorCode,
|
|
298
|
+
TQueryParameters,
|
|
299
|
+
ExtractThisContext<TFragmentBuilder>
|
|
300
|
+
>,
|
|
301
|
+
) => FragnoRouteConfig<
|
|
302
|
+
TMethod,
|
|
303
|
+
TPath,
|
|
304
|
+
TInputSchema,
|
|
305
|
+
TOutputSchema,
|
|
306
|
+
TErrorCode,
|
|
307
|
+
TQueryParameters,
|
|
308
|
+
ExtractThisContext<TFragmentBuilder>
|
|
309
|
+
>;
|
|
310
|
+
},
|
|
311
|
+
) => TRoutes,
|
|
312
|
+
) => RouteFactory<
|
|
313
|
+
ExtractFragmentConfig<TFragmentBuilder>,
|
|
314
|
+
ExtractFragmentDeps<TFragmentBuilder>,
|
|
315
|
+
ExtractFragmentServices<TFragmentBuilder>,
|
|
316
|
+
TRoutes
|
|
317
|
+
>;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Overload that infers types from FragmentBuilder or DatabaseFragmentBuilder (type parameter)
|
|
321
|
+
export function defineRoutes<const TFragmentBuilder extends AnyFragmentBuilder>(): {
|
|
322
|
+
create: <
|
|
323
|
+
const TRoutes extends readonly FragnoRouteConfig<
|
|
324
|
+
HTTPMethod,
|
|
325
|
+
string,
|
|
326
|
+
StandardSchemaV1 | undefined,
|
|
327
|
+
StandardSchemaV1 | undefined,
|
|
328
|
+
string,
|
|
329
|
+
string,
|
|
330
|
+
ExtractThisContext<TFragmentBuilder>
|
|
331
|
+
>[],
|
|
332
|
+
>(
|
|
333
|
+
fn: (
|
|
334
|
+
context: RouteFactoryContext<
|
|
335
|
+
ExtractFragmentConfig<TFragmentBuilder>,
|
|
336
|
+
ExtractFragmentDeps<TFragmentBuilder>,
|
|
337
|
+
ExtractFragmentServices<TFragmentBuilder>
|
|
338
|
+
> & {
|
|
339
|
+
defineRoute: <
|
|
340
|
+
const TMethod extends HTTPMethod,
|
|
341
|
+
const TPath extends string,
|
|
342
|
+
const TInputSchema extends StandardSchemaV1 | undefined,
|
|
343
|
+
const TOutputSchema extends StandardSchemaV1 | undefined,
|
|
344
|
+
const TErrorCode extends string = string,
|
|
345
|
+
const TQueryParameters extends string = string,
|
|
346
|
+
>(
|
|
347
|
+
config: FragnoRouteConfig<
|
|
348
|
+
TMethod,
|
|
349
|
+
TPath,
|
|
350
|
+
TInputSchema,
|
|
351
|
+
TOutputSchema,
|
|
352
|
+
TErrorCode,
|
|
353
|
+
TQueryParameters,
|
|
354
|
+
ExtractThisContext<TFragmentBuilder>
|
|
355
|
+
>,
|
|
356
|
+
) => FragnoRouteConfig<
|
|
357
|
+
TMethod,
|
|
358
|
+
TPath,
|
|
359
|
+
TInputSchema,
|
|
360
|
+
TOutputSchema,
|
|
361
|
+
TErrorCode,
|
|
362
|
+
TQueryParameters,
|
|
363
|
+
ExtractThisContext<TFragmentBuilder>
|
|
364
|
+
>;
|
|
365
|
+
},
|
|
366
|
+
) => TRoutes,
|
|
367
|
+
) => RouteFactory<
|
|
368
|
+
ExtractFragmentConfig<TFragmentBuilder>,
|
|
369
|
+
ExtractFragmentDeps<TFragmentBuilder>,
|
|
370
|
+
ExtractFragmentServices<TFragmentBuilder>,
|
|
371
|
+
TRoutes
|
|
372
|
+
>;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// Overload that accepts manual type parameters
|
|
376
|
+
export function defineRoutes<TConfig = {}, TDeps = {}, TServices = {}>(): {
|
|
377
|
+
create: <
|
|
378
|
+
const TRoutes extends readonly FragnoRouteConfig<
|
|
379
|
+
HTTPMethod,
|
|
380
|
+
string,
|
|
381
|
+
StandardSchemaV1 | undefined,
|
|
382
|
+
StandardSchemaV1 | undefined,
|
|
383
|
+
string,
|
|
384
|
+
string,
|
|
385
|
+
RequestThisContext
|
|
386
|
+
>[],
|
|
387
|
+
>(
|
|
388
|
+
fn: (
|
|
389
|
+
context: RouteFactoryContext<TConfig, TDeps, TServices> & {
|
|
390
|
+
defineRoute: typeof defineRoute;
|
|
391
|
+
},
|
|
392
|
+
) => TRoutes,
|
|
393
|
+
) => RouteFactory<TConfig, TDeps, TServices, TRoutes>;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Implementation
|
|
397
|
+
export function defineRoutes<
|
|
398
|
+
const TConfig = {},
|
|
399
|
+
const TDeps = {},
|
|
400
|
+
const TServices = {},
|
|
401
|
+
const TFragmentBuilder extends AnyFragmentBuilder | undefined = undefined,
|
|
402
|
+
>(
|
|
403
|
+
// Parameter is only used for type inference, not runtime
|
|
404
|
+
_fragmentBuilder?: TFragmentBuilder,
|
|
405
|
+
) {
|
|
129
406
|
return {
|
|
130
407
|
create: <
|
|
131
408
|
const TRoutes extends readonly FragnoRouteConfig<
|
|
@@ -134,12 +411,44 @@ export function defineRoutes<TConfig = {}, TDeps = {}, TServices = {}>() {
|
|
|
134
411
|
StandardSchemaV1 | undefined,
|
|
135
412
|
StandardSchemaV1 | undefined,
|
|
136
413
|
string,
|
|
137
|
-
string
|
|
414
|
+
string,
|
|
415
|
+
RequestThisContext
|
|
138
416
|
>[],
|
|
139
417
|
>(
|
|
140
|
-
fn: (
|
|
141
|
-
|
|
142
|
-
|
|
418
|
+
fn: (
|
|
419
|
+
context: RouteFactoryContext<
|
|
420
|
+
TFragmentBuilder extends AnyFragmentBuilder
|
|
421
|
+
? ExtractFragmentConfig<TFragmentBuilder>
|
|
422
|
+
: TConfig,
|
|
423
|
+
TFragmentBuilder extends AnyFragmentBuilder
|
|
424
|
+
? ExtractFragmentDeps<TFragmentBuilder>
|
|
425
|
+
: TDeps,
|
|
426
|
+
TFragmentBuilder extends AnyFragmentBuilder
|
|
427
|
+
? ExtractFragmentServices<TFragmentBuilder>
|
|
428
|
+
: TServices
|
|
429
|
+
> & {
|
|
430
|
+
defineRoute: typeof defineRoute;
|
|
431
|
+
},
|
|
432
|
+
) => TRoutes,
|
|
433
|
+
): RouteFactory<
|
|
434
|
+
TFragmentBuilder extends AnyFragmentBuilder
|
|
435
|
+
? ExtractFragmentConfig<TFragmentBuilder>
|
|
436
|
+
: TConfig,
|
|
437
|
+
TFragmentBuilder extends AnyFragmentBuilder ? ExtractFragmentDeps<TFragmentBuilder> : TDeps,
|
|
438
|
+
TFragmentBuilder extends AnyFragmentBuilder
|
|
439
|
+
? ExtractFragmentServices<TFragmentBuilder>
|
|
440
|
+
: TServices,
|
|
441
|
+
TRoutes
|
|
442
|
+
> => {
|
|
443
|
+
// Create a wrapper around the callback that adds the defineRoute function
|
|
444
|
+
return (ctx: RouteFactoryContext<unknown, unknown, unknown>) => {
|
|
445
|
+
const extendedCtx = {
|
|
446
|
+
...ctx,
|
|
447
|
+
defineRoute,
|
|
448
|
+
};
|
|
449
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
450
|
+
return fn(extendedCtx as any);
|
|
451
|
+
};
|
|
143
452
|
},
|
|
144
453
|
};
|
|
145
454
|
}
|
package/src/mod.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
defineFragment,
|
|
3
|
+
FragmentBuilder,
|
|
4
|
+
type FragmentDefinition,
|
|
5
|
+
type RouteHandler,
|
|
6
|
+
} from "./api/fragment-builder";
|
|
2
7
|
|
|
3
8
|
export {
|
|
4
9
|
createFragment,
|
|
10
|
+
instantiateFragment,
|
|
11
|
+
FragmentInstantiationBuilder,
|
|
5
12
|
type FragnoFragmentSharedConfig,
|
|
6
13
|
type FragnoPublicConfig,
|
|
7
14
|
type FragnoPublicClientConfig,
|
|
@@ -18,3 +25,6 @@ export {
|
|
|
18
25
|
type AnyRouteOrFactory,
|
|
19
26
|
type FlattenRouteFactories,
|
|
20
27
|
} from "./api/route";
|
|
28
|
+
|
|
29
|
+
export { RequestInputContext } from "./api/request-input-context";
|
|
30
|
+
export { RequestOutputContext } from "./api/request-output-context";
|
package/src/test/test.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, expectTypeOf } from "vitest";
|
|
1
|
+
import { describe, it, expect, expectTypeOf, assert } from "vitest";
|
|
2
2
|
import { createFragmentForTest } from "./test";
|
|
3
3
|
import { defineFragment } from "../api/fragment-builder";
|
|
4
4
|
import { defineRoute, defineRoutes } from "../api/route";
|
|
@@ -46,9 +46,11 @@ describe("createFragmentForTest", () => {
|
|
|
46
46
|
.withDependencies(({ config }) => ({
|
|
47
47
|
client: { apiKey: config.apiKey },
|
|
48
48
|
}))
|
|
49
|
-
.
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
.providesService(({ deps, defineService }) =>
|
|
50
|
+
defineService({
|
|
51
|
+
getApiKey: () => deps.client.apiKey,
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
52
54
|
|
|
53
55
|
const testFragment = createFragmentForTest(fragment, [], {
|
|
54
56
|
config: { apiKey: "test-key" },
|
|
@@ -62,9 +64,11 @@ describe("createFragmentForTest", () => {
|
|
|
62
64
|
.withDependencies(({ config }) => ({
|
|
63
65
|
client: { apiKey: config.apiKey },
|
|
64
66
|
}))
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
.providesService(({ deps, defineService }) =>
|
|
68
|
+
defineService({
|
|
69
|
+
getApiKey: () => deps.client.apiKey,
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
68
72
|
|
|
69
73
|
const testFragment = createFragmentForTest(fragment, [], {
|
|
70
74
|
config: { apiKey: "test-key" },
|
|
@@ -81,9 +85,11 @@ describe("createFragmentForTest", () => {
|
|
|
81
85
|
|
|
82
86
|
const fragment = defineFragment<Config>("test")
|
|
83
87
|
.withDependencies(() => ({ dep: "value" }))
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
.providesService(({ config, defineService }) =>
|
|
89
|
+
defineService({
|
|
90
|
+
multiply: (x: number) => x * config.multiplier,
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
87
93
|
|
|
88
94
|
const routeFactory = defineRoutes<Config, Deps, Services>().create(({ services }) => [
|
|
89
95
|
defineRoute({
|
|
@@ -233,7 +239,7 @@ describe("fragment.callRoute", () => {
|
|
|
233
239
|
});
|
|
234
240
|
|
|
235
241
|
it("should handle route factory created with defineRoutes", async () => {
|
|
236
|
-
const fragment = defineFragment<{ apiKey: string }>("test").
|
|
242
|
+
const fragment = defineFragment<{ apiKey: string }>("test").providesService(() => ({
|
|
237
243
|
getGreeting: (name: string) => `Hello, ${name}!`,
|
|
238
244
|
getCount: () => 42,
|
|
239
245
|
}));
|
|
@@ -270,10 +276,9 @@ describe("fragment.callRoute", () => {
|
|
|
270
276
|
pathParams: { name: "World" },
|
|
271
277
|
});
|
|
272
278
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
279
|
+
console.log(greetingResponse);
|
|
280
|
+
assert(greetingResponse.type === "json");
|
|
281
|
+
expect(greetingResponse.data).toEqual({ message: "Hello, World!" });
|
|
277
282
|
|
|
278
283
|
// Test second route
|
|
279
284
|
const countResponse = await testFragment.callRoute("GET", "/count");
|