@senzops/apm-worker 1.0.0 → 1.0.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/README.md CHANGED
@@ -37,15 +37,12 @@ Initialize Senzor in the global scope and wrap your `fetch` handler.
37
37
  ```typescript
38
38
  import Senzor from "@senzops/apm-worker";
39
39
 
40
- // 1. Initialize (Global Scope)
41
40
  Senzor.init({
42
41
  apiKey: "sz_apm_...",
43
42
  });
44
43
 
45
44
  export default {
46
- // 2. Wrap the fetch handler
47
45
  fetch: Senzor.worker(async (request, env, ctx) => {
48
- // ... your code ...
49
46
  return new Response("Hello World!");
50
47
  }),
51
48
  };
@@ -55,51 +52,30 @@ export default {
55
52
 
56
53
  ## **⚡ Usage with Nitro / Nuxt**
57
54
 
58
- If you are using **Nitro** (standalone) or **Nuxt** deployed to Cloudflare Workers.
55
+ For **Nitro** (standalone) or **Nuxt**, use a server plugin to instrument the entire application globally without wrapping individual handlers.
59
56
 
60
- ### **1. Create a Server Plugin**
57
+ ### **1. Create a Plugin**
61
58
 
62
- Create a file at `server/plugins/senzor.ts`:
59
+ Create `server/plugins/senzor.ts`:
63
60
 
64
61
  ```typescript
65
62
  import Senzor from "@senzops/apm-worker";
66
63
 
67
64
  export default defineNitroPlugin((nitroApp) => {
68
65
  // 1. Initialize
69
- // Note: For Workers, 'env' vars might need runtime access or define replacement
66
+ // Use process.env or runtime config if available
70
67
  Senzor.init({
71
68
  apiKey: process.env.SENZOR_API_KEY || "sz_apm_...",
72
69
  });
73
70
 
74
- // 2. Hook into request handling
75
- nitroApp.hooks.hook("request", (event) => {
76
- // Nitro hooks don't easily allow wrapping the entire execution context for AsyncLocalStorage yet.
77
- // For full auto-instrumentation, consider wrapping specific event handlers or using the middleware approach below.
78
- });
71
+ // 2. Register Global Instrumentation
72
+ Senzor.nitroPlugin(nitroApp);
79
73
  });
80
74
  ```
81
75
 
82
- ### **Recommended: Wrap Event Handlers**
76
+ ### **2. Configuration**
83
77
 
84
- To ensure `fetch` auto-instrumentation works correctly with `AsyncLocalStorage`, wrap your event handlers.
85
-
86
- ```typescript
87
- // server/api/hello.ts
88
- import Senzor from "@senzops/apm-worker";
89
-
90
- export default Senzor.nitro(
91
- defineEventHandler(async (event) => {
92
- // ✅ This fetch is automatically tracked
93
- await fetch("https://google.com");
94
-
95
- return { hello: "world" };
96
- }),
97
- );
98
- ```
99
-
100
- ### **Configuration (nitro.config.ts)**
101
-
102
- Ensure you enable the node compatibility for Cloudflare.
78
+ Ensure `nodejs_compat` is enabled in your `nitro.config.ts`:
103
79
 
104
80
  ```typescript
105
81
  // nitro.config.ts
@@ -112,6 +88,8 @@ export default defineNitroConfig({
112
88
  });
