Skip to content

Commit

Permalink
Http verbs (#105)
Browse files Browse the repository at this point in the history
* fix: support HEAD, PATCH and OPTIONS http methods

* feat: use GET without body as default HEAD handling

* chore: remove console.log and update lint config

* fix: update CI to use node 20,22 and windows

* fix: collapse default HEAD entry in routes log
  • Loading branch information
rturnq authored Jan 16, 2025
1 parent 74e93a3 commit c03efd5
Show file tree
Hide file tree
Showing 55 changed files with 1,008 additions and 232 deletions.
5 changes: 5 additions & 0 deletions .changeset/short-lions-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@marko/run": patch
---

Support PATCH, OPTIONS and HEAD http methods
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ jobs:
- name: Lint Code
run: npm run @ci:lint
test:
runs-on: ubuntu-latest
name: "test: node@${{ matrix.node }}"
runs-on: ${{ matrix.os }}
name: "test: node@${{ matrix.node }} (${{ matrix.os }})"
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node: [18, 20]
node: [20, 22]
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down
3 changes: 2 additions & 1 deletion packages/run/.mocharc.json → .mocharc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"exit": true,
"timeout": 20000,
"extension": ["js", "ts", "marko"],
"watchFiles": ["src/**/*.ts", "src/**/*.marko"],
"spec": ["packages/**/src/**/*.test.@(js|ts)"],
"watchFiles": ["packages/**/src/**/*.@(ts|marko)"],
"require": ["tsx", "mocha-snap"]
}
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default tseslint.config(
"**/.app",
"**/.cache",
"**/.marko-run",
"**/.netlify",
"**/__snapshots__",
"**/*.marko.js",
"**/*actual*",
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"lint": "eslint --format unix . && prettier . --check --with-node-modules --log-level=warn",
"prepare": "husky",
"report": "open ./coverage/lcov-report/index.html",
"test": "npm run test -w packages -w packages/adapters --if-present",
"test:update": "UPDATE_EXPECTATIONS=1 mocha --update"
"test": "cross-env NODE_ENV=test NODE_OPTIONS='$NODE_OPTIONS --import tsx' mocha",
"test:update": "cross-env UPDATE_EXPECTATIONS=1 npm test -- --update"
},
"devDependencies": {
"@changesets/cli": "^2.27.11",
Expand Down
6 changes: 1 addition & 5 deletions packages/run/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@
"dist"
],
"scripts": {
"build": "rm -rf ./dist && tsc -b && tsx scripts/build.ts",
"test": "cross-env NODE_ENV=test NODE_OPTIONS='$NODE_OPTIONS --import tsx' mocha \"./src/**/__tests__/*.test.?(c)ts\"",
"test:inspect": "npm test -- --inspect",
"test:update": "npm test -- --update",
"test:watch": "npm test -- --watch"
"build": "rm -rf ./dist && tsc -b && tsx scripts/build.ts"
},
"dependencies": {
"@marko/run-explorer": "^0.1.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
Do NOT manually edit this file or your changes will be lost.
*/

import { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform } from "@marko/run/namespace";
import type * as Run from "@marko/run";


declare module "@marko/run" {
interface AppData extends Run.DefineApp<{
routes: {
"/": Routes["/"];
}
}> {}
}

declare module "../src/routes/+handler" {
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = Run.Routes["/"];
export type Context = Run.MultiRouteContext<Route>;
export type Handler = Run.HandlerLike<Route>;
/** @deprecated use `((context, next) => { ... }) satisfies MarkoRun.Handler` instead */
export const route: Run.HandlerTypeFn<Route>;
}
}

declare module "../src/routes/+page.marko" {
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = Run.Routes["/"];
export type Context = Run.MultiRouteContext<Route> & Marko.Global;
export type Handler = Run.HandlerLike<Route>;
/** @deprecated use `((context, next) => { ... }) satisfies MarkoRun.Handler` instead */
export const route: Run.HandlerTypeFn<Route>;
}
}

