Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add toWebHandlerLayer to loaders/actions #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 117 additions & 52 deletions app/services.server/Remix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import { MigratorLive } from "./Database";
import { Password } from "./Password";
import { Users } from "./Users";

const AppLayer = Layer.mergeAll(Auth.layer, Users.layer, Password.layer, MigratorLive);
const AppLayer = Layer.mergeAll(
Auth.layer,
Users.layer,
Password.layer,
MigratorLive
);

const runtime = ManagedRuntime.make(AppLayer);

Expand All @@ -18,45 +23,69 @@ const Params = Context.GenericTag<Params, RemixParams>("@services/Params");

type AppEnv = Layer.Layer.Success<typeof AppLayer>;

type RequestEnv = HttpServer.request.ServerRequest | Params | Session;
type RequestEnv = Layer.Layer.Success<ReturnType<typeof makeRequestContext>>;

export interface RemixHandler<E, R> extends
Effect.Effect<
export interface RemixHandler<E, R>
extends Effect.Effect<
HttpServer.response.ServerResponse,
E | HttpServer.response.ServerResponse,
R | AppEnv | RequestEnv
>
{}
> {}

export const makeServerContext = (
args: LoaderFunctionArgs | ActionFunctionArgs,
export const makeRequestContext = (
args: LoaderFunctionArgs | ActionFunctionArgs
) =>
Layer.provideMerge(
Session.layer,
Layer.succeedContext(
Context.empty().pipe(
Context.add(
HttpServer.request.ServerRequest,
HttpServer.request.fromWeb(args.request),
HttpServer.request.fromWeb(args.request)
),
Context.add(Params, args.params),
),
),
Context.add(Params, args.params)
)
)
);

