@edraj/sauron-node 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +140 -0
- package/dist/client.js +24 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/transaction-extra.d.ts +36 -0
- package/dist/transaction-extra.js +53 -0
- package/dist/transport.js +1 -1
- package/dist/types.d.ts +48 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@edraj/sauron-node` are documented here.
|
|
4
4
|
|
|
5
|
+
## 1.5.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`tags` and `extra` on transactions.** `trackTransaction` now accepts two
|
|
10
|
+
developer-supplied maps: `tags` (flat string→string, indexed) and `extra`
|
|
11
|
+
(freeform JSON). `extra` is where a request body, a response body, an order
|
|
12
|
+
id or a retry count goes — the span that times an HTTP call can now carry
|
|
13
|
+
what the call actually sent and received.
|
|
14
|
+
|
|
15
|
+
Both are visible on the new **Transactions** page and in the session
|
|
16
|
+
timeline, and both are searchable: `@tag.tier:premium`,
|
|
17
|
+
`extra.order_id:9001`, or `extra.response:~9001` to match a substring
|
|
18
|
+
*inside* a stored response body.
|
|
19
|
+
|
|
20
|
+
**They are per-call only.** Unlike `captureException` and `track`, a
|
|
21
|
+
transaction does not inherit the scope — `setTag()` / `setExtra()` defaults
|
|
22
|
+
are not merged in. Transactions are the highest-volume signal an app emits,
|
|
23
|
+
one per navigation and per request, so inheriting a global blob would write
|
|
24
|
+
it onto every row. This asymmetry is deliberate and is documented on the
|
|
25
|
+
method.
|
|
26
|
+
|
|
27
|
+
`extra` is serialized and capped at **16 KB**. Past that the whole map is
|
|
28
|
+
replaced with a `{"_truncated": true, "_bytes": N}` marker, and the
|
|
29
|
+
dashboard says so on the row rather than showing a short object that looks
|
|
30
|
+
complete. The cap is not cosmetic: envelopes are batched, and one oversized
|
|
31
|
+
body would push the whole envelope past the ingest limit and take every
|
|
32
|
+
unrelated span sent with it — a silent loss of data nobody asked about.
|
|
33
|
+
Size is measured in UTF-8 bytes, so non-ASCII payloads are counted at what
|
|
34
|
+
they actually cost on the wire.
|
|
35
|
+
|
|
36
|
+
Nothing in `extra` is scrubbed. `beforeSend` remains the redaction seam;
|
|
37
|
+
think twice before attaching a body that can carry tokens or personal data.
|
|
38
|
+
|
|
39
|
+
An app that sets neither field serializes byte-identically to before: both
|
|
40
|
+
keys are omitted when empty, never sent as `null`.
|
|
41
|
+
|
|
42
|
+
Signature: `trackTransaction({ …, tags?: Record<string, string>, extra?: Record<string, unknown> })`. `MAX_TRANSACTION_EXTRA_BYTES` and `capTransactionExtra` are now exported from the package entrypoint, so a caller can size a payload before attaching it.
|
|
43
|
+
|
|
44
|
+
## 1.4.0
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- **`trackTransaction` shipped transactions with no duration, silently.** This
|
|
49
|
+
SDK's input field is `duration_ms`; the browser SDK's is `durationMs`. A
|
|
50
|
+
snippet moved between the two — or any plain-JavaScript caller, where the type
|
|
51
|
+
checker is not there to object — produced a transaction item missing the one
|
|
52
|
+
field it exists to carry, and nothing anywhere complained: the item validated,
|
|
53
|
+
shipped, and looked delivered.
|
|
54
|
+
|
|
55
|
+
`durationMs` is now accepted as an alias, and a transaction with no usable
|
|
56
|
+
duration is **dropped with a debug log line rather than sent**. Refusing is the
|
|
57
|
+
point: sending it anyway is what let the misspelling survive unnoticed.
|
|
58
|
+
`duration_ms: 0` is a legitimate duration and is still sent.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- `TransactionInput.duration_ms` is now optional, since `durationMs` may supply
|
|
63
|
+
it instead. Supply exactly one — `duration_ms` wins if both are present.
|
|
64
|
+
Supplying neither is the drop above, not a compile error.
|
|
65
|
+
|
|
5
66
|
## 1.3.0
|
|
6
67
|
|
|
7
68
|
### Added
|
package/README.md
CHANGED
|
@@ -275,10 +275,28 @@ function trackTransaction(input: TransactionInput): void
|
|
|
275
275
|
| `input.http_status` | `number` | omitted | e.g. `200`. |
|
|
276
276
|
| `input.url` | `string` | omitted | Request URL/path. |
|
|
277
277
|
| `input.distinct_id` | `string` | scope user's `id`, else omitted | Explicit value wins over the scope. |
|
|
278
|
+
| `input.tags` | `Record<string, string>` | omitted | Indexed string→string labels. Filter with `@tag.key:value` on the Transactions page. |
|
|
279
|
+
| `input.extra` | `Record<string, unknown>` | omitted | Freeform JSON — request body, response body, SQL text, row counts. Searchable with `extra.key:value`. |
|
|
278
280
|
|
|
279
281
|
Emits a `transaction` item. Absent optional fields are omitted from the wire JSON
|
|
280
282
|
rather than serialized as `null`. Returns `void`.
|
|
281
283
|
|
|
284
|
+
**`tags` and `extra` are per-call only.** Unlike `track()` and
|
|
285
|
+
`captureException()`, a transaction does **not** inherit the scope:
|
|
286
|
+
`setTag()` / `setExtra()` defaults are not merged in. Transactions are the
|
|
287
|
+
highest-volume signal a service emits — one per request and per query — so
|
|
288
|
+
inheriting a global blob would write it onto every row.
|
|
289
|
+
|
|
290
|
+
`extra` is serialized and capped at **16 KB**
|
|
291
|
+
(`MAX_TRANSACTION_EXTRA_BYTES`, exported from `transaction-extra`). Past that
|
|
292
|
+
the whole map is replaced with `{ _truncated: true, _bytes: N }` and the
|
|
293
|
+
dashboard says so on the row. The cap is not cosmetic: envelopes are batched,
|
|
294
|
+
and one oversized body would push the whole envelope past the ingest limit and
|
|
295
|
+
drop every unrelated span sent with it.
|
|
296
|
+
|
|
297
|
+
Nothing in `extra` is scrubbed. Use `beforeSend` for redaction, and think twice
|
|
298
|
+
before attaching a body that can carry tokens, passwords or personal data.
|
|
299
|
+
|
|
282
300
|
```ts
|
|
283
301
|
const started = Date.now();
|
|
284
302
|
await handler(req, res);
|
|
@@ -293,6 +311,128 @@ trackTransaction({
|
|
|
293
311
|
});
|
|
294
312
|
```
|
|
295
313
|
|
|
314
|
+
#### Example: an Express route, with request and response bodies
|
|
315
|
+
|
|
316
|
+
`res.json` is wrapped rather than read afterwards, because by the time the
|
|
317
|
+
`finish` event fires the payload is already on the wire and gone.
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
import express from 'express';
|
|
321
|
+
import { trackTransaction } from '@edraj/sauron-node';
|
|
322
|
+
|
|
323
|
+
export function tracedRoute(routeLabel: string, handler: express.RequestHandler) {
|
|
324
|
+
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
325
|
+
const started = Date.now();
|
|
326
|
+
let responseBody: unknown;
|
|
327
|
+
|
|
328
|
+
// Capture on the way out. Reading it in `finish` is too late — the body
|
|
329
|
+
// has already been serialized and released by then.
|
|
330
|
+
const json = res.json.bind(res);
|
|
331
|
+
res.json = (body: unknown) => {
|
|
332
|
+
responseBody = body;
|
|
333
|
+
return json(body);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
res.on('finish', () => {
|
|
337
|
+
trackTransaction({
|
|
338
|
+
name: routeLabel, // 'POST /orders', NOT '/orders/8412'
|
|
339
|
+
op: 'http',
|
|
340
|
+
duration_ms: Date.now() - started,
|
|
341
|
+
http_method: req.method,
|
|
342
|
+
http_status: res.statusCode,
|
|
343
|
+
url: req.originalUrl,
|
|
344
|
+
status: res.statusCode < 400 ? 'ok' : 'error',
|
|
345
|
+
distinct_id: (req as { userId?: string }).userId,
|
|
346
|
+
tags: { route: routeLabel, tier: (req as { plan?: string }).plan ?? 'free' },
|
|
347
|
+
extra: {
|
|
348
|
+
request: req.body,
|
|
349
|
+
response: responseBody,
|
|
350
|
+
query: req.query,
|
|
351
|
+
// Header VALUES are omitted on purpose — `authorization` and
|
|
352
|
+
// `cookie` live there.
|
|
353
|
+
request_headers: Object.keys(req.headers),
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
await handler(req, res, next);
|
|
360
|
+
} catch (err) {
|
|
361
|
+
next(err);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const app = express();
|
|
367
|
+
app.post('/orders', tracedRoute('POST /orders', createOrder));
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
On the dashboard: **Transactions → the row → expand**. Both bodies render as a
|
|
371
|
+
JSON tree, and every one of these finds it:
|
|
372
|
+
|
|
373
|
+
```text
|
|
374
|
+
extra.response:~9001 # substring, inside the stored response body
|
|
375
|
+
@tag.route:"POST /orders" # indexed tag
|
|
376
|
+
op:http http.status:>=500 # the failures
|
|
377
|
+
duration:>2s # the slow ones
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### Example: a SQL query (`pg`)
|
|
381
|
+
|
|
382
|
+
Put the **statement** in `extra` and keep `name` a stable label — a query with
|
|
383
|
+
literals baked in would mint a new dashboard row per execution.
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
import { Pool } from 'pg';
|
|
387
|
+
import { trackTransaction } from '@edraj/sauron-node';
|
|
388
|
+
|
|
389
|
+
const pool = new Pool();
|
|
390
|
+
|
|
391
|
+
export async function tracedQuery<T>(label: string, sql: string, params: unknown[] = []) {
|
|
392
|
+
const started = Date.now();
|
|
393
|
+
try {
|
|
394
|
+
const result = await pool.query<T>(sql, params);
|
|
395
|
+
trackTransaction({
|
|
396
|
+
// The LABEL, not the statement. `op` is free-form on this SDK, so a
|
|
397
|
+
// 'db' op is stored as-is — but note the browser SDK coerces anything
|
|
398
|
+
// outside navigation|http|resource|screen_load|custom to 'custom', so
|
|
399
|
+
// use a tag if you need the two to agree.
|
|
400
|
+
name: label,
|
|
401
|
+
op: 'db',
|
|
402
|
+
duration_ms: Date.now() - started,
|
|
403
|
+
status: 'ok',
|
|
404
|
+
tags: { db: 'postgres', table: 'orders' },
|
|
405
|
+
extra: {
|
|
406
|
+
statement: sql,
|
|
407
|
+
row_count: result.rowCount,
|
|
408
|
+
// Bind PARAMETERS are user data. Log them only if you have decided
|
|
409
|
+
// that is acceptable, or log their shape instead.
|
|
410
|
+
params,
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
return result;
|
|
414
|
+
} catch (err) {
|
|
415
|
+
trackTransaction({
|
|
416
|
+
name: label,
|
|
417
|
+
op: 'db',
|
|
418
|
+
duration_ms: Date.now() - started,
|
|
419
|
+
status: 'error',
|
|
420
|
+
tags: { db: 'postgres', table: 'orders' },
|
|
421
|
+
extra: { statement: sql, error: String(err) },
|
|
422
|
+
});
|
|
423
|
+
throw err;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
await tracedQuery(
|
|
428
|
+
'SELECT orders',
|
|
429
|
+
'SELECT id, total FROM orders WHERE user_id = $1 ORDER BY created_at DESC LIMIT 20',
|
|
430
|
+
[userId],
|
|
431
|
+
);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Then `@tag.table:orders duration:>500ms` is your slow-query list.
|
|
435
|
+
|
|
296
436
|
### `addBreadcrumb(crumb)`
|
|
297
437
|
|
|
298
438
|
```ts
|
package/dist/client.js
CHANGED
|
@@ -6,6 +6,7 @@ import { parseError } from './stacktrace.js';
|
|
|
6
6
|
import { installAutoCapture, installShutdownHooks } from './autocapture.js';
|
|
7
7
|
import { getCurrentScope, getGlobalScope, normalizeBreadcrumb, } from './scope.js';
|
|
8
8
|
import { normalizeReason, normalizeWorkflowName } from './workflow.js';
|
|
9
|
+
import { capTransactionExtra } from './transaction-extra.js';
|
|
9
10
|
const DEFAULTS = {
|
|
10
11
|
release: null,
|
|
11
12
|
sampleRate: 1,
|
|
@@ -179,12 +180,25 @@ export class SauronClient {
|
|
|
179
180
|
trackTransaction(input) {
|
|
180
181
|
if (typeof input?.name !== 'string' || input.name.length === 0)
|
|
181
182
|
return;
|
|
183
|
+
// `durationMs` accepted as an alias — see TransactionInput. Without it, a
|
|
184
|
+
// caller using the browser SDK's spelling shipped a transaction with the
|
|
185
|
+
// duration field absent and no complaint from anywhere.
|
|
186
|
+
const duration = input.duration_ms ?? input.durationMs;
|
|
187
|
+
if (typeof duration !== 'number' || !Number.isFinite(duration)) {
|
|
188
|
+
// Refused loudly rather than sent. A transaction whose whole purpose is
|
|
189
|
+
// to record a duration is not worth persisting without one, and silence
|
|
190
|
+
// here is what let the mis-spelling survive: the item looked delivered.
|
|
191
|
+
this.debugLog(`dropped transaction "${input.name}": duration_ms must be a finite number ` +
|
|
192
|
+
`(got ${typeof duration === 'number' ? duration : typeof duration}). ` +
|
|
193
|
+
`Use duration_ms, or durationMs if you are porting from the browser SDK.`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
182
196
|
const distinctId = input.distinct_id ?? getCurrentScope().data.user?.id ?? undefined;
|
|
183
197
|
const item = {
|
|
184
198
|
type: 'transaction',
|
|
185
199
|
name: input.name,
|
|
186
200
|
op: input.op ?? 'custom',
|
|
187
|
-
duration_ms:
|
|
201
|
+
duration_ms: duration,
|
|
188
202
|
timestamp: isoNow(),
|
|
189
203
|
};
|
|
190
204
|
if (input.status !== undefined)
|
|
@@ -197,6 +211,15 @@ export class SauronClient {
|
|
|
197
211
|
item.url = input.url;
|
|
198
212
|
if (distinctId != null)
|
|
199
213
|
item.distinct_id = distinctId;
|
|
214
|
+
// Per-call only — deliberately no `globalScope.tags`/`.extra` merge here,
|
|
215
|
+
// unlike `track()` and `captureException` below. See TransactionInput.
|
|
216
|
+
// Omitted when empty so an app that never sets them is byte-identical on
|
|
217
|
+
// the wire to before these fields existed.
|
|
218
|
+
if (input.tags && Object.keys(input.tags).length > 0)
|
|
219
|
+
item.tags = { ...input.tags };
|
|
220
|
+
if (input.extra && Object.keys(input.extra).length > 0) {
|
|
221
|
+
item.extra = capTransactionExtra({ ...input.extra });
|
|
222
|
+
}
|
|
200
223
|
this.dispatch(item);
|
|
201
224
|
}
|
|
202
225
|
/** Capture a product-analytics event. `distinctId` is required. */
|
package/dist/index.d.ts
CHANGED
|
@@ -77,3 +77,4 @@ export declare function setExtra(key: string, value: unknown): void;
|
|
|
77
77
|
export declare function flush(): Promise<void>;
|
|
78
78
|
/** Flush and stop the background timer, then clear the active client. */
|
|
79
79
|
export declare function close(): Promise<void>;
|
|
80
|
+
export { MAX_TRANSACTION_EXTRA_BYTES, capTransactionExtra, } from './transaction-extra.js';
|
package/dist/index.js
CHANGED
|
@@ -112,3 +112,6 @@ export async function close() {
|
|
|
112
112
|
activeClient = null;
|
|
113
113
|
await client.close();
|
|
114
114
|
}
|
|
115
|
+
// Exported so a caller can size a payload BEFORE attaching it — the cap is
|
|
116
|
+
// otherwise invisible until the dashboard shows a truncation marker.
|
|
117
|
+
export { MAX_TRANSACTION_EXTRA_BYTES, capTransactionExtra, } from './transaction-extra.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The size cap on a transaction's developer-supplied `extra`.
|
|
3
|
+
*
|
|
4
|
+
* Its own module rather than a private helper inside `client.ts` so the limit
|
|
5
|
+
* and its behaviour are directly testable — the failure this guards against
|
|
6
|
+
* (an oversized payload taking a whole batched envelope down with it) is not
|
|
7
|
+
* visible from the outside once it happens.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Largest serialized `extra` a single transaction may carry, in bytes.
|
|
11
|
+
*
|
|
12
|
+
* Transactions are the highest-volume signal and they ship in BATCHED
|
|
13
|
+
* envelopes, so one oversized payload does not fail alone — ingest rejects the
|
|
14
|
+
* whole envelope past `INGEST_MAX_BODY_BYTES` (1 MiB by default) and every
|
|
15
|
+
* unrelated span batched with it is lost. Since the motivating use of
|
|
16
|
+
* transaction `extra` is request and response bodies, that is not a remote
|
|
17
|
+
* hazard.
|
|
18
|
+
*
|
|
19
|
+
* Kept identical across all five SDKs. If it moves, it moves everywhere.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MAX_TRANSACTION_EXTRA_BYTES: number;
|
|
22
|
+
/**
|
|
23
|
+
* Cap a transaction's `extra`, substituting a marker when it is too large.
|
|
24
|
+
*
|
|
25
|
+
* Replaces the WHOLE map rather than trimming keys: a half-written JSON value
|
|
26
|
+
* is worse than an honest marker, and per-key trimming would make the result
|
|
27
|
+
* depend on key iteration order, which differs across the five SDKs. The marker
|
|
28
|
+
* is deliberately readable on the dashboard — `_truncated` says data was
|
|
29
|
+
* dropped rather than silently serving a short object that looks complete.
|
|
30
|
+
*
|
|
31
|
+
* A value that cannot be serialized at all (a cycle, a BigInt) becomes the same
|
|
32
|
+
* marker with `_bytes: -1`, because the alternative is throwing from inside
|
|
33
|
+
* `trackTransaction` — and an SDK that crashes the app it is measuring is worse
|
|
34
|
+
* than one that drops a payload.
|
|
35
|
+
*/
|
|
36
|
+
export declare function capTransactionExtra(extra: Record<string, unknown>, maxBytes?: number): Record<string, unknown>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The size cap on a transaction's developer-supplied `extra`.
|
|
3
|
+
*
|
|
4
|
+
* Its own module rather than a private helper inside `client.ts` so the limit
|
|
5
|
+
* and its behaviour are directly testable — the failure this guards against
|
|
6
|
+
* (an oversized payload taking a whole batched envelope down with it) is not
|
|
7
|
+
* visible from the outside once it happens.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Largest serialized `extra` a single transaction may carry, in bytes.
|
|
11
|
+
*
|
|
12
|
+
* Transactions are the highest-volume signal and they ship in BATCHED
|
|
13
|
+
* envelopes, so one oversized payload does not fail alone — ingest rejects the
|
|
14
|
+
* whole envelope past `INGEST_MAX_BODY_BYTES` (1 MiB by default) and every
|
|
15
|
+
* unrelated span batched with it is lost. Since the motivating use of
|
|
16
|
+
* transaction `extra` is request and response bodies, that is not a remote
|
|
17
|
+
* hazard.
|
|
18
|
+
*
|
|
19
|
+
* Kept identical across all five SDKs. If it moves, it moves everywhere.
|
|
20
|
+
*/
|
|
21
|
+
export const MAX_TRANSACTION_EXTRA_BYTES = 16 * 1024;
|
|
22
|
+
/**
|
|
23
|
+
* Cap a transaction's `extra`, substituting a marker when it is too large.
|
|
24
|
+
*
|
|
25
|
+
* Replaces the WHOLE map rather than trimming keys: a half-written JSON value
|
|
26
|
+
* is worse than an honest marker, and per-key trimming would make the result
|
|
27
|
+
* depend on key iteration order, which differs across the five SDKs. The marker
|
|
28
|
+
* is deliberately readable on the dashboard — `_truncated` says data was
|
|
29
|
+
* dropped rather than silently serving a short object that looks complete.
|
|
30
|
+
*
|
|
31
|
+
* A value that cannot be serialized at all (a cycle, a BigInt) becomes the same
|
|
32
|
+
* marker with `_bytes: -1`, because the alternative is throwing from inside
|
|
33
|
+
* `trackTransaction` — and an SDK that crashes the app it is measuring is worse
|
|
34
|
+
* than one that drops a payload.
|
|
35
|
+
*/
|
|
36
|
+
export function capTransactionExtra(extra, maxBytes = MAX_TRANSACTION_EXTRA_BYTES) {
|
|
37
|
+
let bytes;
|
|
38
|
+
try {
|
|
39
|
+
const json = JSON.stringify(extra);
|
|
40
|
+
if (json === undefined)
|
|
41
|
+
return { _truncated: true, _bytes: -1 };
|
|
42
|
+
// UTF-8 byte length, not `json.length`: the latter undercounts every
|
|
43
|
+
// non-ASCII byte, which is exactly what a response body full of user text
|
|
44
|
+
// is made of.
|
|
45
|
+
bytes = Buffer.byteLength(json, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { _truncated: true, _bytes: -1 };
|
|
49
|
+
}
|
|
50
|
+
if (bytes <= maxBytes)
|
|
51
|
+
return extra;
|
|
52
|
+
return { _truncated: true, _bytes: bytes };
|
|
53
|
+
}
|
package/dist/transport.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { maybeGzip } from './gzip.js';
|
|
2
2
|
import { BoundedQueue } from './queue.js';
|
|
3
3
|
const SDK_NAME = 'sauron-node';
|
|
4
|
-
const SDK_VERSION = '1.
|
|
4
|
+
const SDK_VERSION = '1.5.0';
|
|
5
5
|
/** Default exponential-backoff base (ms) for the first retry. */
|
|
6
6
|
const DEFAULT_RETRY_BASE_MS = 200;
|
|
7
7
|
/** Hard cap on any single backoff delay (ms). */
|
package/dist/types.d.ts
CHANGED
|
@@ -161,19 +161,66 @@ export interface TransactionItem {
|
|
|
161
161
|
workflow_id?: string;
|
|
162
162
|
workflow_name?: string;
|
|
163
163
|
timestamp: string;
|
|
164
|
+
/**
|
|
165
|
+
* Developer-supplied flat string tags for THIS transaction. NOT merged with
|
|
166
|
+
* the scope — see {@link TransactionInput.tags}. Omitted when empty.
|
|
167
|
+
*/
|
|
168
|
+
tags?: Record<string, string>;
|
|
169
|
+
/**
|
|
170
|
+
* Developer-supplied freeform JSON for THIS transaction. Capped and replaced
|
|
171
|
+
* with a truncation marker past `MAX_TRANSACTION_EXTRA_BYTES`. Omitted when
|
|
172
|
+
* empty.
|
|
173
|
+
*/
|
|
174
|
+
extra?: Record<string, unknown>;
|
|
164
175
|
}
|
|
165
176
|
/** Caller input for {@link TransactionItem} via `trackTransaction`. */
|
|
166
177
|
export interface TransactionInput {
|
|
167
178
|
name: string;
|
|
168
179
|
/** Operation class: `navigation | http | resource | screen_load | custom`. Default `custom`. */
|
|
169
180
|
op?: string;
|
|
170
|
-
duration_ms
|
|
181
|
+
duration_ms?: number;
|
|
182
|
+
/**
|
|
183
|
+
* Accepted alias for {@link duration_ms}.
|
|
184
|
+
*
|
|
185
|
+
* The browser SDK's equivalent input takes `durationMs` (`sdks/js`), so a
|
|
186
|
+
* snippet moved between the two — or any plain-JavaScript caller, where the
|
|
187
|
+
* type checker is not there to object — used to produce a transaction with no
|
|
188
|
+
* duration at all. The item still validated and still shipped, just without
|
|
189
|
+
* the one field it exists to carry, which is the worst possible outcome: no
|
|
190
|
+
* error anywhere and a silently useless performance record.
|
|
191
|
+
*
|
|
192
|
+
* Supply exactly one. `duration_ms` wins if both are present.
|
|
193
|
+
*/
|
|
194
|
+
durationMs?: number;
|
|
171
195
|
status?: string;
|
|
172
196
|
http_method?: string;
|
|
173
197
|
http_status?: number;
|
|
174
198
|
url?: string;
|
|
175
199
|
/** Falls back to the scoped user's id when omitted. */
|
|
176
200
|
distinct_id?: string;
|
|
201
|
+
/**
|
|
202
|
+
* Flat string tags for this transaction.
|
|
203
|
+
*
|
|
204
|
+
* **Per-call only — the scope is NOT merged in**, which is the one place
|
|
205
|
+
* transactions differ from `track()` and `captureException()`. Those two
|
|
206
|
+
* merge `setTag`/`setExtra` defaults; a transaction carries only what its own
|
|
207
|
+
* call site attached. Transactions are the highest-volume signal (one per
|
|
208
|
+
* navigation and per HTTP call), so inheriting a global blob would write it
|
|
209
|
+
* onto every row.
|
|
210
|
+
*/
|
|
211
|
+
tags?: Record<string, string>;
|
|
212
|
+
/**
|
|
213
|
+
* Freeform JSON for this transaction — the request body, the response body,
|
|
214
|
+
* an order id, a retry count.
|
|
215
|
+
*
|
|
216
|
+
* Per-call only, for the reason on {@link TransactionInput.tags}. Serialized
|
|
217
|
+
* and capped at `MAX_TRANSACTION_EXTRA_BYTES`; past that the whole map is
|
|
218
|
+
* replaced with `{ _truncated: true, _bytes: N }` so one large body cannot
|
|
219
|
+
* take a batched envelope over the ingest limit and drop every span in it.
|
|
220
|
+
*
|
|
221
|
+
* Nothing here is scrubbed. `beforeSend` is the redaction seam.
|
|
222
|
+
*/
|
|
223
|
+
extra?: Record<string, unknown>;
|
|
177
224
|
}
|
|
178
225
|
/** Any item that can appear in an envelope's `items` array. */
|
|
179
226
|
export type EnvelopeItem = ErrorItem | EventItem | IdentifyItem | TransactionItem;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edraj/sauron-node",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Sauron server-side Node/TypeScript SDK: product-analytics events + exception capture for Node backends.",
|
|
5
5
|
"homepage": "https://github.com/edraj/sauron/tree/main/sdks/node#readme",
|
|
6
6
|
"repository": {
|