@effectify/react-router 0.4.9 → 0.5.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/README.md CHANGED
@@ -26,7 +26,7 @@ Create a server runtime with your Effect layers:
26
26
 
27
27
  ```typescript
28
28
  // lib/server-runtime.ts
29
- import { make } from "@effectify/react-router";
29
+ import { make } from "@effectify/react-router"
30
30
  import * as Layer from "effect/Layer"
31
31
 
32
32
  const layers = Layer.empty
@@ -40,22 +40,22 @@ Use the Effect-based loaders and actions in your React Router routes:
40
40
 
41
41
  ```typescript
42
42
  // routes/home.tsx
43
- import type * as Route from "./+types.home";
44
- import { httpSuccess, httpFailure, LoaderArgsContext } from "@effectify/react-router";
45
- import { withLoaderEffect } from "~/lib/server-runtime";
43
+ import type * as Route from "./+types.home"
44
+ import { httpFailure, httpSuccess, LoaderArgsContext } from "@effectify/react-router"
45
+ import { withLoaderEffect } from "~/lib/server-runtime"
46
46
  import * as Effect from "effect/Effect"
47
47
 
48
48
  export const loader = withLoaderEffect(
49
- Effect.gen(function* () {
49
+ Effect.gen(function*() {
50
50
  const { request } = yield* LoaderArgsContext
51
51
  yield* Effect.log("request", request)
52
-
52
+
53
53
  // Improved DX: Simple syntax for success responses
54
- return yield* httpSuccess({ hello: 'world' })
55
-
54
+ return yield* httpSuccess({ hello: "world" })
55
+
56
56
  // For error responses, use:
57
57
  // return yield* httpFailure("Something went wrong")
58
- })
58
+ }),
59
59
  )
60
60
 