export const loader =
<E, R extends AppEnv | RequestEnv>(effect: RemixHandler<E, R>) => async (args: LoaderFunctionArgs) =>
effect.pipe(
Effect.map(HttpServer.response.toWeb),
Effect.provide(makeServerContext(args)),
runtime.runPromise,
export const loader = <E, R extends AppEnv | RequestEnv>(
effect: RemixHandler<E, R>
) => {
const webHandler = effect.pipe(
Effect.map((response) =>
HttpServer.app.toWebHandlerLayer(response, AppLayer)
)
);

return async (args: LoaderFunctionArgs) => {
const { close, handler } = await webHandler.pipe(
Effect.provide(makeRequestContext(args)),
runtime.runPromise
);

export const unwrapLoader = <E1, R1 extends AppEnv | RequestEnv, E2, R2 extends AppEnv>(
effect: Effect.Effect<RemixHandler<E1, R1>, E2, R2>,
process.on("SIGTERM", () => {
close().then(() => process.exit(0));
});

process.on("SIGINT", () => {
close().then(() => process.exit(0));
});

return handler(args.request);
};
};

export const unwrapLoader = <
E1,
R1 extends AppEnv | RequestEnv,
E2,
R2 extends AppEnv
>(
effect: Effect.Effect<RemixHandler<E1, R1>, E2, R2>
) => {
const awaitedHandler = runtime.runPromise(effect).then(action);
return async (args: LoaderFunctionArgs) => awaitedHandler.then(handler => handler(args));
return async (args: LoaderFunctionArgs) =>
awaitedHandler.then((handler) => handler(args));
};

// <Eff extends YieldWrap<Effect<any, any, any>>, AEff>(
Expand All @@ -70,99 +99,135 @@ export const loaderGen: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv | RequestEnv>>>(
f: (
resume: Effect.Adapter,
resume: Effect.Adapter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Generator<Eff, HttpServer.response.ServerResponse, never>,
) => Generator<Eff, HttpServer.response.ServerResponse, never>
): (args: LoaderFunctionArgs) => Promise<Response>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<Self, Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv | RequestEnv>>>(
<
Self,
Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv | RequestEnv>>
>(
self: Self,
f: (
this: Self,
resume: Effect.Adapter,
resume: Effect.Adapter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Generator<Eff, HttpServer.response.ServerResponse, never>,
) => Generator<Eff, HttpServer.response.ServerResponse, never>
): (args: LoaderFunctionArgs) => Promise<Response>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} = (...args: [any]) => loader(Effect.gen(...args));

export const unwrapLoaderGen: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv>>, AEff extends RemixHandler<any, any>>(
<
Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv>>,
AEff extends RemixHandler<any, any>
>(
f: (
resume: Effect.Adapter,
resume: Effect.Adapter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Generator<Eff, AEff, any>,
) => Generator<Eff, AEff, any>
): (args: LoaderFunctionArgs) => Promise<Response>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<
Self,
Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv | RequestEnv>>,
AEff extends RemixHandler<any, any>,
AEff extends RemixHandler<any, any>
>(
self: Self,
f: (
this: Self,
resume: Effect.Adapter,
resume: Effect.Adapter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Generator<Eff, AEff, any>,
) => Generator<Eff, AEff, any>
): (args: LoaderFunctionArgs) => Promise<Response>;
} = (...args: [any]) => unwrapLoader(Effect.gen(...args) as any);

export const action =
<E, R extends AppEnv | RequestEnv>(effect: RemixHandler<E, R>) => async (args: ActionFunctionArgs) =>
effect.pipe(
Effect.map(HttpServer.response.toWeb),
Effect.provide(makeServerContext(args)),
runtime.runPromise,
export const action = <E, R extends AppEnv | RequestEnv>(
effect: RemixHandler<E, R>
) => {
const webHandler = effect.pipe(
Effect.map((response) =>
HttpServer.app.toWebHandlerLayer(response, AppLayer)
Copy link
Author

@stevebluck stevebluck May 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shame there is no dual api for this.

)
);

return async (args: ActionFunctionArgs) => {
const { close, handler } = await webHandler.pipe(
Effect.provide(makeRequestContext(args)),
runtime.runPromise
);

export const unwrapAction = <E1, R1 extends AppEnv | RequestEnv, E2, R2 extends AppEnv>(
effect: Effect.Effect<RemixHandler<E1, R1>, E2, R2>,
process.on("SIGTERM", () => {
close().then(() => process.exit(0));
});

process.on("SIGINT", () => {
close().then(() => process.exit(0));
});

return handler(args.request);
};
};
export const unwrapAction = <
E1,
R1 extends AppEnv | RequestEnv,
E2,
R2 extends AppEnv
>(
effect: Effect.Effect<RemixHandler<E1, R1>, E2, R2>
) => {
const awaitedHandler = runtime.runPromise(effect).then(action);
return async (args: LoaderFunctionArgs) => awaitedHandler.then(handler => handler(args));
return async (args: LoaderFunctionArgs) =>
awaitedHandler.then((handler) => handler(args));
};

export const actionGen: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv | RequestEnv>>>(
f: (
resume: Effect.Adapter,
resume: Effect.Adapter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Generator<Eff, HttpServer.response.ServerResponse, never>,
) => Generator<Eff, HttpServer.response.ServerResponse, never>
): (args: ActionFunctionArgs) => Promise<Response>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<Self, Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv | RequestEnv>>>(
<
Self,
Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv | RequestEnv>>
>(
self: Self,
f: (
this: Self,
resume: Effect.Adapter,
resume: Effect.Adapter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Generator<Eff, HttpServer.response.ServerResponse, never>,
) => Generator<Eff, HttpServer.response.ServerResponse, never>
): (args: ActionFunctionArgs) => Promise<Response>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} = (...args: [any]) => action(Effect.gen(...args));

export const unwrapActionGen: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv>>, AEff extends RemixHandler<any, any>>(
<
Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv>>,
AEff extends RemixHandler<any, any>
>(
f: (
resume: Effect.Adapter,
resume: Effect.Adapter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Generator<Eff, AEff, any>,
) => Generator<Eff, AEff, any>
): (args: ActionFunctionArgs) => Promise<Response>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
<
Self,
Eff extends Utils.YieldWrap<Effect.Effect<any, any, AppEnv | RequestEnv>>,
AEff extends RemixHandler<any, any>,
AEff extends RemixHandler<any, any>
>(
self: Self,
f: (
this: Self,
resume: Effect.Adapter,
resume: Effect.Adapter
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) => Generator<Eff, AEff, any>,
) => Generator<Eff, AEff, any>
): (args: ActionFunctionArgs) => Promise<Response>;
} = (...args: [any]) => unwrapAction(Effect.gen(...args) as any);