Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.

This commit is contained in:
becarta
2025-05-23 12:43:00 +02:00
parent f40db0f5c9
commit a544759a3b
11127 changed files with 1647032 additions and 0 deletions

11
node_modules/astro/dist/actions/consts.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
export declare const VIRTUAL_MODULE_ID = "astro:actions";
export declare const RESOLVED_VIRTUAL_MODULE_ID: string;
export declare const ACTIONS_TYPES_FILE = "astro/actions.d.ts";
export declare const VIRTUAL_INTERNAL_MODULE_ID = "astro:internal-actions";
export declare const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = "\0astro:internal-actions";
export declare const NOOP_ACTIONS = "\0noop-actions";
export declare const ACTION_QUERY_PARAMS: {
actionName: string;
actionPayload: string;
actionRedirect: string;
};

20
node_modules/astro/dist/actions/consts.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
const VIRTUAL_MODULE_ID = "astro:actions";
const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
const ACTIONS_TYPES_FILE = "astro/actions.d.ts";
const VIRTUAL_INTERNAL_MODULE_ID = "astro:internal-actions";
const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = "\0astro:internal-actions";
const NOOP_ACTIONS = "\0noop-actions";
const ACTION_QUERY_PARAMS = {
actionName: "_astroAction",
actionPayload: "_astroActionPayload",
actionRedirect: "_astroActionRedirect"
};
export {
ACTIONS_TYPES_FILE,
ACTION_QUERY_PARAMS,
NOOP_ACTIONS,
RESOLVED_VIRTUAL_INTERNAL_MODULE_ID,
RESOLVED_VIRTUAL_MODULE_ID,
VIRTUAL_INTERNAL_MODULE_ID,
VIRTUAL_MODULE_ID
};

8
node_modules/astro/dist/actions/integration.d.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
import type { AstroIntegration, AstroSettings } from '../@types/astro.js';
/**
* This integration is applied when the user is using Actions in their project.
* It will inject the necessary routes and middlewares to handle actions.
*/
export default function astroIntegrationActionsRouteHandler({ settings, }: {
settings: AstroSettings;
}): AstroIntegration;

45
node_modules/astro/dist/actions/integration.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
import { ActionsWithoutServerOutputError } from "../core/errors/errors-data.js";
import { AstroError } from "../core/errors/errors.js";
import { isServerLikeOutput, viteID } from "../core/util.js";
import { ACTIONS_TYPES_FILE, VIRTUAL_MODULE_ID } from "./consts.js";
function astroIntegrationActionsRouteHandler({
settings
}) {
return {
name: VIRTUAL_MODULE_ID,
hooks: {
async "astro:config:setup"(params) {
params.injectRoute({
pattern: "/_actions/[...path]",
entrypoint: "astro/actions/runtime/route.js",
prerender: false
});
params.addMiddleware({
entrypoint: "astro/actions/runtime/middleware.js",
order: "post"
});
},
"astro:config:done": async (params) => {
if (!isServerLikeOutput(params.config)) {
const error = new AstroError(ActionsWithoutServerOutputError);
error.stack = void 0;
throw error;
}
const stringifiedActionsImport = JSON.stringify(
viteID(new URL("./actions", params.config.srcDir))
);
settings.injectedTypes.push({
filename: ACTIONS_TYPES_FILE,
content: `declare module "astro:actions" {
type Actions = typeof import(${stringifiedActionsImport})["server"];
export const actions: Actions;
}`
});
}
}
};
}
export {
astroIntegrationActionsRouteHandler as default
};

