@edraj/sauron-node 1.4.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 +39 -0
- package/README.md +140 -0
- package/dist/client.js +10 -0
- 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 +34 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
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
|
+
|
|
5
44
|
## 1.4.0
|
|
6
45
|
|
|
7
46
|
### Fixed
|
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,
|
|
@@ -210,6 +211,15 @@ export class SauronClient {
|
|
|
210
211
|
item.url = input.url;
|
|
211
212
|
if (distinctId != null)
|
|
212
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
|
+
}
|
|
213
223
|
this.dispatch(item);
|
|
214
224
|
}
|
|
215
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,6 +161,17 @@ 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 {
|
|
@@ -187,6 +198,29 @@ export interface TransactionInput {
|
|
|
187
198
|
url?: string;
|
|
188
199
|
/** Falls back to the scoped user's id when omitted. */
|
|
189
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>;
|
|
190
224
|
}
|
|
191
225
|
/** Any item that can appear in an envelope's `items` array. */
|
|
192
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": {
|