@servicetitan/journey 2.0.0 → 2.1.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.
Files changed (150) hide show
  1. package/dist/__test-utils__/test-runtime.d.ts.map +1 -0
  2. package/dist/__test-utils__/test-runtime.js +47 -0
  3. package/dist/__test-utils__/test-runtime.js.map +1 -0
  4. package/dist/__tests__/core/ambient.test.d.ts.map +1 -0
  5. package/dist/__tests__/core/api.test.d.ts +2 -0
  6. package/dist/__tests__/core/api.test.d.ts.map +1 -0
  7. package/dist/__tests__/core/define-journey.test.d.ts.map +1 -0
  8. package/dist/__tests__/core/requests.test.d.ts.map +1 -0
  9. package/dist/__tests__/core/runtime.test.d.ts.map +1 -0
  10. package/dist/__tests__/core/step.test.d.ts.map +1 -0
  11. package/dist/__tests__/core/timeouts.test.d.ts.map +1 -0
  12. package/dist/__tests__/global.test.d.ts +3 -0
  13. package/dist/__tests__/global.test.d.ts.map +1 -0
  14. package/dist/__tests__/integrations/axios.test.d.ts.map +1 -0
  15. package/dist/__tests__/integrations/context.test.d.ts.map +1 -0
  16. package/dist/__tests__/integrations/expose-app-journey.test.d.ts.map +1 -0
  17. package/dist/__tests__/integrations/fetch.test.d.ts.map +1 -0
  18. package/dist/__tests__/integrations/jquery.test.d.ts.map +1 -0
  19. package/dist/__tests__/integrations/request-key.test.d.ts.map +1 -0
  20. package/dist/__tests__/integrations/scope.test.d.ts.map +1 -0
  21. package/dist/__tests__/sinks/console.test.d.ts +2 -0
  22. package/dist/__tests__/sinks/console.test.d.ts.map +1 -0
  23. package/dist/__tests__/sinks/datadog.test.d.ts.map +1 -0
  24. package/dist/__tests__/sinks/sinks.test.d.ts.map +1 -0
  25. package/dist/config.d.ts +8 -4
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +19 -13
  28. package/dist/config.js.map +1 -1
  29. package/dist/core/{registry.d.ts → api.d.ts} +2 -22
  30. package/dist/core/api.d.ts.map +1 -0
  31. package/dist/core/{registry.js → api.js} +13 -40
  32. package/dist/core/api.js.map +1 -0
  33. package/dist/core/endpoint-policy.d.ts.map +1 -0
  34. package/dist/core/endpoint-policy.js.map +1 -0
  35. package/dist/core/index.d.ts +4 -4
  36. package/dist/core/index.d.ts.map +1 -1
  37. package/dist/core/index.js +3 -2
  38. package/dist/core/index.js.map +1 -1
  39. package/dist/core/runtime.d.ts +35 -7
  40. package/dist/core/runtime.d.ts.map +1 -1
  41. package/dist/core/runtime.js +39 -5
  42. package/dist/core/runtime.js.map +1 -1
  43. package/dist/core/step-tag.d.ts +6 -0
  44. package/dist/core/step-tag.d.ts.map +1 -1
  45. package/dist/core/step-tag.js +7 -0
  46. package/dist/core/step-tag.js.map +1 -1
  47. package/dist/core/types.d.ts +3 -1
  48. package/dist/core/types.d.ts.map +1 -1
  49. package/dist/core/types.js.map +1 -1
  50. package/dist/global.d.ts +67 -0
  51. package/dist/global.d.ts.map +1 -0
  52. package/dist/global.js +116 -0
  53. package/dist/global.js.map +1 -0
  54. package/dist/index.d.ts +8 -3
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +11 -5
  57. package/dist/index.js.map +1 -1
  58. package/dist/integrations/fetch.d.ts +3 -2
  59. package/dist/integrations/fetch.d.ts.map +1 -1
  60. package/dist/integrations/fetch.js.map +1 -1
  61. package/dist/integrations/jquery/index.d.ts +2 -2
  62. package/dist/integrations/jquery/index.d.ts.map +1 -1
  63. package/dist/integrations/jquery/index.js +3 -2
  64. package/dist/integrations/jquery/index.js.map +1 -1
  65. package/dist/integrations/react/scope.d.ts.map +1 -1
  66. package/dist/integrations/react/scope.js +3 -2
  67. package/dist/integrations/react/scope.js.map +1 -1
  68. package/dist/sinks/console.d.ts +11 -0
  69. package/dist/sinks/console.d.ts.map +1 -0
  70. package/dist/sinks/console.js +18 -0
  71. package/dist/sinks/console.js.map +1 -0
  72. package/dist/sinks/datadog.d.ts +12 -1
  73. package/dist/sinks/datadog.d.ts.map +1 -1
  74. package/dist/sinks/datadog.js +28 -7
  75. package/dist/sinks/datadog.js.map +1 -1
  76. package/package.json +2 -2
  77. package/src/{__mocks__ → __test-utils__}/test-runtime.ts +6 -2
  78. package/src/__tests__/config.test.ts +9 -12
  79. package/src/__tests__/{ambient.test.ts → core/ambient.test.ts} +8 -7
  80. package/src/__tests__/{registry.test.ts → core/api.test.ts} +30 -25
  81. package/src/__tests__/{define-journey.test.ts → core/define-journey.test.ts} +5 -4
  82. package/src/__tests__/{requests.test.ts → core/requests.test.ts} +12 -8
  83. package/src/__tests__/{runtime.test.ts → core/runtime.test.ts} +18 -18
  84. package/src/__tests__/{step.test.ts → core/step.test.ts} +16 -18
  85. package/src/__tests__/{timeouts.test.ts → core/timeouts.test.ts} +15 -10
  86. package/src/__tests__/global.test.ts +113 -0
  87. package/src/__tests__/{axios.test.ts → integrations/axios.test.ts} +46 -15
  88. package/src/__tests__/{context.test.tsx → integrations/context.test.tsx} +1 -1
  89. package/src/__tests__/{expose-app-journey.test.ts → integrations/expose-app-journey.test.ts} +2 -2
  90. package/src/__tests__/{fetch.test.ts → integrations/fetch.test.ts} +6 -6
  91. package/src/__tests__/{jquery.test.ts → integrations/jquery.test.ts} +49 -12
  92. package/src/__tests__/{request-key.test.ts → integrations/request-key.test.ts} +1 -1
  93. package/src/__tests__/{scope.test.tsx → integrations/scope.test.tsx} +4 -4
  94. package/src/__tests__/sinks/console.test.ts +98 -0
  95. package/src/__tests__/{datadog.test.ts → sinks/datadog.test.ts} +62 -5
  96. package/src/__tests__/{sinks.test.ts → sinks/sinks.test.ts} +13 -12
  97. package/src/config.ts +20 -12
  98. package/src/core/{registry.ts → api.ts} +12 -65
  99. package/src/core/index.ts +5 -14
  100. package/src/core/runtime.ts +56 -7
  101. package/src/core/step-tag.ts +9 -0
  102. package/src/core/types.ts +3 -1
  103. package/src/global.ts +196 -0
  104. package/src/index.ts +20 -7
  105. package/src/integrations/fetch.ts +2 -2
  106. package/src/integrations/jquery/index.ts +4 -9
  107. package/src/integrations/react/scope.tsx +3 -2
  108. package/src/sinks/console.ts +20 -0
  109. package/src/sinks/datadog.ts +31 -6
  110. package/dist/__mocks__/test-runtime.d.ts.map +0 -1
  111. package/dist/__tests__/ambient.test.d.ts.map +0 -1
  112. package/dist/__tests__/axios.test.d.ts.map +0 -1
  113. package/dist/__tests__/context.test.d.ts.map +0 -1
  114. package/dist/__tests__/datadog.test.d.ts.map +0 -1
  115. package/dist/__tests__/define-journey.test.d.ts.map +0 -1
  116. package/dist/__tests__/expose-app-journey.test.d.ts.map +0 -1
  117. package/dist/__tests__/fetch.test.d.ts.map +0 -1
  118. package/dist/__tests__/jquery.test.d.ts.map +0 -1
  119. package/dist/__tests__/registry.test.d.ts +0 -2
  120. package/dist/__tests__/registry.test.d.ts.map +0 -1
  121. package/dist/__tests__/request-key.test.d.ts.map +0 -1
  122. package/dist/__tests__/requests.test.d.ts.map +0 -1
  123. package/dist/__tests__/runtime.test.d.ts.map +0 -1
  124. package/dist/__tests__/scope.test.d.ts.map +0 -1
  125. package/dist/__tests__/sinks.test.d.ts.map +0 -1
  126. package/dist/__tests__/step.test.d.ts.map +0 -1
  127. package/dist/__tests__/timeouts.test.d.ts.map +0 -1
  128. package/dist/core/registry.d.ts.map +0 -1
  129. package/dist/core/registry.js.map +0 -1
  130. package/dist/endpoint-policy.d.ts.map +0 -1
  131. package/dist/endpoint-policy.js.map +0 -1
  132. /package/dist/{__mocks__ → __test-utils__}/test-runtime.d.ts +0 -0
  133. /package/dist/__tests__/{ambient.test.d.ts → core/ambient.test.d.ts} +0 -0
  134. /package/dist/__tests__/{define-journey.test.d.ts → core/define-journey.test.d.ts} +0 -0
  135. /package/dist/__tests__/{requests.test.d.ts → core/requests.test.d.ts} +0 -0
  136. /package/dist/__tests__/{runtime.test.d.ts → core/runtime.test.d.ts} +0 -0
  137. /package/dist/__tests__/{step.test.d.ts → core/step.test.d.ts} +0 -0
  138. /package/dist/__tests__/{timeouts.test.d.ts → core/timeouts.test.d.ts} +0 -0
  139. /package/dist/__tests__/{axios.test.d.ts → integrations/axios.test.d.ts} +0 -0
  140. /package/dist/__tests__/{context.test.d.ts → integrations/context.test.d.ts} +0 -0
  141. /package/dist/__tests__/{expose-app-journey.test.d.ts → integrations/expose-app-journey.test.d.ts} +0 -0
  142. /package/dist/__tests__/{fetch.test.d.ts → integrations/fetch.test.d.ts} +0 -0
  143. /package/dist/__tests__/{jquery.test.d.ts → integrations/jquery.test.d.ts} +0 -0
  144. /package/dist/__tests__/{request-key.test.d.ts → integrations/request-key.test.d.ts} +0 -0
  145. /package/dist/__tests__/{scope.test.d.ts → integrations/scope.test.d.ts} +0 -0
  146. /package/dist/__tests__/{datadog.test.d.ts → sinks/datadog.test.d.ts} +0 -0
  147. /package/dist/__tests__/{sinks.test.d.ts → sinks/sinks.test.d.ts} +0 -0
  148. /package/dist/{endpoint-policy.d.ts → core/endpoint-policy.d.ts} +0 -0
  149. /package/dist/{endpoint-policy.js → core/endpoint-policy.js} +0 -0
  150. /package/src/{endpoint-policy.ts → core/endpoint-policy.ts} +0 -0
