rumdl 0.2.63

A fast Markdown linter and formatter written in Rust
Documentation
const EVENT_SCHEMA = Object.freeze({
  cta_select: {
    action: ["open_quickstart", "compare_markdownlint", "open_playground", "install"],
    location: ["hero", "next"],
    referrer: ["direct", "chatgpt", "google", "copilot", "perplexity", "github", "other"],
  },
  command_copy: {
    command: ["uvx_check"],
    result: ["success", "failure"],
    referrer: ["direct", "chatgpt", "google", "copilot", "perplexity", "github", "other"],
  },
  playground_ready: { source: ["default", "shared", "draft"] },
  playground_example: { example: ["common", "headings", "links", "clean"] },
  playground_fix: {
    scope: ["single", "all"],
    outcome: ["clean", "remaining", "unchanged"],
  },
  playground_config: {
    flavor: ["standard", "mkdocs", "mdx", "pandoc", "quarto", "obsidian", "kramdown", "azure_devops", "myst", "hugo", "mdg"],
    disabled: ["0", "1", "2_4", "5_plus"],
    line_length: ["under_80", "80", "81_120", "over_120"],
  },
  playground_share: { result: ["success", "failure", "too_large"] },
  playground_error: { stage: ["load", "lint", "config", "share"] },
});

const RESPONSE_HEADERS = {
  "cache-control": "no-store",
  "content-type": "text/plain; charset=utf-8",
};

function response(status = 204, analytics = "accepted") {
  return new Response(null, {
    status,
    headers: { ...RESPONSE_HEADERS, "x-rumdl-analytics": analytics },
  });
}

function validatePayload(payload) {
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
  const schema = EVENT_SCHEMA[payload.event];
  if (!schema || !payload.properties || typeof payload.properties !== "object") return null;

  const properties = {};
  for (const [key, allowed] of Object.entries(schema)) {
    const value = String(payload.properties[key] ?? "");
    if (!allowed.includes(value)) return null;
    properties[key] = value;
  }
  return { event: payload.event, properties };
}

export async function onRequestPost(context) {
  const { request } = context;
  const url = new URL(request.url);
  const origin = request.headers.get("origin");
  if (origin && origin !== url.origin) return response(403, "rejected-origin");

  const contentType = request.headers.get("content-type") || "";
  if (!contentType.startsWith("application/json")) return response(415, "rejected-type");

  const declaredLength = Number(request.headers.get("content-length") || 0);
  if (declaredLength > 1024) return response(413, "rejected-size");

  let text;
  try {
    text = await request.text();
  } catch {
    return response(400, "rejected-body");
  }
  if (text.length > 1024) return response(413, "rejected-size");

  let event;
  try {
    event = validatePayload(JSON.parse(text));
  } catch {
    return response(400, "rejected-json");
  }
  if (!event) return response(422, "rejected-schema");

  const dataset = context.env.RUMDL_ANALYTICS;
  if (!dataset || typeof dataset.writeDataPoint !== "function") {
    return response(204, "binding-disabled");
  }

  const values = Object.values(event.properties);
  dataset.writeDataPoint({
    indexes: [event.event],
    blobs: [event.event, values[0] || "", values[1] || "", values[2] || ""],
    doubles: [1],
  });
  return response();
}