@fragno-dev/core 0.0.6 → 0.1.0

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 (74) hide show
  1. package/.turbo/turbo-build.log +54 -43
  2. package/.turbo/turbo-test.log +297 -0
  3. package/CHANGELOG.md +14 -0
  4. package/dist/api/api.d.ts +1 -1
  5. package/dist/api/api.js +1 -1
  6. package/dist/api/fragment-builder.d.ts +3 -0
  7. package/dist/api/fragment-builder.js +3 -0
  8. package/dist/api/fragment-instantiation.d.ts +3 -0
  9. package/dist/api/fragment-instantiation.js +5 -0
  10. package/dist/{api-CBDGZiLC.d.ts → api-Dcr4_-3g.d.ts} +3 -3
  11. package/dist/api-Dcr4_-3g.d.ts.map +1 -0
  12. package/dist/{api-DgHfYjq2.js → api-DngJDcmO.js} +2 -2
  13. package/dist/{api-DgHfYjq2.js.map → api-DngJDcmO.js.map} +1 -1
  14. package/dist/client/client.d.ts +2 -2
  15. package/dist/client/client.js +4 -4
  16. package/dist/client/client.svelte.d.ts +14 -14
  17. package/dist/client/client.svelte.d.ts.map +1 -1
  18. package/dist/client/client.svelte.js +4 -4
  19. package/dist/client/react.d.ts +12 -12
  20. package/dist/client/react.d.ts.map +1 -1
  21. package/dist/client/react.js +6 -8
  22. package/dist/client/react.js.map +1 -1
  23. package/dist/client/solid.d.ts +45 -0
  24. package/dist/client/solid.d.ts.map +1 -0
  25. package/dist/client/solid.js +110 -0
  26. package/dist/client/solid.js.map +1 -0
  27. package/dist/client/vanilla.d.ts +21 -21
  28. package/dist/client/vanilla.d.ts.map +1 -1
  29. package/dist/client/vanilla.js +4 -4
  30. package/dist/client/vue.d.ts +14 -14
  31. package/dist/client/vue.d.ts.map +1 -1
  32. package/dist/client/vue.js +4 -4
  33. package/dist/{client-DWjxKDnE.js → client-CZCasGGB.js} +7 -10
  34. package/dist/client-CZCasGGB.js.map +1 -0
  35. package/dist/fragment-builder-DOnCVBqc.js +47 -0
  36. package/dist/fragment-builder-DOnCVBqc.js.map +1 -0
  37. package/dist/fragment-builder-Dcdsms1l.d.ts +356 -0
  38. package/dist/fragment-builder-Dcdsms1l.d.ts.map +1 -0
  39. package/dist/fragment-instantiation-f4AhwQss.js +197 -0
  40. package/dist/fragment-instantiation-f4AhwQss.js.map +1 -0
  41. package/dist/integrations/react-ssr.js +1 -1
  42. package/dist/mod.d.ts +3 -3
  43. package/dist/mod.js +5 -204
  44. package/dist/{route-Bp6eByhz.js → route-B4RbOWjd.js} +6 -6
  45. package/dist/route-B4RbOWjd.js.map +1 -0
  46. package/dist/{ssr-tJHqcNSw.js → ssr-CamRrMc0.js} +2 -2
  47. package/dist/{ssr-tJHqcNSw.js.map → ssr-CamRrMc0.js.map} +1 -1
  48. package/package.json +34 -5
  49. package/src/api/fragment-builder.ts +81 -0
  50. package/src/api/{fragment.ts → fragment-instantiation.ts} +65 -67
  51. package/src/api/fragment.test.ts +44 -16
  52. package/src/api/request-middleware.test.ts +6 -5
  53. package/src/api/request-output-context.ts +3 -3
  54. package/src/api/route.ts +1 -8
  55. package/src/client/client-builder.test.ts +2 -2
  56. package/src/client/client-error.test.ts +17 -1
  57. package/src/client/client.ssr.test.ts +1 -1
  58. package/src/client/client.svelte.test.ts +1 -1
  59. package/src/client/client.test.ts +1 -1
  60. package/src/client/client.ts +5 -2
  61. package/src/client/react.test.ts +1 -1
  62. package/src/client/solid.test.ts +840 -0
  63. package/src/client/solid.ts +261 -0
  64. package/src/client/vanilla.test.ts +1 -1
  65. package/src/client/vue.test.ts +1 -1
  66. package/src/mod.ts +3 -3
  67. package/tsdown.config.ts +3 -0
  68. package/vitest.config.ts +10 -7
  69. package/dist/api-CBDGZiLC.d.ts.map +0 -1
  70. package/dist/client-B6s-lTFe.d.ts +0 -315
  71. package/dist/client-B6s-lTFe.d.ts.map +0 -1
  72. package/dist/client-DWjxKDnE.js.map +0 -1
  73. package/dist/mod.js.map +0 -1
  74. package/dist/route-Bp6eByhz.js.map +0 -1
