@edraj/sauron-node 1.0.0 → 1.4.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/dist/types.d.ts CHANGED
@@ -68,6 +68,30 @@ export interface ScopeData {
68
68
  contexts: Record<string, unknown>;
69
69
  extra: Record<string, unknown>;
70
70
  breadcrumbs: Breadcrumb[];
71
+ /**
72
+ * The workflow bounded by `startWorkflow`/`endWorkflow`/`cancelWorkflow` on
73
+ * THIS scope, or `null` if none. Request-isolated: the async-local child
74
+ * scope created by `withScope`/`runWithAsyncScope` gets its own copy, so
75
+ * concurrent requests never observe each other's workflow.
76
+ */
77
+ workflow: ActiveWorkflow | null;
78
+ }
79
+ /** Exactly six reachable outcomes for a workflow lifecycle call. No seventh. */
80
+ export type WorkflowStatus = 'ok' | 'already_active' | 'not_active' | 'name_mismatch' | 'invalid_name' | 'disabled';
81
+ /** Return value of `startWorkflow` / `endWorkflow` / `cancelWorkflow`. */
82
+ export interface WorkflowResult {
83
+ status: WorkflowStatus;
84
+ /** Present when `status` is `'ok'`. */
85
+ workflowId?: string;
86
+ }
87
+ /** The workflow currently bounded on a scope. */
88
+ export interface ActiveWorkflow {
89
+ /** Fresh client-generated UUID v4, minted by `startWorkflow`. */
90
+ workflowId: string;
91
+ /** Trimmed, caller-supplied name (see `normalizeWorkflowName`). */
92
+ name: string;
93
+ /** ISO-8601 timestamp of when the workflow started. */
94
+ startedAt: string;
71
95
  }
72
96
  /** An error item (manual `captureException` / `captureMessage`). */
