Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new fields to API route context #4986

Merged
merged 19 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from 12 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
7 changes: 7 additions & 0 deletions .changeset/fifty-ads-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'astro': minor
---

Add `site`, `generator`, `url`, `clientAddress`, `props`, and `redirect` fields to API route context.

Check out the docs for more information: https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes
5 changes: 5 additions & 0 deletions .changeset/selfish-foxes-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': minor
---

Support passing a custom status code for Astro.redirect
128 changes: 109 additions & 19 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1085,8 +1078,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;
Expand All @@ -1096,12 +1087,111 @@ 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 cookies values.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*/
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;
/**
* Parameters passed to a dynamic page generated.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*/
params: Params;
/**
* List of props passed.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*/
props: Props;
/**
* Redirect to another page (**SSR Only**)
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*/
redirect(path: string, status?: number): 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 passed to a dynamic page generated using `getStaticPaths`.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*
* 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 getStaticProps({ params }) {
bluwy marked this conversation as resolved.
Show resolved Hide resolved
* 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`.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*
* 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.
bluwy marked this conversation as resolved.
Show resolved Hide resolved
*
* 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;
Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,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';
Expand Down
2 changes: 2 additions & 0 deletions packages/astro/src/core/constants.ts
Original file line number Diff line number Diff line change
@@ -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';
2 changes: 2 additions & 0 deletions packages/astro/src/core/endpoint/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
55 changes: 52 additions & 3 deletions packages/astro/src/core/endpoint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -17,6 +20,7 @@ export type EndpointOptions = Pick<
| 'site'
| 'ssr'
| 'status'
| 'adapterName'
>;

type EndpointCallResult =
Expand All @@ -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);
},
};
}

Expand All @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/render/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function getParamsAndProps(
}

export interface RenderOptions {
adapterName: string | undefined;
adapterName?: string;
logging: LogOptions;
links: Set<SSRElement>;
styles?: Set<SSRElement>;
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/core/render/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,9 @@ export function createResult(args: CreateResultArgs): SSRResult {
request,
url,
redirect: args.ssr
? (path: string) => {
? (path: string, status?: number) => {
bluwy marked this conversation as resolved.
Show resolved Hide resolved
return new Response(null, {
status: 302,
status: status || 302,
headers: {
Location: path,
},
Expand Down
3 changes: 0 additions & 3 deletions packages/astro/src/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/events/index.ts
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
4 changes: 1 addition & 3 deletions packages/astro/src/runtime/server/astro-global.ts
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
8 changes: 8 additions & 0 deletions packages/astro/test/fixtures/ssr-api-route/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -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(),
});
1 change: 1 addition & 0 deletions packages/astro/test/fixtures/ssr-api-route/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/node": "^1.1.0",
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -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,
})
};
}
Loading