129 lines
4.6 KiB
JavaScript
129 lines
4.6 KiB
JavaScript
import { z } from "zod";
|
|
import { ActionCalledFromServerError } from "../../../core/errors/errors-data.js";
|
|
import { AstroError } from "../../../core/errors/errors.js";
|
|
import {
|
|
isActionAPIContext
|
|
} from "../utils.js";
|
|
import { ActionError, ActionInputError, callSafely } from "./shared.js";
|
|
export * from "./shared.js";
|
|
function defineAction({
|
|
accept,
|
|
input: inputSchema,
|
|
handler
|
|
}) {
|
|
const serverHandler = accept === "form" ? getFormServerHandler(handler, inputSchema) : getJsonServerHandler(handler, inputSchema);
|
|
async function safeServerHandler(unparsedInput) {
|
|
if (typeof this === "function" || !isActionAPIContext(this)) {
|
|
throw new AstroError(ActionCalledFromServerError);
|
|
}
|
|
return callSafely(() => serverHandler(unparsedInput, this));
|
|
}
|
|
Object.assign(safeServerHandler, {
|
|
orThrow(unparsedInput) {
|
|
if (typeof this === "function") {
|
|
throw new AstroError(ActionCalledFromServerError);
|
|
}
|
|
return serverHandler(unparsedInput, this);
|
|
}
|
|
});
|
|
return safeServerHandler;
|
|
}
|
|
function getFormServerHandler(handler, inputSchema) {
|
|
return async (unparsedInput, context) => {
|
|
if (!(unparsedInput instanceof FormData)) {
|
|
throw new ActionError({
|
|
code: "UNSUPPORTED_MEDIA_TYPE",
|
|
message: "This action only accepts FormData."
|
|
});
|
|
}
|
|
if (!inputSchema) return await handler(unparsedInput, context);
|
|
const baseSchema = unwrapBaseObjectSchema(inputSchema, unparsedInput);
|
|
const parsed = await inputSchema.safeParseAsync(
|
|
baseSchema instanceof z.ZodObject ? formDataToObject(unparsedInput, baseSchema) : unparsedInput
|
|
);
|
|
if (!parsed.success) {
|
|
throw new ActionInputError(parsed.error.issues);
|
|
}
|
|
return await handler(parsed.data, context);
|
|
};
|
|
}
|
|
function getJsonServerHandler(handler, inputSchema) {
|
|
return async (unparsedInput, context) => {
|
|
if (unparsedInput instanceof FormData) {
|
|
throw new ActionError({
|
|
code: "UNSUPPORTED_MEDIA_TYPE",
|
|
message: "This action only accepts JSON."
|
|
});
|
|
}
|
|
if (!inputSchema) return await handler(unparsedInput, context);
|
|
const parsed = await inputSchema.safeParseAsync(unparsedInput);
|
|
if (!parsed.success) {
|
|
throw new ActionInputError(parsed.error.issues);
|
|
}
|
|
return await handler(parsed.data, context);
|
|
};
|
|
}
|
|
function formDataToObject(formData, schema) {
|
|
const obj = schema._def.unknownKeys === "passthrough" ? Object.fromEntries(formData.entries()) : {};
|
|
for (const [key, baseValidator] of Object.entries(schema.shape)) {
|
|
let validator = baseValidator;
|
|
while (validator instanceof z.ZodOptional || validator instanceof z.ZodNullable || validator instanceof z.ZodDefault) {
|
|
if (validator instanceof z.ZodDefault && !formData.has(key)) {
|
|
obj[key] = validator._def.defaultValue();
|
|
}
|
|
validator = validator._def.innerType;
|
|
}
|
|
if (!formData.has(key) && key in obj) {
|
|
continue;
|
|
} else if (validator instanceof z.ZodBoolean) {
|
|
const val = formData.get(key);
|
|
obj[key] = val === "true" ? true : val === "false" ? false : formData.has(key);
|
|
} else if (validator instanceof z.ZodArray) {
|
|
obj[key] = handleFormDataGetAll(key, formData, validator);
|
|
} else {
|
|
obj[key] = handleFormDataGet(key, formData, validator, baseValidator);
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
function handleFormDataGetAll(key, formData, validator) {
|
|
const entries = Array.from(formData.getAll(key));
|
|
const elementValidator = validator._def.type;
|
|
if (elementValidator instanceof z.ZodNumber) {
|
|
return entries.map(Number);
|
|
} else if (elementValidator instanceof z.ZodBoolean) {
|
|
return entries.map(Boolean);
|
|
}
|
|
return entries;
|
|
}
|
|
function handleFormDataGet(key, formData, validator, baseValidator) {
|
|
const value = formData.get(key);
|
|
if (!value) {
|
|
return baseValidator instanceof z.ZodOptional ? void 0 : null;
|
|
}
|
|
return validator instanceof z.ZodNumber ? Number(value) : value;
|
|
}
|
|
function unwrapBaseObjectSchema(schema, unparsedInput) {
|
|
while (schema instanceof z.ZodEffects || schema instanceof z.ZodPipeline) {
|
|
if (schema instanceof z.ZodEffects) {
|
|
schema = schema._def.schema;
|
|
}
|
|
if (schema instanceof z.ZodPipeline) {
|
|
schema = schema._def.in;
|
|
}
|
|
}
|
|
if (schema instanceof z.ZodDiscriminatedUnion) {
|
|
const typeKey = schema._def.discriminator;
|
|
const typeValue = unparsedInput.get(typeKey);
|
|
if (typeof typeValue !== "string") return schema;
|
|
const objSchema = schema._def.optionsMap.get(typeValue);
|
|
if (!objSchema) return schema;
|
|
return objSchema;
|
|
}
|
|
return schema;
|
|
}
|
|
export {
|
|
defineAction,
|
|
formDataToObject
|
|
};
|