Skip to content
Open
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
19 changes: 18 additions & 1 deletion .prettierrc.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// @ts-check
/** @type {import("prettier").Config} */
export default {
plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
plugins: [
"prettier-plugin-astro",
"prettier-plugin-tailwindcss",
"./plugins/prettier-plugin-mdx-inline/index.mjs",
],
useTabs: true,
overrides: [
{
Expand All @@ -10,5 +14,18 @@ export default {
parser: "astro",
},
},
// Prettier's MDX formatter wraps inline JSX elements (like <code> and
// <GlossaryTooltip>) onto new lines, which causes MDX v2+ to inject <p>
// tags inside them — breaking the rendered HTML. This custom plugin
// prevents that by keeping configured elements on a single line.
// This may become unnecessary once prettier adds MDX v3 support:
// https://github.com/prettier/prettier/issues/12209
{
files: "*.mdx",
options: {
parser: "mdx-inline",
mdxInlineElements: "code,GlossaryTooltip",
},
},
],
};
188 changes: 188 additions & 0 deletions plugins/prettier-plugin-mdx-inline/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/**
* prettier-plugin-mdx-inline
*
* Prevents prettier from reformatting specific JSX elements in MDX files.
*
* Problem: Prettier's MDX formatter treats standalone JSX elements (like
* `<code>`) as block-level and wraps their children
* onto new lines when they exceed printWidth. MDX v2+ then interprets those
* newlines as markdown paragraph boundaries, injecting <p> tags inside inline
* elements — producing broken HTML like `<code><p>...</p></code>`.
*
* Solution: This plugin intercepts the parsed MDX AST and converts matching
* JSX nodes to opaque HTML nodes that prettier outputs verbatim. It also
* collapses any existing multi-line formatting back to a single line.
*
* Configuration (.prettierrc.mjs):
*
* export default {
* plugins: ["./prettier-plugin-mdx-inline/index.mjs"],
* overrides: [{
* files: "*.mdx",
* options: { parser: "mdx-inline" },
* }],
* };
*
* You must specify which elements to protect via `mdxInlineElements`:
*
* overrides: [{
* files: "*.mdx",
* options: {
* parser: "mdx-inline",
* mdxInlineElements: "code,GlossaryTooltip",
* },
* }],
*/

/**
* Extract the element name from the start of a JSX string.
* e.g., "<code>" → "code", "<GlossaryTooltip term="x">" → "GlossaryTooltip"
*/
function getElementName(value) {
const match = value.trim().match(/^<([a-zA-Z][a-zA-Z0-9]*)/);
return match ? match[1] : null;
}

/**
* Collapse a multi-line JSX element value onto a single line.
*
* Handles:
* - {" "} spacer expressions inserted by prettier
* - Newlines with surrounding whitespace from indentation
* - Multiple consecutive spaces from collapsing
* - Trailing content after the closing tag (e.g., in list items)
*
* Preserves:
* - Attribute values (strings in the opening tag)
* - Self-closing tags within content (e.g., <Type text="..." />)
*/
function collapseInlineJsx(value) {
// Remove {" "} spacers — these are prettier artifacts for preserving spaces
let result = value.replace(/\{" "\}/g, " ");

const elementName = getElementName(result);
if (!elementName) return result;

// Find the end of the opening tag by tracking string context.
// We need to skip over attribute values that may contain '>' characters.
let inString = false;
let stringChar = "";
let openTagEnd = -1;

for (let i = 0; i < result.length; i++) {
const ch = result[i];
if (inString) {
if (ch === stringChar && result[i - 1] !== "\\") {
inString = false;
}
} else if (ch === '"' || ch === "'") {
inString = true;
stringChar = ch;
} else if (ch === ">") {
openTagEnd = i;
break;
}
}

if (openTagEnd === -1) return result;

// Find the matching closing tag — it may not be at the very end of the
// value if there is trailing content (e.g., in a list item where the
// description text follows the </code> within the same JSX node).
const closeTag = `</${elementName}>`;
const closeTagIndex = result.indexOf(closeTag, openTagEnd);
if (closeTagIndex === -1) return result;

const openTag = result.substring(0, openTagEnd + 1);
const content = result.substring(openTagEnd + 1, closeTagIndex);
const trailing = result.substring(closeTagIndex + closeTag.length);

// Collapse whitespace in the content between tags
const collapsed = content
.replace(/\n\s*/g, " ") // newlines + indentation → single space
.replace(/\s{2,}/g, " ") // multiple spaces → single space
.trim();

return openTag + collapsed + closeTag + trailing;
}