15
node_modules/astro/dist/actions/plugins.d.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
import type fsMod from 'node:fs';
import type { Plugin as VitePlugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
/**
* This plugin is responsible to load the known file `actions/index.js` / `actions.js`
* If the file doesn't exist, it returns an empty object.
* @param settings
*/
export declare function vitePluginUserActions({ settings }: {
settings: AstroSettings;
}): VitePlugin;
export declare function vitePluginActions({ fs, settings, }: {
fs: typeof fsMod;
settings: AstroSettings;
}): VitePlugin;

80
node_modules/astro/dist/actions/plugins.js generated vendored Normal file
View File

@@ -0,0 +1,80 @@
import {
NOOP_ACTIONS,
RESOLVED_VIRTUAL_INTERNAL_MODULE_ID,
RESOLVED_VIRTUAL_MODULE_ID,
VIRTUAL_INTERNAL_MODULE_ID,
VIRTUAL_MODULE_ID
} from "./consts.js";
import { isActionsFilePresent } from "./utils.js";
function vitePluginUserActions({ settings }) {
let resolvedActionsId;
return {
name: "@astro/plugin-actions",
async resolveId(id) {
if (id === NOOP_ACTIONS) {
return NOOP_ACTIONS;
}
if (id === VIRTUAL_INTERNAL_MODULE_ID) {
const resolvedModule = await this.resolve(
`${decodeURI(new URL("actions", settings.config.srcDir).pathname)}`
);
if (!resolvedModule) {
return NOOP_ACTIONS;
}
resolvedActionsId = resolvedModule.id;
return RESOLVED_VIRTUAL_INTERNAL_MODULE_ID;
}
},
load(id) {
if (id === NOOP_ACTIONS) {
return "export const server = {}";
} else if (id === RESOLVED_VIRTUAL_INTERNAL_MODULE_ID) {
return `export { server } from '${resolvedActionsId}';`;
}
}
};
}
function vitePluginActions({
fs,
settings
}) {
return {
name: VIRTUAL_MODULE_ID,
enforce: "pre",
resolveId(id) {
if (id === VIRTUAL_MODULE_ID) {
return RESOLVED_VIRTUAL_MODULE_ID;
}
},
async configureServer(server) {
const filePresentOnStartup = await isActionsFilePresent(fs, settings.config.srcDir);
async function watcherCallback() {
const filePresent = await isActionsFilePresent(fs, settings.config.srcDir);
if (filePresentOnStartup !== filePresent) {
server.restart();
}
}
server.watcher.on("add", watcherCallback);
server.watcher.on("change", watcherCallback);
},
async load(id, opts) {
if (id !== RESOLVED_VIRTUAL_MODULE_ID) return;
let code = await fs.promises.readFile(
new URL("../../templates/actions.mjs", import.meta.url),
"utf-8"
);
if (opts?.ssr) {
code += `
export * from 'astro/actions/runtime/virtual/server.js';`;
} else {
code += `
export * from 'astro/actions/runtime/virtual/client.js';`;
}
return code;
}
};
}
export {
vitePluginActions,
vitePluginUserActions
};

View File

@@ -0,0 +1,9 @@
import { type SerializedActionResult } from './virtual/shared.js';
export type ActionPayload = {
actionResult: SerializedActionResult;
actionName: string;
};
export type Locals = {
_actionPayload: ActionPayload;
};
export declare const onRequest: import("../../@types/astro.js").MiddlewareHandler;

110
node_modules/astro/dist/actions/runtime/middleware.js generated vendored Normal file
View File

@@ -0,0 +1,110 @@
import { yellow } from "kleur/colors";
import { defineMiddleware } from "../../core/middleware/index.js";
import { getOriginPathname } from "../../core/routing/rewrite.js";
import { ACTION_QUERY_PARAMS } from "../consts.js";
import { ACTION_API_CONTEXT_SYMBOL, formContentTypes, hasContentType } from "./utils.js";
import { getAction } from "./virtual/get-action.js";
import {
serializeActionResult
} from "./virtual/shared.js";
const onRequest = defineMiddleware(async (context, next) => {
if (context._isPrerendered) {
if (context.request.method === "POST") {
console.warn(
yellow("[astro:actions]"),
"POST requests should not be sent to prerendered pages. If you're using Actions, disable prerendering with `export const prerender = false`."
);
}
return next();
}
const locals = context.locals;
if (locals._actionPayload) return next();
const actionPayload = context.cookies.get(ACTION_QUERY_PARAMS.actionPayload)?.json();
if (actionPayload) {
if (!isActionPayload(actionPayload)) {
throw new Error("Internal: Invalid action payload in cookie.");
}
return renderResult({ context, next, ...actionPayload });
}
const actionName = context.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);
if (context.request.method === "POST" && actionName) {
return handlePost({ context, next, actionName });
}
return next();
});
async function renderResult({
context,
next,
actionResult,
actionName
}) {
const locals = context.locals;
locals._actionPayload = { actionResult, actionName };
const response = await next();
context.cookies.delete(ACTION_QUERY_PARAMS.actionPayload);
if (actionResult.type === "error") {
return new Response(response.body, {
status: actionResult.status,
statusText: actionResult.type,
headers: response.headers
});
}
return response;
}
async function handlePost({
context,
next,
actionName
}) {
const { request } = context;
const baseAction = await getAction(actionName);
const contentType = request.headers.get("content-type");
let formData;
if (contentType && hasContentType(contentType, formContentTypes)) {
formData = await request.clone().formData();
}
const { getActionResult, callAction, props, redirect, ...actionAPIContext } = context;
Reflect.set(actionAPIContext, ACTION_API_CONTEXT_SYMBOL, true);
const action = baseAction.bind(actionAPIContext);
const actionResult = await action(formData);
if (context.url.searchParams.get(ACTION_QUERY_PARAMS.actionRedirect) === "false") {
return renderResult({
context,
next,
actionName,
actionResult: serializeActionResult(actionResult)
});
}
return redirectWithResult({ context, actionName, actionResult });
}
async function redirectWithResult({
context,
actionName,
actionResult
}) {
context.cookies.set(ACTION_QUERY_PARAMS.actionPayload, {
actionName,
actionResult: serializeActionResult(actionResult)
});
if (actionResult.error) {
const referer2 = context.request.headers.get("Referer");
if (!referer2) {
throw new Error("Internal: Referer unexpectedly missing from Action POST request.");
}
return context.redirect(referer2);
}
const referer = getOriginPathname(context.request);
if (referer) {
return context.redirect(referer);
}
return context.redirect(context.url.pathname);
}
function isActionPayload(json) {
if (typeof json !== "object" || json == null) return false;
if (!("actionResult" in json) || typeof json.actionResult !== "object") return false;
if (!("actionName" in json) || typeof json.actionName !== "string") return false;
return true;
}
export {
onRequest
};

