@drawbridge/drawbridge-telemetry 0.0.1 → 0.0.3

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
@@ -21,8 +21,6 @@ Requires `@sentry/core` >= 10 as a peer dependency. Backend services satisfy thi
21
21
 
22
22
  ## Naming conventions
23
23
 
24
- Codified in the plan at `~/.claude/plans/yes-lets-see-it-enumerated-bumblebee.md`. Summary:
25
-
26
24
  - **Event names** — `<noun>.<verb>` past tense, lowercase, dot-separated. Verbs match drawbridge-api's action status vocabulary (`processing` / `succeeded` / `failed`; never `pending` / `completed`). Nouns are singular and match Mongo collection names.
27
25
  - **Tag keys** — camelCase. Canonical `traceId` everywhere; `X-Request-Id` header stays for wire compat.
28
26
  - **Event envelope** — `{ name, traceId, userId, organizationId, data: {...}, createdAt }`.
@@ -52,10 +50,10 @@ Sentry events for request BullMQ worker
52
50
 
53
51
  For cron-style repeatable BullMQ jobs (no parent request or change event), synthesize a `__traceId` at enqueue time — pass it via `data.__traceId` to `enqueueFromWorker`.
54
52
 
55
- ## Build
53
+ ## Build & publish
56
54
 
57
55
  ```sh
58
- npm run build
56
+ npm run build # tsup + npm publish
59
57
  ```
60
58
 
61
- Builds CJS + ESM bundles into `dist/` and publishes. Bump `version` in package.json before publishing.
59
+ Bump `version` in `package.json` before publishing.
package/dist/bullmq.cjs CHANGED
@@ -34,6 +34,7 @@ __export(bullmq_exports, {
34
34
  wrapWorkerHandler: () => wrapWorkerHandler
35
35
  });
36
36
  module.exports = __toCommonJS(bullmq_exports);
37
+ var import_crypto = require("crypto");
37
38
  var Sentry2 = __toESM(require("@sentry/core"), 1);
38
39
 
39
40
  // index.js
@@ -50,9 +51,9 @@ var currentTraceId = () => {
50
51
  };
51
52
 
52
53
  // bullmq.js