/**
* Parse the configured element list from the options.
*/
function getInlineElements(options) {
const configured = options.mdxInlineElements;
if (!configured || typeof configured !== "string") {
return [];
}
return configured
.split(",")
.map((s) => s.trim())
.filter(Boolean);
}

/**
* Check if a JSX node value starts with one of the inline element names.
*/
function isInlineElement(value, elements) {
const trimmed = value.trim();
for (const el of elements) {
if (trimmed.startsWith(`<${el}>`) || trimmed.startsWith(`<${el} `)) {
return true;
}
}
return false;
}

/**
* Walk the AST and convert matching JSX nodes to HTML nodes.
*/
function transformAst(ast, elements) {
function walk(node) {
if (node.type === "jsx" && isInlineElement(node.value, elements)) {
// Convert to HTML type so prettier outputs it verbatim
node.type = "html";
// Collapse multi-line content back to a single line
node.value = collapseInlineJsx(node.value);
}
if (node.children) {
node.children.forEach(walk);
}
}
walk(ast);
return ast;
}

/** @type {import("prettier").Plugin} */
const plugin = {
options: {
mdxInlineElements: {
type: "string",
category: "MDX",
default: "",
description:
"Comma-separated list of JSX element names that should not be reformatted.",
},
},

parsers: {
"mdx-inline": {
async parse(text, options) {
// Delegate to the built-in MDX parser via prettier's stable plugin export
const { parsers } = await import("prettier/plugins/markdown");
const ast = await parsers.mdx.parse(text, options);

// Transform matching JSX nodes to prevent reformatting
const elements = getInlineElements(options);
transformAst(ast, elements);

return ast;
},
// Use the built-in mdast printer — we only modify the AST
astFormat: "mdast",
locStart: (node) => node.position?.start?.offset ?? 0,
locEnd: (node) => node.position?.end?.offset ?? 0,
},
},
};

export default plugin;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
DashButton,
} from "~/components";

{/* prettier-ignore */}
<GlossaryTooltip term="JSON web token (JWT)">JSON web tokens (JWT)</GlossaryTooltip> are often used as part of an authentication component on many web applications today. Since JWTs are crucial to identifying users and their access, ensuring the token’s integrity is important.

API Shield’s JWT validation stops JWT replay attacks and JWT tampering by cryptographically verifying incoming JWTs before they are passed to your API origin. JWT validation will also stop requests with expired tokens or tokens that are not yet valid.
Expand Down Expand Up @@ -126,7 +125,7 @@ API Shield will verify JSON Web Tokens regardless of whether or not they have th

### Ignore `OPTIONS` pre-flight CORS requests

Due to cross-origin resource sharing (CORS) security, web browsers will send "pre-flight" requests using the `OPTIONS` verb to API endpoints before sending a `GET` (or other verb) request. By definition, `OPTIONS` requests do not include headers or cookies and are anonymous.
Due to cross-origin resource sharing (CORS) security, web browsers will send "pre-flight" requests using the `OPTIONS` verb to API endpoints before sending a `GET` (or other verb) request. By definition, `OPTIONS` requests do not include headers or cookies and are anonymous.

If you expect web browsers to be valid clients of your API, and to prevent blocking `OPTIONS` requests from those browsers, Cloudflare recommends adding `or http.request.method eq "OPTIONS"` to your JWT validation rules.