2
node_modules/astro/dist/actions/runtime/route.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
import type { APIRoute } from '../../@types/astro.js';
export declare const POST: APIRoute;

45
node_modules/astro/dist/actions/runtime/route.js generated vendored Normal file
View File

@@ -0,0 +1,45 @@
import { ACTION_API_CONTEXT_SYMBOL, formContentTypes, hasContentType } from "./utils.js";
import { getAction } from "./virtual/get-action.js";
import { serializeActionResult } from "./virtual/shared.js";
const POST = async (context) => {
const { request, url } = context;
let baseAction;
try {
baseAction = await getAction(url.pathname);
} catch (e) {
if (import.meta.env.DEV) throw e;
console.error(e);
return new Response(e instanceof Error ? e.message : null, { status: 404 });
}
const contentType = request.headers.get("Content-Type");
const contentLength = request.headers.get("Content-Length");
let args;
if (!contentType || contentLength === "0") {
args = void 0;
} else if (contentType && hasContentType(contentType, formContentTypes)) {
args = await request.clone().formData();
} else if (contentType && hasContentType(contentType, ["application/json"])) {
args = await request.clone().json();
} else {
return new Response(null, { status: 415 });
}
const { getActionResult, callAction, props, redirect, ...actionAPIContext } = context;
Reflect.set(actionAPIContext, ACTION_API_CONTEXT_SYMBOL, true);
const action = baseAction.bind(actionAPIContext);
const result = await action(args);
const serialized = serializeActionResult(result);
if (serialized.type === "empty") {
return new Response(null, {
status: serialized.status
});
}
return new Response(serialized.body, {
status: serialized.status,
headers: {
"Content-Type": serialized.contentType
}
});
};
export {
POST
};

