@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.
- package/.turbo/turbo-build.log +54 -43
- package/.turbo/turbo-test.log +297 -0
- package/CHANGELOG.md +14 -0
- package/dist/api/api.d.ts +1 -1
- package/dist/api/api.js +1 -1
- package/dist/api/fragment-builder.d.ts +3 -0
- package/dist/api/fragment-builder.js +3 -0
- package/dist/api/fragment-instantiation.d.ts +3 -0
- package/dist/api/fragment-instantiation.js +5 -0
- package/dist/{api-CBDGZiLC.d.ts → api-Dcr4_-3g.d.ts} +3 -3
- package/dist/api-Dcr4_-3g.d.ts.map +1 -0
- package/dist/{api-DgHfYjq2.js → api-DngJDcmO.js} +2 -2
- package/dist/{api-DgHfYjq2.js.map → api-DngJDcmO.js.map} +1 -1
- package/dist/client/client.d.ts +2 -2
- package/dist/client/client.js +4 -4
- package/dist/client/client.svelte.d.ts +14 -14
- package/dist/client/client.svelte.d.ts.map +1 -1
- package/dist/client/client.svelte.js +4 -4
- package/dist/client/react.d.ts +12 -12
- package/dist/client/react.d.ts.map +1 -1
- package/dist/client/react.js +6 -8
- package/dist/client/react.js.map +1 -1
- package/dist/client/solid.d.ts +45 -0
- package/dist/client/solid.d.ts.map +1 -0
- package/dist/client/solid.js +110 -0
- package/dist/client/solid.js.map +1 -0
- package/dist/client/vanilla.d.ts +21 -21
- package/dist/client/vanilla.d.ts.map +1 -1
- package/dist/client/vanilla.js +4 -4
- package/dist/client/vue.d.ts +14 -14
- package/dist/client/vue.d.ts.map +1 -1
- package/dist/client/vue.js +4 -4
- package/dist/{client-DWjxKDnE.js → client-CZCasGGB.js} +7 -10
- package/dist/client-CZCasGGB.js.map +1 -0
- package/dist/fragment-builder-DOnCVBqc.js +47 -0
- package/dist/fragment-builder-DOnCVBqc.js.map +1 -0
- package/dist/fragment-builder-Dcdsms1l.d.ts +356 -0
- package/dist/fragment-builder-Dcdsms1l.d.ts.map +1 -0
- package/dist/fragment-instantiation-f4AhwQss.js +197 -0
- package/dist/fragment-instantiation-f4AhwQss.js.map +1 -0
- package/dist/integrations/react-ssr.js +1 -1
- package/dist/mod.d.ts +3 -3
- package/dist/mod.js +5 -204
- package/dist/{route-Bp6eByhz.js → route-B4RbOWjd.js} +6 -6
- package/dist/route-B4RbOWjd.js.map +1 -0
- package/dist/{ssr-tJHqcNSw.js → ssr-CamRrMc0.js} +2 -2
- package/dist/{ssr-tJHqcNSw.js.map → ssr-CamRrMc0.js.map} +1 -1
- package/package.json +34 -5
- package/src/api/fragment-builder.ts +81 -0
- package/src/api/{fragment.ts → fragment-instantiation.ts} +65 -67
- package/src/api/fragment.test.ts +44 -16
- package/src/api/request-middleware.test.ts +6 -5
- package/src/api/request-output-context.ts +3 -3
- package/src/api/route.ts +1 -8
- package/src/client/client-builder.test.ts +2 -2
- package/src/client/client-error.test.ts +17 -1
- package/src/client/client.ssr.test.ts +1 -1
- package/src/client/client.svelte.test.ts +1 -1
- package/src/client/client.test.ts +1 -1
- package/src/client/client.ts +5 -2
- package/src/client/react.test.ts +1 -1
- package/src/client/solid.test.ts +840 -0
- package/src/client/solid.ts +261 -0
- package/src/client/vanilla.test.ts +1 -1
- package/src/client/vue.test.ts +1 -1
- package/src/mod.ts +3 -3
- package/tsdown.config.ts +3 -0
- package/vitest.config.ts +10 -7
- package/dist/api-CBDGZiLC.d.ts.map +0 -1
- package/dist/client-B6s-lTFe.d.ts +0 -315
- package/dist/client-B6s-lTFe.d.ts.map +0 -1
- package/dist/client-DWjxKDnE.js.map +0 -1
- package/dist/mod.js.map +0 -1
- 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 =
|
|
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
|
-
|
|
119
|
+
fragmentBuilder: {
|
|
120
|
+
definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;
|
|
121
|
+
$requiredOptions: TOptions;
|
|
122
|
+
},
|
|
151
123
|
config: TConfig,
|
|
152
124
|
routesOrFactories: TRoutesOrFactories,
|
|
153
|
-
|
|
154
|
-
): FragnoInstantiatedFragment<
|
|
155
|
-
|
|
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
|
|
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:
|
|
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];
|
package/src/api/fragment.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { test, expect, describe, expectTypeOf } from "vitest";
|
|
2
|
-
import { defineFragment,
|
|
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((
|
|
39
|
-
expectTypeOf(
|
|
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((
|
|
66
|
-
expectTypeOf(
|
|
68
|
+
.withDependencies(({ config }) => {
|
|
69
|
+
expectTypeOf(config).toEqualTypeOf<{
|
|
67
70
|
apiKey: string;
|
|
68
71
|
baseUrl: string;
|
|
69
72
|
}>();
|
|
70
73
|
|
|
71
|
-
return { httpClient: { baseUrl:
|
|
74
|
+
return { httpClient: { baseUrl: config.baseUrl } };
|
|
72
75
|
})
|
|
73
|
-
.withServices((
|
|
74
|
-
expectTypeOf(
|
|
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<
|
|
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((
|
|
177
|
-
client: `Client for ${
|
|
182
|
+
.withDependencies(({ config }) => ({
|
|
183
|
+
client: `Client for ${config.apiKey}`,
|
|
178
184
|
}))
|
|
179
|
-
.withServices((
|
|
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((
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
|
54
|
+
return Response.json({ message: message, code }, { status: 500, headers });
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
if (typeof initOrStatus === "number") {
|
|
58
|
-
return Response.json({
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
|
package/src/client/client.ts
CHANGED
|
@@ -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 {
|
|
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,
|
package/src/client/react.test.ts
CHANGED
|
@@ -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";
|