Expand All @@ -137,6 +136,7 @@ If you expect web browsers to be valid clients of your API, and to prevent block
JWT validation is available for all API Shield customers. Enterprise customers who have not purchased API Shield can preview [API Shield as a non-contract service](https://dash.cloudflare.com/?to=/:account/:zone/security/api-shield) in the Cloudflare dashboard or by contacting your account team.

---

## Limitations

Currently, the following known limitations exist:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ You can migrate to Schema validation 2.0 manually by uploading your schemas to t

## Process

{/* prettier-ignore */}
<GlossaryTooltip term="API endpoint">Endpoints</GlossaryTooltip> must be added to [Endpoint Management](/api-shield/management-and-monitoring/endpoint-management/) for Schema validation to protect them. Uploading a schema via the Cloudflare dashboard will automatically add endpoints, or you can manually add them from [API Discovery](/api-shield/security/api-discovery/).

If you are uploading a schema via the API or Terraform, you must parse the schema and add your endpoints manually.
Expand Down Expand Up @@ -434,12 +433,12 @@ Schema validation inspects request bodies up to a maximum size that depends on y

The default body size limits are:

| Plan | Default body size limit |
| --- | --- |
| Free | 1 KB |
| Pro | 8 KB |
| Business | 8 KB |
| Enterprise | 128 KB |
| Plan | Default body size limit |
| ---------- | ----------------------- |
| Free | 1 KB |
| Pro | 8 KB |
| Business | 8 KB |
| Enterprise | 128 KB |

:::note
This limit is separate from the [WAF maximum body inspection size](/waf/managed-rules/#maximum-body-size), which controls how much of the request payload the WAF scans. Increasing one does not affect the other.
Expand Down
25 changes: 5 additions & 20 deletions src/content/docs/durable-objects/api/base.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ class MyDurableObject(DurableObject):

### `fetch`

- <code>
fetch(request <Type text="Request" />)
</code>
- <code>fetch(request <Type text="Request" />)</code>
: <Type text="Response" /> | <Type text="Promise<Response>" />- Takes an HTTP
[Request](https://developers.cloudflare.com/workers/runtime-apis/request/) and
returns an HTTP
Expand Down Expand Up @@ -93,16 +91,11 @@ export class MyDurableObject extends DurableObject<Env> {

### `alarm`

- <code>
alarm(alarmInfo? <Type text="AlarmInvocationInfo" />)
</code>
- <code>alarm(alarmInfo? <Type text="AlarmInvocationInfo" />)</code>
: <Type text="void" /> | <Type text="Promise<void>" />
- Called by the system when a scheduled alarm time is reached.

- The `alarm()` handler has guaranteed at-least-once execution and will be retried upon failure using exponential backoff, starting at two second delays for up to six retries. Retries will be performed if the method fails with an uncaught exception.

- This method can be `async`.

- Refer to [Alarms](/durable-objects/api/alarms/) for more information.

#### Parameters
Expand Down Expand Up @@ -132,10 +125,7 @@ export class MyDurableObject extends DurableObject<Env> {

### `webSocketMessage`

- <code>
webSocketMessage(ws <Type text="WebSocket" />, message{" "}
<Type text="string | ArrayBuffer" />)
</code>
- <code>webSocketMessage(ws <Type text="WebSocket" />, message <Type text="string | ArrayBuffer" />)</code>
: <Type text="void" /> | <Type text="Promise<void>" />- Called by the system
when an accepted WebSocket receives a message. - This method is not called for
WebSocket control frames. The system will respond to an incoming [WebSocket
Expand Down Expand Up @@ -170,10 +160,7 @@ export class MyDurableObject extends DurableObject<Env> {

### `webSocketClose`

- <code>
webSocketClose(ws <Type text="WebSocket" />, code <Type text="number" />,
reason <Type text="string" />, wasClean <Type text="boolean" />)
</code>
- <code>webSocketClose(ws <Type text="WebSocket" />, code <Type text="number" />, reason <Type text="string" />, wasClean <Type text="boolean" />)</code>
: <Type text="void" /> | <Type text="Promise<void>" />- Called by the system
when a WebSocket connection is closed.
- With the [`web_socket_auto_reply_to_close`](/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the runtime automatically sends a reciprocal Close frame and transitions `readyState` to `CLOSED` before this handler is called. You do not need to call `ws.close()` — but doing so is safe (the call is silently ignored).
Expand Down Expand Up @@ -209,9 +196,7 @@ export class MyDurableObject extends DurableObject<Env> {

### `webSocketError`

- <code>
webSocketError(ws <Type text="WebSocket" />, error <Type text="unknown" />)
</code>
- <code>webSocketError(ws <Type text="WebSocket" />, error <Type text="unknown" />)</code>
: <Type text="void" /> | <Type text="Promise<void>" />- Called by the system
when a non-disconnection error occurs on a WebSocket connection. - This method
can be `async`.
Expand Down
Loading
Loading