Skip to content

Commit 87552ef

Browse files
elliott-with-the-longest-name-on-githubRich-Harrisgtm-nayan
authored
feat: uppercase endpoint methods (#5513)
* feat: Endpoint names uppercased * feat: Throw for old endpoint names * boring: Update tests * fix: Missed stuff * feat: Documentation * feat: Changeset * feat: Last few old-method-name references * feat: Minor tidy * check page endpoint methods as well as standalone endpoints * format * update template * ofc I miss the entire point of this change * update kit.svelte.dev Co-authored-by: Rich Harris <[email protected]> Co-authored-by: Rich Harris <[email protected]> Co-authored-by: gtm-nayan <[email protected]>
1 parent 2a796e4 commit 87552ef

File tree

100 files changed

+174
-151
lines changed

Some content is hidden

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

100 files changed

+174
-151
lines changed

Diff for: β€Ž.changeset/empty-teachers-cheat.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
[breaking] Endpoint method names uppercased to match HTTP specifications

Diff for: β€Ž.changeset/honest-coins-switch.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'create-svelte': patch
3+
---
4+
5+
uppercase handlers

Diff for: β€Ždocumentation/docs/03-routing.md

+14-14
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Their job is to return a `{ status, headers, body }` object representing the res
5858
```js
5959
/// file: src/routes/random.js
6060
/** @type {import('@sveltejs/kit').RequestHandler} */
61-
export async function get() {
61+
export async function GET() {
6262
return {
6363
status: 200,
6464
headers: {
@@ -117,7 +117,7 @@ export type RequestHandler<Body = any> = GenericRequestHandler<{ id: string }, B
117117
import db from '$lib/database';
118118

119119
/** @type {import('./__types/[id]').RequestHandler} */
120-
export async function get({ params }) {
120+
export async function GET({ params }) {
121121
// `params.id` comes from [id].js
122122
const item = await db.get(params.id);
123123

@@ -135,7 +135,7 @@ export async function get({ params }) {
135135
}
136136
```
137137

138-
> The type of the `get` function above comes from `./__types/[id].d.ts`, which is a file generated by SvelteKit (inside your [`outDir`](/docs/configuration#outdir), using the [`rootDirs`](https://www.typescriptlang.org/tsconfig#rootDirs) option) that provides type safety when accessing `params`. See the section on [generated types](/docs/types#generated-types) for more detail.
138+
> The type of the `GET` function above comes from `./__types/[id].d.ts`, which is a file generated by SvelteKit (inside your [`outDir`](/docs/configuration#outdir), using the [`rootDirs`](https://www.typescriptlang.org/tsconfig#rootDirs) option) that provides type safety when accessing `params`. See the section on [generated types](/docs/types#generated-types) for more detail.
139139
140140
To get the raw data instead of the page, you can include an `accept: application/json` header in the request, or β€” for convenience β€” append `/__data.json` to the URL, e.g. `/items/[id]/__data.json`.
141141

@@ -158,13 +158,13 @@ Endpoints can handle any HTTP method β€” not just `GET` β€” by exporting the cor
158158

159159
```js
160160
// @noErrors
161-
export function post(event) {...}
162-
export function put(event) {...}
163-
export function patch(event) {...}
164-
export function del(event) {...} // `delete` is a reserved word
161+
export function POST(event) {...}
162+
export function PUT(event) {...}
163+
export function PATCH(event) {...}
164+
export function DELETE(event) {...}
165165
```
166166

167-
These functions can, like `get`, return a `body` that will be passed to the page as props. Whereas 4xx/5xx responses from `get` will result in an error page rendering, similar responses to non-GET requests do not, allowing you to do things like render form validation errors:
167+
These functions can, like `GET`, return a `body` that will be passed to the page as props. Whereas 4xx/5xx responses from `GET` will result in an error page rendering, similar responses to non-GET requests do not, allowing you to do things like render form validation errors:
168168

169169
```js
170170
/// file: src/routes/items.js
@@ -188,7 +188,7 @@ export type RequestHandler<Body = any> = GenericRequestHandler<{}, Body>;
188188
import * as db from '$lib/database';
189189

190190
/** @type {import('./__types/items').RequestHandler} */
191-
export async function get() {
191+
export async function GET() {
192192
const items = await db.list();
193193

194194
return {
@@ -197,7 +197,7 @@ export async function get() {
197197
}
198198

199199
/** @type {import('./__types/items').RequestHandler} */
200-
export async function post({ request }) {
200+
export async function POST({ request }) {
201201
const [errors, item] = await db.create(request);
202202

203203
if (errors) {
@@ -221,10 +221,10 @@ export async function post({ request }) {
221221
```svelte
222222
/// file: src/routes/items.svelte
223223
<script>
224-
// The page always has access to props from `get`...
224+
// The page always has access to props from `GET`...
225225
export let items;
226226
227-
// ...plus props from `post` when the page is rendered
227+
// ...plus props from `POST` when the page is rendered
228228
// in response to a POST request, for example after
229229
// submitting the form below
230230
export let errors;
@@ -260,7 +260,7 @@ export {};
260260
// @filename: index.js
261261
// ---cut---
262262
/** @type {import('@sveltejs/kit').RequestHandler} */
263-
export async function post({ request }) {
263+
export async function POST({ request }) {
264264
const data = await request.formData(); // or .json(), or .text(), etc
265265

266266
await create(data);
@@ -280,7 +280,7 @@ const cookie2: string;
280280
// @filename: index.js
281281
// ---cut---
282282
/** @type {import('@sveltejs/kit').RequestHandler} */
283-
export function get() {
283+
export function GET() {
284284
return {
285285
headers: {
286286
'set-cookie': [cookie1, cookie2]

Diff for: β€Žpackages/create-svelte/templates/default/src/routes/todos/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { api } from './_api';
22
import type { RequestHandler } from './__types';
33

44
/** @type {import('./__types').RequestHandler} */
5-
export const get: RequestHandler = async ({ locals }) => {
5+
export const GET: RequestHandler = async ({ locals }) => {
66
// locals.userid comes from src/hooks.js
77
const response = await api('GET', `todos/${locals.userid}`);
88

@@ -30,7 +30,7 @@ export const get: RequestHandler = async ({ locals }) => {
3030
};
3131

3232
/** @type {import('./__types').RequestHandler} */
33-
export const post: RequestHandler = async ({ request, locals }) => {
33+
export const POST: RequestHandler = async ({ request, locals }) => {
3434
const form = await request.formData();
3535

3636
await api('POST', `todos/${locals.userid}`, {
@@ -50,7 +50,7 @@ const redirect = {
5050
};
5151

5252
/** @type {import('./__types').RequestHandler} */
53-
export const patch: RequestHandler = async ({ request, locals }) => {
53+
export const PATCH: RequestHandler = async ({ request, locals }) => {
5454
const form = await request.formData();
5555

5656
await api('PATCH', `todos/${locals.userid}/${form.get('uid')}`, {
@@ -62,7 +62,7 @@ export const patch: RequestHandler = async ({ request, locals }) => {
6262
};
6363

6464
/** @type {import('./__types').RequestHandler} */
65-
export const del: RequestHandler = async ({ request, locals }) => {
65+
export const DELETE: RequestHandler = async ({ request, locals }) => {
6666
const form = await request.formData();
6767

6868
await api('DELETE', `todos/${locals.userid}/${form.get('uid')}`);

Diff for: β€Žpackages/kit/src/core/adapt/builder.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function create_builder({ config, build_data, prerendered, log }) {
4747
content: segment
4848
})),
4949
pattern: route.pattern,
50-
methods: route.type === 'page' ? ['get'] : build_data.server.methods[route.file]
50+
methods: route.type === 'page' ? ['GET'] : build_data.server.methods[route.file]
5151
}));
5252

5353
const seen = new Set();

Diff for: β€Žpackages/kit/src/runtime/server/endpoint.js

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { to_headers } from '../../utils/http.js';
22
import { hash } from '../hash.js';
3-
import { is_pojo, normalize_request_method, serialize_error } from './utils.js';
3+
import { check_method_names, is_pojo, serialize_error } from './utils.js';
44

55
/** @param {string} body */
66
function error(body) {
@@ -43,32 +43,33 @@ export function is_text(content_type) {
4343
* @returns {Promise<Response>}
4444
*/
4545
export async function render_endpoint(event, mod, options) {
46-
const method = normalize_request_method(event);
46+
const { method } = event.request;
47+
48+
check_method_names(mod);
4749

4850
/** @type {import('types').RequestHandler} */
4951
let handler = mod[method];
5052

51-
if (!handler && method === 'head') {
52-
handler = mod.get;
53+
if (!handler && method === 'HEAD') {
54+
handler = mod.GET;
5355
}
5456

5557
if (!handler) {
5658
const allowed = [];
5759

58-
for (const method in ['get', 'post', 'put', 'patch']) {
59-
if (mod[method]) allowed.push(method.toUpperCase());
60+
for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
61+
if (mod[method]) allowed.push(method);
6062
}
6163

62-
if (mod.del) allowed.push('DELETE');
63-
if (mod.get || mod.head) allowed.push('HEAD');
64+
if (mod.GET || mod.HEAD) allowed.push('HEAD');
6465

6566
return event.request.headers.get('x-sveltekit-load')
6667
? // TODO would be nice to avoid these requests altogether,
6768
// by noting whether or not page endpoints export `get`
6869
new Response(undefined, {
6970
status: 204
7071
})
71-
: new Response(`${event.request.method} method not allowed`, {
72+
: new Response(`${method} method not allowed`, {
7273
status: 405,
7374
headers: {
7475
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
@@ -132,7 +133,7 @@ export async function render_endpoint(event, mod, options) {
132133
}
133134

134135
return new Response(
135-
method !== 'head' && !bodyless_status_codes.has(status) ? normalized_body : undefined,
136+
method !== 'HEAD' && !bodyless_status_codes.has(status) ? normalized_body : undefined,
136137
{
137138
status,
138139
headers

Diff for: β€Žpackages/kit/src/runtime/server/page/load_node.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as set_cookie_parser from 'set-cookie-parser';
33
import { normalize } from '../../load.js';
44
import { respond } from '../index.js';
55
import { LoadURL, PrerenderingURL, is_root_relative, resolve } from '../../../utils/url.js';
6-
import { is_pojo, lowercase_keys, normalize_request_method } from '../utils.js';
6+
import { check_method_names, is_pojo, lowercase_keys } from '../utils.js';
77
import { coalesce_to_error } from '../../../utils/error.js';
88
import { domain_matches, path_matches } from './cookie.js';
99

@@ -409,13 +409,15 @@ async function load_shadow_data(route, event, options, prerender) {
409409
try {
410410
const mod = await route.shadow();
411411

412-
if (prerender && (mod.post || mod.put || mod.del || mod.patch)) {
412+
check_method_names(mod);
413+
414+
if (prerender && (mod.POST || mod.PUT || mod.DELETE || mod.PATCH)) {
413415
throw new Error('Cannot prerender pages that have endpoints with mutative methods');
414416
}
415417

416-
const method = normalize_request_method(event);
417-
const is_get = method === 'head' || method === 'get';
418-
const handler = method === 'head' ? mod.head || mod.get : mod[method];
418+
const { method } = event.request;
419+
const is_get = method === 'HEAD' || method === 'GET';
420+
const handler = method === 'HEAD' ? mod.HEAD || mod.GET : mod[method];
419421

420422
if (!handler && !is_get) {
421423
return {
@@ -462,7 +464,7 @@ async function load_shadow_data(route, event, options, prerender) {
462464
data.body = body;
463465
}
464466

465-
const get = (method === 'head' && mod.head) || mod.get;
467+
const get = (method === 'HEAD' && mod.HEAD) || mod.GET;
466468
if (get) {
467469
const { status, headers, body } = validate_shadow_output(await get(event));
468470
add_cookies(/** @type {string[]} */ (data.cookies), headers);

Diff for: β€Žpackages/kit/src/runtime/server/utils.js

+11-6
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,6 @@ export function is_pojo(body) {
5050
return true;
5151
}
5252

53-
/** @param {import('types').RequestEvent} event */
54-
export function normalize_request_method(event) {
55-
const method = event.request.method.toLowerCase();
56-
return method === 'delete' ? 'del' : method; // 'delete' is a reserved word
57-
}
58-
5953
/**
6054
* Serialize an error into a JSON string, by copying its `name`, `message`
6155
* and (in dev) `stack`, plus any custom properties, plus recursively
@@ -97,6 +91,17 @@ function clone_error(error, get_stack) {
9791
return object;
9892
}
9993

94+
// TODO: Remove for 1.0
95+
/** @param {Record<string, any>} mod */
96+
export function check_method_names(mod) {
97+
['get', 'post', 'put', 'patch', 'del'].forEach((m) => {
98+
if (m in mod) {
99+
const replacement = m === 'del' ? 'DELETE' : m.toUpperCase();
100+
throw Error(`Endpoint method "${m}" has changed to "${replacement}"`);
101+
}
102+
});
103+
}
104+
100105
/** @type {import('types').SSRErrorPage} */
101106
export const GENERIC_ERROR = {
102107
id: '__error'

Diff for: β€Žpackages/kit/src/vite/build/build_server.js

+8-14
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import { mkdirp, posixify } from '../../utils/filesystem.js';
44
import { get_vite_config, merge_vite_configs, resolve_entry } from '../utils.js';
55
import { load_template } from '../../core/config/index.js';
66
import { get_runtime_directory } from '../../core/utils.js';
7-
import { create_build, find_deps, get_default_config, remove_svelte_kit } from './utils.js';
7+
import {
8+
create_build,
9+
find_deps,
10+
get_default_config,
11+
remove_svelte_kit,
12+
is_http_method
13+
} from './utils.js';
814
import { s } from '../../utils/misc.js';
915

1016
/**
@@ -255,16 +261,6 @@ export async function build_server(options, client) {
255261
};
256262
}
257263

258-
/** @type {Record<string, string>} */
259-
const method_names = {
260-
get: 'get',
261-
head: 'head',
262-
post: 'post',
263-
put: 'put',
264-
del: 'delete',
265-
patch: 'patch'
266-
};
267-
268264
/**
269265
* @param {string} cwd
270266
* @param {import('rollup').OutputChunk[]} output
@@ -285,9 +281,7 @@ function get_methods(cwd, output, manifest_data) {
285281
const file = route.type === 'endpoint' ? route.file : route.shadow;
286282

287283
if (file && lookup[file]) {
288-
methods[file] = lookup[file]
289-
.map((x) => /** @type {import('types').HttpMethod} */ (method_names[x]))
290-
.filter(Boolean);
284+
methods[file] = lookup[file].filter(is_http_method);
291285
}
292286
});
293287

Diff for: β€Žpackages/kit/src/vite/build/utils.js

+11
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,14 @@ export function remove_svelte_kit(config) {
150150
.flat(Infinity)
151151
.filter((plugin) => plugin.name !== 'vite-plugin-svelte-kit');
152152
}
153+
154+
const method_names = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']);
155+
156+
// If we'd written this in TypeScript, it could be easy...
157+
/**
158+
* @param {string} str
159+
* @returns {str is import('types').HttpMethod}
160+
*/
161+
export function is_http_method(str) {
162+
return method_names.has(str);
163+
}

Diff for: β€Žpackages/kit/test/apps/amp/src/routes/origin/index.json.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @type {import('@sveltejs/kit').RequestHandler} */
2-
export function get({ url }) {
2+
export function GET({ url }) {
33
return {
44
body: { origin: url.origin }
55
};

Diff for: β€Žpackages/kit/test/apps/amp/src/routes/valid/index.json.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function get() {
1+
export function GET() {
22
return {
33
body: {
44
answer: 42

Diff for: β€Žpackages/kit/test/apps/basics/src/routes/answer.json.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function get() {
1+
export function GET() {
22
return {
33
body: {
44
answer: 42

Diff for: β€Žpackages/kit/test/apps/basics/src/routes/caching/private/uses-fetch.json.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function get() {
1+
export function GET() {
22
return {
33
body: {
44
answer: 42

Diff for: β€Žpackages/kit/test/apps/basics/src/routes/delete-route/[id].json.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/** @type {import('@sveltejs/kit').RequestHandler} */
2-
export function del(req) {
2+
export function DELETE(req) {
33
return {
44
status: 200,
55
body: {

Diff for: β€Žpackages/kit/test/apps/basics/src/routes/encoded/endpoint.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export async function get() {
1+
export async function GET() {
22
return {
33
body: {
44
fruit: 'πŸŽπŸ‡πŸŒ'
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/** @type {import('@sveltejs/kit').RequestHandler} */
2-
export function get() {
2+
export function GET() {
33
return { body: {} };
44
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/** @type {import('@sveltejs/kit').RequestHandler} */
2-
export function get() {
2+
export function GET() {
33
return {};
44
}

Diff for: β€Žpackages/kit/test/apps/basics/src/routes/endpoint-output/fetched.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function get() {
1+
export function GET() {
22
return {
33
headers: {
44
'x-foo': 'bar'

0 commit comments

Comments
Β (0)