JUMBO refactor, still work in progress
This commit is contained in:
106
packages/backend/src/api.ts
Normal file
106
packages/backend/src/api.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import * as Api from "common/Api";
|
||||
import * as Cbor from "common/Cbor";
|
||||
import * as Client from "common/Client";
|
||||
import { Cause, Effect, Either, HashMap, Inspectable, Option, pipe } from "effect";
|
||||
|
||||
/* NOTE We shouldn't need to extract this to a separate type, but if we don't do
|
||||
* it the TypeScript parser in VS Code sort of blows up and the syntax colors
|
||||
* turn wrong.
|
||||
*/
|
||||
type Return<Impl extends Api.ApiBundleImpl<any>> = Effect.Effect<Response, never, Effect.Effect.Context<ReturnType<Impl[keyof Impl]>>>;
|
||||
|
||||
const catchToResponse = Effect.catchAll((error: unknown) => pipe(
|
||||
error,
|
||||
Inspectable.toJSON,
|
||||
Cbor.encode,
|
||||
Effect.map(Client.cborBody),
|
||||
Effect.matchEffect({
|
||||
onSuccess: ({ body, headers }) => Effect.fail(new Response(body, { status: 500, headers })),
|
||||
onFailure: () => Effect.fail(new Response(null, { status: 500 })),
|
||||
}),
|
||||
));
|
||||
|
||||
export const implement = <
|
||||
const Record extends { readonly [_: string]: Api.ApiAny },
|
||||
const Impl extends Api.ApiBundleImpl<Record>,
|
||||
>(
|
||||
bundle: Api.ApiBundle<Record>,
|
||||
impl: Impl,
|
||||
): (key: string, request: Request) => Return<Impl> => {
|
||||
return (key, requestObject) => {
|
||||
/* Force both return types to be `Response`. We can use the error route
|
||||
* for it's short-circuit capabilities.
|
||||
*/
|
||||
const effect: Effect.Effect<Response, Response, any> = Effect.gen(function* () {
|
||||
const maybeApi = HashMap.get(bundle.map, key);
|
||||
if (Option.isNone(maybeApi)) {
|
||||
return RESPONSE_API_NOT_FOUND;
|
||||
}
|
||||
const { value: api } = maybeApi;
|
||||
|
||||
const fn = impl[key];
|
||||
if (fn === undefined) {
|
||||
return RESPONSE_API_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
const request = yield* pipe(
|
||||
requestObject,
|
||||
Client.decodeBody(api.request),
|
||||
catchToResponse,
|
||||
);
|
||||
|
||||
const result = yield* pipe(
|
||||
request,
|
||||
fn,
|
||||
Effect.sandbox,
|
||||
Effect.either,
|
||||
);
|
||||
|
||||
const { status, response } = pipe(
|
||||
result,
|
||||
Either.match({
|
||||
onLeft: (cause) => pipe(
|
||||
cause,
|
||||
Cause.failureOrCause,
|
||||
Either.match({
|
||||
onLeft: (error) => ({
|
||||
status: 400,
|
||||
response: pipe(
|
||||
error,
|
||||
Client.encodeBody(api.error),
|
||||
catchToResponse,
|
||||
),
|
||||
}),
|
||||
onRight: (die) => ({
|
||||
status: 500,
|
||||
response: pipe(
|
||||
die,
|
||||
Inspectable.toJSON,
|
||||
Cbor.encode,
|
||||
Effect.map(Client.cborBody),
|
||||
catchToResponse,
|
||||
),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
onRight: (response) => ({
|
||||
status: 200,
|
||||
response: pipe(
|
||||
response,
|
||||
Client.encodeBody(api.response),
|
||||
catchToResponse,
|
||||
),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
const { body, headers } = yield* response;
|
||||
return new Response(body, { status, headers });
|
||||
});
|
||||
|
||||
return Effect.catchAll(effect, Effect.succeed);
|
||||
};
|
||||
};
|
||||
|
||||
const RESPONSE_API_NOT_FOUND = new Response(null, { status: 404 });
|
||||
const RESPONSE_API_NOT_IMPLEMENTED = new Response(null, { status: 501 });
|
||||
Reference in New Issue
Block a user