@miden-sdk/telemetry-sentry 0.0.1 → 0.17.0-rc.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/AGENTS.md ADDED
@@ -0,0 +1,52 @@
1
+ # @miden-sdk/telemetry-sentry - Agent Guide
2
+
3
+ **Audience: AI coding agents** forwarding Miden SDK observations to Sentry.
4
+
5
+ This file ships inside the published package, so the copy at
6
+ `node_modules/@miden-sdk/telemetry-sentry/AGENTS.md` matches the version you
7
+ have installed. Prefer it over your training data.
8
+
9
+ ## The default that protects the quota
10
+
11
+ `minDurationMs` defaults to **`Infinity`**: failures only. A success is
12
+ forwarded only when it took at least that long; a failure always is. That
13
+ default is deliberate, not a placeholder. The SDK emits one observation per
14
+ client operation, so `minDurationMs: 0`, set "to see everything", bills a Sentry
15
+ quota for the SDK's entire successful call volume. Leave it out, or set a
16
+ threshold you chose on purpose. `@miden-sdk/telemetry-otel` has no equivalent
17
+ guard, so do not reason from one package to the other.
18
+
19
+ ## Wiring it
20
+
21
+ ```ts
22
+ const client = await MidenClient.create({
23
+ rpcUrl: "testnet",
24
+ feeFaucetId: FEE_FAUCET,
25
+ observer: createSentryObserver({ client: Sentry, minDurationMs: 5_000 }),
26
+ });
27
+ ```
28
+
29
+ `client` is required and must expose `captureMessage(message, context)`; the
30
+ factory throws a `TypeError` at wiring time when it does not. Nothing here
31
+ imports `@sentry/*` or calls `Sentry.init` - that stays yours. Each forwarded
32
+ observation is one `captureMessage`: message `miden.<op> <outcome>`, `level`
33
+ `"error"` or `"info"`, `tags.op`, `tags.outcome`, `extra.durationMs`. The
34
+ observer never throws, so a broken Sentry client cannot fail the operation it
35
+ reports on.
36
+
37
+ ## `observer` and `observeSensitive` on the client
38
+
39
+ `ClientOptions.observer` is registered process-wide and the newest registration
40
+ replaces the previous one, so a second `MidenClient` takes over the sink.
41
+ `observeSensitive` is per instance and read once, at construction.
42
+
43
+ `observation.sensitive` carries the **verbatim, unredacted** error message and
44
+ stack, and only on failure. It is opt-in at both ends - `observeSensitive: true`
45
+ on the client, `includeSensitive: true` here - and only the literal `true`
46
+ enables either. An application with confidentiality obligations to its users, a
47
+ wallet above all, leaves both off.
48
+
49
+ ## Going deeper
50
+
51
+ - Options and the full reported shape: `README.md`.
52
+ - The client that emits them: `node_modules/@miden-sdk/miden-sdk/AGENTS.md`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Miden
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,114 @@
1
1
  # @miden-sdk/telemetry-sentry
2
2
 