61
61
  export default function Home({ loaderData }: Route.ComponentProps) {
@@ -75,31 +75,35 @@ The library provides helper functions for better DX when returning HTTP response
75
75
  ### Success Responses
76
76
 
77
77
  Instead of the verbose:
78
+
78
79
  ```typescript
79
- return yield* Effect.succeed(new HttpResponseSuccess({ data: { hello: 'world' }}))
80
+ return yield * Effect.succeed(new HttpResponseSuccess({ data: { hello: "world" } }))
80
81
  ```
81
82
 
82
83
  Use the simplified syntax:
84
+
83
85
  ```typescript
84
- return yield* httpSuccess({ hello: 'world' })
86
+ return yield * httpSuccess({ hello: "world" })
85
87
  ```
86
88
 
87
89
  ### Error Responses
88
90
 
89
91
  For error handling, use the `httpFailure` helper:
92
+
90
93
  ```typescript
91
- return yield* httpFailure("Something went wrong")
94
+ return yield * httpFailure("Something went wrong")
92
95
  // or with more complex error objects
93
- return yield* httpFailure({ code: 'VALIDATION_ERROR', message: 'Invalid input' })
96
+ return yield * httpFailure({ code: "VALIDATION_ERROR", message: "Invalid input" })
94
97
  ```
95
98
 
96
99
  ### Redirects
97
100
 
98
101
  For redirects, use the `httpRedirect` helper:
102
+
99
103
  ```typescript
100
- return yield* httpRedirect('/login')
104
+ return yield * httpRedirect("/login")
101
105
  // or with custom status/headers
102
- return yield* httpRedirect('/dashboard', { status: 301 })
106
+ return yield * httpRedirect("/dashboard", { status: 301 })
103
107
  ```
104
108
 
105
109
  ## API
@@ -115,12 +119,14 @@ Creates Effect-based runtime helpers for React Router.
115
119
  #### Returns
116
120
 
117
121
  An object containing:
122
+
118
123
  - `withLoaderEffect`: Wrapper for React Router loaders using Effect
119
124
  - `withActionEffect`: Wrapper for React Router actions using Effect
120
125
 
121
126
  ### `LoaderArgsContext`
122
127
 
123
128
  Effect context providing access to React Router loader arguments including:
129
+
124
130
  - `request`: The incoming Request object
125
131
  - `params`: Route parameters
126
132
  - `context`: Additional context data
@@ -150,7 +156,7 @@ The library provides comprehensive error handling with full ErrorBoundary suppor
150
156
 
151
157
  ```typescript
152
158
  // The library automatically logs errors like this:
153
- Effect.tapError((cause) => Effect.logError('Loader effect failed', cause))
159
+ Effect.tapError((cause) => Effect.logError("Loader effect failed", cause))
154
160
 
155
161
  // Loader errors are automatically converted to Response objects for ErrorBoundary:
156
162
  // - Effect errors → Response with { ok: false, errors: [...] } and status 500
@@ -167,7 +173,7 @@ const runtime = make(pipe(
167
173
  // Your app layers
168
174
  MyAppLayer,
169
175
  // Custom logger layer
170
- Logger.replace(Logger.defaultLogger, customLogger)
176
+ Logger.replace(Logger.defaultLogger, customLogger),
171
177
  ))
172
178
  ```
173
179
 
@@ -178,4 +184,4 @@ const runtime = make(pipe(
178
184
 
179
185
  ## License
180
186
 
181
- MIT
187
+ MIT
@@ -1,4 +1,4 @@
1
- export * from './lib/context.js';
2
- export * as HttpApiHandler from './lib/http-api-handler.js';
3
- export * from './lib/http-response.js';
4
- export * as Runtime from './lib/runtime.js';
1
+ export * from "./lib/context.js";
2
+ export * as HttpApiHandler from "./lib/http-api-handler.js";
3
+ export * from "./lib/http-response.js";
4
+ export * as Runtime from "./lib/runtime.js";
package/dist/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export * from './lib/context.js';
2
- export * as HttpApiHandler from './lib/http-api-handler.js';
3
- export * from './lib/http-response.js';
4
- export * as Runtime from './lib/runtime.js';
1
+ export * from "./lib/context.js";
2
+ export * as HttpApiHandler from "./lib/http-api-handler.js";
3
+ export * from "./lib/http-response.js";
4
+ export * as Runtime from "./lib/runtime.js";
@@ -1,5 +1,5 @@
1
- import * as Context from 'effect/Context';
2
- import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
1
+ import * as Context from "effect/Context";
2
+ import type { ActionFunctionArgs, LoaderFunctionArgs } from "react-router";
3
3
  declare const ActionArgsContext_base: Context.TagClass<ActionArgsContext, "ActionArgsContext", ActionFunctionArgs<any>>;
4
4
  export declare class ActionArgsContext extends ActionArgsContext_base {
5
5
  }
@@ -1,5 +1,5 @@
1
- import * as Context from 'effect/Context';
2
- export class ActionArgsContext extends Context.Tag('ActionArgsContext')() {
1
+ import * as Context from "effect/Context";
2
+ export class ActionArgsContext extends Context.Tag("ActionArgsContext")() {
3
3
  }
4
- export class LoaderArgsContext extends Context.Tag('LoaderArgsContext')() {
4
+ export class LoaderArgsContext extends Context.Tag("LoaderArgsContext")() {
5
5
  }
@@ -1,13 +1,12 @@
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';
1
+ import type * as HttpApi from "@effect/platform/HttpApi";
2
+ import * as HttpApiScalar from "@effect/platform/HttpApiScalar";
3
+ import * as Layer from "effect/Layer";
4
+ import type { ActionFunctionArgs, LoaderFunctionArgs } from "react-router";
6
5
  export type HttpApiOptions = {
7
6
  apiLive: Layer.Layer<HttpApi.Api, never, never>;
8
7
  scalar?: HttpApiScalar.ScalarConfig;
9
8
  };
10
- export type RoutePath = '/' | `/${string}/`;
9
+ export type RoutePath = "/" | `/${string}/`;
11
10
  export declare const make: (options: HttpApiOptions & {
12
11
  pathPrefix?: RoutePath;
13
12
  }) => ({ request }: ActionFunctionArgs | LoaderFunctionArgs) => Promise<Response>;
@@ -1,11 +1,11 @@
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';
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
7
  export const make = (options) => ({ request }) => pipe(Option.fromNullable(options.scalar), Option.map((scalar) => HttpApiScalar.layer({
8
- path: `${options.pathPrefix || '/api/'}docs`,
8
+ path: `${options.pathPrefix || "/api/"}docs`,
9
9
  scalar: {
10
10
  ...scalar,
11
11
  baseServerURL: new URL(request.url).origin,
@@ -1,4 +1,4 @@
1
- import * as Effect from 'effect/Effect';
1
+ import * as Effect from "effect/Effect";
2
2
  declare const HttpResponseSuccess_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => Readonly<A> & {
3
3
  readonly _tag: "HttpResponseSuccess";
4
4
  };
@@ -1,11 +1,11 @@
1
- import * as Data from 'effect/Data';
2
- import * as Effect from 'effect/Effect';
3
- import * as Match from 'effect/Match';
4
- export class HttpResponseSuccess extends Data.TaggedClass('HttpResponseSuccess') {
1
+ import * as Data from "effect/Data";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Match from "effect/Match";
4
+ export class HttpResponseSuccess extends Data.TaggedClass("HttpResponseSuccess") {
5
5
  }
6
- export class HttpResponseFailure extends Data.TaggedClass('HttpResponseFailure') {
6
+ export class HttpResponseFailure extends Data.TaggedClass("HttpResponseFailure") {
7
7
  }
8
- export class HttpResponseRedirect extends Data.TaggedClass('HttpResponseRedirect') {
8
+ export class HttpResponseRedirect extends Data.TaggedClass("HttpResponseRedirect") {
9
9
  }
10
10
  export const matchHttpResponse = () => Match.typeTags();
11
11
  // Helper functions for better DX
@@ -1,8 +1,8 @@
1
- import * as Effect from 'effect/Effect';
2
- import type * as Layer from 'effect/Layer';
3
- import { type ActionFunctionArgs, type LoaderFunctionArgs } from 'react-router';
4
- import { ActionArgsContext, LoaderArgsContext } from '../lib/context.js';
5
- import { type HttpResponse } from '../lib/http-response.js';
1
+ import * as Effect from "effect/Effect";
2
+ import type * as Layer from "effect/Layer";
3
+ import { type ActionFunctionArgs, type LoaderFunctionArgs } from "react-router";
4
+ import { ActionArgsContext, LoaderArgsContext } from "../lib/context.js";
5
+ import { type HttpResponse } from "../lib/http-response.js";
6
6
  export declare const make: <R, E>(layer: Layer.Layer<R, E, never>) => {
7
7
  withLoaderEffect: <A, B, R0 extends R | LoaderArgsContext>(self: Effect.Effect<HttpResponse<A> | Response, B, R0>) => (args: LoaderFunctionArgs) => Promise<{
8
8
  ok: true;
@@ -1,18 +1,18 @@
1
- import * as Effect from 'effect/Effect';
2
- import * as Exit from 'effect/Exit';
3
- import { pipe } from 'effect/Function';
4
- import * as Logger from 'effect/Logger';
5
- import * as ManagedRuntime from 'effect/ManagedRuntime';
6
- import { data, redirect } from 'react-router';
7
- import { ActionArgsContext, LoaderArgsContext } from '../lib/context.js';
8
- import { matchHttpResponse } from '../lib/http-response.js';
1
+ import * as Effect from "effect/Effect";
2
+ import * as Exit from "effect/Exit";
3
+ import { pipe } from "effect/Function";
4
+ import * as Logger from "effect/Logger";
5
+ import * as ManagedRuntime from "effect/ManagedRuntime";
6
+ import { data, redirect } from "react-router";
7
+ import { ActionArgsContext, LoaderArgsContext } from "../lib/context.js";
8
+ import { matchHttpResponse } from "../lib/http-response.js";
9
9
  export const make = (layer) => {
10
10
  const runtime = ManagedRuntime.make(layer);
11
11
  const withLoaderEffect = (self) => (args) => {
12
- const runnable = pipe(self, Effect.provide(Logger.pretty), Effect.provideService(LoaderArgsContext, args), Effect.tapError((cause) => Effect.logError('Loader effect failed', cause)));
12
+ const runnable = pipe(self, Effect.provide(Logger.pretty), Effect.provideService(LoaderArgsContext, args), Effect.tapError((cause) => Effect.logError("Loader effect failed", cause)));
13
13
  return runtime.runPromiseExit(runnable).then(Exit.match({
14
14
  onFailure: (cause) => {
15
- if (cause._tag === 'Fail') {
15
+ if (cause._tag === "Fail") {
16
16
  // Preserve the original error for ErrorBoundary
17
17
  const error = cause.error;
18
18
  if (error instanceof Response) {
@@ -25,15 +25,15 @@ export const make = (layer) => {
25
25
  const errorData = { ok: false, errors: [String(error)] };
26
26
  throw new Response(JSON.stringify(errorData), {
27
27
  status: 500,
28
- headers: { 'Content-Type': 'application/json' },
28
+ headers: { "Content-Type": "application/json" },
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));
33
- const errorData = { ok: false, errors: ['Internal server error'] };
32
+ console.error("Runtime execution failed with defect:", JSON.stringify(cause, null, 2));
33
+ const errorData = { ok: false, errors: ["Internal server error"] };
34
34
  throw new Response(JSON.stringify(errorData), {
35
35
  status: 500,
36
- headers: { 'Content-Type': 'application/json' },
36
+ headers: { "Content-Type": "application/json" },
37
37
  });
38
38
  },
39
39
  onSuccess: (result) => {
@@ -47,16 +47,16 @@ export const make = (layer) => {
47
47
  HttpResponseSuccess: ({ data: response }) => ({ ok: true, data: response }),
48
48
  HttpResponseFailure: ({ cause }) => {
49
49
  // Convert HttpResponseFailure to Response for ErrorBoundary with ok: false
50
- const errorMessage = typeof cause === 'string' ? cause : String(cause);
50
+ const errorMessage = typeof cause === "string" ? cause : String(cause);
51
51
  const errorData = { ok: false, errors: [errorMessage] };
52
52
  throw new Response(JSON.stringify(errorData), {
53
53
  status: 500,
54
- headers: { 'Content-Type': 'application/json' },
54
+ headers: { "Content-Type": "application/json" },
55
55
  });
56
56
  },
57
57
  HttpResponseRedirect: ({ to, init = {} }) => {
58
58
  redirect(to, init);
59
- return { ok: false, errors: ['Redirecting...'] };
59
+ return { ok: false, errors: ["Redirecting..."] };
60
60
  },
61
61
  })(result);
62
62
  },
@@ -67,13 +67,13 @@ export const make = (layer) => {
67
67
  const runnable = pipe(self, Effect.provide(Logger.pretty), Effect.provideService(ActionArgsContext, args), Effect.tapError((cause) => {
68
68
  // Don't log if it's a Response - that's expected behavior
69
69
  if (!(cause instanceof Response)) {
70
- return Effect.logError('Action effect failed', cause);
70
+ return Effect.logError("Action effect failed", cause);
71
71
  }
72
72
  return Effect.void;
73
73
  }));
74
74
  return runtime.runPromiseExit(runnable).then(Exit.match({
75
75
  onFailure: (cause) => {
76
- if (cause._tag === 'Fail') {
76
+ if (cause._tag === "Fail") {
77
77
  const error = cause.error;
78
78
  // If the error is a Response, throw it directly to preserve headers
79
79
  if (error instanceof Response) {
@@ -82,7 +82,7 @@ export const make = (layer) => {
82
82
  return data({ ok: false, errors: [String(error)] }, { status: 400 });
83
83
  }
84
84
  // Handle other types of failures
85
- return data({ ok: false, errors: ['Internal server error'] }, { status: 400 });
85
+ return data({ ok: false, errors: ["Internal server error"] }, { status: 400 });
86
86
  },
87
87
  onSuccess: (result) => {
88
88
  // If the result is a Response, throw it directly to preserve headers (including Set-Cookie)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effectify/react-router",
3
- "version": "0.4.9",
3
+ "version": "0.5.0",
4
4
  "description": "Integration of React Router with Effect for React applications",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -24,7 +24,7 @@
24
24
  "peerDependencies": {
25
25
  "@effect/platform": "0.94.0",
26
26
  "@effect/platform-node": "0.104.0",
27
- "effect": "3.19.13",
27
+ "effect": "3.19.15",
28
28
  "react-router": "7.9.6"
29
29
  },
30
30
  "devDependencies": {},