@fragno-dev/core 0.2.0 → 0.2.2

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 (146) hide show
  1. package/.turbo/turbo-build.log +72 -62
  2. package/CHANGELOG.md +28 -0
  3. package/dist/api/api.d.ts +3 -2
  4. package/dist/api/api.d.ts.map +1 -1
  5. package/dist/api/api.js +2 -1
  6. package/dist/api/api.js.map +1 -1
  7. package/dist/api/bind-services.d.ts +0 -1
  8. package/dist/api/bind-services.d.ts.map +1 -1
  9. package/dist/api/bind-services.js.map +1 -1
  10. package/dist/api/error.d.ts.map +1 -1
  11. package/dist/api/error.js.map +1 -1
  12. package/dist/api/fragment-definition-builder.d.ts +26 -44
  13. package/dist/api/fragment-definition-builder.d.ts.map +1 -1
  14. package/dist/api/fragment-definition-builder.js +15 -22
  15. package/dist/api/fragment-definition-builder.js.map +1 -1
  16. package/dist/api/fragment-instantiator.d.ts +51 -37
  17. package/dist/api/fragment-instantiator.d.ts.map +1 -1
  18. package/dist/api/fragment-instantiator.js +74 -69
  19. package/dist/api/fragment-instantiator.js.map +1 -1
  20. package/dist/api/request-context-storage.d.ts +4 -0
  21. package/dist/api/request-context-storage.d.ts.map +1 -1
  22. package/dist/api/request-context-storage.js +6 -0
  23. package/dist/api/request-context-storage.js.map +1 -1
  24. package/dist/api/request-input-context.d.ts.map +1 -1
  25. package/dist/api/request-input-context.js.map +1 -1
  26. package/dist/api/request-middleware.d.ts +1 -1
  27. package/dist/api/request-middleware.d.ts.map +1 -1
  28. package/dist/api/request-middleware.js.map +1 -1
  29. package/dist/api/request-output-context.d.ts +1 -1
  30. package/dist/api/request-output-context.d.ts.map +1 -1
  31. package/dist/api/request-output-context.js.map +1 -1
  32. package/dist/api/route-caller.d.ts +30 -0
  33. package/dist/api/route-caller.d.ts.map +1 -0
  34. package/dist/api/route-caller.js +63 -0
  35. package/dist/api/route-caller.js.map +1 -0
  36. package/dist/api/route-handler-input-options.d.ts.map +1 -1
  37. package/dist/api/route.d.ts +1 -1
  38. package/dist/api/route.d.ts.map +1 -1
  39. package/dist/api/route.js.map +1 -1
  40. package/dist/api/shared-types.d.ts.map +1 -1
  41. package/dist/client/client-error.d.ts.map +1 -1
  42. package/dist/client/client-error.js.map +1 -1
  43. package/dist/client/client.d.ts +91 -52
  44. package/dist/client/client.d.ts.map +1 -1
  45. package/dist/client/client.js +25 -9
  46. package/dist/client/client.js.map +1 -1
  47. package/dist/client/client.svelte.d.ts +6 -5
  48. package/dist/client/client.svelte.d.ts.map +1 -1
  49. package/dist/client/client.svelte.js +10 -2
  50. package/dist/client/client.svelte.js.map +1 -1
  51. package/dist/client/internal/ndjson-streaming.js.map +1 -1
  52. package/dist/client/react.d.ts +5 -4
  53. package/dist/client/react.d.ts.map +1 -1
  54. package/dist/client/react.js +104 -12
  55. package/dist/client/react.js.map +1 -1
  56. package/dist/client/solid.d.ts +7 -5
  57. package/dist/client/solid.d.ts.map +1 -1
  58. package/dist/client/solid.js +23 -9
  59. package/dist/client/solid.js.map +1 -1
  60. package/dist/client/vanilla.d.ts +16 -4
  61. package/dist/client/vanilla.d.ts.map +1 -1
  62. package/dist/client/vanilla.js +21 -1
  63. package/dist/client/vanilla.js.map +1 -1
  64. package/dist/client/vue.d.ts +7 -5
  65. package/dist/client/vue.d.ts.map +1 -1
  66. package/dist/client/vue.js +18 -10
  67. package/dist/client/vue.js.map +1 -1
  68. package/dist/id.d.ts +2 -0
  69. package/dist/id.js +3 -0
  70. package/dist/internal/cuid.d.ts +16 -0
  71. package/dist/internal/cuid.d.ts.map +1 -0
  72. package/dist/internal/cuid.js +82 -0
  73. package/dist/internal/cuid.js.map +1 -0
  74. package/dist/mod-client.d.ts +5 -4
  75. package/dist/mod-client.d.ts.map +1 -1
  76. package/dist/mod-client.js +7 -5
  77. package/dist/mod-client.js.map +1 -1
  78. package/dist/mod.d.ts +6 -5
  79. package/dist/mod.js +2 -1
  80. package/dist/runtime.js +1 -1
  81. package/dist/runtime.js.map +1 -1
  82. package/dist/test/test.d.ts +6 -6
  83. package/dist/test/test.d.ts.map +1 -1
  84. package/dist/test/test.js.map +1 -1
  85. package/dist/util/ssr.js.map +1 -1
  86. package/package.json +24 -40
  87. package/src/api/api.test.ts +3 -1
  88. package/src/api/api.ts +6 -0
  89. package/src/api/bind-services.ts +0 -5
  90. package/src/api/error.ts +1 -0
  91. package/src/api/fragment-definition-builder.extend.test.ts +2 -1
  92. package/src/api/fragment-definition-builder.test.ts +2 -1
  93. package/src/api/fragment-definition-builder.ts +49 -124
  94. package/src/api/fragment-instantiator.test.ts +92 -233
  95. package/src/api/fragment-instantiator.ts +228 -196
  96. package/src/api/fragment-services.test.ts +1 -0
  97. package/src/api/internal/path-runtime.test.ts +1 -0
  98. package/src/api/internal/path-type.test.ts +3 -1
  99. package/src/api/internal/route.test.ts +1 -0
  100. package/src/api/request-context-storage.ts +7 -0
  101. package/src/api/request-input-context.test.ts +4 -2
  102. package/src/api/request-input-context.ts +2 -1
  103. package/src/api/request-middleware.test.ts +9 -14
  104. package/src/api/request-middleware.ts +3 -2
  105. package/src/api/request-output-context.test.ts +3 -1
  106. package/src/api/request-output-context.ts +2 -1
  107. package/src/api/route-caller.test.ts +195 -0
  108. package/src/api/route-caller.ts +167 -0
  109. package/src/api/route-handler-input-options.ts +2 -1
  110. package/src/api/route.test.ts +4 -2
  111. package/src/api/route.ts +2 -1
  112. package/src/api/shared-types.ts +2 -1
  113. package/src/client/client-builder.test.ts +4 -2
  114. package/src/client/client-error.test.ts +2 -1
  115. package/src/client/client-error.ts +1 -1
  116. package/src/client/client-types.test.ts +19 -5
  117. package/src/client/client.ssr.test.ts +6 -4
  118. package/src/client/client.svelte.test.ts +18 -9
  119. package/src/client/client.svelte.ts +38 -13
  120. package/src/client/client.test.ts +49 -10
  121. package/src/client/client.ts +291 -141
  122. package/src/client/internal/ndjson-streaming.test.ts +6 -3
  123. package/src/client/internal/ndjson-streaming.ts +1 -0
  124. package/src/client/react.test.ts +176 -6
  125. package/src/client/react.ts +226 -31
  126. package/src/client/solid.test.ts +29 -5
  127. package/src/client/solid.ts +60 -22
  128. package/src/client/vanilla.test.ts +148 -6
  129. package/src/client/vanilla.ts +63 -9
  130. package/src/client/vue.test.ts +223 -84
  131. package/src/client/vue.ts +57 -30
  132. package/src/id.ts +1 -0
  133. package/src/internal/cuid.test.ts +164 -0
  134. package/src/internal/cuid.ts +133 -0
  135. package/src/mod-client.ts +4 -2
  136. package/src/mod.ts +3 -2
  137. package/src/runtime.ts +1 -1
  138. package/src/test/test.test.ts +4 -2
  139. package/src/test/test.ts +7 -9
  140. package/src/util/async.test.ts +1 -0
  141. package/src/util/content-type.test.ts +1 -0
  142. package/src/util/nanostores.test.ts +3 -1
  143. package/src/util/ssr.ts +1 -0
  144. package/tsconfig.json +1 -1
  145. package/tsdown.config.ts +1 -0
  146. package/vitest.config.ts +2 -1