53
- var wrapWorkerHandler = (handler) => async (jobData, job) => {
54
+ var wrapWorkerHandler = (handler) => async (job) => {
54
55
  var _a;
55
- const traceId = (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId;
56
+ const traceId = ((_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId) || (0, import_crypto.randomUUID)();
56
57
  return withTraceScope(traceId, (scope) => {
57
58
  if (job == null ? void 0 : job.queueName) {
58
59
  scope.setTag("queue", job.queueName);
@@ -62,7 +63,7 @@ var wrapWorkerHandler = (handler) => async (jobData, job) => {
62
63
  scope.setTag("jobName", job.name);
63
64
  }
64
65
  ;
65
- return handler(jobData, job);
66
+ return handler(job);
66
67
  });
67
68
  };
68
69
  var enqueueFromWorker = async (queue, name, data, options = {}) => {
package/dist/bullmq.d.cts CHANGED
@@ -1,19 +1,22 @@
1
+ import { randomUUID } from 'crypto';
1
2
  import { currentTraceId, withTraceScope } from './index.cjs';
2
3
  import '@sentry/core';
3
4
 
4
5
  // Wrap a BullMQ worker handler so it runs inside a Sentry scope tagged
5
- // with the job's traceId, queue name, and job name. The wrapped handler
6
- // keeps the same `( jobData, job )` signature the existing queue-factory
7
- // uses, so worker code itself doesn't change.
6
+ // with the job's traceId, queue name, and job name. Matches BullMQ's
7
+ // native single-arg `( job )` callback shape pass it directly to
8
+ // `new Worker( name, wrapWorkerHandler( handler ), ... )`.
8
9
  //
9
- // The traceId originates from one of two places:
10
+ // The traceId originates from one of three places:
10
11
  // - HTTP request: the API's req.id is forwarded as job.data.__traceId
11
12
  // - Change stream: the Mongo resume token (_id._data) is forwarded
12
- // Either way, the wrapper just reads job.data.__traceId.
13
+ // - Synthesized: any job arriving with no __traceId gets a fresh UUID,
14
+ // so cron / repeatable jobs (no parent context) still produce
15
+ // correlated downstream events.
13
16
 
14
- const wrapWorkerHandler = ( handler ) => async ( jobData, job ) => {
17
+ const wrapWorkerHandler = ( handler ) => async ( job ) => {
15
18
 
16
- const traceId = job?.data?.__traceId;
19
+ const traceId = job?.data?.__traceId || randomUUID();
17
20
 
18
21
  return withTraceScope( traceId, ( scope ) => {
19
22
 
@@ -27,7 +30,7 @@ const wrapWorkerHandler = ( handler ) => async ( jobData, job ) => {
27
30
  scope.setTag( 'jobName', job.name );
28
31
 
29
32
  }
30
- return handler( jobData, job );
33
+ return handler( job );
31
34
 
32
35
  } );
33
36
 
package/dist/bullmq.d.ts CHANGED
@@ -1,19 +1,22 @@
1
+ import { randomUUID } from 'crypto';
1
2
  import { currentTraceId, withTraceScope } from './index.js';
2
3
  import '@sentry/core';
3
4
 
4
5
  // Wrap a BullMQ worker handler so it runs inside a Sentry scope tagged
5
- // with the job's traceId, queue name, and job name. The wrapped handler
6
- // keeps the same `( jobData, job )` signature the existing queue-factory
7
- // uses, so worker code itself doesn't change.
6
+ // with the job's traceId, queue name, and job name. Matches BullMQ's
7
+ // native single-arg `( job )` callback shape pass it directly to
8
+ // `new Worker( name, wrapWorkerHandler( handler ), ... )`.
8
9
  //
9
- // The traceId originates from one of two places:
10
+ // The traceId originates from one of three places:
10
11
  // - HTTP request: the API's req.id is forwarded as job.data.__traceId
11
12
  // - Change stream: the Mongo resume token (_id._data) is forwarded
12
- // Either way, the wrapper just reads job.data.__traceId.
13
+ // - Synthesized: any job arriving with no __traceId gets a fresh UUID,
14
+ // so cron / repeatable jobs (no parent context) still produce
15
+ // correlated downstream events.
13
16
 
14
- const wrapWorkerHandler = ( handler ) => async ( jobData, job ) => {
17
+ const wrapWorkerHandler = ( handler ) => async ( job ) => {
15
18
 
16
- const traceId = job?.data?.__traceId;
19
+ const traceId = job?.data?.__traceId || randomUUID();
17
20
 
18
21
  return withTraceScope( traceId, ( scope ) => {
19
22
 
@@ -27,7 +30,7 @@ const wrapWorkerHandler = ( handler ) => async ( jobData, job ) => {
27
30
  scope.setTag( 'jobName', job.name );
28
31
 
29
32
  }
30
- return handler( jobData, job );
33
+ return handler( job );
31
34
 
32
35
  } );
33
36
 
package/dist/bullmq.js CHANGED
@@ -4,10 +4,11 @@ import {
4
4
  } from "./chunk-SRV5HFQT.js";
5
5
 
6
6
  // bullmq.js
7
+ import { randomUUID } from "crypto";
7
8
  import * as Sentry from "@sentry/core";
8
- var wrapWorkerHandler = (handler) => async (jobData, job) => {
9
+ var wrapWorkerHandler = (handler) => async (job) => {
9
10
  var _a;
10
- const traceId = (_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId;
11
+ const traceId = ((_a = job == null ? void 0 : job.data) == null ? void 0 : _a.__traceId) || randomUUID();
11
12
  return withTraceScope(traceId, (scope) => {
12
13
  if (job == null ? void 0 : job.queueName) {
13
14
  scope.setTag("queue", job.queueName);
@@ -17,7 +18,7 @@ var wrapWorkerHandler = (handler) => async (jobData, job) => {
17
18
  scope.setTag("jobName", job.name);
18
19
  }
19
20
  ;
20
- return handler(jobData, job);
21
+ return handler(job);
21
22
  });
22
23
  };
23
24
  var enqueueFromWorker = async (queue, name, data, options = {}) => {
package/dist/express.cjs CHANGED
@@ -29,27 +29,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // express.js
30
30
  var express_exports = {};
31
31
  __export(express_exports, {
32
+ applyRequestContext: () => applyRequestContext,
32
33
  expressContextMiddleware: () => expressContextMiddleware
33
34
  });
34
35
  module.exports = __toCommonJS(express_exports);
35
36
  var Sentry = __toESM(require("@sentry/core"), 1);
36
- var expressContextMiddleware = () => (req, res, next) => {
37
+ var applyRequestContext = (req) => {
37
38
  const scope = Sentry.getCurrentScope();
38
39
  if (req == null ? void 0 : req.id) {
39
40
  scope.setTag("traceId", req.id);
40
41
  }
41
42
  ;
42
- if (req == null ? void 0 : req.user) {
43
- scope.setUser({
44
- id: req.user.id || req.user._id,
45
- email: req.user.email
46
- });
47
- }
48
- ;
49
- if (req == null ? void 0 : req.organization) {
50
- scope.setTag("organization", req.organization.id || req.organization._id);
51
- }
52
- ;
53
43
  if (req == null ? void 0 : req.method) {
54
44
  scope.setTag("method", req.method);
55
45
  }
@@ -58,9 +48,25 @@ var expressContextMiddleware = () => (req, res, next) => {
58
48
  scope.setTag("route", req.path);
59
49
  }
60
50
  ;
51
+ const user = (req == null ? void 0 : req.authenticated) || (req == null ? void 0 : req.user);
52
+ if (user) {
53
+ scope.setUser({
54
+ id: user.id || user._id,
55
+ email: user.email
56
+ });
57
+ }
58
+ ;
59
+ if (req == null ? void 0 : req.organization) {
60
+ scope.setTag("organization", req.organization.id || req.organization._id);
61
+ }
62
+ ;
63
+ };
64
+ var expressContextMiddleware = () => (req, res, next) => {
65
+ applyRequestContext(req);
61
66
  next();
62
67
  };
63
68
  // Annotate the CommonJS export names for ESM import in node:
64
69
  0 && (module.exports = {
70
+ applyRequestContext,
65
71
  expressContextMiddleware
66
72
  });
@@ -1,16 +1,14 @@
1
1
  import * as Sentry from '@sentry/core';
2
2
 
3
- // Express middleware that tags the active Sentry scope with the current
4
- // user/organization/route/method/traceId. Mount AFTER auth resolution (so
5
- // req.user and req.organization are populated) and AFTER any req.id
6
- // middleware (so req.id exists to use as the traceId).
3
+ // Apply all available context tags from a request to the active Sentry
4
+ // scope. Idempotent safe to call multiple times as the request enriches
5
+ // (e.g. once at request entry for traceId/method/path, again from auth
6
+ // middleware once req.authenticated and req.organization are populated).
7
7
  //
8
- // Every Sentry event captured during this request — and any downstream
9
- // BullMQ jobs that propagate the traceId via job.data.__traceId — is
10
- // filterable by these tags. Without this, errors arrive in Sentry as
11
- // anonymous stack traces.
8
+ // Reads from req.authenticated first (drawbridge-api convention) and
9
+ // falls back to req.user for compatibility with other services.
12
10
 
13
- const expressContextMiddleware = () => ( req, res, next ) => {
11
+ const applyRequestContext = ( req ) => {
14
12
 
15
13
  const scope = Sentry.getCurrentScope();
16
14
 
@@ -19,11 +17,27 @@ const expressContextMiddleware = () => ( req, res, next ) => {
19
17
  scope.setTag( 'traceId', req.id );
20
18
 
21
19
  }
22
- if( req?.user ){
20
+ if( req?.method ){
21
+
22
+ scope.setTag( 'method', req.method );
23
+
24
+ }
25
+ // req.route is set by Express only AFTER route matching, which hasn't
26
+ // happened yet at request-entry middleware time. Use req.path as a
27
+ // best-effort route tag — aggregation by route pattern is left to a
28
+ // future res.on('finish') refinement.
29
+ if( req?.path ){
30
+
31
+ scope.setTag( 'route', req.path );
32
+
33
+ }
34
+ const user = req?.authenticated || req?.user;
35
+
36
+ if( user ){
23
37
 
24
38
  scope.setUser({
25
- id : req.user.id || req.user._id,
26
- email : req.user.email
39
+ id : user.id || user._id,
40
+ email : user.email
27
41
  });
28
42
 
29
43
  }
@@ -32,22 +46,21 @@ const expressContextMiddleware = () => ( req, res, next ) => {
32
46
  scope.setTag( 'organization', req.organization.id || req.organization._id );
33
47
 
34
48
  }
35
- if( req?.method ){
49
+ };
36
50
 
37
- scope.setTag( 'method', req.method );
51
+ // Express middleware factory — runs `applyRequestContext` at request entry
52
+ // and calls next. Tags user/organization opportunistically (only if auth
53
+ // has already populated them); for per-route auth patterns, call
54
+ // `applyRequestContext( req )` again from inside auth middleware after
55
+ // req.authenticated is set so user/org tags apply to the rest of the
56
+ // request's async context.
38
57
 
39
- }
40
- // req.route is set by Express only AFTER route matching, which hasn't
41
- // happened yet at middleware time. Use req.path as a best-effort route
42
- // tag — it captures the URL hit; aggregation by route pattern is left
43
- // to a future res.on('finish') refinement.
44
- if( req?.path ){
58
+ const expressContextMiddleware = () => ( req, res, next ) => {
45
59
 
46
- scope.setTag( 'route', req.path );
60
+ applyRequestContext( req );
47
61
 
48
- }
49
62
  next();
50
63
 
51
64
  };
52
65
 
53
- export { expressContextMiddleware };
66
+ export { applyRequestContext, expressContextMiddleware };
package/dist/express.d.ts CHANGED
@@ -1,16 +1,14 @@
1
1
  import * as Sentry from '@sentry/core';
2
2
 
3
- // Express middleware that tags the active Sentry scope with the current
4
- // user/organization/route/method/traceId. Mount AFTER auth resolution (so
5
- // req.user and req.organization are populated) and AFTER any req.id
6
- // middleware (so req.id exists to use as the traceId).
3
+ // Apply all available context tags from a request to the active Sentry
4
+ // scope. Idempotent safe to call multiple times as the request enriches
5
+ // (e.g. once at request entry for traceId/method/path, again from auth
6
+ // middleware once req.authenticated and req.organization are populated).
7
7
  //
8
- // Every Sentry event captured during this request — and any downstream
9
- // BullMQ jobs that propagate the traceId via job.data.__traceId — is
10
- // filterable by these tags. Without this, errors arrive in Sentry as
11
- // anonymous stack traces.
8
+ // Reads from req.authenticated first (drawbridge-api convention) and
9
+ // falls back to req.user for compatibility with other services.
12
10
 
13
- const expressContextMiddleware = () => ( req, res, next ) => {
11
+ const applyRequestContext = ( req ) => {
14
12
 
15
13
  const scope = Sentry.getCurrentScope();
16
14
 
@@ -19,11 +17,27 @@ const expressContextMiddleware = () => ( req, res, next ) => {
19
17
  scope.setTag( 'traceId', req.id );
20
18
 
21
19
  }
22
- if( req?.user ){
20
+ if( req?.method ){
21
+
22
+ scope.setTag( 'method', req.method );
23
+
24
+ }
25
+ // req.route is set by Express only AFTER route matching, which hasn't
26
+ // happened yet at request-entry middleware time. Use req.path as a
27
+ // best-effort route tag — aggregation by route pattern is left to a
28
+ // future res.on('finish') refinement.
29
+ if( req?.path ){
30
+
31
+ scope.setTag( 'route', req.path );
32
+
33
+ }
34
+ const user = req?.authenticated || req?.user;
35
+
36
+ if( user ){
23
37
 
24
38
  scope.setUser({
25
- id : req.user.id || req.user._id,
26
- email : req.user.email
39
+ id : user.id || user._id,
40
+ email : user.email
27
41
  });
28
42
 
29
43
  }
@@ -32,22 +46,21 @@ const expressContextMiddleware = () => ( req, res, next ) => {
32
46
  scope.setTag( 'organization', req.organization.id || req.organization._id );
33
47
 
34
48
  }
35
- if( req?.method ){
49
+ };
36
50
 
37
- scope.setTag( 'method', req.method );
51
+ // Express middleware factory — runs `applyRequestContext` at request entry
52
+ // and calls next. Tags user/organization opportunistically (only if auth
53
+ // has already populated them); for per-route auth patterns, call
54
+ // `applyRequestContext( req )` again from inside auth middleware after
55
+ // req.authenticated is set so user/org tags apply to the rest of the
56
+ // request's async context.
38
57
 
39
- }
40
- // req.route is set by Express only AFTER route matching, which hasn't
41
- // happened yet at middleware time. Use req.path as a best-effort route
42
- // tag — it captures the URL hit; aggregation by route pattern is left
43
- // to a future res.on('finish') refinement.
44
- if( req?.path ){
58
+ const expressContextMiddleware = () => ( req, res, next ) => {
45
59
 
46
- scope.setTag( 'route', req.path );
60
+ applyRequestContext( req );
47
61
 
48
- }
49
62
  next();
50
63
 
51
64
  };
52
65
 
53
- export { expressContextMiddleware };
66
+ export { applyRequestContext, expressContextMiddleware };
package/dist/express.js CHANGED
@@ -1,22 +1,11 @@
1
1
  // express.js
2
2
  import * as Sentry from "@sentry/core";
3
- var expressContextMiddleware = () => (req, res, next) => {
3
+ var applyRequestContext = (req) => {
4
4
  const scope = Sentry.getCurrentScope();
5
5
  if (req == null ? void 0 : req.id) {
6
6
  scope.setTag("traceId", req.id);
7
7
  }
8
8
  ;
9
- if (req == null ? void 0 : req.user) {
10
- scope.setUser({
11
- id: req.user.id || req.user._id,
12
- email: req.user.email
13
- });
14
- }
15
- ;
16
- if (req == null ? void 0 : req.organization) {
17
- scope.setTag("organization", req.organization.id || req.organization._id);
18
- }
19
- ;
20
9
  if (req == null ? void 0 : req.method) {
21
10
  scope.setTag("method", req.method);
22
11
  }
@@ -25,8 +14,24 @@ var expressContextMiddleware = () => (req, res, next) => {
25
14
  scope.setTag("route", req.path);
26
15
  }
27
16
  ;
17
+ const user = (req == null ? void 0 : req.authenticated) || (req == null ? void 0 : req.user);
18
+ if (user) {
19
+ scope.setUser({
20
+ id: user.id || user._id,
21
+ email: user.email
22
+ });
23
+ }
24
+ ;
25
+ if (req == null ? void 0 : req.organization) {
26
+ scope.setTag("organization", req.organization.id || req.organization._id);
27
+ }
28
+ ;
29
+ };
30
+ var expressContextMiddleware = () => (req, res, next) => {
31
+ applyRequestContext(req);
28
32
  next();
29
33
  };
30
34
  export {
35
+ applyRequestContext,
31
36
  expressContextMiddleware
32
37
  };
package/package.json CHANGED
@@ -1,9 +1,5 @@
1
1
  {
2
2
  "type": "module",
3
- "dependencies": {
4
- "tsup": "8.5.1",
5
- "typescript": "5.9.3"
6
- },
7
3
  "peerDependencies": {
8
4
  "@sentry/core": ">=10"
9
5
  },
@@ -13,7 +9,9 @@
13
9
  }
14
10
  },
15
11
  "devDependencies": {
16
- "@sentry/core": "10.27.0"
12
+ "@sentry/core": "10.27.0",
13
+ "tsup": "8.5.1",
14
+ "typescript": "5.9.3"
17
15
  },
18
16
  "exports": {
19
17
  ".": {
@@ -52,5 +50,5 @@
52
50
  "build": "tsup && npm publish"
53
51
  },
54
52
  "types": "dist/index.d.ts",
55
- "version": "0.0.1"
53
+ "version": "0.0.3"
56
54
  }