@@ -7,7 +7,6 @@ import { RequestInputContext } from "./request-input-context";
7
7
  import type { ExtractPathParams } from "./internal/path";
8
8
  import { RequestOutputContext } from "./request-output-context";
9
9
  import {
10
- type EmptyObject,
11
10
  type AnyFragnoRouteConfig,
12
11
  type AnyRouteOrFactory,
13
12
  type FlattenRouteFactories,
@@ -18,6 +17,7 @@ import {
18
17
  RequestMiddlewareOutputContext,
19
18
  type FragnoMiddlewareCallback,
20
19
  } from "./request-middleware";
20
+ import type { FragmentDefinition } from "./fragment-builder";
21
21
 
22
22
  export interface FragnoPublicConfig {
23
23
  mountRoute?: string;
@@ -37,6 +37,16 @@ type ReactRouterHandlers = {
37
37
  action: (args: { request: Request }) => Promise<Response>;
38
38
  };
39
39
 
40
+ type SolidStartHandlers = {
41
+ GET: (args: { request: Request }) => Promise<Response>;
42
+ POST: (args: { request: Request }) => Promise<Response>;
43
+ PUT: (args: { request: Request }) => Promise<Response>;
44
+ DELETE: (args: { request: Request }) => Promise<Response>;
45
+ PATCH: (args: { request: Request }) => Promise<Response>;
46
+ HEAD: (args: { request: Request }) => Promise<Response>;
47
+ OPTIONS: (args: { request: Request }) => Promise<Response>;
48
+ };
49
+
40
50
  type StandardHandlers = {
41
51
  GET: (req: Request) => Promise<Response>;
42
52
  POST: (req: Request) => Promise<Response>;
@@ -52,24 +62,32 @@ type HandlersByFramework = {
52
62
  "react-router": ReactRouterHandlers;
53
63
  "next-js": StandardHandlers;
54
64
  "svelte-kit": StandardHandlers;
65
+ "solid-start": SolidStartHandlers;
55
66
  };
56
67
 
68
+ // Not actually a symbol, since we might be dealing with multiple instances of this code.
69
+ export const instantiatedFragmentFakeSymbol = "$fragno-instantiated-fragment" as const;
70
+
57
71
  type FullstackFrameworks = keyof HandlersByFramework;
58
72
 
59
73
  export interface FragnoInstantiatedFragment<
60
74
  TRoutes extends readonly AnyFragnoRouteConfig[] = [],
61
- TDeps = EmptyObject,
75
+ TDeps = {},
62
76
  TServices extends Record<string, unknown> = Record<string, unknown>,
77
+ TAdditionalContext extends Record<string, unknown> = {},
63
78
  > {
79
+ [instantiatedFragmentFakeSymbol]: typeof instantiatedFragmentFakeSymbol;
80
+
64
81
  config: FragnoFragmentSharedConfig<TRoutes>;
65
82
  deps: TDeps;
66
83
  services: TServices;
84
+ additionalContext?: TAdditionalContext;
67
85
  handlersFor: <T extends FullstackFrameworks>(framework: T) => HandlersByFramework[T];
68
86
  handler: (req: Request) => Promise<Response>;
69
87
  mountRoute: string;
70
88
  withMiddleware: (
71
89
  handler: FragnoMiddlewareCallback<TRoutes, TDeps, TServices>,
72
- ) => FragnoInstantiatedFragment<TRoutes, TDeps, TServices>;
90
+ ) => FragnoInstantiatedFragment<TRoutes, TDeps, TServices, TAdditionalContext>;
73
91
  }
74
92
 
75
93
  export interface FragnoFragmentSharedConfig<
@@ -90,81 +108,38 @@ export type AnyFragnoFragmentSharedConfig = FragnoFragmentSharedConfig<
90
108
  readonly AnyFragnoRouteConfig[]
91
109
  >;
92
110
 
93
- interface FragmentDefinition<
94
- TConfig,
95
- TDeps = EmptyObject,
96
- TServices extends Record<string, unknown> = EmptyObject,
97
- > {
98
- name: string;
99
- dependencies?: (config: TConfig) => TDeps;
100
- services?: (config: TConfig, deps: TDeps) => TServices;
101
- }
102
-
103
- export class FragmentBuilder<
104
- TConfig,
105
- TDeps = EmptyObject,
106
- TServices extends Record<string, unknown> = EmptyObject,
107
- > {
108
- #definition: FragmentDefinition<TConfig, TDeps, TServices>;
109
-
110
- constructor(definition: FragmentDefinition<TConfig, TDeps, TServices>) {
111
- this.#definition = definition;
112
- }
113
-
114
- get definition() {
115
- return this.#definition;
116
- }
117
-
118
- withDependencies<TNewDeps>(
119
- fn: (config: TConfig) => TNewDeps,
120
- ): FragmentBuilder<TConfig, TNewDeps, TServices> {
121
- return new FragmentBuilder<TConfig, TNewDeps, TServices>({
122
- ...this.#definition,
123
- dependencies: fn,
124
- } as FragmentDefinition<TConfig, TNewDeps, TServices>);
125
- }
126
-
127
- withServices<TNewServices extends Record<string, unknown>>(
128
- fn: (config: TConfig, deps: TDeps) => TNewServices,
129
- ): FragmentBuilder<TConfig, TDeps, TNewServices> {
130
- return new FragmentBuilder<TConfig, TDeps, TNewServices>({
131
- ...this.#definition,
132
- services: fn,
133
- } as FragmentDefinition<TConfig, TDeps, TNewServices>);
134
- }
135
- }
136
-
137
- // eslint-disable-next-line @typescript-eslint/no-empty-object-type
138
- export function defineFragment<TConfig = {}>(name: string): FragmentBuilder<TConfig> {
139
- return new FragmentBuilder({
140
- name,
141
- });
142
- }
143
-
144
111
  export function createFragment<
145
- TConfig,
146
- TDeps,
147
- TServices extends Record<string, unknown>,
112
+ const TConfig,
113
+ const TDeps,
114
+ const TServices extends Record<string, unknown>,
148
115
  const TRoutesOrFactories extends readonly AnyRouteOrFactory[],
116
+ const TAdditionalContext extends Record<string, unknown>,
117
+ const TOptions extends FragnoPublicConfig,
149
118
  >(
150
- fragmentDefinition: FragmentBuilder<TConfig, TDeps, TServices>,
119
+ fragmentBuilder: {
120
+ definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;
121
+ $requiredOptions: TOptions;
122
+ },
151
123
  config: TConfig,
152
124
  routesOrFactories: TRoutesOrFactories,
153
- fragnoConfig: FragnoPublicConfig = {},
154
- ): FragnoInstantiatedFragment<FlattenRouteFactories<TRoutesOrFactories>, TDeps, TServices> {
155
- const definition = fragmentDefinition.definition;
125
+ options: TOptions,
126
+ ): FragnoInstantiatedFragment<
127
+ FlattenRouteFactories<TRoutesOrFactories>,
128
+ TDeps,
129
+ TServices,
130
+ TAdditionalContext
131
+ > {
132
+ const definition = fragmentBuilder.definition;
156
133
 
157
- const dependencies = definition.dependencies ? definition.dependencies(config) : ({} as TDeps);
158
- const services = definition.services
159
- ? definition.services(config, dependencies)
160
- : ({} as TServices);
134
+ const dependencies = definition.dependencies?.(config, options) ?? ({} as TDeps);
135
+ const services = definition.services?.(config, options, dependencies) ?? ({} as TServices);
161
136
 
162
137
  const context = { config, deps: dependencies, services };
163
138
  const routes = resolveRouteFactories(context, routesOrFactories);
164
139
 
165
140
  const mountRoute = getMountRoute({
166
141
  name: definition.name,
167
- mountRoute: fragnoConfig.mountRoute,
142
+ mountRoute: options.mountRoute,
168
143
  });
169
144
 
170
145
  const router =
@@ -190,8 +165,10 @@ export function createFragment<
190
165
  const fragment: FragnoInstantiatedFragment<
191
166
  FlattenRouteFactories<TRoutesOrFactories>,
192
167
  TDeps,
193
- TServices
168
+ TServices,
169
+ TAdditionalContext & TOptions
194
170
  > = {
171
+ [instantiatedFragmentFakeSymbol]: instantiatedFragmentFakeSymbol,
195
172
  mountRoute,
196
173
  config: {
197
174
  name: definition.name,
@@ -199,6 +176,10 @@ export function createFragment<
199
176
  },
200
177
  services,
201
178
  deps: dependencies,
179
+ additionalContext: {
180
+ ...definition.additionalContext,
181
+ ...options,
182
+ } as TAdditionalContext & TOptions,
202
183
  withMiddleware: (handler) => {
203
184
  if (middlewareHandler) {
204
185
  throw new Error("Middleware already set");
@@ -210,6 +191,14 @@ export function createFragment<
210
191
  },
211
192
  handlersFor: <T extends FullstackFrameworks>(framework: T): HandlersByFramework[T] => {
212
193
  const handler = fragment.handler;
194
+
195
+ // LLMs hallucinate these values sometimes, solution isn't obvious so we throw this error
196
+ // @ts-expect-error TS2367
197
+ if (framework === "h3" || framework === "nuxt") {
198
+ throw new Error(`To get handlers for h3, use the 'fromWebHandler' utility function:
199
+ import { fromWebHandler } from "h3";
200
+ export default fromWebHandler(myFragment().handler);`);
201
+ }
213
202
  const allHandlers = {
214
203
  astro: { ALL: handler },
215
204
  "react-router": {
@@ -234,6 +223,15 @@ export function createFragment<
234
223
  HEAD: handler,
235
224
  OPTIONS: handler,
236
225
  },
226
+ "solid-start": {
227
+ GET: ({ request }: { request: Request }) => handler(request),
228
+ POST: ({ request }: { request: Request }) => handler(request),
229
+ PUT: ({ request }: { request: Request }) => handler(request),
230
+ DELETE: ({ request }: { request: Request }) => handler(request),
231
+ PATCH: ({ request }: { request: Request }) => handler(request),
232
+ HEAD: ({ request }: { request: Request }) => handler(request),
233
+ OPTIONS: ({ request }: { request: Request }) => handler(request),
234
+ },
237
235
  } satisfies HandlersByFramework;
238
236
 
239
237
  return allHandlers[framework];
@@ -1,5 +1,6 @@
1
1
  import { test, expect, describe, expectTypeOf } from "vitest";
2
- import { defineFragment, createFragment, type FragmentBuilder } from "./fragment";
2
+ import { defineFragment, type FragmentBuilder } from "./fragment-builder";
3
+ import { createFragment } from "./fragment-instantiation";
3
4
  import { defineRoute, defineRoutes, type RouteFactory, resolveRouteFactories } from "./route";
4
5
  import { z } from "zod";
5
6
  import type { InferOr } from "../util/types-util";
@@ -25,6 +26,7 @@ describe("new-fragment API", () => {
25
26
  debug: boolean;
26
27
  },
27
28
  Empty,
29
+ Empty,
28
30
  Empty
29
31
  >
30
32
  >();
@@ -35,8 +37,8 @@ describe("new-fragment API", () => {
35
37
  apiKey: "test-key",
36
38
  };
37
39
 
38
- const _fragment = defineFragment<typeof _config>("test").withDependencies((cfg) => {
39
- expectTypeOf(cfg).toEqualTypeOf<typeof _config>();
40
+ const _fragment = defineFragment<typeof _config>("test").withDependencies(({ config }) => {
41
+ expectTypeOf(config).toEqualTypeOf<typeof _config>();
40
42
  return {
41
43
  httpClient: { fetch: () => Promise.resolve(new Response()) },
42
44
  logger: { log: (msg: string) => console.log(msg) },
@@ -50,6 +52,7 @@ describe("new-fragment API", () => {
50
52
  httpClient: { fetch: () => Promise<Response> };
51
53
  logger: { log: (msg: string) => void };
52
54
  },
55
+ Empty,
53
56
  Empty
54
57
  >
55
58
  >();
@@ -62,16 +65,16 @@ describe("new-fragment API", () => {
62
65
  };
63
66
 
64
67
  const _fragment = defineFragment<typeof _config>("test")
65
- .withDependencies((cfg) => {
66
- expectTypeOf(cfg).toEqualTypeOf<{
68
+ .withDependencies(({ config }) => {
69
+ expectTypeOf(config).toEqualTypeOf<{
67
70
  apiKey: string;
68
71
  baseUrl: string;
69
72
  }>();
70
73
 
71
- return { httpClient: { baseUrl: cfg.baseUrl } };
74
+ return { httpClient: { baseUrl: config.baseUrl } };
72
75
  })
73
- .withServices((cfg, deps) => {
74
- expectTypeOf(cfg).toEqualTypeOf<typeof _config>();
76
+ .withServices(({ config, deps }) => {
77
+ expectTypeOf(config).toEqualTypeOf<typeof _config>();
75
78
  expectTypeOf(deps).toEqualTypeOf<{ httpClient: { baseUrl: string } }>();
76
79
 
77
80
  return {
@@ -97,7 +100,8 @@ describe("new-fragment API", () => {
97
100
  get: (key: string) => string;
98
101
  set: (key: string, value: string) => void;
99
102
  };
100
- }
103
+ },
104
+ Empty
101
105
  >
102
106
  >();
103
107
  });
@@ -155,13 +159,15 @@ describe("new-fragment API", () => {
155
159
  const _config = { test: true };
156
160
 
157
161
  const lib1 = defineFragment<typeof _config>("test");
158
- expectTypeOf(lib1).toEqualTypeOf<FragmentBuilder<typeof _config, Empty, Empty>>();
162
+ expectTypeOf(lib1).toEqualTypeOf<FragmentBuilder<typeof _config, Empty, Empty, Empty>>();
159
163
 
160
164
  const lib2 = lib1.withDependencies(() => ({ dep1: "value1" }));
161
- expectTypeOf(lib2).toEqualTypeOf<FragmentBuilder<typeof _config, { dep1: string }, Empty>>();
165
+ expectTypeOf(lib2).toEqualTypeOf<
166
+ FragmentBuilder<typeof _config, { dep1: string }, Empty, Empty>
167
+ >();
162
168
  const lib3 = lib2.withServices(() => ({ service1: "value1" }));
163
169
  expectTypeOf(lib3).toEqualTypeOf<
164
- FragmentBuilder<typeof _config, { dep1: string }, { service1: string }>
170
+ FragmentBuilder<typeof _config, { dep1: string }, { service1: string }, Empty>
165
171
  >();
166
172
 
167
173
  expect(lib1).not.toBe(lib2);
@@ -173,10 +179,10 @@ describe("new-fragment API", () => {
173
179
  const _config = { apiKey: "test" };
174
180
 
175
181
  const fragment = defineFragment<typeof _config>("my-lib")
176
- .withDependencies((_cfg) => ({
177
- client: `Client for ${_cfg.apiKey}`,
182
+ .withDependencies(({ config }) => ({
183
+ client: `Client for ${config.apiKey}`,
178
184
  }))
179
- .withServices((_cfg, deps) => ({
185
+ .withServices(({ deps }) => ({
180
186
  service: `Service using ${deps.client}`,
181
187
  }));
182
188
 
@@ -211,7 +217,7 @@ describe("new-fragment API", () => {
211
217
  ]);
212
218
 
213
219
  const fragmentDef = defineFragment("greeting")
214
- .withDependencies((_config) => ({
220
+ .withDependencies(() => ({
215
221
  formatter: (s: string) => s.toUpperCase(),
216
222
  }))
217
223
  .withServices(() => ({
@@ -506,4 +512,26 @@ describe("new-fragment API", () => {
506
512
  expectTypeOf(routes[1].path).toEqualTypeOf<"/status">();
507
513
  });
508
514
  });
515
+
516
+ describe("Database Integration", () => {
517
+ test("createFragment without database works without adapter", () => {
518
+ const fragmentDef = defineFragment("test").withDependencies(() => ({
519
+ service: { data: "test" },
520
+ }));
521
+
522
+ const fragment = createFragment(fragmentDef, {}, [], {});
523
+
524
+ expect(fragment.deps.service.data).toBe("test");
525
+ });
526
+
527
+ test("createFragment accepts options parameter", () => {
528
+ const fragmentDef = defineFragment("test").withDependencies(() => ({
529
+ service: { data: "test" },
530
+ }));
531
+
532
+ const fragment = createFragment(fragmentDef, {}, [], { mountRoute: "/custom" });
533
+
534
+ expect(fragment.mountRoute).toBe("/custom");
535
+ });
536
+ });
509
537
  });
@@ -1,5 +1,6 @@
1
1
  import { test, expect, describe, expectTypeOf } from "vitest";
2
- import { defineFragment, createFragment } from "./fragment";
2
+ import { defineFragment } from "./fragment-builder";
3
+ import { createFragment } from "./fragment-instantiation";
3
4
  import { defineRoute } from "./route";
4
5
  import { z } from "zod";
5
6
  import { FragnoApiValidationError } from "./error";
@@ -45,7 +46,7 @@ describe("Request Middleware", () => {
45
46
  expect(unauthorizedRes.status).toBe(401);
46
47
  const unauthorizedBody = await unauthorizedRes.json();
47
48
  expect(unauthorizedBody).toEqual({
48
- error: "Unauthorized",
49
+ message: "Unauthorized",
49
50
  code: "UNAUTHORIZED",
50
51
  });
51
52
 
@@ -134,7 +135,7 @@ describe("Request Middleware", () => {
134
135
  expect(res.status).toBe(403);
135
136
 
136
137
  expect(await res.json()).toEqual({
137
- error: "Creating users has been disabled.",
138
+ message: "Creating users has been disabled.",
138
139
  code: "CREATE_USERS_DISABLED",
139
140
  });
140
141
 
@@ -273,7 +274,7 @@ describe("Request Middleware", () => {
273
274
  }),
274
275
  ] as const;
275
276
 
276
- const instance = createFragment(fragment, config, routes);
277
+ const instance = createFragment(fragment, config, routes, {});
277
278
 
278
279
  const withMiddleware = instance.withMiddleware(async () => {
279
280
  return undefined;
@@ -439,7 +440,7 @@ describe("Request Middleware", () => {
439
440
 
440
441
  const body = await res.json();
441
442
  expect(body).toEqual({
442
- error: "Request validation failed in middleware",
443
+ message: "Request validation failed in middleware",
443
444
  code: "MIDDLEWARE_VALIDATION_ERROR",
444
445
  });
445
446
  });
@@ -51,16 +51,16 @@ export abstract class OutputContext<const TOutput, const TErrorCode extends stri
51
51
  headers?: HeadersInit,
52
52
  ): Response {
53
53
  if (typeof initOrStatus === "undefined") {
54
- return Response.json({ error: message, code }, { status: 500, headers });
54
+ return Response.json({ message: message, code }, { status: 500, headers });
55
55
  }
56
56
 
57
57
  if (typeof initOrStatus === "number") {
58
- return Response.json({ error: message, code }, { status: initOrStatus, headers });
58
+ return Response.json({ message: message, code }, { status: initOrStatus, headers });
59
59
  }
60
60
 
61
61
  const mergedHeaders = mergeHeaders(initOrStatus.headers, headers);
62
62
  return Response.json(
63
- { error: message, code },
63
+ { message: message, code },
64
64
  { status: initOrStatus.status, headers: mergedHeaders },
65
65
  );
66
66
  }
package/src/api/route.ts CHANGED
@@ -125,14 +125,7 @@ export function defineRoute<
125
125
  return config;
126
126
  }
127
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
- >() {
128
+ export function defineRoutes<TConfig = {}, TDeps = {}, TServices = {}>() {
136
129
  return {
137
130
  create: <
138
131
  const TRoutes extends readonly FragnoRouteConfig<
@@ -2,8 +2,8 @@ import { test, expect, expectTypeOf, describe } from "vitest";
2
2
  import { z } from "zod";
3
3
  import { createClientBuilder } from "./client";
4
4
  import { addRoute } from "../api/api";
5
- import { defineFragment } from "../api/fragment";
6
- import type { FragnoPublicClientConfig } from "../api/fragment";
5
+ import { defineFragment } from "../api/fragment-builder";
6
+ import type { FragnoPublicClientConfig } from "../api/fragment-instantiation";
7
7
 
8
8
  // Test route configurations
9
9
  const testFragment = defineFragment("test-fragment");
@@ -1,6 +1,7 @@
1
1
  import { test, expect, describe } from "vitest";
2
- import { FragnoClientApiError } from "./client-error";
2
+ import { FragnoClientApiError, FragnoClientUnknownApiError } from "./client-error";
3
3
  import { FragnoApiError } from "../api/error";
4
+ import { RequestOutputContext } from "../api/request-output-context";
4
5
 
5
6
  describe("Error Conversion", () => {
6
7
  test("should convert API error to client error", async () => {
@@ -12,4 +13,19 @@ describe("Error Conversion", () => {
12
13
  expect(clientError.code).toBe("API_ERROR");
13
14
  expect(clientError.status).toBe(500);
14
15
  });
16
+
17
+ test("error() should never result in an unknown error", async () => {
18
+ const ctx = new RequestOutputContext();
19
+ const response = ctx.error({ message: "test", code: "MY_TEST_ERROR" }, { status: 400 });
20
+
21
+ expect(response).toBeInstanceOf(Response);
22
+ expect(response.status).toBe(400);
23
+
24
+ const clientError = await FragnoClientApiError.fromResponse(response);
25
+ expect(clientError).toBeInstanceOf(FragnoClientApiError);
26
+ expect(clientError).not.toBeInstanceOf(FragnoClientUnknownApiError);
27
+ expect(clientError.message).toBe("test");
28
+ expect(clientError.code).toBe("MY_TEST_ERROR");
29
+ expect(clientError.status).toBe(400);
30
+ });
15
31
  });
@@ -10,7 +10,7 @@ import { describe, expect, test } from "vitest";
10
10
  import { type FragnoPublicClientConfig } from "../mod";
11
11
  import { createClientBuilder } from "./client";
12
12
  import { defineRoute } from "../api/route";
13
- import { defineFragment } from "../api/fragment";
13
+ import { defineFragment } from "../api/fragment-builder";
14
14
  import { z } from "zod";
15
15
  import { createAsyncIteratorFromCallback, waitForAsyncIterator } from "../util/async";
16
16
 
@@ -3,7 +3,7 @@ import { type FragnoPublicClientConfig } from "../mod";
3
3
  import { createClientBuilder } from "./client";
4
4
  import { render } from "@testing-library/svelte";
5
5
  import { defineRoute } from "../api/route";
6
- import { defineFragment } from "../api/fragment";
6
+ import { defineFragment } from "../api/fragment-builder";
7
7
  import { z } from "zod";
8
8
  import { readableToAtom, useFragno } from "./client.svelte";
9
9
  import { writable, readable, get, derived } from "svelte/store";
@@ -6,7 +6,7 @@ import { useFragno } from "./vanilla";
6
6
  import { createAsyncIteratorFromCallback, waitForAsyncIterator } from "../util/async";
7
7
  import type { FragnoPublicClientConfig } from "../mod";
8
8
  import { atom, computed, effect } from "nanostores";
9
- import { defineFragment } from "../api/fragment";
9
+ import { defineFragment } from "../api/fragment-builder";
10
10
  import { RequestOutputContext } from "../api/request-output-context";
11
11
  import { FragnoClientUnknownApiError } from "./client-error";
12
12
 
@@ -12,7 +12,10 @@ import {
12
12
  import { getMountRoute } from "../api/internal/route";
13
13
  import { RequestInputContext } from "../api/request-input-context";
14
14
  import { RequestOutputContext } from "../api/request-output-context";
15
- import type { FragnoFragmentSharedConfig, FragnoPublicClientConfig } from "../api/fragment";
15
+ import type {
16
+ FragnoFragmentSharedConfig,
17
+ FragnoPublicClientConfig,
18
+ } from "../api/fragment-instantiation";
16
19
  import { FragnoClientApiError, FragnoClientError, FragnoClientFetchError } from "./client-error";
17
20
  import type { InferOr } from "../util/types-util";
18
21
  import { parseContentType } from "../util/content-type";
@@ -22,7 +25,7 @@ import {
22
25
  } from "./internal/ndjson-streaming";
23
26
  import { addStore, getInitialData, SSR_ENABLED } from "../util/ssr";
24
27
  import { unwrapObject } from "../util/nanostores";
25
- import type { FragmentBuilder } from "../api/fragment";
28
+ import type { FragmentBuilder } from "../api/fragment-builder";
26
29
  import {
27
30
  type AnyRouteOrFactory,
28
31
  type FlattenRouteFactories,
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  import { createClientBuilder } from "./client";
6
6
  import { useFragno, useStore, type FragnoReactStore } from "./react";
7
7
  import { defineRoute } from "../api/route";
8
- import { defineFragment } from "../api/fragment";
8
+ import { defineFragment } from "../api/fragment-builder";
9
9
  import type { FragnoPublicClientConfig } from "../mod";
10
10
  import { FragnoClientFetchNetworkError, type FragnoClientError } from "./client-error";
11
11
  import { RequestOutputContext } from "../api/request-output-context";