@zerolith/otel 0.1.0-alpha.7 → 0.1.0-alpha.8

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 CHANGED
@@ -16,15 +16,15 @@ tracedHydrate(App(router), "#app", tracer);
16
16
  - `tracedHydrate` wraps `hydrate` in a span and records the number of mismatches.
17
17
  - `tracedTransport` wraps a client transport in a client span per request, with the method, URL, and status, and injects a `traceparent` header from the active span so the server's trace continues the browser's.
18
18
 
19
- The guide is on the [docs site](https://molexxxx.github.io/zerolith/guide/observability/).
19
+ The guide is on the [docs site](https://zerolith.dev/guide/observability/).
20
20
 
21
21
  Status: alpha. Install with `npm install @zerolith/otel@alpha`; the API may change before 1.0.
22
22
 
23
23
  ## Documentation
24
24
 
25
- - The [observability guide](https://molexxxx.github.io/zerolith/guide/observability/) explains it with examples that run as tests.
26
- - The [reference](https://molexxxx.github.io/zerolith/reference/zerolith-otel/) lists every export, generated from the source.
27
- - New to zerolith? The [tutorial](https://molexxxx.github.io/zerolith/learn/) builds an app step by step, and the [playground](https://molexxxx.github.io/zerolith/playground/) runs code in the browser.
25
+ - The [observability guide](https://zerolith.dev/guide/observability/) explains it with examples that run as tests.
26
+ - The [reference](https://zerolith.dev/reference/zerolith-otel/) lists every export, generated from the source.
27
+ - New to zerolith? The [tutorial](https://zerolith.dev/learn/) builds an app step by step, and the [playground](https://zerolith.dev/playground/) runs code in the browser.
28
28
 
29
29
  ## License
30
30
 
@@ -0,0 +1,2 @@
1
+ import{SpanKind as e,SpanStatusCode as t,context as n,trace as r}from"@opentelemetry/api";import{hydrate as i}from"zerolith";function a(){return r.getTracer(`@zerolith/otel`)}const o=new Set([`X-Amz-Signature`,`X-Amz-Credential`,`X-Amz-Security-Token`,`sig`,`X-Goog-Signature`]);function s(e){return e.replace(/^\?/,``).split(`&`).map(e=>{let t=e.split(`=`,1)[0]??``;return o.has(t)?`${t}=REDACTED`:e}).join(`&`)}export function navigationObserver(n=a()){let r=new Map;return i=>{if(i.type===`start`){let t=s(i.url.search),a=n.startSpan(`navigate`,{kind:e.INTERNAL,attributes:{"url.path":i.url.pathname,...t===``?{}:{"url.query":t},"zerolith.navigation.mode":i.mode}});r.set(i.id,a);return}let a=r.get(i.id);if(a!==void 0){if(r.delete(i.id),i.type===`commit`)a.setAttribute(`zerolith.route`,i.name??``),a.setStatus({code:t.OK});else if(i.type===`cancel`)a.setAttribute(`zerolith.navigation.canceled`,!0);else{let e=i.error;a.recordException(e instanceof Error?e:Error(String(e))),a.setStatus({code:t.ERROR,message:e instanceof Error?e.message:String(e)})}a.end()}}}export function tracedHydrate(e,n,r=a()){return r.startActiveSpan(`hydrate`,r=>{try{let a=i(e,n);return r.setAttribute(`zerolith.hydration.mismatches`,a.mismatches.length),r.setStatus({code:t.OK}),a}catch(e){throw r.recordException(e instanceof Error?e:Error(String(e))),r.setStatus({code:t.ERROR}),e}finally{r.end()}})}export function traceparent(e=n.active()){let t=r.getSpanContext(e);if(t!==void 0&&r.isSpanContextValid(t))return`00-${t.traceId}-${t.spanId}-${t.traceFlags.toString(16).padStart(2,`0`)}`}export function tracedTransport(n,r=a()){let i=n??(e=>fetch(e));return n=>{let a=new URL(n.url);return a.search=s(a.search),r.startActiveSpan(n.method,{kind:e.CLIENT,attributes:{"http.request.method":n.method,"url.full":a.href}},async e=>{try{let r=traceparent(),a=r===void 0?n:new Request(n,{headers:c(n.headers,`traceparent`,r)}),o=await i(a);return e.setAttribute(`http.response.status_code`,o.status),e.setStatus(o.ok?{code:t.OK}:{code:t.ERROR,message:`HTTP ${o.status}`}),o}catch(n){throw e.recordException(n instanceof Error?n:Error(String(n))),e.setStatus({code:t.ERROR,message:n instanceof Error?n.message:String(n)}),n}finally{e.end()}})}}function c(e,t,n){let r=new Headers(e);return r.set(t,n),r}
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/index.ts"],"sourcesContent":["/**\n * @zerolith/otel: spans through `@opentelemetry/api` for the three things\n * worth tracing in a zerolith application: a navigation, a hydration, and\n * a request of the typed client. The application supplies the tracer, so\n * the SDK and the exporter stay its choice, and the API's no-op tracer\n * makes every call free until one is configured.\n */\nimport { type Context, type Span, SpanKind, SpanStatusCode, type Tracer, context, trace } from \"@opentelemetry/api\";\nimport { type HydrateResult, type MountTarget, hydrate } from \"zerolith\";\nimport type { Transport } from \"@zerolith/client\";\nimport type { NavigationEvent } from \"@zerolith/router\";\n\n/** The tracer used when none is given: the API's tracer for this package, a no-op until an SDK registers a provider. */\nfunction defaultTracer(): Tracer\n{\n return trace.getTracer(\"@zerolith/otel\");\n}\n\n/** The query keys whose values the URL conventions redact by default, such as a signed URL's signature. */\nconst SENSITIVE_QUERY = new Set([\"X-Amz-Signature\", \"X-Amz-Credential\", \"X-Amz-Security-Token\", \"sig\", \"X-Goog-Signature\"]);\n\n/** A URL's query component without its `?`, with the value of each key the conventions redact replaced by `REDACTED`. */\nfunction redactedQuery(search: string): string\n{\n return search.replace(/^\\?/, \"\").split(\"&\").map((pair) =>\n {\n const key = pair.split(\"=\", 1)[0] ?? \"\";\n return SENSITIVE_QUERY.has(key) ? `${key}=REDACTED` : pair;\n }).join(\"&\");\n}\n\n/**\n * The `observe` function for `createRouter`: a span named `navigate`\n * opens when a navigation starts and ends when it commits, fails, or is\n * canceled by a newer navigation, with `zerolith.route`, `url.path`,\n * `url.query` when the URL has a query, and `zerolith.navigation.mode` as\n * attributes, the loader's error recorded on failure, and\n * `zerolith.navigation.canceled` set on a canceled one, whose status stays\n * unset. The values of the query keys the URL conventions redact, such as\n * `sig`, are recorded as `REDACTED`.\n */\nexport function navigationObserver(tracer: Tracer = defaultTracer()): (event: NavigationEvent) => void\n{\n const open = new Map<number, Span>();\n return (event) =>\n {\n if (event.type === \"start\")\n {\n const query = redactedQuery(event.url.search);\n const span = tracer.startSpan(\"navigate\", {\n kind: SpanKind.INTERNAL,\n attributes: { \"url.path\": event.url.pathname, ...(query === \"\" ? {} : { \"url.query\": query }), \"zerolith.navigation.mode\": event.mode }\n });\n open.set(event.id, span);\n return;\n }\n const span = open.get(event.id);\n if (span === undefined)\n {\n return;\n }\n open.delete(event.id);\n if (event.type === \"commit\")\n {\n span.setAttribute(\"zerolith.route\", event.name ?? \"\");\n span.setStatus({ code: SpanStatusCode.OK });\n }\n else if (event.type === \"cancel\")\n {\n span.setAttribute(\"zerolith.navigation.canceled\", true);\n }\n else\n {\n const error = event.error;\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) });\n }\n span.end();\n };\n}\n\n/** `hydrate` inside a span named `hydrate`, with the number of mismatches as `zerolith.hydration.mismatches`. */\nexport function tracedHydrate(view: unknown, container: MountTarget, tracer: Tracer = defaultTracer()): HydrateResult\n{\n return tracer.startActiveSpan(\"hydrate\", (span) =>\n {\n try\n {\n const result = hydrate(view, container);\n span.setAttribute(\"zerolith.hydration.mismatches\", result.mismatches.length);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n }\n catch (error)\n {\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n }\n finally\n {\n span.end();\n }\n });\n}\n\n/** The W3C `traceparent` header for a context, or undefined when no span is active in it. */\nexport function traceparent(active: Context = context.active()): string | undefined\n{\n const span = trace.getSpanContext(active);\n if (span === undefined || !trace.isSpanContextValid(span))\n {\n return undefined;\n }\n return `00-${span.traceId}-${span.spanId}-${span.traceFlags.toString(16).padStart(2, \"0\")}`;\n}\n\n/**\n * Wraps a transport, or `fetch` when none is given, so every request runs\n * in a client span named by its method, as `GET`, with\n * `http.request.method`, `url.full`, and `http.response.status_code`,\n * carries a `traceparent` header from that span, and records a failed\n * request as an error. The span is not named by the request's path, which\n * would give every note its own span name; the HTTP conventions name a\n * client span by its method when no route template is known. `url.full`\n * records the values of the query keys the conventions redact, such as\n * `sig`, as `REDACTED`.\n */\nexport function tracedTransport(transport: Transport | undefined, tracer: Tracer = defaultTracer()): Transport\n{\n const send: Transport = transport ?? ((request) => fetch(request));\n return (request) =>\n {\n const full = new URL(request.url);\n full.search = redactedQuery(full.search);\n return tracer.startActiveSpan(request.method, { kind: SpanKind.CLIENT, attributes: { \"http.request.method\": request.method, \"url.full\": full.href } }, async (span) =>\n {\n try\n {\n const header = traceparent();\n const traced = header === undefined ? request : new Request(request, { headers: withHeader(request.headers, \"traceparent\", header) });\n const response = await send(traced);\n span.setAttribute(\"http.response.status_code\", response.status);\n span.setStatus(response.ok ? { code: SpanStatusCode.OK } : { code: SpanStatusCode.ERROR, message: `HTTP ${response.status}` });\n return response;\n }\n catch (error)\n {\n span.recordException(error instanceof Error ? error : new Error(String(error)));\n span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error) });\n throw error;\n }\n finally\n {\n span.end();\n }\n });\n };\n}\n\nfunction withHeader(headers: Headers, name: string, value: string): Headers\n{\n const copy = new Headers(headers);\n copy.set(name, value);\n return copy;\n}\n"],"names":["defaultTracer","trace","SENSITIVE_QUERY","redactedQuery","search","key","pair","open","event","query","span","tracer","SpanKind","SpanStatusCode","error","result","hydrate","view","container","context","active","send","transport","request","full","header","traced","withHeader","response","copy","headers","name","value"],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@zerolith/otel",
3
- "version": "0.1.0-alpha.7",
3
+ "version": "0.1.0-alpha.8",
4
4
  "description": "OpenTelemetry spans for zerolith applications through @opentelemetry/api: a span per navigation, per hydration, and per request of the typed client with traceparent propagation",
5
5
  "license": "Apache-2.0",
6
6
  "author": "molexxxx",
7
- "homepage": "https://github.com/molexxxx/zerolith#readme",
7
+ "homepage": "https://zerolith.dev/",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/molexxxx/zerolith.git",
@@ -26,7 +26,8 @@
26
26
  "exports": {
27
27
  ".": {
28
28
  "types": "./dist/index.d.ts",
29
- "default": "./dist/index.js"
29
+ "development": "./dist/index.js",
30
+ "default": "./dist/production/index.js"
30
31
  },
31
32
  "./package.json": "./package.json"
32
33
  },
@@ -37,21 +38,21 @@
37
38
  "README.md"
38
39
  ],
39
40
  "scripts": {
40
- "build": "tsc -p tsconfig.build.json"
41
+ "build": "tsc -p tsconfig.build.json && node ../../scripts/production.ts"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@opentelemetry/api": ">=1.9.0 <2.0.0",
44
- "@zerolith/client": "^0.1.0-alpha.7",
45
- "@zerolith/router": "^0.1.0-alpha.7",
46
- "zerolith": "^0.1.0-alpha.7"
45
+ "@zerolith/client": "^0.1.0-alpha.8",
46
+ "@zerolith/router": "^0.1.0-alpha.8",
47
+ "zerolith": "^0.1.0-alpha.8"
47
48
  },
48
49
  "peerDependenciesMeta": {
49
50
  "@zerolith/client": { "optional": true },
50
51
  "@zerolith/router": { "optional": true }
51
52
  },
52
53
  "devDependencies": {
53
- "@zerolith/client": "^0.1.0-alpha.7",
54
- "@zerolith/router": "^0.1.0-alpha.7",
55
- "zerolith": "^0.1.0-alpha.7"
54
+ "@zerolith/client": "^0.1.0-alpha.8",
55
+ "@zerolith/router": "^0.1.0-alpha.8",
56
+ "zerolith": "^0.1.0-alpha.8"
56
57
  }
57
58
  }