package/src/config.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { defaultRuntime } from './core/runtime';
2
- import { setJourneySinks, type JourneySink } from './core/registry';
1
+ import { moduleRuntime } from './core/runtime';
2
+ import type { JourneySink } from './core/types';
3
+ import { installJourneyGlobal, registerJourneyRuntime } from './global';
3
4
  import {
4
5
  sortEndpointsByLongestMatch,
5
6
  type AppRequestTimeouts,
6
7
  type EndpointPolicy,
7
- } from './endpoint-policy';
8
+ } from './core/endpoint-policy';
8
9
  import { instrumentAxios, type AxiosLikeInstance } from './integrations/axios';
9
10
  import { instrumentFetch, type InstrumentFetchOptions } from './integrations/fetch';
10
11
  import {
@@ -13,9 +14,9 @@ import {
13
14
  type JqueryStatic,
14
15
  type WindowWithAppJourney,
15
16
  } from './integrations/jquery';
17
+ import { sendToConsole } from './sinks/console';
16
18
  import { sendToDatadog } from './sinks/datadog';
17
19
 
18
- export { longestEndpointMatch, sortEndpointsByLongestMatch } from './endpoint-policy';
19
20
  export type { AppRequestTimeouts, EndpointPolicy };
20
21
 
21
22
  /** Optional app-wide / per-bundle configuration. */
@@ -25,7 +26,8 @@ export interface JourneyConfig {
25
26
  /**
26
27
  * Replace the sink list used when a journey finishes.
27
28
  * Only applied when provided — omit to leave the current list unchanged.
28
- * Package default is `[sendToDatadog]`; pass `[]` to disable emission.
29
+ * Package default is `[sendToDatadog, sendToConsole]`; pass `[]` to disable emission.
30
+ * `sendToConsole` is a no-op unless `localStorage['st:journey:debug'] === 'true'`.
29
31
  */
30
32
  sinks?: readonly JourneySink[];
31
33
  /**
@@ -48,11 +50,13 @@ export interface JourneyConfig {
48
50
  }
49
51
 
50
52
  /*
51
- * Default emission target. Kept as a module side effect so any import of this file
52
- * (see index.ts `import './config'`) installs Datadog. configureJourney only replaces
53
- * sinks when `sinks` is passed explicitly.
53
+ * Default emission targets + `__stJourney` bag. Kept as a module side effect so any import
54
+ * of this file (see index.ts `import './config'`) installs Datadog, console, and
55
+ * `__stJourney`. configureJourney only replaces sinks when `sinks` is passed explicitly.
54
56
  */
55
- setJourneySinks([sendToDatadog]);
57
+ moduleRuntime.setSinks([sendToDatadog, sendToConsole]);
58
+ installJourneyGlobal();
59
+ registerJourneyRuntime(moduleRuntime);
56
60
 
57
61
  /**
58
62
  * Apply bundle options: request thresholds, sinks, and/or HTTP / legacy wiring.
@@ -60,13 +64,13 @@ setJourneySinks([sendToDatadog]);
60
64
  */
61
65
  export function configureJourney(config: JourneyConfig): void {
62
66
  if (config.requestTimeouts !== undefined) {
63
- defaultRuntime.setRequestTimeouts({
67
+ moduleRuntime.setRequestTimeouts({
64
68
  ...config.requestTimeouts,
65
69
  endpoints: sortEndpointsByLongestMatch(config.requestTimeouts.endpoints),
66
70
  });
67
71
  }
68
72
  if (config.sinks !== undefined) {
69
- setJourneySinks(config.sinks);
73
+ moduleRuntime.setSinks(config.sinks);
70
74
  }
71
75
  if (config.axios !== undefined) {
72
76
  instrumentAxios(config.axios);
@@ -82,6 +86,10 @@ export function configureJourney(config: JourneyConfig): void {
82
86
  }
83
87
  }
84
88
 
89
+ /**
90
+ * @internal Read the module runtime's app-wide request timeout config.
91
+ * Prefer `configureJourney({ requestTimeouts })` to set thresholds. Test / introspection only.
92
+ */
85
93
  export function getRequestTimeouts(): AppRequestTimeouts {
86
- return defaultRuntime.getRequestTimeouts();
94
+ return moduleRuntime.getRequestTimeouts();
87
95
  }
@@ -1,10 +1,7 @@
1
- import { defaultRuntime } from './runtime';
1
+ import { moduleRuntime } from './runtime';
2
2
  import type {
3
3
  JourneyDef,
4
- JourneyEvent,
5
4
  JourneyHandle,
6
- JourneySink,
7
- JourneyState,
8
5
  JourneyStepOptions,
9
6
  JourneyStepRef,
10
7
  JourneyStepTarget,
@@ -12,18 +9,6 @@ import type {
12
9
  TagValue,
13
10
  } from './types';
14
11
 
15
- export type { JourneyHandle, JourneySink };
16
-
17
- /** Look up an open journey by name. */
18
- export function getOpenJourney(name: string): JourneyState | undefined {
19
- return defaultRuntime.getOpenJourney(name);
20
- }
21
-
22
- /** First open journey among `names` (order preserved), or null. */
23
- export function firstOpenJourney(names: readonly string[]): string | null {
24
- return defaultRuntime.firstOpenJourney(names);
25
- }
26
-
27
12
  /**
28
13
  * Active step for soft-ambient HTTP attribution, or null when 0 or 2+ steps are in flight
29
14
  * app-wide. Untagged traffic is attributed to that lone step even if unrelated — prefer
@@ -31,7 +16,7 @@ export function firstOpenJourney(names: readonly string[]): string | null {
31
16
  * a request has no explicit stamp.
32
17
  */
33
18
  export function activeJourneyStep(): JourneyStepRef | null {
34
- return defaultRuntime.activeJourneyStep();
19
+ return moduleRuntime.activeJourneyStep();
35
20
  }
36
21
 
37
22
  /**
@@ -44,7 +29,7 @@ export function reportBackendRequest(
44
29
  durationMs: number,
45
30
  requestKey: string
46
31
  ): void {
47
- defaultRuntime.reportBackendRequest(step, status, durationMs, requestKey);
32
+ moduleRuntime.reportBackendRequest(step, status, durationMs, requestKey);
48
33
  }
49
34
 
50
35
  /**
@@ -58,81 +43,43 @@ export async function journeyStep<T>(
58
43
  fn: (step: StepHandle) => T | Promise<T>,
59
44
  opts?: JourneyStepOptions
60
45
  ): Promise<T> {
61
- return defaultRuntime.journeyStep(journey, stepName, fn, opts);
62
- }
63
-
64
- /**
65
- * journeyStep deferred by one microtask so a parent `<JourneyScope>` opens first
66
- * (React runs child mount effects before parent). Use for mount-effect steps only.
67
- */
68
- export async function journeyMountStep<T>(
69
- journey: JourneyStepTarget,
70
- stepName: string,
71
- fn: (step: StepHandle) => T | Promise<T>,
72
- opts?: JourneyStepOptions
73
- ): Promise<T> {
74
- return defaultRuntime.journeyMountStep(journey, stepName, fn, opts);
75
- }
76
-
77
- /** Replace the sink list (e.g. host bootstrap or tests). Pass `[]` to disable emission. */
78
- export function setJourneySinks(next: readonly JourneySink[]): void {
79
- defaultRuntime.setSinks(next);
80
- }
81
-
82
- /** Append a sink alongside the current ones. */
83
- export function addJourneySink(sink: JourneySink): void {
84
- defaultRuntime.addSink(sink);
85
- }
86
-
87
- /** Emit a terminal journey event to every configured sink. */
88
- export function emitJourneyEvent(payload: JourneyEvent): void {
89
- defaultRuntime.emit(payload);
46
+ return moduleRuntime.journeyStep(journey, stepName, fn, opts);
90
47
  }
91
48
 
92
49
  /** Merge custom tags onto an open journey. No-op if not open. Prefer `handle.setTags`. */
93
50
  export function setJourneyTags(name: string | null, tags: Record<string, TagValue>): void {
94
- defaultRuntime.setJourneyTags(name, tags);
95
- }
96
-
97
- /** Begin a journey. Restarts (closes) an already-open journey of the same name. */
98
- export function startJourney(def: JourneyDef): void {
99
- defaultRuntime.startJourney(def);
100
- }
101
-
102
- /** Fail-fast: record reason and emit immediately. Idempotent via `closed`. */
103
- export function failJourneyNow(journey: JourneyState, reason: string): void {
104
- defaultRuntime.failJourneyNow(journey, reason);
51
+ moduleRuntime.setJourneyTags(name, tags);
105
52
  }
106
53
 
107
54
  /** Mark a named journey Bad for a custom reason (fail-fast). Prefer `handle.fail`. */
108
55
  export function failJourney(name: string, reason: string): void {
109
- defaultRuntime.failJourney(name, reason);
56
+ moduleRuntime.failJourney(name, reason);
110
57
  }
111
58
 
112
59
  /** Successful end. Outcome stays Bad if a breach already occurred. No-op when name is null. Prefer `handle.complete`. */
113
60
  export function completeJourney(name: string | null, tags?: Record<string, TagValue>): void {
114
- defaultRuntime.completeJourney(name, tags);
61
+ moduleRuntime.completeJourney(name, tags);
115
62
  }
116
63
 
117
64
  /** User left before finishing. Stays Bad if a breach already occurred (breach-anchored). Prefer `handle.exclude`. */
118
65
  export function excludeJourney(name: string): void {
119
- defaultRuntime.excludeJourney(name);
66
+ moduleRuntime.excludeJourney(name);
120
67
  }
121
68
 
122
69
  /** Turn a journey config into a handle with bound lifecycle methods. Preferred public API. */
123
70
  export function defineJourney(def: JourneyDef): JourneyHandle {
124
71
  const ensureStarted = () => {
125
- if (!getOpenJourney(def.name)) {
126
- startJourney(def);
72
+ if (!moduleRuntime.getOpenJourney(def.name)) {
73
+ moduleRuntime.startJourney(def);
127
74
  }
128
75
  };
129
76
 
130
77
  const handle: JourneyHandle = {
131
78
  ...def,
132
79
  start(tags) {
133
- startJourney(def);
80
+ moduleRuntime.startJourney(def);
134
81
  if (tags) {
135
- setJourneyTags(def.name, tags);
82
+ moduleRuntime.setJourneyTags(def.name, tags);
136
83
  }
137
84
  },
138
85
  step(stepName, fn, opts) {
package/src/core/index.ts CHANGED
@@ -1,34 +1,25 @@
1
- /** Core engine barrel — free helpers over the default runtime, plus handles and types. */
1
+ /** Core barrel — public free helpers, handles, and types. Engine lives in `./runtime`. */
2
2
 
3
3
  export {
4
4
  activeJourneyStep,
5
- addJourneySink,
6
5
  completeJourney,
7
6
  defineJourney,
8
- emitJourneyEvent,
9
7
  excludeJourney,
10
8
  failJourney,
11
- firstOpenJourney,
12
- journeyMountStep,
13
9
  journeyStep,
14
10
  reportBackendRequest,
15
- setJourneySinks,
16
11
  setJourneyTags,
17
- startJourney,
18
- type JourneyHandle,
19
- type JourneySink,
20
- } from './registry';
12
+ } from './api';
21
13
  export {
22
- createJourneyRuntime,
23
- defaultRuntime,
14
+ // @internal — accidentally public via package index; not a supported consumer API.
24
15
  resetJourney,
25
- type JourneyRuntime,
26
- type JourneyRuntimeOptions,
27
16
  } from './runtime';
28
17
  export type {
29
18
  JourneyDef,
30
19
  JourneyEvent,
20
+ JourneyHandle,
31
21
  JourneyNameLike,
22
+ JourneySink,
32
23
  JourneyStepEvent,
33
24
  JourneyStepOptions,
34
25
  JourneyStepRef,
@@ -2,7 +2,7 @@ import {
2
2
  longestEndpointMatch,
3
3
  sortEndpointsByLongestMatch,
4
4
  type AppRequestTimeouts,
5
- } from '../endpoint-policy';
5
+ } from './endpoint-policy';
6
6
  import { JourneyStepTag } from './step-tag';
7
7
  import type {
8
8
  JourneyDef,
@@ -36,16 +36,37 @@ function toName(target: string | { readonly name: string }): string {
36
36
 
37
37
  /** @internal Private — not part of the public package API. */
38
38
  export interface JourneyRuntimeOptions {
39
- /** Initial sinks. Defaults to `[]` — package bootstrap installs Datadog on the default runtime. */
39
+ /** Initial sinks. Defaults to `[]` — package bootstrap installs Datadog on the module runtime. */
40
40
  sinks?: readonly JourneySink[];
41
41
  requestTimeouts?: AppRequestTimeouts;
42
42
  }
43
43
 
44
+ /** @internal Read-only snapshot for DevTools (`__stJourney.getDebugState`; empty unless debugging). */
45
+ export interface JourneyRuntimeDebugSnapshot {
46
+ openJourneys: readonly {
47
+ name: string;
48
+ team: string;
49
+ group: string;
50
+ service: string;
51
+ verdict: Outcome;
52
+ reason: string | null;
53
+ tags: Record<string, TagValue>;
54
+ stepsInFlight: readonly string[];
55
+ steps: readonly {
56
+ name: string;
57
+ outcome: 'good' | 'bad';
58
+ durationMs: number;
59
+ reason?: string;
60
+ }[];
61
+ }[];
62
+ activeStep: { journey: string; name: string } | null;
63
+ }
64
+
44
65
  /**
45
66
  * @internal Private — not part of the public package API.
46
67
  * Isolated journey engine (open journeys, ambient steps, sinks, request thresholds).
47
68
  * App code should keep using free helpers (`defineJourney`, `journeyStep`, …) — they
48
- * bind to the process default runtime. Prefer `resetJourney()` in tests.
69
+ * bind to the module runtime. Prefer `resetJourney()` in tests.
49
70
  */
50
71
  export interface JourneyRuntime {
51
72
  getRequestTimeouts(): AppRequestTimeouts;
@@ -56,6 +77,8 @@ export interface JourneyRuntime {
56
77
  emit(payload: JourneyEvent): void;
57
78
 
58
79
  getOpenJourney(name: string): JourneyState | undefined;
80
+ /** @internal DevTools snapshot of open journeys / ambient step. */
81
+ getDebugSnapshot(): JourneyRuntimeDebugSnapshot;
59
82
  firstOpenJourney(names: readonly string[]): string | null;
60
83
  setJourneyTags(name: string | null, tags: Record<string, TagValue>): void;
61
84
  startJourney(def: JourneyDef): void;
@@ -278,6 +301,29 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
278
301
  return journeys.get(name);
279
302
  },
280
303
 
304
+ getDebugSnapshot() {
305
+ const active = api.activeJourneyStep();
306
+ return {
307
+ openJourneys: [...journeys.values()].map(journey => ({
308
+ name: journey.name,
309
+ team: journey.team,
310
+ group: journey.group,
311
+ service: journey.service,
312
+ verdict: journey.verdict,
313
+ reason: journey.reason,
314
+ tags: { ...journey.tags },
315
+ stepsInFlight: getInFlightSteps(journey).map(step => step.name),
316
+ steps: journey.steps.map(step => ({
317
+ name: step.name,
318
+ outcome: step.outcome,
319
+ durationMs: step.durationMs,
320
+ ...(step.reason ? { reason: step.reason } : {}),
321
+ })),
322
+ })),
323
+ activeStep: active ? { journey: active.journey.name, name: active.name } : null,
324
+ };
325
+ },
326
+
281
327
  firstOpenJourney(names) {
282
328
  for (const name of names) {
283
329
  if (journeys.has(name)) {
@@ -476,14 +522,17 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
476
522
 
477
523
  /**
478
524
  * @internal Private — not part of the public package API.
479
- * Process-wide engine used by free helpers. App code does not need to touch this.
525
+ * Module-scoped engine used by free helpers. App code does not need to touch this.
526
+ * Each host/MFE bundle gets its own copy when the package is duplicated.
480
527
  */
481
- export const defaultRuntime: JourneyRuntime = createJourneyRuntime();
528
+ export const moduleRuntime: JourneyRuntime = createJourneyRuntime();
482
529
 
483
530
  /**
484
- * Reset the default engine (open journeys, ambient steps, sinks, request thresholds).
531
+ * @internal Accidentally exported removing it is a breaking change, so it stays
532
+ * for compatibility. Not part of the supported public API.
533
+ * Reset the module engine (open journeys, ambient steps, sinks, request thresholds).
485
534
  * Intended for tests — call in `beforeEach` / `afterEach` instead of manual cleanup.
486
535
  */
487
536
  export function resetJourney(options?: JourneyRuntimeOptions): void {
488
- defaultRuntime.reset(options);
537
+ moduleRuntime.reset(options);
489
538
  }
@@ -10,6 +10,15 @@ import type { JourneyStepRef, StepRecord } from './types';
10
10
  */
11
11
  export class JourneyStepTag {
12
12
  constructor(readonly step: StepRecord) {}
13
+
14
+ /**
15
+ * Circular by design (`step.journey.steps → step`) for mergeConfig/deep-merge
16
+ * safety. Prevent accidental `JSON.stringify` (e.g. AxiosError.toJSON() includes
17
+ * config by reference) elsewhere in the app from throwing "circular structure".
18
+ */
19
+ toJSON(): string {
20
+ return '[JourneyStepTag]';
21
+ }
13
22
  }
14
23
 
15
24
  /** Unwrap `step.tag().journeyStep` or a JourneyStepTag / StepRecord stamp. */
package/src/core/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { EndpointPolicy } from '../endpoint-policy';
1
+ import type { EndpointPolicy } from './endpoint-policy';
2
2
 
3
3
  /** Arbitrary tag values a developer can attach. */
4
4
  export type TagValue = string | number | boolean;
@@ -63,6 +63,7 @@ export interface JourneyStepOptions {
63
63
  timeoutMs?: number;
64
64
  }
65
65
 
66
+ /** @internal Engine open-journey record — not part of the supported public API. */
66
67
  export interface JourneyState {
67
68
  name: string;
68
69
  team: string;
@@ -87,6 +88,7 @@ export interface JourneyState {
87
88
  closed: boolean;
88
89
  }
89
90
 
91
+ /** @internal Engine in-flight / completed step record — not part of the supported public API. */
90
92
  export interface StepRecord {
91
93
  name: string;
92
94
  startedAt: number;
package/src/global.ts ADDED
@@ -0,0 +1,196 @@
1
+ import type { JourneyRuntime, JourneyRuntimeDebugSnapshot } from './core/runtime';
2
+
3
+ /** Force-enable journey host wiring outside go* hosts (`__stJourney.enable()`). */
4
+ export const JOURNEY_ENABLED_STORAGE_KEY = 'st:journey:enabled';
5
+
6
+ /** Opt-in journey console logging (`__stJourney.enableDebugging()`). */
7
+ export const JOURNEY_DEBUG_STORAGE_KEY = 'st:journey:debug';
8
+
9
+ /** @internal Shared cross-bundle bag on globalThis (`rum` fallback + DevTools helpers). */
10
+ export const ST_JOURNEY_GLOBAL_KEY = '__stJourney';
11
+
12
+ export interface JourneyDebugRuntimeEntry extends JourneyRuntimeDebugSnapshot {
13
+ id: string;
14
+ }
15
+
16
+ export interface JourneyDebugState {
17
+ /** Force-enable flag (`st:journey:enabled`). */
18
+ enabled: boolean;
19
+ /** Console-logging flag (`st:journey:debug`). */
20
+ debug: boolean;
21
+ hasRum: boolean;
22
+ runtimes: JourneyDebugRuntimeEntry[];
23
+ }
24
+
25
+ /**
26
+ * @internal Cross-bundle surface on `globalThis.__stJourney`.
27
+ * Prefer importing `isJourneyEnabled` / `isJourneyDebugEnabled` from the package in app code.
28
+ */
29
+ export interface StJourneyGlobal {
30
+ /**
31
+ * Fallback RUM instance from `setJourneyRum` when `globalThis.DD_RUM` is absent.
32
+ * Prefer `DD_RUM` (ST host: `datadogGuard`).
33
+ */
34
+ rum?: unknown;
35
+ enable(): void;
36
+ disable(): void;
37
+ isEnabled(): boolean;
38
+ enableDebugging(): void;
39
+ disableDebugging(): void;
40
+ isDebugEnabled(): boolean;
41
+ /**
42
+ * Open-journey snapshot for DevTools. Returns empty `runtimes` unless debugging
43
+ * is enabled (`enableDebugging()` / `st:journey:debug`).
44
+ */
45
+ getDebugState(): JourneyDebugState;
46
+ }
47
+
48
+ interface RuntimeRegistration {
49
+ id: string;
50
+ getSnapshot: () => JourneyRuntimeDebugSnapshot;
51
+ }
52
+
53
+ interface StJourneyGlobalInternal extends StJourneyGlobal {
54
+ /** @internal */
55
+ runtimeRegistrations: RuntimeRegistration[];
56
+ }
57
+
58
+ let runtimeSeq = 0;
59
+
60
+ function readFlag(key: string): boolean {
61
+ try {
62
+ return globalThis.localStorage?.getItem(key) === 'true';
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ function writeFlag(key: string, on: boolean): void {
69
+ try {
70
+ if (on) {
71
+ globalThis.localStorage?.setItem(key, 'true');
72
+ } else {
73
+ globalThis.localStorage?.removeItem(key);
74
+ }
75
+ } catch {
76
+ // ignore storage failures (privacy mode, non-browser)
77
+ }
78
+ }
79
+
80
+ /** True when `localStorage['st:journey:enabled'] === 'true'` (force-on outside go*). */
81
+ export function isJourneyEnabled(): boolean {
82
+ return readFlag(JOURNEY_ENABLED_STORAGE_KEY);
83
+ }
84
+
85
+ /** Set the force-enable flag. Reload after calling from DevTools. */
86
+ export function enableJourney(): void {
87
+ writeFlag(JOURNEY_ENABLED_STORAGE_KEY, true);
88
+ }
89
+
90
+ /** Clear the force-enable flag. Reload after calling from DevTools. */
91
+ export function disableJourney(): void {
92
+ writeFlag(JOURNEY_ENABLED_STORAGE_KEY, false);
93
+ }
94
+
95
+ /** True when `localStorage['st:journey:debug'] === 'true'`. */
96
+ export function isJourneyDebugEnabled(): boolean {
97
+ return readFlag(JOURNEY_DEBUG_STORAGE_KEY);
98
+ }
99
+
100
+ /** Set the console-debug flag. Reload after calling from DevTools. */
101
+ export function enableJourneyDebugging(): void {
102
+ writeFlag(JOURNEY_DEBUG_STORAGE_KEY, true);
103
+ }
104
+
105
+ /** Clear the console-debug flag. Reload after calling from DevTools. */
106
+ export function disableJourneyDebugging(): void {
107
+ writeFlag(JOURNEY_DEBUG_STORAGE_KEY, false);
108
+ }
109
+
110
+ function hasRum(): boolean {
111
+ return (
112
+ (globalThis as Record<string, unknown>).DD_RUM != null || getJourneyRumFallback() != null
113
+ );
114
+ }
115
+
116
+ function buildDebugState(runtimes: RuntimeRegistration[]): JourneyDebugState {
117
+ if (!isJourneyDebugEnabled()) {
118
+ return {
119
+ enabled: isJourneyEnabled(),
120
+ debug: false,
121
+ hasRum: hasRum(),
122
+ runtimes: [],
123
+ };
124
+ }
125
+
126
+ return {
127
+ enabled: isJourneyEnabled(),
128
+ debug: true,
129
+ hasRum: hasRum(),
130
+ runtimes: runtimes.map(entry => ({
131
+ id: entry.id,
132
+ ...entry.getSnapshot(),
133
+ })),
134
+ };
135
+ }
136
+
137
+ function ensureGlobal(): StJourneyGlobalInternal {
138
+ const g = globalThis as Record<string, unknown>;
139
+ const existing = g[ST_JOURNEY_GLOBAL_KEY] as StJourneyGlobalInternal | undefined;
140
+ // Reuse when a prior load already installed the full surface (incl. enable).
141
+ if (existing?.runtimeRegistrations && typeof existing.enable === 'function') {
142
+ return existing;
143
+ }
144
+
145
+ const runtimes: RuntimeRegistration[] = existing?.runtimeRegistrations ?? [];
146
+
147
+ const api: StJourneyGlobalInternal = {
148
+ rum: existing?.rum,
149
+ runtimeRegistrations: runtimes,
150
+ enable: enableJourney,
151
+ disable: disableJourney,
152
+ isEnabled: isJourneyEnabled,
153
+ enableDebugging: enableJourneyDebugging,
154
+ disableDebugging: disableJourneyDebugging,
155
+ isDebugEnabled: isJourneyDebugEnabled,
156
+ getDebugState() {
157
+ return buildDebugState(api.runtimeRegistrations);
158
+ },
159
+ };
160
+
161
+ g[ST_JOURNEY_GLOBAL_KEY] = api;
162
+ return api;
163
+ }
164
+
165
+ /**
166
+ * @internal Register this bundle's module runtime on `__stJourney` so DevTools can
167
+ * list every host/MFE engine copy on the page.
168
+ */
169
+ export function registerJourneyRuntime(runtime: JourneyRuntime): void {
170
+ const globalApi = ensureGlobal();
171
+ const id = `runtime-${++runtimeSeq}`;
172
+ globalApi.runtimeRegistrations.push({
173
+ id,
174
+ getSnapshot: () => runtime.getDebugSnapshot(),
175
+ });
176
+ }
177
+
178
+ /** @internal Ensure `__stJourney` exists (idempotent). */
179
+ export function installJourneyGlobal(): void {
180
+ ensureGlobal();
181
+ }
182
+
183
+ /**
184
+ * @internal Store a fallback RUM instance on `__stJourney.rum` when `DD_RUM` is absent.
185
+ * Used by `setJourneyRum`.
186
+ */
187
+ export function setJourneyRumFallback(rum: unknown): void {
188
+ ensureGlobal().rum = rum;
189
+ }
190
+
191
+ /** @internal Read `__stJourney.rum` (setJourneyRum fallback). */
192
+ export function getJourneyRumFallback(): unknown {
193
+ const existing = (globalThis as Record<string, unknown>)[ST_JOURNEY_GLOBAL_KEY] as
194
+ StJourneyGlobal | undefined;
195
+ return existing?.rum;
196
+ }
package/src/index.ts CHANGED
@@ -6,17 +6,18 @@
6
6
  import './config';
7
7
 
8
8
  export {
9
- // Custom transport adapters only.
10
- activeJourneyStep,
11
- // Prefer defineJourney(...).complete/fail/… — free helpers for name-from-context call sites.
12
- completeJourney,
9
+ // Preferred app API.
13
10
  defineJourney,
11
+ // Name-string lifecycle (prefer handle methods when you own the journey).
12
+ completeJourney,
14
13
  excludeJourney,
15
14
  failJourney,
16
15
  journeyStep,
16
+ setJourneyTags,
17
+ // Custom transport adapters / request attribution.
18
+ activeJourneyStep,
17
19
  reportBackendRequest,
18
20
  resolveJourneyStep,
19
- setJourneyTags,
20
21
  JourneyStepTag,
21
22
  type JourneyDef,
22
23
  type JourneyHandle,
@@ -36,14 +37,26 @@ export {
36
37
 
37
38
  export type { JourneySink } from './core';
38
39
 
39
- /** Test helper — clears open journeys / ambient steps on the default engine. */
40
+ /**
41
+ * @internal Accidentally exported — removing it is a breaking change, so it stays
42
+ * for compatibility. Not part of the supported public API. Test helper only.
43
+ */
40
44
  export { resetJourney } from './core';
41
45
 
42
46
  export { instrumentAxios } from './integrations/axios';
43
47
  export { instrumentFetch } from './integrations/fetch';
44
48
  export { exposeAppJourney, instrumentJquery } from './integrations/jquery';
45
49
  export { requestKey, type JourneyTimedMeta } from './integrations/request-key';
46
- export { sendToDatadog, setJourneyRum } from './sinks/datadog';
50
+ export {
51
+ disableJourney,
52
+ disableJourneyDebugging,
53
+ enableJourney,
54
+ enableJourneyDebugging,
55
+ isJourneyDebugEnabled,
56
+ isJourneyEnabled,
57
+ } from './global';
58
+ export { sendToConsole } from './sinks/console';
59
+ export { normalizeTagValue, sendToDatadog, setJourneyRum } from './sinks/datadog';
47
60
 
48
61
  export type { JourneyEvent, JourneyStepEvent, TagValue } from './core';
49
62
 
@@ -2,10 +2,10 @@ import { activeJourneyStep, reportBackendRequest, resolveJourneyStep } from '../
2
2
  import { requestKey, type JourneyTimedMeta } from './request-key';
3
3
 
4
4
  /** Minimal structural fetch shape. */
5
- export type FetchLike = (input: any, init?: any) => Promise<{ status: number }>;
5
+ type FetchLike = (input: any, init?: any) => Promise<{ status: number }>;
6
6
 
7
7
  /** Holder of a `fetch` property — globalThis by default, or a scoped object. */
8
- export interface FetchTarget {
8
+ interface FetchTarget {
9
9
  fetch: FetchLike;
10
10
  }
11
11