summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/fifty-ads-march.md19
-rw-r--r--.changeset/selfish-foxes-bake.md14
-rw-r--r--packages/astro/src/@types/astro.ts130
-rw-r--r--packages/astro/src/cli/index.ts3
-rw-r--r--packages/astro/src/core/constants.ts2
-rw-r--r--packages/astro/src/core/endpoint/dev/index.ts2
-rw-r--r--packages/astro/src/core/endpoint/index.ts55
-rw-r--r--packages/astro/src/core/render/core.ts2
-rw-r--r--packages/astro/src/core/render/result.ts13
-rw-r--r--packages/astro/src/core/util.ts3
-rw-r--r--packages/astro/src/events/index.ts2
-rw-r--r--packages/astro/src/runtime/server/astro-global.ts4
-rw-r--r--packages/astro/test/fixtures/ssr-api-route/astro.config.mjs8
-rw-r--r--packages/astro/test/fixtures/ssr-api-route/package.json1
-rw-r--r--packages/astro/test/fixtures/ssr-api-route/src/pages/context/[param].js18
-rw-r--r--packages/astro/test/ssr-api-route.test.js16
-rw-r--r--pnpm-lock.yaml2
17 files changed, 257 insertions, 37 deletions
diff --git a/.changeset/fifty-ads-march.md b/.changeset/fifty-ads-march.md
new file mode 100644
index 000000000..d06e8fc36
--- /dev/null
+++ b/.changeset/fifty-ads-march.md
@@ -0,0 +1,19 @@
+---
+'astro': minor
+---
+
+## New properties for API routes
+
+In API routes, you can now get the `site`, `generator`, `url`, `clientAddress`, `props`, and `redirect` fields on the APIContext, which is the first parameter passed to an API route. This was done to make the APIContext more closely align with the `Astro` global in .astro pages.
+
+For example, here's how you might use the `clientAddress`, which is the user's IP address, to selectively allow users.
+
+```js
+export function post({ clientAddress, request, redirect }) {
+ if(!allowList.has(clientAddress)) {
+ return redirect('/not-allowed');
+ }
+}
+```
+
+Check out the docs for more information on the newly available fields: https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes
diff --git a/.changeset/selfish-foxes-bake.md b/.changeset/selfish-foxes-bake.md
new file mode 100644
index 000000000..9e253388a
--- /dev/null
+++ b/.changeset/selfish-foxes-bake.md
@@ -0,0 +1,14 @@
+---
+'astro': minor
+---
+
+## Support passing a custom status code for Astro.redirect
+
+New in this minor is the ability to pass a status code to `Astro.redirect`. By default it uses `302` but now you can pass another code as the second argument:
+
+```astro
+---
+// This page was moved
+return Astro.redirect('/posts/new-post-name', 301);
+---
+```
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index b95aae047..16b5b5226 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -94,7 +94,8 @@ export interface BuildConfig {
* [Astro reference](https://docs.astro.build/reference/api-reference/#astro-global)
*/
export interface AstroGlobal<Props extends Record<string, any> = Record<string, any>>
- extends AstroGlobalPartial {
+ extends AstroGlobalPartial,
+ AstroSharedContext<Props> {
/**
* Canonical URL of the current page.
* @deprecated Use `Astro.url` instead.
@@ -107,21 +108,13 @@ export interface AstroGlobal<Props extends Record<string, any> = Record<string,
* ```
*/
canonicalURL: URL;
- /** The address (usually IP address) of the user. Used with SSR only.
- *
- */
- clientAddress: string;
/**
* A full URL object of the request URL.
* Equivalent to: `new URL(Astro.request.url)`
*
* [Astro reference](https://docs.astro.build/en/reference/api-reference/#url)
*/
- /**
- * Utility for getting and setting cookies values.
- */
- cookies: AstroCookies;
- url: URL;
+ url: AstroSharedContext['url'];
/** Parameters passed to a dynamic page generated using [getStaticPaths](https://docs.astro.build/en/reference/api-reference/#getstaticpaths)
*
* Example usage:
@@ -138,9 +131,9 @@ export interface AstroGlobal<Props extends Record<string, any> = Record<string,
* <h1>{id}</h1>
* ```
*
- * [Astro reference](https://docs.astro.build/en/reference/api-reference/#params)
+ * [Astro reference](https://docs.astro.build/en/reference/api-reference/#astroparams)
*/
- params: Params;
+ params: AstroSharedContext['params'];
/** List of props passed to this component
*
* A common way to get specific props is through [destructuring](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment), ex:
@@ -150,7 +143,7 @@ export interface AstroGlobal<Props extends Record<string, any> = Record<string,
*
* [Astro reference](https://docs.astro.build/en/core-concepts/astro-components/#component-props)
*/
- props: Props;
+ props: AstroSharedContext<Props>['props'];
/** Information about the current request. This is a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
*
* For example, to get a URL object of the current URL, you can use:
@@ -184,11 +177,11 @@ export interface AstroGlobal<Props extends Record<string, any> = Record<string,
*
* [Astro reference](https://docs.astro.build/en/guides/server-side-rendering/#astroredirect)
*/
- redirect(path: string): Response;
+ redirect: AstroSharedContext['redirect'];
/**
* The <Astro.self /> element allows a component to reference itself recursively.
*
- * [Astro reference](https://docs.astro.build/en/guides/server-side-rendering/#astroself)
+ * [Astro reference](https://docs.astro.build/en/guides/api-reference/#astroself)
*/
self: AstroComponentFactory;
/** Utility functions for modifying an Astro component’s slotted children
@@ -1077,8 +1070,6 @@ export type PaginateFunction = (data: any[], args?: PaginateOptions) => GetStati
export type Params = Record<string, string | number | undefined>;
-export type Props = Record<string, unknown>;
-
export interface AstroAdapter {
name: string;
serverEntrypoint?: string;
@@ -1088,12 +1079,113 @@ export interface AstroAdapter {
type Body = string;
-export interface APIContext {
+// Shared types between `Astro` global and API context object
+interface AstroSharedContext<Props extends Record<string, any> = Record<string, any>> {
+ /**
+ * The address (usually IP address) of the user. Used with SSR only.
+ */
+ clientAddress: string;
+ /**
+ * Utility for getting and setting the values of cookies.
+ */
cookies: AstroCookies;
- params: Params;
+ /**
+ * Information about the current request. This is a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object
+ */
request: Request;
+ /**
+ * A full URL object of the request URL.
+ */
+ url: URL;
+ /**
+ * Route parameters for this request if this is a dynamic route.
+ */
+ params: Params;
+ /**
+ * List of props returned for this path by `getStaticPaths` (**Static Only**).
+ */
+ props: Props;
+ /**
+ * Redirect to another page (**SSR Only**).
+ */
+ redirect(path: string, status?: 301 | 302 | 308): Response;
+}
+
+export interface APIContext<Props extends Record<string, any> = Record<string, any>>
+ extends AstroSharedContext<Props> {
+ site: URL | undefined;
+ generator: string;
+ /**
+ * A full URL object of the request URL.
+ * Equivalent to: `new URL(request.url)`
+ */
+ url: AstroSharedContext['url'];
+ /**
+ * Parameters matching the page’s dynamic route pattern.
+ * In static builds, this will be the `params` generated by `getStaticPaths`.
+ * In SSR builds, this can be any path segments matching the dynamic route pattern.
+ *
+ * Example usage:
+ * ```ts
+ * export function getStaticPaths() {
+ * return [
+ * { params: { id: '0' }, props: { name: 'Sarah' } },
+ * { params: { id: '1' }, props: { name: 'Chris' } },
+ * { params: { id: '2' }, props: { name: 'Fuzzy' } },
+ * ];
+ * }
+ *
+ * export async function get({ params }) {
+ * return {
+ * body: `Hello user ${params.id}!`,
+ * }
+ * }
+ * ```
+ *
+ * [context reference](https://docs.astro.build/en/guides/api-reference/#contextparams)
+ */
+ params: AstroSharedContext['params'];
+ /**
+ * List of props passed from `getStaticPaths`. Only available to static builds.
+ *
+ * Example usage:
+ * ```ts
+ * export function getStaticPaths() {
+ * return [
+ * { params: { id: '0' }, props: { name: 'Sarah' } },
+ * { params: { id: '1' }, props: { name: 'Chris' } },
+ * { params: { id: '2' }, props: { name: 'Fuzzy' } },
+ * ];
+ * }
+ *
+ * export function get({ props }) {
+ * return {
+ * body: `Hello ${props.name}!`,
+ * }
+ * }
+ * ```
+ *
+ * [context reference](https://docs.astro.build/en/guides/api-reference/#contextprops)
+ */
+ props: AstroSharedContext<Props>['props'];
+ /**
+ * Redirect to another page. Only available in SSR builds.
+ *
+ * Example usage:
+ * ```ts
+ * // src/pages/secret.ts
+ * export function get({ redirect }) {
+ * return redirect('/login');
+ * }
+ * ```
+ *
+ * [context reference](https://docs.astro.build/en/guides/api-reference/#contextredirect)
+ */
+ redirect: AstroSharedContext['redirect'];
}
+export type Props = Record<string, unknown>;
+
export interface EndpointOutput {
body: Body;
encoding?: BufferEncoding;
diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts
index ab52f6415..18910e154 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -21,7 +21,8 @@ import { enableVerboseLogging, nodeLogDestination } from '../core/logger/node.js
import { formatConfigErrorMessage, formatErrorMessage, printHelp } from '../core/messages.js';
import { appendForwardSlash } from '../core/path.js';
import preview from '../core/preview/index.js';
-import { ASTRO_VERSION, createSafeError } from '../core/util.js';
+import { ASTRO_VERSION } from '../core/constants.js';
+import { createSafeError } from '../core/util.js';
import * as event from '../events/index.js';
import { eventConfigError, eventError, telemetry } from '../events/index.js';
import { check } from './check/index.js';
diff --git a/packages/astro/src/core/constants.ts b/packages/astro/src/core/constants.ts
new file mode 100644
index 000000000..15c5d34e4
--- /dev/null
+++ b/packages/astro/src/core/constants.ts
@@ -0,0 +1,2 @@
+// process.env.PACKAGE_VERSION is injected when we build and publish the astro package.
+export const ASTRO_VERSION = process.env.PACKAGE_VERSION ?? 'development';
diff --git a/packages/astro/src/core/endpoint/dev/index.ts b/packages/astro/src/core/endpoint/dev/index.ts
index b2f16225b..b27127119 100644
--- a/packages/astro/src/core/endpoint/dev/index.ts
+++ b/packages/astro/src/core/endpoint/dev/index.ts
@@ -8,5 +8,7 @@ export async function call(ssrOpts: SSROptions) {
return await callEndpoint(mod as unknown as EndpointHandler, {
...ssrOpts,
ssr: ssrOpts.settings.config.output === 'server',
+ site: ssrOpts.settings.config.site,
+ adapterName: ssrOpts.settings.config.adapter?.name,
});
}
diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts
index 9f1adb655..3df01af25 100644
--- a/packages/astro/src/core/endpoint/index.ts
+++ b/packages/astro/src/core/endpoint/index.ts
@@ -4,6 +4,9 @@ import type { RenderOptions } from '../render/core';
import { renderEndpoint } from '../../runtime/server/index.js';
import { AstroCookies, attachToResponse } from '../cookies/index.js';
import { getParamsAndProps, GetParamsAndPropsError } from '../render/core.js';
+import { ASTRO_VERSION } from '../constants.js';
+
+const clientAddressSymbol = Symbol.for('astro.clientAddress');
export type EndpointOptions = Pick<
RenderOptions,
@@ -17,6 +20,7 @@ export type EndpointOptions = Pick<
| 'site'
| 'ssr'
| 'status'
+ | 'adapterName'
>;
type EndpointCallResult =
@@ -30,11 +34,50 @@ type EndpointCallResult =
response: Response;
};
-function createAPIContext(request: Request, params: Params): APIContext {
+function createAPIContext({
+ request,
+ params,
+ site,
+ props,
+ adapterName,
+}: {
+ request: Request;
+ params: Params;
+ site?: string;
+ props: Record<string, any>;
+ adapterName?: string;
+}): APIContext {
return {
cookies: new AstroCookies(request),
request,
params,
+ site: site ? new URL(site) : undefined,
+ generator: `Astro v${ASTRO_VERSION}`,
+ props,
+ redirect(path, status) {
+ return new Response(null, {
+ status: status || 302,
+ headers: {
+ Location: path,
+ },
+ });
+ },
+ url: new URL(request.url),
+ get clientAddress() {
+ if (!(clientAddressSymbol in request)) {
+ if (adapterName) {
+ throw new Error(
+ `clientAddress is not available in the ${adapterName} adapter. File an issue with the adapter to add support.`
+ );
+ } else {
+ throw new Error(
+ `clientAddress is not available in your environment. Ensure that you are using an SSR adapter that supports this feature.`
+ );
+ }
+ }
+
+ return Reflect.get(request, clientAddressSymbol);
+ },
};
}
@@ -49,9 +92,15 @@ export async function call(
`[getStaticPath] route pattern matched, but no matching static path found. (${opts.pathname})`
);
}
- const [params] = paramsAndPropsResp;
+ const [params, props] = paramsAndPropsResp;
- const context = createAPIContext(opts.request, params);
+ const context = createAPIContext({
+ request: opts.request,
+ params,
+ props,
+ site: opts.site,
+ adapterName: opts.adapterName,
+ });
const response = await renderEndpoint(mod, context, opts.ssr);
if (response instanceof Response) {
diff --git a/packages/astro/src/core/render/core.ts b/packages/astro/src/core/render/core.ts
index bbbb6c53f..5b7a3122a 100644
--- a/packages/astro/src/core/render/core.ts
+++ b/packages/astro/src/core/render/core.ts
@@ -68,7 +68,7 @@ export async function getParamsAndProps(
}
export interface RenderOptions {
- adapterName: string | undefined;
+ adapterName?: string;
logging: LogOptions;
links: Set<SSRElement>;
styles?: Set<SSRElement>;
diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts
index f0cd02c54..a87ea54c7 100644
--- a/packages/astro/src/core/render/result.ts
+++ b/packages/astro/src/core/render/result.ts
@@ -166,7 +166,8 @@ export function createResult(args: CreateResultArgs): SSRResult {
) {
const astroSlots = new Slots(result, slots, args.logging);
- const Astro = {
+ const Astro: AstroGlobal = {
+ // @ts-expect-error set prototype
__proto__: astroGlobal,
get clientAddress() {
if (!(clientAddressSymbol in request)) {
@@ -196,9 +197,9 @@ export function createResult(args: CreateResultArgs): SSRResult {
request,
url,
redirect: args.ssr
- ? (path: string) => {
+ ? (path, status) => {
return new Response(null, {
- status: 302,
+ status: status || 302,
headers: {
Location: path,
},
@@ -237,9 +238,9 @@ ${extra}`
// Intentionally return an empty string so that it is not relied upon.
return '';
},
- response,
- slots: astroSlots,
- } as unknown as AstroGlobal;
+ response: response as AstroGlobal['response'],
+ slots: astroSlots as unknown as AstroGlobal['slots'],
+ };
Object.defineProperty(Astro, 'canonicalURL', {
get: function () {
diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts
index ed049ff41..4c1d387cb 100644
--- a/packages/astro/src/core/util.ts
+++ b/packages/astro/src/core/util.ts
@@ -8,9 +8,6 @@ import type { ErrorPayload, ViteDevServer } from 'vite';
import type { AstroConfig, AstroSettings, RouteType } from '../@types/astro';
import { prependForwardSlash, removeTrailingForwardSlash } from './path.js';
-// process.env.PACKAGE_VERSION is injected when we build and publish the astro package.
-export const ASTRO_VERSION = process.env.PACKAGE_VERSION ?? 'development';
-
/** Returns true if argument is an object of any prototype/class (but not null). */
export function isObject(value: unknown): value is Record<string, any> {
return typeof value === 'object' && value != null;
diff --git a/packages/astro/src/events/index.ts b/packages/astro/src/events/index.ts
index 784e7723f..31e549ad7 100644
--- a/packages/astro/src/events/index.ts
+++ b/packages/astro/src/events/index.ts
@@ -1,6 +1,6 @@
import { AstroTelemetry } from '@astrojs/telemetry';
import { createRequire } from 'module';
-import { ASTRO_VERSION } from '../core/util.js';
+import { ASTRO_VERSION } from '../core/constants.js';
const require = createRequire(import.meta.url);
function getViteVersion() {
diff --git a/packages/astro/src/runtime/server/astro-global.ts b/packages/astro/src/runtime/server/astro-global.ts
index 5ffca377a..101ec53ac 100644
--- a/packages/astro/src/runtime/server/astro-global.ts
+++ b/packages/astro/src/runtime/server/astro-global.ts
@@ -1,7 +1,5 @@
import type { AstroGlobalPartial } from '../../@types/astro';
-
-// process.env.PACKAGE_VERSION is injected when we build and publish the astro package.
-const ASTRO_VERSION = process.env.PACKAGE_VERSION ?? 'development';
+import { ASTRO_VERSION } from '../../core/constants.js';
/** Create the Astro.fetchContent() runtime function. */
function createDeprecatedFetchContentFn() {
diff --git a/packages/astro/test/fixtures/ssr-api-route/astro.config.mjs b/packages/astro/test/fixtures/ssr-api-route/astro.config.mjs
new file mode 100644
index 000000000..ef9763e01
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-api-route/astro.config.mjs
@@ -0,0 +1,8 @@
+import { defineConfig } from 'astro/config';
+import node from '@astrojs/node';
+
+// https://astro.build/config
+export default defineConfig({
+ output: 'server',
+ adapter: node(),
+});
diff --git a/packages/astro/test/fixtures/ssr-api-route/package.json b/packages/astro/test/fixtures/ssr-api-route/package.json
index f31c4aeee..942034f45 100644
--- a/packages/astro/test/fixtures/ssr-api-route/package.json
+++ b/packages/astro/test/fixtures/ssr-api-route/package.json
@@ -3,6 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
+ "@astrojs/node": "^1.1.0",
"astro": "workspace:*"
}
}
diff --git a/packages/astro/test/fixtures/ssr-api-route/src/pages/context/[param].js b/packages/astro/test/fixtures/ssr-api-route/src/pages/context/[param].js
new file mode 100644
index 000000000..0ff1f625a
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-api-route/src/pages/context/[param].js
@@ -0,0 +1,18 @@
+/**
+ * @param {import('astro').APIContext} api
+ */
+export function get(ctx) {
+ return {
+ body: JSON.stringify({
+ cookiesExist: !!ctx.cookies,
+ requestExist: !!ctx.request,
+ redirectExist: !!ctx.redirect,
+ propsExist: !!ctx.props,
+ params: ctx.params,
+ site: ctx.site?.toString(),
+ generator: ctx.generator,
+ url: ctx.url.toString(),
+ clientAddress: ctx.clientAddress,
+ })
+ };
+}
diff --git a/packages/astro/test/ssr-api-route.test.js b/packages/astro/test/ssr-api-route.test.js
index 1409657c3..33b1ffdab 100644
--- a/packages/astro/test/ssr-api-route.test.js
+++ b/packages/astro/test/ssr-api-route.test.js
@@ -35,6 +35,22 @@ describe('API routes in SSR', () => {
expect(body.length).to.equal(3);
});
+ it('Has valid api context', async () => {
+ const app = await fixture.loadTestAdapterApp();
+ const request = new Request('http://example.com/context/any');
+ const response = await app.render(request);
+ expect(response.status).to.equal(200);
+ const data = await response.json();
+ expect(data.cookiesExist).to.equal(true);
+ expect(data.requestExist).to.equal(true);
+ expect(data.redirectExist).to.equal(true);
+ expect(data.propsExist).to.equal(true);
+ expect(data.params).to.deep.equal({ param: 'any' });
+ expect(data.generator).to.match(/^Astro v/);
+ expect(data.url).to.equal('http://example.com/context/any');
+ expect(data.clientAddress).to.equal('0.0.0.0');
+ });
+
describe('API Routes - Dev', () => {
let devServer;
before(async () => {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b9258f1bb..ae9834b3e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2161,8 +2161,10 @@ importers:
packages/astro/test/fixtures/ssr-api-route:
specifiers:
+ '@astrojs/node': ^1.1.0
astro: workspace:*
dependencies:
+ '@astrojs/node': link:../../../../integrations/node
astro: link:../../..
packages/astro/test/fixtures/ssr-api-route-custom-404: