@effectify/react-router 0.3.0 → 0.4.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/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** biome-ignore-all lint/suspicious/noConsole: <debug> */
|
|
2
|
+
import type * as HttpApi from '@effect/platform/HttpApi';
|
|
3
|
+
import * as HttpApiScalar from '@effect/platform/HttpApiScalar';
|
|
4
|
+
import * as Layer from 'effect/Layer';
|
|
5
|
+
import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
|
|
6
|
+
export type HttpApiOptions = {
|
|
7
|
+
apiLive: Layer.Layer<HttpApi.Api, never, never>;
|
|
8
|
+
scalar?: HttpApiScalar.ScalarConfig;
|
|
9
|
+
};
|
|
10
|
+
export type RoutePath = '/' | `/${string}/`;
|
|
11
|
+
export declare const make: (options: HttpApiOptions & {
|
|
12
|
+
pathPrefix?: RoutePath;
|
|
13
|
+
}) => ({ request }: ActionFunctionArgs | LoaderFunctionArgs) => Promise<Response>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as HttpApiBuilder from '@effect/platform/HttpApiBuilder';
|
|
2
|
+
import * as HttpApiScalar from '@effect/platform/HttpApiScalar';
|
|
3
|
+
import * as HttpServer from '@effect/platform/HttpServer';
|
|
4
|
+
import { Option } from 'effect';
|
|
5
|
+
import { pipe } from 'effect/Function';
|
|
6
|
+
import * as Layer from 'effect/Layer';
|
|
7
|
+
export const make = (options) => ({ request }) => pipe(Option.fromNullable(options.scalar), Option.map((scalar) => HttpApiScalar.layer({
|
|
8
|
+
path: `${options.pathPrefix || '/api/'}docs`,
|
|
9
|
+
scalar: {
|
|
10
|
+
...scalar,
|
|
11
|
+
baseServerURL: new URL(request.url).origin,
|
|
12
|
+
},
|
|
13
|
+
}).pipe(Layer.provide(options.apiLive))), Option.getOrElse(() => Layer.empty), (ApiDocsLive) => {
|
|
14
|
+
const EnvLive = Layer.mergeAll(options.apiLive, ApiDocsLive, HttpServer.layerContext);
|
|
15
|
+
const { handler } = HttpApiBuilder.toWebHandler(EnvLive);
|
|
16
|
+
return handler(request);
|
|
17
|
+
});
|
|
@@ -4,21 +4,21 @@ import { type ActionFunctionArgs, type LoaderFunctionArgs } from 'react-router';
|
|
|
4
4
|
import { ActionArgsContext, LoaderArgsContext } from '../lib/context.js';
|
|
5
5
|
import { type HttpResponse } from '../lib/http-response.js';
|
|
6
6
|
export declare const make: <R, E>(layer: Layer.Layer<R, E, never>) => {
|
|
7
|
-
withLoaderEffect: <A, B>(self: Effect.Effect<HttpResponse<A
|
|
7
|
+
withLoaderEffect: <A, B>(self: Effect.Effect<HttpResponse<A> | Response, B, R | LoaderArgsContext>) => (args: LoaderFunctionArgs) => Promise<{
|
|
8
8
|
ok: true;
|
|
9
9
|
data: A;
|
|
10
10
|
} | {
|
|
11
11
|
ok: false;
|
|
12
12
|
errors: string[];
|
|
13
13
|
}>;
|
|
14
|
-
withActionEffect: <A, B_1>(self: Effect.Effect<HttpResponse<A
|
|
14
|
+
withActionEffect: <A, B_1>(self: Effect.Effect<HttpResponse<A> | Response, B_1, R | ActionArgsContext>) => (args: ActionFunctionArgs) => Promise<Response | import("react-router").UNSAFE_DataWithResponseInit<{
|
|
15
15
|
ok: false;
|
|
16
|
-
errors:
|
|
16
|
+
errors: string[];
|
|
17
17
|
}> | {
|
|
18
18
|
ok: true;
|
|
19
19
|
response: A;
|
|
20
20
|
} | import("react-router").UNSAFE_DataWithResponseInit<{
|
|
21
21
|
ok: false;
|
|
22
22
|
errors: string[];
|
|
23
|
-
}
|
|
23
|
+
}>>;
|
|
24
24
|
};
|
package/dist/src/lib/runtime.js
CHANGED
|
@@ -29,49 +29,75 @@ export const make = (layer) => {
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
// Handle other types of failures (interrupts, defects, etc.)
|
|
32
|
+
console.error('Runtime execution failed with defect:', JSON.stringify(cause, null, 2));
|
|
32
33
|
const errorData = { ok: false, errors: ['Internal server error'] };
|
|
33
34
|
throw new Response(JSON.stringify(errorData), {
|
|
34
35
|
status: 500,
|
|
35
36
|
headers: { 'Content-Type': 'application/json' },
|
|
36
37
|
});
|
|
37
38
|
},
|
|
38
|
-
onSuccess:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
onSuccess: (result) => {
|
|
40
|
+
// If the result is a Response, throw it directly to preserve headers (including Set-Cookie)
|
|
41
|
+
// This is how React Router expects to handle Responses from loaders/actions
|
|
42
|
+
if (result instanceof Response) {
|
|
43
|
+
throw result;
|
|
44
|
+
}
|
|
45
|
+
// Otherwise, match the HttpResponse types
|
|
46
|
+
return matchHttpResponse()({
|
|
47
|
+
HttpResponseSuccess: ({ data: response }) => ({ ok: true, data: response }),
|
|
48
|
+
HttpResponseFailure: ({ cause }) => {
|
|
49
|
+
// Convert HttpResponseFailure to Response for ErrorBoundary with ok: false
|
|
50
|
+
const errorMessage = typeof cause === 'string' ? cause : String(cause);
|
|
51
|
+
const errorData = { ok: false, errors: [errorMessage] };
|
|
52
|
+
throw new Response(JSON.stringify(errorData), {
|
|
53
|
+
status: 500,
|
|
54
|
+
headers: { 'Content-Type': 'application/json' },
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
HttpResponseRedirect: ({ to, init = {} }) => {
|
|
58
|
+
redirect(to, init);
|
|
59
|
+
return { ok: false, errors: ['Redirecting...'] };
|
|
60
|
+
},
|
|
61
|
+
})(result);
|
|
62
|
+
},
|
|
56
63
|
}));
|
|
57
64
|
};
|
|
58
65
|
// Don't throw the Error requests, handle them in the normal UI. No ErrorBoundary
|
|
59
66
|
const withActionEffect = (self) => (args) => {
|
|
60
|
-
const runnable = pipe(self, Effect.provide(Logger.pretty), Effect.provideService(ActionArgsContext, args), Effect.tapError((cause) =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const runnable = pipe(self, Effect.provide(Logger.pretty), Effect.provideService(ActionArgsContext, args), Effect.tapError((cause) => {
|
|
68
|
+
// Don't log if it's a Response - that's expected behavior
|
|
69
|
+
if (!(cause instanceof Response)) {
|
|
70
|
+
return Effect.logError('Action effect failed', cause);
|
|
71
|
+
}
|
|
72
|
+
return Effect.void;
|
|
73
|
+
}));
|
|
74
|
+
return runtime.runPromiseExit(runnable).then(Exit.match({
|
|
75
|
+
onFailure: (cause) => {
|
|
76
|
+
if (cause._tag === 'Fail') {
|
|
77
|
+
const error = cause.error;
|
|
78
|
+
// If the error is a Response, throw it directly to preserve headers
|
|
79
|
+
if (error instanceof Response) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return data({ ok: false, errors: [String(error)] }, { status: 400 });
|
|
83
|
+
}
|
|
84
|
+
// Handle other types of failures
|
|
85
|
+
return data({ ok: false, errors: ['Internal server error'] }, { status: 400 });
|
|
86
|
+
},
|
|
87
|
+
onSuccess: (result) => {
|
|
88
|
+
// If the result is a Response, throw it directly to preserve headers (including Set-Cookie)
|
|
89
|
+
// This is how React Router expects to handle Responses from actions
|
|
90
|
+
if (result instanceof Response) {
|
|
91
|
+
throw result;
|
|
92
|
+
}
|
|
93
|
+
// Otherwise, match the HttpResponse types
|
|
94
|
+
return matchHttpResponse()({
|
|
95
|
+
HttpResponseSuccess: ({ data: response }) => ({ ok: true, response }),
|
|
96
|
+
HttpResponseFailure: ({ cause }) => data({ ok: false, errors: [String(cause)] }, { status: 400 }),
|
|
97
|
+
HttpResponseRedirect: ({ to, init = {} }) => redirect(to, init),
|
|
98
|
+
})(result);
|
|
99
|
+
},
|
|
73
100
|
}));
|
|
74
|
-
return runtime.runPromise(runnable);
|
|
75
101
|
};
|
|
76
102
|
return { withLoaderEffect, withActionEffect };
|
|
77
103
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effectify/react-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Integration of React Router with Effect for React applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"!**/*.tsbuildinfo"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@effect/platform": "0.
|
|
26
|
-
"@effect/platform-node": "0.
|
|
27
|
-
"effect": "3.
|
|
28
|
-
"react-router": "7.9.
|
|
25
|
+
"@effect/platform": "0.93.3",
|
|
26
|
+
"@effect/platform-node": "0.101.1",
|
|
27
|
+
"effect": "3.19.6",
|
|
28
|
+
"react-router": "7.9.6"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {},
|
|
31
31
|
"peerDependencies": {},
|