Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .commands/test-types.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tsc -p tests/plugins/codeGenerator/tsconfig.json
tsc -p tests/plugins/openApiGenerator/tsconfig.json
tsc -p tests/plugins/cacheController/tsconfig.json
tsc -p tests/plugins/static/tsconfig.json
tsc -p tests/plugins/cors/tsconfig.json
# integration
npm -w integration run test:types
# documentation
Expand Down
36 changes: 36 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ export default pipe(
"// @filename: @duplojs/http/openApiGenerator.ts",
`export * from "@v${namedGroups?.version ?? ""}/openApiGenerator";`,

"// @filename: @duplojs/http/static.ts",
`export * from "@v${namedGroups?.version ?? ""}/static";`,

"// @filename: @duplojs/http/cors.ts",
`export * from "@v${namedGroups?.version ?? ""}/cors";`,

"// @filename: @duplojs/http/cacheController.ts",
`export * from "@v${namedGroups?.version ?? ""}/cacheController";`,

"// @filename: index.ts",
"// ---cut---",
],
Expand All @@ -133,6 +142,9 @@ export default pipe(
"@v0/node": ["libs/v0/interfaces/node/index"],
"@v0/codeGenerator": ["libs/v0/plugins/codeGenerator/index"],
"@v0/openApiGenerator": ["libs/v0/plugins/openApiGenerator/index"],
"@v0/static": ["libs/v0/plugins/static/index"],
"@v0/cors": ["libs/v0/plugins/cors/index"],
"@v0/cacheController": ["libs/v0/plugins/cacheController/index"],
},
},
},
Expand Down Expand Up @@ -264,6 +276,18 @@ export default pipe(
text: "Génération OpenAPI",
link: "/fr/v0/guide/plugins/openApiGenerator",
},
{
text: "Créer un point d'entrée static",
link: "/fr/v0/guide/plugins/static",
},
{
text: "Gestion CORS",
link: "/fr/v0/guide/plugins/cors",
},
{
text: "Contrôle du cache",
link: "/fr/v0/guide/plugins/cacheController",
},
],
},
{
Expand Down Expand Up @@ -408,6 +432,18 @@ export default pipe(
text: "OpenAPI generation",
link: "/en/v0/guide/plugins/openApiGenerator",
},
{
text: "Create a static entry point",
link: "/fr/v0/guide/plugins/static",
},
{
text: "CORS Management",
link: "/fr/v0/guide/plugins/cors",
},
{
text: "Cache Control",
link: "/fr/v0/guide/plugins/cacheController",
},
],
},
{
Expand Down
4 changes: 2 additions & 2 deletions docs/en/v0/guide/features/formData.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: "Handle complex FormData payloads."
prev:
text: "OpenAPI generation"
link: "/en/v0/guide/plugins/openApiGenerator"
text: "Cache control"
link: "/en/v0/guide/plugins/cacheController"
next:
text: "Server-Sent Events (SSE)"
link: "/en/v0/guide/features/SSE"
Expand Down
63 changes: 63 additions & 0 deletions docs/en/v0/guide/plugins/cacheController.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
description: "Manage Cache-Control headers."
prev:
text: "CORS handling"
link: "/en/v0/guide/plugins/cors"
next:
text: "Advanced FormData"
link: "/en/v0/guide/features/formData"
---

# Cache control

`@duplojs/http/cacheController` provides `createCacheControllerHooks`, a hook that automatically adds the [`Cache-Control`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) header to matching responses.

The hook only adds the header to `2xx` and `3xx` responses.

## On one route

```ts twoslash {2,12-16}
// @version: 0
<!--@include: @/examples/v0/guide/plugins/cacheController/route.ts-->
```

In this case, the configuration only applies to the `GET /articles` route.

- `public: true` allows shared caching.
- `maxAge: 300` allows a 5-minute cache.
- `staleWhileRevalidate: 60` lets clients serve a slightly stale response while revalidating.

## On all Hub routes

```ts twoslash {2,5-10}
// @version: 0
<!--@include: @/examples/v0/guide/plugins/cacheController/hub.ts-->
```

Here, the hook is added globally with `addRouteHooks`. Every route registered in this `Hub` inherits the same cache policy.

## Config interface

```ts
interface CacheControlDirectives {
maxAge?: number;
sMaxAge?: number;
public?: true;
private?: true | string[];
noCache?: true | string[];
noStore?: true;
noTransform?: true;
mustRevalidate?: true;
proxyRevalidate?: true;
immutable?: true;
staleWhileRevalidate?: number;
staleIfError?: number;
mustUnderstand?: true;
extensions?: Record<string, string>;
}
```

## Useful notes

- `private` and `noCache` can also receive a list of header names.
- `extensions` lets you add custom directives when you need a more specific header.
58 changes: 58 additions & 0 deletions docs/en/v0/guide/plugins/cors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
description: "Handle CORS headers."
prev:
text: "Create a static entry point"
link: "/en/v0/guide/plugins/static"
next:
text: "Cache control"
link: "/en/v0/guide/plugins/cacheController"
---

# CORS handling

`@duplojs/http/cors` automatically adds [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) headers to regular responses and creates an `OPTIONS /*` route to handle [preflight requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#functional_overview).

## Full example

```ts twoslash {2,6-16}
// @version: 0
<!--@include: @/examples/v0/guide/plugins/cors/plugin.ts-->
```

In this example:

- `allowOrigin` restricts which origins are allowed.
- `allowHeaders` declares which [HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers) are accepted during preflight.
- `exposeHeaders` makes selected headers readable by the browser.
- `allowMethods: true` automatically generates `access-control-allow-methods` from the routes registered in the `Hub`.
- `credentials: true` adds `access-control-allow-credentials: true` for [credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS#requests_with_credentials).
- `maxAge` defines the preflight cache duration in seconds.

## Params interface

```ts
interface CorsPluginParams {
readonly allowOrigin?:
| string
| RegExp
| readonly string[]
| ((origin: string) => boolean | Promise<boolean>)
| true;
readonly allowHeaders?: string | readonly string[] | true;
readonly exposeHeaders?: string | readonly string[];
readonly maxAge?: number;
readonly credentials?: boolean;
readonly allowMethods?: RequestMethods | readonly RequestMethods[] | true;
}
```

You must provide at least one option to the plugin.

## Option details

- `allowOrigin` accepts a single origin, a list, a regular expression, a validation function, or `true` to allow all origins.
- `allowHeaders` accepts a string, a list, or `true` for all headers.
- `exposeHeaders` lists the headers the browser can read after the request.
- `allowMethods` accepts one method, a list, or `true` to compute allowed methods automatically per path.
- `credentials` enables cookies and other authentication credentials to be shared.
- `maxAge` controls how long the `OPTIONS` response can be cached.
4 changes: 2 additions & 2 deletions docs/en/v0/guide/plugins/openApiGenerator.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ prev:
text: "Code generation"
link: "/en/v0/guide/plugins/codeGenerator"
next:
text: "Advanced FormData"
link: "/en/v0/guide/features/formData"
text: "Create a static entry point"
link: "/en/v0/guide/plugins/static"
---

# OpenAPI generation
Expand Down
56 changes: 56 additions & 0 deletions docs/en/v0/guide/plugins/static.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
description: "Create a static entry point easily."
prev:
text: "OpenAPI generation"
link: "/en/v0/guide/plugins/openApiGenerator"
next:
text: "CORS handling"
link: "/en/v0/guide/plugins/cors"
---

# Create a static entry point

`@duplojs/http/static` lets you expose a file or a folder in read-only mode.
You can either plug `staticPlugin` directly into the `Hub`, or manually register a route with `makeRouteFile` or `makeRouteFolder`.

## With `staticPlugin`

```ts twoslash {3,9-22}
// @version: 0
<!--@include: @/examples/v0/guide/plugins/static/plugin.ts-->
```

The plugin automatically chooses the right behavior based on the source you pass.

- `source` is a `FileInterface` or a `FolderInterface`.
- `path` exposes a single file on a specific route.
- `prefix` exposes a whole folder under a URL prefix.
- `cacheControlConfig` adds `cache-control` headers.
- `directoryFallBackFile` serves a default file for a folder, for example `index.html`.

## With `makeRouteFile`

```ts twoslash {5-12}
// @version: 0
<!--@include: @/examples/v0/guide/plugins/static/makeRouteFile.ts-->
```

Use this function when you want to register a static route yourself for a single file.

- `source` is the file to serve.
- `path` is the HTTP route to expose.
- `cacheControlConfig` is optional.

## With `makeRouteFolder`

```ts twoslash {5-13}
// @version: 0
<!--@include: @/examples/v0/guide/plugins/static/makeRouteFolder.ts-->
```

This version serves every file in a folder from a shared prefix.

- `source` is the root folder.
- `prefix` is the URL prefix used to resolve files.
- `directoryFallBackFile` serves a default file for a folder, for example `index.html`.
- `cacheControlConfig` is optional.
10 changes: 10 additions & 0 deletions docs/examples/v0/guide/plugins/cacheController/hub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createHub } from "@duplojs/http";
import { createCacheControllerHooks } from "@duplojs/http/cacheController";

export const hub = createHub({ environment: "DEV" })
.addRouteHooks(
createCacheControllerHooks({
private: true,
noCache: true,
}),
);
27 changes: 27 additions & 0 deletions docs/examples/v0/guide/plugins/cacheController/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ResponseContract, useRouteBuilder } from "@duplojs/http";
import { createCacheControllerHooks } from "@duplojs/http/cacheController";
import { DPE } from "@duplojs/utils";

const articleSchema = DPE.object({
id: DPE.number(),
title: DPE.string(),
});

useRouteBuilder("GET", "/articles", {
hooks: [
createCacheControllerHooks({
public: true,
maxAge: 300,
staleWhileRevalidate: 60,
}),
],
})
.handler(
ResponseContract.ok("articles.list", articleSchema.array()),
(__, { response }) => response("articles.list", [
{
id: 1,
title: "Hello",
},
]),
);
17 changes: 17 additions & 0 deletions docs/examples/v0/guide/plugins/cors/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createHub } from "@duplojs/http";
import { corsPlugin } from "@duplojs/http/cors";

