3. Endpoint Pipe function - Refzlund/sveltekit-zero-api GitHub Wiki

Example

import { endpointPipe, type KitEvent } from 'sveltekit-zero-api'
import { Ok, Unauthorized, BadRequest } from 'sveltekit-zero-api/http'
import { authGuard, parseJSON } from '$endpoint-services'

interface Post {
    body: {
        foo: string
    }
}

export async function POST<const R extends Post>(event: KitEvent<Post>) {
    
    return endpointPipe(event) (
        authGuard('admin'),
        parseJSON<Post>, // Parses JSON in a try-catch block
        (event, locals: { something: string }) => {
            const { foo } = event.locals.json
                                        // ^ Typed via parseJSON<Post>
            locals.something = 'Helloo'
            return Ok()
        },
        (event) => {
            event.locals.something
                          // ^ Is now typed
            console.log('This function won\'t run, because the previous definitively returns a response')
        }
    )
}



Modular endpoint using the endpointPipe function

This pipe function promotes flexible code-splitting, reusability, and usage of the SvelteKit event.locals by keeping it typed based on previous run functions!

The pipe takes in event and an optional default-fallback response:

endpointPipe(event, () => BadRequest({ body: { message: 'End of pipe:(' } }))

It returns another function, and this function accepts our pipe-line functions. An example of such a function could be:

endpointPipe(event)(
    (event) => {
        return Ok()
    }
)

This function has 2 arguments; (event, locals) => {...}.

You type event to set requirements to both JSON body/query AND the event.locals. You type locals to update the event.locals for functions coming after.



Request Input requirements

interface UpdateUserName {
    body: {
        name: string
    },
    query: { 
        age: number
    }
}
function updateUserName(event: KitEvent<UpdateUserName>) {

Notice it's inside KitEvent<...> just like with endpoints. Same concept.



event.locals requirements

function updateUserName(event: KitEvent & { locals: { user: { name: string } } }) {

Here we use & to extend the event. This also sets a type requirement.



Updating event.locals

To type locals, you use the second parameter in a function:

function getUserName(
    event: KitEvent & { locals: { json: { name: string } } }, 
    locals: { name: string }
) {
    locals.name      =      event.locals.json.name
    // ^  These are the same object  ^
}



Example: parseJSON

How many times do you use await event.request.json()? Every time... Pretty much.

If the endpoint receives an invalid JSON format, and you don't try-catch block it — then it will throw an InternalServerError (500). This is obviously not a server error, and should return 400.

Let's create a pipeline function!

// parse-json.ts
export async function parseJSON<T extends { body: Record<any, any> | any[] }>(
    event: KitEvent<T>,
    locals: { json: T['body'] }
) {
    try {
        if(event.request.bodyUsed)
            throw new Error('Body has already been read!') // An actual server error.
        locals.json = await event.request.json()
    } catch (error) {
        return BadRequest({
            body: {
                message: 'Bad JSON in request body'
            }
        })
    }
}

We type locals: {} to update it for functions running after. We use a TypeScript Generic to know what the contents are for the JSON.

And in the function itself, we only return a response if an error occurs.

Usage

import { parseJSON } from '$endpoint-services'

export async function POST(event: KitEvent<Post>) {
    
    return endpointPipe(event) (
        parseJSON<Post>,
        ({ locals }) => {
            // Do stuff with `event.locals`
            return Ok({...})
        }
    )
}
⚠️ **GitHub.com Fallback** ⚠️