17
node_modules/astro/dist/actions/runtime/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
import type { APIContext } from '../../@types/astro.js';
export declare const ACTION_API_CONTEXT_SYMBOL: unique symbol;
export declare const formContentTypes: string[];
export declare function hasContentType(contentType: string, expected: string[]): boolean;
export type ActionAPIContext = Omit<APIContext, 'getActionResult' | 'callAction' | 'props' | 'redirect'>;
export type MaybePromise<T> = T | Promise<T>;
/**
* Used to preserve the input schema type in the error object.
* This allows for type inference on the `fields` property
* when type narrowed to an `ActionInputError`.
*
* Example: Action has an input schema of `{ name: z.string() }`.
* When calling the action and checking `isInputError(result.error)`,
* `result.error.fields` will be typed with the `name` field.
*/
export type ErrorInferenceObject = Record<string, any>;
export declare function isActionAPIContext(ctx: ActionAPIContext): boolean;

16
node_modules/astro/dist/actions/runtime/utils.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
const ACTION_API_CONTEXT_SYMBOL = Symbol.for("astro.actionAPIContext");
const formContentTypes = ["application/x-www-form-urlencoded", "multipart/form-data"];
function hasContentType(contentType, expected) {
const type = contentType.split(";")[0].toLowerCase();
return expected.some((t) => type === t);
}
function isActionAPIContext(ctx) {
const symbol = Reflect.get(ctx, ACTION_API_CONTEXT_SYMBOL);
return symbol === true;
}
export {
ACTION_API_CONTEXT_SYMBOL,
formContentTypes,
hasContentType,
isActionAPIContext
};

View File

@@ -0,0 +1,2 @@
export * from './shared.js';
export declare function defineAction(): void;

View File

@@ -0,0 +1,7 @@
export * from "./shared.js";
function defineAction() {
throw new Error("[astro:action] `defineAction()` unexpectedly used on the client.");
}
export {
defineAction
};

View File

@@ -0,0 +1,8 @@
import type { ZodType } from 'zod';
import type { ActionAccept, ActionClient } from './server.js';
/**
* Get server-side action based on the route path.
* Imports from the virtual module `astro:internal-actions`, which maps to
* the user's `src/actions/index.ts` file at build-time.
*/
export declare function getAction(path: string): Promise<ActionClient<unknown, ActionAccept, ZodType>>;

View File

@@ -0,0 +1,29 @@
import { ActionNotFoundError } from "../../../core/errors/errors-data.js";
import { AstroError } from "../../../core/errors/errors.js";
async function getAction(path) {
const pathKeys = path.replace(/^.*\/_actions\//, "").split(".").map((key) => decodeURIComponent(key));
let { server: actionLookup } = await import("astro:internal-actions");
if (actionLookup == null || !(typeof actionLookup === "object")) {
throw new TypeError(
`Expected \`server\` export in actions file to be an object. Received ${typeof actionLookup}.`
);
}
for (const key of pathKeys) {
if (!(key in actionLookup)) {
throw new AstroError({
...ActionNotFoundError,
message: ActionNotFoundError.message(pathKeys.join("."))
});
}
actionLookup = actionLookup[key];
}
if (typeof actionLookup !== "function") {
throw new TypeError(
`Expected handler for action ${pathKeys.join(".")} to be a function. Received ${typeof actionLookup}.`
);
}
return actionLookup;
}
export {
getAction
};

View File

@@ -0,0 +1,20 @@
import { z } from 'zod';
import { type ActionAPIContext, type ErrorInferenceObject, type MaybePromise } from '../utils.js';
import { type SafeResult } from './shared.js';
export * from './shared.js';
export type ActionAccept = 'form' | 'json';
export type ActionHandler<TInputSchema, TOutput> = TInputSchema extends z.ZodType ? (input: z.infer<TInputSchema>, context: ActionAPIContext) => MaybePromise<TOutput> : (input: any, context: ActionAPIContext) => MaybePromise<TOutput>;
export type ActionReturnType<T extends ActionHandler<any, any>> = Awaited<ReturnType<T>>;
export type ActionClient<TOutput, TAccept extends ActionAccept | undefined, TInputSchema extends z.ZodType | undefined> = TInputSchema extends z.ZodType ? ((input: TAccept extends 'form' ? FormData : z.input<TInputSchema>) => Promise<SafeResult<z.input<TInputSchema> extends ErrorInferenceObject ? z.input<TInputSchema> : ErrorInferenceObject, Awaited<TOutput>>>) & {
queryString: string;
orThrow: (input: TAccept extends 'form' ? FormData : z.input<TInputSchema>) => Promise<Awaited<TOutput>>;
} : ((input?: any) => Promise<SafeResult<never, Awaited<TOutput>>>) & {
orThrow: (input?: any) => Promise<Awaited<TOutput>>;
};
export declare function defineAction<TOutput, TAccept extends ActionAccept | undefined = undefined, TInputSchema extends z.ZodType | undefined = TAccept extends 'form' ? z.ZodType<FormData> : undefined>({ accept, input: inputSchema, handler, }: {
input?: TInputSchema;
accept?: TAccept;
handler: ActionHandler<TInputSchema, TOutput>;
}): ActionClient<TOutput, TAccept, TInputSchema> & string;
/** Transform form data to an object based on a Zod schema. */
export declare function formDataToObject<T extends z.AnyZodObject>(formData: FormData, schema: T): Record<string, unknown>;

View File

@@ -0,0 +1,128 @@
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
};

