0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-06 22:10:10 -05:00
astro/.changeset/shaggy-moons-peel.md
Ben Holmes c0c509b6bf
Actions experimental release (#10858)
* feat: port astro-actions poc

* feat: basic blog example

* feat: basic validationError class

* feat: standard error types and safe() wrapper

* refactor: move enhanceProps to astro:actions

* fix: throw internal server errors

* chore: refine enhance: true error message

* fix: remove FormData fallback from route

* refactor: clarify what enhance: true allows

* feat: progressively enhanced comments

* chore: changeset

* refactor: enhance -> acceptFormData

* wip: migrate actions to core

* feat: working actions demo from astro core!

* chore: changeset

* chore: delete old changeset

* fix: Function type lint

* refactor: expose defineAction from `astro:actions`

* fix: add null check to experimental

* fix: export `types/actions.d.ts`

* feat: more robust form data parsing

* feat: support formData from rpc call

* feat: remove acceptFormData flag requirement

* feat: add actions.d.ts type reference on startup

* refactor: actionNameProps -> getNameProps

* fix: actions type import

* chore: expose zod from `astro:actions`

* fix: zod export path

* feat: add explicit `accept` property

* Use zod package instead of relative path outside of src

* feat: clean up error throwing and handling flow

* fix: make `accept` optional

* docs: beef up actions experimental docs

* fix: defineAction type narrowing on `accept`

* fix: bad `getNameProps()` arg type

* refactor: move to single `error` object + `isInputError()` util

* fix: move res.json() parse to avoid double parse

* feat: support async zod schemas

* feat: serialize and expose zod properties on input error

* feat: test input error in comment example

* fix: remove ZodError import

* fix: add actions-module to files export

* fix: use workspace for test pkg versions

* refactor: default export -> server export

* fix: type inference for json vs. form

* refactor: accept form -> defineFormAction

* refactor: better callSafely signature

* feat: block action calls from the server with RFC link

* feat: move getActionResult to global

* refactor: getNameProps -> getActionProps

* refactor: body.toString()

* edit: capitAl

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* edit: highlight `actions`

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* edit: add actions file name

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* edit: not you can. You DO

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* edit: declare with feeling

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* edit: clarify what the `handler` does

* edit: schema -> input

* edit: add FormData mdn reference

* edit: add defineFormAction() explainer

* refactor: inline getDotAstroTypeRefs

* edit: yeah yeah maybe

* fix: existsSync test mock

* refactor: use callSafely in middleware

* test: upgradeFormData()

* chore: stray console log

* refactor: extract helper functions

* fix: include status in error response

* fix: return `undefined` when there's no action result

* fix: content-type

* test: e2e like button action

* test: comment e2e

* fix: existsSync mock for other sync test

* test: action dev server raw fetch

* test: build preview

* chore: fix lock

* fix: add dotAstroDir to existsSync

* chore: slim down e2e fixture

* chore: remove unneeded disabled test

* refactor: better api context error

* fix: return `false` for envDts

* refactor: defineFormAction -> defineAction with accept

* fix: check FormData on getActionProps

* edit: uppercase

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

* fix: add switch default for 500

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* fix: add `toLowerCase()` on content-type check

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* chore: use VIRTUAL_MODULE_ID for plugin

* fix: remove incorrect ts-ignore

* chore: remove unneeded POST method check

* refactor: route callSafely

* refactor: error switch case to map

* chore: add link to trpc error code table

* fix: add readable error on failed json.stringify

* refactor: add param -> callerParam with comment

* feat: always return safe from getActionResult()

* refactor: move actions module to templates/

* refactor: remove unneeded existsSync on dotAstro

* fix: hasContentType util for toLowerCase()

* chore: comment on 415 code

* refactor: upgradeFormData -> formDataToObj

* fix: avoid leaking stack in production

* refactor: defineProperty with write false

* fix: revert package.json back to spaces

* edit: use config docs for changeset

* refactor: stringifiedActionsPath -> stringifiedActionsImport

* fix: avoid double-handling for route

* fix: support zero arg actions

* refactor: move actionHandler to helper fn

* fix: restore mdast deps

* docs: add `output` to config

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: bholmesdev <bholmesdev@gmail.com>
2024-05-08 07:53:17 -04:00

95 lines
3.2 KiB
Markdown

---
"astro": minor
---
Adds experimental support for the Actions API. Actions let you define type-safe endpoints you can query from client components with progressive enhancement built in.
Actions help you write type-safe backend functions you can call from anywhere. Enable server rendering [using the `output` property](https://docs.astro.build/en/basics/rendering-modes/#on-demand-rendered) and add the `actions` flag to the `experimental` object:
```js
{
output: 'hybrid', // or 'server'
experimental: {
actions: true,
},
}
```
Declare all your actions in `src/actions/index.ts`. This file is the global actions handler.
Define an action using the `defineAction()` utility from the `astro:actions` module. These accept the `handler` property to define your server-side request handler. If your action accepts arguments, apply the `input` property to validate parameters with Zod.
This example defines two actions: `like` and `comment`. The `like` action accepts a JSON object with a `postId` string, while the `comment` action accepts [FormData](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects) with `postId`, `author`, and `body` strings. Each `handler` updates your database and return a type-safe response.
```ts
// src/actions/index.ts
import { defineAction, z } from "astro:actions";
export const server = {
like: defineAction({
input: z.object({ postId: z.string() }),
handler: async ({ postId }, context) => {
// update likes in db
return likes;
},
}),
comment: defineAction({
accept: 'form',
input: z.object({
postId: z.string(),
author: z.string(),
body: z.string(),
}),
handler: async ({ postId }, context) => {
// insert comments in db
return comment;
},
}),
};
```
Then, call an action from your client components using the `actions` object from `astro:actions`. You can pass a type-safe object when using JSON, or a [FormData](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Using_FormData_Objects) object when using `accept: 'form'` in your action definition:
```tsx "actions"
// src/components/blog.tsx
import { actions } from "astro:actions";
import { useState } from "preact/hooks";
export function Like({ postId }: { postId: string }) {
const [likes, setLikes] = useState(0);
return (
<button
onClick={async () => {
const newLikes = await actions.like({ postId });
setLikes(newLikes);
}}
>
{likes} likes
</button>
);
}
export function Comment({ postId }: { postId: string }) {
return (
<form
onSubmit={async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const result = await actions.blog.comment(formData);
// handle result
}}
>
<input type="hidden" name="postId" value={postId} />
<label for="author">Author</label>
<input id="author" type="text" name="author" />
<textarea rows={10} name="body"></textarea>
<button type="submit">Post</button>
</form>
);
}
```
For a complete overview, and to give feedback on this experimental API, see the [Actions RFC](https://github.com/withastro/roadmap/blob/actions/proposals/0046-actions.md).