@entreprenoid/analytics 0.1.0
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/README.md +82 -0
- package/dist/chunk-5NZNPRMN.js +355 -0
- package/dist/chunk-5NZNPRMN.js.map +1 -0
- package/dist/chunk-EVNUKN5A.js +114 -0
- package/dist/chunk-EVNUKN5A.js.map +1 -0
- package/dist/chunk-GKFYGAKF.js +104 -0
- package/dist/chunk-GKFYGAKF.js.map +1 -0
- package/dist/chunk-IHZCOC2U.js +690 -0
- package/dist/chunk-IHZCOC2U.js.map +1 -0
- package/dist/core/breaker.d.ts +33 -0
- package/dist/core/collector.d.ts +40 -0
- package/dist/core/config.d.ts +101 -0
- package/dist/core/encode.d.ts +32 -0
- package/dist/core/queue.d.ts +39 -0
- package/dist/core/safe.d.ts +17 -0
- package/dist/core/transport.d.ts +45 -0
- package/dist/express.cjs +1042 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.ts +51 -0
- package/dist/express.js +4 -0
- package/dist/express.js.map +1 -0
- package/dist/index.cjs +1289 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/next.cjs +801 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.ts +90 -0
- package/dist/next.js +4 -0
- package/dist/next.js.map +1 -0
- package/dist/observe/redact.d.ts +58 -0
- package/dist/observe/request.d.ts +57 -0
- package/dist/observe/response.d.ts +24 -0
- package/dist/runtime.d.ts +27 -0
- package/dist/serve/accept.d.ts +7 -0
- package/dist/serve/discovery.d.ts +45 -0
- package/dist/serve/twin.d.ts +102 -0
- package/dist/web.cjs +790 -0
- package/dist/web.cjs.map +1 -0
- package/dist/web.d.ts +43 -0
- package/dist/web.js +4 -0
- package/dist/web.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { resolveConfig, createCollector, normalisePath, decideTwin, buildTwinResponse, advertiseHeader, safe, observeRequest, runtimeName, observeResponse, newEventId, SDK_VERSION, SDK_NAME } from './chunk-IHZCOC2U.js';
|
|
2
|
+
|
|
3
|
+
// src/web.ts
|
|
4
|
+
function observe(handler, options = {}) {
|
|
5
|
+
const config = resolveConfig(options);
|
|
6
|
+
const collector = options.collector ?? createCollector(config);
|
|
7
|
+
if (options.twin?.discovery) {
|
|
8
|
+
console.warn(
|
|
9
|
+
"[entreprenoid] twin.discovery is configured but the web adapter does not serve /llms.txt, /llms-full.txt or /install.md. Serve them from your own router, or use the Express adapter. See https://entreprenoid.com/install.md"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
return async (request, ...rest) => {
|
|
13
|
+
const startedAt = Date.now();
|
|
14
|
+
let served;
|
|
15
|
+
let twinResponse;
|
|
16
|
+
if (options.twin) {
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const path = normalisePath(url.pathname, config.redactPatterns);
|
|
19
|
+
const decision = decideTwin({
|
|
20
|
+
method: request.method,
|
|
21
|
+
path,
|
|
22
|
+
accept: request.headers.get("accept")
|
|
23
|
+
});
|
|
24
|
+
if (decision.action === "serve") {
|
|
25
|
+
try {
|
|
26
|
+
const found = await options.twin.resolve(decision.lookupPath);
|
|
27
|
+
if (found) {
|
|
28
|
+
const built = buildTwinResponse(found, decision, options.twin);
|
|
29
|
+
served = {
|
|
30
|
+
decision: "served",
|
|
31
|
+
reason: decision.reason,
|
|
32
|
+
format: built.headers["content-type"] ?? "text/markdown"
|
|
33
|
+
};
|
|
34
|
+
twinResponse = new Response(
|
|
35
|
+
request.method.toUpperCase() === "HEAD" ? null : built.body,
|
|
36
|
+
{ status: built.status, headers: built.headers }
|
|
37
|
+
);
|
|
38
|
+
} else {
|
|
39
|
+
served = { decision: "fell_through", reason: "no_twin" };
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
served = { decision: "error", reason: "resolver_error" };
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
served = { decision: "fell_through", reason: decision.reason };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const response = twinResponse ?? await handler(request, ...rest);
|
|
49
|
+
if (options.twin && !twinResponse && options.twin.advertise !== false) {
|
|
50
|
+
try {
|
|
51
|
+
const path = normalisePath(new URL(request.url).pathname, config.redactPatterns);
|
|
52
|
+
if (await options.twin.resolve(path)) {
|
|
53
|
+
response.headers.append("link", advertiseHeader(path));
|
|
54
|
+
served = { decision: "advertised", reason: "no_twin" };
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
safe(() => {
|
|
60
|
+
if (config.disabled) return;
|
|
61
|
+
const url = new URL(request.url);
|
|
62
|
+
const observed = observeRequest(
|
|
63
|
+
{
|
|
64
|
+
method: request.method,
|
|
65
|
+
url: url.pathname + url.search,
|
|
66
|
+
host: url.host,
|
|
67
|
+
protocol: url.protocol === "https:" ? "https" : "http",
|
|
68
|
+
userAgent: request.headers.get("user-agent") ?? void 0,
|
|
69
|
+
referer: request.headers.get("referer") ?? void 0
|
|
70
|
+
},
|
|
71
|
+
config
|
|
72
|
+
);
|
|
73
|
+
const event = {
|
|
74
|
+
...observed,
|
|
75
|
+
eventId: newEventId(),
|
|
76
|
+
siteId: config.siteId,
|
|
77
|
+
observedAt: new Date(startedAt).toISOString(),
|
|
78
|
+
...served ? { serve: served } : {},
|
|
79
|
+
response: observeResponse({
|
|
80
|
+
status: response.status,
|
|
81
|
+
contentType: response.headers.get("content-type"),
|
|
82
|
+
contentLength: response.headers.get("content-length"),
|
|
83
|
+
latencyMs: Date.now() - startedAt,
|
|
84
|
+
observation: "measured"
|
|
85
|
+
}),
|
|
86
|
+
sdk: {
|
|
87
|
+
name: SDK_NAME,
|
|
88
|
+
version: SDK_VERSION,
|
|
89
|
+
adapter: "web",
|
|
90
|
+
runtime: runtimeName()
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
collector.record(event);
|
|
94
|
+
});
|
|
95
|
+
if (options.waitUntil) {
|
|
96
|
+
safe(() => options.waitUntil?.(collector.flush()));
|
|
97
|
+
}
|
|
98
|
+
return response;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { observe };
|
|
103
|
+
//# sourceMappingURL=chunk-GKFYGAKF.js.map
|
|
104
|
+
//# sourceMappingURL=chunk-GKFYGAKF.js.map
|
|
@@ -0,0 +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"]}
|