@@ -1,6 +1,8 @@
1
+ import { computed, task, type ReadableAtom, type Store } from "nanostores";
2
+
1
3
  import { nanoquery, type FetcherStore, type MutatorStore } from "@nanostores/query";
2
4
  import type { StandardSchemaV1 } from "@standard-schema/spec";
3
- import { computed, task, type ReadableAtom, type Store } from "nanostores";
5
+
4
6
  import type {
5
7
  FragnoRouteConfig,
6
8
  HTTPMethod,
@@ -8,6 +10,7 @@ import type {
8
10
  RequestThisContext,
9
11
  RouteContentType,
10
12
  } from "../api/api";
13
+ import type { FragmentDefinition } from "../api/fragment-definition-builder";
11
14
  import {
12
15
  buildPath,
13
16
  extractPathParams,
@@ -18,29 +21,28 @@ import {
18
21
  import { getMountRoute } from "../api/internal/route";
19
22
  import { RequestInputContext } from "../api/request-input-context";
20
23
  import { RequestOutputContext } from "../api/request-output-context";
24
+ import {
25
+ type AnyFragnoRouteConfig,
26
+ type AnyRouteOrFactory,
27
+ type FlattenRouteFactories,
28
+ resolveRouteFactories,
29
+ } from "../api/route";
21
30
  import type {
22
31
  FetcherConfig,
23
32
  FragnoFragmentSharedConfig,
24
33
  FragnoPublicClientConfig,
25
34
  FragnoPublicConfig,
26
35
  } from "../api/shared-types";
27
- import { FragnoClientApiError, FragnoClientError, FragnoClientFetchError } from "./client-error";
28
- import type { InferOr } from "../util/types-util";
29
36
  import { parseContentType } from "../util/content-type";
37
+ import { unwrapObject } from "../util/nanostores";
38
+ import { addStore, getInitialData, SSR_ENABLED } from "../util/ssr";
39
+ import type { InferOr } from "../util/types-util";
40
+ import { FragnoClientApiError, FragnoClientError, FragnoClientFetchError } from "./client-error";
41
+ import { mergeFetcherConfigs } from "./internal/fetcher-merge";
30
42
  import {
31
43
  handleNdjsonStreamingFirstItem,
32
44
  type NdjsonStreamingStore,
33
45
  } from "./internal/ndjson-streaming";
34
- import { addStore, getInitialData, SSR_ENABLED } from "../util/ssr";
35
- import { unwrapObject } from "../util/nanostores";
36
- import type { FragmentDefinition } from "../api/fragment-definition-builder";
37
- import type { AnyFragnoInstantiatedFragment } from "../api/fragment-instantiator";
38
- import {
39
- type AnyRouteOrFactory,
40
- type FlattenRouteFactories,
41
- resolveRouteFactories,
42
- } from "../api/route";
43
- import { mergeFetcherConfigs } from "./internal/fetcher-merge";
44
46
 
45
47
  /**
46
48
  * Symbols used to identify hook types
@@ -153,6 +155,31 @@ function prepareRequestBody(
153
155
  };
154
156
  }
155
157
 
158
+ async function schemaAllowsUndefined(schema: StandardSchemaV1): Promise<boolean> {
159
+ try {
160
+ const result = await schema["~standard"].validate(undefined);
161
+ return !result.issues;
162
+ } catch {
163
+ return false;
164
+ }
165
+ }
166
+
167
+ async function assertBodyProvided(
168
+ body: unknown,
169
+ inputSchema: StandardSchemaV1 | undefined,
170
+ errorMessage: string,
171
+ ): Promise<void> {
172
+ if (typeof body !== "undefined" || inputSchema === undefined) {
173
+ return;
174
+ }
175
+
176
+ if (await schemaAllowsUndefined(inputSchema)) {
177
+ return;
178
+ }
179
+
180
+ throw new Error(errorMessage);
181
+ }
182
+
156
183
  /**
157
184
  * Merge request headers from multiple sources.
158
185
  * Returns undefined if there are no headers to merge.
@@ -191,58 +218,128 @@ function mergeRequestHeaders(
191
218
  }
192
219
 
193
220
  /**
194
- * Extract only GET routes from a library config's routes array
195
221
  * @internal
196
222
  */
197
- export type ExtractGetRoutes<
198
- T extends readonly FragnoRouteConfig<
199
- HTTPMethod,
200
- string,
201
- StandardSchemaV1 | undefined,
202
- StandardSchemaV1 | undefined,
203
- string,
204
- string
205
- >[],
206
- > = {
223
+ type FilterRouteByMethod<TRoute, TExpectedMethod extends HTTPMethod> =
224
+ TRoute extends FragnoRouteConfig<
225
+ infer TMethod,
226
+ infer TPath,
227
+ infer TInputSchema,
228
+ infer TOutputSchema,
229
+ infer TErrorCode,
230
+ infer TQueryParameters,
231
+ infer TThisContext
232
+ >
233
+ ? [Extract<TMethod, TExpectedMethod>] extends [never]
234
+ ? [Extract<TExpectedMethod, TMethod>] extends [never]
235
+ ? never
236
+ : FragnoRouteConfig<
237
+ TMethod,
238
+ TPath,
239
+ TInputSchema,
240
+ TOutputSchema,
241
+ TErrorCode,
242
+ TQueryParameters,
243
+ TThisContext
244
+ >
245
+ : FragnoRouteConfig<
246
+ TMethod,
247
+ TPath,
248
+ TInputSchema,
249
+ TOutputSchema,
250
+ TErrorCode,
251
+ TQueryParameters,
252
+ TThisContext
253
+ >
254
+ : never;
255
+
256
+ /**
257
+ * @internal
258
+ */
259
+ type FilterRouteByPath<TRoute, TPath extends string> =
260
+ TRoute extends FragnoRouteConfig<
261
+ infer TMethod,
262
+ infer TRoutePath,
263
+ infer TInputSchema,
264
+ infer TOutputSchema,
265
+ infer TErrorCode,
266
+ infer TQueryParameters,
267
+ infer TThisContext
268
+ >
269
+ ? [Extract<TRoutePath, TPath>] extends [never]
270
+ ? [Extract<TPath, TRoutePath>] extends [never]
271
+ ? never
272
+ : FragnoRouteConfig<
273
+ TMethod,
274
+ TRoutePath,
275
+ TInputSchema,
276
+ TOutputSchema,
277
+ TErrorCode,
278
+ TQueryParameters,
279
+ TThisContext
280
+ >
281
+ : FragnoRouteConfig<
282
+ TMethod,
283
+ TRoutePath,
284
+ TInputSchema,
285
+ TOutputSchema,
286
+ TErrorCode,
287
+ TQueryParameters,
288
+ TThisContext
289
+ >
290
+ : never;
291
+
292
+ /**
293
+ * @internal
294
+ */
295
+ type ExtractGetRoutesExact<T extends readonly AnyFragnoRouteConfig[]> = {
207
296
  [K in keyof T]: T[K] extends FragnoRouteConfig<
208
- infer Method,
209
- infer Path,
210
- infer Input,
211
- infer Output,
212
- infer ErrorCode,
213
- infer QueryParams
297
+ infer TMethod,
298
+ infer TPath,
299
+ infer TInputSchema,
300
+ infer TOutputSchema,
301
+ infer TErrorCode,
302
+ infer TQueryParameters,
303
+ infer TThisContext
214
304
  >
215
- ? Method extends "GET"
216
- ? FragnoRouteConfig<Method, Path, Input, Output, ErrorCode, QueryParams>
305
+ ? TMethod extends "GET"
306
+ ? FragnoRouteConfig<
307
+ TMethod,
308
+ TPath,
309
+ TInputSchema,
310
+ TOutputSchema,
311
+ TErrorCode,
312
+ TQueryParameters,
313
+ TThisContext
314
+ >
217
315
  : never
218
316
  : never;
219
317
  }[number][];
220
318
 
221
319
  /**
222
- * Extract the path from a route configuration for a given method
320
+ * Extract only GET routes from a library config's routes array
223
321
  * @internal
224
322
  */
225
- export type ExtractRoutePath<
226
- T extends readonly FragnoRouteConfig<
227
- HTTPMethod,
228
- string,
229
- StandardSchemaV1 | undefined,
230
- StandardSchemaV1 | undefined,
231
- string,
232
- string
233
- >[],
323
+ export type ExtractGetRoutes<T extends readonly AnyFragnoRouteConfig[]> = ExtractGetRoutesExact<T>;
324
+
325
+ /**
326
+ * @internal
327
+ */
328
+ type ExtractRoutePathExact<
329
+ T extends readonly AnyFragnoRouteConfig[],
234
330
  TExpectedMethod extends HTTPMethod = HTTPMethod,
235
331
  > = {
236
332
  [K in keyof T]: T[K] extends FragnoRouteConfig<
237
- infer Method,
238
- infer Path,
333
+ infer TMethod,
334
+ infer TPath,
239
335
  StandardSchemaV1 | undefined,
240
336
  StandardSchemaV1 | undefined,
241
337
  string,
242
- string
338
+ string,
339
+ RequestThisContext
243
340
  >
244
- ? Method extends TExpectedMethod
245
- ? Path
341
+ ? TMethod extends TExpectedMethod
342
+ ? TPath
246
343
  : never
247
344
  : never;
248
345
  }[number];
@@ -250,103 +347,154 @@ export type ExtractRoutePath<
250
347
  /**
251
348
  * @internal
252
349
  */
253
- export type ExtractGetRoutePaths<
254
- T extends readonly FragnoRouteConfig<
255
- HTTPMethod,
256
- string,
350
+ type ExtractRoutePathLoose<
351
+ T extends readonly AnyFragnoRouteConfig[],
352
+ TExpectedMethod extends HTTPMethod = HTTPMethod,
353
+ > = {
354
+ [K in keyof T]: FilterRouteByMethod<T[K], TExpectedMethod> extends FragnoRouteConfig<
355
+ infer _TMethod,
356
+ infer TPath,
257
357
  StandardSchemaV1 | undefined,
258
358
  StandardSchemaV1 | undefined,
259
359
  string,
260
- string
261
- >[],
262
- > = ExtractRoutePath<T, "GET">;
360
+ string,
361
+ RequestThisContext
362
+ >
363
+ ? TPath
364
+ : never;
365
+ }[number];
263
366
 
264
367
  /**
265
368
  * @internal
266
369
  */
267
- export type ExtractNonGetRoutePaths<
268
- T extends readonly FragnoRouteConfig<
269
- HTTPMethod,
270
- string,
271
- StandardSchemaV1 | undefined,
272
- StandardSchemaV1 | undefined,
273
- string,
274
- string
275
- >[],
276
- > = ExtractRoutePath<T, NonGetHTTPMethod>;
370
+ type HasWidenedRouteShape<T extends readonly AnyFragnoRouteConfig[]> =
371
+ T[number] extends infer TRoute
372
+ ? TRoute extends FragnoRouteConfig<
373
+ infer TMethod,
374
+ infer TPath,
375
+ StandardSchemaV1 | undefined,
376
+ StandardSchemaV1 | undefined,
377
+ string,
378
+ string,
379
+ RequestThisContext
380
+ >
381
+ ? string extends TPath
382
+ ? true
383
+ : HTTPMethod extends TMethod
384
+ ? true
385
+ : false
386
+ : false
387
+ : false;
277
388
 
278
389
  /**
279
- * Extract the route configuration type(s) for a given path from a routes array.
280
- * Optionally narrow by HTTP method via the third type parameter.
281
- *
282
- * Defaults to extracting all methods for the matching path, producing a union
283
- * if multiple methods exist for the same path.
390
+ * Extract the path from a route configuration for a given method
284
391
  * @internal
285
392
  */
286
- export type ExtractRouteByPath<
287
- TRoutes extends readonly FragnoRouteConfig<
288
- HTTPMethod,
289
- string,
290
- StandardSchemaV1 | undefined,
291
- StandardSchemaV1 | undefined,
292
- string,
293
- string
294
- >[],
393
+ export type ExtractRoutePath<
394
+ T extends readonly AnyFragnoRouteConfig[],
395
+ TExpectedMethod extends HTTPMethod = HTTPMethod,
396
+ > = [ExtractRoutePathExact<T, TExpectedMethod>] extends [never]
397
+ ? HasWidenedRouteShape<T> extends true
398
+ ? ExtractRoutePathLoose<T, TExpectedMethod> & string
399
+ : never
400
+ : ExtractRoutePathExact<T, TExpectedMethod>;
401
+
402
+ /**
403
+ * @internal
404
+ */
405
+ export type ExtractGetRoutePaths<T extends readonly AnyFragnoRouteConfig[]> = ExtractRoutePath<
406
+ T,
407
+ "GET"
408
+ >;
409
+
410
+ /**
411
+ * @internal
412
+ */
413
+ export type ExtractNonGetRoutePaths<T extends readonly AnyFragnoRouteConfig[]> = ExtractRoutePath<
414
+ T,
415
+ NonGetHTTPMethod
416
+ >;
417
+
418
+ /**
419
+ * @internal
420
+ */
421
+ type ExtractRouteByPathExact<
422
+ TRoutes extends readonly AnyFragnoRouteConfig[],
295
423
  TPath extends string,
296
424
  TMethod extends HTTPMethod = HTTPMethod,
297
425
  > = {
298
426
  [K in keyof TRoutes]: TRoutes[K] extends FragnoRouteConfig<
299
- infer M,
427
+ infer TRouteMethod,
300
428
  TPath,
301
- infer Input,
302
- infer Output,
303
- infer ErrorCode,
304
- infer QueryParams
429
+ infer TInputSchema,
430
+ infer TOutputSchema,
431
+ infer TErrorCode,
432
+ infer TQueryParameters,
433
+ infer TThisContext
305
434
  >
306
- ? M extends TMethod
307
- ? FragnoRouteConfig<M, TPath, Input, Output, ErrorCode, QueryParams>
435
+ ? TRouteMethod extends TMethod
436
+ ? FragnoRouteConfig<
437
+ TRouteMethod,
438
+ TPath,
439
+ TInputSchema,
440
+ TOutputSchema,
441
+ TErrorCode,
442
+ TQueryParameters,
443
+ TThisContext
444
+ >
308
445
  : never
309
446
  : never;
310
447
  }[number];
311
448
 
449
+ /**
450
+ * @internal
451
+ */
452
+ type ExtractRouteByPathLoose<
453
+ TRoutes extends readonly AnyFragnoRouteConfig[],
454
+ TPath extends string,
455
+ TMethod extends HTTPMethod = HTTPMethod,
456
+ > = {
457
+ [K in keyof TRoutes]: FilterRouteByPath<FilterRouteByMethod<TRoutes[K], TMethod>, TPath>;
458
+ }[number];
459
+
460
+ /**
461
+ * Extract the route configuration type(s) for a given path from a routes array.
462
+ * Optionally narrow by HTTP method via the third type parameter.
463
+ *
464
+ * Defaults to extracting all methods for the matching path, producing a union
465
+ * if multiple methods exist for the same path.
466
+ * @internal
467
+ */
468
+ export type ExtractRouteByPath<
469
+ TRoutes extends readonly AnyFragnoRouteConfig[],
470
+ TPath extends string,
471
+ TMethod extends HTTPMethod = HTTPMethod,
472
+ > = [ExtractRouteByPathExact<TRoutes, TPath, TMethod>] extends [never]
473
+ ? HasWidenedRouteShape<TRoutes> extends true
474
+ ? ExtractRouteByPathLoose<TRoutes, TPath, TMethod>
475
+ : never
476
+ : ExtractRouteByPathExact<TRoutes, TPath, TMethod>;
477
+
312
478
  /**
313
479
  * Extract the output schema type for a specific route path from a routes array
314
480
  * @internal
315
481
  */
316
482
  export type ExtractOutputSchemaForPath<
317
- TRoutes extends readonly FragnoRouteConfig<
318
- HTTPMethod,
319
- string,
320
- StandardSchemaV1 | undefined,
321
- StandardSchemaV1 | undefined
322
- >[],
483
+ TRoutes extends readonly AnyFragnoRouteConfig[],
323
484
  TPath extends string,
324
- > = {
325
- [K in keyof TRoutes]: TRoutes[K] extends FragnoRouteConfig<
326
- infer Method,
327
- TPath,
328
- StandardSchemaV1 | undefined,
329
- infer Output
330
- >
331
- ? Method extends "GET"
332
- ? Output
333
- : never
485
+ > =
486
+ ExtractRouteByPath<TRoutes, TPath, "GET"> extends {
487
+ outputSchema?: infer TOutputSchema;
488
+ }
489
+ ? TOutputSchema
334
490
  : never;
335
- }[number];
336
491
 
337
492
  /**
338
493
  * Check if a path exists as a GET route in the routes array
339
494
  * @internal
340
495
  */
341
496
  export type IsValidGetRoutePath<
342
- TRoutes extends readonly FragnoRouteConfig<
343
- HTTPMethod,
344
- string,
345
- StandardSchemaV1 | undefined,
346
- StandardSchemaV1 | undefined,
347
- string,
348
- string
349
- >[],
497
+ TRoutes extends readonly AnyFragnoRouteConfig[],
350
498
  TPath extends string,
351
499
  > = TPath extends ExtractGetRoutePaths<TRoutes> ? true : false;
352
500
 
@@ -355,14 +503,7 @@ export type IsValidGetRoutePath<
355
503
  * @internal
356
504
  */
357
505
  export type ValidateGetRoutePath<
358
- TRoutes extends readonly FragnoRouteConfig<
359
- HTTPMethod,
360
- string,
361
- StandardSchemaV1 | undefined,
362
- StandardSchemaV1 | undefined,
363
- string,
364
- string
365
- >[],
506
+ TRoutes extends readonly AnyFragnoRouteConfig[],
366
507
  TPath extends string,
367
508
  > =
368
509
  TPath extends ExtractGetRoutePaths<TRoutes>
@@ -373,16 +514,8 @@ export type ValidateGetRoutePath<
373
514
  * Helper type to check if a routes array has any GET routes
374
515
  * @internal
375
516
  */
376
- export type HasGetRoutes<
377
- T extends readonly FragnoRouteConfig<
378
- HTTPMethod,
379
- string,
380
- StandardSchemaV1 | undefined,
381
- StandardSchemaV1 | undefined,
382
- string,
383
- string
384
- >[],
385
- > = ExtractGetRoutePaths<T> extends never ? false : true;
517
+ export type HasGetRoutes<T extends readonly AnyFragnoRouteConfig[]> =
518
+ ExtractGetRoutePaths<T> extends never ? false : true;
386
519
 
387
520
  /**
388
521
  * @internal
@@ -398,11 +531,20 @@ export type ObjectContainingStoreField<T extends object> = T extends Store
398
531
  /**
399
532
  * @internal
400
533
  */
401
- export type FragnoStoreData<T extends object> = {
534
+ export type FragnoStoreObjectData<T extends object> = {
402
535
  obj: T;
403
536
  [STORE_SYMBOL]: true;
404
537
  };
405
538
 
539
+ export type FragnoStoreFactoryData<T extends object, TArgs extends unknown[] = []> = {
540
+ factory: (...args: TArgs) => T;
541
+ [STORE_SYMBOL]: true;
542
+ };
543
+
544
+ export type FragnoStoreData<T extends object, TArgs extends unknown[] = []> =
545
+ | FragnoStoreObjectData<T>
546
+ | FragnoStoreFactoryData<T, TArgs>;
547
+
406
548
  export type FragnoClientHookData<
407
549
  TMethod extends HTTPMethod,
408
550
  TPath extends string,
@@ -627,7 +769,9 @@ export function isMutatorHook<
627
769
  /**
628
770
  * @internal
629
771
  */
630
- export function isStore<TStore extends Store>(obj: unknown): obj is FragnoStoreData<TStore> {
772
+ export function isStore<TStore extends object, TArgs extends unknown[] = []>(
773
+ obj: unknown,
774
+ ): obj is FragnoStoreData<TStore, TArgs> {
631
775
  return (
632
776
  typeof obj === "object" && obj !== null && STORE_SYMBOL in obj && obj[STORE_SYMBOL] === true
633
777
  );
@@ -712,8 +856,18 @@ export class ClientBuilder<
712
856
  return Object.fromEntries(this.#cache.entries());
713
857
  }
714
858
 
715
- createStore<const T extends object>(obj: T): FragnoStoreData<T> {
716
- return { obj: obj, [STORE_SYMBOL]: true };
859
+ createStore<const TArgs extends unknown[], const T extends object>(
860
+ factory: (...args: TArgs) => T,
861
+ ): FragnoStoreFactoryData<T, TArgs>;
862
+ createStore<const T extends object>(obj: T): FragnoStoreObjectData<T>;
863
+ createStore<const TArgs extends unknown[], const T extends object>(
864
+ input: T | ((...args: TArgs) => T),
865
+ ): FragnoStoreData<T, TArgs> {
866
+ if (typeof input === "function") {
867
+ return { factory: input as (...args: TArgs) => T, [STORE_SYMBOL]: true };
868
+ }
869
+
870
+ return { obj: input, [STORE_SYMBOL]: true };
717
871
  }
718
872
 
719
873
  /**
@@ -757,7 +911,7 @@ export class ClientBuilder<
757
911
  if (this.#fetcherConfig?.type === "function") {
758
912
  return this.#fetcherConfig.fetcher;
759
913
  }
760
- return fetch;
914
+ return globalThis.fetch.bind(globalThis);
761
915
  }
762
916
 
763
917
  #getFetcherOptions(): RequestInit | undefined {
@@ -1150,9 +1304,7 @@ export class ClientBuilder<
1150
1304
  query?: Record<string, string>;
1151
1305
  };
1152
1306
 
1153
- if (typeof body === "undefined" && route.inputSchema !== undefined) {
1154
- throw new Error("Body is required.");
1155
- }
1307
+ await assertBodyProvided(body, route.inputSchema, "Body is required.");
1156
1308
 
1157
1309
  const response = await executeMutateQuery({ body, path, query });
1158
1310
 
@@ -1221,9 +1373,7 @@ export class ClientBuilder<
1221
1373
  query?: Record<string, string>;
1222
1374
  };
1223
1375
 
1224
- if (typeof body === "undefined" && route.inputSchema !== undefined) {
1225
- throw new Error("Body is required for mutateQuery");
1226
- }
1376
+ await assertBodyProvided(body, route.inputSchema, "Body is required for mutateQuery");
1227
1377
 
1228
1378
  const response = await executeMutateQuery({ body, path, query });
1229
1379
 
@@ -1300,7 +1450,7 @@ export function createClientBuilder<
1300
1450
  THandlerThisContext extends RequestThisContext,
1301
1451
  TRequestStorage,
1302
1452
  const TRoutesOrFactories extends readonly AnyRouteOrFactory[],
1303
- TLinkedFragments extends Record<string, AnyFragnoInstantiatedFragment> = {},
1453
+ TInternalRoutes extends readonly AnyRouteOrFactory[] = readonly [],
1304
1454
  >(
1305
1455
  definition: FragmentDefinition<
1306
1456
  TConfig,
@@ -1313,7 +1463,7 @@ export function createClientBuilder<
1313
1463
  TServiceThisContext,
1314
1464
  THandlerThisContext,
1315
1465
  TRequestStorage,
1316
- TLinkedFragments
1466
+ TInternalRoutes
1317
1467
  >,
1318
1468
  publicConfig: FragnoPublicClientConfig,
1319
1469
  routesOrFactories: TRoutesOrFactories,
@@ -1,10 +1,13 @@
1
1
  import { describe, test, expect, vi } from "vitest";
2
- import { handleNdjsonStreamingFirstItem, type NdjsonStreamingStore } from "./ndjson-streaming";
3
- import { FragnoClientError, FragnoClientFetchAbortError } from "../client-error";
4
- import { nanoquery } from "@nanostores/query";
2
+
5
3
  import { z } from "zod";
4
+
5
+ import { nanoquery } from "@nanostores/query";
6
6
  import type { StandardSchemaV1 } from "@standard-schema/spec";
7
+
7
8
  import { createAsyncIteratorFromCallback } from "../../util/async";
9
+ import { FragnoClientError, FragnoClientFetchAbortError } from "../client-error";
10
+ import { handleNdjsonStreamingFirstItem, type NdjsonStreamingStore } from "./ndjson-streaming";
8
11
 
9
12
  describe("handleNdjsonStreaming", () => {
10
13
  test("should return first item and continue streaming updates", async () => {
@@ -1,4 +1,5 @@
1
1
  import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+
2
3
  import {
3
4
  FragnoClientError,
4
5
  FragnoClientFetchError,