diff --git a/.changeset/fast-dolls-eat.md b/.changeset/fast-dolls-eat.md new file mode 100644 index 0000000000..3eda1c985f --- /dev/null +++ b/.changeset/fast-dolls-eat.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Implements the Dynamic Route API RFC diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 2848760d28..805eeb4079 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -856,12 +856,27 @@ export interface AstroAdapter { args?: any; } +export interface APIContext { + params: Params; + request: Request; +} + export interface EndpointOutput { body: Output; } +interface APIRoute { + (context: APIContext): EndpointOutput | Response; + + /** + * @deprecated + * Use { context: APIRouteContext } object instead. + */ + (params: Params, request: Request): EndpointOutput | Response; +} + export interface EndpointHandler { - [method: string]: (params: any, request: Request) => EndpointOutput | Response; + [method: string]: APIRoute; } export interface AstroRenderer { diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts index b564a6ca61..7621285552 100644 --- a/packages/astro/src/core/render/route-cache.ts +++ b/packages/astro/src/core/render/route-cache.ts @@ -52,6 +52,7 @@ export async function callGetStaticPaths({ const keyedStaticPaths = staticPaths as GetStaticPathsResultKeyed; keyedStaticPaths.keyed = new Map(); + for (const sp of keyedStaticPaths) { const paramsKey = stringifyParams(sp.params); keyedStaticPaths.keyed.set(paramsKey, sp); diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index c9df2e62b0..dd278ae5ad 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -1,4 +1,5 @@ import type { + APIContext, AstroComponentMetadata, AstroGlobalPartial, EndpointHandler, @@ -468,7 +469,46 @@ export async function renderEndpoint(mod: EndpointHandler, request: Request, par `Endpoint handler not found! Expected an exported function for "${chosenMethod}"` ); } - return await handler.call(mod, params, request); + + if(handler.length > 1) { + // eslint-disable-next-line no-console + console.warn(` +API routes with 2 arguments have been deprecated. Instead they take a single argument in the form of: + +export function get({ params, request }) { + //... +} + +Update your code to remove this warning.`) + } + + const context = { + request, + params + }; + + const proxy = new Proxy(context, { + get(target, prop) { + if(prop in target) { + return Reflect.get(target, prop); + } else if(prop in params) { + // eslint-disable-next-line no-console + console.warn(` +API routes no longer pass params as the first argument. Instead an object containing a params property is provided in the form of: + +export function get({ params }) { + // ... +} + +Update your code to remove this warning.`); + return Reflect.get(params, prop); + } else { + return undefined; + } + } + }) as APIContext & Params; + + return await handler.call(mod, proxy, request); } async function replaceHeadInjection(result: SSRResult, html: string): Promise { diff --git a/packages/astro/test/api-routes.test.js b/packages/astro/test/api-routes.test.js new file mode 100644 index 0000000000..d7d0c3d29f --- /dev/null +++ b/packages/astro/test/api-routes.test.js @@ -0,0 +1,44 @@ +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('API routes', () => { + let fixture; + + before(async () => { + fixture = await loadFixture({ root: './fixtures/api-routes/' }); + await fixture.build(); + }); + + describe('Deprecated API', () => { + it('two argument supported', async () => { + const one = JSON.parse(await fixture.readFile('/old-api/twoarg/one.json')); + expect(one).to.deep.equal({ + param: 'one', + pathname: '/old-api/twoarg/one.json' + }); + const two = JSON.parse(await fixture.readFile('/old-api/twoarg/two.json')); + expect(two).to.deep.equal({ + param: 'two', + pathname: '/old-api/twoarg/two.json' + }); + }); + + it('param first argument is supported', async () => { + const one = JSON.parse(await fixture.readFile('/old-api/onearg/one.json')); + expect(one).to.deep.equal({ + param: 'one' + }); + }); + }); + + describe('1.0 API', () => { + it('Receives a context argument', async () => { + const one = JSON.parse(await fixture.readFile('/context/data/one.json')); + expect(one).to.deep.equal({ + param: 'one', + pathname: '/context/data/one.json' + }); + }); + }); +}); diff --git a/packages/astro/test/fixtures/api-routes/src/pages/context/data/[param].json.js b/packages/astro/test/fixtures/api-routes/src/pages/context/data/[param].json.js new file mode 100644 index 0000000000..2ed42a5ec5 --- /dev/null +++ b/packages/astro/test/fixtures/api-routes/src/pages/context/data/[param].json.js @@ -0,0 +1,24 @@ + +export function getStaticPaths() { + return [ + { + params: { + param: 'one' + } + }, + { + params: { + param: 'two' + } + }, + ] +} + +export function get({ params, request }) { + return { + body: JSON.stringify({ + param: params.param, + pathname: new URL(request.url).pathname + }) + }; +} diff --git a/packages/astro/test/fixtures/api-routes/src/pages/old-api/onearg/[param].json.js b/packages/astro/test/fixtures/api-routes/src/pages/old-api/onearg/[param].json.js new file mode 100644 index 0000000000..63771f8e0e --- /dev/null +++ b/packages/astro/test/fixtures/api-routes/src/pages/old-api/onearg/[param].json.js @@ -0,0 +1,23 @@ + +export function getStaticPaths() { + return [ + { + params: { + param: 'one' + } + }, + { + params: { + param: 'two' + } + }, + ] +} + +export function get(params) { + return { + body: JSON.stringify({ + param: params.param + }) + }; +} diff --git a/packages/astro/test/fixtures/api-routes/src/pages/old-api/twoarg/[param].json.js b/packages/astro/test/fixtures/api-routes/src/pages/old-api/twoarg/[param].json.js new file mode 100644 index 0000000000..ab71d68968 --- /dev/null +++ b/packages/astro/test/fixtures/api-routes/src/pages/old-api/twoarg/[param].json.js @@ -0,0 +1,24 @@ + +export function getStaticPaths() { + return [ + { + params: { + param: 'one' + } + }, + { + params: { + param: 'two' + } + }, + ] +} + +export function get(params, request) { + return { + body: JSON.stringify({ + param: params.param, + pathname: new URL(request.url).pathname + }) + }; +} diff --git a/packages/astro/test/fixtures/ssr-dynamic/src/pages/api/products/[id].js b/packages/astro/test/fixtures/ssr-dynamic/src/pages/api/products/[id].js index 2d619ee744..4d96b62a5e 100644 --- a/packages/astro/test/fixtures/ssr-dynamic/src/pages/api/products/[id].js +++ b/packages/astro/test/fixtures/ssr-dynamic/src/pages/api/products/[id].js @@ -1,5 +1,5 @@ -export function get(params) { +export function get({ params }) { return { body: JSON.stringify(params) };