export const hub = createHub({ environment: "DEV" })
.plug(
corsPlugin({
allowOrigin: [
"https://app.example.com",
"https://admin.example.com",
],
allowHeaders: ["content-type", "authorization"],
exposeHeaders: ["x-request-id", "x-rate-limit-remaining"],
allowMethods: true,
credentials: true,
maxAge: 600,
}),
);
15 changes: 15 additions & 0 deletions docs/examples/v0/guide/plugins/static/makeRouteFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { createHub } from "@duplojs/http";
import { SF } from "@duplojs/server-utils";
import { makeRouteFile } from "@duplojs/http/static";

const faviconRoute = makeRouteFile({
source: SF.createFileInterface("./public/favicon.ico"),
path: "/favicon.ico",
cacheControlConfig: {
maxAge: 86400,
public: true,
},
});

export const hub = createHub({ environment: "DEV" })
.register(faviconRoute);
16 changes: 16 additions & 0 deletions docs/examples/v0/guide/plugins/static/makeRouteFolder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createHub } from "@duplojs/http";
import { SF } from "@duplojs/server-utils";
import { makeRouteFolder } from "@duplojs/http/static";

const docsRoute = makeRouteFolder({
source: SF.createFolderInterface("./public/docs"),
prefix: "/docs",
directoryFallBackFile: "index.html",
cacheControlConfig: {
maxAge: 600,
public: true,
},
});

export const hub = createHub({ environment: "DEV" })
.register(docsRoute);
22 changes: 22 additions & 0 deletions docs/examples/v0/guide/plugins/static/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createHub } from "@duplojs/http";
import { SF } from "@duplojs/server-utils";
import { staticPlugin } from "@duplojs/http/static";

const logoFile = SF.createFileInterface("./public/logo.svg");
const assetsFolder = SF.createFolderInterface("./public");

export const hub = createHub({ environment: "DEV" })
.plug(
staticPlugin(logoFile, {
path: "/logo",
cacheControlConfig: {
maxAge: 3600,
public: true,
},
}),
)
.plug(
staticPlugin(assetsFolder, {
prefix: "/assets",
}),
);
4 changes: 2 additions & 2 deletions docs/fr/v0/guide/features/formData.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: "Gérer des FormData à structure complexe."
prev:
text: "Génération OpenAPI"
link: "/fr/v0/guide/plugins/openApiGenerator"
text: "Contrôle du cache"
link: "/fr/v0/guide/plugins/cacheController"
next:
text: "Server-Sent Events (SSE)"
link: "/fr/v0/guide/features/SSE"
Expand Down
Loading
Loading