113
89
  ```
114
90
 
91
+ That's it! Every request to your Nitro app is now traced, and any `fetch` calls made within your API routes will automatically appear as spans in the trace.
92
+
115
93
  ---
116
94
 
117
95
  ## **📋 Production Checklist**
package/dist/index.d.mts CHANGED
@@ -20,6 +20,15 @@ interface TraceController {
20
20
  readonly traceId: string;
21
21
  }
22
22
 
23
+ interface NitroApp {
24
+ h3App: {
25
+ handler: (event: any) => Promise<any>;
26
+ stack: any[];
27
+ };
28
+ hooks: any;
29
+ [key: string]: any;
30
+ }
31
+
23
32
  declare const Senzor: {
24
33
  /**
25
34
  * Initialize the SDK.
@@ -28,14 +37,17 @@ declare const Senzor: {
28
37
  init: (options: SenzorOptions) => void;
29
38
  /**
30
39
  * Wrap your Cloudflare Worker 'fetch' handler.
31
- * Inject trace controller as the 4th argument.
32
40
  */
33
41
  worker: (handler: (request: Request, env: any, ctx: any, trace: TraceController) => Promise<Response>) => (request: Request, env: any, ctx: any) => Promise<Response>;
34
42
  /**
35
- * Wrap a Nitro/H3 event handler.
36
- * Use this for Nuxt or pure Nitro server routes.
43
+ * Wrap a generic H3 event handler.
44
+ */
45
+ wrapH3: (handler: (event: any) => any) => (event: any) => Promise<any>;
46
+ /**
47
+ * Nitro/Nuxt Plugin for global instrumentation.
48
+ * Usage: export default Senzor.nitroPlugin; in server/plugins/senzor.ts
37
49
  */
38
- nitro: (handler: (event: any) => any) => (event: any) => Promise<any>;
50
+ nitroPlugin: (nitroApp: NitroApp) => void;
39
51
  };
40
52
 
41
- export { Senzor, type TraceController, Senzor as default };
53
+ export { type NitroApp, Senzor, type TraceController, Senzor as default };
package/dist/index.d.ts CHANGED
@@ -20,6 +20,15 @@ interface TraceController {
20
20
  readonly traceId: string;
21
21
  }
22
22
 
23
+ interface NitroApp {
24
+ h3App: {
25
+ handler: (event: any) => Promise<any>;
26
+ stack: any[];
27
+ };
28
+ hooks: any;
29
+ [key: string]: any;
30
+ }
31
+
23
32
  declare const Senzor: {
24
33
  /**
25
34
  * Initialize the SDK.
@@ -28,14 +37,17 @@ declare const Senzor: {
28
37
  init: (options: SenzorOptions) => void;
29
38
  /**
30
39
  * Wrap your Cloudflare Worker 'fetch' handler.
31
- * Inject trace controller as the 4th argument.
32
40
  */
33
41
  worker: (handler: (request: Request, env: any, ctx: any, trace: TraceController) => Promise<Response>) => (request: Request, env: any, ctx: any) => Promise<Response>;
34
42
  /**
35
- * Wrap a Nitro/H3 event handler.
36
- * Use this for Nuxt or pure Nitro server routes.
43
+ * Wrap a generic H3 event handler.
44
+ */
45
+ wrapH3: (handler: (event: any) => any) => (event: any) => Promise<any>;
46
+ /**
47
+ * Nitro/Nuxt Plugin for global instrumentation.
48
+ * Usage: export default Senzor.nitroPlugin; in server/plugins/senzor.ts
37
49
  */
38
- nitro: (handler: (event: any) => any) => (event: any) => Promise<any>;
50
+ nitroPlugin: (nitroApp: NitroApp) => void;
39
51
  };
40
52
 
41
- export { Senzor, type TraceController, Senzor as default };
53
+ export { type NitroApp, Senzor, type TraceController, Senzor as default };
@@ -1,2 +1,2 @@
1
- "use strict";(()=>{var z=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(t,e)=>(typeof require<"u"?require:t)[e]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var m=class{constructor(t){this.config=t;this.queue=[]}add(t){this.queue.push(t)}async flush(){if(this.queue.length===0)return;let t=[...this.queue];this.queue=[];try{let e=this.config.endpoint||"https://api.senzor.dev/api/ingest/apm";await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-service-api-key":this.config.apiKey},body:JSON.stringify(t),keepalive:!0}),this.config.debug&&console.log(`[Senzor] Flushed ${t.length} traces`)}catch(e){this.config.debug&&console.error("[Senzor] Ingestion Error:",e)}}};var T=z("async_hooks"),f=new T.AsyncLocalStorage;var w=!1,S=()=>{if(w)return;let r=globalThis.fetch;globalThis.fetch=async(t,e)=>{let n=f.getStore();if(!n)return r(t,e);let o="",a="GET";typeof t=="string"?o=t:t instanceof URL?o=t.toString():t instanceof Request&&(o=t.url,a=t.method),e&&e.method&&(a=e.method);let s=`HTTP ${a}`,c=n.startSpan(s,"http"),l=new Headers(e?.headers||(t instanceof Request?t.headers:{})),i=crypto.randomUUID().replace(/-/g,"").substring(0,16),h=`00-${n.traceId}-${i}-01`;l.set("traceparent",h);let u={...e,headers:l};try{let p=await r(t,u);return c.end({url:o,method:a,status:p.status},p.status),p}catch(p){throw c.end({url:o,method:a,error:p.message},500),p}},w=!0};var g=class{constructor(){this.transport=null;this.options=null}init(t){if(!t.apiKey){console.warn("[Senzor] API Key missing. SDK disabled.");return}this.options=t,this.transport=new m(t);try{S()}catch(e){t.debug&&console.warn("[Senzor] Failed to instrument fetch:",e)}t.debug&&console.log("[Senzor] Initialized for Serverless")}createTrace(t){if(!this.transport)return{controller:{startSpan:()=>({end:()=>{}}),captureException:()=>{},traceId:"00000000000000000000000000000000"},end:()=>{},flush:async()=>{}};let e=crypto.randomUUID().replace(/-/g,""),n=performance.now(),o=[];return{controller:{traceId:e,startSpan:(i,h="custom")=>{let u=performance.now(),p=u-n;return{end:(R,U)=>{o.push({name:i,type:h,startTime:p,duration:performance.now()-u,status:U,meta:R})}}},captureException:i=>{o.push({name:"exception",type:"custom",startTime:performance.now()-n,duration:0,status:500,meta:{error:i.message||String(i),stack:i.stack}})}},end:(i,h="UNKNOWN")=>{let u=performance.now()-n,p={traceId:e,method:t.method||"GET",route:h,path:t.path||"/",status:i,duration:u,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:o};this.transport?.add(p)},flush:async()=>{await this.transport?.flush()}}}track(t){if(!this.transport)return;let e={traceId:crypto.randomUUID(),method:t.method||"GET",route:t.route,path:t.path||"/",status:t.status,duration:t.duration,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:t.spans||[]};this.transport.add(e),this.transport.flush().catch(()=>{})}startTrace(t,e){return e()}endTrace(t,e){}},d=new g;var y=r=>!r||r==="/"?"/":r.replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,":uuid").replace(/[0-9a-fA-F]{24}/g,":objectId").replace(/\/(\d+)(?=\/|$)/g,"/:id").split("?")[0],b=(r,t)=>r.route&&r.route.path?(r.baseUrl||"")+r.route.path:r.context&&r.context.matchedRoute?r.context.matchedRoute.path:r.routerPath?r.routerPath:y(t);var x=r=>async(t,e,n)=>{let a=new URL(t.url).pathname,s=d.createTrace({method:t.method,path:a,userAgent:t.headers.get("user-agent")||void 0,ip:t.headers.get("cf-connecting-ip")||t.headers.get("x-forwarded-for")||void 0});return f.run(s.controller,async()=>{let c,l=500;try{return c=await r(t,e,n,s.controller),l=c.status,c}catch(i){throw s.controller.captureException(i),i}finally{s.end(l,y(a)),n&&typeof n.waitUntil=="function"?n.waitUntil(s.flush()):await s.flush()}})};var I=r=>async t=>{let e=t.node.req,n=e.originalUrl||e.url||"/",o=d.createTrace({method:e.method||"GET",path:n,ip:e.headers["x-forwarded-for"]||e.socket?.remoteAddress||t.context?.cf?.connectingIp,userAgent:e.headers["user-agent"]});return f.run(o.controller,async()=>{let a,s=200;try{return a=await r(t),t.node.res.statusCode&&(s=t.node.res.statusCode),a&&a.statusCode&&(s=a.statusCode),a}catch(c){throw s=c.statusCode||c.status||500,o.controller.captureException(c),c}finally{o.end(s,b(t,n));let l=(t.context?.cloudflare?.context||t.context?.cf)?.waitUntil||t.waitUntil;l&&typeof l=="function"?l(o.flush()):o.flush().catch(()=>{})}})};var A={init:r=>d.init(r),worker:x,nitro:I},Q=A;})();
1
+ "use strict";(()=>{var z=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(t,e)=>(typeof require<"u"?require:t)[e]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var m=class{constructor(t){this.config=t;this.queue=[]}add(t){this.queue.push(t)}async flush(){if(this.queue.length===0)return;let t=[...this.queue];this.queue=[];try{let e=this.config.endpoint||"https://api.senzor.dev/api/ingest/apm";await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-service-api-key":this.config.apiKey},body:JSON.stringify(t),keepalive:!0}),this.config.debug&&console.log(`[Senzor] Flushed ${t.length} traces`)}catch(e){this.config.debug&&console.error("[Senzor] Ingestion Error:",e)}}};var T=z("async_hooks"),d=new T.AsyncLocalStorage;var x=!1,S=()=>{if(x)return;let r=globalThis.fetch;globalThis.fetch=async(t,e)=>{let o=d.getStore();if(!o)return r(t,e);let a="",i="GET";typeof t=="string"?a=t:t instanceof URL?a=t.toString():t instanceof Request&&(a=t.url,i=t.method),e&&e.method&&(i=e.method);let n=`HTTP ${i}`,s=o.startSpan(n,"http"),p=new Headers(e?.headers||(t instanceof Request?t.headers:{})),c=crypto.randomUUID().replace(/-/g,"").substring(0,16),u=`00-${o.traceId}-${c}-01`;p.set("traceparent",u);let h={...e,headers:p};try{let l=await r(t,h);return s.end({url:a,method:i,status:l.status},l.status),l}catch(l){throw s.end({url:a,method:i,error:l.message},500),l}},x=!0};var y=class{constructor(){this.transport=null;this.options=null}init(t){if(!t.apiKey){console.warn("[Senzor] API Key missing. SDK disabled.");return}this.options=t,this.transport=new m(t);try{S()}catch(e){t.debug&&console.warn("[Senzor] Failed to instrument fetch:",e)}t.debug&&console.log("[Senzor] Initialized for Serverless")}createTrace(t){if(!this.transport)return{controller:{startSpan:()=>({end:()=>{}}),captureException:()=>{},traceId:"00000000000000000000000000000000"},end:()=>{},flush:async()=>{}};let e=crypto.randomUUID().replace(/-/g,""),o=performance.now(),a=[];return{controller:{traceId:e,startSpan:(c,u="custom")=>{let h=performance.now(),l=h-o;return{end:(U,C)=>{a.push({name:c,type:u,startTime:l,duration:performance.now()-h,status:C,meta:U})}}},captureException:c=>{a.push({name:"exception",type:"custom",startTime:performance.now()-o,duration:0,status:500,meta:{error:c.message||String(c),stack:c.stack}})}},end:(c,u="UNKNOWN")=>{let h=performance.now()-o,l={traceId:e,method:t.method||"GET",route:u,path:t.path||"/",status:c,duration:h,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:a};this.transport?.add(l)},flush:async()=>{await this.transport?.flush()}}}track(t){if(!this.transport)return;let e={traceId:crypto.randomUUID(),method:t.method||"GET",route:t.route,path:t.path||"/",status:t.status,duration:t.duration,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:t.spans||[]};this.transport.add(e),this.transport.flush().catch(()=>{})}startTrace(t,e){return e()}endTrace(t,e){}},f=new y;var w=r=>!r||r==="/"?"/":r.replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,":uuid").replace(/[0-9a-fA-F]{24}/g,":objectId").replace(/\/(\d+)(?=\/|$)/g,"/:id").split("?")[0],g=(r,t)=>r.route&&r.route.path?(r.baseUrl||"")+r.route.path:r.context&&r.context.matchedRoute?r.context.matchedRoute.path:r.routerPath?r.routerPath:w(t);var b=r=>async(t,e,o)=>{let i=new URL(t.url).pathname,n=f.createTrace({method:t.method,path:i,userAgent:t.headers.get("user-agent")||void 0,ip:t.headers.get("cf-connecting-ip")||t.headers.get("x-forwarded-for")||void 0});return d.run(n.controller,async()=>{let s,p=500;try{return s=await r(t,e,o,n.controller),p=s.status,s}catch(c){throw n.controller.captureException(c),c}finally{n.end(p,w(i)),o&&typeof o.waitUntil=="function"?o.waitUntil(n.flush()):await n.flush()}})};var A=r=>async t=>{let e=t.node.req,o=e.originalUrl||e.url||"/",a=f.createTrace({method:e.method||"GET",path:o,ip:e.headers["x-forwarded-for"]||e.socket?.remoteAddress||t.context?.cf?.connectingIp,userAgent:e.headers["user-agent"]});return d.run(a.controller,async()=>{let i,n=200;try{return i=await r(t),t.node.res.statusCode&&(n=t.node.res.statusCode),i&&i.statusCode&&(n=i.statusCode),i}catch(s){throw n=s.statusCode||s.status||500,a.controller.captureException(s),s}finally{a.end(n,g(t,o));let p=(t.context?.cloudflare?.context||t.context?.cf)?.waitUntil||t.waitUntil;p&&typeof p=="function"?p(a.flush()):a.flush().catch(()=>{})}})};var I=r=>{if(!r.h3App||!r.h3App.handler){f.options?.debug&&console.warn("[Senzor] NitroApp.h3App.handler not found. Skipping instrumentation.");return}let t=r.h3App.handler;r.h3App.handler=async e=>{let o=e.node?.req||e.req,a=o?.originalUrl||o?.url||e.path||"/",i=o?.method||e.method||"GET",n=f.createTrace({method:i,path:a,ip:o?.headers?.["x-forwarded-for"]||o?.socket?.remoteAddress||e.context?.cf?.connectingIp,userAgent:o?.headers?.["user-agent"]});return d.run(n.controller,async()=>{let s,p=200;try{return s=await t(e),e.node?.res?.statusCode&&(p=e.node.res.statusCode),s?.status&&(p=s.status),s}catch(c){throw p=c.statusCode||c.status||500,n.controller.captureException(c),c}finally{n.end(p,g(e,a));let u=(e.context?.cloudflare?.context||e.context?.cf||e.context)?.waitUntil||e.waitUntil;u&&typeof u=="function"?u(n.flush()):n.flush().catch(()=>{})}})}};var P={init:r=>f.init(r),worker:b,wrapH3:A,nitroPlugin:I},et=P;})();
2
2
  //# sourceMappingURL=index.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/transport.ts","../src/core/context.ts","../src/instrumentation/fetch.ts","../src/core/client.ts","../src/core/normalizer.ts","../src/wrappers/worker.ts","../src/wrappers/h3.ts","../src/index.ts"],"sourcesContent":["import { SenzorOptions, TraceData } from './types';\r\n\r\nexport class Transport {\r\n private queue: TraceData[] = [];\r\n\r\n constructor(private config: SenzorOptions) { }\r\n\r\n public add(trace: TraceData) {\r\n this.queue.push(trace);\r\n }\r\n\r\n /**\r\n * Flushes the queue to the API. \r\n * Returns a promise that should be passed to ctx.waitUntil()\r\n */\r\n public async flush(): Promise<void> {\r\n if (this.queue.length === 0) return;\r\n\r\n const batch = [...this.queue];\r\n this.queue = []; // Clear immediately\r\n\r\n try {\r\n const endpoint = this.config.endpoint || 'https://api.senzor.dev/api/ingest/apm';\r\n\r\n await fetch(endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'x-service-api-key': this.config.apiKey,\r\n },\r\n body: JSON.stringify(batch),\r\n // keepalive is not strictly necessary in workers if awaited/waitUntil'd, but good practice\r\n keepalive: true,\r\n });\r\n\r\n if (this.config.debug) console.log(`[Senzor] Flushed ${batch.length} traces`);\r\n } catch (err) {\r\n if (this.config.debug) console.error('[Senzor] Ingestion Error:', err);\r\n }\r\n }\r\n}\r\n","import { AsyncLocalStorage } from 'node:async_hooks';\r\nimport { TraceController } from './types';\r\n\r\n// This relies on the 'nodejs_compat' compatibility flag in Cloudflare Workers.\r\nexport const storage = new AsyncLocalStorage<TraceController>();\r\n","import { storage } from '../core/context';\r\n\r\nlet isInstrumented = false;\r\n\r\nexport const enableFetchInstrumentation = () => {\r\n if (isInstrumented) return;\r\n\r\n const originalFetch = globalThis.fetch;\r\n\r\n // Monkey-patch global fetch\r\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\r\n // 1. Check for active trace context\r\n const controller = storage.getStore();\r\n\r\n // If no trace is active, just pass through\r\n if (!controller) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n // 2. Resolve URL and Method\r\n let url = '';\r\n let method = 'GET';\r\n\r\n if (typeof input === 'string') {\r\n url = input;\r\n } else if (input instanceof URL) {\r\n url = input.toString();\r\n } else if (input instanceof Request) {\r\n url = input.url;\r\n method = input.method;\r\n }\r\n\r\n if (init && init.method) {\r\n method = init.method;\r\n }\r\n\r\n // 3. Start Span\r\n const spanName = `HTTP ${method}`;\r\n const span = controller.startSpan(spanName, 'http');\r\n\r\n // 4. Inject Trace Headers (W3C Trace Context)\r\n // We clone headers to avoid side effects on the input object\r\n const headers = new Headers(init?.headers || (input instanceof Request ? input.headers : {}));\r\n\r\n // Generate a span ID for the outgoing request\r\n const spanId = crypto.randomUUID().replace(/-/g, '').substring(0, 16);\r\n const traceParent = `00-${controller.traceId}-${spanId}-01`;\r\n headers.set('traceparent', traceParent);\r\n\r\n const newInit: RequestInit = {\r\n ...init,\r\n headers\r\n };\r\n\r\n try {\r\n const response = await originalFetch(input, newInit);\r\n span.end({\r\n url,\r\n method,\r\n status: response.status\r\n }, response.status);\r\n return response;\r\n } catch (err: any) {\r\n span.end({\r\n url,\r\n method,\r\n error: err.message\r\n }, 500);\r\n throw err;\r\n }\r\n };\r\n\r\n isInstrumented = true;\r\n};\r\n","import { Transport } from './transport';\r\nimport { SenzorOptions, TraceData, Span, TraceController } from './types';\r\nimport { enableFetchInstrumentation } from '../instrumentation/fetch';\r\n\r\nexport class SenzorClient {\r\n private transport: Transport | null = null;\r\n private options: SenzorOptions | null = null;\r\n\r\n public init(options: SenzorOptions) {\r\n if (!options.apiKey) {\r\n console.warn('[Senzor] API Key missing. SDK disabled.');\r\n return;\r\n }\r\n this.options = options;\r\n this.transport = new Transport(options);\r\n\r\n // Auto-instrument global fetch\r\n try {\r\n enableFetchInstrumentation();\r\n } catch (e) {\r\n if (options.debug) console.warn('[Senzor] Failed to instrument fetch:', e);\r\n }\r\n\r\n if (options.debug) console.log('[Senzor] Initialized for Serverless');\r\n }\r\n\r\n /**\r\n * Creates a detached trace session.\r\n */\r\n public createTrace(data: Partial<TraceData>): { controller: TraceController, end: (status: number, route?: string) => void, flush: () => Promise<void> } {\r\n if (!this.transport) {\r\n // Return dummy if not initialized\r\n return {\r\n controller: {\r\n startSpan: () => ({ end: () => { } }),\r\n captureException: () => { },\r\n traceId: '00000000000000000000000000000000'\r\n },\r\n end: () => { },\r\n flush: async () => { }\r\n };\r\n }\r\n\r\n const traceId = crypto.randomUUID().replace(/-/g, ''); // 32 hex chars usually\r\n const startTime = performance.now();\r\n const spans: Span[] = [];\r\n\r\n const startSpan = (name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') => {\r\n const spanStartAbs = performance.now();\r\n const startRel = spanStartAbs - startTime;\r\n return {\r\n end: (meta?: any, status?: number) => {\r\n spans.push({\r\n name,\r\n type,\r\n startTime: startRel, // Relative to trace start\r\n duration: performance.now() - spanStartAbs,\r\n status,\r\n meta\r\n });\r\n }\r\n };\r\n };\r\n\r\n const controller: TraceController = {\r\n traceId,\r\n startSpan,\r\n captureException: (err: any) => {\r\n spans.push({\r\n name: 'exception',\r\n type: 'custom',\r\n startTime: performance.now() - startTime,\r\n duration: 0,\r\n status: 500,\r\n meta: { error: err.message || String(err), stack: err.stack }\r\n });\r\n }\r\n };\r\n\r\n const end = (status: number, route: string = 'UNKNOWN') => {\r\n const duration = performance.now() - startTime;\r\n const payload: TraceData = {\r\n traceId,\r\n method: data.method || 'GET',\r\n route,\r\n path: data.path || '/',\r\n status,\r\n duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans\r\n };\r\n this.transport?.add(payload);\r\n };\r\n\r\n const flush = async () => {\r\n await this.transport?.flush();\r\n };\r\n\r\n return { controller, end, flush };\r\n }\r\n\r\n /**\r\n * Track a single request trace immediately.\r\n */\r\n public track(data: Partial<TraceData> & { status: number, duration: number, route: string }) {\r\n if (!this.transport) return;\r\n\r\n const payload: TraceData = {\r\n traceId: crypto.randomUUID(),\r\n method: data.method || 'GET',\r\n route: data.route,\r\n path: data.path || '/',\r\n status: data.status,\r\n duration: data.duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans: data.spans || []\r\n };\r\n\r\n this.transport.add(payload);\r\n this.transport.flush().catch(() => { });\r\n }\r\n\r\n // Stubs for legacy Node support\r\n public startTrace<T>(data: Partial<TraceData>, callback: () => T): T { return callback(); }\r\n public endTrace(status: number, data?: { route?: string }) { }\r\n}\r\n\r\nexport const client = new SenzorClient();\r\n","/**\r\n * Heuristic URL Normalizer\r\n * Converts raw paths with IDs into generic patterns to prevent high cardinality.\r\n * Example: /users/123/orders/abc-def -> /users/:id/orders/:uuid\r\n */\r\nexport const normalizePath = (path: string): string => {\r\n if (!path || path === '/') return '/';\r\n\r\n return path\r\n // Replace UUIDs (long alphanumeric strings)\r\n .replace(\r\n /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,\r\n ':uuid'\r\n )\r\n // Replace MongoDB ObjectIds (24 hex chars)\r\n .replace(/[0-9a-fA-F]{24}/g, ':objectId')\r\n // Replace pure numeric IDs (e.g., /123)\r\n .replace(/\\/(\\d+)(?=\\/|$)/g, '/:id')\r\n // Remove query strings\r\n .split('?')[0];\r\n};\r\n\r\n/**\r\n * Tries to extract route from Framework internals, falls back to heuristic\r\n */\r\nexport const getRoute = (req: any, fallbackPath: string): string => {\r\n // Express / Connect\r\n if (req.route && req.route.path) {\r\n return (req.baseUrl || '') + req.route.path;\r\n }\r\n\r\n // H3 / Nitro (Nuxt)\r\n if (req.context && req.context.matchedRoute) {\r\n return req.context.matchedRoute.path;\r\n }\r\n\r\n // Fastify\r\n if (req.routerPath) {\r\n return req.routerPath;\r\n }\r\n\r\n // Fallback: Heuristic Normalization\r\n return normalizePath(fallbackPath);\r\n};","import { client } from '../core/client';\r\nimport { normalizePath } from '../core/normalizer';\r\nimport { TraceController } from '../core/types';\r\nimport { storage } from '../core/context';\r\n\r\ntype WorkerHandler = (request: Request, env: any, ctx: any, trace: TraceController) => Promise<Response>;\r\n\r\nexport const wrapWorker = (handler: WorkerHandler) => {\r\n return async (request: Request, env: any, ctx: any) => {\r\n // 2. Extract Request Info\r\n const url = new URL(request.url);\r\n const path = url.pathname;\r\n\r\n // 3. Start Trace\r\n const session = client.createTrace({\r\n method: request.method,\r\n path: path,\r\n userAgent: request.headers.get('user-agent') || undefined,\r\n ip: request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for') || undefined\r\n });\r\n\r\n // 4. Run Handler within Context (AsyncLocalStorage)\r\n // This enables the global fetch instrumentation to find the current trace controller\r\n return storage.run(session.controller, async () => {\r\n let response: Response;\r\n let status = 500;\r\n\r\n try {\r\n // Inject trace controller as 4th arg for manual usage\r\n response = await handler(request, env, ctx, session.controller);\r\n status = response.status;\r\n return response;\r\n } catch (err: any) {\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 5. End Trace\r\n session.end(status, normalizePath(path));\r\n\r\n // 6. Flush (Async WaitUntil)\r\n if (ctx && typeof ctx.waitUntil === 'function') {\r\n ctx.waitUntil(session.flush());\r\n } else {\r\n await session.flush();\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Minimal types for H3 to avoid peer-deps\r\ntype EventHandler = (event: any) => any;\r\n\r\nexport const wrapH3 = (handler: EventHandler) => {\r\n return async (event: any) => {\r\n const req = event.node.req;\r\n const path = req.originalUrl || req.url || '/';\r\n\r\n // 1. Start Trace Session\r\n const session = client.createTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req.headers['user-agent'],\r\n });\r\n\r\n // 2. Run Handler within AsyncLocalStorage Context\r\n // This ensures global fetch() auto-instrumentation works inside Nitro handlers\r\n return storage.run(session.controller, async () => {\r\n let response: any;\r\n let status = 200;\r\n\r\n try {\r\n response = await handler(event);\r\n\r\n // H3/Nitro response status handling\r\n if (event.node.res.statusCode) status = event.node.res.statusCode;\r\n if (response && response.statusCode) status = response.statusCode;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush Data (Non-blocking for Cloudflare)\r\n // Nitro exposes Cloudflare context in event.context.cloudflare\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf;\r\n // Or sometimes directly on event in newer H3 versions if adapter binds it\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n // If not in a worker environment or waitUntil missing, flush async but don't block response significantly\r\n // (Note: without waitUntil, the runtime might kill the process before flush completes)\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from './core/client';\r\nimport { wrapWorker } from './wrappers/worker';\r\nimport { wrapH3 } from './wrappers/h3';\r\nimport { SenzorOptions } from './core/types';\r\n\r\nconst Senzor = {\r\n /**\r\n * Initialize the SDK.\r\n * Call this in the global scope of your Worker or Plugin.\r\n */\r\n init: (options: SenzorOptions) => client.init(options),\r\n\r\n /**\r\n * Wrap your Cloudflare Worker 'fetch' handler.\r\n * Inject trace controller as the 4th argument.\r\n */\r\n worker: wrapWorker,\r\n\r\n /**\r\n * Wrap a Nitro/H3 event handler.\r\n * Use this for Nuxt or pure Nitro server routes.\r\n */\r\n nitro: wrapH3\r\n};\r\n\r\nexport default Senzor;\r\nexport { Senzor };\r\nexport type { TraceController } from './core/types';\r\n"],"mappings":"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","names":["Transport","config","trace","batch","endpoint","err","import_node_async_hooks","storage","isInstrumented","enableFetchInstrumentation","originalFetch","input","init","controller","storage","url","method","spanName","span","headers","spanId","traceParent","newInit","response","err","SenzorClient","options","Transport","enableFetchInstrumentation","data","traceId","startTime","spans","name","type","spanStartAbs","startRel","meta","status","err","route","duration","payload","callback","client","normalizePath","path","getRoute","req","fallbackPath","wrapWorker","handler","request","env","ctx","path","session","client","storage","response","status","err","normalizePath","wrapH3","handler","event","req","path","session","client","storage","response","status","err","getRoute","waitUntil","Senzor","options","client","wrapWorker","wrapH3","index_default"]}
1
+ {"version":3,"sources":["../src/core/transport.ts","../src/core/context.ts","../src/instrumentation/fetch.ts","../src/core/client.ts","../src/core/normalizer.ts","../src/wrappers/worker.ts","../src/wrappers/h3.ts","../src/wrappers/nitro.ts","../src/index.ts"],"sourcesContent":["import { SenzorOptions, TraceData } from './types';\r\n\r\nexport class Transport {\r\n private queue: TraceData[] = [];\r\n\r\n constructor(private config: SenzorOptions) { }\r\n\r\n public add(trace: TraceData) {\r\n this.queue.push(trace);\r\n }\r\n\r\n /**\r\n * Flushes the queue to the API. \r\n * Returns a promise that should be passed to ctx.waitUntil()\r\n */\r\n public async flush(): Promise<void> {\r\n if (this.queue.length === 0) return;\r\n\r\n const batch = [...this.queue];\r\n this.queue = []; // Clear immediately\r\n\r\n try {\r\n const endpoint = this.config.endpoint || 'https://api.senzor.dev/api/ingest/apm';\r\n\r\n await fetch(endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'x-service-api-key': this.config.apiKey,\r\n },\r\n body: JSON.stringify(batch),\r\n // keepalive is not strictly necessary in workers if awaited/waitUntil'd, but good practice\r\n keepalive: true,\r\n });\r\n\r\n if (this.config.debug) console.log(`[Senzor] Flushed ${batch.length} traces`);\r\n } catch (err) {\r\n if (this.config.debug) console.error('[Senzor] Ingestion Error:', err);\r\n }\r\n }\r\n}\r\n","import { AsyncLocalStorage } from 'node:async_hooks';\r\nimport { TraceController } from './types';\r\n\r\n// This relies on the 'nodejs_compat' compatibility flag in Cloudflare Workers.\r\nexport const storage = new AsyncLocalStorage<TraceController>();\r\n","import { storage } from '../core/context';\r\n\r\nlet isInstrumented = false;\r\n\r\nexport const enableFetchInstrumentation = () => {\r\n if (isInstrumented) return;\r\n\r\n const originalFetch = globalThis.fetch;\r\n\r\n // Monkey-patch global fetch\r\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\r\n // 1. Check for active trace context\r\n const controller = storage.getStore();\r\n\r\n // If no trace is active, just pass through\r\n if (!controller) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n // 2. Resolve URL and Method\r\n let url = '';\r\n let method = 'GET';\r\n\r\n if (typeof input === 'string') {\r\n url = input;\r\n } else if (input instanceof URL) {\r\n url = input.toString();\r\n } else if (input instanceof Request) {\r\n url = input.url;\r\n method = input.method;\r\n }\r\n\r\n if (init && init.method) {\r\n method = init.method;\r\n }\r\n\r\n // 3. Start Span\r\n const spanName = `HTTP ${method}`;\r\n const span = controller.startSpan(spanName, 'http');\r\n\r\n // 4. Inject Trace Headers (W3C Trace Context)\r\n // We clone headers to avoid side effects on the input object\r\n const headers = new Headers(init?.headers || (input instanceof Request ? input.headers : {}));\r\n\r\n // Generate a span ID for the outgoing request\r\n const spanId = crypto.randomUUID().replace(/-/g, '').substring(0, 16);\r\n const traceParent = `00-${controller.traceId}-${spanId}-01`;\r\n headers.set('traceparent', traceParent);\r\n\r\n const newInit: RequestInit = {\r\n ...init,\r\n headers\r\n };\r\n\r\n try {\r\n const response = await originalFetch(input, newInit);\r\n span.end({\r\n url,\r\n method,\r\n status: response.status\r\n }, response.status);\r\n return response;\r\n } catch (err: any) {\r\n span.end({\r\n url,\r\n method,\r\n error: err.message\r\n }, 500);\r\n throw err;\r\n }\r\n };\r\n\r\n isInstrumented = true;\r\n};\r\n","import { Transport } from './transport';\r\nimport { SenzorOptions, TraceData, Span, TraceController } from './types';\r\nimport { enableFetchInstrumentation } from '../instrumentation/fetch';\r\n\r\nexport class SenzorClient {\r\n private transport: Transport | null = null;\r\n public options: SenzorOptions | null = null;\r\n\r\n public init(options: SenzorOptions) {\r\n if (!options.apiKey) {\r\n console.warn('[Senzor] API Key missing. SDK disabled.');\r\n return;\r\n }\r\n this.options = options;\r\n this.transport = new Transport(options);\r\n\r\n // Auto-instrument global fetch\r\n try {\r\n enableFetchInstrumentation();\r\n } catch (e) {\r\n if (options.debug) console.warn('[Senzor] Failed to instrument fetch:', e);\r\n }\r\n\r\n if (options.debug) console.log('[Senzor] Initialized for Serverless');\r\n }\r\n\r\n /**\r\n * Creates a detached trace session.\r\n */\r\n public createTrace(data: Partial<TraceData>): { controller: TraceController, end: (status: number, route?: string) => void, flush: () => Promise<void> } {\r\n if (!this.transport) {\r\n // Return dummy if not initialized\r\n return {\r\n controller: {\r\n startSpan: () => ({ end: () => { } }),\r\n captureException: () => { },\r\n traceId: '00000000000000000000000000000000'\r\n },\r\n end: () => { },\r\n flush: async () => { }\r\n };\r\n }\r\n\r\n const traceId = crypto.randomUUID().replace(/-/g, ''); // 32 hex chars usually\r\n const startTime = performance.now();\r\n const spans: Span[] = [];\r\n\r\n const startSpan = (name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') => {\r\n const spanStartAbs = performance.now();\r\n const startRel = spanStartAbs - startTime;\r\n return {\r\n end: (meta?: any, status?: number) => {\r\n spans.push({\r\n name,\r\n type,\r\n startTime: startRel, // Relative to trace start\r\n duration: performance.now() - spanStartAbs,\r\n status,\r\n meta\r\n });\r\n }\r\n };\r\n };\r\n\r\n const controller: TraceController = {\r\n traceId,\r\n startSpan,\r\n captureException: (err: any) => {\r\n spans.push({\r\n name: 'exception',\r\n type: 'custom',\r\n startTime: performance.now() - startTime,\r\n duration: 0,\r\n status: 500,\r\n meta: { error: err.message || String(err), stack: err.stack }\r\n });\r\n }\r\n };\r\n\r\n const end = (status: number, route: string = 'UNKNOWN') => {\r\n const duration = performance.now() - startTime;\r\n const payload: TraceData = {\r\n traceId,\r\n method: data.method || 'GET',\r\n route,\r\n path: data.path || '/',\r\n status,\r\n duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans\r\n };\r\n this.transport?.add(payload);\r\n };\r\n\r\n const flush = async () => {\r\n await this.transport?.flush();\r\n };\r\n\r\n return { controller, end, flush };\r\n }\r\n\r\n /**\r\n * Track a single request trace immediately.\r\n */\r\n public track(data: Partial<TraceData> & { status: number, duration: number, route: string }) {\r\n if (!this.transport) return;\r\n\r\n const payload: TraceData = {\r\n traceId: crypto.randomUUID(),\r\n method: data.method || 'GET',\r\n route: data.route,\r\n path: data.path || '/',\r\n status: data.status,\r\n duration: data.duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans: data.spans || []\r\n };\r\n\r\n this.transport.add(payload);\r\n this.transport.flush().catch(() => { });\r\n }\r\n\r\n // Stubs for legacy Node support\r\n public startTrace<T>(data: Partial<TraceData>, callback: () => T): T { return callback(); }\r\n public endTrace(status: number, data?: { route?: string }) { }\r\n}\r\n\r\nexport const client = new SenzorClient();","/**\r\n * Heuristic URL Normalizer\r\n * Converts raw paths with IDs into generic patterns to prevent high cardinality.\r\n * Example: /users/123/orders/abc-def -> /users/:id/orders/:uuid\r\n */\r\nexport const normalizePath = (path: string): string => {\r\n if (!path || path === '/') return '/';\r\n\r\n return path\r\n // Replace UUIDs (long alphanumeric strings)\r\n .replace(\r\n /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,\r\n ':uuid'\r\n )\r\n // Replace MongoDB ObjectIds (24 hex chars)\r\n .replace(/[0-9a-fA-F]{24}/g, ':objectId')\r\n // Replace pure numeric IDs (e.g., /123)\r\n .replace(/\\/(\\d+)(?=\\/|$)/g, '/:id')\r\n // Remove query strings\r\n .split('?')[0];\r\n};\r\n\r\n/**\r\n * Tries to extract route from Framework internals, falls back to heuristic\r\n */\r\nexport const getRoute = (req: any, fallbackPath: string): string => {\r\n // Express / Connect\r\n if (req.route && req.route.path) {\r\n return (req.baseUrl || '') + req.route.path;\r\n }\r\n\r\n // H3 / Nitro (Nuxt)\r\n if (req.context && req.context.matchedRoute) {\r\n return req.context.matchedRoute.path;\r\n }\r\n\r\n // Fastify\r\n if (req.routerPath) {\r\n return req.routerPath;\r\n }\r\n\r\n // Fallback: Heuristic Normalization\r\n return normalizePath(fallbackPath);\r\n};","import { client } from '../core/client';\r\nimport { normalizePath } from '../core/normalizer';\r\nimport { TraceController } from '../core/types';\r\nimport { storage } from '../core/context';\r\n\r\ntype WorkerHandler = (request: Request, env: any, ctx: any, trace: TraceController) => Promise<Response>;\r\n\r\nexport const wrapWorker = (handler: WorkerHandler) => {\r\n return async (request: Request, env: any, ctx: any) => {\r\n // 2. Extract Request Info\r\n const url = new URL(request.url);\r\n const path = url.pathname;\r\n\r\n // 3. Start Trace\r\n const session = client.createTrace({\r\n method: request.method,\r\n path: path,\r\n userAgent: request.headers.get('user-agent') || undefined,\r\n ip: request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for') || undefined\r\n });\r\n\r\n // 4. Run Handler within Context (AsyncLocalStorage)\r\n // This enables the global fetch instrumentation to find the current trace controller\r\n return storage.run(session.controller, async () => {\r\n let response: Response;\r\n let status = 500;\r\n\r\n try {\r\n // Inject trace controller as 4th arg for manual usage\r\n response = await handler(request, env, ctx, session.controller);\r\n status = response.status;\r\n return response;\r\n } catch (err: any) {\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 5. End Trace\r\n session.end(status, normalizePath(path));\r\n\r\n // 6. Flush (Async WaitUntil)\r\n if (ctx && typeof ctx.waitUntil === 'function') {\r\n ctx.waitUntil(session.flush());\r\n } else {\r\n await session.flush();\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Minimal types for H3 to avoid peer-deps\r\ntype EventHandler = (event: any) => any;\r\n\r\nexport const wrapH3 = (handler: EventHandler) => {\r\n return async (event: any) => {\r\n const req = event.node.req;\r\n const path = req.originalUrl || req.url || '/';\r\n\r\n // 1. Start Trace Session\r\n const session = client.createTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req.headers['user-agent'],\r\n });\r\n\r\n // 2. Run Handler within AsyncLocalStorage Context\r\n // This ensures global fetch() auto-instrumentation works inside Nitro handlers\r\n return storage.run(session.controller, async () => {\r\n let response: any;\r\n let status = 200;\r\n\r\n try {\r\n response = await handler(event);\r\n\r\n // H3/Nitro response status handling\r\n if (event.node.res.statusCode) status = event.node.res.statusCode;\r\n if (response && response.statusCode) status = response.statusCode;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush Data (Non-blocking for Cloudflare)\r\n // Nitro exposes Cloudflare context in event.context.cloudflare\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf;\r\n // Or sometimes directly on event in newer H3 versions if adapter binds it\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n // If not in a worker environment or waitUntil missing, flush async but don't block response significantly\r\n // (Note: without waitUntil, the runtime might kill the process before flush completes)\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Type definitions for NitroApp to avoid heavy dependencies\r\nexport interface NitroApp {\r\n h3App: {\r\n handler: (event: any) => Promise<any>;\r\n stack: any[];\r\n };\r\n hooks: any;\r\n [key: string]: any;\r\n}\r\n\r\n/**\r\n * A Nitro Plugin to automatically instrument the entire application.\r\n * Usage: Create 'server/plugins/senzor.ts' and export default senzorPlugin;\r\n */\r\nexport const senzorPlugin = (nitroApp: NitroApp) => {\r\n // Capture the original handler\r\n if (!nitroApp.h3App || !nitroApp.h3App.handler) {\r\n if (client.options?.debug) console.warn('[Senzor] NitroApp.h3App.handler not found. Skipping instrumentation.');\r\n return;\r\n }\r\n\r\n const originalHandler = nitroApp.h3App.handler;\r\n\r\n // Replace the main H3 app handler with a wrapped version\r\n nitroApp.h3App.handler = async (event: any) => {\r\n const req = event.node?.req || event.req;\r\n // Fallback for H3 event structure\r\n const path = req?.originalUrl || req?.url || (event.path as string) || '/';\r\n const method = req?.method || (event.method as string) || 'GET';\r\n\r\n // 1. Start Trace\r\n const session = client.createTrace({\r\n method: method,\r\n path: path,\r\n ip: req?.headers?.['x-forwarded-for'] || req?.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req?.headers?.['user-agent'],\r\n });\r\n\r\n // 2. Run execution inside AsyncLocalStorage context\r\n return storage.run(session.controller, async () => {\r\n let response;\r\n let status = 200;\r\n\r\n try {\r\n response = await originalHandler(event);\r\n\r\n // Try to determine status from response or event\r\n if (event.node?.res?.statusCode) status = event.node.res.statusCode;\r\n if (response?.status) status = response.status;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush (Non-blocking)\r\n // Check for Cloudflare context in various locations\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf || event.context;\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from './core/client';\r\nimport { wrapWorker } from './wrappers/worker';\r\nimport { wrapH3 } from './wrappers/h3';\r\nimport { senzorPlugin, NitroApp } from './wrappers/nitro';\r\nimport { SenzorOptions } from './core/types';\r\n\r\nconst Senzor = {\r\n /**\r\n * Initialize the SDK.\r\n * Call this in the global scope of your Worker or Plugin.\r\n */\r\n init: (options: SenzorOptions) => client.init(options),\r\n\r\n /**\r\n * Wrap your Cloudflare Worker 'fetch' handler.\r\n */\r\n worker: wrapWorker,\r\n\r\n /**\r\n * Wrap a generic H3 event handler.\r\n */\r\n wrapH3: wrapH3,\r\n\r\n /**\r\n * Nitro/Nuxt Plugin for global instrumentation.\r\n * Usage: export default Senzor.nitroPlugin; in server/plugins/senzor.ts\r\n */\r\n nitroPlugin: senzorPlugin\r\n};\r\n\r\nexport default Senzor;\r\nexport { Senzor };\r\nexport type { TraceController } from './core/types';\r\n// Re-export types that are used in public API signatures\r\nexport type { NitroApp } from './wrappers/nitro';\r\n"],"mappings":"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","names":["Transport","config","trace","batch","endpoint","err","import_node_async_hooks","storage","isInstrumented","enableFetchInstrumentation","originalFetch","input","init","controller","storage","url","method","spanName","span","headers","spanId","traceParent","newInit","response","err","SenzorClient","options","Transport","enableFetchInstrumentation","data","traceId","startTime","spans","name","type","spanStartAbs","startRel","meta","status","err","route","duration","payload","callback","client","normalizePath","path","getRoute","req","fallbackPath","wrapWorker","handler","request","env","ctx","path","session","client","storage","response","status","err","normalizePath","wrapH3","handler","event","req","path","session","client","storage","response","status","err","getRoute","waitUntil","senzorPlugin","nitroApp","client","originalHandler","event","req","path","method","session","storage","response","status","err","getRoute","waitUntil","Senzor","options","client","wrapWorker","wrapH3","senzorPlugin","index_default"]}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var g=Object.defineProperty;var C=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var O=(r,t)=>{for(var e in t)g(r,e,{get:t[e],enumerable:!0})},k=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of D(t))!P.call(r,o)&&o!==e&&g(r,o,{get:()=>t[o],enumerable:!(n=C(t,o))||n.enumerable});return r};var E=r=>k(g({},"__esModule",{value:!0}),r);var H={};O(H,{Senzor:()=>U,default:()=>F});module.exports=E(H);var m=class{constructor(t){this.config=t;this.queue=[]}add(t){this.queue.push(t)}async flush(){if(this.queue.length===0)return;let t=[...this.queue];this.queue=[];try{let e=this.config.endpoint||"https://api.senzor.dev/api/ingest/apm";await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-service-api-key":this.config.apiKey},body:JSON.stringify(t),keepalive:!0}),this.config.debug&&console.log(`[Senzor] Flushed ${t.length} traces`)}catch(e){this.config.debug&&console.error("[Senzor] Ingestion Error:",e)}}};var w=require("async_hooks"),f=new w.AsyncLocalStorage;var S=!1,b=()=>{if(S)return;let r=globalThis.fetch;globalThis.fetch=async(t,e)=>{let n=f.getStore();if(!n)return r(t,e);let o="",a="GET";typeof t=="string"?o=t:t instanceof URL?o=t.toString():t instanceof Request&&(o=t.url,a=t.method),e&&e.method&&(a=e.method);let s=`HTTP ${a}`,c=n.startSpan(s,"http"),l=new Headers(e?.headers||(t instanceof Request?t.headers:{})),i=crypto.randomUUID().replace(/-/g,"").substring(0,16),h=`00-${n.traceId}-${i}-01`;l.set("traceparent",h);let u={...e,headers:l};try{let p=await r(t,u);return c.end({url:o,method:a,status:p.status},p.status),p}catch(p){throw c.end({url:o,method:a,error:p.message},500),p}},S=!0};var y=class{constructor(){this.transport=null;this.options=null}init(t){if(!t.apiKey){console.warn("[Senzor] API Key missing. SDK disabled.");return}this.options=t,this.transport=new m(t);try{b()}catch(e){t.debug&&console.warn("[Senzor] Failed to instrument fetch:",e)}t.debug&&console.log("[Senzor] Initialized for Serverless")}createTrace(t){if(!this.transport)return{controller:{startSpan:()=>({end:()=>{}}),captureException:()=>{},traceId:"00000000000000000000000000000000"},end:()=>{},flush:async()=>{}};let e=crypto.randomUUID().replace(/-/g,""),n=performance.now(),o=[];return{controller:{traceId:e,startSpan:(i,h="custom")=>{let u=performance.now(),p=u-n;return{end:(z,A)=>{o.push({name:i,type:h,startTime:p,duration:performance.now()-u,status:A,meta:z})}}},captureException:i=>{o.push({name:"exception",type:"custom",startTime:performance.now()-n,duration:0,status:500,meta:{error:i.message||String(i),stack:i.stack}})}},end:(i,h="UNKNOWN")=>{let u=performance.now()-n,p={traceId:e,method:t.method||"GET",route:h,path:t.path||"/",status:i,duration:u,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:o};this.transport?.add(p)},flush:async()=>{await this.transport?.flush()}}}track(t){if(!this.transport)return;let e={traceId:crypto.randomUUID(),method:t.method||"GET",route:t.route,path:t.path||"/",status:t.status,duration:t.duration,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:t.spans||[]};this.transport.add(e),this.transport.flush().catch(()=>{})}startTrace(t,e){return e()}endTrace(t,e){}},d=new y;var T=r=>!r||r==="/"?"/":r.replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,":uuid").replace(/[0-9a-fA-F]{24}/g,":objectId").replace(/\/(\d+)(?=\/|$)/g,"/:id").split("?")[0],x=(r,t)=>r.route&&r.route.path?(r.baseUrl||"")+r.route.path:r.context&&r.context.matchedRoute?r.context.matchedRoute.path:r.routerPath?r.routerPath:T(t);var I=r=>async(t,e,n)=>{let a=new URL(t.url).pathname,s=d.createTrace({method:t.method,path:a,userAgent:t.headers.get("user-agent")||void 0,ip:t.headers.get("cf-connecting-ip")||t.headers.get("x-forwarded-for")||void 0});return f.run(s.controller,async()=>{let c,l=500;try{return c=await r(t,e,n,s.controller),l=c.status,c}catch(i){throw s.controller.captureException(i),i}finally{s.end(l,T(a)),n&&typeof n.waitUntil=="function"?n.waitUntil(s.flush()):await s.flush()}})};var R=r=>async t=>{let e=t.node.req,n=e.originalUrl||e.url||"/",o=d.createTrace({method:e.method||"GET",path:n,ip:e.headers["x-forwarded-for"]||e.socket?.remoteAddress||t.context?.cf?.connectingIp,userAgent:e.headers["user-agent"]});return f.run(o.controller,async()=>{let a,s=200;try{return a=await r(t),t.node.res.statusCode&&(s=t.node.res.statusCode),a&&a.statusCode&&(s=a.statusCode),a}catch(c){throw s=c.statusCode||c.status||500,o.controller.captureException(c),c}finally{o.end(s,x(t,n));let l=(t.context?.cloudflare?.context||t.context?.cf)?.waitUntil||t.waitUntil;l&&typeof l=="function"?l(o.flush()):o.flush().catch(()=>{})}})};var U={init:r=>d.init(r),worker:I,nitro:R},F=U;0&&(module.exports={Senzor});
1
+ "use strict";var y=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var k=Object.getOwnPropertyNames;var D=Object.prototype.hasOwnProperty;var E=(r,t)=>{for(var e in t)y(r,e,{get:t[e],enumerable:!0})},O=(r,t,e,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of k(t))!D.call(r,n)&&n!==e&&y(r,n,{get:()=>t[n],enumerable:!(o=R(t,n))||o.enumerable});return r};var F=r=>O(y({},"__esModule",{value:!0}),r);var N={};E(N,{Senzor:()=>C,default:()=>H});module.exports=F(N);var m=class{constructor(t){this.config=t;this.queue=[]}add(t){this.queue.push(t)}async flush(){if(this.queue.length===0)return;let t=[...this.queue];this.queue=[];try{let e=this.config.endpoint||"https://api.senzor.dev/api/ingest/apm";await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-service-api-key":this.config.apiKey},body:JSON.stringify(t),keepalive:!0}),this.config.debug&&console.log(`[Senzor] Flushed ${t.length} traces`)}catch(e){this.config.debug&&console.error("[Senzor] Ingestion Error:",e)}}};var x=require("async_hooks"),d=new x.AsyncLocalStorage;var S=!1,b=()=>{if(S)return;let r=globalThis.fetch;globalThis.fetch=async(t,e)=>{let o=d.getStore();if(!o)return r(t,e);let n="",i="GET";typeof t=="string"?n=t:t instanceof URL?n=t.toString():t instanceof Request&&(n=t.url,i=t.method),e&&e.method&&(i=e.method);let a=`HTTP ${i}`,s=o.startSpan(a,"http"),p=new Headers(e?.headers||(t instanceof Request?t.headers:{})),c=crypto.randomUUID().replace(/-/g,"").substring(0,16),u=`00-${o.traceId}-${c}-01`;p.set("traceparent",u);let h={...e,headers:p};try{let l=await r(t,h);return s.end({url:n,method:i,status:l.status},l.status),l}catch(l){throw s.end({url:n,method:i,error:l.message},500),l}},S=!0};var w=class{constructor(){this.transport=null;this.options=null}init(t){if(!t.apiKey){console.warn("[Senzor] API Key missing. SDK disabled.");return}this.options=t,this.transport=new m(t);try{b()}catch(e){t.debug&&console.warn("[Senzor] Failed to instrument fetch:",e)}t.debug&&console.log("[Senzor] Initialized for Serverless")}createTrace(t){if(!this.transport)return{controller:{startSpan:()=>({end:()=>{}}),captureException:()=>{},traceId:"00000000000000000000000000000000"},end:()=>{},flush:async()=>{}};let e=crypto.randomUUID().replace(/-/g,""),o=performance.now(),n=[];return{controller:{traceId:e,startSpan:(c,u="custom")=>{let h=performance.now(),l=h-o;return{end:(z,P)=>{n.push({name:c,type:u,startTime:l,duration:performance.now()-h,status:P,meta:z})}}},captureException:c=>{n.push({name:"exception",type:"custom",startTime:performance.now()-o,duration:0,status:500,meta:{error:c.message||String(c),stack:c.stack}})}},end:(c,u="UNKNOWN")=>{let h=performance.now()-o,l={traceId:e,method:t.method||"GET",route:u,path:t.path||"/",status:c,duration:h,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:n};this.transport?.add(l)},flush:async()=>{await this.transport?.flush()}}}track(t){if(!this.transport)return;let e={traceId:crypto.randomUUID(),method:t.method||"GET",route:t.route,path:t.path||"/",status:t.status,duration:t.duration,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:t.spans||[]};this.transport.add(e),this.transport.flush().catch(()=>{})}startTrace(t,e){return e()}endTrace(t,e){}},f=new w;var T=r=>!r||r==="/"?"/":r.replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,":uuid").replace(/[0-9a-fA-F]{24}/g,":objectId").replace(/\/(\d+)(?=\/|$)/g,"/:id").split("?")[0],g=(r,t)=>r.route&&r.route.path?(r.baseUrl||"")+r.route.path:r.context&&r.context.matchedRoute?r.context.matchedRoute.path:r.routerPath?r.routerPath:T(t);var A=r=>async(t,e,o)=>{let i=new URL(t.url).pathname,a=f.createTrace({method:t.method,path:i,userAgent:t.headers.get("user-agent")||void 0,ip:t.headers.get("cf-connecting-ip")||t.headers.get("x-forwarded-for")||void 0});return d.run(a.controller,async()=>{let s,p=500;try{return s=await r(t,e,o,a.controller),p=s.status,s}catch(c){throw a.controller.captureException(c),c}finally{a.end(p,T(i)),o&&typeof o.waitUntil=="function"?o.waitUntil(a.flush()):await a.flush()}})};var I=r=>async t=>{let e=t.node.req,o=e.originalUrl||e.url||"/",n=f.createTrace({method:e.method||"GET",path:o,ip:e.headers["x-forwarded-for"]||e.socket?.remoteAddress||t.context?.cf?.connectingIp,userAgent:e.headers["user-agent"]});return d.run(n.controller,async()=>{let i,a=200;try{return i=await r(t),t.node.res.statusCode&&(a=t.node.res.statusCode),i&&i.statusCode&&(a=i.statusCode),i}catch(s){throw a=s.statusCode||s.status||500,n.controller.captureException(s),s}finally{n.end(a,g(t,o));let p=(t.context?.cloudflare?.context||t.context?.cf)?.waitUntil||t.waitUntil;p&&typeof p=="function"?p(n.flush()):n.flush().catch(()=>{})}})};var U=r=>{if(!r.h3App||!r.h3App.handler){f.options?.debug&&console.warn("[Senzor] NitroApp.h3App.handler not found. Skipping instrumentation.");return}let t=r.h3App.handler;r.h3App.handler=async e=>{let o=e.node?.req||e.req,n=o?.originalUrl||o?.url||e.path||"/",i=o?.method||e.method||"GET",a=f.createTrace({method:i,path:n,ip:o?.headers?.["x-forwarded-for"]||o?.socket?.remoteAddress||e.context?.cf?.connectingIp,userAgent:o?.headers?.["user-agent"]});return d.run(a.controller,async()=>{let s,p=200;try{return s=await t(e),e.node?.res?.statusCode&&(p=e.node.res.statusCode),s?.status&&(p=s.status),s}catch(c){throw p=c.statusCode||c.status||500,a.controller.captureException(c),c}finally{a.end(p,g(e,n));let u=(e.context?.cloudflare?.context||e.context?.cf||e.context)?.waitUntil||e.waitUntil;u&&typeof u=="function"?u(a.flush()):a.flush().catch(()=>{})}})}};var C={init:r=>f.init(r),worker:A,wrapH3:I,nitroPlugin:U},H=C;0&&(module.exports={Senzor});
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/core/transport.ts","../src/core/context.ts","../src/instrumentation/fetch.ts","../src/core/client.ts","../src/core/normalizer.ts","../src/wrappers/worker.ts","../src/wrappers/h3.ts"],"sourcesContent":["import { client } from './core/client';\r\nimport { wrapWorker } from './wrappers/worker';\r\nimport { wrapH3 } from './wrappers/h3';\r\nimport { SenzorOptions } from './core/types';\r\n\r\nconst Senzor = {\r\n /**\r\n * Initialize the SDK.\r\n * Call this in the global scope of your Worker or Plugin.\r\n */\r\n init: (options: SenzorOptions) => client.init(options),\r\n\r\n /**\r\n * Wrap your Cloudflare Worker 'fetch' handler.\r\n * Inject trace controller as the 4th argument.\r\n */\r\n worker: wrapWorker,\r\n\r\n /**\r\n * Wrap a Nitro/H3 event handler.\r\n * Use this for Nuxt or pure Nitro server routes.\r\n */\r\n nitro: wrapH3\r\n};\r\n\r\nexport default Senzor;\r\nexport { Senzor };\r\nexport type { TraceController } from './core/types';\r\n","import { SenzorOptions, TraceData } from './types';\r\n\r\nexport class Transport {\r\n private queue: TraceData[] = [];\r\n\r\n constructor(private config: SenzorOptions) { }\r\n\r\n public add(trace: TraceData) {\r\n this.queue.push(trace);\r\n }\r\n\r\n /**\r\n * Flushes the queue to the API. \r\n * Returns a promise that should be passed to ctx.waitUntil()\r\n */\r\n public async flush(): Promise<void> {\r\n if (this.queue.length === 0) return;\r\n\r\n const batch = [...this.queue];\r\n this.queue = []; // Clear immediately\r\n\r\n try {\r\n const endpoint = this.config.endpoint || 'https://api.senzor.dev/api/ingest/apm';\r\n\r\n await fetch(endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'x-service-api-key': this.config.apiKey,\r\n },\r\n body: JSON.stringify(batch),\r\n // keepalive is not strictly necessary in workers if awaited/waitUntil'd, but good practice\r\n keepalive: true,\r\n });\r\n\r\n if (this.config.debug) console.log(`[Senzor] Flushed ${batch.length} traces`);\r\n } catch (err) {\r\n if (this.config.debug) console.error('[Senzor] Ingestion Error:', err);\r\n }\r\n }\r\n}\r\n","import { AsyncLocalStorage } from 'node:async_hooks';\r\nimport { TraceController } from './types';\r\n\r\n// This relies on the 'nodejs_compat' compatibility flag in Cloudflare Workers.\r\nexport const storage = new AsyncLocalStorage<TraceController>();\r\n","import { storage } from '../core/context';\r\n\r\nlet isInstrumented = false;\r\n\r\nexport const enableFetchInstrumentation = () => {\r\n if (isInstrumented) return;\r\n\r\n const originalFetch = globalThis.fetch;\r\n\r\n // Monkey-patch global fetch\r\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\r\n // 1. Check for active trace context\r\n const controller = storage.getStore();\r\n\r\n // If no trace is active, just pass through\r\n if (!controller) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n // 2. Resolve URL and Method\r\n let url = '';\r\n let method = 'GET';\r\n\r\n if (typeof input === 'string') {\r\n url = input;\r\n } else if (input instanceof URL) {\r\n url = input.toString();\r\n } else if (input instanceof Request) {\r\n url = input.url;\r\n method = input.method;\r\n }\r\n\r\n if (init && init.method) {\r\n method = init.method;\r\n }\r\n\r\n // 3. Start Span\r\n const spanName = `HTTP ${method}`;\r\n const span = controller.startSpan(spanName, 'http');\r\n\r\n // 4. Inject Trace Headers (W3C Trace Context)\r\n // We clone headers to avoid side effects on the input object\r\n const headers = new Headers(init?.headers || (input instanceof Request ? input.headers : {}));\r\n\r\n // Generate a span ID for the outgoing request\r\n const spanId = crypto.randomUUID().replace(/-/g, '').substring(0, 16);\r\n const traceParent = `00-${controller.traceId}-${spanId}-01`;\r\n headers.set('traceparent', traceParent);\r\n\r\n const newInit: RequestInit = {\r\n ...init,\r\n headers\r\n };\r\n\r\n try {\r\n const response = await originalFetch(input, newInit);\r\n span.end({\r\n url,\r\n method,\r\n status: response.status\r\n }, response.status);\r\n return response;\r\n } catch (err: any) {\r\n span.end({\r\n url,\r\n method,\r\n error: err.message\r\n }, 500);\r\n throw err;\r\n }\r\n };\r\n\r\n isInstrumented = true;\r\n};\r\n","import { Transport } from './transport';\r\nimport { SenzorOptions, TraceData, Span, TraceController } from './types';\r\nimport { enableFetchInstrumentation } from '../instrumentation/fetch';\r\n\r\nexport class SenzorClient {\r\n private transport: Transport | null = null;\r\n private options: SenzorOptions | null = null;\r\n\r\n public init(options: SenzorOptions) {\r\n if (!options.apiKey) {\r\n console.warn('[Senzor] API Key missing. SDK disabled.');\r\n return;\r\n }\r\n this.options = options;\r\n this.transport = new Transport(options);\r\n\r\n // Auto-instrument global fetch\r\n try {\r\n enableFetchInstrumentation();\r\n } catch (e) {\r\n if (options.debug) console.warn('[Senzor] Failed to instrument fetch:', e);\r\n }\r\n\r\n if (options.debug) console.log('[Senzor] Initialized for Serverless');\r\n }\r\n\r\n /**\r\n * Creates a detached trace session.\r\n */\r\n public createTrace(data: Partial<TraceData>): { controller: TraceController, end: (status: number, route?: string) => void, flush: () => Promise<void> } {\r\n if (!this.transport) {\r\n // Return dummy if not initialized\r\n return {\r\n controller: {\r\n startSpan: () => ({ end: () => { } }),\r\n captureException: () => { },\r\n traceId: '00000000000000000000000000000000'\r\n },\r\n end: () => { },\r\n flush: async () => { }\r\n };\r\n }\r\n\r\n const traceId = crypto.randomUUID().replace(/-/g, ''); // 32 hex chars usually\r\n const startTime = performance.now();\r\n const spans: Span[] = [];\r\n\r\n const startSpan = (name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') => {\r\n const spanStartAbs = performance.now();\r\n const startRel = spanStartAbs - startTime;\r\n return {\r\n end: (meta?: any, status?: number) => {\r\n spans.push({\r\n name,\r\n type,\r\n startTime: startRel, // Relative to trace start\r\n duration: performance.now() - spanStartAbs,\r\n status,\r\n meta\r\n });\r\n }\r\n };\r\n };\r\n\r\n const controller: TraceController = {\r\n traceId,\r\n startSpan,\r\n captureException: (err: any) => {\r\n spans.push({\r\n name: 'exception',\r\n type: 'custom',\r\n startTime: performance.now() - startTime,\r\n duration: 0,\r\n status: 500,\r\n meta: { error: err.message || String(err), stack: err.stack }\r\n });\r\n }\r\n };\r\n\r\n const end = (status: number, route: string = 'UNKNOWN') => {\r\n const duration = performance.now() - startTime;\r\n const payload: TraceData = {\r\n traceId,\r\n method: data.method || 'GET',\r\n route,\r\n path: data.path || '/',\r\n status,\r\n duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans\r\n };\r\n this.transport?.add(payload);\r\n };\r\n\r\n const flush = async () => {\r\n await this.transport?.flush();\r\n };\r\n\r\n return { controller, end, flush };\r\n }\r\n\r\n /**\r\n * Track a single request trace immediately.\r\n */\r\n public track(data: Partial<TraceData> & { status: number, duration: number, route: string }) {\r\n if (!this.transport) return;\r\n\r\n const payload: TraceData = {\r\n traceId: crypto.randomUUID(),\r\n method: data.method || 'GET',\r\n route: data.route,\r\n path: data.path || '/',\r\n status: data.status,\r\n duration: data.duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans: data.spans || []\r\n };\r\n\r\n this.transport.add(payload);\r\n this.transport.flush().catch(() => { });\r\n }\r\n\r\n // Stubs for legacy Node support\r\n public startTrace<T>(data: Partial<TraceData>, callback: () => T): T { return callback(); }\r\n public endTrace(status: number, data?: { route?: string }) { }\r\n}\r\n\r\nexport const client = new SenzorClient();\r\n","/**\r\n * Heuristic URL Normalizer\r\n * Converts raw paths with IDs into generic patterns to prevent high cardinality.\r\n * Example: /users/123/orders/abc-def -> /users/:id/orders/:uuid\r\n */\r\nexport const normalizePath = (path: string): string => {\r\n if (!path || path === '/') return '/';\r\n\r\n return path\r\n // Replace UUIDs (long alphanumeric strings)\r\n .replace(\r\n /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,\r\n ':uuid'\r\n )\r\n // Replace MongoDB ObjectIds (24 hex chars)\r\n .replace(/[0-9a-fA-F]{24}/g, ':objectId')\r\n // Replace pure numeric IDs (e.g., /123)\r\n .replace(/\\/(\\d+)(?=\\/|$)/g, '/:id')\r\n // Remove query strings\r\n .split('?')[0];\r\n};\r\n\r\n/**\r\n * Tries to extract route from Framework internals, falls back to heuristic\r\n */\r\nexport const getRoute = (req: any, fallbackPath: string): string => {\r\n // Express / Connect\r\n if (req.route && req.route.path) {\r\n return (req.baseUrl || '') + req.route.path;\r\n }\r\n\r\n // H3 / Nitro (Nuxt)\r\n if (req.context && req.context.matchedRoute) {\r\n return req.context.matchedRoute.path;\r\n }\r\n\r\n // Fastify\r\n if (req.routerPath) {\r\n return req.routerPath;\r\n }\r\n\r\n // Fallback: Heuristic Normalization\r\n return normalizePath(fallbackPath);\r\n};","import { client } from '../core/client';\r\nimport { normalizePath } from '../core/normalizer';\r\nimport { TraceController } from '../core/types';\r\nimport { storage } from '../core/context';\r\n\r\ntype WorkerHandler = (request: Request, env: any, ctx: any, trace: TraceController) => Promise<Response>;\r\n\r\nexport const wrapWorker = (handler: WorkerHandler) => {\r\n return async (request: Request, env: any, ctx: any) => {\r\n // 2. Extract Request Info\r\n const url = new URL(request.url);\r\n const path = url.pathname;\r\n\r\n // 3. Start Trace\r\n const session = client.createTrace({\r\n method: request.method,\r\n path: path,\r\n userAgent: request.headers.get('user-agent') || undefined,\r\n ip: request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for') || undefined\r\n });\r\n\r\n // 4. Run Handler within Context (AsyncLocalStorage)\r\n // This enables the global fetch instrumentation to find the current trace controller\r\n return storage.run(session.controller, async () => {\r\n let response: Response;\r\n let status = 500;\r\n\r\n try {\r\n // Inject trace controller as 4th arg for manual usage\r\n response = await handler(request, env, ctx, session.controller);\r\n status = response.status;\r\n return response;\r\n } catch (err: any) {\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 5. End Trace\r\n session.end(status, normalizePath(path));\r\n\r\n // 6. Flush (Async WaitUntil)\r\n if (ctx && typeof ctx.waitUntil === 'function') {\r\n ctx.waitUntil(session.flush());\r\n } else {\r\n await session.flush();\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Minimal types for H3 to avoid peer-deps\r\ntype EventHandler = (event: any) => any;\r\n\r\nexport const wrapH3 = (handler: EventHandler) => {\r\n return async (event: any) => {\r\n const req = event.node.req;\r\n const path = req.originalUrl || req.url || '/';\r\n\r\n // 1. Start Trace Session\r\n const session = client.createTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req.headers['user-agent'],\r\n });\r\n\r\n // 2. Run Handler within AsyncLocalStorage Context\r\n // This ensures global fetch() auto-instrumentation works inside Nitro handlers\r\n return storage.run(session.controller, async () => {\r\n let response: any;\r\n let status = 200;\r\n\r\n try {\r\n response = await handler(event);\r\n\r\n // H3/Nitro response status handling\r\n if (event.node.res.statusCode) status = event.node.res.statusCode;\r\n if (response && response.statusCode) status = response.statusCode;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush Data (Non-blocking for Cloudflare)\r\n // Nitro exposes Cloudflare context in event.context.cloudflare\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf;\r\n // Or sometimes directly on event in newer H3 versions if adapter binds it\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n // If not in a worker environment or waitUntil missing, flush async but don't block response significantly\r\n // (Note: without waitUntil, the runtime might kill the process before flush completes)\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n"],"mappings":"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","names":["index_exports","__export","Senzor","index_default","__toCommonJS","Transport","config","trace","batch","endpoint","err","import_node_async_hooks","storage","isInstrumented","enableFetchInstrumentation","originalFetch","input","init","controller","storage","url","method","spanName","span","headers","spanId","traceParent","newInit","response","err","SenzorClient","options","Transport","enableFetchInstrumentation","data","traceId","startTime","spans","name","type","spanStartAbs","startRel","meta","status","err","route","duration","payload","callback","client","normalizePath","path","getRoute","req","fallbackPath","wrapWorker","handler","request","env","ctx","path","session","client","storage","response","status","err","normalizePath","wrapH3","handler","event","req","path","session","client","storage","response","status","err","getRoute","waitUntil","Senzor","options","client","wrapWorker","wrapH3","index_default"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/core/transport.ts","../src/core/context.ts","../src/instrumentation/fetch.ts","../src/core/client.ts","../src/core/normalizer.ts","../src/wrappers/worker.ts","../src/wrappers/h3.ts","../src/wrappers/nitro.ts"],"sourcesContent":["import { client } from './core/client';\r\nimport { wrapWorker } from './wrappers/worker';\r\nimport { wrapH3 } from './wrappers/h3';\r\nimport { senzorPlugin, NitroApp } from './wrappers/nitro';\r\nimport { SenzorOptions } from './core/types';\r\n\r\nconst Senzor = {\r\n /**\r\n * Initialize the SDK.\r\n * Call this in the global scope of your Worker or Plugin.\r\n */\r\n init: (options: SenzorOptions) => client.init(options),\r\n\r\n /**\r\n * Wrap your Cloudflare Worker 'fetch' handler.\r\n */\r\n worker: wrapWorker,\r\n\r\n /**\r\n * Wrap a generic H3 event handler.\r\n */\r\n wrapH3: wrapH3,\r\n\r\n /**\r\n * Nitro/Nuxt Plugin for global instrumentation.\r\n * Usage: export default Senzor.nitroPlugin; in server/plugins/senzor.ts\r\n */\r\n nitroPlugin: senzorPlugin\r\n};\r\n\r\nexport default Senzor;\r\nexport { Senzor };\r\nexport type { TraceController } from './core/types';\r\n// Re-export types that are used in public API signatures\r\nexport type { NitroApp } from './wrappers/nitro';\r\n","import { SenzorOptions, TraceData } from './types';\r\n\r\nexport class Transport {\r\n private queue: TraceData[] = [];\r\n\r\n constructor(private config: SenzorOptions) { }\r\n\r\n public add(trace: TraceData) {\r\n this.queue.push(trace);\r\n }\r\n\r\n /**\r\n * Flushes the queue to the API. \r\n * Returns a promise that should be passed to ctx.waitUntil()\r\n */\r\n public async flush(): Promise<void> {\r\n if (this.queue.length === 0) return;\r\n\r\n const batch = [...this.queue];\r\n this.queue = []; // Clear immediately\r\n\r\n try {\r\n const endpoint = this.config.endpoint || 'https://api.senzor.dev/api/ingest/apm';\r\n\r\n await fetch(endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'x-service-api-key': this.config.apiKey,\r\n },\r\n body: JSON.stringify(batch),\r\n // keepalive is not strictly necessary in workers if awaited/waitUntil'd, but good practice\r\n keepalive: true,\r\n });\r\n\r\n if (this.config.debug) console.log(`[Senzor] Flushed ${batch.length} traces`);\r\n } catch (err) {\r\n if (this.config.debug) console.error('[Senzor] Ingestion Error:', err);\r\n }\r\n }\r\n}\r\n","import { AsyncLocalStorage } from 'node:async_hooks';\r\nimport { TraceController } from './types';\r\n\r\n// This relies on the 'nodejs_compat' compatibility flag in Cloudflare Workers.\r\nexport const storage = new AsyncLocalStorage<TraceController>();\r\n","import { storage } from '../core/context';\r\n\r\nlet isInstrumented = false;\r\n\r\nexport const enableFetchInstrumentation = () => {\r\n if (isInstrumented) return;\r\n\r\n const originalFetch = globalThis.fetch;\r\n\r\n // Monkey-patch global fetch\r\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\r\n // 1. Check for active trace context\r\n const controller = storage.getStore();\r\n\r\n // If no trace is active, just pass through\r\n if (!controller) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n // 2. Resolve URL and Method\r\n let url = '';\r\n let method = 'GET';\r\n\r\n if (typeof input === 'string') {\r\n url = input;\r\n } else if (input instanceof URL) {\r\n url = input.toString();\r\n } else if (input instanceof Request) {\r\n url = input.url;\r\n method = input.method;\r\n }\r\n\r\n if (init && init.method) {\r\n method = init.method;\r\n }\r\n\r\n // 3. Start Span\r\n const spanName = `HTTP ${method}`;\r\n const span = controller.startSpan(spanName, 'http');\r\n\r\n // 4. Inject Trace Headers (W3C Trace Context)\r\n // We clone headers to avoid side effects on the input object\r\n const headers = new Headers(init?.headers || (input instanceof Request ? input.headers : {}));\r\n\r\n // Generate a span ID for the outgoing request\r\n const spanId = crypto.randomUUID().replace(/-/g, '').substring(0, 16);\r\n const traceParent = `00-${controller.traceId}-${spanId}-01`;\r\n headers.set('traceparent', traceParent);\r\n\r\n const newInit: RequestInit = {\r\n ...init,\r\n headers\r\n };\r\n\r\n try {\r\n const response = await originalFetch(input, newInit);\r\n span.end({\r\n url,\r\n method,\r\n status: response.status\r\n }, response.status);\r\n return response;\r\n } catch (err: any) {\r\n span.end({\r\n url,\r\n method,\r\n error: err.message\r\n }, 500);\r\n throw err;\r\n }\r\n };\r\n\r\n isInstrumented = true;\r\n};\r\n","import { Transport } from './transport';\r\nimport { SenzorOptions, TraceData, Span, TraceController } from './types';\r\nimport { enableFetchInstrumentation } from '../instrumentation/fetch';\r\n\r\nexport class SenzorClient {\r\n private transport: Transport | null = null;\r\n public options: SenzorOptions | null = null;\r\n\r\n public init(options: SenzorOptions) {\r\n if (!options.apiKey) {\r\n console.warn('[Senzor] API Key missing. SDK disabled.');\r\n return;\r\n }\r\n this.options = options;\r\n this.transport = new Transport(options);\r\n\r\n // Auto-instrument global fetch\r\n try {\r\n enableFetchInstrumentation();\r\n } catch (e) {\r\n if (options.debug) console.warn('[Senzor] Failed to instrument fetch:', e);\r\n }\r\n\r\n if (options.debug) console.log('[Senzor] Initialized for Serverless');\r\n }\r\n\r\n /**\r\n * Creates a detached trace session.\r\n */\r\n public createTrace(data: Partial<TraceData>): { controller: TraceController, end: (status: number, route?: string) => void, flush: () => Promise<void> } {\r\n if (!this.transport) {\r\n // Return dummy if not initialized\r\n return {\r\n controller: {\r\n startSpan: () => ({ end: () => { } }),\r\n captureException: () => { },\r\n traceId: '00000000000000000000000000000000'\r\n },\r\n end: () => { },\r\n flush: async () => { }\r\n };\r\n }\r\n\r\n const traceId = crypto.randomUUID().replace(/-/g, ''); // 32 hex chars usually\r\n const startTime = performance.now();\r\n const spans: Span[] = [];\r\n\r\n const startSpan = (name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') => {\r\n const spanStartAbs = performance.now();\r\n const startRel = spanStartAbs - startTime;\r\n return {\r\n end: (meta?: any, status?: number) => {\r\n spans.push({\r\n name,\r\n type,\r\n startTime: startRel, // Relative to trace start\r\n duration: performance.now() - spanStartAbs,\r\n status,\r\n meta\r\n });\r\n }\r\n };\r\n };\r\n\r\n const controller: TraceController = {\r\n traceId,\r\n startSpan,\r\n captureException: (err: any) => {\r\n spans.push({\r\n name: 'exception',\r\n type: 'custom',\r\n startTime: performance.now() - startTime,\r\n duration: 0,\r\n status: 500,\r\n meta: { error: err.message || String(err), stack: err.stack }\r\n });\r\n }\r\n };\r\n\r\n const end = (status: number, route: string = 'UNKNOWN') => {\r\n const duration = performance.now() - startTime;\r\n const payload: TraceData = {\r\n traceId,\r\n method: data.method || 'GET',\r\n route,\r\n path: data.path || '/',\r\n status,\r\n duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans\r\n };\r\n this.transport?.add(payload);\r\n };\r\n\r\n const flush = async () => {\r\n await this.transport?.flush();\r\n };\r\n\r\n return { controller, end, flush };\r\n }\r\n\r\n /**\r\n * Track a single request trace immediately.\r\n */\r\n public track(data: Partial<TraceData> & { status: number, duration: number, route: string }) {\r\n if (!this.transport) return;\r\n\r\n const payload: TraceData = {\r\n traceId: crypto.randomUUID(),\r\n method: data.method || 'GET',\r\n route: data.route,\r\n path: data.path || '/',\r\n status: data.status,\r\n duration: data.duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans: data.spans || []\r\n };\r\n\r\n this.transport.add(payload);\r\n this.transport.flush().catch(() => { });\r\n }\r\n\r\n // Stubs for legacy Node support\r\n public startTrace<T>(data: Partial<TraceData>, callback: () => T): T { return callback(); }\r\n public endTrace(status: number, data?: { route?: string }) { }\r\n}\r\n\r\nexport const client = new SenzorClient();","/**\r\n * Heuristic URL Normalizer\r\n * Converts raw paths with IDs into generic patterns to prevent high cardinality.\r\n * Example: /users/123/orders/abc-def -> /users/:id/orders/:uuid\r\n */\r\nexport const normalizePath = (path: string): string => {\r\n if (!path || path === '/') return '/';\r\n\r\n return path\r\n // Replace UUIDs (long alphanumeric strings)\r\n .replace(\r\n /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,\r\n ':uuid'\r\n )\r\n // Replace MongoDB ObjectIds (24 hex chars)\r\n .replace(/[0-9a-fA-F]{24}/g, ':objectId')\r\n // Replace pure numeric IDs (e.g., /123)\r\n .replace(/\\/(\\d+)(?=\\/|$)/g, '/:id')\r\n // Remove query strings\r\n .split('?')[0];\r\n};\r\n\r\n/**\r\n * Tries to extract route from Framework internals, falls back to heuristic\r\n */\r\nexport const getRoute = (req: any, fallbackPath: string): string => {\r\n // Express / Connect\r\n if (req.route && req.route.path) {\r\n return (req.baseUrl || '') + req.route.path;\r\n }\r\n\r\n // H3 / Nitro (Nuxt)\r\n if (req.context && req.context.matchedRoute) {\r\n return req.context.matchedRoute.path;\r\n }\r\n\r\n // Fastify\r\n if (req.routerPath) {\r\n return req.routerPath;\r\n }\r\n\r\n // Fallback: Heuristic Normalization\r\n return normalizePath(fallbackPath);\r\n};","import { client } from '../core/client';\r\nimport { normalizePath } from '../core/normalizer';\r\nimport { TraceController } from '../core/types';\r\nimport { storage } from '../core/context';\r\n\r\ntype WorkerHandler = (request: Request, env: any, ctx: any, trace: TraceController) => Promise<Response>;\r\n\r\nexport const wrapWorker = (handler: WorkerHandler) => {\r\n return async (request: Request, env: any, ctx: any) => {\r\n // 2. Extract Request Info\r\n const url = new URL(request.url);\r\n const path = url.pathname;\r\n\r\n // 3. Start Trace\r\n const session = client.createTrace({\r\n method: request.method,\r\n path: path,\r\n userAgent: request.headers.get('user-agent') || undefined,\r\n ip: request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for') || undefined\r\n });\r\n\r\n // 4. Run Handler within Context (AsyncLocalStorage)\r\n // This enables the global fetch instrumentation to find the current trace controller\r\n return storage.run(session.controller, async () => {\r\n let response: Response;\r\n let status = 500;\r\n\r\n try {\r\n // Inject trace controller as 4th arg for manual usage\r\n response = await handler(request, env, ctx, session.controller);\r\n status = response.status;\r\n return response;\r\n } catch (err: any) {\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 5. End Trace\r\n session.end(status, normalizePath(path));\r\n\r\n // 6. Flush (Async WaitUntil)\r\n if (ctx && typeof ctx.waitUntil === 'function') {\r\n ctx.waitUntil(session.flush());\r\n } else {\r\n await session.flush();\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Minimal types for H3 to avoid peer-deps\r\ntype EventHandler = (event: any) => any;\r\n\r\nexport const wrapH3 = (handler: EventHandler) => {\r\n return async (event: any) => {\r\n const req = event.node.req;\r\n const path = req.originalUrl || req.url || '/';\r\n\r\n // 1. Start Trace Session\r\n const session = client.createTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req.headers['user-agent'],\r\n });\r\n\r\n // 2. Run Handler within AsyncLocalStorage Context\r\n // This ensures global fetch() auto-instrumentation works inside Nitro handlers\r\n return storage.run(session.controller, async () => {\r\n let response: any;\r\n let status = 200;\r\n\r\n try {\r\n response = await handler(event);\r\n\r\n // H3/Nitro response status handling\r\n if (event.node.res.statusCode) status = event.node.res.statusCode;\r\n if (response && response.statusCode) status = response.statusCode;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush Data (Non-blocking for Cloudflare)\r\n // Nitro exposes Cloudflare context in event.context.cloudflare\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf;\r\n // Or sometimes directly on event in newer H3 versions if adapter binds it\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n // If not in a worker environment or waitUntil missing, flush async but don't block response significantly\r\n // (Note: without waitUntil, the runtime might kill the process before flush completes)\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Type definitions for NitroApp to avoid heavy dependencies\r\nexport interface NitroApp {\r\n h3App: {\r\n handler: (event: any) => Promise<any>;\r\n stack: any[];\r\n };\r\n hooks: any;\r\n [key: string]: any;\r\n}\r\n\r\n/**\r\n * A Nitro Plugin to automatically instrument the entire application.\r\n * Usage: Create 'server/plugins/senzor.ts' and export default senzorPlugin;\r\n */\r\nexport const senzorPlugin = (nitroApp: NitroApp) => {\r\n // Capture the original handler\r\n if (!nitroApp.h3App || !nitroApp.h3App.handler) {\r\n if (client.options?.debug) console.warn('[Senzor] NitroApp.h3App.handler not found. Skipping instrumentation.');\r\n return;\r\n }\r\n\r\n const originalHandler = nitroApp.h3App.handler;\r\n\r\n // Replace the main H3 app handler with a wrapped version\r\n nitroApp.h3App.handler = async (event: any) => {\r\n const req = event.node?.req || event.req;\r\n // Fallback for H3 event structure\r\n const path = req?.originalUrl || req?.url || (event.path as string) || '/';\r\n const method = req?.method || (event.method as string) || 'GET';\r\n\r\n // 1. Start Trace\r\n const session = client.createTrace({\r\n method: method,\r\n path: path,\r\n ip: req?.headers?.['x-forwarded-for'] || req?.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req?.headers?.['user-agent'],\r\n });\r\n\r\n // 2. Run execution inside AsyncLocalStorage context\r\n return storage.run(session.controller, async () => {\r\n let response;\r\n let status = 200;\r\n\r\n try {\r\n response = await originalHandler(event);\r\n\r\n // Try to determine status from response or event\r\n if (event.node?.res?.statusCode) status = event.node.res.statusCode;\r\n if (response?.status) status = response.status;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush (Non-blocking)\r\n // Check for Cloudflare context in various locations\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf || event.context;\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n"],"mappings":"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","names":["index_exports","__export","Senzor","index_default","__toCommonJS","Transport","config","trace","batch","endpoint","err","import_node_async_hooks","storage","isInstrumented","enableFetchInstrumentation","originalFetch","input","init","controller","storage","url","method","spanName","span","headers","spanId","traceParent","newInit","response","err","SenzorClient","options","Transport","enableFetchInstrumentation","data","traceId","startTime","spans","name","type","spanStartAbs","startRel","meta","status","err","route","duration","payload","callback","client","normalizePath","path","getRoute","req","fallbackPath","wrapWorker","handler","request","env","ctx","path","session","client","storage","response","status","err","normalizePath","wrapH3","handler","event","req","path","session","client","storage","response","status","err","getRoute","waitUntil","senzorPlugin","nitroApp","client","originalHandler","event","req","path","method","session","storage","response","status","err","getRoute","waitUntil","Senzor","options","client","wrapWorker","wrapH3","senzorPlugin","index_default"]}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- var m=class{constructor(t){this.config=t;this.queue=[]}add(t){this.queue.push(t)}async flush(){if(this.queue.length===0)return;let t=[...this.queue];this.queue=[];try{let e=this.config.endpoint||"https://api.senzor.dev/api/ingest/apm";await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-service-api-key":this.config.apiKey},body:JSON.stringify(t),keepalive:!0}),this.config.debug&&console.log(`[Senzor] Flushed ${t.length} traces`)}catch(e){this.config.debug&&console.error("[Senzor] Ingestion Error:",e)}}};import{AsyncLocalStorage as U}from"async_hooks";var f=new U;var T=!1,w=()=>{if(T)return;let r=globalThis.fetch;globalThis.fetch=async(t,e)=>{let n=f.getStore();if(!n)return r(t,e);let o="",a="GET";typeof t=="string"?o=t:t instanceof URL?o=t.toString():t instanceof Request&&(o=t.url,a=t.method),e&&e.method&&(a=e.method);let s=`HTTP ${a}`,c=n.startSpan(s,"http"),l=new Headers(e?.headers||(t instanceof Request?t.headers:{})),i=crypto.randomUUID().replace(/-/g,"").substring(0,16),h=`00-${n.traceId}-${i}-01`;l.set("traceparent",h);let u={...e,headers:l};try{let p=await r(t,u);return c.end({url:o,method:a,status:p.status},p.status),p}catch(p){throw c.end({url:o,method:a,error:p.message},500),p}},T=!0};var g=class{constructor(){this.transport=null;this.options=null}init(t){if(!t.apiKey){console.warn("[Senzor] API Key missing. SDK disabled.");return}this.options=t,this.transport=new m(t);try{w()}catch(e){t.debug&&console.warn("[Senzor] Failed to instrument fetch:",e)}t.debug&&console.log("[Senzor] Initialized for Serverless")}createTrace(t){if(!this.transport)return{controller:{startSpan:()=>({end:()=>{}}),captureException:()=>{},traceId:"00000000000000000000000000000000"},end:()=>{},flush:async()=>{}};let e=crypto.randomUUID().replace(/-/g,""),n=performance.now(),o=[];return{controller:{traceId:e,startSpan:(i,h="custom")=>{let u=performance.now(),p=u-n;return{end:(I,R)=>{o.push({name:i,type:h,startTime:p,duration:performance.now()-u,status:R,meta:I})}}},captureException:i=>{o.push({name:"exception",type:"custom",startTime:performance.now()-n,duration:0,status:500,meta:{error:i.message||String(i),stack:i.stack}})}},end:(i,h="UNKNOWN")=>{let u=performance.now()-n,p={traceId:e,method:t.method||"GET",route:h,path:t.path||"/",status:i,duration:u,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:o};this.transport?.add(p)},flush:async()=>{await this.transport?.flush()}}}track(t){if(!this.transport)return;let e={traceId:crypto.randomUUID(),method:t.method||"GET",route:t.route,path:t.path||"/",status:t.status,duration:t.duration,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:t.spans||[]};this.transport.add(e),this.transport.flush().catch(()=>{})}startTrace(t,e){return e()}endTrace(t,e){}},d=new g;var y=r=>!r||r==="/"?"/":r.replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,":uuid").replace(/[0-9a-fA-F]{24}/g,":objectId").replace(/\/(\d+)(?=\/|$)/g,"/:id").split("?")[0],S=(r,t)=>r.route&&r.route.path?(r.baseUrl||"")+r.route.path:r.context&&r.context.matchedRoute?r.context.matchedRoute.path:r.routerPath?r.routerPath:y(t);var b=r=>async(t,e,n)=>{let a=new URL(t.url).pathname,s=d.createTrace({method:t.method,path:a,userAgent:t.headers.get("user-agent")||void 0,ip:t.headers.get("cf-connecting-ip")||t.headers.get("x-forwarded-for")||void 0});return f.run(s.controller,async()=>{let c,l=500;try{return c=await r(t,e,n,s.controller),l=c.status,c}catch(i){throw s.controller.captureException(i),i}finally{s.end(l,y(a)),n&&typeof n.waitUntil=="function"?n.waitUntil(s.flush()):await s.flush()}})};var x=r=>async t=>{let e=t.node.req,n=e.originalUrl||e.url||"/",o=d.createTrace({method:e.method||"GET",path:n,ip:e.headers["x-forwarded-for"]||e.socket?.remoteAddress||t.context?.cf?.connectingIp,userAgent:e.headers["user-agent"]});return f.run(o.controller,async()=>{let a,s=200;try{return a=await r(t),t.node.res.statusCode&&(s=t.node.res.statusCode),a&&a.statusCode&&(s=a.statusCode),a}catch(c){throw s=c.statusCode||c.status||500,o.controller.captureException(c),c}finally{o.end(s,S(t,n));let l=(t.context?.cloudflare?.context||t.context?.cf)?.waitUntil||t.waitUntil;l&&typeof l=="function"?l(o.flush()):o.flush().catch(()=>{})}})};var z={init:r=>d.init(r),worker:b,nitro:x},M=z;export{z as Senzor,M as default};
1
+ var m=class{constructor(t){this.config=t;this.queue=[]}add(t){this.queue.push(t)}async flush(){if(this.queue.length===0)return;let t=[...this.queue];this.queue=[];try{let e=this.config.endpoint||"https://api.senzor.dev/api/ingest/apm";await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-service-api-key":this.config.apiKey},body:JSON.stringify(t),keepalive:!0}),this.config.debug&&console.log(`[Senzor] Flushed ${t.length} traces`)}catch(e){this.config.debug&&console.error("[Senzor] Ingestion Error:",e)}}};import{AsyncLocalStorage as C}from"async_hooks";var d=new C;var T=!1,x=()=>{if(T)return;let r=globalThis.fetch;globalThis.fetch=async(t,e)=>{let o=d.getStore();if(!o)return r(t,e);let a="",i="GET";typeof t=="string"?a=t:t instanceof URL?a=t.toString():t instanceof Request&&(a=t.url,i=t.method),e&&e.method&&(i=e.method);let n=`HTTP ${i}`,s=o.startSpan(n,"http"),p=new Headers(e?.headers||(t instanceof Request?t.headers:{})),c=crypto.randomUUID().replace(/-/g,"").substring(0,16),u=`00-${o.traceId}-${c}-01`;p.set("traceparent",u);let h={...e,headers:p};try{let l=await r(t,h);return s.end({url:a,method:i,status:l.status},l.status),l}catch(l){throw s.end({url:a,method:i,error:l.message},500),l}},T=!0};var y=class{constructor(){this.transport=null;this.options=null}init(t){if(!t.apiKey){console.warn("[Senzor] API Key missing. SDK disabled.");return}this.options=t,this.transport=new m(t);try{x()}catch(e){t.debug&&console.warn("[Senzor] Failed to instrument fetch:",e)}t.debug&&console.log("[Senzor] Initialized for Serverless")}createTrace(t){if(!this.transport)return{controller:{startSpan:()=>({end:()=>{}}),captureException:()=>{},traceId:"00000000000000000000000000000000"},end:()=>{},flush:async()=>{}};let e=crypto.randomUUID().replace(/-/g,""),o=performance.now(),a=[];return{controller:{traceId:e,startSpan:(c,u="custom")=>{let h=performance.now(),l=h-o;return{end:(I,U)=>{a.push({name:c,type:u,startTime:l,duration:performance.now()-h,status:U,meta:I})}}},captureException:c=>{a.push({name:"exception",type:"custom",startTime:performance.now()-o,duration:0,status:500,meta:{error:c.message||String(c),stack:c.stack}})}},end:(c,u="UNKNOWN")=>{let h=performance.now()-o,l={traceId:e,method:t.method||"GET",route:u,path:t.path||"/",status:c,duration:h,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:a};this.transport?.add(l)},flush:async()=>{await this.transport?.flush()}}}track(t){if(!this.transport)return;let e={traceId:crypto.randomUUID(),method:t.method||"GET",route:t.route,path:t.path||"/",status:t.status,duration:t.duration,ip:t.ip,userAgent:t.userAgent,timestamp:new Date().toISOString(),spans:t.spans||[]};this.transport.add(e),this.transport.flush().catch(()=>{})}startTrace(t,e){return e()}endTrace(t,e){}},f=new y;var w=r=>!r||r==="/"?"/":r.replace(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,":uuid").replace(/[0-9a-fA-F]{24}/g,":objectId").replace(/\/(\d+)(?=\/|$)/g,"/:id").split("?")[0],g=(r,t)=>r.route&&r.route.path?(r.baseUrl||"")+r.route.path:r.context&&r.context.matchedRoute?r.context.matchedRoute.path:r.routerPath?r.routerPath:w(t);var S=r=>async(t,e,o)=>{let i=new URL(t.url).pathname,n=f.createTrace({method:t.method,path:i,userAgent:t.headers.get("user-agent")||void 0,ip:t.headers.get("cf-connecting-ip")||t.headers.get("x-forwarded-for")||void 0});return d.run(n.controller,async()=>{let s,p=500;try{return s=await r(t,e,o,n.controller),p=s.status,s}catch(c){throw n.controller.captureException(c),c}finally{n.end(p,w(i)),o&&typeof o.waitUntil=="function"?o.waitUntil(n.flush()):await n.flush()}})};var b=r=>async t=>{let e=t.node.req,o=e.originalUrl||e.url||"/",a=f.createTrace({method:e.method||"GET",path:o,ip:e.headers["x-forwarded-for"]||e.socket?.remoteAddress||t.context?.cf?.connectingIp,userAgent:e.headers["user-agent"]});return d.run(a.controller,async()=>{let i,n=200;try{return i=await r(t),t.node.res.statusCode&&(n=t.node.res.statusCode),i&&i.statusCode&&(n=i.statusCode),i}catch(s){throw n=s.statusCode||s.status||500,a.controller.captureException(s),s}finally{a.end(n,g(t,o));let p=(t.context?.cloudflare?.context||t.context?.cf)?.waitUntil||t.waitUntil;p&&typeof p=="function"?p(a.flush()):a.flush().catch(()=>{})}})};var A=r=>{if(!r.h3App||!r.h3App.handler){f.options?.debug&&console.warn("[Senzor] NitroApp.h3App.handler not found. Skipping instrumentation.");return}let t=r.h3App.handler;r.h3App.handler=async e=>{let o=e.node?.req||e.req,a=o?.originalUrl||o?.url||e.path||"/",i=o?.method||e.method||"GET",n=f.createTrace({method:i,path:a,ip:o?.headers?.["x-forwarded-for"]||o?.socket?.remoteAddress||e.context?.cf?.connectingIp,userAgent:o?.headers?.["user-agent"]});return d.run(n.controller,async()=>{let s,p=200;try{return s=await t(e),e.node?.res?.statusCode&&(p=e.node.res.statusCode),s?.status&&(p=s.status),s}catch(c){throw p=c.statusCode||c.status||500,n.controller.captureException(c),c}finally{n.end(p,g(e,a));let u=(e.context?.cloudflare?.context||e.context?.cf||e.context)?.waitUntil||e.waitUntil;u&&typeof u=="function"?u(n.flush()):n.flush().catch(()=>{})}})}};var z={init:r=>f.init(r),worker:S,wrapH3:b,nitroPlugin:A},tt=z;export{z as Senzor,tt as default};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/transport.ts","../src/core/context.ts","../src/instrumentation/fetch.ts","../src/core/client.ts","../src/core/normalizer.ts","../src/wrappers/worker.ts","../src/wrappers/h3.ts","../src/index.ts"],"sourcesContent":["import { SenzorOptions, TraceData } from './types';\r\n\r\nexport class Transport {\r\n private queue: TraceData[] = [];\r\n\r\n constructor(private config: SenzorOptions) { }\r\n\r\n public add(trace: TraceData) {\r\n this.queue.push(trace);\r\n }\r\n\r\n /**\r\n * Flushes the queue to the API. \r\n * Returns a promise that should be passed to ctx.waitUntil()\r\n */\r\n public async flush(): Promise<void> {\r\n if (this.queue.length === 0) return;\r\n\r\n const batch = [...this.queue];\r\n this.queue = []; // Clear immediately\r\n\r\n try {\r\n const endpoint = this.config.endpoint || 'https://api.senzor.dev/api/ingest/apm';\r\n\r\n await fetch(endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'x-service-api-key': this.config.apiKey,\r\n },\r\n body: JSON.stringify(batch),\r\n // keepalive is not strictly necessary in workers if awaited/waitUntil'd, but good practice\r\n keepalive: true,\r\n });\r\n\r\n if (this.config.debug) console.log(`[Senzor] Flushed ${batch.length} traces`);\r\n } catch (err) {\r\n if (this.config.debug) console.error('[Senzor] Ingestion Error:', err);\r\n }\r\n }\r\n}\r\n","import { AsyncLocalStorage } from 'node:async_hooks';\r\nimport { TraceController } from './types';\r\n\r\n// This relies on the 'nodejs_compat' compatibility flag in Cloudflare Workers.\r\nexport const storage = new AsyncLocalStorage<TraceController>();\r\n","import { storage } from '../core/context';\r\n\r\nlet isInstrumented = false;\r\n\r\nexport const enableFetchInstrumentation = () => {\r\n if (isInstrumented) return;\r\n\r\n const originalFetch = globalThis.fetch;\r\n\r\n // Monkey-patch global fetch\r\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\r\n // 1. Check for active trace context\r\n const controller = storage.getStore();\r\n\r\n // If no trace is active, just pass through\r\n if (!controller) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n // 2. Resolve URL and Method\r\n let url = '';\r\n let method = 'GET';\r\n\r\n if (typeof input === 'string') {\r\n url = input;\r\n } else if (input instanceof URL) {\r\n url = input.toString();\r\n } else if (input instanceof Request) {\r\n url = input.url;\r\n method = input.method;\r\n }\r\n\r\n if (init && init.method) {\r\n method = init.method;\r\n }\r\n\r\n // 3. Start Span\r\n const spanName = `HTTP ${method}`;\r\n const span = controller.startSpan(spanName, 'http');\r\n\r\n // 4. Inject Trace Headers (W3C Trace Context)\r\n // We clone headers to avoid side effects on the input object\r\n const headers = new Headers(init?.headers || (input instanceof Request ? input.headers : {}));\r\n\r\n // Generate a span ID for the outgoing request\r\n const spanId = crypto.randomUUID().replace(/-/g, '').substring(0, 16);\r\n const traceParent = `00-${controller.traceId}-${spanId}-01`;\r\n headers.set('traceparent', traceParent);\r\n\r\n const newInit: RequestInit = {\r\n ...init,\r\n headers\r\n };\r\n\r\n try {\r\n const response = await originalFetch(input, newInit);\r\n span.end({\r\n url,\r\n method,\r\n status: response.status\r\n }, response.status);\r\n return response;\r\n } catch (err: any) {\r\n span.end({\r\n url,\r\n method,\r\n error: err.message\r\n }, 500);\r\n throw err;\r\n }\r\n };\r\n\r\n isInstrumented = true;\r\n};\r\n","import { Transport } from './transport';\r\nimport { SenzorOptions, TraceData, Span, TraceController } from './types';\r\nimport { enableFetchInstrumentation } from '../instrumentation/fetch';\r\n\r\nexport class SenzorClient {\r\n private transport: Transport | null = null;\r\n private options: SenzorOptions | null = null;\r\n\r\n public init(options: SenzorOptions) {\r\n if (!options.apiKey) {\r\n console.warn('[Senzor] API Key missing. SDK disabled.');\r\n return;\r\n }\r\n this.options = options;\r\n this.transport = new Transport(options);\r\n\r\n // Auto-instrument global fetch\r\n try {\r\n enableFetchInstrumentation();\r\n } catch (e) {\r\n if (options.debug) console.warn('[Senzor] Failed to instrument fetch:', e);\r\n }\r\n\r\n if (options.debug) console.log('[Senzor] Initialized for Serverless');\r\n }\r\n\r\n /**\r\n * Creates a detached trace session.\r\n */\r\n public createTrace(data: Partial<TraceData>): { controller: TraceController, end: (status: number, route?: string) => void, flush: () => Promise<void> } {\r\n if (!this.transport) {\r\n // Return dummy if not initialized\r\n return {\r\n controller: {\r\n startSpan: () => ({ end: () => { } }),\r\n captureException: () => { },\r\n traceId: '00000000000000000000000000000000'\r\n },\r\n end: () => { },\r\n flush: async () => { }\r\n };\r\n }\r\n\r\n const traceId = crypto.randomUUID().replace(/-/g, ''); // 32 hex chars usually\r\n const startTime = performance.now();\r\n const spans: Span[] = [];\r\n\r\n const startSpan = (name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') => {\r\n const spanStartAbs = performance.now();\r\n const startRel = spanStartAbs - startTime;\r\n return {\r\n end: (meta?: any, status?: number) => {\r\n spans.push({\r\n name,\r\n type,\r\n startTime: startRel, // Relative to trace start\r\n duration: performance.now() - spanStartAbs,\r\n status,\r\n meta\r\n });\r\n }\r\n };\r\n };\r\n\r\n const controller: TraceController = {\r\n traceId,\r\n startSpan,\r\n captureException: (err: any) => {\r\n spans.push({\r\n name: 'exception',\r\n type: 'custom',\r\n startTime: performance.now() - startTime,\r\n duration: 0,\r\n status: 500,\r\n meta: { error: err.message || String(err), stack: err.stack }\r\n });\r\n }\r\n };\r\n\r\n const end = (status: number, route: string = 'UNKNOWN') => {\r\n const duration = performance.now() - startTime;\r\n const payload: TraceData = {\r\n traceId,\r\n method: data.method || 'GET',\r\n route,\r\n path: data.path || '/',\r\n status,\r\n duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans\r\n };\r\n this.transport?.add(payload);\r\n };\r\n\r\n const flush = async () => {\r\n await this.transport?.flush();\r\n };\r\n\r\n return { controller, end, flush };\r\n }\r\n\r\n /**\r\n * Track a single request trace immediately.\r\n */\r\n public track(data: Partial<TraceData> & { status: number, duration: number, route: string }) {\r\n if (!this.transport) return;\r\n\r\n const payload: TraceData = {\r\n traceId: crypto.randomUUID(),\r\n method: data.method || 'GET',\r\n route: data.route,\r\n path: data.path || '/',\r\n status: data.status,\r\n duration: data.duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans: data.spans || []\r\n };\r\n\r\n this.transport.add(payload);\r\n this.transport.flush().catch(() => { });\r\n }\r\n\r\n // Stubs for legacy Node support\r\n public startTrace<T>(data: Partial<TraceData>, callback: () => T): T { return callback(); }\r\n public endTrace(status: number, data?: { route?: string }) { }\r\n}\r\n\r\nexport const client = new SenzorClient();\r\n","/**\r\n * Heuristic URL Normalizer\r\n * Converts raw paths with IDs into generic patterns to prevent high cardinality.\r\n * Example: /users/123/orders/abc-def -> /users/:id/orders/:uuid\r\n */\r\nexport const normalizePath = (path: string): string => {\r\n if (!path || path === '/') return '/';\r\n\r\n return path\r\n // Replace UUIDs (long alphanumeric strings)\r\n .replace(\r\n /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,\r\n ':uuid'\r\n )\r\n // Replace MongoDB ObjectIds (24 hex chars)\r\n .replace(/[0-9a-fA-F]{24}/g, ':objectId')\r\n // Replace pure numeric IDs (e.g., /123)\r\n .replace(/\\/(\\d+)(?=\\/|$)/g, '/:id')\r\n // Remove query strings\r\n .split('?')[0];\r\n};\r\n\r\n/**\r\n * Tries to extract route from Framework internals, falls back to heuristic\r\n */\r\nexport const getRoute = (req: any, fallbackPath: string): string => {\r\n // Express / Connect\r\n if (req.route && req.route.path) {\r\n return (req.baseUrl || '') + req.route.path;\r\n }\r\n\r\n // H3 / Nitro (Nuxt)\r\n if (req.context && req.context.matchedRoute) {\r\n return req.context.matchedRoute.path;\r\n }\r\n\r\n // Fastify\r\n if (req.routerPath) {\r\n return req.routerPath;\r\n }\r\n\r\n // Fallback: Heuristic Normalization\r\n return normalizePath(fallbackPath);\r\n};","import { client } from '../core/client';\r\nimport { normalizePath } from '../core/normalizer';\r\nimport { TraceController } from '../core/types';\r\nimport { storage } from '../core/context';\r\n\r\ntype WorkerHandler = (request: Request, env: any, ctx: any, trace: TraceController) => Promise<Response>;\r\n\r\nexport const wrapWorker = (handler: WorkerHandler) => {\r\n return async (request: Request, env: any, ctx: any) => {\r\n // 2. Extract Request Info\r\n const url = new URL(request.url);\r\n const path = url.pathname;\r\n\r\n // 3. Start Trace\r\n const session = client.createTrace({\r\n method: request.method,\r\n path: path,\r\n userAgent: request.headers.get('user-agent') || undefined,\r\n ip: request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for') || undefined\r\n });\r\n\r\n // 4. Run Handler within Context (AsyncLocalStorage)\r\n // This enables the global fetch instrumentation to find the current trace controller\r\n return storage.run(session.controller, async () => {\r\n let response: Response;\r\n let status = 500;\r\n\r\n try {\r\n // Inject trace controller as 4th arg for manual usage\r\n response = await handler(request, env, ctx, session.controller);\r\n status = response.status;\r\n return response;\r\n } catch (err: any) {\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 5. End Trace\r\n session.end(status, normalizePath(path));\r\n\r\n // 6. Flush (Async WaitUntil)\r\n if (ctx && typeof ctx.waitUntil === 'function') {\r\n ctx.waitUntil(session.flush());\r\n } else {\r\n await session.flush();\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Minimal types for H3 to avoid peer-deps\r\ntype EventHandler = (event: any) => any;\r\n\r\nexport const wrapH3 = (handler: EventHandler) => {\r\n return async (event: any) => {\r\n const req = event.node.req;\r\n const path = req.originalUrl || req.url || '/';\r\n\r\n // 1. Start Trace Session\r\n const session = client.createTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req.headers['user-agent'],\r\n });\r\n\r\n // 2. Run Handler within AsyncLocalStorage Context\r\n // This ensures global fetch() auto-instrumentation works inside Nitro handlers\r\n return storage.run(session.controller, async () => {\r\n let response: any;\r\n let status = 200;\r\n\r\n try {\r\n response = await handler(event);\r\n\r\n // H3/Nitro response status handling\r\n if (event.node.res.statusCode) status = event.node.res.statusCode;\r\n if (response && response.statusCode) status = response.statusCode;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush Data (Non-blocking for Cloudflare)\r\n // Nitro exposes Cloudflare context in event.context.cloudflare\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf;\r\n // Or sometimes directly on event in newer H3 versions if adapter binds it\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n // If not in a worker environment or waitUntil missing, flush async but don't block response significantly\r\n // (Note: without waitUntil, the runtime might kill the process before flush completes)\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from './core/client';\r\nimport { wrapWorker } from './wrappers/worker';\r\nimport { wrapH3 } from './wrappers/h3';\r\nimport { SenzorOptions } from './core/types';\r\n\r\nconst Senzor = {\r\n /**\r\n * Initialize the SDK.\r\n * Call this in the global scope of your Worker or Plugin.\r\n */\r\n init: (options: SenzorOptions) => client.init(options),\r\n\r\n /**\r\n * Wrap your Cloudflare Worker 'fetch' handler.\r\n * Inject trace controller as the 4th argument.\r\n */\r\n worker: wrapWorker,\r\n\r\n /**\r\n * Wrap a Nitro/H3 event handler.\r\n * Use this for Nuxt or pure Nitro server routes.\r\n */\r\n nitro: wrapH3\r\n};\r\n\r\nexport default Senzor;\r\nexport { Senzor };\r\nexport type { TraceController } from './core/types';\r\n"],"mappings":"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","names":["Transport","config","trace","batch","endpoint","err","AsyncLocalStorage","storage","isInstrumented","enableFetchInstrumentation","originalFetch","input","init","controller","storage","url","method","spanName","span","headers","spanId","traceParent","newInit","response","err","SenzorClient","options","Transport","enableFetchInstrumentation","data","traceId","startTime","spans","name","type","spanStartAbs","startRel","meta","status","err","route","duration","payload","callback","client","normalizePath","path","getRoute","req","fallbackPath","wrapWorker","handler","request","env","ctx","path","session","client","storage","response","status","err","normalizePath","wrapH3","handler","event","req","path","session","client","storage","response","status","err","getRoute","waitUntil","Senzor","options","client","wrapWorker","wrapH3","index_default"]}
1
+ {"version":3,"sources":["../src/core/transport.ts","../src/core/context.ts","../src/instrumentation/fetch.ts","../src/core/client.ts","../src/core/normalizer.ts","../src/wrappers/worker.ts","../src/wrappers/h3.ts","../src/wrappers/nitro.ts","../src/index.ts"],"sourcesContent":["import { SenzorOptions, TraceData } from './types';\r\n\r\nexport class Transport {\r\n private queue: TraceData[] = [];\r\n\r\n constructor(private config: SenzorOptions) { }\r\n\r\n public add(trace: TraceData) {\r\n this.queue.push(trace);\r\n }\r\n\r\n /**\r\n * Flushes the queue to the API. \r\n * Returns a promise that should be passed to ctx.waitUntil()\r\n */\r\n public async flush(): Promise<void> {\r\n if (this.queue.length === 0) return;\r\n\r\n const batch = [...this.queue];\r\n this.queue = []; // Clear immediately\r\n\r\n try {\r\n const endpoint = this.config.endpoint || 'https://api.senzor.dev/api/ingest/apm';\r\n\r\n await fetch(endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n 'x-service-api-key': this.config.apiKey,\r\n },\r\n body: JSON.stringify(batch),\r\n // keepalive is not strictly necessary in workers if awaited/waitUntil'd, but good practice\r\n keepalive: true,\r\n });\r\n\r\n if (this.config.debug) console.log(`[Senzor] Flushed ${batch.length} traces`);\r\n } catch (err) {\r\n if (this.config.debug) console.error('[Senzor] Ingestion Error:', err);\r\n }\r\n }\r\n}\r\n","import { AsyncLocalStorage } from 'node:async_hooks';\r\nimport { TraceController } from './types';\r\n\r\n// This relies on the 'nodejs_compat' compatibility flag in Cloudflare Workers.\r\nexport const storage = new AsyncLocalStorage<TraceController>();\r\n","import { storage } from '../core/context';\r\n\r\nlet isInstrumented = false;\r\n\r\nexport const enableFetchInstrumentation = () => {\r\n if (isInstrumented) return;\r\n\r\n const originalFetch = globalThis.fetch;\r\n\r\n // Monkey-patch global fetch\r\n globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {\r\n // 1. Check for active trace context\r\n const controller = storage.getStore();\r\n\r\n // If no trace is active, just pass through\r\n if (!controller) {\r\n return originalFetch(input, init);\r\n }\r\n\r\n // 2. Resolve URL and Method\r\n let url = '';\r\n let method = 'GET';\r\n\r\n if (typeof input === 'string') {\r\n url = input;\r\n } else if (input instanceof URL) {\r\n url = input.toString();\r\n } else if (input instanceof Request) {\r\n url = input.url;\r\n method = input.method;\r\n }\r\n\r\n if (init && init.method) {\r\n method = init.method;\r\n }\r\n\r\n // 3. Start Span\r\n const spanName = `HTTP ${method}`;\r\n const span = controller.startSpan(spanName, 'http');\r\n\r\n // 4. Inject Trace Headers (W3C Trace Context)\r\n // We clone headers to avoid side effects on the input object\r\n const headers = new Headers(init?.headers || (input instanceof Request ? input.headers : {}));\r\n\r\n // Generate a span ID for the outgoing request\r\n const spanId = crypto.randomUUID().replace(/-/g, '').substring(0, 16);\r\n const traceParent = `00-${controller.traceId}-${spanId}-01`;\r\n headers.set('traceparent', traceParent);\r\n\r\n const newInit: RequestInit = {\r\n ...init,\r\n headers\r\n };\r\n\r\n try {\r\n const response = await originalFetch(input, newInit);\r\n span.end({\r\n url,\r\n method,\r\n status: response.status\r\n }, response.status);\r\n return response;\r\n } catch (err: any) {\r\n span.end({\r\n url,\r\n method,\r\n error: err.message\r\n }, 500);\r\n throw err;\r\n }\r\n };\r\n\r\n isInstrumented = true;\r\n};\r\n","import { Transport } from './transport';\r\nimport { SenzorOptions, TraceData, Span, TraceController } from './types';\r\nimport { enableFetchInstrumentation } from '../instrumentation/fetch';\r\n\r\nexport class SenzorClient {\r\n private transport: Transport | null = null;\r\n public options: SenzorOptions | null = null;\r\n\r\n public init(options: SenzorOptions) {\r\n if (!options.apiKey) {\r\n console.warn('[Senzor] API Key missing. SDK disabled.');\r\n return;\r\n }\r\n this.options = options;\r\n this.transport = new Transport(options);\r\n\r\n // Auto-instrument global fetch\r\n try {\r\n enableFetchInstrumentation();\r\n } catch (e) {\r\n if (options.debug) console.warn('[Senzor] Failed to instrument fetch:', e);\r\n }\r\n\r\n if (options.debug) console.log('[Senzor] Initialized for Serverless');\r\n }\r\n\r\n /**\r\n * Creates a detached trace session.\r\n */\r\n public createTrace(data: Partial<TraceData>): { controller: TraceController, end: (status: number, route?: string) => void, flush: () => Promise<void> } {\r\n if (!this.transport) {\r\n // Return dummy if not initialized\r\n return {\r\n controller: {\r\n startSpan: () => ({ end: () => { } }),\r\n captureException: () => { },\r\n traceId: '00000000000000000000000000000000'\r\n },\r\n end: () => { },\r\n flush: async () => { }\r\n };\r\n }\r\n\r\n const traceId = crypto.randomUUID().replace(/-/g, ''); // 32 hex chars usually\r\n const startTime = performance.now();\r\n const spans: Span[] = [];\r\n\r\n const startSpan = (name: string, type: 'db' | 'http' | 'function' | 'custom' = 'custom') => {\r\n const spanStartAbs = performance.now();\r\n const startRel = spanStartAbs - startTime;\r\n return {\r\n end: (meta?: any, status?: number) => {\r\n spans.push({\r\n name,\r\n type,\r\n startTime: startRel, // Relative to trace start\r\n duration: performance.now() - spanStartAbs,\r\n status,\r\n meta\r\n });\r\n }\r\n };\r\n };\r\n\r\n const controller: TraceController = {\r\n traceId,\r\n startSpan,\r\n captureException: (err: any) => {\r\n spans.push({\r\n name: 'exception',\r\n type: 'custom',\r\n startTime: performance.now() - startTime,\r\n duration: 0,\r\n status: 500,\r\n meta: { error: err.message || String(err), stack: err.stack }\r\n });\r\n }\r\n };\r\n\r\n const end = (status: number, route: string = 'UNKNOWN') => {\r\n const duration = performance.now() - startTime;\r\n const payload: TraceData = {\r\n traceId,\r\n method: data.method || 'GET',\r\n route,\r\n path: data.path || '/',\r\n status,\r\n duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans\r\n };\r\n this.transport?.add(payload);\r\n };\r\n\r\n const flush = async () => {\r\n await this.transport?.flush();\r\n };\r\n\r\n return { controller, end, flush };\r\n }\r\n\r\n /**\r\n * Track a single request trace immediately.\r\n */\r\n public track(data: Partial<TraceData> & { status: number, duration: number, route: string }) {\r\n if (!this.transport) return;\r\n\r\n const payload: TraceData = {\r\n traceId: crypto.randomUUID(),\r\n method: data.method || 'GET',\r\n route: data.route,\r\n path: data.path || '/',\r\n status: data.status,\r\n duration: data.duration,\r\n ip: data.ip,\r\n userAgent: data.userAgent,\r\n timestamp: new Date().toISOString(),\r\n spans: data.spans || []\r\n };\r\n\r\n this.transport.add(payload);\r\n this.transport.flush().catch(() => { });\r\n }\r\n\r\n // Stubs for legacy Node support\r\n public startTrace<T>(data: Partial<TraceData>, callback: () => T): T { return callback(); }\r\n public endTrace(status: number, data?: { route?: string }) { }\r\n}\r\n\r\nexport const client = new SenzorClient();","/**\r\n * Heuristic URL Normalizer\r\n * Converts raw paths with IDs into generic patterns to prevent high cardinality.\r\n * Example: /users/123/orders/abc-def -> /users/:id/orders/:uuid\r\n */\r\nexport const normalizePath = (path: string): string => {\r\n if (!path || path === '/') return '/';\r\n\r\n return path\r\n // Replace UUIDs (long alphanumeric strings)\r\n .replace(\r\n /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g,\r\n ':uuid'\r\n )\r\n // Replace MongoDB ObjectIds (24 hex chars)\r\n .replace(/[0-9a-fA-F]{24}/g, ':objectId')\r\n // Replace pure numeric IDs (e.g., /123)\r\n .replace(/\\/(\\d+)(?=\\/|$)/g, '/:id')\r\n // Remove query strings\r\n .split('?')[0];\r\n};\r\n\r\n/**\r\n * Tries to extract route from Framework internals, falls back to heuristic\r\n */\r\nexport const getRoute = (req: any, fallbackPath: string): string => {\r\n // Express / Connect\r\n if (req.route && req.route.path) {\r\n return (req.baseUrl || '') + req.route.path;\r\n }\r\n\r\n // H3 / Nitro (Nuxt)\r\n if (req.context && req.context.matchedRoute) {\r\n return req.context.matchedRoute.path;\r\n }\r\n\r\n // Fastify\r\n if (req.routerPath) {\r\n return req.routerPath;\r\n }\r\n\r\n // Fallback: Heuristic Normalization\r\n return normalizePath(fallbackPath);\r\n};","import { client } from '../core/client';\r\nimport { normalizePath } from '../core/normalizer';\r\nimport { TraceController } from '../core/types';\r\nimport { storage } from '../core/context';\r\n\r\ntype WorkerHandler = (request: Request, env: any, ctx: any, trace: TraceController) => Promise<Response>;\r\n\r\nexport const wrapWorker = (handler: WorkerHandler) => {\r\n return async (request: Request, env: any, ctx: any) => {\r\n // 2. Extract Request Info\r\n const url = new URL(request.url);\r\n const path = url.pathname;\r\n\r\n // 3. Start Trace\r\n const session = client.createTrace({\r\n method: request.method,\r\n path: path,\r\n userAgent: request.headers.get('user-agent') || undefined,\r\n ip: request.headers.get('cf-connecting-ip') || request.headers.get('x-forwarded-for') || undefined\r\n });\r\n\r\n // 4. Run Handler within Context (AsyncLocalStorage)\r\n // This enables the global fetch instrumentation to find the current trace controller\r\n return storage.run(session.controller, async () => {\r\n let response: Response;\r\n let status = 500;\r\n\r\n try {\r\n // Inject trace controller as 4th arg for manual usage\r\n response = await handler(request, env, ctx, session.controller);\r\n status = response.status;\r\n return response;\r\n } catch (err: any) {\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 5. End Trace\r\n session.end(status, normalizePath(path));\r\n\r\n // 6. Flush (Async WaitUntil)\r\n if (ctx && typeof ctx.waitUntil === 'function') {\r\n ctx.waitUntil(session.flush());\r\n } else {\r\n await session.flush();\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Minimal types for H3 to avoid peer-deps\r\ntype EventHandler = (event: any) => any;\r\n\r\nexport const wrapH3 = (handler: EventHandler) => {\r\n return async (event: any) => {\r\n const req = event.node.req;\r\n const path = req.originalUrl || req.url || '/';\r\n\r\n // 1. Start Trace Session\r\n const session = client.createTrace({\r\n method: req.method || 'GET',\r\n path: path,\r\n ip: req.headers['x-forwarded-for'] || req.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req.headers['user-agent'],\r\n });\r\n\r\n // 2. Run Handler within AsyncLocalStorage Context\r\n // This ensures global fetch() auto-instrumentation works inside Nitro handlers\r\n return storage.run(session.controller, async () => {\r\n let response: any;\r\n let status = 200;\r\n\r\n try {\r\n response = await handler(event);\r\n\r\n // H3/Nitro response status handling\r\n if (event.node.res.statusCode) status = event.node.res.statusCode;\r\n if (response && response.statusCode) status = response.statusCode;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush Data (Non-blocking for Cloudflare)\r\n // Nitro exposes Cloudflare context in event.context.cloudflare\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf;\r\n // Or sometimes directly on event in newer H3 versions if adapter binds it\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n // If not in a worker environment or waitUntil missing, flush async but don't block response significantly\r\n // (Note: without waitUntil, the runtime might kill the process before flush completes)\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from '../core/client';\r\nimport { getRoute } from '../core/normalizer';\r\nimport { storage } from '../core/context';\r\n\r\n// Type definitions for NitroApp to avoid heavy dependencies\r\nexport interface NitroApp {\r\n h3App: {\r\n handler: (event: any) => Promise<any>;\r\n stack: any[];\r\n };\r\n hooks: any;\r\n [key: string]: any;\r\n}\r\n\r\n/**\r\n * A Nitro Plugin to automatically instrument the entire application.\r\n * Usage: Create 'server/plugins/senzor.ts' and export default senzorPlugin;\r\n */\r\nexport const senzorPlugin = (nitroApp: NitroApp) => {\r\n // Capture the original handler\r\n if (!nitroApp.h3App || !nitroApp.h3App.handler) {\r\n if (client.options?.debug) console.warn('[Senzor] NitroApp.h3App.handler not found. Skipping instrumentation.');\r\n return;\r\n }\r\n\r\n const originalHandler = nitroApp.h3App.handler;\r\n\r\n // Replace the main H3 app handler with a wrapped version\r\n nitroApp.h3App.handler = async (event: any) => {\r\n const req = event.node?.req || event.req;\r\n // Fallback for H3 event structure\r\n const path = req?.originalUrl || req?.url || (event.path as string) || '/';\r\n const method = req?.method || (event.method as string) || 'GET';\r\n\r\n // 1. Start Trace\r\n const session = client.createTrace({\r\n method: method,\r\n path: path,\r\n ip: req?.headers?.['x-forwarded-for'] || req?.socket?.remoteAddress || event.context?.cf?.connectingIp,\r\n userAgent: req?.headers?.['user-agent'],\r\n });\r\n\r\n // 2. Run execution inside AsyncLocalStorage context\r\n return storage.run(session.controller, async () => {\r\n let response;\r\n let status = 200;\r\n\r\n try {\r\n response = await originalHandler(event);\r\n\r\n // Try to determine status from response or event\r\n if (event.node?.res?.statusCode) status = event.node.res.statusCode;\r\n if (response?.status) status = response.status;\r\n\r\n return response;\r\n } catch (err: any) {\r\n status = err.statusCode || err.status || 500;\r\n session.controller.captureException(err);\r\n throw err;\r\n } finally {\r\n // 3. End Trace\r\n session.end(status, getRoute(event, path));\r\n\r\n // 4. Flush (Non-blocking)\r\n // Check for Cloudflare context in various locations\r\n const cfCtx = event.context?.cloudflare?.context || event.context?.cf || event.context;\r\n const waitUntil = cfCtx?.waitUntil || event.waitUntil;\r\n\r\n if (waitUntil && typeof waitUntil === 'function') {\r\n waitUntil(session.flush());\r\n } else {\r\n session.flush().catch(() => { });\r\n }\r\n }\r\n });\r\n };\r\n};\r\n","import { client } from './core/client';\r\nimport { wrapWorker } from './wrappers/worker';\r\nimport { wrapH3 } from './wrappers/h3';\r\nimport { senzorPlugin, NitroApp } from './wrappers/nitro';\r\nimport { SenzorOptions } from './core/types';\r\n\r\nconst Senzor = {\r\n /**\r\n * Initialize the SDK.\r\n * Call this in the global scope of your Worker or Plugin.\r\n */\r\n init: (options: SenzorOptions) => client.init(options),\r\n\r\n /**\r\n * Wrap your Cloudflare Worker 'fetch' handler.\r\n */\r\n worker: wrapWorker,\r\n\r\n /**\r\n * Wrap a generic H3 event handler.\r\n */\r\n wrapH3: wrapH3,\r\n\r\n /**\r\n * Nitro/Nuxt Plugin for global instrumentation.\r\n * Usage: export default Senzor.nitroPlugin; in server/plugins/senzor.ts\r\n */\r\n nitroPlugin: senzorPlugin\r\n};\r\n\r\nexport default Senzor;\r\nexport { Senzor };\r\nexport type { TraceController } from './core/types';\r\n// Re-export types that are used in public API signatures\r\nexport type { NitroApp } from './wrappers/nitro';\r\n"],"mappings":"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","names":["Transport","config","trace","batch","endpoint","err","AsyncLocalStorage","storage","isInstrumented","enableFetchInstrumentation","originalFetch","input","init","controller","storage","url","method","spanName","span","headers","spanId","traceParent","newInit","response","err","SenzorClient","options","Transport","enableFetchInstrumentation","data","traceId","startTime","spans","name","type","spanStartAbs","startRel","meta","status","err","route","duration","payload","callback","client","normalizePath","path","getRoute","req","fallbackPath","wrapWorker","handler","request","env","ctx","path","session","client","storage","response","status","err","normalizePath","wrapH3","handler","event","req","path","session","client","storage","response","status","err","getRoute","waitUntil","senzorPlugin","nitroApp","client","originalHandler","event","req","path","method","session","storage","response","status","err","getRoute","waitUntil","Senzor","options","client","wrapWorker","wrapH3","senzorPlugin","index_default"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@senzops/apm-worker",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Cloudflare Worker / Serverless APM SDK for Senzor",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,4 +29,4 @@
29
29
  ],
30
30
  "author": "Senzops",
31
31
  "license": "MIT"
32
- }
32
+ }
@@ -4,7 +4,7 @@ import { enableFetchInstrumentation } from '../instrumentation/fetch';
4
4
 
5
5
  export class SenzorClient {
6
6
  private transport: Transport | null = null;
7
- private options: SenzorOptions | null = null;
7
+ public options: SenzorOptions | null = null;
8
8
 
9
9
  public init(options: SenzorOptions) {
10
10
  if (!options.apiKey) {
@@ -129,4 +129,4 @@ export class SenzorClient {
129
129
  public endTrace(status: number, data?: { route?: string }) { }
130
130
  }
131
131
 
132
- export const client = new SenzorClient();
132
+ export const client = new SenzorClient();
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { client } from './core/client';
2
2
  import { wrapWorker } from './wrappers/worker';
3
3
  import { wrapH3 } from './wrappers/h3';
4
+ import { senzorPlugin, NitroApp } from './wrappers/nitro';
4
5
  import { SenzorOptions } from './core/types';
5
6
 
6
7
  const Senzor = {
@@ -12,17 +13,23 @@ const Senzor = {
12
13
 
13
14
  /**
14
15
  * Wrap your Cloudflare Worker 'fetch' handler.
15
- * Inject trace controller as the 4th argument.
16
16
  */
17
17
  worker: wrapWorker,
18
18
 
19
19
  /**
20
- * Wrap a Nitro/H3 event handler.
21
- * Use this for Nuxt or pure Nitro server routes.
20
+ * Wrap a generic H3 event handler.
22
21
  */
23
- nitro: wrapH3
22
+ wrapH3: wrapH3,
23
+
24
+ /**
25
+ * Nitro/Nuxt Plugin for global instrumentation.
26
+ * Usage: export default Senzor.nitroPlugin; in server/plugins/senzor.ts
27
+ */
28
+ nitroPlugin: senzorPlugin
24
29
  };
25
30
 
26
31
  export default Senzor;
27
32
  export { Senzor };
28
33
  export type { TraceController } from './core/types';
34
+ // Re-export types that are used in public API signatures
35
+ export type { NitroApp } from './wrappers/nitro';
@@ -0,0 +1,77 @@
1
+ import { client } from '../core/client';
2
+ import { getRoute } from '../core/normalizer';
3
+ import { storage } from '../core/context';
4
+
5
+ // Type definitions for NitroApp to avoid heavy dependencies
6
+ export interface NitroApp {
7
+ h3App: {
8
+ handler: (event: any) => Promise<any>;
9
+ stack: any[];
10
+ };
11
+ hooks: any;
12
+ [key: string]: any;
13
+ }
14
+
15
+ /**
16
+ * A Nitro Plugin to automatically instrument the entire application.
17
+ * Usage: Create 'server/plugins/senzor.ts' and export default senzorPlugin;
18
+ */
19
+ export const senzorPlugin = (nitroApp: NitroApp) => {
20
+ // Capture the original handler
21
+ if (!nitroApp.h3App || !nitroApp.h3App.handler) {
22
+ if (client.options?.debug) console.warn('[Senzor] NitroApp.h3App.handler not found. Skipping instrumentation.');
23
+ return;
24
+ }
25
+
26
+ const originalHandler = nitroApp.h3App.handler;
27
+
28
+ // Replace the main H3 app handler with a wrapped version
29
+ nitroApp.h3App.handler = async (event: any) => {
30
+ const req = event.node?.req || event.req;
31
+ // Fallback for H3 event structure
32
+ const path = req?.originalUrl || req?.url || (event.path as string) || '/';
33
+ const method = req?.method || (event.method as string) || 'GET';
34
+
35
+ // 1. Start Trace
36
+ const session = client.createTrace({
37
+ method: method,
38
+ path: path,
39
+ ip: req?.headers?.['x-forwarded-for'] || req?.socket?.remoteAddress || event.context?.cf?.connectingIp,
40
+ userAgent: req?.headers?.['user-agent'],
41
+ });
42
+
43
+ // 2. Run execution inside AsyncLocalStorage context
44
+ return storage.run(session.controller, async () => {
45
+ let response;
46
+ let status = 200;
47
+
48
+ try {
49
+ response = await originalHandler(event);
50
+
51
+ // Try to determine status from response or event
52
+ if (event.node?.res?.statusCode) status = event.node.res.statusCode;
53
+ if (response?.status) status = response.status;
54
+
55
+ return response;
56
+ } catch (err: any) {
57
+ status = err.statusCode || err.status || 500;
58
+ session.controller.captureException(err);
59
+ throw err;
60
+ } finally {
61
+ // 3. End Trace
62
+ session.end(status, getRoute(event, path));
63
+
64
+ // 4. Flush (Non-blocking)
65
+ // Check for Cloudflare context in various locations
66
+ const cfCtx = event.context?.cloudflare?.context || event.context?.cf || event.context;
67
+ const waitUntil = cfCtx?.waitUntil || event.waitUntil;
68
+
69
+ if (waitUntil && typeof waitUntil === 'function') {
70
+ waitUntil(session.flush());
71
+ } else {
72
+ session.flush().catch(() => { });
73
+ }
74
+ }
75
+ });
76
+ };
77
+ };