3
- Placeholder. This version exists only so that an npm trusted publisher could be
4
- attached to the package name; npm cannot attach one to a package that does not
5
- yet exist. Install `@miden-sdk/telemetry-sentry@latest` instead.
3
+ Opt-in Sentry binding for Miden SDK observations.
4
+
5
+ `@miden-sdk/miden-sdk` emits one observation per client operation — name,
6
+ outcome, duration — and never transports one. It has no telemetry dependency
7
+ and no egress primitive in that path. This package is the adapter that turns
8
+ those observations into Sentry calls, so you can wire the SDK into a Sentry
9
+ project you already own without the core gaining anything.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @miden-sdk/telemetry-sentry
15
+ ```
16
+
17
+ Sentry itself is **not** a dependency of this package, not even a peer. The
18
+ binding is defined against the `captureMessage` shape rather than a Sentry
19
+ import, so you own your Sentry version, its configuration, and its lifecycle.
20
+ Nothing here calls `Sentry.init`, constructs a client, or knows a DSN.
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ import * as Sentry from "@sentry/browser";
26
+ import { MidenClient } from "@miden-sdk/miden-sdk";
27
+ import { createSentryObserver } from "@miden-sdk/telemetry-sentry";
28
+
29
+ Sentry.init({ dsn: "..." }); // yours to call, once, in your entry point
30
+
31
+ const client = await MidenClient.create({
32
+ rpcUrl: "testnet",
33
+ feeFaucetId: FEE_FAUCET,
34
+ observer: createSentryObserver({ client: Sentry, minDurationMs: 5_000 }),
35
+ });
36
+ ```
37
+
38
+ Pass anything with a `captureMessage(message, { level, tags, extra })` method:
39
+ the module namespace of any Sentry SDK, a `Scope`, a queue of your own, or a
40
+ test double.
41
+
42
+ ## Options
43
+
44
+ ### `client`
45
+
46
+ - **Type:** `SentryLikeClient` — an object with a `captureMessage` method
47
+ - **Required.** `createSentryObserver` throws a `TypeError` at wiring time if
48
+ it is missing or cannot capture a message. That is the one thing this
49
+ package reports by throwing, and it throws from the factory rather than the
50
+ observer: an observer cannot report its own misconfiguration, so one built
51
+ around a missing client would discard every observation forever and look
52
+ exactly like a working one.
53
+
54
+ ### `minDurationMs`
55
+
56
+ - **Type:** `number`
57
+ - **Default:** `Infinity` — failures only
58
+
59
+ Successful operations are forwarded only when they took at least this long.
60
+ Failures are always forwarded, however fast they were. The default is
61
+ deliberately conservative: the SDK's successful call volume is not something
62
+ to bill a Sentry quota for because an option was left out.
63
+
64
+ ### `includeSensitive`
65
+
66
+ - **Type:** `boolean`
67
+ - **Default:** `false`
68
+
69
+ See below. Only a literal `true` enables it.
70
+
71
+ ## Reported shape
72
+
73
+ One `captureMessage` per forwarded observation, synchronously, with nothing
74
+ scheduled afterwards:
75
+
76
+ | | |
77
+ |---|---|
78
+ | message | `miden.<op> <outcome>`, e.g. `miden.proveTransaction error` |
79
+ | `level` | `"error"` on failure, `"info"` on success |
80
+ | `tags.op` | the operation name |
81
+ | `tags.outcome` | `"ok"` or `"error"` |
82
+ | `extra.durationMs` | wall time the caller waited |
83
+
84
+ ## Sensitive detail
85
+
86
+ An observation's `sensitive` channel carries verbatim error text — the
87
+ untouched `error.message` and stack, neither classified nor redacted, and
88
+ populated only when an operation fails. It also declares an `accountId` field,
89
+ which the SDK does not currently populate; entries with no value are filtered
90
+ out before the report is built, so nothing appears in its place. It is opt-in
91
+ at **both** ends:
92
+ `observeSensitive: true` when you construct the client, and
93
+ `includeSensitive: true` here. Either one left alone and nothing from that
94
+ channel reaches Sentry.
95
+
96
+ By default the SDK omits the `sensitive` key from an observation entirely, and
97
+ this binding forwards nothing in its place — no placeholder, no inferred
98
+ value, no empty rows in the Sentry UI. An observation without the key is still
99
+ reported; only its detail is absent.
100
+
101
+ Do not enable either flag in an application with confidentiality obligations
102
+ to its users. A wallet, for example, must never enable it.
103
+
104
+ ## Failure behaviour
105
+
106
+ The observer never throws. A Sentry client that is down, misconfigured, or
107
+ throwing takes the report with it and nothing else — the client operation
108
+ being reported on still succeeds, and the next observation is still reported.
109
+ The SDK guards observer invocation too, but this binding is usable outside
110
+ that guard, so it does not rely on it.
111
+
112
+ ## License
113
+
114
+ MIT
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Opt-in Sentry binding for Miden SDK observations.
3
+ *
4
+ * `@miden-sdk/miden-sdk` emits an observation per client operation and never
5
+ * transports one — it has no telemetry dependency and no egress primitive in
6
+ * that path. This package is the adapter that turns those observations into
7
+ * Sentry calls, so a consumer can wire the SDK into a Sentry project they
8
+ * already own without the core gaining anything.
9
+ *
10
+ * Two properties hold deliberately and are enforced by tests:
11
+ *
12
+ * - Sentry is a peer concern. Nothing here imports `@sentry/*`; the binding is
13
+ * defined against the `captureMessage` shape, so the consumer owns their
14
+ * client, its version, and its lifecycle. `Sentry.init` is theirs to call.
15
+ * - The binding has no transport of its own. It hands data to the client it
16
+ * was given and stops there.
17
+ */
18
+ import type { MidenObservation } from "@miden-sdk/miden-sdk";
19
+ /** Severity Sentry files the message under. */
20
+ export type SentryLevel = "error" | "info";
21
+ /** The context object this binding passes to `captureMessage`. */
22
+ export interface SentryMessageContext {
23
+ level: SentryLevel;
24
+ tags: Record<string, string>;
25
+ extra: Record<string, unknown>;
26
+ }
27
+ /**
28
+ * The slice of a Sentry client this binding uses.
29
+ *
30
+ * Satisfied by the module namespace of any Sentry SDK
31
+ * (`import * as Sentry from "@sentry/browser"`), and by anything else with a
32
+ * matching `captureMessage` — a queue, a test double, your own reporter.
33
+ */
34
+ export interface SentryLikeClient {
35
+ captureMessage(message: string, context: SentryMessageContext): unknown;
36
+ }
37
+ export interface SentryObserverOptions {
38
+ /** An already-initialised Sentry client. This package never initialises one. */
39
+ client: SentryLikeClient;
40
+ /**
41
+ * Forward successful operations only when they took at least this long.
42
+ * Failures are always forwarded. Defaults to `Infinity` — failures only,
43
+ * because the SDK's successful call volume is not something to bill a
44
+ * Sentry quota for by accident.
45
+ */
46
+ minDurationMs?: number;
47
+ /**
48
+ * Forward `observation.sensitive` (verbatim error text, and account
49
+ * identifiers where the SDK supplies them) when it is present. Defaults to
50
+ * `false`, and only a literal `true` enables it: the field is dropped even
51
+ * when present, so turning on `observeSensitive` at the client does not by
52
+ * itself disclose anything through this binding.
53
+ */
54
+ includeSensitive?: boolean;
55
+ }
56
+ /**
57
+ * Build an observer for `ClientOptions.observer` that reports Miden SDK
58
+ * operations to a Sentry client.
59
+ *
60
+ * The returned observer never throws: a reporting failure must not be able to
61
+ * fail the client operation being reported on. The SDK guards observer
62
+ * invocation as well, but this adapter is usable outside that guard so it
63
+ * does not rely on it.
64
+ *
65
+ * Misconfiguration is the one thing reported by throwing, and it is thrown
66
+ * from here rather than from the observer — at wiring time, where a stack
67
+ * trace points at the call that is wrong. An observer that quietly discarded
68
+ * every observation would be indistinguishable from a working one.
69
+ *
70
+ * @throws {TypeError} if `options.client` cannot capture a message.
71
+ */
72
+ export declare function createSentryObserver(options: SentryObserverOptions): (observation: MidenObservation) => void;
package/dist/index.js ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Opt-in Sentry binding for Miden SDK observations.
3
+ *
4
+ * `@miden-sdk/miden-sdk` emits an observation per client operation and never
5
+ * transports one — it has no telemetry dependency and no egress primitive in
6
+ * that path. This package is the adapter that turns those observations into
7
+ * Sentry calls, so a consumer can wire the SDK into a Sentry project they
8
+ * already own without the core gaining anything.
9
+ *
10
+ * Two properties hold deliberately and are enforced by tests:
11
+ *
12
+ * - Sentry is a peer concern. Nothing here imports `@sentry/*`; the binding is
13
+ * defined against the `captureMessage` shape, so the consumer owns their
14
+ * client, its version, and its lifecycle. `Sentry.init` is theirs to call.
15
+ * - The binding has no transport of its own. It hands data to the client it
16
+ * was given and stops there.
17
+ */
18
+ /**
19
+ * Entries to merge into `extra` from the observation's sensitive channel.
20
+ *
21
+ * The channel is opt-in at both ends, so the key is normally ABSENT rather
22
+ * than present-and-undefined — hence the `in` test. Reading the field
23
+ * unconditionally would put `undefined` values into the payload for every
24
+ * observation the SDK emits by default, and the same goes for the individual
25
+ * fields: `MidenObservationSensitive` declares each one optional and the SDK
26
+ * populates only some, so an unset field is dropped rather than forwarded as
27
+ * an empty row in the Sentry UI.
28
+ */
29
+ function sensitiveEntries(observation, includeSensitive) {
30
+ if (!includeSensitive)
31
+ return [];
32
+ if (!("sensitive" in observation))
33
+ return [];
34
+ const sensitive = observation.sensitive;
35
+ if (sensitive === null || typeof sensitive !== "object")
36
+ return [];
37
+ return Object.entries(sensitive).filter(([, value]) => value !== undefined);
38
+ }
39
+ /**
40
+ * Build an observer for `ClientOptions.observer` that reports Miden SDK
41
+ * operations to a Sentry client.
42
+ *
43
+ * The returned observer never throws: a reporting failure must not be able to
44
+ * fail the client operation being reported on. The SDK guards observer
45
+ * invocation as well, but this adapter is usable outside that guard so it
46
+ * does not rely on it.
47
+ *
48
+ * Misconfiguration is the one thing reported by throwing, and it is thrown
49
+ * from here rather than from the observer — at wiring time, where a stack
50
+ * trace points at the call that is wrong. An observer that quietly discarded
51
+ * every observation would be indistinguishable from a working one.
52
+ *
53
+ * @throws {TypeError} if `options.client` cannot capture a message.
54
+ */
55
+ export function createSentryObserver(options) {
56
+ const client = options?.client;
57
+ if (typeof client?.captureMessage !== "function") {
58
+ throw new TypeError("createSentryObserver: options.client must expose a " +
59
+ "captureMessage(message, context) method.");
60
+ }
61
+ const minDurationMs = options.minDurationMs ?? Number.POSITIVE_INFINITY;
62
+ const includeSensitive = options.includeSensitive === true;
63
+ return (observation) => {
64
+ try {
65
+ const failed = observation.outcome === "error";
66
+ // Written as `>=` rather than a negated `<` so a duration that is not
67
+ // a number drops out instead of comparing false and sailing through.
68
+ if (!failed && !(observation.durationMs >= minDurationMs))
69
+ return;
70
+ client.captureMessage(`miden.${observation.op} ${observation.outcome}`, {
71
+ level: failed ? "error" : "info",
72
+ tags: { op: observation.op, outcome: observation.outcome },
73
+ // `fromEntries` defines own properties rather than assigning them, so
74
+ // a `__proto__` key in the sensitive channel lands as data instead of
75
+ // reaching the payload's prototype. `durationMs` goes last: the
76
+ // measured duration outranks anything of the same name.
77
+ extra: Object.fromEntries([
78
+ ...sensitiveEntries(observation, includeSensitive),
79
+ ["durationMs", observation.durationMs],
80
+ ]),
81
+ });
82
+ }
83
+ catch {
84
+ // A telemetry binding must never fail a client operation.
85
+ }
86
+ };
87
+ }
package/package.json CHANGED
@@ -1,11 +1,56 @@
1
1
  {
2
2
  "name": "@miden-sdk/telemetry-sentry",
3
- "version": "0.0.1",
4
- "description": "Placeholder reserving this name so an npm trusted publisher can be attached. Superseded by 0.16.2 - do not install this version.",
3
+ "version": "0.17.0-rc.1",
4
+ "description": "Opt-in Sentry binding for Miden SDK observations",
5
+ "type": "module",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./package.json": "./package.json"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "AGENTS.md"
18
+ ],
19
+ "peerDependencies": {
20
+ "@miden-sdk/miden-sdk": "^0.17.0-rc.1"
21
+ },
22
+ "devDependencies": {
23
+ "@typescript-eslint/eslint-plugin": "^8.25.0",
24
+ "@typescript-eslint/parser": "^8.25.0",
25
+ "@vitest/coverage-v8": "^3.0.0",
26
+ "eslint": "^9.30.1",
27
+ "typescript": "^5.0.0",
28
+ "vitest": "^3.0.0",
29
+ "@miden-sdk/miden-sdk": "0.17.0-rc.1"
30
+ },
31
+ "keywords": [
32
+ "miden",
33
+ "sentry",
34
+ "telemetry",
35
+ "observability",
36
+ "monitoring",
37
+ "zk"
38
+ ],
39
+ "author": "Miden Contributors",
5
40
  "license": "MIT",
6
41
  "repository": {
7
42
  "type": "git",
8
- "url": "git+https://github.com/0xMiden/web-sdk.git",
9
- "directory": "packages/telemetry-sentry"
43
+ "url": "git+https://github.com/0xMiden/web-sdk.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/0xMiden/web-sdk/issues"
47
+ },
48
+ "scripts": {
49
+ "build": "tsc -p tsconfig.json",
50
+ "lint": "eslint src",
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "vitest run",
53
+ "test:unit": "vitest run",
54
+ "test:coverage": "vitest run --coverage"
10
55
  }
11
- }
56
+ }