0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

Remove support for simple objects in endpoints (#9181)

* Deprecate simple object from endpoints

* Update changeset

* Add missing Response return

Co-authored-by: Happydev <81974850+MoustaphaDev@users.noreply.github.com>

* Update .changeset/clever-beds-notice.md

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

---------

Co-authored-by: Happydev <81974850+MoustaphaDev@users.noreply.github.com>
Co-authored-by: Matthew Phillips <matthew@skypack.dev>
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Bjorn Lu 2023-11-28 01:51:57 +08:00 committed by GitHub
parent 37697a2c55
commit cdabf6ef02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 206 additions and 529 deletions

View file

@ -0,0 +1,9 @@
---
'astro': major
---
Removes support for returning simple objects from endpoints (deprecated since Astro 3.0). You should return a `Response` instead.
`ResponseWithEncoding` is also removed. You can refactor the code to return a response with an array buffer instead, which is encoding agnostic.
The types for middlewares have also been revised. To type a middleware function, you should now use `MiddlewareHandler` instead of `MiddlewareResponseHandler`. If you used `defineMiddleware()` to type the function, no changes are needed.

View file

@ -19,7 +19,6 @@ import type { AstroConfigType } from '../core/config/index.js';
import type { AstroTimer } from '../core/config/timer.js';
import type { TSConfig } from '../core/config/tsconfig.js';
import type { AstroCookies } from '../core/cookies/index.js';
import type { ResponseWithEncoding } from '../core/endpoint/index.js';
import type { AstroIntegrationLogger, Logger, LoggerLevel } from '../core/logger/core.js';
import type { AstroDevOverlay, DevOverlayCanvas } from '../runtime/client/dev-overlay/overlay.js';
import type { DevOverlayHighlight } from '../runtime/client/dev-overlay/ui-library/highlight.js';
@ -2005,8 +2004,6 @@ export interface AstroAdapter {
supportedAstroFeatures: AstroFeatureMap;
}
type Body = string;
export type ValidRedirectStatus = 300 | 301 | 302 | 303 | 304 | 307 | 308;
// Shared types between `Astro` global and API context object
@ -2163,7 +2160,6 @@ export interface APIContext<
* ```
*/
locals: App.Locals;
ResponseWithEncoding: typeof ResponseWithEncoding;
/**
* Available only when `experimental.i18n` enabled and in SSR.
@ -2199,22 +2195,12 @@ export interface APIContext<
currentLocale: string | undefined;
}
export type EndpointOutput =
| {
body: Body;
encoding?: BufferEncoding;
}
| {
body: Uint8Array;
encoding: 'binary';
};
export type APIRoute<Props extends Record<string, any> = Record<string, any>> = (
context: APIContext<Props>
) => EndpointOutput | Response | Promise<EndpointOutput | Response>;
) => Response | Promise<Response>;
export interface EndpointHandler {
[method: string]: APIRoute | ((params: Params, request: Request) => EndpointOutput | Response);
[method: string]: APIRoute | ((params: Params, request: Request) => Response);
}
export type Props = Record<string, unknown>;
@ -2319,20 +2305,16 @@ export interface AstroIntegration {
};
}
export type MiddlewareNext<R> = () => Promise<R>;
export type MiddlewareHandler<R> = (
export type MiddlewareNext = () => Promise<Response>;
export type MiddlewareHandler = (
context: APIContext,
next: MiddlewareNext<R>
) => Promise<R> | R | Promise<void> | void;
export type MiddlewareResponseHandler = MiddlewareHandler<Response>;
export type MiddlewareEndpointHandler = MiddlewareHandler<Response | EndpointOutput>;
export type MiddlewareNextResponse = MiddlewareNext<Response>;
next: MiddlewareNext
) => Promise<Response> | Response | Promise<void> | void;
// NOTE: when updating this file with other functions,
// remember to update `plugin-page.ts` too, to add that function as a no-op function.
export type AstroMiddlewareInstance<R> = {
onRequest?: MiddlewareHandler<R>;
export type AstroMiddlewareInstance = {
onRequest?: MiddlewareHandler;
};
export type AstroIntegrationMiddleware = {

View file

@ -1,7 +1,6 @@
import type {
EndpointHandler,
ManifestData,
MiddlewareEndpointHandler,
RouteData,
SSRElement,
SSRManifest,
@ -181,16 +180,14 @@ export class App {
);
if (i18nMiddleware) {
if (mod.onRequest) {
this.#pipeline.setMiddlewareFunction(
sequence(i18nMiddleware, mod.onRequest as MiddlewareEndpointHandler)
);
this.#pipeline.setMiddlewareFunction(sequence(i18nMiddleware, mod.onRequest));
} else {
this.#pipeline.setMiddlewareFunction(i18nMiddleware);
}
this.#pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else {
if (mod.onRequest) {
this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
this.#pipeline.setMiddlewareFunction(mod.onRequest);
}
}
response = await this.#pipeline.renderRoute(renderContext, pageModule);
@ -322,7 +319,7 @@ export class App {
);
const page = (await mod.page()) as any;
if (skipMiddleware === false && mod.onRequest) {
this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
this.#pipeline.setMiddlewareFunction(mod.onRequest);
}
if (skipMiddleware) {
// make sure middleware set by other requests is cleared out
@ -367,8 +364,8 @@ export class App {
const status = override?.status
? override.status
: oldResponse.status === 200
? newResponse.status
: oldResponse.status;
? newResponse.status
: oldResponse.status;
return new Response(newResponse.body, {
status,

View file

@ -8,7 +8,6 @@ import type {
AstroSettings,
ComponentInstance,
GetStaticPathsItem,
MiddlewareEndpointHandler,
RouteData,
RouteType,
SSRError,
@ -269,15 +268,13 @@ async function generatePage(
);
if (config.experimental.i18n && i18nMiddleware) {
if (onRequest) {
pipeline.setMiddlewareFunction(
sequence(i18nMiddleware, onRequest as MiddlewareEndpointHandler)
);
pipeline.setMiddlewareFunction(sequence(i18nMiddleware, onRequest));
} else {
pipeline.setMiddlewareFunction(i18nMiddleware);
}
pipeline.onBeforeRenderRoute(i18nPipelineHook);
} else if (onRequest) {
pipeline.setMiddlewareFunction(onRequest as MiddlewareEndpointHandler);
pipeline.setMiddlewareFunction(onRequest);
}
if (!pageModulePromise) {
throw new Error(
@ -560,7 +557,6 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
});
let body: string | Uint8Array;
let encoding: BufferEncoding | undefined;
let response: Response;
try {
@ -603,7 +599,6 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
// If there's no body, do nothing
if (!response.body) return;
body = Buffer.from(await response.arrayBuffer());
encoding = (response.headers.get('X-Astro-Encoding') as BufferEncoding | null) ?? 'utf-8';
}
const outFolder = getOutFolder(pipeline.getConfig(), pathname, route.type);
@ -611,7 +606,7 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
route.distURL = outFile;
await fs.promises.mkdir(outFolder, { recursive: true });
await fs.promises.writeFile(outFile, body, encoding);
await fs.promises.writeFile(outFile, body);
}
}

View file

@ -54,7 +54,7 @@ export interface SinglePageBuiltModule {
/**
* The `onRequest` hook exported by the middleware
*/
onRequest?: MiddlewareHandler<unknown>;
onRequest?: MiddlewareHandler;
renderers: SSRLoadedRenderer[];
}

View file

@ -1,12 +1,4 @@
import mime from 'mime';
import type {
APIContext,
EndpointHandler,
EndpointOutput,
MiddlewareEndpointHandler,
MiddlewareHandler,
Params,
} from '../../@types/astro.js';
import type { APIContext, EndpointHandler, MiddlewareHandler, Params } from '../../@types/astro.js';
import { renderEndpoint } from '../../runtime/server/index.js';
import { ASTRO_VERSION } from '../constants.js';
import { AstroCookies, attachCookiesToResponse } from '../cookies/index.js';
@ -19,8 +11,6 @@ import {
} from '../render/context.js';
import { type Environment, type RenderContext } from '../render/index.js';
const encoder = new TextEncoder();
const clientAddressSymbol = Symbol.for('astro.clientAddress');
const clientLocalsSymbol = Symbol.for('astro.locals');
@ -69,7 +59,6 @@ export function createAPIContext({
},
});
},
ResponseWithEncoding,
get preferredLocale(): string | undefined {
if (preferredLocale) {
return preferredLocale;
@ -143,36 +132,11 @@ export function createAPIContext({
return context;
}
type ResponseParameters = ConstructorParameters<typeof Response>;
export class ResponseWithEncoding extends Response {
constructor(body: ResponseParameters[0], init: ResponseParameters[1], encoding?: BufferEncoding) {
// If a body string is given, try to encode it to preserve the behaviour as simple objects.
// We don't do the full handling as simple objects so users can control how headers are set instead.
if (typeof body === 'string') {
// In NodeJS, we can use Buffer.from which supports all BufferEncoding
if (typeof Buffer !== 'undefined' && Buffer.from) {
body = Buffer.from(body, encoding);
}
// In non-NodeJS, use the web-standard TextEncoder for utf-8 strings
else if (encoding == null || encoding === 'utf8' || encoding === 'utf-8') {
body = encoder.encode(body);
}
}
super(body, init);
if (encoding) {
this.headers.set('X-Astro-Encoding', encoding);
}
}
}
export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>(
export async function callEndpoint(
mod: EndpointHandler,
env: Environment,
ctx: RenderContext,
onRequest: MiddlewareHandler<MiddlewareResult> | undefined
onRequest: MiddlewareHandler | undefined
): Promise<Response> {
const context = createAPIContext({
request: ctx.request,
@ -187,107 +151,13 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
let response;
if (onRequest) {
response = await callMiddleware<Response | EndpointOutput>(
env.logger,
onRequest as MiddlewareEndpointHandler,
context,
async () => {
return await renderEndpoint(mod, context, env.ssr, env.logger);
}
);
response = await callMiddleware(onRequest, context, async () => {
return await renderEndpoint(mod, context, env.ssr, env.logger);
});
} else {
response = await renderEndpoint(mod, context, env.ssr, env.logger);
}
const isEndpointSSR = env.ssr && !ctx.route?.prerender;
if (response instanceof Response) {
if (isEndpointSSR && response.headers.get('X-Astro-Encoding')) {
env.logger.warn(
null,
'`ResponseWithEncoding` is ignored in SSR. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
);
}
attachCookiesToResponse(response, context.cookies);
return response;
}
// The endpoint returned a simple object, convert it to a Response
// TODO: Remove in Astro 4.0
env.logger.warn(
null,
`${ctx.route.component} returns a simple object which is deprecated. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.`
);
if (isEndpointSSR) {
if (response.hasOwnProperty('headers')) {
env.logger.warn(
null,
'Setting headers is not supported when returning an object. Please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
);
}
if (response.encoding) {
env.logger.warn(
null,
'`encoding` is ignored in SSR. To return a charset other than UTF-8, please return an instance of Response. See https://docs.astro.build/en/core-concepts/endpoints/#server-endpoints-api-routes for more information.'
);
}
}
let body: BodyInit;
const headers = new Headers();
// Try to get the MIME type for this route
const pathname = ctx.route
? // Try the static route `pathname`
ctx.route.pathname ??
// Dynamic routes don't include `pathname`, so synthesize a path for these (e.g. 'src/pages/[slug].svg')
ctx.route.segments.map((s) => s.map((p) => p.content).join('')).join('/')
: // Fallback to pathname of the request
ctx.pathname;
const mimeType = mime.getType(pathname) || 'text/plain';
headers.set('Content-Type', `${mimeType};charset=utf-8`);
// Save encoding to X-Astro-Encoding to be used later during SSG with `fs.writeFile`.
// It won't work in SSR and is already warned above.
if (response.encoding) {
headers.set('X-Astro-Encoding', response.encoding);
}
// For Uint8Array (binary), it can passed to Response directly
if (response.body instanceof Uint8Array) {
body = response.body;
headers.set('Content-Length', body.byteLength.toString());
}
// In NodeJS, we can use Buffer.from which supports all BufferEncoding
else if (typeof Buffer !== 'undefined' && Buffer.from) {
body = Buffer.from(response.body, response.encoding);
headers.set('Content-Length', body.byteLength.toString());
}
// In non-NodeJS, use the web-standard TextEncoder for utf-8 strings only
// to calculate the content length
else if (
response.encoding == null ||
response.encoding === 'utf8' ||
response.encoding === 'utf-8'
) {
body = encoder.encode(response.body);
headers.set('Content-Length', body.byteLength.toString());
}
// Fallback pass it to Response directly. It will mainly rely on X-Astro-Encoding
// to be further processed in SSG.
else {
body = response.body;
// NOTE: Can't calculate the content length as we can't encode to figure out the real length.
// But also because we don't need the length for SSG as it's only being written to disk.
}
response = new Response(body, {
status: 200,
headers,
});
attachCookiesToResponse(response, context.cookies);
return response;
}

View file

@ -1,13 +1,6 @@
import { bold } from 'kleur/colors';
import type {
APIContext,
EndpointOutput,
MiddlewareHandler,
MiddlewareNext,
} from '../../@types/astro.js';
import type { APIContext, MiddlewareHandler, MiddlewareNext } from '../../@types/astro.js';
import { attachCookiesToResponse, responseHasCookies } from '../cookies/index.js';
import { AstroError, AstroErrorData } from '../errors/index.js';
import type { Environment } from '../render/index.js';
/**
* Utility function that is in charge of calling the middleware.
@ -43,15 +36,14 @@ import type { Environment } from '../render/index.js';
* @param apiContext The API context
* @param responseFunction A callback function that should return a promise with the response
*/
export async function callMiddleware<R>(
logger: Environment['logger'],
onRequest: MiddlewareHandler<R>,
export async function callMiddleware(
onRequest: MiddlewareHandler,
apiContext: APIContext,
responseFunction: () => Promise<R>
): Promise<Response | R> {
responseFunction: () => Promise<Response>
): Promise<Response> {
let nextCalled = false;
let responseFunctionPromise: Promise<R> | undefined = undefined;
const next: MiddlewareNext<R> = async () => {
let responseFunctionPromise: Promise<Response> | undefined = undefined;
const next: MiddlewareNext = async () => {
nextCalled = true;
responseFunctionPromise = responseFunction();
return responseFunctionPromise;
@ -60,15 +52,6 @@ export async function callMiddleware<R>(
let middlewarePromise = onRequest(apiContext, next);
return await Promise.resolve(middlewarePromise).then(async (value) => {
if (isEndpointOutput(value)) {
logger.warn(
null,
apiContext.url.pathname +
' Using simple endpoints can cause unexpected issues in the chain of middleware functions.' +
`\nIt's strongly suggested to use full ${bold('Response')} objects.`
);
}
// first we check if `next` was called
if (nextCalled) {
/**
@ -84,7 +67,7 @@ export async function callMiddleware<R>(
if (value instanceof Response === false) {
throw new AstroError(AstroErrorData.MiddlewareNotAResponse);
}
return ensureCookiesAttached(apiContext, value as Response);
return ensureCookiesAttached(apiContext, value);
} else {
/**
* Here we handle the case where `next` was called and returned nothing.
@ -107,7 +90,7 @@ export async function callMiddleware<R>(
throw new AstroError(AstroErrorData.MiddlewareNotAResponse);
} else {
// Middleware did not call resolve and returned a value
return ensureCookiesAttached(apiContext, value as Response);
return ensureCookiesAttached(apiContext, value);
}
});
}
@ -118,11 +101,3 @@ function ensureCookiesAttached(apiContext: APIContext, response: Response): Resp
}
return response;
}
function isEndpointOutput(endpointResult: any): endpointResult is EndpointOutput {
return (
!(endpointResult instanceof Response) &&
typeof endpointResult === 'object' &&
typeof endpointResult.body === 'string'
);
}

View file

@ -1,8 +1,8 @@
import type { MiddlewareEndpointHandler, Params } from '../../@types/astro.js';
import type { MiddlewareHandler, Params } from '../../@types/astro.js';
import { createAPIContext } from '../endpoint/index.js';
import { sequence } from './sequence.js';
function defineMiddleware(fn: MiddlewareEndpointHandler) {
function defineMiddleware(fn: MiddlewareHandler) {
return fn;
}

View file

@ -1,4 +1,4 @@
import type { APIContext, MiddlewareEndpointHandler } from '../../@types/astro.js';
import type { APIContext, MiddlewareHandler } from '../../@types/astro.js';
import { defineMiddleware } from './index.js';
// From SvelteKit: https://github.com/sveltejs/kit/blob/master/packages/kit/src/exports/hooks/sequence.js
@ -6,11 +6,11 @@ import { defineMiddleware } from './index.js';
*
* It accepts one or more middleware handlers and makes sure that they are run in sequence.
*/
export function sequence(...handlers: MiddlewareEndpointHandler[]): MiddlewareEndpointHandler {
export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
const filtered = handlers.filter((h) => !!h);
const length = filtered.length;
if (!length) {
const handler: MiddlewareEndpointHandler = defineMiddleware((context, next) => {
const handler: MiddlewareHandler = defineMiddleware((context, next) => {
return next();
});
return handler;

View file

@ -1,10 +1,4 @@
import type {
ComponentInstance,
EndpointHandler,
MiddlewareEndpointHandler,
MiddlewareHandler,
MiddlewareResponseHandler,
} from '../@types/astro.js';
import type { ComponentInstance, EndpointHandler, MiddlewareHandler } from '../@types/astro.js';
import { callEndpoint, createAPIContext } from './endpoint/index.js';
import { callMiddleware } from './middleware/callMiddleware.js';
import { renderPage } from './render/core.js';
@ -28,7 +22,7 @@ export type PipelineHookFunction = (ctx: RenderContext, mod: ComponentInstance |
*/
export class Pipeline {
env: Environment;
#onRequest?: MiddlewareEndpointHandler;
#onRequest?: MiddlewareHandler;
#hooks: PipelineHooks = {
before: [],
};
@ -60,7 +54,7 @@ export class Pipeline {
/**
* A middleware function that will be called before each request.
*/
setMiddlewareFunction(onRequest: MiddlewareEndpointHandler) {
setMiddlewareFunction(onRequest: MiddlewareHandler) {
this.#onRequest = onRequest;
}
@ -115,11 +109,11 @@ export class Pipeline {
*
* It throws an error if the page can't be rendered.
*/
async #tryRenderRoute<MiddlewareReturnType = Response>(
async #tryRenderRoute(
renderContext: Readonly<RenderContext>,
env: Readonly<Environment>,
mod: Readonly<ComponentInstance> | undefined,
onRequest?: MiddlewareHandler<MiddlewareReturnType>
onRequest?: MiddlewareHandler
): Promise<Response> {
const apiContext = createAPIContext({
request: renderContext.request,
@ -137,19 +131,14 @@ export class Pipeline {
case 'fallback':
case 'redirect': {
if (onRequest) {
return await callMiddleware<Response>(
env.logger,
onRequest as MiddlewareResponseHandler,
apiContext,
() => {
return renderPage({
mod,
renderContext,
env,
cookies: apiContext.cookies,
});
}
);
return await callMiddleware(onRequest, apiContext, () => {
return renderPage({
mod,
renderContext,
env,
cookies: apiContext.cookies,
});
});
} else {
return await renderPage({
mod,

View file

@ -24,5 +24,5 @@ export interface SSROptions {
/**
* Optional middlewares
*/
middleware?: AstroMiddlewareInstance<unknown>;
middleware?: AstroMiddlewareInstance;
}

View file

@ -1,5 +1,5 @@
import { appendForwardSlash, joinPaths } from '@astrojs/internal-helpers/path';
import type { MiddlewareEndpointHandler, RouteData, SSRManifest } from '../@types/astro.js';
import type { MiddlewareHandler, RouteData, SSRManifest } from '../@types/astro.js';
import type { PipelineHookFunction } from '../core/pipeline.js';
const routeDataSymbol = Symbol.for('astro.routeData');
@ -19,7 +19,7 @@ export function createI18nMiddleware(
i18n: SSRManifest['i18n'],
base: SSRManifest['base'],
trailingSlash: SSRManifest['trailingSlash']
): MiddlewareEndpointHandler | undefined {
): MiddlewareHandler | undefined {
if (!i18n) {
return undefined;
}

View file

@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url';
import type {
ComponentInstance,
ManifestData,
MiddlewareEndpointHandler,
MiddlewareHandler,
RouteData,
SSRElement,
SSRManifest,
@ -288,7 +288,7 @@ export async function handleRoute({
});
}
const onRequest = middleware?.onRequest as MiddlewareEndpointHandler | undefined;
const onRequest = middleware?.onRequest as MiddlewareHandler | undefined;
if (config.experimental.i18n) {
const i18Middleware = createI18nMiddleware(
config.experimental.i18n,

View file

@ -15,10 +15,10 @@ export function getStaticPaths() {
}
export function GET({ params, request }) {
return {
body: JSON.stringify({
return new Response(
JSON.stringify({
param: params.param,
pathname: new URL(request.url).pathname
})
};
);
}

View file

@ -1,14 +1,12 @@
export async function getStaticPaths() {
return [
return [
{ params: { slug: 'thing1' } },
{ params: { slug: 'thing2' } }
];
}
export async function GET() {
return {
body: JSON.stringify({
title: '[slug]'
}, null, 4)
};
return Response.json({
title: '[slug]',
});
}

View file

@ -1,6 +1,4 @@
export async function GET() {
const docs = await import.meta.glob('./*.md', { eager: true });
return {
body: JSON.stringify(Object.values(docs).map(doc => doc.frontmatter)),
}
return Response.json(Object.values(docs).map((doc) => doc.frontmatter));
}

View file

@ -1,9 +1,7 @@
import { getHeadings } from './with-layout.md';
export async function GET() {
return {
body: JSON.stringify({
headings: getHeadings(),
}),
}
return Response.json({
headings: getHeadings(),
});
}

View file

@ -1,10 +1,8 @@
import { rawContent, compiledContent } from './basic.md';
export async function GET() {
return {
body: JSON.stringify({
raw: rawContent(),
compiled: await compiledContent(),
}),
}
return Response.json({
raw: rawContent(),
compiled: await compiledContent(),
});
}

View file

@ -1,7 +1,5 @@
import { frontmatter } from './vite-env-vars.md';
export async function GET() {
return {
body: JSON.stringify(frontmatter),
}
return Response.json(frontmatter);
}

View file

@ -8,7 +8,7 @@ export async function GET() {
const withSlugConfig = stripAllRenderFn(await getCollection('with-custom-slugs'));
const withUnionSchema = stripAllRenderFn(await getCollection('with-union-schema'));
return {
body: devalue.stringify({withoutConfig, withSchemaConfig, withSlugConfig, withUnionSchema}),
}
return new Response(
devalue.stringify({ withoutConfig, withSchemaConfig, withSlugConfig, withUnionSchema })
);
}

View file

@ -5,10 +5,17 @@ import { stripRenderFn } from '../utils.js';
export async function GET() {
const columbiaWithoutConfig = stripRenderFn(await getEntryBySlug('without-config', 'columbia'));
const oneWithSchemaConfig = stripRenderFn(await getEntryBySlug('with-schema-config', 'one'));
const twoWithSlugConfig = stripRenderFn(await getEntryBySlug('with-custom-slugs', 'interesting-two'));
const twoWithSlugConfig = stripRenderFn(
await getEntryBySlug('with-custom-slugs', 'interesting-two')
);
const postWithUnionSchema = stripRenderFn(await getEntryBySlug('with-union-schema', 'post'));
return {
body: devalue.stringify({columbiaWithoutConfig, oneWithSchemaConfig, twoWithSlugConfig, postWithUnionSchema}),
}
return new Response(
devalue.stringify({
columbiaWithoutConfig,
oneWithSchemaConfig,
twoWithSlugConfig,
postWithUnionSchema,
})
);
}

View file

@ -3,8 +3,5 @@ import type { APIRoute } from "../../../../../src/@types/astro";
export const GET = (async ({ params, request }) => {
const url = new URL(request.url);
const src = url.searchParams.get("src");
return {
body: "An image: " + JSON.stringify(src),
};
return new Response("An image: " + JSON.stringify(src));
}) satisfies APIRoute;

View file

@ -1,9 +1,9 @@
import { getEntry } from 'astro:content';
const ids = ['Ben Holmes', 'Fred K Schott', 'Nate Moore']
const ids = ['Ben Holmes', 'Fred K Schott', 'Nate Moore'];
export function getStaticPaths() {
return ids.map(id => ({ params: { id } }))
return ids.map((id) => ({ params: { id } }));
}
/** @param {import('astro').APIContext} params */
@ -11,12 +11,8 @@ export async function GET({ params }) {
const { id } = params;
const author = await getEntry('authors-without-config', id);
if (!author) {
return {
body: JSON.stringify({ error: `Author ${id} Not found` }),
}
return Response.json({ error: `Author ${id} Not found` });
} else {
return {
body: JSON.stringify(author),
}
return Response.json(author);
}
}

View file

@ -2,8 +2,5 @@ import { getCollection } from 'astro:content';
export async function GET() {
const authors = await getCollection('authors-without-config');
return {
body: JSON.stringify(authors),
}
return Response.json(authors);
}

View file

@ -1,9 +1,9 @@
import { getEntry } from 'astro:content';
const langs = ['en', 'es', 'fr']
const langs = ['en', 'es', 'fr'];
export function getStaticPaths() {
return langs.map(lang => ({ params: { lang } }))
return langs.map((lang) => ({ params: { lang } }));
}
/** @param {import('astro').APIContext} params */
@ -11,12 +11,8 @@ export async function GET({ params }) {
const { lang } = params;
const translations = await getEntry('i18n', lang);
if (!translations) {
return {
body: JSON.stringify({ error: `Translation ${lang} Not found` }),
}
return Response.json({ error: `Translation ${lang} Not found` });
} else {
return {
body: JSON.stringify(translations),
}
return Response.json(translations);
}
}

View file

@ -2,8 +2,5 @@ import { getCollection } from 'astro:content';
export async function GET() {
const translations = await getCollection('i18n');
return {
body: JSON.stringify(translations),
}
return Response.json(translations);
}

View file

@ -2,8 +2,5 @@ import { getDataEntryById } from 'astro:content';
export async function GET() {
const item = await getDataEntryById('i18n', 'en');
return {
body: JSON.stringify(item),
}
return Response.json(item);
}

View file

@ -1,15 +1,13 @@
import type { APIRoute } from "astro";
import type { APIRoute } from 'astro';
const slugs = ["one", undefined];
const slugs = ['one', undefined];
export const GET: APIRoute = ({ params }) => {
return {
body: JSON.stringify({
slug: params.slug || "index",
}),
};
return Response.json({
slug: params.slug || 'index',
});
};
export function getStaticPaths() {
return slugs.map((u) => ({ params: { slug: u } }));
return slugs.map((u) => ({ params: { slug: u } }));
}

View file

@ -1,12 +0,0 @@
// NOTE: test deprecated object form
// Returns the file body for this non-HTML file.
// The content type is based off of the extension in the filename,
// in this case: about.json.
export async function GET() {
return {
body: JSON.stringify({
name: 'Astro',
url: 'https://astro.build/',
}),
};
}

View file

@ -1,18 +0,0 @@
import { promises as fs } from 'node:fs';
import type { APIRoute } from 'astro';
// NOTE: test deprecated object form
export const GET: APIRoute = async function get() {
try {
// Image is in the public domain. Sourced from
// https://en.wikipedia.org/wiki/File:Portrait_placeholder.png
const buffer = await fs.readFile('./test/fixtures/non-html-pages/src/images/placeholder.png');
return {
body: buffer.toString('binary'),
encoding: 'binary',
} as const;
} catch (error: unknown) {
throw new Error(`Something went wrong in placeholder.png route!: ${error as string}`);
}
};

View file

@ -2,12 +2,13 @@ import { promises as fs } from 'node:fs';
import type { APIRoute } from 'astro';
export const GET: APIRoute = async function get({ ResponseWithEncoding }) {
export const GET: APIRoute = async function get() {
try {
// Image is in the public domain. Sourced from
// https://en.wikipedia.org/wiki/File:Portrait_placeholder.png
const buffer = await fs.readFile('./test/fixtures/non-html-pages/src/images/placeholder.png');
return new ResponseWithEncoding(buffer.toString('binary'), undefined, 'binary')
// NOTE: SSG only so not Content-Type needed
return new Response(buffer.buffer)
} catch (error: unknown) {
throw new Error(`Something went wrong in placeholder.png route!: ${error as string}`);
}

View file

@ -1,11 +1,11 @@
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ params }) => {
return {
body: JSON.stringify({
return new Response(
JSON.stringify({
path: params.slug,
}),
};
})
);
};
export function getStaticPaths() {

View file

@ -1,12 +1,12 @@
import type { APIRoute } from 'astro';
export const GET: APIRoute = async ({ params }) => {
return {
body: JSON.stringify({
return new Response(
JSON.stringify({
foo: params.foo,
bar: params.bar,
}),
};
bar: params.bar,
})
);
};
export function getStaticPaths() {

View file

@ -2,17 +2,15 @@
* @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,
})
};
return Response.json({
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,
});
}

View file

@ -1,10 +0,0 @@
// NOTE: test deprecated object form
export function GET() {
return {
body: JSON.stringify([
{ name: 'lettuce' },
{ name: 'broccoli' },
{ name: 'pizza' }
])
};
}

View file

@ -1,6 +1,3 @@
export function GET({ params }) {
return {
body: JSON.stringify(params)
};
return Response.json(params);
}

View file

@ -1,16 +1,14 @@
export const prerender = true;
export async function getStaticPaths() {
return [
return [
{ params: { slug: 'thing1' } },
{ params: { slug: 'thing2' } }
];
}
export async function GET() {
return {
body: JSON.stringify({
title: '[slug]'
}, null, 4)
};
return Response.json({
title: '[slug]',
});
}

View file

@ -1,8 +1,6 @@
export async function GET() {
return {
body: JSON.stringify({
name: 'Astro Technology Company',
url: 'https://astro.build/'
})
}
export function GET() {
return Response.json({
name: 'Astro Technology Company',
url: 'https://astro.build/',
});
}

View file

@ -6,11 +6,9 @@ export async function getStaticPaths() {
}
export async function GET({ params }) {
return {
body: JSON.stringify({
slug: params.slug,
name: 'Astro Technology Company',
url: 'https://astro.build/'
})
}
return Response.json({
slug: params.slug,
name: 'Astro Technology Company',
url: 'https://astro.build/'
});
}

View file

@ -15,8 +15,5 @@ async function fetchPosts() {
export async function GET() {
const posts = await fetchPosts();
return {
body: JSON.stringify(posts, null, 4),
};
return Response.json(posts);
}

View file

@ -6,10 +6,8 @@ export async function getStaticPaths() {
}
export async function GET({ params }) {
return {
body: JSON.stringify({
slug: params.slug,
title: '[slug]'
})
};
return Response.json({
slug: params.slug,
title: '[slug]'
});
}

View file

@ -6,10 +6,8 @@ export async function getStaticPaths() {
}
export async function GET({ params }) {
return {
body: JSON.stringify({
slug: params.slug,
title: 'data [slug]'
})
};
return Response.json({
slug: params.slug,
title: 'data [slug]'
});
}

View file

@ -1,7 +1,3 @@
export async function GET() {
return {
body: JSON.stringify({
title: 'home'
})
};
return Response.json({ title: 'home' });
}

View file

@ -1,14 +1,16 @@
export async function getStaticPaths() {
return [
{ params: { image: 1 } },
{ params: { image: 2 } },
];
return [{ params: { image: 1 } }, { params: { image: 2 } }];
}
export async function GET({ params }) {
return {
body: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200">
return new Response(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200">
<title>${params.image}</title>
</svg>`
};
</svg>`,
{
headers: {
'content-type': 'image/svg+xml',
},
}
);
}

View file

@ -1,9 +1,6 @@
import { readFileSync } from "node:fs";
import { readFile } from 'node:fs/promises';
export async function GET({ params, request }) {
const buffer = readFileSync(new URL('../../astro.png', import.meta.url));
return {
body: buffer.toString('hex'),
encoding: 'hex',
};
export async function GET() {
const buffer = await readFile(new URL('../../astro.png', import.meta.url));
return new Response(buffer.buffer);
}

View file

@ -1,7 +1,12 @@
export async function GET() {
return {
body: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200">
export function GET() {
return new Response(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200">
<title>Static SVG</title>
</svg>`
};
</svg>`,
{
headers: {
'content-type': 'image/svg+xml',
},
}
);
}

View file

@ -15,12 +15,6 @@ describe('Non-HTML Pages', () => {
expect(json).to.have.property('name', 'Astro');
expect(json).to.have.property('url', 'https://astro.build/');
});
it('should match contents (deprecated object form)', async () => {
const json = JSON.parse(await fixture.readFile('/about-object.json'));
expect(json).to.have.property('name', 'Astro');
expect(json).to.have.property('url', 'https://astro.build/');
});
});
describe('png', () => {
@ -40,22 +34,5 @@ describe('Non-HTML Pages', () => {
'iVBORw0KGgoAAAANSUhEUgAAAGQAAACWCAMAAAAfZt10AAAABlBMVEXd3d3+/v7B/CFgAAAA3UlEQVR42u3ZMQ7DIBQFQeb+l06bNgUbG/5eYApLFjzWNE3TNE3TNE035av9AhAQEBBQGAQEFAaFQWFQGBQGhUGCKAwKgwQpDJ6JECgCRYIEikH8YAyCRyEGyRCDvBWRIPNNBpm/8G6kUM45EhXKlQfuFSHFpbFH+jt2j/S7xwqUYvBaCRIozZy6X2km7v1K8uwQIIWBwkBAQEBg3Tyj3z4LnzRBKgwKg8KgMEgQhaEwSBCFQWBEiMIgQQqDBCkMEqQw+APixYgcsa0TERs7D/F6xGmIAxCD/Iw4AvEB92Ec3ZAPdlMAAAAASUVORK5CYII='
);
});
it('should not have had its encoding mangled (deprecated object form)', async () => {
const buffer = await fixture.readFile('/placeholder-object.png', 'base64');
// Sanity check the first byte
const hex = Buffer.from(buffer, 'base64').toString('hex');
const firstHexByte = hex.slice(0, 2);
// If we accidentally utf8 encode the png, the first byte (in hex) will be 'c2'
expect(firstHexByte).to.not.equal('c2');
// and if correctly encoded in binary, it should be '89'
expect(firstHexByte).to.equal('89');
// Make sure the whole buffer (in base64) matches this snapshot
expect(buffer).to.equal(
'iVBORw0KGgoAAAANSUhEUgAAAGQAAACWCAMAAAAfZt10AAAABlBMVEXd3d3+/v7B/CFgAAAA3UlEQVR42u3ZMQ7DIBQFQeb+l06bNgUbG/5eYApLFjzWNE3TNE3TNE035av9AhAQEBBQGAQEFAaFQWFQGBQGhUGCKAwKgwQpDJ6JECgCRYIEikH8YAyCRyEGyRCDvBWRIPNNBpm/8G6kUM45EhXKlQfuFSHFpbFH+jt2j/S7xwqUYvBaCRIozZy6X2km7v1K8uwQIIWBwkBAQEBg3Tyj3z4LnzRBKgwKg8KgMEgQhaEwSBCFQWBEiMIgQQqDBCkMEqQw+APixYgcsa0TERs7D/F6xGmIAxCD/Iw4AvEB92Ec3ZAPdlMAAAAASUVORK5CYII='
);
});
});
});

View file

@ -33,17 +33,6 @@ describe('API routes in SSR', () => {
expect(body.length).to.equal(3);
});
it('Can load the API route too (deprecated object form)', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/food-object.json');
const response = await app.render(request);
expect(response.status).to.equal(200);
expect(response.headers.get('Content-Type')).to.equal('application/json;charset=utf-8');
expect(response.headers.get('Content-Length')).to.not.be.empty;
const body = await response.json();
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');
@ -96,13 +85,6 @@ describe('API routes in SSR', () => {
expect(res.status).to.equal(200);
});
it('Infer content type with charset for { body } shorthand (deprecated object form)', async () => {
const response = await fixture.fetch('/food-object.json', {
method: 'GET',
});
expect(response.headers.get('Content-Type')).to.equal('application/json;charset=utf-8');
});
it('Can set multiple headers of the same type', async () => {
const response = await new Promise((resolve) => {
let { port } = devServer.address;

View file

@ -4,7 +4,5 @@ import { stripAllRenderFn } from '../../utils.js';
export async function GET() {
const posts = await getCollection('blog');
return {
body: stringify(stripAllRenderFn(posts))
};
return new Response(stringify(stripAllRenderFn(posts)));
}

View file

@ -4,7 +4,5 @@ import { stripRenderFn } from '../../utils.js';
export async function GET() {
const post = await getEntryBySlug('blog', 'post-1');
return {
body: stringify(stripRenderFn(post)),
};
return new Response(stringify(stripRenderFn(post)));
}

View file

@ -1,6 +1,6 @@
export async function GET() {
const docs = await import.meta.glob('./*.mdx', { eager: true });
return {
body: JSON.stringify(Object.values(docs).map(doc => doc.frontmatter)),
}
return new Response(
JSON.stringify(Object.values(docs).map(doc => doc.frontmatter))
);
}

View file

@ -1,9 +1,6 @@
export async function GET() {
const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
return {
body: JSON.stringify({
titles: Object.values(mdxPages ?? {}).map(v => v?.frontmatter?.title),
})
}
return Response.json({
titles: Object.values(mdxPages ?? {}).map((v) => v?.frontmatter?.title),
});
}

View file

@ -1,11 +1,8 @@
export async function GET() {
const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
return {
body: JSON.stringify({
headingsByPage: Object.fromEntries(
Object.entries(mdxPages ?? {}).map(([k, v]) => [k, v?.getHeadings()])
),
}),
}
return Response.json({
headingsByPage: Object.fromEntries(
Object.entries(mdxPages ?? {}).map(([k, v]) => [k, v?.getHeadings()])
),
});
}

View file

@ -1,9 +1,6 @@
export async function GET() {
const mdxPages = await import.meta.glob('./*.mdx', { eager: true });
return {
body: JSON.stringify({
urls: Object.values(mdxPages ?? {}).map(v => v?.url),
})
}
return Response.json({
urls: Object.values(mdxPages ?? {}).map((v) => v?.url),
});
}

View file

@ -1,7 +1,5 @@
import { frontmatter } from './vite-env-vars.mdx';
export function GET() {
return {
body: JSON.stringify(frontmatter),
}
return Response.json(frontmatter);
}

View file

@ -1,9 +1,7 @@
export async function GET() {
let number = Math.random();
return {
body: JSON.stringify({
number,
message: `Here's a random number: ${number}`,
}),
};
return Response.json({
number,
message: `Here's a random number: ${number}`,
});
}