Skip to content

Commit 7082c62

Browse files
Create @fujocoded/astro-smooth-actions package
1 parent 837d71f commit 7082c62

File tree

6 files changed

+184
-38
lines changed

6 files changed

+184
-38
lines changed

astro-smooth-actions/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
`@fujocoded/astro-smooth-actions`
2+
3+
A quick integration to make Astro Action smoother, even when used without
4+
client-side JavaScript.
5+
6+
When installed, it automatically persists <u>form</u> action results in the
7+
session, preventing URL changes and form resubmission dialogs. and providing a
8+
smooth user experience
9+
10+
If you don't know what any of this means, you should likely install this if:
11+
12+
- use Astro Actions with Forms
13+
- [have access to session storage](https://docs.astro.build/en/guides/sessions/)
14+
- want your forms to work smoothly without client side JavaScript, .
15+
16+
## How It Works
17+
18+
> [!NOTE]
19+
>
20+
> In practice, this integration wraps the pattern described in ["Advanced:
21+
> Persist action results with a
22+
> session"](https://docs.astro.build/en/guides/actions/#advanced-persist-action-results-with-a-session)
23+
> into an installable plugin.
24+
25+
This integration implements the [POST/Redirect/GET
26+
pattern](https://docs.astro.bu
27+
ild/en/guides/actions/#advanced-persist-action-results-with-a-session):
28+
29+
1. Form submission (with form payload) is intercepted by its middleware
30+
2. Action is executed on the server
31+
3. Result is stored in the session
32+
4. User is redirected back to original page (without form payload)
33+
5. New page load retrieves result from session
34+
6. Result is cleared from session
35+
7. Page is displayed to the user
36+
37+
Since this final page is just a regular page with no form submission data, the
38+
user avoids all the issues associated with pages resulting from form requests.

astro-smooth-actions/package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "@fujocoded/astro-smooth-action",
3+
"type": "module",
4+
"version": "0.0.1",
5+
"description": "An Astro integration for smooth action handling",
6+
"main": "dist/index.cjs",
7+
"module": "dist/index.js",
8+
"exports": {
9+
".": {
10+
"import": {
11+
"types": "./dist/index.d.ts",
12+
"default": "./dist/index.js"
13+
}
14+
}
15+
},
16+
"files": [
17+
"dist",
18+
"LICENSE",
19+
"README.md",
20+
"package.json"
21+
],
22+
"scripts": {
23+
"build": "tsdown",
24+
"validate": " npx publint"
25+
},
26+
"keywords": [
27+
"withastro",
28+
"astro-integration"
29+
],
30+
"repository": {
31+
"type": "git",
32+
"url": "git+https://github.com/fujowebdev/fujocoded-plugins.git"
33+
},
34+
"author": "FujoCoded LLC",
35+
"license": "MIT",
36+
"devDependencies": {
37+
"astro": "^5.0.0",
38+
"tsdown": "^0.14.1"
39+
},
40+
"publishConfig": {
41+
"access": "public"
42+
}
43+
}

astro-smooth-actions/src/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { AstroIntegration } from "astro";
2+
import path from "node:path";
3+
4+
export { onRequest } from "./middleware.js";
5+
6+
export default function astroActionSessions(): AstroIntegration {
7+
return {
8+
name: "astro-smooth-actions",
9+
hooks: {
10+
"astro:config:setup": ({ addMiddleware }) => {
11+
addMiddleware({
12+
order: "pre",
13+
entrypoint: path.join(import.meta.dirname, "./middleware.js"),
14+
});
15+
},
16+
"astro:config:done": ({ config, logger }) => {
17+
if (!config.session?.driver && !config.adapter) {
18+
logger.warn(
19+
"The astro-smooth-actions integration uses Astro's session storage, which requires a session driver or an adapter. You have neither set. For more information, see https://docs.astro.build/en/guides/sessions/"
20+
);
21+
}
22+
},
23+
},
24+
};
25+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { getActionContext } from "astro:actions";
2+
import { type MiddlewareHandler } from "astro";
3+
4+
export const onRequest: MiddlewareHandler = async (context, next) => {
5+
const { action, setActionResult, serializeActionResult } =
6+
getActionContext(context);
7+
8+
const latestAction = await context.session?.get(
9+
`smooth-actions:latest-astro-action`
10+
);
11+
12+
if (latestAction) {
13+
// There was an action result stored in the session, so we can restore it
14+
// for this request and then delete the session entry. This makes it look
15+
// like the action was executed on this request, and there was no magic
16+
// redirect happening behind the scenes.
17+
setActionResult(latestAction.name, latestAction.result);
18+
await context.session?.delete(`smooth-actions:latest-astro-action`);
19+
return next();
20+
}
21+
22+
if (action?.calledFrom !== "form") {
23+
// If the action was not called from a form, we can just move on to the next
24+
// middleware. This is not this middleware's job to handle.
25+
return next();
26+
}
27+
28+
// If we're here, there was an action called from a form, so we need to execute it. Then we
29+
// store the result in the session so that it can be restored for the next
30+
// request.
31+
const result = await action.handler();
32+
context.session?.set(`smooth-actions:latest-astro-action`, {
33+
name: action.name,
34+
result: serializeActionResult(result),
35+
});
36+
37+
if (result.error) {
38+
// If the action failed, we need to redirect back to the page where the
39+
// form is located. This is because the form will need to be resubmitted
40+
// with the error message.
41+
const referer = context.request.headers.get("Referer");
42+
if (!referer) {
43+
throw new Error("Action submission went wrong");
44+
}
45+
return context.redirect(referer);
46+
}
47+
48+
// If the action succeeded, we can redirect to the original page. This will
49+
// get rid of the POST request and replace it with a GET request.
50+
return context.redirect(context.originPathname);
51+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineConfig, type UserConfig } from "tsdown";
2+
import { glob } from "glob";
3+
4+
export default defineConfig([
5+
{
6+
name: "integration",
7+
dts: true,
8+
clean: true,
9+
unbundle: true,
10+
entry: ["src/index.ts", "src/middleware.ts"],
11+
external: [/^astro:/],
12+
},
13+
]);

0 commit comments

Comments
 (0)