View File

@@ -0,0 +1,57 @@
import type { z } from 'zod';
import type { ErrorInferenceObject, MaybePromise, ActionAPIContext as _ActionAPIContext } from '../utils.js';
export type ActionAPIContext = _ActionAPIContext;
export declare const ACTION_QUERY_PARAMS: {
actionName: string;
actionPayload: string;
actionRedirect: string;
};
export declare const ACTION_ERROR_CODES: readonly ["BAD_REQUEST", "UNAUTHORIZED", "FORBIDDEN", "NOT_FOUND", "TIMEOUT", "CONFLICT", "PRECONDITION_FAILED", "PAYLOAD_TOO_LARGE", "UNSUPPORTED_MEDIA_TYPE", "UNPROCESSABLE_CONTENT", "TOO_MANY_REQUESTS", "CLIENT_CLOSED_REQUEST", "INTERNAL_SERVER_ERROR"];
export type ActionErrorCode = (typeof ACTION_ERROR_CODES)[number];
export declare class ActionError<_T extends ErrorInferenceObject = ErrorInferenceObject> extends Error {
type: string;
code: ActionErrorCode;
status: number;
constructor(params: {
message?: string;
code: ActionErrorCode;
stack?: string;
});
static codeToStatus(code: ActionErrorCode): number;
static statusToCode(status: number): ActionErrorCode;
static fromJson(body: any): ActionError<ErrorInferenceObject>;
}
export declare function isActionError(error?: unknown): error is ActionError;
export declare function isInputError<T extends ErrorInferenceObject>(error?: ActionError<T>): error is ActionInputError<T>;
export declare function isInputError(error?: unknown): error is ActionInputError<ErrorInferenceObject>;
export type SafeResult<TInput extends ErrorInferenceObject, TOutput> = {
data: TOutput;
error: undefined;
} | {
data: undefined;
error: ActionError<TInput>;
};
export declare class ActionInputError<T extends ErrorInferenceObject> extends ActionError {
type: string;
issues: z.ZodIssue[];
fields: z.ZodError<T>['formErrors']['fieldErrors'];
constructor(issues: z.ZodIssue[]);
}
export declare function callSafely<TOutput>(handler: () => MaybePromise<TOutput>): Promise<SafeResult<z.ZodType, TOutput>>;
export declare function getActionQueryString(name: string): string;
export type SerializedActionResult = {
type: 'data';
contentType: 'application/json+devalue';
status: 200;
body: string;
} | {
type: 'error';
contentType: 'application/json';
status: number;
body: string;
} | {
type: 'empty';
status: 204;
};
export declare function serializeActionResult(res: SafeResult<any, any>): SerializedActionResult;
export declare function deserializeActionResult(res: SerializedActionResult): SafeResult<any, any>;

View File

