Last active
June 11, 2023 16:41
-
-
Save darklight9811/78350d0d611ea99ffec96287e8278c14 to your computer and use it in GitHub Desktop.
Nextjs Action Utility
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Packages | |
| import { cache } from "react" | |
| // Types | |
| import type { z } from "zod" | |
| import type { ZodSchema } from "zod" | |
| // ------------------------------------------------- | |
| // Type | |
| // ------------------------------------------------- | |
| type Config<Bind extends Record<string, any>> = { | |
| /** | |
| * ### Bind | |
| * | |
| * Add utility methods to the request object | |
| */ | |
| bind?: Bind; | |
| } | |
| export type PrepareApi< | |
| Bound extends Record<string, any> = {}, | |
| Input = void, | |
| Output = Input, | |
| Service = Bound & { input: Input } | |
| > = { | |
| /** | |
| * ### Parse | |
| * | |
| * Run the piped value | |
| */ | |
| (data: Input): Promise<{ data: Output; ok: true; errors: void } | { errors: any; ok: false; data: void }>; | |
| /** | |
| * ### Auth | |
| * | |
| * Make sure authentication is according to type | |
| */ | |
| auth(callback: (data: Service) => Promise<any | void>): PrepareApi<Bound, Input, Output>; | |
| /** | |
| * ### ZOD | |
| * | |
| * Parse data coming through the last item in the pipe | |
| */ | |
| zod<Schema extends ZodSchema, Result = z.infer<Schema>>( | |
| schema: Schema | |
| ): PrepareApi< | |
| Bound, | |
| Input extends void ? Result : Input, | |
| Result | |
| >; | |
| /** | |
| * ### Map | |
| * | |
| * Run code without delivering it to API | |
| */ | |
| map<Callback extends (data: Service) => Promise<any>, Result = Awaited<ReturnType<Callback>>>( | |
| cb: Callback | |
| ): PrepareApi< | |
| Bound, | |
| Result, | |
| Result | |
| >; | |
| /** | |
| * ### Action | |
| * | |
| * Run code as a server action | |
| */ | |
| action<Callback extends (data: Service) => Promise<any>, Result = Awaited<ReturnType<Callback>>>( | |
| cb: Callback | |
| ): PrepareApi< | |
| Bound, | |
| Input, | |
| Result | |
| >; | |
| /** | |
| * ### Cache | |
| * | |
| * Runs an action through a react cache wrapper, **for this to work you need to | |
| * pass a primite (non object/array)**. | |
| */ | |
| cache<Callback extends (data: Service) => Promise<any>, Result = Awaited<ReturnType<Callback>>>( | |
| cb: Callback | |
| ): PrepareApi< | |
| Bound, | |
| Input, | |
| Result | |
| >; | |
| }; | |
| // ------------------------------------------------- | |
| // Helpers | |
| // ------------------------------------------------- | |
| function parseForm(data: any) { | |
| if (data instanceof FormData) { | |
| const obj = {} as Record<string, any> | |
| data.forEach(function (value, key) { | |
| obj[key] = value | |
| }) | |
| return obj | |
| } | |
| return data | |
| } | |
| // ------------------------------------------------- | |
| // Main | |
| // ------------------------------------------------- | |
| export default function createApi<Bind extends Record<string, any>>(config: Config<Bind>) { | |
| const bound = (config.bind || {}) as Bind | |
| function prepare<Input = void, Output = Input>( | |
| cb: (input: { input: Input } & Bind) => Promise<Output> = async (t) => t as any | |
| ): PrepareApi<Bind, Input, Output> { | |
| const parse: PrepareApi<Bind, Input, Output> = async function parse( | |
| data: any | |
| ): Promise<Output> { | |
| return Promise.resolve(cb({ ...bound, input: data })) | |
| .then(function (data) { | |
| return { ok: true, data } | |
| }) | |
| .catch(function (err) { | |
| if ( | |
| ["ZodError", "AuthError"].includes(err.name) | |
| ) return { | |
| ok: false, | |
| errors: err.errors.map((error: any) => ({ | |
| field: error.path, | |
| message: error.message, | |
| })), | |
| } as any | |
| // we want to unexpected errors to throw the app flow so the dev can fix them | |
| throw err | |
| }) | |
| } as any | |
| parse.zod = function zod(schema: ZodSchema) { | |
| return prepare<any, z.infer<typeof schema>>(async function (data) { | |
| return schema.parse(parseForm(data.input)) | |
| }) | |
| } | |
| parse.map = function (callback) { | |
| return prepare<any, any>(function (data) { | |
| return Promise.resolve(callback({ ...bound, input: data as any })).then(function (response) { | |
| return cb({ ...bound, input: response }) | |
| }) | |
| }) | |
| } | |
| parse.cache = function (callback) { | |
| return prepare<any, any>(cache(function (data: any) { | |
| return cb(data).then(function (response) { | |
| return callback({ ...bound, input: response as any }) | |
| }) | |
| })) | |
| } | |
| parse.action = function (callback) { | |
| return prepare(function (data) { | |
| return cb(data).then(function (response) { | |
| return callback({ ...bound, input: response as any }) | |
| }) | |
| }) | |
| } | |
| parse.auth = function (callback) { | |
| return prepare(function (data) { | |
| return callback(data as any).then(function (response) { | |
| if (response) { | |
| throw { | |
| name: "AuthError", | |
| errors: [response], | |
| } | |
| } | |
| return cb(data) | |
| }) | |
| }) | |
| } | |
| return parse as PrepareApi<typeof bound, Input, Output> | |
| } | |
| return prepare() | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment