Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/twelve-carrots-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Refactor Astro Actions to not use a middleware. Doing so should avoid unexpected issues when using the Astro middleware at the edge.
6 changes: 3 additions & 3 deletions packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ export const server = {
handler: async ({ postId }) => {
await new Promise((r) => setTimeout(r, 500));

const { likes } = await db
const result = await db
.update(Likes)
.set({
likes: sql`likes + 1`,
likes: sql`${Likes.likes} + 1`,
})
.where(eq(Likes.postId, postId))
.returning()
.get();

return likes;
return result?.likes;
},
}),

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { actions, isInputError } from 'astro:actions';
import { useState } from 'react';
import {createLoggerFromFlags} from "../../../../../src/cli/flags.ts";

export function PostComment({
postId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { type CollectionEntry, getEntry } from 'astro:content';
import { type CollectionEntry, getEntry, render } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';
import { Logout } from '../../components/Logout';
import { db, eq, Comment, Likes } from 'astro:db';
Expand All @@ -11,7 +11,7 @@ import { isInputError } from 'astro:actions';
type Props = CollectionEntry<'blog'>;

const post = await getEntry('blog', Astro.params.slug)!;
const { Content } = await post.render();
const { Content } = await render(post);

if (Astro.url.searchParams.has('like')) {
await Astro.callAction(actions.blog.like.orThrow, { postId: post.id });
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/actions/consts.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const VIRTUAL_MODULE_ID = 'astro:actions';
export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
export const ACTIONS_TYPES_FILE = 'actions.d.ts';
export const VIRTUAL_INTERNAL_MODULE_ID = 'astro:internal-actions';
export const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = '\0astro:internal-actions';
export const ASTRO_ACTIONS_INTERNAL_MODULE_ID = 'astro-internal:actions';
export const RESOLVED_ASTRO_ACTIONS_INTERNAL_MODULE_ID = '\0' + ASTRO_ACTIONS_INTERNAL_MODULE_ID;
export const NOOP_ACTIONS = '\0noop-actions';

export const ACTION_QUERY_PARAMS = {
Expand Down
7 changes: 1 addition & 6 deletions packages/astro/src/actions/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,13 @@ export default function astroIntegrationActionsRouteHandler({
return {
name: VIRTUAL_MODULE_ID,
hooks: {
async 'astro:config:setup'(params) {
async 'astro:config:setup'() {
settings.injectedRoutes.push({
pattern: ACTION_RPC_ROUTE_PATTERN,
entrypoint: 'astro/actions/runtime/route.js',
prerender: false,
origin: 'internal',
});

params.addMiddleware({
entrypoint: 'astro/actions/runtime/middleware.js',
order: 'post',
});
},
'astro:config:done': async (params) => {
if (params.buildOutput === 'static') {
Expand Down
20 changes: 20 additions & 0 deletions packages/astro/src/actions/loadActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {ModuleLoader} from "../core/module-loader/index.js";
import {ASTRO_ACTIONS_INTERNAL_MODULE_ID} from "./consts.js";
import type {SSRActions} from "../core/app/types.js";
import {ActionsCantBeLoaded} from "../core/errors/errors-data.js";
import {AstroError} from "../core/errors/index.js";

/**
* It accepts a module loader and the astro settings, and it attempts to load the middlewares defined in the configuration.
*
* If not middlewares were not set, the function returns an empty array.
*/
export async function loadActions(moduleLoader: ModuleLoader) {
try {
return (await moduleLoader.import(
ASTRO_ACTIONS_INTERNAL_MODULE_ID,
)) as SSRActions;
} catch (error: any) {
throw new AstroError(ActionsCantBeLoaded, {cause: error});
}
}
44 changes: 39 additions & 5 deletions packages/astro/src/actions/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { shouldAppendForwardSlash } from '../core/build/util.js';
import type { AstroSettings } from '../types/astro.js';
import {
NOOP_ACTIONS,
RESOLVED_VIRTUAL_INTERNAL_MODULE_ID,
RESOLVED_ASTRO_ACTIONS_INTERNAL_MODULE_ID,
RESOLVED_VIRTUAL_MODULE_ID,
VIRTUAL_INTERNAL_MODULE_ID,
ASTRO_ACTIONS_INTERNAL_MODULE_ID,
VIRTUAL_MODULE_ID,
} from './consts.js';
import { isActionsFilePresent } from './utils.js';
import { getOutputDirectory } from '../prerender/utils.js';
import type { StaticBuildOptions } from '../core/build/types.js';
import type { BuildInternals } from '../core/build/internal.js';
import { addRollupInput } from '../core/build/add-rollup-input.js';

/**
* This plugin is responsible to load the known file `actions/index.js` / `actions.js`
Expand All @@ -24,7 +28,7 @@ export function vitePluginUserActions({ settings }: { settings: AstroSettings })
if (id === NOOP_ACTIONS) {
return NOOP_ACTIONS;
}
if (id === VIRTUAL_INTERNAL_MODULE_ID) {
if (id === ASTRO_ACTIONS_INTERNAL_MODULE_ID) {
const resolvedModule = await this.resolve(
`${decodeURI(new URL('actions', settings.config.srcDir).pathname)}`,
);
Expand All @@ -33,20 +37,50 @@ export function vitePluginUserActions({ settings }: { settings: AstroSettings })
return NOOP_ACTIONS;
}
resolvedActionsId = resolvedModule.id;
return RESOLVED_VIRTUAL_INTERNAL_MODULE_ID;
return RESOLVED_ASTRO_ACTIONS_INTERNAL_MODULE_ID;
}
},

load(id) {
if (id === NOOP_ACTIONS) {
return 'export const server = {}';
} else if (id === RESOLVED_VIRTUAL_INTERNAL_MODULE_ID) {
} else if (id === RESOLVED_ASTRO_ACTIONS_INTERNAL_MODULE_ID) {
return `export { server } from '${resolvedActionsId}';`;
}
},
};
}

/**
* This plugin is used to retrieve the final entry point of the bundled actions.ts file
* @param opts
* @param internals
*/
export function vitePluginActionsBuild(
opts: StaticBuildOptions,
internals: BuildInternals,
): VitePlugin {
return {
name: '@astro/plugin-actions-build',

options(options) {
return addRollupInput(options, [ASTRO_ACTIONS_INTERNAL_MODULE_ID]);
},

writeBundle(_, bundle) {
for (const [chunkName, chunk] of Object.entries(bundle)) {
if (
chunk.type !== 'asset' &&
chunk.facadeModuleId === RESOLVED_ASTRO_ACTIONS_INTERNAL_MODULE_ID
) {
const outputDirectory = getOutputDirectory(opts.settings);
internals.astroActionsEntryPoint = new URL(chunkName, outputDirectory);
}
}
},
};
}

export function vitePluginActions({
fs,
settings,
Expand Down
13 changes: 0 additions & 13 deletions packages/astro/src/actions/runtime/middleware.ts

This file was deleted.

39 changes: 0 additions & 39 deletions packages/astro/src/actions/runtime/virtual/get-action.ts

This file was deleted.

9 changes: 4 additions & 5 deletions packages/astro/src/actions/runtime/virtual/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
isActionAPIContext,
} from '../utils.js';
import type { Locals } from '../utils.js';
import { getAction } from './get-action.js';
import {
ACTION_QUERY_PARAMS,
ActionError,
Expand Down Expand Up @@ -239,7 +238,7 @@ function unwrapBaseObjectSchema(schema: z.ZodType, unparsedInput: FormData) {
return schema;
}

export type ActionMiddlewareContext = {
export type AstroActionContext = {
/** Information about an incoming action request. */
action?: {
/** Whether an action was called using an RPC function or by using an HTML form action. */
Expand Down Expand Up @@ -268,15 +267,15 @@ export type ActionMiddlewareContext = {
/**
* Access information about Action requests from middleware.
*/
export function getActionContext(context: APIContext): ActionMiddlewareContext {
export function getActionContext(context: APIContext): AstroActionContext {
const callerInfo = getCallerInfo(context);

// Prevents action results from being handled on a rewrite.
// Also prevents our *own* fallback middleware from running
// if the user's middleware has already handled the result.
const actionResultAlreadySet = Boolean((context.locals as Locals)._actionPayload);

let action: ActionMiddlewareContext['action'] = undefined;
let action: AstroActionContext['action'] = undefined;

if (callerInfo && context.request.method === 'POST' && !actionResultAlreadySet) {
action = {
Expand All @@ -291,7 +290,7 @@ export function getActionContext(context: APIContext): ActionMiddlewareContext {
? removeTrailingForwardSlash(callerInfo.name)
: callerInfo.name;

const baseAction = await getAction(callerInfoName);
const baseAction = await pipeline.getAction(callerInfoName);
let input;
try {
input = await parseRequestBody(context.request);
Expand Down
6 changes: 5 additions & 1 deletion packages/astro/src/actions/runtime/virtual/shared.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { parse as devalueParse, stringify as devalueStringify } from 'devalue';
import type { z } from 'zod';
import { REDIRECT_STATUS_CODES } from '../../../core/constants.js';
import { ActionsReturnedInvalidDataError } from '../../../core/errors/errors-data.js';
import {ActionCalledFromServerError, ActionsReturnedInvalidDataError} from '../../../core/errors/errors-data.js';
import { AstroError } from '../../../core/errors/errors.js';
import { appendForwardSlash as _appendForwardSlash } from '../../../core/path.js';
import { ACTION_QUERY_PARAMS as _ACTION_QUERY_PARAMS } from '../../consts.js';
Expand Down Expand Up @@ -309,3 +309,7 @@ const actionResultErrorStack = (function actionResultErrorStackFn() {
},
};
})();

export function astroCalledServerError(): AstroError {
return new AstroError(ActionCalledFromServerError);
}
7 changes: 7 additions & 0 deletions packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type {
SSRResult,
} from '../../types/public/internal.js';
import type { SinglePageBuiltModule } from '../build/types.js';
import type { ActionAccept, ActionClient } from '../../actions/runtime/virtual/server.js';
import type { ZodType } from 'zod';

export type ComponentPath = string;

Expand Down Expand Up @@ -75,6 +77,7 @@ export type SSRManifest = {
key: Promise<CryptoKey>;
i18n: SSRManifestI18n | undefined;
middleware?: () => Promise<AstroMiddlewareInstance> | AstroMiddlewareInstance;
actions?: SSRActions;
checkOrigin: boolean;
sessionConfig?: ResolvedSessionConfig<any>;
cacheDir: string | URL;
Expand All @@ -85,6 +88,10 @@ export type SSRManifest = {
buildServerDir: string | URL;
};

export type SSRActions = {
server: Record<string, ActionClient<unknown, ActionAccept, ZodType>>;
};

export type SSRManifestI18n = {
fallback: Record<string, string> | undefined;
fallbackType: 'redirect' | 'rewrite';
Expand Down
Loading