Skip to content

Commit cdabf6e

Browse files
bluwyMoustaphaDevmatthewpsarah11918
authored
Remove support for simple objects in endpoints (#9181)
* Deprecate simple object from endpoints * Update changeset * Add missing Response return Co-authored-by: Happydev <[email protected]> * Update .changeset/clever-beds-notice.md Co-authored-by: Sarah Rainsberger <[email protected]> --------- Co-authored-by: Happydev <[email protected]> Co-authored-by: Matthew Phillips <[email protected]> Co-authored-by: Sarah Rainsberger <[email protected]>
1 parent 37697a2 commit cdabf6e

File tree

56 files changed

+206
-529
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+206
-529
lines changed

.changeset/clever-beds-notice.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'astro': major
3+
---
4+
5+
Removes support for returning simple objects from endpoints (deprecated since Astro 3.0). You should return a `Response` instead.
6+
7+
`ResponseWithEncoding` is also removed. You can refactor the code to return a response with an array buffer instead, which is encoding agnostic.
8+
9+
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.

packages/astro/src/@types/astro.ts

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import type { AstroConfigType } from '../core/config/index.js';
1919
import type { AstroTimer } from '../core/config/timer.js';
2020
import type { TSConfig } from '../core/config/tsconfig.js';
2121
import type { AstroCookies } from '../core/cookies/index.js';
22-
import type { ResponseWithEncoding } from '../core/endpoint/index.js';
2322
import type { AstroIntegrationLogger, Logger, LoggerLevel } from '../core/logger/core.js';
2423
import type { AstroDevOverlay, DevOverlayCanvas } from '../runtime/client/dev-overlay/overlay.js';
2524
import type { DevOverlayHighlight } from '../runtime/client/dev-overlay/ui-library/highlight.js';
@@ -2005,8 +2004,6 @@ export interface AstroAdapter {
20052004
supportedAstroFeatures: AstroFeatureMap;
20062005
}
20072006

2008-
type Body = string;
2009-
20102007
export type ValidRedirectStatus = 300 | 301 | 302 | 303 | 304 | 307 | 308;
20112008

20122009
// Shared types between `Astro` global and API context object
@@ -2163,7 +2160,6 @@ export interface APIContext<
21632160
* ```
21642161
*/
21652162
locals: App.Locals;
2166-
ResponseWithEncoding: typeof ResponseWithEncoding;
21672163

21682164
/**
21692165
* Available only when `experimental.i18n` enabled and in SSR.
@@ -2199,22 +2195,12 @@ export interface APIContext<
21992195
currentLocale: string | undefined;
22002196
}
22012197

2202-
export type EndpointOutput =
2203-
| {
2204-
body: Body;
2205-
encoding?: BufferEncoding;
2206-
}
2207-
| {
2208-
body: Uint8Array;
2209-
encoding: 'binary';
2210-
};
2211-
22122198
export type APIRoute<Props extends Record<string, any> = Record<string, any>> = (
22132199
context: APIContext<Props>
2214-
) => EndpointOutput | Response | Promise<EndpointOutput | Response>;
2200+
) => Response | Promise<Response>;
22152201

22162202
export interface EndpointHandler {
2217-
[method: string]: APIRoute | ((params: Params, request: Request) => EndpointOutput | Response);
2203+
[method: string]: APIRoute | ((params: Params, request: Request) => Response);
22182204
}
22192205

22202206
export type Props = Record<string, unknown>;
@@ -2319,20 +2305,16 @@ export interface AstroIntegration {
23192305
};
23202306
}
23212307

2322-
export type MiddlewareNext<R> = () => Promise<R>;
2323-
export type MiddlewareHandler<R> = (
2308+
export type MiddlewareNext = () => Promise<Response>;
2309+
export type MiddlewareHandler = (
23242310
context: APIContext,
2325-
next: MiddlewareNext<R>
2326-
) => Promise<R> | R | Promise<void> | void;
2327-
2328-
export type MiddlewareResponseHandler = MiddlewareHandler<Response>;
2329-
export type MiddlewareEndpointHandler = MiddlewareHandler<Response | EndpointOutput>;
2330-
export type MiddlewareNextResponse = MiddlewareNext<Response>;
2311+
next: MiddlewareNext
2312+
) => Promise<Response> | Response | Promise<void> | void;
23312313

23322314
// NOTE: when updating this file with other functions,
23332315
// remember to update `plugin-page.ts` too, to add that function as a no-op function.
2334-
export type AstroMiddlewareInstance<R> = {
2335-
onRequest?: MiddlewareHandler<R>;
2316+
export type AstroMiddlewareInstance = {
2317+
onRequest?: MiddlewareHandler;
23362318
};
23372319

23382320
export type AstroIntegrationMiddleware = {

packages/astro/src/core/app/index.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type {
22
EndpointHandler,
33
ManifestData,
4-
MiddlewareEndpointHandler,
54
RouteData,
65
SSRElement,
76
SSRManifest,
@@ -181,16 +180,14 @@ export class App {
181180
);
182181
if (i18nMiddleware) {
183182
if (mod.onRequest) {
184-
this.#pipeline.setMiddlewareFunction(
185-
sequence(i18nMiddleware, mod.onRequest as MiddlewareEndpointHandler)
186-
);
183+
this.#pipeline.setMiddlewareFunction(sequence(i18nMiddleware, mod.onRequest));
187184
} else {
188185
this.#pipeline.setMiddlewareFunction(i18nMiddleware);
189186
}
190187
this.#pipeline.onBeforeRenderRoute(i18nPipelineHook);
191188
} else {
192189
if (mod.onRequest) {
193-
this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
190+
this.#pipeline.setMiddlewareFunction(mod.onRequest);
194191
}
195192
}
196193
response = await this.#pipeline.renderRoute(renderContext, pageModule);
@@ -322,7 +319,7 @@ export class App {
322319
);
323320
const page = (await mod.page()) as any;
324321
if (skipMiddleware === false && mod.onRequest) {
325-
this.#pipeline.setMiddlewareFunction(mod.onRequest as MiddlewareEndpointHandler);
322+
this.#pipeline.setMiddlewareFunction(mod.onRequest);
326323
}
327324
if (skipMiddleware) {
328325
// make sure middleware set by other requests is cleared out
@@ -367,8 +364,8 @@ export class App {
367364
const status = override?.status
368365
? override.status
369366
: oldResponse.status === 200
370-
? newResponse.status
371-
: oldResponse.status;
367+
? newResponse.status
368+
: oldResponse.status;
372369

373370
return new Response(newResponse.body, {
374371
status,

packages/astro/src/core/build/generate.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type {
88
AstroSettings,
99
ComponentInstance,
1010
GetStaticPathsItem,
11-
MiddlewareEndpointHandler,
1211
RouteData,
1312
RouteType,
1413
SSRError,
@@ -269,15 +268,13 @@ async function generatePage(
269268
);
270269
if (config.experimental.i18n && i18nMiddleware) {
271270
if (onRequest) {
272-
pipeline.setMiddlewareFunction(
273-
sequence(i18nMiddleware, onRequest as MiddlewareEndpointHandler)
274-
);
271+
pipeline.setMiddlewareFunction(sequence(i18nMiddleware, onRequest));
275272
} else {
276273
pipeline.setMiddlewareFunction(i18nMiddleware);
277274
}
278275
pipeline.onBeforeRenderRoute(i18nPipelineHook);
279276
} else if (onRequest) {
280-
pipeline.setMiddlewareFunction(onRequest as MiddlewareEndpointHandler);
277+
pipeline.setMiddlewareFunction(onRequest);
281278
}
282279
if (!pageModulePromise) {
283280
throw new Error(
@@ -560,7 +557,6 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
560557
});
561558

562559
let body: string | Uint8Array;
563-
let encoding: BufferEncoding | undefined;
564560

565561
let response: Response;
566562
try {
@@ -603,15 +599,14 @@ async function generatePath(pathname: string, gopts: GeneratePathOptions, pipeli
603599
// If there's no body, do nothing
604600
if (!response.body) return;
605601
body = Buffer.from(await response.arrayBuffer());
606-
encoding = (response.headers.get('X-Astro-Encoding') as BufferEncoding | null) ?? 'utf-8';
607602
}
608603

609604
const outFolder = getOutFolder(pipeline.getConfig(), pathname, route.type);
610605
const outFile = getOutFile(pipeline.getConfig(), outFolder, pathname, route.type);
611606
route.distURL = outFile;
612607

613608
await fs.promises.mkdir(outFolder, { recursive: true });
614-
await fs.promises.writeFile(outFile, body, encoding);
609+
await fs.promises.writeFile(outFile, body);
615610
}
616611
}
617612

packages/astro/src/core/build/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export interface SinglePageBuiltModule {
5454
/**
5555
* The `onRequest` hook exported by the middleware
5656
*/
57-
onRequest?: MiddlewareHandler<unknown>;
57+
onRequest?: MiddlewareHandler;
5858
renderers: SSRLoadedRenderer[];
5959
}
6060

Lines changed: 6 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,4 @@
1-
import mime from 'mime';
2-
import type {
3-
APIContext,
4-
EndpointHandler,
5-
EndpointOutput,
6-
MiddlewareEndpointHandler,
7-
MiddlewareHandler,
8-
Params,
9-
} from '../../@types/astro.js';
1+
import type { APIContext, EndpointHandler, MiddlewareHandler, Params } from '../../@types/astro.js';
102
import { renderEndpoint } from '../../runtime/server/index.js';
113
import { ASTRO_VERSION } from '../constants.js';
124
import { AstroCookies, attachCookiesToResponse } from '../cookies/index.js';
@@ -19,8 +11,6 @@ import {
1911
} from '../render/context.js';
2012
import { type Environment, type RenderContext } from '../render/index.js';
2113

22-
const encoder = new TextEncoder();
23-
2414
const clientAddressSymbol = Symbol.for('astro.clientAddress');
2515
const clientLocalsSymbol = Symbol.for('astro.locals');
2616

@@ -69,7 +59,6 @@ export function createAPIContext({
6959
},
7060
});
7161
},
72-
ResponseWithEncoding,
7362
get preferredLocale(): string | undefined {
7463
if (preferredLocale) {
7564
return preferredLocale;
@@ -143,36 +132,11 @@ export function createAPIContext({
143132
return context;
144133
}
145134

146-
type ResponseParameters = ConstructorParameters<typeof Response>;
147-
148-
export class ResponseWithEncoding extends Response {
149-
constructor(body: ResponseParameters[0], init: ResponseParameters[1], encoding?: BufferEncoding) {
150-
// If a body string is given, try to encode it to preserve the behaviour as simple objects.
151-
// We don't do the full handling as simple objects so users can control how headers are set instead.
152-
if (typeof body === 'string') {
153-
// In NodeJS, we can use Buffer.from which supports all BufferEncoding
154-
if (typeof Buffer !== 'undefined' && Buffer.from) {
155-
body = Buffer.from(body, encoding);
156-
}
157-
// In non-NodeJS, use the web-standard TextEncoder for utf-8 strings
158-
else if (encoding == null || encoding === 'utf8' || encoding === 'utf-8') {
159-
body = encoder.encode(body);
160-
}
161-
}
162-
163-
super(body, init);
164-
165-
if (encoding) {
166-
this.headers.set('X-Astro-Encoding', encoding);
167-
}
168-
}
169-
}
170-
171-
export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>(
135+
export async function callEndpoint(
172136
mod: EndpointHandler,
173137
env: Environment,
174138
ctx: RenderContext,
175-
onRequest: MiddlewareHandler<MiddlewareResult> | undefined
139+
onRequest: MiddlewareHandler | undefined
176140
): Promise<Response> {
177141
const context = createAPIContext({
178142
request: ctx.request,
@@ -187,107 +151,13 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
187151

188152
let response;
189153
if (onRequest) {
190-
response = await callMiddleware<Response | EndpointOutput>(
191-
env.logger,
192-
onRequest as MiddlewareEndpointHandler,
193-
context,
194-
async () => {
195-
return await renderEndpoint(mod, context, env.ssr, env.logger);
196-
}
197-
);
154+
response = await callMiddleware(onRequest, context, async () => {
155+
return await renderEndpoint(mod, context, env.ssr, env.logger);
156+
});
198157
} else {
199158
response = await renderEndpoint(mod, context, env.ssr, env.logger);
200159
}
201160

202-
const isEndpointSSR = env.ssr && !ctx.route?.prerender;
203-
204-
if (response instanceof Response) {
205-
if (isEndpointSSR && response.headers.get('X-Astro-Encoding')) {
206-
env.logger.warn(
207-
null,
208-
'`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.'
209-
);
210-
}
211-
attachCookiesToResponse(response, context.cookies);
212-
return response;
213-
}
214-
215-
// The endpoint returned a simple object, convert it to a Response
216-
217-
// TODO: Remove in Astro 4.0
218-
env.logger.warn(
219-
null,
220-
`${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.`
221-
);
222-
223-
if (isEndpointSSR) {
224-
if (response.hasOwnProperty('headers')) {
225-
env.logger.warn(
226-
null,
227-
'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.'
228-
);
229-
}
230-
231-
if (response.encoding) {
232-
env.logger.warn(
233-
null,
234-
'`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.'
235-
);
236-
}
237-
}
238-
239-
let body: BodyInit;
240-
const headers = new Headers();
241-
242-
// Try to get the MIME type for this route
243-
const pathname = ctx.route
244-
? // Try the static route `pathname`
245-
ctx.route.pathname ??
246-
// Dynamic routes don't include `pathname`, so synthesize a path for these (e.g. 'src/pages/[slug].svg')
247-
ctx.route.segments.map((s) => s.map((p) => p.content).join('')).join('/')
248-
: // Fallback to pathname of the request
249-
ctx.pathname;
250-
const mimeType = mime.getType(pathname) || 'text/plain';
251-
headers.set('Content-Type', `${mimeType};charset=utf-8`);
252-
253-
// Save encoding to X-Astro-Encoding to be used later during SSG with `fs.writeFile`.
254-
// It won't work in SSR and is already warned above.
255-
if (response.encoding) {
256-
headers.set('X-Astro-Encoding', response.encoding);
257-
}
258-
259-
// For Uint8Array (binary), it can passed to Response directly
260-
if (response.body instanceof Uint8Array) {
261-
body = response.body;
262-
headers.set('Content-Length', body.byteLength.toString());
263-
}
264-
// In NodeJS, we can use Buffer.from which supports all BufferEncoding
265-
else if (typeof Buffer !== 'undefined' && Buffer.from) {
266-
body = Buffer.from(response.body, response.encoding);
267-
headers.set('Content-Length', body.byteLength.toString());
268-
}
269-
// In non-NodeJS, use the web-standard TextEncoder for utf-8 strings only
270-
// to calculate the content length
271-
else if (
272-
response.encoding == null ||
273-
response.encoding === 'utf8' ||
274-
response.encoding === 'utf-8'
275-
) {
276-
body = encoder.encode(response.body);
277-
headers.set('Content-Length', body.byteLength.toString());
278-
}
279-
// Fallback pass it to Response directly. It will mainly rely on X-Astro-Encoding
280-
// to be further processed in SSG.
281-
else {
282-
body = response.body;
283-
// NOTE: Can't calculate the content length as we can't encode to figure out the real length.
284-
// But also because we don't need the length for SSG as it's only being written to disk.
285-
}
286-
287-
response = new Response(body, {
288-
status: 200,
289-
headers,
290-
});
291161
attachCookiesToResponse(response, context.cookies);
292162
return response;
293163
}

0 commit comments

Comments
 (0)