react-router-effect 1.0.2 → 2.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/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ export { actionMetaKey } from "./action-meta.ts";
2
+ export type { WithActionMeta } from "./action-meta.ts";
1
3
  export {
2
4
  isRouteError,
3
5
  ReturnableDataError,
@@ -6,4 +8,23 @@ export {
6
8
  } from "./errors.ts";
7
9
  export type { AnyRouteError } from "./errors.ts";
8
10
  export { makeEffectRouteFactory } from "./factory.ts";
9
- export type { ErrorHandler, RequestContextKey } from "./factory.ts";
11
+ export type {
12
+ ActionMetaArgs,
13
+ EffectMiddlewareArgs,
14
+ EffectNext,
15
+ ErrorHandler,
16
+ FactoryTypes,
17
+ RequestContextKey,
18
+ RouteHandlers,
19
+ RouteResult,
20
+ } from "./factory.ts";
21
+ export type {
22
+ BrandData,
23
+ EffectData,
24
+ EffectRouteArgs,
25
+ Register,
26
+ RegisteredMeta,
27
+ RegisteredRecover,
28
+ RouteData,
29
+ Serialize,
30
+ } from "./route-data.ts";
@@ -0,0 +1,151 @@
1
+ import type { Effect } from "effect";
2
+ import type { UNSAFE_DataWithResponseInit, useLoaderData } from "react-router";
3
+
4
+ import type { WithActionMeta } from "./action-meta.ts";
5
+ import type { ReturnableDataError } from "./errors.ts";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Route data of plain Effect loaders and actions.
9
+ // ---------------------------------------------------------------------------
10
+
11
+ /**
12
+ * Registers what the app's route factory turns failures into, so that a
13
+ * loader or action written as a plain `Effect.fn` types its data the way a
14
+ * `makeLoader` call would.
15
+ *
16
+ * A `makeLoader` call sees the factory's handlers as type arguments. A plain
17
+ * Effect loader does not, so without this an error that a handler recovers
18
+ * into data, such as a form error that becomes `{ reply }`, would be missing
19
+ * from `useLoaderData`, `useFetcher` and `Route.ComponentProps`.
20
+ *
21
+ * Declare the types directly rather than reading them from the factory's
22
+ * value: the factory's type depends on every Effect in the app, and so on
23
+ * this registration. The server adapter's `RouteEffectCheck` fails when the
24
+ * registration and the factory disagree.
25
+ *
26
+ * ```ts
27
+ * declare module "react-router-effect" {
28
+ * interface Register {
29
+ * // Each error tag a handler recovers, and the data it recovers into.
30
+ * recover: { FormValidationError: { reply: SubmissionResponse } };
31
+ * // What `actionMeta` returns.
32
+ * actionMeta: { realtimeEventIds: ReadonlyArray<string> };
33
+ * }
34
+ * }
35
+ * ```
36
+ */
37
+ export interface Register {}
38
+
39
+ /** The registered recover map: error tag to recovered data. */
40
+ export type RegisteredRecover = Register extends { readonly recover: infer Recover } ? Recover : {};
41
+
42
+ /** The registered action metadata, or `never` when there is none. */
43
+ export type RegisteredMeta = Register extends { readonly actionMeta: infer Meta } ? Meta : never;
44
+
45
+ /** What a failure of type `E` recovers into: `Respond.early` bodies and registered handlers. */
46
+ type Recovered<E> =
47
+ | (E extends ReturnableDataError<infer Body> ? Body : never)
48
+ | (E extends { readonly _tag: infer Tag extends keyof RegisteredRecover }
49
+ ? RegisteredRecover[Tag]
50
+ : never);
51
+
52
+ /**
53
+ * React Router's own serialization of loader data. React Router does not
54
+ * export it, but a zero-argument function is a server loader to its public
55
+ * `useLoaderData` type, so this gives the same type.
56
+ */
57
+ export type Serialize<T> = ReturnType<typeof useLoaderData<() => Promise<T>>>;
58
+
59
+ /**
60
+ * The data an Effect that succeeds with `A` and fails with `E` gives the
61
+ * component: its success and what its failures recover into, serialized as
62
+ * React Router serializes them, with the registered action metadata.
63
+ */
64
+ export type EffectData<A, E> = WithActionMeta<Serialize<A | Recovered<E>>, RegisteredMeta>;
65
+
66
+ /**
67
+ * The route data of a loader or action, for React Router's hooks:
68
+ * `useFetcher<RouteData<typeof action>>()`. A function that returns an Effect
69
+ * gives its `EffectData`. Any other function passes through, so the hook
70
+ * reads it as it always has.
71
+ */
72
+ export type RouteData<F> = F extends (...args: never[]) => Effect.Effect<infer A, infer E, infer _R>
73
+ ? EffectData<A, E>
74
+ : F;
75
+
76
+ /**
77
+ * Loader data as React Router reads it before serializing: a `Response` adds
78
+ * nothing, and a `data(...)` wrapper gives its body.
79
+ */
80
+ type Unwrapped<A> = A extends Response
81
+ ? never
82
+ : A extends UNSAFE_DataWithResponseInit<infer Body>
83
+ ? Body
84
+ : A;
85
+
86
+ /**
87
+ * The data React Router reads from an Effect through `unstable_SerializesTo`.
88
+ *
89
+ * TypeScript compares this brand whenever it relates two Effects
90
+ * structurally, so it must keep the subtype order of `A` and `E`: when
91
+ * `Effect<A1, E1>` is assignable to `Effect<A2, E2>`, `BrandData<A1, E1>` must
92
+ * be assignable to `BrandData<A2, E2>`. React Router's `Serialize` does not
93
+ * (it turns `unknown` into `undefined`), so the brand holds the data before
94
+ * serialization; `RouteData` applies the actual serialization.
95
+ *
96
+ * - A `void` or `any` success gives `unknown`, which accepts every other brand.
97
+ * - A `never` success gives only what the failures recover into.
98
+ *
99
+ * `Effect` declares its parameters `out`, and TypeScript verifies that
100
+ * annotation against this member, and against it again in every Effect type
101
+ * that declares variance (`Exit`, `Config`, ...). It relates two conditional
102
+ * types branch by branch when their check types are related and their
103
+ * `extends` types are identical. So every conditional here, and in the types
104
+ * it uses, checks `A` or `E` (or a tuple of one) against a type that does not
105
+ * depend on them. A conditional with `A` or `E` on its `extends` side, such as
106
+ * `0 extends 1 & A` to detect `any`, fails the verification: a consumer that
107
+ * checks library types (`skipLibCheck: false`) then gets TS2636 in Effect's own
108
+ * declarations. `test/consumer` checks the built package for that.
109
+ */
110
+ export type BrandData<A, E> = [A] extends [never]
111
+ ? WithActionMeta<Recovered<E>, RegisteredMeta>
112
+ : [A] extends [void]
113
+ ? unknown
114
+ : WithActionMeta<Unwrapped<A> | Recovered<E>, RegisteredMeta>;
115
+
116
+ declare module "effect/Effect" {
117
+ // React Router reads loader and action data through this brand, so an
118
+ // Effect-returning loader types `Route.ComponentProps` like a promise one.
119
+ interface Effect<out A, out E, out R> {
120
+ readonly unstable__ReactRouter_SerializesTo: [BrandData<A, E>];
121
+ }
122
+ }
123
+
124
+ declare module "effect/Cause" {
125
+ // A yieldable error is structurally an Effect that never succeeds, so it
126
+ // needs the brand too, or it stops being assignable to `Effect`.
127
+ interface YieldableError {
128
+ readonly unstable__ReactRouter_SerializesTo: [never];
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Route args for a loader or action written as a plain `Effect.fn`, such as
134
+ * `EffectRouteArgs<Route.ActionArgs>`. It keeps hooks that take `typeof loader`
135
+ * or `typeof action` typed, such as `useFetcher<typeof action>()`.
136
+ *
137
+ * Those hooks read a function's data through React Router's `SerializeFrom`.
138
+ * It treats a function whose argument extends `ClientDataFunctionArgs` as a
139
+ * client function, and gives its raw return value without serialization.
140
+ * Typegen's `Route.LoaderArgs` and `Route.ActionArgs` have the same members as
141
+ * `ClientDataFunctionArgs`, so a server function typed with them matches too.
142
+ * For an Effect, the raw return value is the Effect itself: only serialization
143
+ * reads the `unstable_SerializesTo` brand.
144
+ *
145
+ * `pattern` is optional here, so the argument no longer matches and the hooks
146
+ * read the brand. `Route.ComponentProps` and `RouteData` don't go through
147
+ * `SerializeFrom`, so they are correct without it.
148
+ */
149
+ export type EffectRouteArgs<Args extends { readonly pattern: string }> = Omit<Args, "pattern"> & {
150
+ readonly pattern?: string;
151
+ };
@@ -0,0 +1,10 @@
1
+ /** `react-router-effect/server`: serve React Router from the Effect HTTP server. */
2
+ import "./route-data.ts";
3
+
4
+ export * as ReactRouterHttp from "./server.ts";
5
+ export type {
6
+ RegisterMismatch,
7
+ RouteEffectCheck,
8
+ RouteEffectFailure,
9
+ RouteEffectFailures,
10
+ } from "./check.ts";