@@ -0,0 +1,226 @@
import { parse as devalueParse, stringify as devalueStringify } from "devalue";
import { REDIRECT_STATUS_CODES } from "../../../core/constants.js";
import { ActionsReturnedInvalidDataError } from "../../../core/errors/errors-data.js";
import { AstroError } from "../../../core/errors/errors.js";
import { ACTION_QUERY_PARAMS as _ACTION_QUERY_PARAMS } from "../../consts.js";
const ACTION_QUERY_PARAMS = _ACTION_QUERY_PARAMS;
const ACTION_ERROR_CODES = [
"BAD_REQUEST",
"UNAUTHORIZED",
"FORBIDDEN",
"NOT_FOUND",
"TIMEOUT",
"CONFLICT",
"PRECONDITION_FAILED",
"PAYLOAD_TOO_LARGE",
"UNSUPPORTED_MEDIA_TYPE",
"UNPROCESSABLE_CONTENT",
"TOO_MANY_REQUESTS",
"CLIENT_CLOSED_REQUEST",
"INTERNAL_SERVER_ERROR"
];
const codeToStatusMap = {
// Implemented from tRPC error code table
// https://trpc.io/docs/server/error-handling#error-codes
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
TIMEOUT: 405,
CONFLICT: 409,
PRECONDITION_FAILED: 412,
PAYLOAD_TOO_LARGE: 413,
UNSUPPORTED_MEDIA_TYPE: 415,
UNPROCESSABLE_CONTENT: 422,
TOO_MANY_REQUESTS: 429,
CLIENT_CLOSED_REQUEST: 499,
INTERNAL_SERVER_ERROR: 500
};
const statusToCodeMap = Object.entries(codeToStatusMap).reduce(
// reverse the key-value pairs
(acc, [key, value]) => ({ ...acc, [value]: key }),
{}
);
class ActionError extends Error {
type = "AstroActionError";
code = "INTERNAL_SERVER_ERROR";
status = 500;
constructor(params) {
super(params.message);
this.code = params.code;
this.status = ActionError.codeToStatus(params.code);
if (params.stack) {
this.stack = params.stack;
}
}
static codeToStatus(code) {
return codeToStatusMap[code];
}
static statusToCode(status) {
return statusToCodeMap[status] ?? "INTERNAL_SERVER_ERROR";
}
static fromJson(body) {
if (isInputError(body)) {
return new ActionInputError(body.issues);
}
if (isActionError(body)) {
return new ActionError(body);
}
return new ActionError({
code: "INTERNAL_SERVER_ERROR"
});
}
}
function isActionError(error) {
return typeof error === "object" && error != null && "type" in error && error.type === "AstroActionError";
}
function isInputError(error) {
return typeof error === "object" && error != null && "type" in error && error.type === "AstroActionInputError" && "issues" in error && Array.isArray(error.issues);
}
class ActionInputError extends ActionError {
type = "AstroActionInputError";
// We don't expose all ZodError properties.
// Not all properties will serialize from server to client,
// and we don't want to import the full ZodError object into the client.
issues;
fields;
constructor(issues) {
super({
message: `Failed to validate: ${JSON.stringify(issues, null, 2)}`,
code: "BAD_REQUEST"
});
this.issues = issues;
this.fields = {};
for (const issue of issues) {
if (issue.path.length > 0) {
const key = issue.path[0].toString();
this.fields[key] ??= [];
this.fields[key]?.push(issue.message);
}
}
}
}
async function callSafely(handler) {
try {
const data = await handler();
return { data, error: void 0 };
} catch (e) {
if (e instanceof ActionError) {
return { data: void 0, error: e };
}
return {
data: void 0,
error: new ActionError({
message: e instanceof Error ? e.message : "Unknown error",
code: "INTERNAL_SERVER_ERROR"
})
};
}
}
function getActionQueryString(name) {
const searchParams = new URLSearchParams({ [_ACTION_QUERY_PARAMS.actionName]: name });
return `?${searchParams.toString()}`;
}
function serializeActionResult(res) {
if (res.error) {
if (import.meta.env?.DEV) {
actionResultErrorStack.set(res.error.stack);
}
return {
type: "error",
status: res.error.status,
contentType: "application/json",
body: JSON.stringify({
...res.error,
message: res.error.message
})
};
}
if (res.data === void 0) {
return {
type: "empty",
status: 204
};
}
let body;
try {
body = devalueStringify(res.data, {
// Add support for URL objects
URL: (value) => value instanceof URL && value.href
});
} catch (e) {
let hint = ActionsReturnedInvalidDataError.hint;
if (res.data instanceof Response) {
hint = REDIRECT_STATUS_CODES.includes(res.data.status) ? "If you need to redirect when the action succeeds, trigger a redirect where the action is called. See the Actions guide for server and client redirect examples: https://docs.astro.build/en/guides/actions." : "If you need to return a Response object, try using a server endpoint instead. See https://docs.astro.build/en/guides/endpoints/#server-endpoints-api-routes";
}
throw new AstroError({
...ActionsReturnedInvalidDataError,
message: ActionsReturnedInvalidDataError.message(String(e)),
hint
});
}
return {
type: "data",
status: 200,
contentType: "application/json+devalue",
body
};
}
function deserializeActionResult(res) {
if (res.type === "error") {
let json;
try {
json = JSON.parse(res.body);
} catch {
return {
data: void 0,
error: new ActionError({
message: res.body,
code: "INTERNAL_SERVER_ERROR"
})
};
}
if (import.meta.env?.PROD) {
return { error: ActionError.fromJson(json), data: void 0 };
} else {
const error = ActionError.fromJson(json);
error.stack = actionResultErrorStack.get();
return {
error,
data: void 0
};
}
}
if (res.type === "empty") {
return { data: void 0, error: void 0 };
}
return {
data: devalueParse(res.body, {
URL: (href) => new URL(href)
}),
error: void 0
};
}
const actionResultErrorStack = /* @__PURE__ */ function actionResultErrorStackFn() {
let errorStack;
return {
set(stack) {
errorStack = stack;
},
get() {
return errorStack;
}
};
}();
export {
ACTION_ERROR_CODES,
ACTION_QUERY_PARAMS,
ActionError,
ActionInputError,
callSafely,
deserializeActionResult,
getActionQueryString,
isActionError,
isInputError,
serializeActionResult
};