type Routes = {
"/": { verb: "get" | "post"; };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Loading

```html
page: GET /
```

# Step 0
()=>assertNoBody("HEAD")

# Step 1
()=>assertBody("POST")

# Step 2
()=>assertBody("PUT")

# Step 3
()=>assertBody("DELETE")

# Step 4
()=>assertBody("PATCH")

# Step 5
()=>assertBody("OPTIONS")

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Loading

```html
page: GET /
```

# Step 0
()=>assertNoBody("HEAD")

# Step 1
()=>assertBody("POST")

# Step 2
()=>assertBody("PUT")

# Step 3
()=>assertBody("DELETE")

# Step 4
()=>assertBody("PATCH")

# Step 5
()=>assertBody("OPTIONS")

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const handler: MarkoRun.Handler = (context) => {
return new Response(
`handler: ${context.request.method} ${context.url.pathname}`,
{ headers: { "content-type": "text/plain" } },
);
};

export { handler as POST }
export { handler as PUT }
export { handler as DELETE }
export { handler as PATCH }
export { handler as HEAD }
export { handler as OPTIONS }

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- page: GET ${$global.url.pathname}
26 changes: 26 additions & 0 deletions packages/run/src/__tests__/fixtures/all-http-verbs/test.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import assert from "assert";

export const steps = [
() => assertNoBody('HEAD'),
() => assertBody('POST'),
() => assertBody('PUT'),
() => assertBody('DELETE'),
() => assertBody('PATCH'),
() => assertBody('OPTIONS')
]

async function assertBody(method: string) {
const url = new URL(page.url());
const response = await page.request.fetch(url.href, { method });
assert.equal(response.ok(), true, `Response for ${method} is not ok`);
const body = await response.text();
assert.equal(body, `handler: ${method} ${url.pathname}`, `Response for ${method} has an unexpected body: "${body}"`);
}

async function assertNoBody(method: string) {
const url = new URL(page.url());
const response = await page.request.fetch(url.href, { method });
assert.equal(response.ok(), true, `Response for ${method} is not ok`);
const body = await response.text();
assert.equal(body, '', `Response for ${method} has a non-empty body: "${body}"`);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig-base.json",
"include": ["src/**/*", ".marko-run/*"],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
Do NOT manually edit this file or your changes will be lost.
*/

import { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform } from "@marko/run/namespace";
import type * as Run from "@marko/run";


declare module "@marko/run" {
interface AppData extends Run.DefineApp<{
routes: {
"/": Routes["/"];
}
}> {}
}

declare module "../src/routes/+page.marko" {
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = Run.Routes["/"];
export type Context = Run.MultiRouteContext<Route> & Marko.Global;
export type Handler = Run.HandlerLike<Route>;
/** @deprecated use `((context, next) => { ... }) satisfies MarkoRun.Handler` instead */
export const route: Run.HandlerTypeFn<Route>;
}
}

type Routes = {
"/": { verb: "get"; };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Loading

```html
page: GET /
```

# Step 0
()=>requestHead()

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Loading

```html
page: GET /
```

# Step 0
()=>requestHead()

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-- page: GET ${$global.url.pathname}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import assert from "assert";

export const steps = [
() => requestHead(),
]

async function requestHead() {
const url = new URL(page.url());
const response = await page.request.fetch(url.href, { method: 'HEAD' });
const headers = response.headers();
assert.equal(response.ok(), true);
assert.match(headers['content-type'], /text\/html/);
assert.equal(await response.body(), '');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig-base.json",
"include": ["src/**/*", ".marko-run/*"],
}
13 changes: 11 additions & 2 deletions packages/run/src/adapter/dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,20 @@ declare global {
export async function createViteDevServer(
config?: InlineConfig,
): Promise<ViteDevServer> {
const devServer = await createServer({
const finalConfig = {
...config,
appType: "custom",
server: { ...config?.server, middlewareMode: true },
});
} satisfies InlineConfig;

const { cors } = finalConfig.server;
if (cors === undefined) {
finalConfig.server.cors = { preflightContinue: true };
} else if (typeof cors === "object") {
cors.preflightContinue ??= true;
}

const devServer = await createServer(finalConfig);

getDevGlobal().addDevServer(devServer);

Expand Down
17 changes: 15 additions & 2 deletions packages/run/src/runtime/internal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
AnyRoute,
Awaitable,
Context,
InputObject,
MultiRouteContext,
Expand Down Expand Up @@ -49,15 +50,15 @@ export function createContext<TRoute extends AnyRoute>(
route: route.path,
serializedGlobals,
}
: {
: ({
request,
url,
platform,
meta: {},
params: {},
route: "",
serializedGlobals,
};
} as unknown as Context<TRoute>);

let input: InputObject | undefined;
return [
Expand Down Expand Up @@ -161,6 +162,18 @@ export function normalize(
return passthrough;
}

export function stripResponseBodySync(response: Response): Response {
return response.body ? new Response(null, response) : response;
}

export function stripResponseBody(
response: Awaitable<Response>,
): Awaitable<Response> {
return "then" in response
? response.then(stripResponseBodySync)
: stripResponseBodySync(response);
}

export function passthrough() {}

export function noContent() {
Expand Down
2 changes: 1 addition & 1 deletion packages/run/src/runtime/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type Awaitable<T> = Promise<T> | T;
export type Awaitable<T> = Promise<T> | T;
type OneOrMany<T> = T | T[];
type NoParams = {};
type AllKeys<T> = T extends T ? keyof T : never;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @marko/run/router
import { NotHandled, NotMatched, createContext } from 'virtual:marko-run/runtime/internal';
import { get1 } from 'virtual:marko-run/__marko-run__route.js';
import { get1, head1 } from 'virtual:marko-run/__marko-run__route.js';
import page500 from './.marko/route.500.marko?marko-server-entry';

const page500ResponseInit = {
Expand All @@ -23,6 +23,12 @@ export function match(method, pathname) {
if (len === 1) return { handler: get1, params: {}, meta: {}, path: '/' }; // /
return null;
}
case 'HEAD':
case 'head': {
const len = pathname.length;
if (len === 1) return { handler: head1, params: {}, meta: {}, path: '/' }; // /
return null;
}
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ import Page from '../src/routes/+page.marko';
### Handler
```js
// virtual:marko-run/__marko-run__route.js
import { pageResponse } from 'virtual:marko-run/runtime/internal';
import { pageResponse, stripResponseBody } from 'virtual:marko-run/runtime/internal';
import page from './.marko/route.marko?marko-server-entry';

export async function get1(context, buildInput) {
export function get1(context, buildInput) {
return pageResponse(page, buildInput());
}

export function head1(context, buildInput) {
return stripResponseBody(get1(context, buildInput));
}
```


Expand Down
Loading

0 comments on commit c03efd5

Please sign in to comment.