Initial commit
This commit is contained in:
225
node_modules/astro/dist/actions/runtime/server.js
generated
vendored
Normal file
225
node_modules/astro/dist/actions/runtime/server.js
generated
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
import { z } from "zod";
|
||||
import { shouldAppendForwardSlash } from "../../core/build/util.js";
|
||||
import { AstroError } from "../../core/errors/errors.js";
|
||||
import { ActionCalledFromServerError, ActionNotFoundError } from "../../core/errors/errors-data.js";
|
||||
import { removeTrailingForwardSlash } from "../../core/path.js";
|
||||
import { apiContextRoutesSymbol } from "../../core/render-context.js";
|
||||
import { ACTION_RPC_ROUTE_PATTERN } from "../consts.js";
|
||||
import {
|
||||
ACTION_QUERY_PARAMS,
|
||||
ActionError,
|
||||
ActionInputError,
|
||||
callSafely,
|
||||
deserializeActionResult,
|
||||
serializeActionResult
|
||||
} from "./shared.js";
|
||||
import {
|
||||
ACTION_API_CONTEXT_SYMBOL,
|
||||
formContentTypes,
|
||||
hasContentType,
|
||||
isActionAPIContext
|
||||
} from "./utils.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;
|
||||
}
|
||||
function getActionContext(context) {
|
||||
const callerInfo = getCallerInfo(context);
|
||||
const actionResultAlreadySet = Boolean(context.locals._actionPayload);
|
||||
let action = void 0;
|
||||
if (callerInfo && context.request.method === "POST" && !actionResultAlreadySet) {
|
||||
action = {
|
||||
calledFrom: callerInfo.from,
|
||||
name: callerInfo.name,
|
||||
handler: async () => {
|
||||
const pipeline = Reflect.get(context, apiContextRoutesSymbol);
|
||||
const callerInfoName = shouldAppendForwardSlash(
|
||||
pipeline.manifest.trailingSlash,
|
||||
pipeline.manifest.buildFormat
|
||||
) ? removeTrailingForwardSlash(callerInfo.name) : callerInfo.name;
|
||||
let baseAction;
|
||||
try {
|
||||
baseAction = await pipeline.getAction(callerInfoName);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && "name" in error && typeof error.name === "string" && error.name === ActionNotFoundError.name) {
|
||||
return { data: void 0, error: new ActionError({ code: "NOT_FOUND" }) };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
let input;
|
||||
try {
|
||||
input = await parseRequestBody(context.request);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError) {
|
||||
return { data: void 0, error: new ActionError({ code: "UNSUPPORTED_MEDIA_TYPE" }) };
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
const omitKeys = ["props", "getActionResult", "callAction", "redirect"];
|
||||
const actionAPIContext = Object.create(
|
||||
Object.getPrototypeOf(context),
|
||||
Object.fromEntries(
|
||||
Object.entries(Object.getOwnPropertyDescriptors(context)).filter(
|
||||
([key]) => !omitKeys.includes(key)
|
||||
)
|
||||
)
|
||||
);
|
||||
Reflect.set(actionAPIContext, ACTION_API_CONTEXT_SYMBOL, true);
|
||||
const handler = baseAction.bind(actionAPIContext);
|
||||
return handler(input);
|
||||
}
|
||||
};
|
||||
}
|
||||
function setActionResult(actionName, actionResult) {
|
||||
context.locals._actionPayload = {
|
||||
actionResult,
|
||||
actionName
|
||||
};
|
||||
}
|
||||
return {
|
||||
action,
|
||||
setActionResult,
|
||||
serializeActionResult,
|
||||
deserializeActionResult
|
||||
};
|
||||
}
|
||||
function getCallerInfo(ctx) {
|
||||
if (ctx.routePattern === ACTION_RPC_ROUTE_PATTERN) {
|
||||
return { from: "rpc", name: ctx.url.pathname.replace(/^.*\/_actions\//, "") };
|
||||
}
|
||||
const queryParam = ctx.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);
|
||||
if (queryParam) {
|
||||
return { from: "form", name: queryParam };
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
async function parseRequestBody(request) {
|
||||
const contentType = request.headers.get("content-type");
|
||||
const contentLength = request.headers.get("Content-Length");
|
||||
if (!contentType) return void 0;
|
||||
if (hasContentType(contentType, formContentTypes)) {
|
||||
return await request.clone().formData();
|
||||
}
|
||||
if (hasContentType(contentType, ["application/json"])) {
|
||||
return contentLength === "0" ? void 0 : await request.clone().json();
|
||||
}
|
||||
throw new TypeError("Unsupported content type");
|
||||
}
|
||||
export {
|
||||
defineAction,
|
||||
formDataToObject,
|
||||
getActionContext
|
||||
};
|
||||
Reference in New Issue
Block a user