Files
kycnotme/web/src/lib/callActionWithUrlParams.ts
2025-05-19 10:23:36 +00:00

185 lines
5.6 KiB
TypeScript

import { serialize } from 'object-to-formdata'
import { urlParamsToFormData, urlParamsToObject } from './urls'
import type { AstroGlobal } from 'astro'
import type { ActionAccept, ActionClient, SafeResult } from 'astro:actions'
import type { z } from 'astro:schema'
/**
* Call an Action directly from an Astro page or API endpoint.
*
* The action input is obtained from the URL search params.
*
* Returns a Promise with the action result.
*
* It uses {@link AstroGlobal.callAction} internally.
*
* Example usage:
*
* ```typescript
* import { actions } from 'astro:actions';
*
* const result = await callActionWithUrlParamsUnhandledErrors(Astro, actions.getPost, 'form');
* ```
*/
export function callActionWithUrlParamsUnhandledErrors<
TAccept extends ActionAccept,
TInputSchema extends z.ZodType,
TOutput,
TAction extends
| ActionClient<TOutput, TAccept, TInputSchema>
| ActionClient<TOutput, TAccept, TInputSchema>['orThrow'],
P extends Parameters<TAction>[0],
>(context: AstroGlobal, action: TAction, accept: P extends FormData ? 'form' : 'json') {
const input =
accept === 'form'
? urlParamsToFormData(context.url.searchParams)
: urlParamsToObject(context.url.searchParams)
return context.callAction(action, input as P)
}
/**
* Call an Action directly from an Astro page or API endpoint.
*
* The action input is obtained from the URL search params.
*
* Returns a Promise with the action result's data.
*
* It stores the errors in {@link context.locals.banners}
*
* It uses {@link AstroGlobal.callAction} internally.
*
* Example usage:
*
* ```typescript
* import { actions } from 'astro:actions';
*
* const data = await callActionWithUrlParams(Astro, actions.getPost, 'form');
* ```
*/
export async function callActionWithUrlParams<
TAccept extends ActionAccept,
TInputSchema extends z.ZodType,
TOutput,
TAction extends
| ActionClient<TOutput, TAccept, TInputSchema>
| ActionClient<TOutput, TAccept, TInputSchema>['orThrow'],
P extends Parameters<TAction>[0],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TInputSchema2 extends ReturnType<TAction> extends Promise<SafeResult<infer I, any>> ? I : never,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TOutput2 extends ReturnType<TAction> extends Promise<SafeResult<any, infer O>> ? O : never,
>(context: AstroGlobal, action: TAction, accept: P extends FormData ? 'form' : 'json') {
const input =
accept === 'form'
? urlParamsToFormData(context.url.searchParams)
: urlParamsToObject(context.url.searchParams)
const result = (await context.callAction(action, input as P)) as SafeResult<
TInputSchema2,
Awaited<TOutput2>
>
if (result.error) {
context.locals.banners.add({
uiMessage: result.error.message,
type: 'error',
origin: 'action',
error: result.error,
})
}
return result.data
}
/**
* Call an Action directly from an Astro page or API endpoint.
*
* Returns a Promise with the action result.
*
* It uses {@link AstroGlobal.callAction} internally.
*
* Example usage:
*
* ```typescript
* import { actions } from 'astro:actions';
*
* const input = { id: 123 }
* const result = await callActionWithObjectUnhandledErrors(Astro, actions.getPost, input, 'form');
* ```
*/
export function callActionWithObjectUnhandledErrors<
TAccept extends ActionAccept,
TInputSchema extends z.ZodType,
TOutput,
TAction extends
| ActionClient<TOutput, TAccept, TInputSchema>
| ActionClient<TOutput, TAccept, TInputSchema>['orThrow'],
P extends Parameters<TAction>[0],
TInputSchema2 extends ReturnType<TAction> extends Promise<SafeResult<infer I, unknown>> ? I : never,
>(
context: AstroGlobal,
action: TAction,
input: [TInputSchema2] extends [FormData] | [never] ? undefined : TInputSchema2,
accept: P extends FormData ? 'form' : 'json'
) {
const parsedInput = accept === 'form' ? serialize(input) : input
return context.callAction(action, parsedInput as P)
}
/**
* Call an Action directly from an Astro page or API endpoint.
*
* The action input is a plain object.
*
* Returns a Promise with the action result's data.
*
* It stores the errors in {@link context.locals.banners}
*
* It uses {@link AstroGlobal.callAction} internally.
*
* Example usage:
*
* ```typescript
* import { actions } from 'astro:actions';
*
* const input = { id: 123 }
* const data = await callActionWithObject(Astro, actions.getPost, input, 'form');
* ```
*/
export async function callActionWithObject<
TAccept extends ActionAccept,
TInputSchema extends z.ZodType,
TOutput,
TAction extends
| ActionClient<TOutput, TAccept, TInputSchema>
| ActionClient<TOutput, TAccept, TInputSchema>['orThrow'],
P extends Parameters<TAction>[0],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TInputSchema2 extends ReturnType<TAction> extends Promise<SafeResult<infer I, any>> ? I : never,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TOutput2 extends ReturnType<TAction> extends Promise<SafeResult<any, infer O>> ? O : never,
>(
context: AstroGlobal,
action: TAction,
input: [TInputSchema2] extends [FormData] | [never] ? undefined : TInputSchema2,
accept: P extends FormData ? 'form' : 'json'
) {
const parsedInput = accept === 'form' ? serialize(input) : input
const result = (await context.callAction(action, parsedInput as P)) as SafeResult<
TInputSchema2,
Awaited<TOutput2>
>
if (result.error) {
context.locals.banners.add({
uiMessage: result.error.message,
type: 'error',
origin: 'action',
error: result.error,
})
}
return result.data
}