@entreprenoid/analytics 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/{chunk-IHZCOC2U.js → chunk-IOMJAF33.js} +3 -3
- package/dist/{chunk-IHZCOC2U.js.map → chunk-IOMJAF33.js.map} +1 -1
- package/dist/{chunk-5NZNPRMN.js → chunk-P3NEQOKC.js} +3 -3
- package/dist/{chunk-5NZNPRMN.js.map → chunk-P3NEQOKC.js.map} +1 -1
- package/dist/{chunk-EVNUKN5A.js → chunk-SJTHL7HF.js} +3 -3
- package/dist/{chunk-EVNUKN5A.js.map → chunk-SJTHL7HF.js.map} +1 -1
- package/dist/{chunk-GKFYGAKF.js → chunk-WNCAN2XS.js} +3 -3
- package/dist/{chunk-GKFYGAKF.js.map → chunk-WNCAN2XS.js.map} +1 -1
- package/dist/express.cjs +1 -1
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/next.cjs +1 -1
- package/dist/next.cjs.map +1 -1
- package/dist/next.js +2 -2
- package/dist/runtime.d.ts +1 -1
- package/dist/web.cjs +1 -1
- package/dist/web.cjs.map +1 -1
- package/dist/web.js +2 -2
- package/package.json +3 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/serve/discovery.ts","../src/express.ts"],"names":[],"mappings":"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","file":"chunk-5NZNPRMN.js","sourcesContent":["import type { Twin, TwinOptions } from \"./twin.js\";\n\n/**\n * The three files that let an agent find a site's content without crawling it\n * blindly: `/llms.txt`, `/llms-full.txt` and `/install.md`.\n *\n * ── Why generate them rather than let customers write them ───────────────────\n * A hand-written index is wrong the first time a page is added, and an index\n * that lists pages which no longer exist is worse than none -- an agent spends\n * its budget on 404s and concludes the site is broken. These are derived from\n * the same manifest that serves the twins, so they cannot disagree with what is\n * actually servable.\n *\n * `llms.txt` is the convention proposed by Answer.AI in 2024: a markdown index\n * of a site's high-value content. `llms-full.txt` is the same set with the\n * bodies inline, for an agent that would rather make one request than a hundred.\n */\n\nexport interface TwinEntry {\n /** The page path, e.g. `/docs/intro`. */\n path: string;\n /** One line. This is what an agent reads when deciding whether to fetch. */\n summary?: string;\n title?: string;\n}\n\nexport interface DiscoveryOptions {\n siteName: string;\n /** One or two sentences. Appears under the heading in `llms.txt`. */\n description?: string;\n origin?: string;\n entries: readonly TwinEntry[];\n}\n\nfunction titleFor(entry: TwinEntry): string {\n if (entry.title) return entry.title;\n if (entry.path === \"/\") return \"Home\";\n const last = entry.path.split(\"/\").filter(Boolean).pop() ?? entry.path;\n return last.replace(/[-_]/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nconst twinPath = (path: string) => (path === \"/\" ? \"/index.md\" : `${path}.md`);\n\n/** The index: every page, one line each. */\nexport function renderLlmsTxt(options: DiscoveryOptions): string {\n const origin = options.origin ?? \"\";\n const lines: string[] = [`# ${options.siteName}`, \"\"];\n\n if (options.description) {\n for (const line of options.description.split(\"\\n\")) lines.push(`> ${line}`);\n lines.push(\"\");\n }\n\n lines.push(\n \"Every page listed here is also available as markdown: append `.md` to the path\",\n \"(`/` becomes `/index.md`), or send `Accept: text/markdown`.\",\n \"\",\n \"## Pages\",\n \"\",\n );\n\n for (const entry of options.entries) {\n const href = `${origin}${twinPath(entry.path)}`;\n lines.push(`- [${titleFor(entry)}](${href})${entry.summary ? `: ${entry.summary}` : \"\"}`);\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** The bulk file: one request instead of a hundred. */\nexport async function renderLlmsFullTxt(\n options: DiscoveryOptions,\n resolve: TwinOptions[\"resolve\"],\n): Promise<string> {\n const parts: string[] = [`# ${options.siteName}`, \"\"];\n if (options.description) {\n for (const line of options.description.split(\"\\n\")) parts.push(`> ${line}`);\n parts.push(\"\");\n }\n\n for (const entry of options.entries) {\n let twin: Twin | null | undefined;\n try {\n twin = await resolve(entry.path);\n } catch {\n // ⚠️ A resolver that throws on one page must not empty the whole file.\n // Skipping the page is a smaller lie than serving an index of nothing.\n continue;\n }\n if (!twin) continue;\n parts.push(`---`, ``, `## ${titleFor(entry)}`, ``, `Source: ${entry.path}`, ``, twin.body.trim(), ``);\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * The install guide, written for an AI coding agent rather than a person.\n *\n * ⚠️ This is the install path for a product whose users install things by\n * asking a model. It is deliberately imperative, names the exact failure modes,\n * and tells the agent to STOP rather than guess at a missing value -- because an\n * agent that guesses a credential writes a broken config that looks finished.\n */\nexport function renderInstallMd(options: { packageName?: string } = {}): string {\n const pkg = options.packageName ?? \"@entreprenoid/analytics\";\n return `# entreprenoid install guide (for AI coding agents)\n\nYou are installing **entreprenoid** — analytics for the agentic web. It records every\nrequest a site serves, especially from AI agents, and can serve those agents a clean\nmarkdown twin of each page. Collection is fire-and-forget: it never blocks and never\nfails the user's responses.\n\n## Before you start\n\nEvery environment variable this package reads:\n\n| Variable | Required | What it is |\n| --- | --- | --- |\n| \\`ENTREPRENOID_INGEST_URL\\` | **yes** | The ingest endpoint. ⚠️ It must end in \\`/v1/events\\` — the SDK POSTs this URL verbatim and appends nothing, so a bare origin sends every batch to \\`POST /\\`, which is rejected. The SDK fails open, so you would see nothing at all. |\n| \\`ENTREPRENOID_SERVER_KEY\\` | **yes** | The site's key, starting with \\`ep_live_server_\\`. |\n| \\`ENTREPRENOID_SITE_ID\\` | no | The dashboard shows it beside the key. Safe to set, safe to omit: the server resolves the site from the credential and ignores this value. |\n| \\`ENTREPRENOID_ENABLED\\` | no | \\`false\\` makes collection a transparent no-op without removing any code. Useful per environment. |\n| \\`ENTREPRENOID_DEBUG\\` | no | \\`true\\` prints one startup line saying whether collection is on, and why not if it is off. |\n\n**If either required value is missing, STOP and ask.** Do not invent them, and do not\nleave a placeholder — a config that looks complete and is not is worse than no config.\n\nSafety rules that apply throughout:\n\n- **Never hardcode the key**, never print it, never commit it. Environment variables only.\n- Before writing an env file, make sure it is gitignored.\n- ⚠️ **Never prefix the key with \\`NEXT_PUBLIC_\\`, \\`VITE_\\`, \\`PUBLIC_\\` or \\`REACT_APP_\\`.**\n Those prefixes inline a value into client-side JavaScript, which publishes the key to\n everyone who loads the page. The SDK refuses to start if it finds one.\n- Only touch the files this install needs.\n\n## Step 1 — install the package\n\nDetect the package manager from the lockfile:\n\n| Lockfile | Command |\n| --- | --- |\n| \\`pnpm-lock.yaml\\` | \\`pnpm add ${pkg}\\` |\n| \\`yarn.lock\\` | \\`yarn add ${pkg}\\` |\n| \\`bun.lock\\` | \\`bun add ${pkg}\\` |\n| \\`package-lock.json\\` or none | \\`npm install ${pkg}\\` |\n\n## Step 2 — wire up the collector (pick exactly ONE)\n\n### Express\n\n\\`\\`\\`ts\nimport { entreprenoid } from \"${pkg}/express\";\n\napp.use(entreprenoid());\n\\`\\`\\`\n\nAdd it **before** your routes so it observes all of them.\n\n### Next.js (App Router, 14+)\n\nUse the **Next adapter**, not the web one. In \\`proxy.ts\\` at the project root\n(\\`middleware.ts\\` on Next 15 and earlier — same file, renamed in Next 16):\n\n\\`\\`\\`ts\nimport { after } from \"next/server\";\nimport { proxy } from \"${pkg}/next\";\n\nexport default proxy({ after });\n\nexport const config = {\n matcher: [\"/((?!_next/static|_next/image|favicon.ico).*)\"],\n};\n\\`\\`\\`\n\n⚠️ **Do not use \\`${pkg}/web\\` in a Next proxy.** It runs and it lies. A proxy\nexecutes *before* the route and hands control onward with a sentinel response — status\n200, no real content type — so the web adapter would record a **measured 200 for every\nrequest**, including the ones your routes render as 404 or 500.\n\nThe Next adapter emits only what a proxy can actually know, and **omits the response\nentirely** rather than guessing at it. Your dashboard will show those requests with no\nstatus, which is the truth: nothing observed one.\n\n⚠️ **Pass \\`after\\`.** Without it the collector relies on its own timer, and a serverless\ninvocation can be frozen before that timer fires — events are simply lost, silently.\n\n### Web-standard runtimes (Cloudflare Workers, Deno, Bun, Hono)\n\n\\`\\`\\`ts\nimport { observe } from \"${pkg}/web\";\n\nexport default {\n fetch: observe(handler, { waitUntil: (p) => ctx.waitUntil(p) }),\n};\n\\`\\`\\`\n\nPass \\`waitUntil\\` where the runtime offers one, or a serverless invocation can be\nfrozen before the events are sent.\n\n## Step 3 — optional: serve a markdown twin\n\nAgents pay for every token they read, and most of a modern page is markup they do not\nwant. Supply a resolver and the same middleware will serve clean markdown when a client\nasks for it:\n\n\\`\\`\\`ts\napp.use(entreprenoid({\n twin: { resolve: (path) => markdownFor(path) },\n}));\n\\`\\`\\`\n\n⚠️ **A browser never receives markdown.** The twin is served only when the path ends in\n\\`.md\\` or the \\`Accept\\` header explicitly prefers \\`text/markdown\\` — never based on the\nUser-Agent, which would be cloaking and would break shared caching.\n\n## Step 4 — ⚠️ look at the routes before you go live\n\n**Do not skip this one.** The path is sent as it arrives. The query string is dropped\nbefore anything parses it, and the \\`Referer\\` is reduced to an origin — but the path\nitself is data, and on a lot of sites the path carries secrets:\n\n\\`\\`\\`\n/reveal/<single-use-token> /join/<invite-code>\n/confirm/<token> /upload/<ticket>\n\\`\\`\\`\n\nRead the project's routes. For each one, decide:\n\n\\`\\`\\`ts\napp.use(entreprenoid({\n // Collapse identifiers so analytics never sees a per-user value, and so one\n // route does not become ten thousand rows.\n routeTemplate: (path) => path.replace(/\\\\/\\\\d+(?=\\\\/|$)/g, \"/:id\"),\n\n // Replace any segment that is a token, code or personal identifier.\n redactPatterns: [/^[A-Za-z0-9_-]{20,}$/],\n\n // Traffic you do not want counted: health checks, your own office, previews.\n isInternal: (req) => req.path.startsWith(\"/_health\"),\n}));\n\\`\\`\\`\n\n⚠️ **A default backstop already runs, and you should not rely on it.** Segments that\nlook like credentials — uuids, cuids, JWTs, long hex, dense mixed-case strings — are\nreplaced with \\`[redacted]\\` before the event is sent, and the event records that it\nhappened. It cannot catch a short token like \\`/j/aB3xK9\\`, and it does not know which of\nthis project's ids are sensitive. **Only the routes tell you that.** Set\n\\`redactHighEntropyPaths: false\\` to turn the backstop off; that never disables\n\\`redactPatterns\\`, which are yours.\n\nIf you are unsure whether a path segment is a secret, treat it as one and say so in your\nsummary to the user.\n\n## Step 5 — verify\n\nStart the app and make one request. Within a few seconds the dashboard should show it.\nIf nothing arrives:\n\n- check the key is set in the server's environment, not the client's\n- check \\`ENTREPRENOID_INGEST_URL\\` ends in \\`/v1/events\\`\n- set \\`ENTREPRENOID_DEBUG=true\\` and read the startup line\n\n**Do not add retry logic, queues or error handling around the SDK.** It already buffers,\nretries with backoff, and fails open. Wrapping it in a try/catch is harmless; awaiting it\nis not, and would put analytics on your critical path.\n`;\n}\n","import type { RequestEvent } from \"@entreprenoid/event-schema\";\nimport { createCollector, type Collector } from \"./core/collector.js\";\nimport { resolveConfig, type EntreprenoidConfig } from \"./core/config.js\";\nimport { safe } from \"./core/safe.js\";\nimport { normalisePath, observeRequest } from \"./observe/request.js\";\nimport { observeResponse } from \"./observe/response.js\";\nimport { SDK_NAME, SDK_VERSION, newEventId, runtimeName } from \"./runtime.js\";\nimport {\n renderInstallMd,\n renderLlmsFullTxt,\n renderLlmsTxt,\n} from \"./serve/discovery.js\";\nimport {\n advertiseHeader,\n buildTwinResponse,\n decideTwin,\n DISCOVERY_PATHS,\n type TwinOptions,\n} from \"./serve/twin.js\";\n\n/**\n * Express middleware.\n *\n * app.use(entreprenoid());\n *\n * ── How it observes a completed response without touching it ─────────────────\n * By listening, not by wrapping. `res` is a writable stream and the tempting\n * approach -- monkey-patching `res.write` and `res.end` to inspect what goes\n * out -- is wrong three ways: it adds a function call to every chunk, it holds\n * the bytes in memory, and any mistake in re-emitting them corrupts the\n * customer's response. Everything we need is already in the headers by the time\n * the response finishes.\n *\n * ⚠️ **`finish` and `close`, with a latch.** `finish` fires when the response\n * completed. `close` fires when the socket went away, which for an aborted\n * request is the ONLY event that fires -- and an aborted request is exactly the\n * kind an AI client makes when it has read enough. Listening to `finish` alone\n * silently loses them. Both are registered and a latch makes sure one event is\n * recorded, not two.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- these are Express's\n types without taking a dependency on Express. The adapter must compile for a\n customer who has express installed and for one who does not. */\ntype Req = {\n method?: string;\n originalUrl?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n protocol?: string;\n secure?: boolean;\n [key: string]: any;\n};\ntype Res = {\n statusCode: number;\n writableFinished?: boolean;\n getHeader(name: string): number | string | string[] | undefined;\n once(event: string, listener: () => void): unknown;\n [key: string]: any;\n};\ntype Next = (error?: unknown) => void;\n\nexport interface ExpressOptions extends EntreprenoidConfig {\n collector?: Collector;\n /**\n * Serve a markdown twin. Omit it and this middleware is observation-only,\n * byte-for-byte as before -- the serve half is opt-in, because it is the half\n * that can change what a visitor receives.\n */\n twin?: TwinOptions;\n}\n\n\nfunction header(req: Req, name: string): string | undefined {\n const value = req.headers[name];\n if (Array.isArray(value)) return value[0];\n return value;\n}\n\nfunction headerString(res: Res, name: string): string | undefined {\n const value = res.getHeader(name);\n if (value === undefined || value === null) return undefined;\n return Array.isArray(value) ? value[0] : String(value);\n}\n\nexport function entreprenoid(options: ExpressOptions = {}) {\n const config = resolveConfig(options);\n const collector = options.collector ?? createCollector(config);\n\n const twin = options.twin;\n\n return function entreprenoidMiddleware(req: Req, res: Res, next: Next): void {\n if (config.disabled && !twin) {\n next();\n return;\n }\n\n const startedAt = Date.now();\n const startedHr = process.hrtime.bigint();\n let recorded = false;\n let served: RequestEvent[\"serve\"];\n\n const record = (): void => {\n if (recorded) return;\n recorded = true;\n\n safe(() => {\n const observed = observeRequest(\n {\n method: req.method ?? \"GET\",\n url: req.originalUrl ?? req.url ?? \"/\",\n host: header(req, \"host\"),\n protocol: req.secure || req.protocol === \"https\" ? \"https\" : \"http\",\n userAgent: header(req, \"user-agent\"),\n referer: header(req, \"referer\") ?? header(req, \"referrer\"),\n },\n config,\n );\n\n // ⚠️ If the response did not finish, we did not measure it. Reporting a\n // status we never saw the client receive would put a guess in the same\n // column as a measurement, which is the one thing this product must not\n // do. An aborted request is recorded with `observation: \"unknown\"` --\n // still counted, honestly labelled.\n const finished = res.writableFinished !== false;\n const latencyMs = Number(process.hrtime.bigint() - startedHr) / 1e6;\n\n const event: RequestEvent = {\n ...observed,\n eventId: newEventId(),\n siteId: config.siteId,\n observedAt: new Date(startedAt).toISOString(),\n response: finished\n ? observeResponse({\n status: res.statusCode,\n contentType: headerString(res, \"content-type\"),\n contentLength: headerString(res, \"content-length\"),\n latencyMs,\n observation: \"measured\",\n })\n : observeResponse({ latencyMs, observation: \"unknown\" }),\n ...(served ? { serve: served } : {}),\n sdk: {\n name: SDK_NAME,\n version: SDK_VERSION,\n adapter: \"express\",\n runtime: runtimeName(),\n },\n };\n\n collector.record(event);\n });\n };\n\n // Registered before next(), so a synchronous handler that responds\n // immediately is still observed.\n safe(() => {\n res.once(\"finish\", record);\n res.once(\"close\", record);\n });\n\n if (!twin) {\n next();\n return;\n }\n\n const path = normalisePath(req.originalUrl ?? req.url ?? \"/\", config.redactPatterns);\n\n // ── Discovery, before anything else ──────────────────────────────────────\n // These are the three files that let an agent find the content at all, and\n // they are cheap: one is a constant, two come from the manifest already in\n // memory.\n if (twin.discovery && DISCOVERY_PATHS.includes(path as never)) {\n const method = (req.method ?? \"GET\").toUpperCase();\n if (method === \"GET\" || method === \"HEAD\") {\n void (async () => {\n try {\n const body =\n path === \"/install.md\"\n ? renderInstallMd()\n : path === \"/llms.txt\"\n ? renderLlmsTxt(twin.discovery!)\n : await renderLlmsFullTxt(twin.discovery!, twin.resolve);\n res.setHeader(\"content-type\", \"text/markdown; charset=utf-8\");\n res.setHeader(\"cache-control\", twin.cacheControl ?? \"public, max-age=3600, s-maxage=86400\");\n res.statusCode = 200;\n served = { decision: \"served\", reason: \"md_path\", format: \"text/markdown; charset=utf-8\" };\n if (method === \"HEAD\") res.end();\n else res.end(body);\n } catch {\n // Their site must still work.\n served = { decision: \"error\", reason: \"resolver_error\" };\n if (!res.headersSent) next();\n }\n })();\n return;\n }\n }\n\n // ── The serve half ───────────────────────────────────────────────────────\n // ⚠️ Everything below can change what the visitor receives, so every exit\n // leads to `next()` unless a twin was actually written. A throw, a rejected\n // promise, a missing twin: all fall through to the customer's own handler\n // with the response untouched.\n const decision = decideTwin({\n method: req.method ?? \"GET\",\n path,\n accept: header(req, \"accept\"),\n });\n\n if (decision.action === \"pass\") {\n if (twin.advertise !== false) {\n void Promise.resolve()\n .then(() => twin.resolve(path))\n .then((found) => {\n if (found && !res.headersSent) {\n safe(() => res.setHeader(\"link\", advertiseHeader(path)));\n served = { decision: \"advertised\", reason: \"no_twin\" };\n }\n })\n .catch(() => undefined)\n .finally(() => next());\n return;\n }\n served = { decision: \"fell_through\", reason: decision.reason };\n next();\n return;\n }\n\n void Promise.resolve()\n .then(() => twin.resolve(decision.lookupPath))\n .then((found) => {\n if (!found) {\n served = { decision: \"fell_through\", reason: \"no_twin\" };\n next();\n return;\n }\n const built = buildTwinResponse(found, decision, twin);\n served = {\n decision: \"served\",\n reason: decision.reason,\n format: built.headers[\"content-type\"] ?? \"text/markdown\",\n };\n for (const [name, value] of Object.entries(built.headers)) {\n res.setHeader(name, value);\n }\n res.statusCode = built.status;\n // HEAD asks for the headers of what a GET would return, and nothing\n // else. Writing a body to it is a protocol violation some clients\n // handle by hanging.\n if ((req.method ?? \"GET\").toUpperCase() === \"HEAD\") res.end();\n else res.end(built.body);\n })\n .catch(() => {\n // The customer's resolver threw. Their site must still work.\n served = { decision: \"error\", reason: \"resolver_error\" };\n if (!res.headersSent) next();\n });\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/serve/discovery.ts","../src/express.ts"],"names":[],"mappings":"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","file":"chunk-P3NEQOKC.js","sourcesContent":["import type { Twin, TwinOptions } from \"./twin.js\";\n\n/**\n * The three files that let an agent find a site's content without crawling it\n * blindly: `/llms.txt`, `/llms-full.txt` and `/install.md`.\n *\n * ── Why generate them rather than let customers write them ───────────────────\n * A hand-written index is wrong the first time a page is added, and an index\n * that lists pages which no longer exist is worse than none -- an agent spends\n * its budget on 404s and concludes the site is broken. These are derived from\n * the same manifest that serves the twins, so they cannot disagree with what is\n * actually servable.\n *\n * `llms.txt` is the convention proposed by Answer.AI in 2024: a markdown index\n * of a site's high-value content. `llms-full.txt` is the same set with the\n * bodies inline, for an agent that would rather make one request than a hundred.\n */\n\nexport interface TwinEntry {\n /** The page path, e.g. `/docs/intro`. */\n path: string;\n /** One line. This is what an agent reads when deciding whether to fetch. */\n summary?: string;\n title?: string;\n}\n\nexport interface DiscoveryOptions {\n siteName: string;\n /** One or two sentences. Appears under the heading in `llms.txt`. */\n description?: string;\n origin?: string;\n entries: readonly TwinEntry[];\n}\n\nfunction titleFor(entry: TwinEntry): string {\n if (entry.title) return entry.title;\n if (entry.path === \"/\") return \"Home\";\n const last = entry.path.split(\"/\").filter(Boolean).pop() ?? entry.path;\n return last.replace(/[-_]/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\nconst twinPath = (path: string) => (path === \"/\" ? \"/index.md\" : `${path}.md`);\n\n/** The index: every page, one line each. */\nexport function renderLlmsTxt(options: DiscoveryOptions): string {\n const origin = options.origin ?? \"\";\n const lines: string[] = [`# ${options.siteName}`, \"\"];\n\n if (options.description) {\n for (const line of options.description.split(\"\\n\")) lines.push(`> ${line}`);\n lines.push(\"\");\n }\n\n lines.push(\n \"Every page listed here is also available as markdown: append `.md` to the path\",\n \"(`/` becomes `/index.md`), or send `Accept: text/markdown`.\",\n \"\",\n \"## Pages\",\n \"\",\n );\n\n for (const entry of options.entries) {\n const href = `${origin}${twinPath(entry.path)}`;\n lines.push(`- [${titleFor(entry)}](${href})${entry.summary ? `: ${entry.summary}` : \"\"}`);\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n/** The bulk file: one request instead of a hundred. */\nexport async function renderLlmsFullTxt(\n options: DiscoveryOptions,\n resolve: TwinOptions[\"resolve\"],\n): Promise<string> {\n const parts: string[] = [`# ${options.siteName}`, \"\"];\n if (options.description) {\n for (const line of options.description.split(\"\\n\")) parts.push(`> ${line}`);\n parts.push(\"\");\n }\n\n for (const entry of options.entries) {\n let twin: Twin | null | undefined;\n try {\n twin = await resolve(entry.path);\n } catch {\n // ⚠️ A resolver that throws on one page must not empty the whole file.\n // Skipping the page is a smaller lie than serving an index of nothing.\n continue;\n }\n if (!twin) continue;\n parts.push(`---`, ``, `## ${titleFor(entry)}`, ``, `Source: ${entry.path}`, ``, twin.body.trim(), ``);\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * The install guide, written for an AI coding agent rather than a person.\n *\n * ⚠️ This is the install path for a product whose users install things by\n * asking a model. It is deliberately imperative, names the exact failure modes,\n * and tells the agent to STOP rather than guess at a missing value -- because an\n * agent that guesses a credential writes a broken config that looks finished.\n */\nexport function renderInstallMd(options: { packageName?: string } = {}): string {\n const pkg = options.packageName ?? \"@entreprenoid/analytics\";\n return `# entreprenoid install guide (for AI coding agents)\n\nYou are installing **entreprenoid** — analytics for the agentic web. It records every\nrequest a site serves, especially from AI agents, and can serve those agents a clean\nmarkdown twin of each page. Collection is fire-and-forget: it never blocks and never\nfails the user's responses.\n\n## Before you start\n\nEvery environment variable this package reads:\n\n| Variable | Required | What it is |\n| --- | --- | --- |\n| \\`ENTREPRENOID_INGEST_URL\\` | **yes** | The ingest endpoint. ⚠️ It must end in \\`/v1/events\\` — the SDK POSTs this URL verbatim and appends nothing, so a bare origin sends every batch to \\`POST /\\`, which is rejected. The SDK fails open, so you would see nothing at all. |\n| \\`ENTREPRENOID_SERVER_KEY\\` | **yes** | The site's key, starting with \\`ep_live_server_\\`. |\n| \\`ENTREPRENOID_SITE_ID\\` | no | The dashboard shows it beside the key. Safe to set, safe to omit: the server resolves the site from the credential and ignores this value. |\n| \\`ENTREPRENOID_ENABLED\\` | no | \\`false\\` makes collection a transparent no-op without removing any code. Useful per environment. |\n| \\`ENTREPRENOID_DEBUG\\` | no | \\`true\\` prints one startup line saying whether collection is on, and why not if it is off. |\n\n**If either required value is missing, STOP and ask.** Do not invent them, and do not\nleave a placeholder — a config that looks complete and is not is worse than no config.\n\nSafety rules that apply throughout:\n\n- **Never hardcode the key**, never print it, never commit it. Environment variables only.\n- Before writing an env file, make sure it is gitignored.\n- ⚠️ **Never prefix the key with \\`NEXT_PUBLIC_\\`, \\`VITE_\\`, \\`PUBLIC_\\` or \\`REACT_APP_\\`.**\n Those prefixes inline a value into client-side JavaScript, which publishes the key to\n everyone who loads the page. The SDK refuses to start if it finds one.\n- Only touch the files this install needs.\n\n## Step 1 — install the package\n\nDetect the package manager from the lockfile:\n\n| Lockfile | Command |\n| --- | --- |\n| \\`pnpm-lock.yaml\\` | \\`pnpm add ${pkg}\\` |\n| \\`yarn.lock\\` | \\`yarn add ${pkg}\\` |\n| \\`bun.lock\\` | \\`bun add ${pkg}\\` |\n| \\`package-lock.json\\` or none | \\`npm install ${pkg}\\` |\n\n## Step 2 — wire up the collector (pick exactly ONE)\n\n### Express\n\n\\`\\`\\`ts\nimport { entreprenoid } from \"${pkg}/express\";\n\napp.use(entreprenoid());\n\\`\\`\\`\n\nAdd it **before** your routes so it observes all of them.\n\n### Next.js (App Router, 14+)\n\nUse the **Next adapter**, not the web one. In \\`proxy.ts\\` at the project root\n(\\`middleware.ts\\` on Next 15 and earlier — same file, renamed in Next 16):\n\n\\`\\`\\`ts\nimport { after } from \"next/server\";\nimport { proxy } from \"${pkg}/next\";\n\nexport default proxy({ after });\n\nexport const config = {\n matcher: [\"/((?!_next/static|_next/image|favicon.ico).*)\"],\n};\n\\`\\`\\`\n\n⚠️ **Do not use \\`${pkg}/web\\` in a Next proxy.** It runs and it lies. A proxy\nexecutes *before* the route and hands control onward with a sentinel response — status\n200, no real content type — so the web adapter would record a **measured 200 for every\nrequest**, including the ones your routes render as 404 or 500.\n\nThe Next adapter emits only what a proxy can actually know, and **omits the response\nentirely** rather than guessing at it. Your dashboard will show those requests with no\nstatus, which is the truth: nothing observed one.\n\n⚠️ **Pass \\`after\\`.** Without it the collector relies on its own timer, and a serverless\ninvocation can be frozen before that timer fires — events are simply lost, silently.\n\n### Web-standard runtimes (Cloudflare Workers, Deno, Bun, Hono)\n\n\\`\\`\\`ts\nimport { observe } from \"${pkg}/web\";\n\nexport default {\n fetch: observe(handler, { waitUntil: (p) => ctx.waitUntil(p) }),\n};\n\\`\\`\\`\n\nPass \\`waitUntil\\` where the runtime offers one, or a serverless invocation can be\nfrozen before the events are sent.\n\n## Step 3 — optional: serve a markdown twin\n\nAgents pay for every token they read, and most of a modern page is markup they do not\nwant. Supply a resolver and the same middleware will serve clean markdown when a client\nasks for it:\n\n\\`\\`\\`ts\napp.use(entreprenoid({\n twin: { resolve: (path) => markdownFor(path) },\n}));\n\\`\\`\\`\n\n⚠️ **A browser never receives markdown.** The twin is served only when the path ends in\n\\`.md\\` or the \\`Accept\\` header explicitly prefers \\`text/markdown\\` — never based on the\nUser-Agent, which would be cloaking and would break shared caching.\n\n## Step 4 — ⚠️ look at the routes before you go live\n\n**Do not skip this one.** The path is sent as it arrives. The query string is dropped\nbefore anything parses it, and the \\`Referer\\` is reduced to an origin — but the path\nitself is data, and on a lot of sites the path carries secrets:\n\n\\`\\`\\`\n/reveal/<single-use-token> /join/<invite-code>\n/confirm/<token> /upload/<ticket>\n\\`\\`\\`\n\nRead the project's routes. For each one, decide:\n\n\\`\\`\\`ts\napp.use(entreprenoid({\n // Collapse identifiers so analytics never sees a per-user value, and so one\n // route does not become ten thousand rows.\n routeTemplate: (path) => path.replace(/\\\\/\\\\d+(?=\\\\/|$)/g, \"/:id\"),\n\n // Replace any segment that is a token, code or personal identifier.\n redactPatterns: [/^[A-Za-z0-9_-]{20,}$/],\n\n // Traffic you do not want counted: health checks, your own office, previews.\n isInternal: (req) => req.path.startsWith(\"/_health\"),\n}));\n\\`\\`\\`\n\n⚠️ **A default backstop already runs, and you should not rely on it.** Segments that\nlook like credentials — uuids, cuids, JWTs, long hex, dense mixed-case strings — are\nreplaced with \\`[redacted]\\` before the event is sent, and the event records that it\nhappened. It cannot catch a short token like \\`/j/aB3xK9\\`, and it does not know which of\nthis project's ids are sensitive. **Only the routes tell you that.** Set\n\\`redactHighEntropyPaths: false\\` to turn the backstop off; that never disables\n\\`redactPatterns\\`, which are yours.\n\nIf you are unsure whether a path segment is a secret, treat it as one and say so in your\nsummary to the user.\n\n## Step 5 — verify\n\nStart the app and make one request. Within a few seconds the dashboard should show it.\nIf nothing arrives:\n\n- check the key is set in the server's environment, not the client's\n- check \\`ENTREPRENOID_INGEST_URL\\` ends in \\`/v1/events\\`\n- set \\`ENTREPRENOID_DEBUG=true\\` and read the startup line\n\n**Do not add retry logic, queues or error handling around the SDK.** It already buffers,\nretries with backoff, and fails open. Wrapping it in a try/catch is harmless; awaiting it\nis not, and would put analytics on your critical path.\n`;\n}\n","import type { RequestEvent } from \"@entreprenoid/event-schema\";\nimport { createCollector, type Collector } from \"./core/collector.js\";\nimport { resolveConfig, type EntreprenoidConfig } from \"./core/config.js\";\nimport { safe } from \"./core/safe.js\";\nimport { normalisePath, observeRequest } from \"./observe/request.js\";\nimport { observeResponse } from \"./observe/response.js\";\nimport { SDK_NAME, SDK_VERSION, newEventId, runtimeName } from \"./runtime.js\";\nimport {\n renderInstallMd,\n renderLlmsFullTxt,\n renderLlmsTxt,\n} from \"./serve/discovery.js\";\nimport {\n advertiseHeader,\n buildTwinResponse,\n decideTwin,\n DISCOVERY_PATHS,\n type TwinOptions,\n} from \"./serve/twin.js\";\n\n/**\n * Express middleware.\n *\n * app.use(entreprenoid());\n *\n * ── How it observes a completed response without touching it ─────────────────\n * By listening, not by wrapping. `res` is a writable stream and the tempting\n * approach -- monkey-patching `res.write` and `res.end` to inspect what goes\n * out -- is wrong three ways: it adds a function call to every chunk, it holds\n * the bytes in memory, and any mistake in re-emitting them corrupts the\n * customer's response. Everything we need is already in the headers by the time\n * the response finishes.\n *\n * ⚠️ **`finish` and `close`, with a latch.** `finish` fires when the response\n * completed. `close` fires when the socket went away, which for an aborted\n * request is the ONLY event that fires -- and an aborted request is exactly the\n * kind an AI client makes when it has read enough. Listening to `finish` alone\n * silently loses them. Both are registered and a latch makes sure one event is\n * recorded, not two.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any -- these are Express's\n types without taking a dependency on Express. The adapter must compile for a\n customer who has express installed and for one who does not. */\ntype Req = {\n method?: string;\n originalUrl?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n protocol?: string;\n secure?: boolean;\n [key: string]: any;\n};\ntype Res = {\n statusCode: number;\n writableFinished?: boolean;\n getHeader(name: string): number | string | string[] | undefined;\n once(event: string, listener: () => void): unknown;\n [key: string]: any;\n};\ntype Next = (error?: unknown) => void;\n\nexport interface ExpressOptions extends EntreprenoidConfig {\n collector?: Collector;\n /**\n * Serve a markdown twin. Omit it and this middleware is observation-only,\n * byte-for-byte as before -- the serve half is opt-in, because it is the half\n * that can change what a visitor receives.\n */\n twin?: TwinOptions;\n}\n\n\nfunction header(req: Req, name: string): string | undefined {\n const value = req.headers[name];\n if (Array.isArray(value)) return value[0];\n return value;\n}\n\nfunction headerString(res: Res, name: string): string | undefined {\n const value = res.getHeader(name);\n if (value === undefined || value === null) return undefined;\n return Array.isArray(value) ? value[0] : String(value);\n}\n\nexport function entreprenoid(options: ExpressOptions = {}) {\n const config = resolveConfig(options);\n const collector = options.collector ?? createCollector(config);\n\n const twin = options.twin;\n\n return function entreprenoidMiddleware(req: Req, res: Res, next: Next): void {\n if (config.disabled && !twin) {\n next();\n return;\n }\n\n const startedAt = Date.now();\n const startedHr = process.hrtime.bigint();\n let recorded = false;\n let served: RequestEvent[\"serve\"];\n\n const record = (): void => {\n if (recorded) return;\n recorded = true;\n\n safe(() => {\n const observed = observeRequest(\n {\n method: req.method ?? \"GET\",\n url: req.originalUrl ?? req.url ?? \"/\",\n host: header(req, \"host\"),\n protocol: req.secure || req.protocol === \"https\" ? \"https\" : \"http\",\n userAgent: header(req, \"user-agent\"),\n referer: header(req, \"referer\") ?? header(req, \"referrer\"),\n },\n config,\n );\n\n // ⚠️ If the response did not finish, we did not measure it. Reporting a\n // status we never saw the client receive would put a guess in the same\n // column as a measurement, which is the one thing this product must not\n // do. An aborted request is recorded with `observation: \"unknown\"` --\n // still counted, honestly labelled.\n const finished = res.writableFinished !== false;\n const latencyMs = Number(process.hrtime.bigint() - startedHr) / 1e6;\n\n const event: RequestEvent = {\n ...observed,\n eventId: newEventId(),\n siteId: config.siteId,\n observedAt: new Date(startedAt).toISOString(),\n response: finished\n ? observeResponse({\n status: res.statusCode,\n contentType: headerString(res, \"content-type\"),\n contentLength: headerString(res, \"content-length\"),\n latencyMs,\n observation: \"measured\",\n })\n : observeResponse({ latencyMs, observation: \"unknown\" }),\n ...(served ? { serve: served } : {}),\n sdk: {\n name: SDK_NAME,\n version: SDK_VERSION,\n adapter: \"express\",\n runtime: runtimeName(),\n },\n };\n\n collector.record(event);\n });\n };\n\n // Registered before next(), so a synchronous handler that responds\n // immediately is still observed.\n safe(() => {\n res.once(\"finish\", record);\n res.once(\"close\", record);\n });\n\n if (!twin) {\n next();\n return;\n }\n\n const path = normalisePath(req.originalUrl ?? req.url ?? \"/\", config.redactPatterns);\n\n // ── Discovery, before anything else ──────────────────────────────────────\n // These are the three files that let an agent find the content at all, and\n // they are cheap: one is a constant, two come from the manifest already in\n // memory.\n if (twin.discovery && DISCOVERY_PATHS.includes(path as never)) {\n const method = (req.method ?? \"GET\").toUpperCase();\n if (method === \"GET\" || method === \"HEAD\") {\n void (async () => {\n try {\n const body =\n path === \"/install.md\"\n ? renderInstallMd()\n : path === \"/llms.txt\"\n ? renderLlmsTxt(twin.discovery!)\n : await renderLlmsFullTxt(twin.discovery!, twin.resolve);\n res.setHeader(\"content-type\", \"text/markdown; charset=utf-8\");\n res.setHeader(\"cache-control\", twin.cacheControl ?? \"public, max-age=3600, s-maxage=86400\");\n res.statusCode = 200;\n served = { decision: \"served\", reason: \"md_path\", format: \"text/markdown; charset=utf-8\" };\n if (method === \"HEAD\") res.end();\n else res.end(body);\n } catch {\n // Their site must still work.\n served = { decision: \"error\", reason: \"resolver_error\" };\n if (!res.headersSent) next();\n }\n })();\n return;\n }\n }\n\n // ── The serve half ───────────────────────────────────────────────────────\n // ⚠️ Everything below can change what the visitor receives, so every exit\n // leads to `next()` unless a twin was actually written. A throw, a rejected\n // promise, a missing twin: all fall through to the customer's own handler\n // with the response untouched.\n const decision = decideTwin({\n method: req.method ?? \"GET\",\n path,\n accept: header(req, \"accept\"),\n });\n\n if (decision.action === \"pass\") {\n if (twin.advertise !== false) {\n void Promise.resolve()\n .then(() => twin.resolve(path))\n .then((found) => {\n if (found && !res.headersSent) {\n safe(() => res.setHeader(\"link\", advertiseHeader(path)));\n served = { decision: \"advertised\", reason: \"no_twin\" };\n }\n })\n .catch(() => undefined)\n .finally(() => next());\n return;\n }\n served = { decision: \"fell_through\", reason: decision.reason };\n next();\n return;\n }\n\n void Promise.resolve()\n .then(() => twin.resolve(decision.lookupPath))\n .then((found) => {\n if (!found) {\n served = { decision: \"fell_through\", reason: \"no_twin\" };\n next();\n return;\n }\n const built = buildTwinResponse(found, decision, twin);\n served = {\n decision: \"served\",\n reason: decision.reason,\n format: built.headers[\"content-type\"] ?? \"text/markdown\",\n };\n for (const [name, value] of Object.entries(built.headers)) {\n res.setHeader(name, value);\n }\n res.statusCode = built.status;\n // HEAD asks for the headers of what a GET would return, and nothing\n // else. Writing a body to it is a protocol violation some clients\n // handle by hanging.\n if ((req.method ?? \"GET\").toUpperCase() === \"HEAD\") res.end();\n else res.end(built.body);\n })\n .catch(() => {\n // The customer's resolver threw. Their site must still work.\n served = { decision: \"error\", reason: \"resolver_error\" };\n if (!res.headersSent) next();\n });\n };\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveConfig, createCollector, decideTwin, normalisePath, buildTwinResponse, safe, observeRequest, runtimeName, observeResponse, newEventId, SDK_VERSION, SDK_NAME } from './chunk-
|
|
1
|
+
import { resolveConfig, createCollector, decideTwin, normalisePath, buildTwinResponse, safe, observeRequest, runtimeName, observeResponse, newEventId, SDK_VERSION, SDK_NAME } from './chunk-IOMJAF33.js';
|
|
2
2
|
|
|
3
3
|
// src/next.ts
|
|
4
4
|
function proxy(options = {}) {
|
|
@@ -110,5 +110,5 @@ function proxy(options = {}) {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
export { proxy };
|
|
113
|
-
//# sourceMappingURL=chunk-
|
|
114
|
-
//# sourceMappingURL=chunk-
|
|
113
|
+
//# sourceMappingURL=chunk-SJTHL7HF.js.map
|
|
114
|
+
//# sourceMappingURL=chunk-SJTHL7HF.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/next.ts"],"names":[],"mappings":"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","file":"chunk-EVNUKN5A.js","sourcesContent":["import type { RequestEvent } from \"@entreprenoid/event-schema\";\nimport { createCollector, type Collector } from \"./core/collector.js\";\nimport { resolveConfig, type EntreprenoidConfig } from \"./core/config.js\";\nimport { safe } from \"./core/safe.js\";\nimport { observeRequest, normalisePath } from \"./observe/request.js\";\nimport { observeResponse } from \"./observe/response.js\";\nimport { newEventId, runtimeName, SDK_NAME, SDK_VERSION } from \"./runtime.js\";\nimport { advertiseHeader, buildTwinResponse, decideTwin, type TwinOptions } from \"./serve/twin.js\";\n\n/**\n * The Next.js adapter, for `proxy.ts` (called `middleware.ts` before Next 16).\n *\n * ── ⚠️ Why this exists rather than \"just use ./web\" ──────────────────────────\n * Because `./web` produces FALSE DATA in a Next proxy, and does it silently.\n *\n * A proxy runs **before the route**. It hands control onward by returning\n * `NextResponse.next()` — a sentinel with status 200, no real content-type and\n * no content-length. `./web` reads that sentinel and records\n * `{ status: 200, observation: \"measured\" }`, so **every event claims a\n * measured 200**, including requests the route renders as 404 or 500.\n *\n * ⚠️ **Our own schema already said this was wrong and refused it**:\n * `observation: \"unknown\"` may not carry a status, and the Next case is the one\n * where the `response` object is absent entirely — \"we never had a chance to\n * look\". Correct behaviour, specified, enforced by a `superRefine`, and until\n * now unreachable because no adapter emitted it. That reads as done, which is\n * worse than reading as missing.\n *\n * ── What this emits ──────────────────────────────────────────────────────────\n * Exactly what a proxy genuinely knows — method, path, host, user agent,\n * referrer origin, campaign — and **no `response` object at all**, unless it\n * served the twin itself, in which case it made the response and can measure it\n * honestly.\n *\n * ── ⚠️ The requestId merge is DESIGNED and NOT BUILT ─────────────────────────\n * `requestId` rides every event so a later post-response observation can be\n * merged onto it. **Nothing performs that merge today** — measured: nothing in\n * the worker or the database reads the field. Next offers no general\n * post-response hook (`onRequestError` fires only on errors; `after()` still\n * runs in the proxy, which cannot see the route's response), so the honest\n * second observation point is a wrapper around each route handler — a second\n * install step, easy to forget, and a half-installed product reports half its\n * traffic.\n *\n * So this ships the single honest observation. That is not a degraded event: a\n * proxy cannot see a response, and saying so precisely is the product's\n * differentiator rather than a shortfall.\n *\n * ── ⚠️ `after` is an OPTION, not an import ───────────────────────────────────\n * There is no `ctx.waitUntil` in a Next proxy; the equivalent is `after()` from\n * `next/server`. Importing it here — even dynamically — would put a bare\n * specifier in a package whose whole discipline is zero runtime dependencies,\n * and a dynamic `import()` slips past `check-sdk-artifact.mjs`'s regex, which\n * would be a gate quietly stopping working. So the customer passes it, exactly\n * as `observe()` already takes `waitUntil`:\n *\n * ```ts\n * import { after } from \"next/server\";\n * import { proxy } from \"@entreprenoid/analytics/next\";\n *\n * export default proxy({ after });\n * export const config = { matcher: [\"/((?!_next/static).*)\"] };\n * ```\n *\n * Without it the collector falls back to its own 2s timer, which a serverless\n * deployment can freeze before it fires.\n */\n\nexport interface ProxyOptions extends EntreprenoidConfig {\n /**\n * `after` from `next/server`. Passed rather than imported — see the docblock.\n * Omitting it is supported and costs reliability on serverless.\n */\n after?: (task: () => void | Promise<void>) => void;\n /** For tests, and for a host that already has a collector. */\n collector?: Collector;\n /**\n * Serve a markdown twin from the proxy.\n *\n * ⚠️ A proxy is a GOOD place for this, unlike the observation half: it runs\n * before the route, so returning the twin short-circuits rendering entirely\n * and the agent never pays for a React tree it discards. And because the\n * proxy built that response itself, the event can honestly say `measured`.\n */\n twin?: TwinOptions;\n}\n\n/**\n * ⚠️ Returns `undefined` to continue, rather than `NextResponse.next()`.\n *\n * Next treats a void return as \"carry on\", which means this adapter never has\n * to import `next/server` at all. It also removes the exact sentinel that made\n * `./web` lie here: there is no fake 200 to accidentally observe.\n */\nexport type ProxyResult = Response | undefined;\n\nexport function proxy(options: ProxyOptions = {}): (request: Request) => Promise<ProxyResult> {\n const config = resolveConfig(options);\n const collector = options.collector ?? createCollector(config);\n\n return async (request: Request): Promise<ProxyResult> => {\n // ⚠️ The clock starts OUTSIDE the try, exactly as in `./web`: if observation\n // setup throws, the request must still reach the route.\n const startedAt = Date.now();\n let served: RequestEvent[\"serve\"];\n let twinResponse: Response | undefined;\n\n // ── The serve half, before the route ─────────────────────────────────────\n // Every failure falls through to the customer's application unchanged.\n if (options.twin) {\n try {\n const url = new URL(request.url);\n const decision = decideTwin({\n method: request.method,\n path: normalisePath(url.pathname),\n accept: request.headers.get(\"accept\"),\n });\n\n if (decision.action === \"serve\") {\n const found = await options.twin.resolve(decision.lookupPath);\n if (found) {\n const built = buildTwinResponse(found, decision, options.twin);\n served = {\n decision: \"served\",\n reason: decision.reason,\n format: built.headers[\"content-type\"] ?? \"text/markdown\",\n };\n twinResponse = new Response(\n request.method.toUpperCase() === \"HEAD\" ? null : built.body,\n { status: built.status, headers: built.headers },\n );\n } else {\n served = { decision: \"fell_through\", reason: \"no_twin\" };\n }\n } else {\n served = { decision: \"fell_through\", reason: decision.reason };\n }\n } catch {\n served = { decision: \"error\", reason: \"resolver_error\" };\n }\n }\n\n // ⚠️ Advertising is deliberately NOT done here. It would mean appending a\n // `link` header to a response this proxy does not have — the route builds\n // it later. `./express` advertises because it sees the real response.\n // Claiming a twin on a response we cannot touch would be a header nobody\n // receives.\n\n const record = (): void => {\n safe(() => {\n if (config.disabled) return;\n\n const url = new URL(request.url);\n const observed = observeRequest(\n {\n method: request.method,\n url: url.pathname + url.search,\n host: url.host,\n protocol: url.protocol === \"https:\" ? \"https\" : \"http\",\n userAgent: request.headers.get(\"user-agent\") ?? undefined,\n referer: request.headers.get(\"referer\") ?? undefined,\n },\n config,\n );\n\n const event: RequestEvent = {\n ...observed,\n eventId: newEventId(),\n // ⚠️ Emitted so a post-response observation CAN be merged later.\n // Nothing merges it today, and the docblock above says so plainly\n // rather than letting the field imply otherwise.\n requestId: newEventId(),\n siteId: config.siteId,\n observedAt: new Date(startedAt).toISOString(),\n ...(served ? { serve: served } : {}),\n // ⚠️ **The response object is present ONLY when we built it.**\n //\n // If the twin was served, this proxy IS the responder and measured it.\n // Otherwise the route has not run yet and there is nothing to\n // observe — so the key is OMITTED, not set to a guess, not set to\n // `observation: \"unknown\"` with a status (which the schema refuses),\n // and not set to a latency-only object (we did not wait for the\n // response, so we did not measure a latency either).\n ...(twinResponse\n ? {\n response: observeResponse({\n status: twinResponse.status,\n contentType: twinResponse.headers.get(\"content-type\"),\n contentLength: twinResponse.headers.get(\"content-length\"),\n latencyMs: Date.now() - startedAt,\n observation: \"measured\",\n }),\n }\n : {}),\n sdk: {\n name: SDK_NAME,\n version: SDK_VERSION,\n // ⚠️ `next-proxy`, which is the name the WIRE SCHEMA already uses\n // in its own docblock (`event.ts:157`). Its own name matters here\n // more than for any other adapter: this is the one whose events\n // legitimately carry no response, and the dashboard must not\n // present an adapter's blind spot as a fact about the traffic.\n adapter: \"next-proxy\",\n runtime: runtimeName(),\n },\n };\n\n collector.record(event);\n });\n };\n\n // ⚠️ `after` runs the recording once the response is on its way, so nothing\n // here is on the path of the request. Without it, `record()` is still\n // synchronous and still non-blocking — `safe()` and the bounded queue see\n // to that — it is only the eventual FLUSH that risks being frozen.\n if (options.after) {\n try {\n options.after(record);\n } catch {\n // A hook that throws is not a reason to lose the event.\n record();\n }\n } else {\n record();\n }\n\n return twinResponse;\n };\n}\n\n/** Advertise a twin from a route handler, where the response actually exists. */\nexport { advertiseHeader };\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/next.ts"],"names":[],"mappings":"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","file":"chunk-SJTHL7HF.js","sourcesContent":["import type { RequestEvent } from \"@entreprenoid/event-schema\";\nimport { createCollector, type Collector } from \"./core/collector.js\";\nimport { resolveConfig, type EntreprenoidConfig } from \"./core/config.js\";\nimport { safe } from \"./core/safe.js\";\nimport { observeRequest, normalisePath } from \"./observe/request.js\";\nimport { observeResponse } from \"./observe/response.js\";\nimport { newEventId, runtimeName, SDK_NAME, SDK_VERSION } from \"./runtime.js\";\nimport { advertiseHeader, buildTwinResponse, decideTwin, type TwinOptions } from \"./serve/twin.js\";\n\n/**\n * The Next.js adapter, for `proxy.ts` (called `middleware.ts` before Next 16).\n *\n * ── ⚠️ Why this exists rather than \"just use ./web\" ──────────────────────────\n * Because `./web` produces FALSE DATA in a Next proxy, and does it silently.\n *\n * A proxy runs **before the route**. It hands control onward by returning\n * `NextResponse.next()` — a sentinel with status 200, no real content-type and\n * no content-length. `./web` reads that sentinel and records\n * `{ status: 200, observation: \"measured\" }`, so **every event claims a\n * measured 200**, including requests the route renders as 404 or 500.\n *\n * ⚠️ **Our own schema already said this was wrong and refused it**:\n * `observation: \"unknown\"` may not carry a status, and the Next case is the one\n * where the `response` object is absent entirely — \"we never had a chance to\n * look\". Correct behaviour, specified, enforced by a `superRefine`, and until\n * now unreachable because no adapter emitted it. That reads as done, which is\n * worse than reading as missing.\n *\n * ── What this emits ──────────────────────────────────────────────────────────\n * Exactly what a proxy genuinely knows — method, path, host, user agent,\n * referrer origin, campaign — and **no `response` object at all**, unless it\n * served the twin itself, in which case it made the response and can measure it\n * honestly.\n *\n * ── ⚠️ The requestId merge is DESIGNED and NOT BUILT ─────────────────────────\n * `requestId` rides every event so a later post-response observation can be\n * merged onto it. **Nothing performs that merge today** — measured: nothing in\n * the worker or the database reads the field. Next offers no general\n * post-response hook (`onRequestError` fires only on errors; `after()` still\n * runs in the proxy, which cannot see the route's response), so the honest\n * second observation point is a wrapper around each route handler — a second\n * install step, easy to forget, and a half-installed product reports half its\n * traffic.\n *\n * So this ships the single honest observation. That is not a degraded event: a\n * proxy cannot see a response, and saying so precisely is the product's\n * differentiator rather than a shortfall.\n *\n * ── ⚠️ `after` is an OPTION, not an import ───────────────────────────────────\n * There is no `ctx.waitUntil` in a Next proxy; the equivalent is `after()` from\n * `next/server`. Importing it here — even dynamically — would put a bare\n * specifier in a package whose whole discipline is zero runtime dependencies,\n * and a dynamic `import()` slips past `check-sdk-artifact.mjs`'s regex, which\n * would be a gate quietly stopping working. So the customer passes it, exactly\n * as `observe()` already takes `waitUntil`:\n *\n * ```ts\n * import { after } from \"next/server\";\n * import { proxy } from \"@entreprenoid/analytics/next\";\n *\n * export default proxy({ after });\n * export const config = { matcher: [\"/((?!_next/static).*)\"] };\n * ```\n *\n * Without it the collector falls back to its own 2s timer, which a serverless\n * deployment can freeze before it fires.\n */\n\nexport interface ProxyOptions extends EntreprenoidConfig {\n /**\n * `after` from `next/server`. Passed rather than imported — see the docblock.\n * Omitting it is supported and costs reliability on serverless.\n */\n after?: (task: () => void | Promise<void>) => void;\n /** For tests, and for a host that already has a collector. */\n collector?: Collector;\n /**\n * Serve a markdown twin from the proxy.\n *\n * ⚠️ A proxy is a GOOD place for this, unlike the observation half: it runs\n * before the route, so returning the twin short-circuits rendering entirely\n * and the agent never pays for a React tree it discards. And because the\n * proxy built that response itself, the event can honestly say `measured`.\n */\n twin?: TwinOptions;\n}\n\n/**\n * ⚠️ Returns `undefined` to continue, rather than `NextResponse.next()`.\n *\n * Next treats a void return as \"carry on\", which means this adapter never has\n * to import `next/server` at all. It also removes the exact sentinel that made\n * `./web` lie here: there is no fake 200 to accidentally observe.\n */\nexport type ProxyResult = Response | undefined;\n\nexport function proxy(options: ProxyOptions = {}): (request: Request) => Promise<ProxyResult> {\n const config = resolveConfig(options);\n const collector = options.collector ?? createCollector(config);\n\n return async (request: Request): Promise<ProxyResult> => {\n // ⚠️ The clock starts OUTSIDE the try, exactly as in `./web`: if observation\n // setup throws, the request must still reach the route.\n const startedAt = Date.now();\n let served: RequestEvent[\"serve\"];\n let twinResponse: Response | undefined;\n\n // ── The serve half, before the route ─────────────────────────────────────\n // Every failure falls through to the customer's application unchanged.\n if (options.twin) {\n try {\n const url = new URL(request.url);\n const decision = decideTwin({\n method: request.method,\n path: normalisePath(url.pathname),\n accept: request.headers.get(\"accept\"),\n });\n\n if (decision.action === \"serve\") {\n const found = await options.twin.resolve(decision.lookupPath);\n if (found) {\n const built = buildTwinResponse(found, decision, options.twin);\n served = {\n decision: \"served\",\n reason: decision.reason,\n format: built.headers[\"content-type\"] ?? \"text/markdown\",\n };\n twinResponse = new Response(\n request.method.toUpperCase() === \"HEAD\" ? null : built.body,\n { status: built.status, headers: built.headers },\n );\n } else {\n served = { decision: \"fell_through\", reason: \"no_twin\" };\n }\n } else {\n served = { decision: \"fell_through\", reason: decision.reason };\n }\n } catch {\n served = { decision: \"error\", reason: \"resolver_error\" };\n }\n }\n\n // ⚠️ Advertising is deliberately NOT done here. It would mean appending a\n // `link` header to a response this proxy does not have — the route builds\n // it later. `./express` advertises because it sees the real response.\n // Claiming a twin on a response we cannot touch would be a header nobody\n // receives.\n\n const record = (): void => {\n safe(() => {\n if (config.disabled) return;\n\n const url = new URL(request.url);\n const observed = observeRequest(\n {\n method: request.method,\n url: url.pathname + url.search,\n host: url.host,\n protocol: url.protocol === \"https:\" ? \"https\" : \"http\",\n userAgent: request.headers.get(\"user-agent\") ?? undefined,\n referer: request.headers.get(\"referer\") ?? undefined,\n },\n config,\n );\n\n const event: RequestEvent = {\n ...observed,\n eventId: newEventId(),\n // ⚠️ Emitted so a post-response observation CAN be merged later.\n // Nothing merges it today, and the docblock above says so plainly\n // rather than letting the field imply otherwise.\n requestId: newEventId(),\n siteId: config.siteId,\n observedAt: new Date(startedAt).toISOString(),\n ...(served ? { serve: served } : {}),\n // ⚠️ **The response object is present ONLY when we built it.**\n //\n // If the twin was served, this proxy IS the responder and measured it.\n // Otherwise the route has not run yet and there is nothing to\n // observe — so the key is OMITTED, not set to a guess, not set to\n // `observation: \"unknown\"` with a status (which the schema refuses),\n // and not set to a latency-only object (we did not wait for the\n // response, so we did not measure a latency either).\n ...(twinResponse\n ? {\n response: observeResponse({\n status: twinResponse.status,\n contentType: twinResponse.headers.get(\"content-type\"),\n contentLength: twinResponse.headers.get(\"content-length\"),\n latencyMs: Date.now() - startedAt,\n observation: \"measured\",\n }),\n }\n : {}),\n sdk: {\n name: SDK_NAME,\n version: SDK_VERSION,\n // ⚠️ `next-proxy`, which is the name the WIRE SCHEMA already uses\n // in its own docblock (`event.ts:157`). Its own name matters here\n // more than for any other adapter: this is the one whose events\n // legitimately carry no response, and the dashboard must not\n // present an adapter's blind spot as a fact about the traffic.\n adapter: \"next-proxy\",\n runtime: runtimeName(),\n },\n };\n\n collector.record(event);\n });\n };\n\n // ⚠️ `after` runs the recording once the response is on its way, so nothing\n // here is on the path of the request. Without it, `record()` is still\n // synchronous and still non-blocking — `safe()` and the bounded queue see\n // to that — it is only the eventual FLUSH that risks being frozen.\n if (options.after) {\n try {\n options.after(record);\n } catch {\n // A hook that throws is not a reason to lose the event.\n record();\n }\n } else {\n record();\n }\n\n return twinResponse;\n };\n}\n\n/** Advertise a twin from a route handler, where the response actually exists. */\nexport { advertiseHeader };\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveConfig, createCollector, normalisePath, decideTwin, buildTwinResponse, advertiseHeader, safe, observeRequest, runtimeName, observeResponse, newEventId, SDK_VERSION, SDK_NAME } from './chunk-
|
|
1
|
+
import { resolveConfig, createCollector, normalisePath, decideTwin, buildTwinResponse, advertiseHeader, safe, observeRequest, runtimeName, observeResponse, newEventId, SDK_VERSION, SDK_NAME } from './chunk-IOMJAF33.js';
|
|
2
2
|
|
|
3
3
|
// src/web.ts
|
|
4
4
|
function observe(handler, options = {}) {
|
|
@@ -100,5 +100,5 @@ function observe(handler, options = {}) {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
export { observe };
|
|
103
|
-
//# sourceMappingURL=chunk-
|
|
104
|
-
//# sourceMappingURL=chunk-
|
|
103
|
+
//# sourceMappingURL=chunk-WNCAN2XS.js.map
|
|
104
|
+
//# sourceMappingURL=chunk-WNCAN2XS.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web.ts"],"names":[],"mappings":"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","file":"chunk-GKFYGAKF.js","sourcesContent":["import type { RequestEvent } from \"@entreprenoid/event-schema\";\nimport { createCollector, type Collector } from \"./core/collector.js\";\nimport { resolveConfig, type EntreprenoidConfig } from \"./core/config.js\";\nimport { safe } from \"./core/safe.js\";\nimport { normalisePath, observeRequest } from \"./observe/request.js\";\nimport { observeResponse } from \"./observe/response.js\";\nimport { SDK_NAME, SDK_VERSION, newEventId, runtimeName } from \"./runtime.js\";\nimport {\n advertiseHeader,\n buildTwinResponse,\n decideTwin,\n type TwinOptions,\n} from \"./serve/twin.js\";\n\n/**\n * Wrap a Web-standard handler.\n *\n * const handler = observe(async (request) => new Response(\"hello\"));\n *\n * ⚠️ **The response is inspected, never consumed.** `res.clone()` and reading\n * `res.body` both exist and both are wrong here: cloning a streamed response\n * forces the runtime to buffer it so two readers can consume it, which turns a\n * streaming page into a buffered one and charges the customer the memory. Only\n * headers are read, and headers have already been computed.\n */\n\nexport interface ObserveOptions extends EntreprenoidConfig {\n /**\n * The runtime's \"keep working after the response\" hook, where there is one\n * (`waitUntil` on Cloudflare and Vercel). Without it the flush races the\n * response and a serverless invocation can be frozen mid-send.\n */\n waitUntil?: (promise: Promise<unknown>) => void;\n /** For tests, and for a host that already has a collector. */\n collector?: Collector;\n /**\n * Serve a markdown twin. Omit it and this wrapper is observation-only.\n * Opt-in, because it is the half that can change what a visitor receives.\n */\n twin?: TwinOptions;\n}\n\n/**\n * A Web-standard handler. The trailing arguments are whatever the runtime\n * passes after the request -- a Cloudflare `env` and `ctx`, a Deno `info` --\n * carried through untouched rather than named, so this compiles on all of them.\n */\nexport type WebHandler<Args extends unknown[] = unknown[]> = (\n request: Request,\n ...rest: Args\n) => Response | Promise<Response>;\n\n\n/**\n * ⚠️ The returned handler is typed `(request, ...rest) => Promise<Response>`\n * rather than as the input type `H`. Returning `H` reads better and is wrong:\n * a handler declared `() => Response` would produce a wrapper the caller cannot\n * pass a request to, which type-checks at the definition and fails at every\n * call site. Caught by `tsc` after the tests were already green.\n */\nexport function observe<Args extends unknown[]>(\n handler: WebHandler<Args>,\n options: ObserveOptions = {},\n): (request: Request, ...rest: Args) => Promise<Response> {\n const config = resolveConfig(options);\n const collector = options.collector ?? createCollector(config);\n\n // ⚠️ **Discovery is Express-only, and silence is the wrong way to say so.**\n //\n // `twin.discovery` generates `/llms.txt`, `/llms-full.txt` and `/install.md`\n // -- and only the Express adapter routes them. A web-runtime customer who\n // configures it has described their content to an index that is never served,\n // and until now got nothing and no error: they would find out when an agent\n // did not.\n //\n // One line, through the existing debug channel, at wiring time rather than\n // per request. Implementing it here is owed; saying nothing is not an option.\n if (options.twin?.discovery) {\n console.warn(\n \"[entreprenoid] twin.discovery is configured but the web adapter does not serve \" +\n \"/llms.txt, /llms-full.txt or /install.md. Serve them from your own router, or use \" +\n \"the Express adapter. See https://entreprenoid.com/install.md\",\n );\n }\n\n return async (request: Request, ...rest: Args): Promise<Response> => {\n // ⚠️ The clock starts OUTSIDE the try. If observation setup were to throw,\n // the handler must still run -- so nothing between here and the call can be\n // allowed to skip it.\n const startedAt = Date.now();\n\n // ── The serve half, BEFORE the handler ───────────────────────────────────\n // ⚠️ Every failure here falls through to the customer's handler. A throw in\n // their resolver, a missing twin, a method we do not answer: the site works\n // normally. A bug in entreprenoid must never be a bug in their website.\n let served: RequestEvent[\"serve\"];\n let twinResponse: Response | undefined;\n\n if (options.twin) {\n const url = new URL(request.url);\n const path = normalisePath(url.pathname, config.redactPatterns);\n const decision = decideTwin({\n method: request.method,\n path,\n accept: request.headers.get(\"accept\"),\n });\n\n if (decision.action === \"serve\") {\n try {\n const found = await options.twin.resolve(decision.lookupPath);\n if (found) {\n const built = buildTwinResponse(found, decision, options.twin);\n served = {\n decision: \"served\",\n reason: decision.reason,\n format: built.headers[\"content-type\"] ?? \"text/markdown\",\n };\n // HEAD asks for what a GET would return, minus the body.\n twinResponse = new Response(\n request.method.toUpperCase() === \"HEAD\" ? null : built.body,\n { status: built.status, headers: built.headers },\n );\n } else {\n served = { decision: \"fell_through\", reason: \"no_twin\" };\n }\n } catch {\n served = { decision: \"error\", reason: \"resolver_error\" };\n }\n } else {\n served = { decision: \"fell_through\", reason: decision.reason };\n }\n }\n\n const response = twinResponse ?? (await handler(request, ...rest));\n\n // Advertise an available twin on a response we did not serve ourselves.\n if (options.twin && !twinResponse && options.twin.advertise !== false) {\n try {\n const path = normalisePath(new URL(request.url).pathname, config.redactPatterns);\n if (await options.twin.resolve(path)) {\n response.headers.append(\"link\", advertiseHeader(path));\n served = { decision: \"advertised\", reason: \"no_twin\" };\n }\n } catch {\n /* advertising is a courtesy; never let it affect the response */\n }\n }\n\n safe(() => {\n if (config.disabled) return;\n\n const url = new URL(request.url);\n const observed = observeRequest(\n {\n method: request.method,\n url: url.pathname + url.search,\n host: url.host,\n protocol: url.protocol === \"https:\" ? \"https\" : \"http\",\n userAgent: request.headers.get(\"user-agent\") ?? undefined,\n referer: request.headers.get(\"referer\") ?? undefined,\n },\n config,\n );\n\n const event: RequestEvent = {\n ...observed,\n eventId: newEventId(),\n siteId: config.siteId,\n observedAt: new Date(startedAt).toISOString(),\n ...(served ? { serve: served } : {}),\n response: observeResponse({\n status: response.status,\n contentType: response.headers.get(\"content-type\"),\n contentLength: response.headers.get(\"content-length\"),\n latencyMs: Date.now() - startedAt,\n observation: \"measured\",\n }),\n sdk: {\n name: SDK_NAME,\n version: SDK_VERSION,\n adapter: \"web\",\n runtime: runtimeName(),\n },\n };\n\n collector.record(event);\n });\n\n if (options.waitUntil) {\n safe(() => options.waitUntil?.(collector.flush()));\n }\n\n return response;\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/web.ts"],"names":[],"mappings":"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","file":"chunk-WNCAN2XS.js","sourcesContent":["import type { RequestEvent } from \"@entreprenoid/event-schema\";\nimport { createCollector, type Collector } from \"./core/collector.js\";\nimport { resolveConfig, type EntreprenoidConfig } from \"./core/config.js\";\nimport { safe } from \"./core/safe.js\";\nimport { normalisePath, observeRequest } from \"./observe/request.js\";\nimport { observeResponse } from \"./observe/response.js\";\nimport { SDK_NAME, SDK_VERSION, newEventId, runtimeName } from \"./runtime.js\";\nimport {\n advertiseHeader,\n buildTwinResponse,\n decideTwin,\n type TwinOptions,\n} from \"./serve/twin.js\";\n\n/**\n * Wrap a Web-standard handler.\n *\n * const handler = observe(async (request) => new Response(\"hello\"));\n *\n * ⚠️ **The response is inspected, never consumed.** `res.clone()` and reading\n * `res.body` both exist and both are wrong here: cloning a streamed response\n * forces the runtime to buffer it so two readers can consume it, which turns a\n * streaming page into a buffered one and charges the customer the memory. Only\n * headers are read, and headers have already been computed.\n */\n\nexport interface ObserveOptions extends EntreprenoidConfig {\n /**\n * The runtime's \"keep working after the response\" hook, where there is one\n * (`waitUntil` on Cloudflare and Vercel). Without it the flush races the\n * response and a serverless invocation can be frozen mid-send.\n */\n waitUntil?: (promise: Promise<unknown>) => void;\n /** For tests, and for a host that already has a collector. */\n collector?: Collector;\n /**\n * Serve a markdown twin. Omit it and this wrapper is observation-only.\n * Opt-in, because it is the half that can change what a visitor receives.\n */\n twin?: TwinOptions;\n}\n\n/**\n * A Web-standard handler. The trailing arguments are whatever the runtime\n * passes after the request -- a Cloudflare `env` and `ctx`, a Deno `info` --\n * carried through untouched rather than named, so this compiles on all of them.\n */\nexport type WebHandler<Args extends unknown[] = unknown[]> = (\n request: Request,\n ...rest: Args\n) => Response | Promise<Response>;\n\n\n/**\n * ⚠️ The returned handler is typed `(request, ...rest) => Promise<Response>`\n * rather than as the input type `H`. Returning `H` reads better and is wrong:\n * a handler declared `() => Response` would produce a wrapper the caller cannot\n * pass a request to, which type-checks at the definition and fails at every\n * call site. Caught by `tsc` after the tests were already green.\n */\nexport function observe<Args extends unknown[]>(\n handler: WebHandler<Args>,\n options: ObserveOptions = {},\n): (request: Request, ...rest: Args) => Promise<Response> {\n const config = resolveConfig(options);\n const collector = options.collector ?? createCollector(config);\n\n // ⚠️ **Discovery is Express-only, and silence is the wrong way to say so.**\n //\n // `twin.discovery` generates `/llms.txt`, `/llms-full.txt` and `/install.md`\n // -- and only the Express adapter routes them. A web-runtime customer who\n // configures it has described their content to an index that is never served,\n // and until now got nothing and no error: they would find out when an agent\n // did not.\n //\n // One line, through the existing debug channel, at wiring time rather than\n // per request. Implementing it here is owed; saying nothing is not an option.\n if (options.twin?.discovery) {\n console.warn(\n \"[entreprenoid] twin.discovery is configured but the web adapter does not serve \" +\n \"/llms.txt, /llms-full.txt or /install.md. Serve them from your own router, or use \" +\n \"the Express adapter. See https://entreprenoid.com/install.md\",\n );\n }\n\n return async (request: Request, ...rest: Args): Promise<Response> => {\n // ⚠️ The clock starts OUTSIDE the try. If observation setup were to throw,\n // the handler must still run -- so nothing between here and the call can be\n // allowed to skip it.\n const startedAt = Date.now();\n\n // ── The serve half, BEFORE the handler ───────────────────────────────────\n // ⚠️ Every failure here falls through to the customer's handler. A throw in\n // their resolver, a missing twin, a method we do not answer: the site works\n // normally. A bug in entreprenoid must never be a bug in their website.\n let served: RequestEvent[\"serve\"];\n let twinResponse: Response | undefined;\n\n if (options.twin) {\n const url = new URL(request.url);\n const path = normalisePath(url.pathname, config.redactPatterns);\n const decision = decideTwin({\n method: request.method,\n path,\n accept: request.headers.get(\"accept\"),\n });\n\n if (decision.action === \"serve\") {\n try {\n const found = await options.twin.resolve(decision.lookupPath);\n if (found) {\n const built = buildTwinResponse(found, decision, options.twin);\n served = {\n decision: \"served\",\n reason: decision.reason,\n format: built.headers[\"content-type\"] ?? \"text/markdown\",\n };\n // HEAD asks for what a GET would return, minus the body.\n twinResponse = new Response(\n request.method.toUpperCase() === \"HEAD\" ? null : built.body,\n { status: built.status, headers: built.headers },\n );\n } else {\n served = { decision: \"fell_through\", reason: \"no_twin\" };\n }\n } catch {\n served = { decision: \"error\", reason: \"resolver_error\" };\n }\n } else {\n served = { decision: \"fell_through\", reason: decision.reason };\n }\n }\n\n const response = twinResponse ?? (await handler(request, ...rest));\n\n // Advertise an available twin on a response we did not serve ourselves.\n if (options.twin && !twinResponse && options.twin.advertise !== false) {\n try {\n const path = normalisePath(new URL(request.url).pathname, config.redactPatterns);\n if (await options.twin.resolve(path)) {\n response.headers.append(\"link\", advertiseHeader(path));\n served = { decision: \"advertised\", reason: \"no_twin\" };\n }\n } catch {\n /* advertising is a courtesy; never let it affect the response */\n }\n }\n\n safe(() => {\n if (config.disabled) return;\n\n const url = new URL(request.url);\n const observed = observeRequest(\n {\n method: request.method,\n url: url.pathname + url.search,\n host: url.host,\n protocol: url.protocol === \"https:\" ? \"https\" : \"http\",\n userAgent: request.headers.get(\"user-agent\") ?? undefined,\n referer: request.headers.get(\"referer\") ?? undefined,\n },\n config,\n );\n\n const event: RequestEvent = {\n ...observed,\n eventId: newEventId(),\n siteId: config.siteId,\n observedAt: new Date(startedAt).toISOString(),\n ...(served ? { serve: served } : {}),\n response: observeResponse({\n status: response.status,\n contentType: response.headers.get(\"content-type\"),\n contentLength: response.headers.get(\"content-length\"),\n latencyMs: Date.now() - startedAt,\n observation: \"measured\",\n }),\n sdk: {\n name: SDK_NAME,\n version: SDK_VERSION,\n adapter: \"web\",\n runtime: runtimeName(),\n },\n };\n\n collector.record(event);\n });\n\n if (options.waitUntil) {\n safe(() => options.waitUntil?.(collector.flush()));\n }\n\n return response;\n };\n}\n"]}
|
package/dist/express.cjs
CHANGED