astro 5.0.0-beta.7 → 5.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client.d.ts +24 -5
- package/components/Image.astro +26 -3
- package/components/Picture.astro +33 -6
- package/components/image.css +17 -0
- package/dist/actions/consts.d.ts +1 -1
- package/dist/actions/consts.js +3 -2
- package/dist/actions/integration.js +5 -4
- package/dist/actions/runtime/middleware.d.ts +0 -8
- package/dist/actions/runtime/middleware.js +7 -111
- package/dist/actions/runtime/route.js +5 -26
- package/dist/actions/runtime/utils.d.ts +10 -0
- package/dist/actions/runtime/utils.js +8 -1
- package/dist/actions/runtime/virtual/client.d.ts +1 -0
- package/dist/actions/runtime/virtual/client.js +5 -1
- package/dist/actions/runtime/virtual/get-action.js +1 -1
- package/dist/actions/runtime/virtual/server.d.ts +32 -2
- package/dist/actions/runtime/virtual/server.js +86 -3
- package/dist/actions/runtime/virtual/shared.d.ts +0 -1
- package/dist/actions/utils.d.ts +1 -2
- package/dist/actions/utils.js +2 -0
- package/dist/assets/build/generate.js +2 -2
- package/dist/assets/consts.js +9 -1
- package/dist/assets/endpoint/config.js +2 -1
- package/dist/assets/internal.d.ts +5 -1
- package/dist/assets/internal.js +66 -12
- package/dist/assets/layout.d.ts +25 -0
- package/dist/assets/layout.js +103 -0
- package/dist/assets/runtime.d.ts +10 -0
- package/dist/assets/runtime.js +69 -0
- package/dist/assets/services/service.d.ts +3 -1
- package/dist/assets/services/service.js +57 -37
- package/dist/assets/services/sharp.js +28 -3
- package/dist/assets/types.d.ts +53 -0
- package/dist/assets/utils/imageAttributes.d.ts +10 -0
- package/dist/assets/utils/imageAttributes.js +33 -0
- package/dist/assets/utils/imageKind.js +1 -1
- package/dist/assets/utils/index.d.ts +1 -0
- package/dist/assets/utils/index.js +2 -0
- package/dist/assets/utils/node/emitAsset.d.ts +4 -1
- package/dist/assets/utils/node/emitAsset.js +3 -0
- package/dist/assets/utils/svg.d.ts +5 -0
- package/dist/assets/utils/svg.js +22 -0
- package/dist/assets/vite-plugin-assets.js +20 -10
- package/dist/config/index.js +2 -2
- package/dist/container/index.js +2 -1
- package/dist/content/content-layer.js +3 -3
- package/dist/content/loaders/glob.js +2 -1
- package/dist/content/runtime.d.ts +4 -3
- package/dist/content/runtime.js +10 -2
- package/dist/content/server-listeners.js +10 -1
- package/dist/content/types-generator.js +23 -30
- package/dist/content/utils.d.ts +1 -1
- package/dist/content/utils.js +12 -8
- package/dist/core/app/pipeline.js +0 -1
- package/dist/core/base-pipeline.d.ts +1 -2
- package/dist/core/base-pipeline.js +1 -7
- package/dist/core/build/generate.js +2 -7
- package/dist/core/build/pipeline.js +2 -2
- package/dist/core/build/plugins/plugin-ssr.js +0 -1
- package/dist/core/config/schema.d.ts +148 -0
- package/dist/core/config/schema.js +30 -4
- package/dist/core/constants.d.ts +4 -0
- package/dist/core/constants.js +3 -1
- package/dist/core/dev/container.js +3 -1
- package/dist/core/dev/dev.js +1 -1
- package/dist/core/errors/dev/vite.d.ts +1 -0
- package/dist/core/errors/dev/vite.js +1 -0
- package/dist/core/errors/errors-data.d.ts +17 -15
- package/dist/core/errors/errors-data.js +12 -7
- package/dist/core/messages.js +2 -2
- package/dist/core/middleware/index.js +5 -1
- package/dist/core/middleware/noop-middleware.js +5 -1
- package/dist/core/middleware/sequence.js +9 -0
- package/dist/core/middleware/vite-plugin.js +1 -13
- package/dist/core/module-loader/vite.js +18 -0
- package/dist/core/render-context.js +30 -11
- package/dist/core/routing/astro-designed-error-pages.js +4 -2
- package/dist/core/routing/manifest/create.d.ts +3 -1
- package/dist/core/routing/manifest/create.js +12 -5
- package/dist/core/routing/manifest/serialization.js +2 -1
- package/dist/core/routing/match.d.ts +7 -0
- package/dist/core/routing/match.js +4 -0
- package/dist/core/routing/rewrite.d.ts +1 -1
- package/dist/core/routing/rewrite.js +1 -1
- package/dist/core/server-islands/endpoint.js +2 -1
- package/dist/core/sync/index.js +12 -3
- package/dist/integrations/hooks.d.ts +5 -0
- package/dist/integrations/hooks.js +41 -1
- package/dist/runtime/server/render/server-islands.js +15 -14
- package/dist/runtime/server/render/util.d.ts +1 -0
- package/dist/runtime/server/render/util.js +1 -0
- package/dist/transitions/router.js +1 -1
- package/dist/types/astro.d.ts +3 -3
- package/dist/types/public/config.d.ts +266 -6
- package/dist/types/public/context.d.ts +5 -0
- package/dist/types/public/integrations.d.ts +32 -10
- package/dist/types/public/internal.d.ts +13 -0
- package/dist/vite-plugin-astro-server/plugin.js +28 -10
- package/dist/vite-plugin-astro-server/route.d.ts +1 -2
- package/dist/vite-plugin-astro-server/route.js +24 -14
- package/package.json +4 -2
- package/templates/actions.mjs +1 -10
package/client.d.ts
CHANGED
|
@@ -47,7 +47,9 @@ declare module 'astro:assets' {
|
|
|
47
47
|
getImage: (
|
|
48
48
|
options: import('./dist/assets/types.js').UnresolvedImageTransform,
|
|
49
49
|
) => Promise<import('./dist/assets/types.js').GetImageResult>;
|
|
50
|
-
imageConfig: import('./dist/types/public/config.js').AstroConfig['image']
|
|
50
|
+
imageConfig: import('./dist/types/public/config.js').AstroConfig['image'] & {
|
|
51
|
+
experimentalResponsiveImages: boolean;
|
|
52
|
+
};
|
|
51
53
|
getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService;
|
|
52
54
|
inferRemoteSize: typeof import('./dist/assets/utils/index.js').inferRemoteSize;
|
|
53
55
|
Image: typeof import('./components/Image.astro').default;
|
|
@@ -101,14 +103,31 @@ declare module '*.webp' {
|
|
|
101
103
|
const metadata: ImageMetadata;
|
|
102
104
|
export default metadata;
|
|
103
105
|
}
|
|
104
|
-
declare module '*.svg' {
|
|
105
|
-
const metadata: ImageMetadata;
|
|
106
|
-
export default metadata;
|
|
107
|
-
}
|
|
108
106
|
declare module '*.avif' {
|
|
109
107
|
const metadata: ImageMetadata;
|
|
110
108
|
export default metadata;
|
|
111
109
|
}
|
|
110
|
+
declare module '*.svg' {
|
|
111
|
+
type Props = {
|
|
112
|
+
/**
|
|
113
|
+
* Accesible, short-text description
|
|
114
|
+
*
|
|
115
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title|MDN Reference}
|
|
116
|
+
*/
|
|
117
|
+
title?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Shorthand for setting the `height` and `width` properties
|
|
120
|
+
*/
|
|
121
|
+
size?: number | string;
|
|
122
|
+
/**
|
|
123
|
+
* Override the default rendering mode for SVGs
|
|
124
|
+
*/
|
|
125
|
+
mode?: import('./dist/assets/utils/svg.js').SvgRenderMode;
|
|
126
|
+
} & astroHTML.JSX.SVGAttributes;
|
|
127
|
+
|
|
128
|
+
const Component: ((_props: Props) => any) & ImageMetadata;
|
|
129
|
+
export default Component;
|
|
130
|
+
}
|
|
112
131
|
|
|
113
132
|
declare module 'astro:transitions' {
|
|
114
133
|
type TransitionModule = typeof import('./dist/virtual-modules/transitions.js');
|
package/components/Image.astro
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
|
|
2
|
+
import { type LocalImageProps, type RemoteImageProps, getImage, imageConfig } from 'astro:assets';
|
|
3
|
+
import type { UnresolvedImageTransform } from '../dist/assets/types';
|
|
4
|
+
import { applyResponsiveAttributes } from '../dist/assets/utils/imageAttributes.js';
|
|
3
5
|
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
|
|
4
6
|
import type { HTMLAttributes } from '../types';
|
|
7
|
+
import './image.css';
|
|
5
8
|
|
|
6
9
|
// The TypeScript diagnostic for JSX props uses the last member of the union to suggest props, so it would be better for
|
|
7
10
|
// LocalImageProps to be last. Unfortunately, when we do this the error messages that remote images get are complete nonsense
|
|
@@ -23,7 +26,17 @@ if (typeof props.height === 'string') {
|
|
|
23
26
|
props.height = parseInt(props.height);
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
const
|
|
29
|
+
const layout = props.layout ?? imageConfig.experimentalLayout ?? 'none';
|
|
30
|
+
const useResponsive = imageConfig.experimentalResponsiveImages && layout !== 'none';
|
|
31
|
+
|
|
32
|
+
if (useResponsive) {
|
|
33
|
+
// Apply defaults from imageConfig if not provided
|
|
34
|
+
props.layout ??= imageConfig.experimentalLayout;
|
|
35
|
+
props.fit ??= imageConfig.experimentalObjectFit ?? 'cover';
|
|
36
|
+
props.position ??= imageConfig.experimentalObjectPosition ?? 'center';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const image = await getImage(props as UnresolvedImageTransform);
|
|
27
40
|
|
|
28
41
|
const additionalAttributes: HTMLAttributes<'img'> = {};
|
|
29
42
|
if (image.srcSet.values.length > 0) {
|
|
@@ -33,6 +46,16 @@ if (image.srcSet.values.length > 0) {
|
|
|
33
46
|
if (import.meta.env.DEV) {
|
|
34
47
|
additionalAttributes['data-image-component'] = 'true';
|
|
35
48
|
}
|
|
49
|
+
|
|
50
|
+
const attributes = useResponsive
|
|
51
|
+
? applyResponsiveAttributes({
|
|
52
|
+
layout,
|
|
53
|
+
image,
|
|
54
|
+
props,
|
|
55
|
+
additionalAttributes,
|
|
56
|
+
})
|
|
57
|
+
: { ...additionalAttributes, ...image.attributes };
|
|
36
58
|
---
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
{/* Applying class outside of the spread prevents it from applying unnecessary astro-* classes */}
|
|
61
|
+
<img src={image.src} {...attributes} class={attributes.class} />
|
package/components/Picture.astro
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { type LocalImageProps, type RemoteImageProps, getImage } from 'astro:assets';
|
|
2
|
+
import { type LocalImageProps, type RemoteImageProps, getImage, imageConfig } from 'astro:assets';
|
|
3
3
|
import * as mime from 'mrmime';
|
|
4
|
+
import { applyResponsiveAttributes } from '../dist/assets/utils/imageAttributes';
|
|
4
5
|
import { isESMImportedImage, resolveSrc } from '../dist/assets/utils/imageKind';
|
|
5
6
|
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';
|
|
6
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
GetImageResult,
|
|
9
|
+
ImageOutputFormat,
|
|
10
|
+
UnresolvedImageTransform,
|
|
11
|
+
} from '../dist/types/public/index.js';
|
|
7
12
|
import type { HTMLAttributes } from '../types';
|
|
13
|
+
import './image.css';
|
|
8
14
|
|
|
9
15
|
type Props = (LocalImageProps | RemoteImageProps) & {
|
|
10
16
|
formats?: ImageOutputFormat[];
|
|
@@ -37,6 +43,17 @@ if (scopedStyleClass) {
|
|
|
37
43
|
pictureAttributes.class = scopedStyleClass;
|
|
38
44
|
}
|
|
39
45
|
}
|
|
46
|
+
|
|
47
|
+
const layout = props.layout ?? imageConfig.experimentalLayout ?? 'none';
|
|
48
|
+
const useResponsive = imageConfig.experimentalResponsiveImages && layout !== 'none';
|
|
49
|
+
|
|
50
|
+
if (useResponsive) {
|
|
51
|
+
// Apply defaults from imageConfig if not provided
|
|
52
|
+
props.layout ??= imageConfig.experimentalLayout;
|
|
53
|
+
props.fit ??= imageConfig.experimentalObjectFit ?? 'cover';
|
|
54
|
+
props.position ??= imageConfig.experimentalObjectPosition ?? 'center';
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
for (const key in props) {
|
|
41
58
|
if (key.startsWith('data-astro-cid')) {
|
|
42
59
|
pictureAttributes[key] = props[key];
|
|
@@ -53,7 +70,7 @@ const optimizedImages: GetImageResult[] = await Promise.all(
|
|
|
53
70
|
format: format,
|
|
54
71
|
widths: props.widths,
|
|
55
72
|
densities: props.densities,
|
|
56
|
-
}),
|
|
73
|
+
} as UnresolvedImageTransform),
|
|
57
74
|
),
|
|
58
75
|
);
|
|
59
76
|
|
|
@@ -71,7 +88,7 @@ const fallbackImage = await getImage({
|
|
|
71
88
|
format: resultFallbackFormat,
|
|
72
89
|
widths: props.widths,
|
|
73
90
|
densities: props.densities,
|
|
74
|
-
});
|
|
91
|
+
} as UnresolvedImageTransform);
|
|
75
92
|
|
|
76
93
|
const imgAdditionalAttributes: HTMLAttributes<'img'> = {};
|
|
77
94
|
const sourceAdditionalAttributes: HTMLAttributes<'source'> = {};
|
|
@@ -85,6 +102,15 @@ if (fallbackImage.srcSet.values.length > 0) {
|
|
|
85
102
|
imgAdditionalAttributes.srcset = fallbackImage.srcSet.attribute;
|
|
86
103
|
}
|
|
87
104
|
|
|
105
|
+
const attributes = useResponsive
|
|
106
|
+
? applyResponsiveAttributes({
|
|
107
|
+
layout,
|
|
108
|
+
image: fallbackImage,
|
|
109
|
+
props,
|
|
110
|
+
additionalAttributes: imgAdditionalAttributes,
|
|
111
|
+
})
|
|
112
|
+
: { ...imgAdditionalAttributes, ...fallbackImage.attributes };
|
|
113
|
+
|
|
88
114
|
if (import.meta.env.DEV) {
|
|
89
115
|
imgAdditionalAttributes['data-image-component'] = 'true';
|
|
90
116
|
}
|
|
@@ -94,7 +120,7 @@ if (import.meta.env.DEV) {
|
|
|
94
120
|
{
|
|
95
121
|
Object.entries(optimizedImages).map(([_, image]) => {
|
|
96
122
|
const srcsetAttribute =
|
|
97
|
-
props.densities || (!props.densities && !props.widths)
|
|
123
|
+
props.densities || (!props.densities && !props.widths && !useResponsive)
|
|
98
124
|
? `${image.src}${image.srcSet.values.length > 0 ? ', ' + image.srcSet.attribute : ''}`
|
|
99
125
|
: image.srcSet.attribute;
|
|
100
126
|
return (
|
|
@@ -106,5 +132,6 @@ if (import.meta.env.DEV) {
|
|
|
106
132
|
);
|
|
107
133
|
})
|
|
108
134
|
}
|
|
109
|
-
|
|
135
|
+
{/* Applying class outside of the spread prevents it from applying unnecessary astro-* classes */}
|
|
136
|
+
<img src={fallbackImage.src} {...attributes} class={attributes.class} />
|
|
110
137
|
</picture>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[data-astro-image] {
|
|
2
|
+
width: 100%;
|
|
3
|
+
height: auto;
|
|
4
|
+
object-fit: var(--fit);
|
|
5
|
+
object-position: var(--pos);
|
|
6
|
+
aspect-ratio: var(--w) / var(--h);
|
|
7
|
+
}
|
|
8
|
+
/* Styles for responsive layout */
|
|
9
|
+
[data-astro-image='responsive'] {
|
|
10
|
+
max-width: calc(var(--w) * 1px);
|
|
11
|
+
max-height: calc(var(--h) * 1px);
|
|
12
|
+
}
|
|
13
|
+
/* Styles for fixed layout */
|
|
14
|
+
[data-astro-image='fixed'] {
|
|
15
|
+
width: calc(var(--w) * 1px);
|
|
16
|
+
height: calc(var(--h) * 1px);
|
|
17
|
+
}
|
package/dist/actions/consts.d.ts
CHANGED
package/dist/actions/consts.js
CHANGED
|
@@ -6,12 +6,13 @@ const RESOLVED_VIRTUAL_INTERNAL_MODULE_ID = "\0astro:internal-actions";
|
|
|
6
6
|
const NOOP_ACTIONS = "\0noop-actions";
|
|
7
7
|
const ACTION_QUERY_PARAMS = {
|
|
8
8
|
actionName: "_astroAction",
|
|
9
|
-
actionPayload: "_astroActionPayload"
|
|
10
|
-
actionRedirect: "_astroActionRedirect"
|
|
9
|
+
actionPayload: "_astroActionPayload"
|
|
11
10
|
};
|
|
11
|
+
const ACTION_RPC_ROUTE_PATTERN = "/_actions/[...path]";
|
|
12
12
|
export {
|
|
13
13
|
ACTIONS_TYPES_FILE,
|
|
14
14
|
ACTION_QUERY_PARAMS,
|
|
15
|
+
ACTION_RPC_ROUTE_PATTERN,
|
|
15
16
|
NOOP_ACTIONS,
|
|
16
17
|
RESOLVED_VIRTUAL_INTERNAL_MODULE_ID,
|
|
17
18
|
RESOLVED_VIRTUAL_MODULE_ID,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActionsWithoutServerOutputError } from "../core/errors/errors-data.js";
|
|
2
2
|
import { AstroError } from "../core/errors/errors.js";
|
|
3
3
|
import { viteID } from "../core/util.js";
|
|
4
|
-
import { ACTIONS_TYPES_FILE, VIRTUAL_MODULE_ID } from "./consts.js";
|
|
4
|
+
import { ACTIONS_TYPES_FILE, ACTION_RPC_ROUTE_PATTERN, VIRTUAL_MODULE_ID } from "./consts.js";
|
|
5
5
|
function astroIntegrationActionsRouteHandler({
|
|
6
6
|
settings
|
|
7
7
|
}) {
|
|
@@ -9,10 +9,11 @@ function astroIntegrationActionsRouteHandler({
|
|
|
9
9
|
name: VIRTUAL_MODULE_ID,
|
|
10
10
|
hooks: {
|
|
11
11
|
async "astro:config:setup"(params) {
|
|
12
|
-
|
|
13
|
-
pattern:
|
|
12
|
+
settings.injectedRoutes.push({
|
|
13
|
+
pattern: ACTION_RPC_ROUTE_PATTERN,
|
|
14
14
|
entrypoint: "astro/actions/runtime/route.js",
|
|
15
|
-
prerender: false
|
|
15
|
+
prerender: false,
|
|
16
|
+
origin: "internal"
|
|
16
17
|
});
|
|
17
18
|
params.addMiddleware({
|
|
18
19
|
entrypoint: "astro/actions/runtime/middleware.js",
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
import { type SerializedActionResult } from './virtual/shared.js';
|
|
2
|
-
export type ActionPayload = {
|
|
3
|
-
actionResult: SerializedActionResult;
|
|
4
|
-
actionName: string;
|
|
5
|
-
};
|
|
6
|
-
export type Locals = {
|
|
7
|
-
_actionPayload: ActionPayload;
|
|
8
|
-
};
|
|
9
1
|
export declare const onRequest: import("../../types/public/common.js").MiddlewareHandler;
|
|
@@ -1,118 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { defineMiddleware } from "../../core/middleware/index.js";
|
|
4
|
-
import { getOriginPathname } from "../../core/routing/rewrite.js";
|
|
5
|
-
import { ACTION_QUERY_PARAMS } from "../consts.js";
|
|
6
|
-
import { formContentTypes, hasContentType } from "./utils.js";
|
|
7
|
-
import { getAction } from "./virtual/get-action.js";
|
|
8
|
-
import {
|
|
9
|
-
serializeActionResult
|
|
10
|
-
} from "./virtual/shared.js";
|
|
11
|
-
const decoder = new TextDecoder();
|
|
12
|
-
const encoder = new TextEncoder();
|
|
1
|
+
import { defineMiddleware } from "../../virtual-modules/middleware.js";
|
|
2
|
+
import { getActionContext } from "./virtual/server.js";
|
|
13
3
|
const onRequest = defineMiddleware(async (context, next) => {
|
|
14
|
-
if (context.isPrerendered)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
return next();
|
|
22
|
-
}
|
|
23
|
-
const locals = context.locals;
|
|
24
|
-
if (locals._actionPayload) return next();
|
|
25
|
-
const actionPayloadCookie = context.cookies.get(ACTION_QUERY_PARAMS.actionPayload)?.value;
|
|
26
|
-
if (actionPayloadCookie) {
|
|
27
|
-
const actionPayload = JSON.parse(decoder.decode(decodeBase64(actionPayloadCookie)));
|
|
28
|
-
if (!isActionPayload(actionPayload)) {
|
|
29
|
-
throw new Error("Internal: Invalid action payload in cookie.");
|
|
30
|
-
}
|
|
31
|
-
return renderResult({ context, next, ...actionPayload });
|
|
32
|
-
}
|
|
33
|
-
const actionName = context.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);
|
|
34
|
-
if (context.request.method === "POST" && actionName) {
|
|
35
|
-
return handlePost({ context, next, actionName });
|
|
4
|
+
if (context.isPrerendered) return next();
|
|
5
|
+
const { action, setActionResult, serializeActionResult } = getActionContext(context);
|
|
6
|
+
if (action?.calledFrom === "form") {
|
|
7
|
+
const actionResult = await action.handler();
|
|
8
|
+
setActionResult(action.name, serializeActionResult(actionResult));
|
|
36
9
|
}
|
|
37
10
|
return next();
|
|
38
11
|
});
|
|
39
|
-
async function renderResult({
|
|
40
|
-
context,
|
|
41
|
-
next,
|
|
42
|
-
actionResult,
|
|
43
|
-
actionName
|
|
44
|
-
}) {
|
|
45
|
-
const locals = context.locals;
|
|
46
|
-
locals._actionPayload = { actionResult, actionName };
|
|
47
|
-
const response = await next();
|
|
48
|
-
context.cookies.delete(ACTION_QUERY_PARAMS.actionPayload);
|
|
49
|
-
if (actionResult.type === "error") {
|
|
50
|
-
return new Response(response.body, {
|
|
51
|
-
status: actionResult.status,
|
|
52
|
-
statusText: actionResult.type,
|
|
53
|
-
headers: response.headers
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
return response;
|
|
57
|
-
}
|
|
58
|
-
async function handlePost({
|
|
59
|
-
context,
|
|
60
|
-
next,
|
|
61
|
-
actionName
|
|
62
|
-
}) {
|
|
63
|
-
const { request } = context;
|
|
64
|
-
const baseAction = await getAction(actionName);
|
|
65
|
-
const contentType = request.headers.get("content-type");
|
|
66
|
-
let formData;
|
|
67
|
-
if (contentType && hasContentType(contentType, formContentTypes)) {
|
|
68
|
-
formData = await request.clone().formData();
|
|
69
|
-
}
|
|
70
|
-
const { getActionResult, callAction, props, redirect, ...actionAPIContext } = context;
|
|
71
|
-
const action = baseAction.bind(actionAPIContext);
|
|
72
|
-
const actionResult = await action(formData);
|
|
73
|
-
if (context.url.searchParams.get(ACTION_QUERY_PARAMS.actionRedirect) === "false") {
|
|
74
|
-
return renderResult({
|
|
75
|
-
context,
|
|
76
|
-
next,
|
|
77
|
-
actionName,
|
|
78
|
-
actionResult: serializeActionResult(actionResult)
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
return redirectWithResult({ context, actionName, actionResult });
|
|
82
|
-
}
|
|
83
|
-
async function redirectWithResult({
|
|
84
|
-
context,
|
|
85
|
-
actionName,
|
|
86
|
-
actionResult
|
|
87
|
-
}) {
|
|
88
|
-
const cookieValue = encodeBase64(
|
|
89
|
-
encoder.encode(
|
|
90
|
-
JSON.stringify({
|
|
91
|
-
actionName,
|
|
92
|
-
actionResult: serializeActionResult(actionResult)
|
|
93
|
-
})
|
|
94
|
-
)
|
|
95
|
-
);
|
|
96
|
-
context.cookies.set(ACTION_QUERY_PARAMS.actionPayload, cookieValue);
|
|
97
|
-
if (actionResult.error) {
|
|
98
|
-
const referer2 = context.request.headers.get("Referer");
|
|
99
|
-
if (!referer2) {
|
|
100
|
-
throw new Error("Internal: Referer unexpectedly missing from Action POST request.");
|
|
101
|
-
}
|
|
102
|
-
return context.redirect(referer2);
|
|
103
|
-
}
|
|
104
|
-
const referer = getOriginPathname(context.request);
|
|
105
|
-
if (referer) {
|
|
106
|
-
return context.redirect(referer);
|
|
107
|
-
}
|
|
108
|
-
return context.redirect(context.url.pathname);
|
|
109
|
-
}
|
|
110
|
-
function isActionPayload(json) {
|
|
111
|
-
if (typeof json !== "object" || json == null) return false;
|
|
112
|
-
if (!("actionResult" in json) || typeof json.actionResult !== "object") return false;
|
|
113
|
-
if (!("actionName" in json) || typeof json.actionName !== "string") return false;
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
12
|
export {
|
|
117
13
|
onRequest
|
|
118
14
|
};
|
|
@@ -1,31 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { getAction } from "./virtual/get-action.js";
|
|
3
|
-
import { serializeActionResult } from "./virtual/shared.js";
|
|
1
|
+
import { getActionContext } from "./virtual/server.js";
|
|
4
2
|
const POST = async (context) => {
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
baseAction = await getAction(url.pathname);
|
|
9
|
-
} catch (e) {
|
|
10
|
-
if (import.meta.env.DEV) throw e;
|
|
11
|
-
console.error(e);
|
|
12
|
-
return new Response(e instanceof Error ? e.message : null, { status: 404 });
|
|
3
|
+
const { action, serializeActionResult } = getActionContext(context);
|
|
4
|
+
if (action?.calledFrom !== "rpc") {
|
|
5
|
+
return new Response("Not found", { status: 404 });
|
|
13
6
|
}
|
|
14
|
-
const
|
|
15
|
-
const contentLength = request.headers.get("Content-Length");
|
|
16
|
-
let args;
|
|
17
|
-
if (!contentType || contentLength === "0") {
|
|
18
|
-
args = void 0;
|
|
19
|
-
} else if (contentType && hasContentType(contentType, formContentTypes)) {
|
|
20
|
-
args = await request.clone().formData();
|
|
21
|
-
} else if (contentType && hasContentType(contentType, ["application/json"])) {
|
|
22
|
-
args = await request.clone().json();
|
|
23
|
-
} else {
|
|
24
|
-
return new Response(null, { status: 415 });
|
|
25
|
-
}
|
|
26
|
-
const { getActionResult, callAction, props, redirect, ...actionAPIContext } = context;
|
|
27
|
-
const action = baseAction.bind(actionAPIContext);
|
|
28
|
-
const result = await action(args);
|
|
7
|
+
const result = await action.handler();
|
|
29
8
|
const serialized = serializeActionResult(result);
|
|
30
9
|
if (serialized.type === "empty") {
|
|
31
10
|
return new Response(null, {
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { APIContext } from '../../types/public/context.js';
|
|
2
|
+
import type { SerializedActionResult } from './virtual/shared.js';
|
|
3
|
+
export type ActionPayload = {
|
|
4
|
+
actionResult: SerializedActionResult;
|
|
5
|
+
actionName: string;
|
|
6
|
+
};
|
|
7
|
+
export type Locals = {
|
|
8
|
+
_actionPayload: ActionPayload;
|
|
9
|
+
};
|
|
10
|
+
export declare const ACTION_API_CONTEXT_SYMBOL: unique symbol;
|
|
2
11
|
export declare const formContentTypes: string[];
|
|
3
12
|
export declare function hasContentType(contentType: string, expected: string[]): boolean;
|
|
4
13
|
export type ActionAPIContext = Omit<APIContext, 'getActionResult' | 'callAction' | 'props' | 'redirect'>;
|
|
@@ -13,3 +22,4 @@ export type MaybePromise<T> = T | Promise<T>;
|
|
|
13
22
|
* `result.error.fields` will be typed with the `name` field.
|
|
14
23
|
*/
|
|
15
24
|
export type ErrorInferenceObject = Record<string, any>;
|
|
25
|
+
export declare function isActionAPIContext(ctx: ActionAPIContext): boolean;
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
const ACTION_API_CONTEXT_SYMBOL = Symbol.for("astro.actionAPIContext");
|
|
1
2
|
const formContentTypes = ["application/x-www-form-urlencoded", "multipart/form-data"];
|
|
2
3
|
function hasContentType(contentType, expected) {
|
|
3
4
|
const type = contentType.split(";")[0].toLowerCase();
|
|
4
5
|
return expected.some((t) => type === t);
|
|
5
6
|
}
|
|
7
|
+
function isActionAPIContext(ctx) {
|
|
8
|
+
const symbol = Reflect.get(ctx, ACTION_API_CONTEXT_SYMBOL);
|
|
9
|
+
return symbol === true;
|
|
10
|
+
}
|
|
6
11
|
export {
|
|
12
|
+
ACTION_API_CONTEXT_SYMBOL,
|
|
7
13
|
formContentTypes,
|
|
8
|
-
hasContentType
|
|
14
|
+
hasContentType,
|
|
15
|
+
isActionAPIContext
|
|
9
16
|
};
|
|
@@ -2,6 +2,10 @@ export * from "./shared.js";
|
|
|
2
2
|
function defineAction() {
|
|
3
3
|
throw new Error("[astro:action] `defineAction()` unexpectedly used on the client.");
|
|
4
4
|
}
|
|
5
|
+
function getActionContext() {
|
|
6
|
+
throw new Error("[astro:action] `getActionContext()` unexpectedly used on the client.");
|
|
7
|
+
}
|
|
5
8
|
export {
|
|
6
|
-
defineAction
|
|
9
|
+
defineAction,
|
|
10
|
+
getActionContext
|
|
7
11
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActionNotFoundError } from "../../../core/errors/errors-data.js";
|
|
2
2
|
import { AstroError } from "../../../core/errors/errors.js";
|
|
3
3
|
async function getAction(path) {
|
|
4
|
-
const pathKeys = path.
|
|
4
|
+
const pathKeys = path.split(".").map((key) => decodeURIComponent(key));
|
|
5
5
|
let { server: actionLookup } = await import("astro:internal-actions");
|
|
6
6
|
if (actionLookup == null || !(typeof actionLookup === "object")) {
|
|
7
7
|
throw new TypeError(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type {
|
|
3
|
-
import { type
|
|
2
|
+
import type { APIContext } from '../../../types/public/index.js';
|
|
3
|
+
import { type ActionAPIContext, type ErrorInferenceObject, type MaybePromise } from '../utils.js';
|
|
4
|
+
import { type SafeResult, type SerializedActionResult, deserializeActionResult, serializeActionResult } from './shared.js';
|
|
4
5
|
export * from './shared.js';
|
|
5
6
|
export type ActionAccept = 'form' | 'json';
|
|
6
7
|
export type ActionHandler<TInputSchema, TOutput> = TInputSchema extends z.ZodType ? (input: z.infer<TInputSchema>, context: ActionAPIContext) => MaybePromise<TOutput> : (input: any, context: ActionAPIContext) => MaybePromise<TOutput>;
|
|
@@ -18,3 +19,32 @@ export declare function defineAction<TOutput, TAccept extends ActionAccept | und
|
|
|
18
19
|
}): ActionClient<TOutput, TAccept, TInputSchema> & string;
|
|
19
20
|
/** Transform form data to an object based on a Zod schema. */
|
|
20
21
|
export declare function formDataToObject<T extends z.AnyZodObject>(formData: FormData, schema: T): Record<string, unknown>;
|
|
22
|
+
export type ActionMiddlewareContext = {
|
|
23
|
+
/** Information about an incoming action request. */
|
|
24
|
+
action?: {
|
|
25
|
+
/** Whether an action was called using an RPC function or by using an HTML form action. */
|
|
26
|
+
calledFrom: 'rpc' | 'form';
|
|
27
|
+
/** The name of the action. Useful to track the source of an action result during a redirect. */
|
|
28
|
+
name: string;
|
|
29
|
+
/** Programatically call the action to get the result. */
|
|
30
|
+
handler: () => Promise<SafeResult<any, any>>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Manually set the action result accessed via `getActionResult()`.
|
|
34
|
+
* Calling this function from middleware will disable Astro's own action result handling.
|
|
35
|
+
*/
|
|
36
|
+
setActionResult(actionName: string, actionResult: SerializedActionResult): void;
|
|
37
|
+
/**
|
|
38
|
+
* Serialize an action result to stored in a cookie or session.
|
|
39
|
+
* Also used to pass a result to Astro templates via `setActionResult()`.
|
|
40
|
+
*/
|
|
41
|
+
serializeActionResult: typeof serializeActionResult;
|
|
42
|
+
/**
|
|
43
|
+
* Deserialize an action result to access data and error objects.
|
|
44
|
+
*/
|
|
45
|
+
deserializeActionResult: typeof deserializeActionResult;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Access information about Action requests from middleware.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getActionContext(context: APIContext): ActionMiddlewareContext;
|
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ActionCalledFromServerError } from "../../../core/errors/errors-data.js";
|
|
3
3
|
import { AstroError } from "../../../core/errors/errors.js";
|
|
4
|
-
import {
|
|
4
|
+
import { ACTION_RPC_ROUTE_PATTERN } from "../../consts.js";
|
|
5
|
+
import {
|
|
6
|
+
ACTION_API_CONTEXT_SYMBOL,
|
|
7
|
+
formContentTypes,
|
|
8
|
+
hasContentType,
|
|
9
|
+
isActionAPIContext
|
|
10
|
+
} from "../utils.js";
|
|
11
|
+
import { getAction } from "./get-action.js";
|
|
12
|
+
import {
|
|
13
|
+
ACTION_QUERY_PARAMS,
|
|
14
|
+
ActionError,
|
|
15
|
+
ActionInputError,
|
|
16
|
+
callSafely,
|
|
17
|
+
deserializeActionResult,
|
|
18
|
+
serializeActionResult
|
|
19
|
+
} from "./shared.js";
|
|
5
20
|
export * from "./shared.js";
|
|
6
21
|
function defineAction({
|
|
7
22
|
accept,
|
|
@@ -10,7 +25,7 @@ function defineAction({
|
|
|
10
25
|
}) {
|
|
11
26
|
const serverHandler = accept === "form" ? getFormServerHandler(handler, inputSchema) : getJsonServerHandler(handler, inputSchema);
|
|
12
27
|
async function safeServerHandler(unparsedInput) {
|
|
13
|
-
if (typeof this === "function") {
|
|
28
|
+
if (typeof this === "function" || !isActionAPIContext(this)) {
|
|
14
29
|
throw new AstroError(ActionCalledFromServerError);
|
|
15
30
|
}
|
|
16
31
|
return callSafely(() => serverHandler(unparsedInput, this));
|
|
@@ -119,7 +134,75 @@ function unwrapBaseObjectSchema(schema, unparsedInput) {
|
|
|
119
134
|
}
|
|
120
135
|
return schema;
|
|
121
136
|
}
|
|
137
|
+
function getActionContext(context) {
|
|
138
|
+
const callerInfo = getCallerInfo(context);
|
|
139
|
+
const actionResultAlreadySet = Boolean(context.locals._actionPayload);
|
|
140
|
+
let action = void 0;
|
|
141
|
+
if (callerInfo && context.request.method === "POST" && !actionResultAlreadySet) {
|
|
142
|
+
action = {
|
|
143
|
+
calledFrom: callerInfo.from,
|
|
144
|
+
name: callerInfo.name,
|
|
145
|
+
handler: async () => {
|
|
146
|
+
const baseAction = await getAction(callerInfo.name);
|
|
147
|
+
let input;
|
|
148
|
+
try {
|
|
149
|
+
input = await parseRequestBody(context.request);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
if (e instanceof TypeError) {
|
|
152
|
+
return { data: void 0, error: new ActionError({ code: "UNSUPPORTED_MEDIA_TYPE" }) };
|
|
153
|
+
}
|
|
154
|
+
throw e;
|
|
155
|
+
}
|
|
156
|
+
const {
|
|
157
|
+
props: _props,
|
|
158
|
+
getActionResult: _getActionResult,
|
|
159
|
+
callAction: _callAction,
|
|
160
|
+
redirect: _redirect,
|
|
161
|
+
...actionAPIContext
|
|
162
|
+
} = context;
|
|
163
|
+
Reflect.set(actionAPIContext, ACTION_API_CONTEXT_SYMBOL, true);
|
|
164
|
+
const handler = baseAction.bind(actionAPIContext);
|
|
165
|
+
return handler(input);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function setActionResult(actionName, actionResult) {
|
|
170
|
+
context.locals._actionPayload = {
|
|
171
|
+
actionResult,
|
|
172
|
+
actionName
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
action,
|
|
177
|
+
setActionResult,
|
|
178
|
+
serializeActionResult,
|
|
179
|
+
deserializeActionResult
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function getCallerInfo(ctx) {
|
|
183
|
+
if (ctx.routePattern === ACTION_RPC_ROUTE_PATTERN) {
|
|
184
|
+
return { from: "rpc", name: ctx.url.pathname.replace(/^.*\/_actions\//, "") };
|
|
185
|
+
}
|
|
186
|
+
const queryParam = ctx.url.searchParams.get(ACTION_QUERY_PARAMS.actionName);
|
|
187
|
+
if (queryParam) {
|
|
188
|
+
return { from: "form", name: queryParam };
|
|
189
|
+
}
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
192
|
+
async function parseRequestBody(request) {
|
|
193
|
+
const contentType = request.headers.get("content-type");
|
|
194
|
+
const contentLength = request.headers.get("Content-Length");
|
|
195
|
+
if (!contentType) return void 0;
|
|
196
|
+
if (hasContentType(contentType, formContentTypes)) {
|
|
197
|
+
return await request.clone().formData();
|
|
198
|
+
}
|
|
199
|
+
if (hasContentType(contentType, ["application/json"])) {
|
|
200
|
+
return contentLength === "0" ? void 0 : await request.clone().json();
|
|
201
|
+
}
|
|
202
|
+
throw new TypeError("Unsupported content type");
|
|
203
|
+
}
|
|
122
204
|
export {
|
|
123
205
|
defineAction,
|
|
124
|
-
formDataToObject
|
|
206
|
+
formDataToObject,
|
|
207
|
+
getActionContext
|
|
125
208
|
};
|
|
@@ -4,7 +4,6 @@ export type ActionAPIContext = _ActionAPIContext;
|
|
|
4
4
|
export declare const ACTION_QUERY_PARAMS: {
|
|
5
5
|
actionName: string;
|
|
6
6
|
actionPayload: string;
|
|
7
|
-
actionRedirect: string;
|
|
8
7
|
};
|
|
9
8
|
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"];
|
|
10
9
|
export type ActionErrorCode = (typeof ACTION_ERROR_CODES)[number];
|
package/dist/actions/utils.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type fsMod from 'node:fs';
|
|
2
2
|
import type { APIContext } from '../types/public/context.js';
|
|
3
|
-
import type
|
|
4
|
-
import type { ActionAPIContext } from './runtime/utils.js';
|
|
3
|
+
import { type ActionAPIContext, type Locals } from './runtime/utils.js';
|
|
5
4
|
export declare function hasActionPayload(locals: APIContext['locals']): locals is Locals;
|
|
6
5
|
export declare function createGetActionResult(locals: APIContext['locals']): APIContext['getActionResult'];
|
|
7
6
|
export declare function createCallAction(context: ActionAPIContext): APIContext['callAction'];
|