73
97
  export interface ErrorItem {
@@ -84,6 +108,14 @@ export interface ErrorItem {
84
108
  fingerprint: string[] | null;
85
109
  user: ErrorUser | null;
86
110
  session_id: string | null;
111
+ /**
112
+ * Id/name of the workflow this error occurred within, if the SDK bounded
113
+ * one via `startWorkflow`. Optional-not-nullable: apps that never use
114
+ * workflows must be byte-identical to before this field existed, so an
115
+ * absent workflow means the key is OMITTED from the item, never `null`.
116
+ */
117
+ workflow_id?: string;
118
+ workflow_name?: string;
87
119
  screen: string | null;
88
120
  }
89
121
  /** A product-analytics event (PostHog-style `track`). */
@@ -94,6 +126,9 @@ export interface EventItem {
94
126
  properties: Record<string, unknown>;
95
127
  timestamp: string;
96
128
  session_id: string | null;
129
+ /** See {@link ErrorItem.workflow_id} — same omit-not-null convention. */
130
+ workflow_id?: string;
131
+ workflow_name?: string;
97
132
  screen: string | null;
98
133
  tags?: Record<string, string>;
99
134
  contexts?: Record<string, unknown>;
@@ -122,6 +157,9 @@ export interface TransactionItem {
122
157
  http_status?: number;
123
158
  url?: string;
124
159
  distinct_id?: string;
160
+ /** See {@link ErrorItem.workflow_id} — same omit-not-null convention. */
161
+ workflow_id?: string;
162
+ workflow_name?: string;
125
163
  timestamp: string;
126
164
  }
127
165
  /** Caller input for {@link TransactionItem} via `trackTransaction`. */
@@ -129,7 +167,20 @@ export interface TransactionInput {
129
167
  name: string;
130
168
  /** Operation class: `navigation | http | resource | screen_load | custom`. Default `custom`. */
131
169
  op?: string;
132
- duration_ms: number;
170
+ duration_ms?: number;
171
+ /**
172
+ * Accepted alias for {@link duration_ms}.
173
+ *
174
+ * The browser SDK's equivalent input takes `durationMs` (`sdks/js`), so a
175
+ * snippet moved between the two — or any plain-JavaScript caller, where the
176
+ * type checker is not there to object — used to produce a transaction with no
177
+ * duration at all. The item still validated and still shipped, just without
178
+ * the one field it exists to carry, which is the worst possible outcome: no
179
+ * error anywhere and a silently useless performance record.
180
+ *
181
+ * Supply exactly one. `duration_ms` wins if both are present.
182
+ */
183
+ durationMs?: number;
133
184
  status?: string;
134
185
  http_method?: string;
135
186
  http_status?: number;
@@ -169,7 +220,6 @@ export interface EnvelopeHeader {
169
220
  dsn: string;
170
221
  sdk: SdkInfo;
171
222
  sent_at: string;
172
- environment: string;
173
223
  release: string | null;
174
224
  }
175
225
  /** The complete, serializable envelope posted to the ingest gateway. */
@@ -223,7 +273,6 @@ export interface TransportOptions {
223
273
  export interface InitOptions {
224
274
  /** `https://<public_key>@<host>/<project_id>` */
225
275
  dsn: string;
226
- environment?: string;
227
276
  release?: string | null;
228
277
  /** Default tags seeded into the global scope at init. */
229
278
  tags?: Record<string, string>;
@@ -269,7 +318,6 @@ export interface InitOptions {
269
318
  /** Fully-resolved options with all defaults applied. */
270
319
  export interface ResolvedOptions {
271
320
  dsn: string;
272
- environment: string;
273
321
  release: string | null;
274
322
  tags: Record<string, string>;
275
323
  contexts: Record<string, unknown>;
@@ -0,0 +1,26 @@
1
+ import type { ActiveWorkflow } from './types.js';
2
+ /** Cap on a workflow name, after trimming. */
3
+ export declare const WORKFLOW_NAME_MAX = 120;
4
+ /** Cap on a cancel reason. */
5
+ export declare const WORKFLOW_REASON_MAX = 120;
6
+ /**
7
+ * Returns the trimmed name, or `null` when invalid (not a string, empty after
8
+ * trimming, or over {@link WORKFLOW_NAME_MAX} characters). Trims BEFORE
9
+ * checking length/emptiness so an all-whitespace or over-long-but-padded name
10
+ * is rejected rather than silently truncated.
11
+ */
12
+ export declare function normalizeWorkflowName(name: unknown): string | null;
13
+ /**
14
+ * Normalize a cancel reason: default to `'user'` for a non-string or
15
+ * all-whitespace value, else trim and cap at {@link WORKFLOW_REASON_MAX}.
16
+ * The internal `force`-supersede path also routes its literal `'superseded'`
17
+ * reason through this, so every reason on the wire is consistently shaped.
18
+ */
19
+ export declare function normalizeReason(reason: unknown): string;
20
+ /**
21
+ * The active workflow on the CURRENT scope — the `AsyncLocalStorage` child
22
+ * inside `withScope`/`runWithAsyncScope`, else the process-wide global scope —
23
+ * or `null` if none. Client-agnostic: reflects scope state regardless of
24
+ * whether an SDK client is initialized, same as `getCurrentScope()` itself.
25
+ */
26
+ export declare function getWorkflow(): ActiveWorkflow | null;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Workflow name/reason normalization — pure helpers, no state of their own.
3
+ *
4
+ * Unlike the browser SDK (`sdks/js/src/workflow.ts`), the active workflow is
5
+ * NOT held in a module-level variable here: a module global would leak one
6
+ * HTTP request's workflow into every other concurrent request's telemetry.
7
+ * Instead it lives on the per-request {@link Scope} (`scope.data.workflow`),
8
+ * isolated by `AsyncLocalStorage` exactly like `user`/`tags`/`breadcrumbs`
9
+ * already are (see `scope.ts`). `getWorkflow()` below is a bare read of that
10
+ * scope field — it takes no client and never throws, mirroring the existing
11
+ * `getCurrentScope()`/`getGlobalScope()` getters.
12
+ */
13
+ import { getCurrentScope } from './scope.js';
14
+ /** Cap on a workflow name, after trimming. */
15
+ export const WORKFLOW_NAME_MAX = 120;
16
+ /** Cap on a cancel reason. */
17
+ export const WORKFLOW_REASON_MAX = 120;
18
+ /**
19
+ * Returns the trimmed name, or `null` when invalid (not a string, empty after
20
+ * trimming, or over {@link WORKFLOW_NAME_MAX} characters). Trims BEFORE
21
+ * checking length/emptiness so an all-whitespace or over-long-but-padded name
22
+ * is rejected rather than silently truncated.
23
+ */
24
+ export function normalizeWorkflowName(name) {
25
+ if (typeof name !== 'string')
26
+ return null;
27
+ const trimmed = name.trim();
28
+ if (trimmed.length === 0 || trimmed.length > WORKFLOW_NAME_MAX)
29
+ return null;
30
+ return trimmed;
31
+ }
32
+ /**
33
+ * Normalize a cancel reason: default to `'user'` for a non-string or
34
+ * all-whitespace value, else trim and cap at {@link WORKFLOW_REASON_MAX}.
35
+ * The internal `force`-supersede path also routes its literal `'superseded'`
36
+ * reason through this, so every reason on the wire is consistently shaped.
37
+ */
38
+ export function normalizeReason(reason) {
39
+ if (typeof reason !== 'string' || reason.trim().length === 0)
40
+ return 'user';
41
+ return reason.trim().slice(0, WORKFLOW_REASON_MAX);
42
+ }
43
+ /**
44
+ * The active workflow on the CURRENT scope — the `AsyncLocalStorage` child
45
+ * inside `withScope`/`runWithAsyncScope`, else the process-wide global scope —
46
+ * or `null` if none. Client-agnostic: reflects scope state regardless of
47
+ * whether an SDK client is initialized, same as `getCurrentScope()` itself.
48
+ */
49
+ export function getWorkflow() {
50
+ return getCurrentScope().data.workflow;
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edraj/sauron-node",
3
- "version": "1.0.0",
3
+ "version": "1.4.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": {