11
node_modules/astro/dist/actions/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
import type fsMod from 'node:fs';
import type { APIContext } from '../@types/astro.js';
import type { Locals } from './runtime/middleware.js';
import { type ActionAPIContext } from './runtime/utils.js';
export declare function hasActionPayload(locals: APIContext['locals']): locals is Locals;
export declare function createGetActionResult(locals: APIContext['locals']): APIContext['getActionResult'];
export declare function createCallAction(context: ActionAPIContext): APIContext['callAction'];
/**
* Check whether the Actions config file is present.
*/
export declare function isActionsFilePresent(fs: typeof fsMod, srcDir: URL): Promise<boolean>;

64
node_modules/astro/dist/actions/utils.js generated vendored Normal file
View File

@@ -0,0 +1,64 @@
import * as eslexer from "es-module-lexer";
import { ACTION_API_CONTEXT_SYMBOL } from "./runtime/utils.js";
import { deserializeActionResult, getActionQueryString } from "./runtime/virtual/shared.js";
function hasActionPayload(locals) {
return "_actionPayload" in locals;
}
function createGetActionResult(locals) {
return (actionFn) => {
if (!hasActionPayload(locals) || actionFn.toString() !== getActionQueryString(locals._actionPayload.actionName)) {
return void 0;
}
return deserializeActionResult(locals._actionPayload.actionResult);
};
}
function createCallAction(context) {
return (baseAction, input) => {
Reflect.set(context, ACTION_API_CONTEXT_SYMBOL, true);
const action = baseAction.bind(context);
return action(input);
};
}
let didInitLexer = false;
async function isActionsFilePresent(fs, srcDir) {
if (!didInitLexer) await eslexer.init;
const actionsFile = search(fs, srcDir);
if (!actionsFile) return false;
let contents;
try {
contents = fs.readFileSync(actionsFile, "utf-8");
} catch {
return false;
}
const [, exports] = eslexer.parse(contents, actionsFile.pathname);
for (const exp of exports) {
if (exp.n === "server") {
return true;
}
}
return false;
}
function search(fs, srcDir) {
const paths = [
"actions.mjs",
"actions.js",
"actions.mts",
"actions.ts",
"actions/index.mjs",
"actions/index.js",
"actions/index.mts",
"actions/index.ts"
].map((p) => new URL(p, srcDir));
for (const file of paths) {
if (fs.existsSync(file)) {
return file;
}
}
return void 0;
}
export {
createCallAction,
createGetActionResult,
hasActionPayload,
isActionsFilePresent
};