@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.
Files changed (79) hide show
  1. package/.turbo/turbo-build.log +45 -53
  2. package/CHANGELOG.md +6 -0
  3. package/dist/api/api.d.ts +2 -2
  4. package/dist/api/api.js +3 -2
  5. package/dist/api/fragment-builder.d.ts +2 -4
  6. package/dist/api/fragment-builder.js +1 -1
  7. package/dist/api/fragment-instantiation.d.ts +2 -4
  8. package/dist/api/fragment-instantiation.js +3 -5
  9. package/dist/api/route.d.ts +2 -3
  10. package/dist/api/route.js +1 -1
  11. package/dist/api-BFrUCIsF.d.ts +963 -0
  12. package/dist/api-BFrUCIsF.d.ts.map +1 -0
  13. package/dist/client/client.d.ts +1 -3
  14. package/dist/client/client.js +4 -5
  15. package/dist/client/client.svelte.d.ts +2 -3
  16. package/dist/client/client.svelte.d.ts.map +1 -1
  17. package/dist/client/client.svelte.js +4 -5
  18. package/dist/client/client.svelte.js.map +1 -1
  19. package/dist/client/react.d.ts +2 -3
  20. package/dist/client/react.d.ts.map +1 -1
  21. package/dist/client/react.js +4 -5
  22. package/dist/client/react.js.map +1 -1
  23. package/dist/client/solid.d.ts +2 -3
  24. package/dist/client/solid.d.ts.map +1 -1
  25. package/dist/client/solid.js +4 -5
  26. package/dist/client/solid.js.map +1 -1
  27. package/dist/client/vanilla.d.ts +2 -3
  28. package/dist/client/vanilla.d.ts.map +1 -1
  29. package/dist/client/vanilla.js +4 -5
  30. package/dist/client/vanilla.js.map +1 -1
  31. package/dist/client/vue.d.ts +2 -3
  32. package/dist/client/vue.d.ts.map +1 -1
  33. package/dist/client/vue.js +4 -5
  34. package/dist/client/vue.js.map +1 -1
  35. package/dist/{client-C5LsYHEI.js → client-DAFHcKqA.js} +4 -4
  36. package/dist/{client-C5LsYHEI.js.map → client-DAFHcKqA.js.map} +1 -1
  37. package/dist/fragment-builder-Boh2vNHq.js +108 -0
  38. package/dist/fragment-builder-Boh2vNHq.js.map +1 -0
  39. package/dist/fragment-instantiation-DUT-HLl1.js +898 -0
  40. package/dist/fragment-instantiation-DUT-HLl1.js.map +1 -0
  41. package/dist/integrations/react-ssr.js +1 -1
  42. package/dist/mod.d.ts +2 -4
  43. package/dist/mod.js +4 -6
  44. package/dist/{route-C5Uryylh.js → route-C4CyNHkC.js} +8 -3
  45. package/dist/route-C4CyNHkC.js.map +1 -0
  46. package/dist/{ssr-BByDVfFD.js → ssr-kyKI7pqH.js} +1 -1
  47. package/dist/{ssr-BByDVfFD.js.map → ssr-kyKI7pqH.js.map} +1 -1
  48. package/dist/test/test.d.ts +6 -7
  49. package/dist/test/test.d.ts.map +1 -1
  50. package/dist/test/test.js +9 -7
  51. package/dist/test/test.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/api/api.ts +45 -6
  54. package/src/api/fragment-builder.ts +463 -25
  55. package/src/api/fragment-instantiation.test.ts +249 -7
  56. package/src/api/fragment-instantiation.ts +283 -16
  57. package/src/api/fragment-services.test.ts +462 -0
  58. package/src/api/fragment.test.ts +65 -17
  59. package/src/api/request-middleware.test.ts +6 -3
  60. package/src/api/route.test.ts +111 -1
  61. package/src/api/route.ts +323 -14
  62. package/src/mod.ts +11 -1
  63. package/src/test/test.test.ts +20 -15
  64. package/src/test/test.ts +48 -9
  65. package/dist/api-BWN97TOr.d.ts +0 -377
  66. package/dist/api-BWN97TOr.d.ts.map +0 -1
  67. package/dist/api-DngJDcmO.js +0 -54
  68. package/dist/api-DngJDcmO.js.map +0 -1
  69. package/dist/fragment-builder-DOnCVBqc.js +0 -47
  70. package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
  71. package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
  72. package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
  73. package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
  74. package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
  75. package/dist/request-output-context-CdIjwmEN.js +0 -320
  76. package/dist/request-output-context-CdIjwmEN.js.map +0 -1
  77. package/dist/route-Bl9Zr1Yv.d.ts +0 -26
  78. package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
  79. package/dist/route-C5Uryylh.js.map +0 -1
@@ -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<TMethod, TPath, undefined, TOutputSchema, TErrorCode, TQueryParameters>;
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<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters>;
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<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters> {
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
- export function defineRoutes<TConfig = {}, TDeps = {}, TServices = {}>() {
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: (context: RouteFactoryContext<TConfig, TDeps, TServices>) => TRoutes,
141
- ): RouteFactory<TConfig, TDeps, TServices, TRoutes> => {
142
- return fn;
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 { defineFragment, FragmentBuilder, type FragmentDefinition } from "./api/fragment-builder";
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";
@@ -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
- .withServices(({ deps }) => ({
50
- getApiKey: () => deps.client.apiKey,
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
- .withServices(({ deps }) => ({
66
- getApiKey: () => deps.client.apiKey,
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
- .withServices(({ config }) => ({
85
- multiply: (x: number) => x * config.multiplier,
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").withServices(() => ({
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
- expect(greetingResponse.type).toBe("json");
274
- if (greetingResponse.type === "json") {
275
- expect(greetingResponse.data).toEqual({ message: "Hello, World!" });
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");