@servicetitan/journey 2.1.0 → 3.0.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.
Files changed (163) hide show
  1. package/README.md +1 -1
  2. package/dist/__test-utils__/test-runtime.d.ts.map +1 -1
  3. package/dist/__test-utils__/test-runtime.js +9 -2
  4. package/dist/__test-utils__/test-runtime.js.map +1 -1
  5. package/dist/__tests__/core/sanitize.test.d.ts +2 -0
  6. package/dist/__tests__/core/sanitize.test.d.ts.map +1 -0
  7. package/dist/__tests__/hardening.test.d.ts +2 -0
  8. package/dist/__tests__/hardening.test.d.ts.map +1 -0
  9. package/dist/__tests__/integrations/host-mfe.test.d.ts +2 -0
  10. package/dist/__tests__/integrations/host-mfe.test.d.ts.map +1 -0
  11. package/dist/__tests__/integrations/use-journey-step.test.d.ts +2 -0
  12. package/dist/__tests__/integrations/use-journey-step.test.d.ts.map +1 -0
  13. package/dist/config.d.ts +57 -13
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +87 -24
  16. package/dist/config.js.map +1 -1
  17. package/dist/core/abort-error.d.ts +4 -0
  18. package/dist/core/abort-error.d.ts.map +1 -0
  19. package/dist/core/abort-error.js +22 -0
  20. package/dist/core/abort-error.js.map +1 -0
  21. package/dist/core/api.d.ts +22 -9
  22. package/dist/core/api.d.ts.map +1 -1
  23. package/dist/core/api.js +77 -30
  24. package/dist/core/api.js.map +1 -1
  25. package/dist/core/endpoint-policy.d.ts +14 -4
  26. package/dist/core/endpoint-policy.d.ts.map +1 -1
  27. package/dist/core/endpoint-policy.js +32 -1
  28. package/dist/core/endpoint-policy.js.map +1 -1
  29. package/dist/core/index.d.ts +4 -3
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/core/index.js +3 -3
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/core/limits.d.ts +29 -0
  34. package/dist/core/limits.d.ts.map +1 -0
  35. package/dist/core/limits.js +63 -0
  36. package/dist/core/limits.js.map +1 -0
  37. package/dist/core/request-policy.d.ts +39 -0
  38. package/dist/core/request-policy.d.ts.map +1 -0
  39. package/dist/core/request-policy.js +18 -0
  40. package/dist/core/request-policy.js.map +1 -0
  41. package/dist/core/runtime.d.ts +65 -15
  42. package/dist/core/runtime.d.ts.map +1 -1
  43. package/dist/core/runtime.js +403 -126
  44. package/dist/core/runtime.js.map +1 -1
  45. package/dist/core/sanitize.d.ts +23 -0
  46. package/dist/core/sanitize.d.ts.map +1 -0
  47. package/dist/core/sanitize.js +143 -0
  48. package/dist/core/sanitize.js.map +1 -0
  49. package/dist/core/step-tag.d.ts +25 -6
  50. package/dist/core/step-tag.d.ts.map +1 -1
  51. package/dist/core/step-tag.js +56 -10
  52. package/dist/core/step-tag.js.map +1 -1
  53. package/dist/core/types.d.ts +73 -25
  54. package/dist/core/types.d.ts.map +1 -1
  55. package/dist/core/types.js +4 -1
  56. package/dist/core/types.js.map +1 -1
  57. package/dist/global.d.ts +91 -13
  58. package/dist/global.d.ts.map +1 -1
  59. package/dist/global.js +235 -25
  60. package/dist/global.js.map +1 -1
  61. package/dist/index.d.ts +7 -8
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +4 -6
  64. package/dist/index.js.map +1 -1
  65. package/dist/integrations/axios.d.ts +5 -1
  66. package/dist/integrations/axios.d.ts.map +1 -1
  67. package/dist/integrations/axios.js +146 -34
  68. package/dist/integrations/axios.js.map +1 -1
  69. package/dist/integrations/fetch.d.ts +3 -2
  70. package/dist/integrations/fetch.d.ts.map +1 -1
  71. package/dist/integrations/fetch.js +105 -26
  72. package/dist/integrations/fetch.js.map +1 -1
  73. package/dist/integrations/http-report.d.ts +27 -0
  74. package/dist/integrations/http-report.d.ts.map +1 -0
  75. package/dist/integrations/http-report.js +123 -0
  76. package/dist/integrations/http-report.js.map +1 -0
  77. package/dist/integrations/jquery/index.d.ts +8 -4
  78. package/dist/integrations/jquery/index.d.ts.map +1 -1
  79. package/dist/integrations/jquery/index.js +26 -8
  80. package/dist/integrations/jquery/index.js.map +1 -1
  81. package/dist/integrations/jquery/instrument.d.ts +14 -5
  82. package/dist/integrations/jquery/instrument.d.ts.map +1 -1
  83. package/dist/integrations/jquery/instrument.js +111 -29
  84. package/dist/integrations/jquery/instrument.js.map +1 -1
  85. package/dist/integrations/react/context.d.ts +4 -1
  86. package/dist/integrations/react/context.d.ts.map +1 -1
  87. package/dist/integrations/react/context.js +4 -1
  88. package/dist/integrations/react/context.js.map +1 -1
  89. package/dist/integrations/react/scope.d.ts +14 -3
  90. package/dist/integrations/react/scope.d.ts.map +1 -1
  91. package/dist/integrations/react/scope.js +46 -8
  92. package/dist/integrations/react/scope.js.map +1 -1
  93. package/dist/integrations/react/stable-config-keys.d.ts +6 -0
  94. package/dist/integrations/react/stable-config-keys.d.ts.map +1 -0
  95. package/dist/integrations/react/stable-config-keys.js +21 -0
  96. package/dist/integrations/react/stable-config-keys.js.map +1 -0
  97. package/dist/integrations/react/use-journey-step.d.ts +11 -0
  98. package/dist/integrations/react/use-journey-step.d.ts.map +1 -0
  99. package/dist/integrations/react/use-journey-step.js +19 -0
  100. package/dist/integrations/react/use-journey-step.js.map +1 -0
  101. package/dist/integrations/request-key.d.ts +8 -3
  102. package/dist/integrations/request-key.d.ts.map +1 -1
  103. package/dist/integrations/request-key.js +10 -9
  104. package/dist/integrations/request-key.js.map +1 -1
  105. package/dist/integrations/soft-skip.d.ts +7 -0
  106. package/dist/integrations/soft-skip.d.ts.map +1 -0
  107. package/dist/integrations/soft-skip.js +30 -0
  108. package/dist/integrations/soft-skip.js.map +1 -0
  109. package/dist/sinks/console.d.ts.map +1 -1
  110. package/dist/sinks/console.js +2 -1
  111. package/dist/sinks/console.js.map +1 -1
  112. package/dist/sinks/datadog.d.ts +2 -2
  113. package/dist/sinks/datadog.d.ts.map +1 -1
  114. package/dist/sinks/datadog.js +18 -28
  115. package/dist/sinks/datadog.js.map +1 -1
  116. package/package.json +2 -4
  117. package/src/__test-utils__/test-runtime.ts +9 -2
  118. package/src/__tests__/config.test.ts +150 -31
  119. package/src/__tests__/core/ambient.test.ts +16 -10
  120. package/src/__tests__/core/api.test.ts +7 -7
  121. package/src/__tests__/core/define-journey.test.ts +28 -11
  122. package/src/__tests__/core/requests.test.ts +49 -31
  123. package/src/__tests__/core/runtime.test.ts +67 -20
  124. package/src/__tests__/core/sanitize.test.ts +69 -0
  125. package/src/__tests__/core/step.test.ts +132 -36
  126. package/src/__tests__/core/timeouts.test.ts +69 -4
  127. package/src/__tests__/global.test.ts +44 -3
  128. package/src/__tests__/hardening.test.ts +613 -0
  129. package/src/__tests__/integrations/axios.test.ts +247 -27
  130. package/src/__tests__/integrations/expose-app-journey.test.ts +9 -3
  131. package/src/__tests__/integrations/fetch.test.ts +153 -19
  132. package/src/__tests__/integrations/host-mfe.test.ts +548 -0
  133. package/src/__tests__/integrations/jquery.test.ts +209 -34
  134. package/src/__tests__/integrations/scope.test.tsx +160 -2
  135. package/src/__tests__/integrations/use-journey-step.test.tsx +43 -0
  136. package/src/__tests__/sinks/console.test.ts +9 -4
  137. package/src/__tests__/sinks/datadog.test.ts +48 -15
  138. package/src/config.ts +162 -38
  139. package/src/core/abort-error.ts +24 -0
  140. package/src/core/api.ts +83 -33
  141. package/src/core/endpoint-policy.ts +46 -4
  142. package/src/core/index.ts +12 -4
  143. package/src/core/limits.ts +73 -0
  144. package/src/core/request-policy.ts +66 -0
  145. package/src/core/runtime.ts +507 -133
  146. package/src/core/sanitize.ts +177 -0
  147. package/src/core/step-tag.ts +82 -12
  148. package/src/core/types.ts +79 -25
  149. package/src/global.ts +312 -42
  150. package/src/index.ts +17 -17
  151. package/src/integrations/axios.ts +183 -45
  152. package/src/integrations/fetch.ts +125 -39
  153. package/src/integrations/http-report.ts +184 -0
  154. package/src/integrations/jquery/index.ts +34 -10
  155. package/src/integrations/jquery/instrument.ts +126 -42
  156. package/src/integrations/react/context.ts +4 -1
  157. package/src/integrations/react/scope.tsx +59 -9
  158. package/src/integrations/react/stable-config-keys.ts +28 -0
  159. package/src/integrations/react/use-journey-step.ts +31 -0
  160. package/src/integrations/request-key.ts +18 -10
  161. package/src/integrations/soft-skip.ts +35 -0
  162. package/src/sinks/console.ts +2 -1
  163. package/src/sinks/datadog.ts +35 -41
@@ -1,35 +1,89 @@
1
- import { longestEndpointMatch, sortEndpointsByLongestMatch } from './endpoint-policy';
2
- import { JourneyStepTag } from './step-tag';
3
- const DEFAULT_REQUEST_TIMEOUTS = {
1
+ import { getAutoAttributeRequests as getSharedAutoAttributeRequests, getHttpAbortedRequestsPolicy as getSharedHttpAbortedRequestsPolicy, getHttpClientErrorRequestsPolicy as getSharedHttpClientErrorRequestsPolicy, getSharedSlowRequests, isJourneyDebugEnabled } from '../global';
2
+ import { isCanceledError } from './abort-error';
3
+ import { resolveEndpointPolicy, sortEndpointsByLongestMatch } from './endpoint-policy';
4
+ import { clampMaxSteps, DEFAULT_MAX_STEPS, DEFAULT_JOURNEY_IDLE_MS, finiteSlowRequestMs, finiteTimeoutMs, MAX_ATTRIBUTED_REQUEST_KEYS, MAX_TAG_KEYS, resolveJourneyTimeoutMs } from './limits';
5
+ import { assignRecord, emptyRecord, sanitizeJourneyEvent, setSafeAttribute } from './sanitize';
6
+ import { JourneyStepStamp, resolveStampOpts } from './step-tag';
7
+ import { JOURNEY_ENGINE } from './types';
8
+ const DEFAULT_SLOW_REQUESTS = {
4
9
  defaultMs: 4000,
5
10
  endpoints: []
6
11
  };
7
12
  const noopStep = {
8
- name: null,
13
+ journeyName: null,
9
14
  setAttribute () {},
10
15
  setTags () {},
11
- complete () {},
12
- fail () {},
13
- tag: ()=>({})
16
+ completeJourney () {},
17
+ failJourney () {},
18
+ stamp: ()=>undefined
14
19
  };
15
20
  function toName(target) {
16
- return typeof target === 'string' ? target : target.name;
21
+ if (typeof target === 'string') {
22
+ return target;
23
+ }
24
+ return 'config' in target ? target.config.name : target.name;
25
+ }
26
+ function freezeJourneyDef(config, tags) {
27
+ const endpoints = sortEndpointsByLongestMatch(config.endpoints);
28
+ return Object.freeze({
29
+ ...config,
30
+ timeoutMs: config.timeoutMs !== undefined ? finiteTimeoutMs(config.timeoutMs) : undefined,
31
+ stepTimeoutMs: config.stepTimeoutMs !== undefined ? finiteTimeoutMs(config.stepTimeoutMs) : undefined,
32
+ slowRequestMs: finiteSlowRequestMs(config.slowRequestMs),
33
+ maxSteps: config.maxSteps !== undefined ? clampMaxSteps(config.maxSteps) : undefined,
34
+ endpoints: endpoints ? Object.freeze([
35
+ ...endpoints
36
+ ]) : undefined,
37
+ expected: config.expected ? Object.freeze([
38
+ ...config.expected
39
+ ]) : undefined,
40
+ tags: Object.freeze({
41
+ ...tags
42
+ })
43
+ });
44
+ }
45
+ function bindJourneyEngine(api, journey, shouldIgnoreRequest) {
46
+ return {
47
+ failJourney (reason) {
48
+ api.failJourney(journey, reason);
49
+ },
50
+ exclude () {
51
+ api.excludeJourney(journey);
52
+ },
53
+ shouldIgnoreRequest (key) {
54
+ return shouldIgnoreRequest(journey, key);
55
+ },
56
+ reportBackendRequest (step, status, durationMs, requestKey) {
57
+ api.reportBackendRequest(step, status, durationMs, requestKey);
58
+ },
59
+ getHttpAbortedRequestsPolicy () {
60
+ return api.getHttpAbortedRequestsPolicy();
61
+ },
62
+ getHttpClientErrorRequestsPolicy () {
63
+ return api.getHttpClientErrorRequestsPolicy();
64
+ }
65
+ };
17
66
  }
18
67
  /**
19
68
  * @internal Private — not part of the public package API.
20
69
  * Build an isolated engine. Rarely needed — apps use the default via free helpers.
21
70
  */ export function createJourneyRuntime(options = {}) {
22
- var _options_sinks, _ref, _ref1;
23
- var _options_requestTimeouts, _options_requestTimeouts1;
71
+ var _options_sinks, _ref, _ref1, _options_maxSteps, _options_defaultJourneyIdleMs;
72
+ var _options_slowRequests, _options_slowRequests1;
73
+ let id = typeof options.id === 'string' && options.id !== '' ? options.id : '';
24
74
  const journeys = new Map();
25
75
  const inFlight = new Set();
26
76
  let sinks = [
27
77
  ...(_options_sinks = options.sinks) !== null && _options_sinks !== void 0 ? _options_sinks : []
28
78
  ];
29
- let requestTimeouts = {
30
- defaultMs: (_ref = (_options_requestTimeouts = options.requestTimeouts) === null || _options_requestTimeouts === void 0 ? void 0 : _options_requestTimeouts.defaultMs) !== null && _ref !== void 0 ? _ref : DEFAULT_REQUEST_TIMEOUTS.defaultMs,
31
- endpoints: sortEndpointsByLongestMatch((_ref1 = (_options_requestTimeouts1 = options.requestTimeouts) === null || _options_requestTimeouts1 === void 0 ? void 0 : _options_requestTimeouts1.endpoints) !== null && _ref1 !== void 0 ? _ref1 : DEFAULT_REQUEST_TIMEOUTS.endpoints)
79
+ let sinksConfigured = false;
80
+ let overlay = {};
81
+ let slowRequests = {
82
+ defaultMs: (_ref = (_options_slowRequests = options.slowRequests) === null || _options_slowRequests === void 0 ? void 0 : _options_slowRequests.defaultMs) !== null && _ref !== void 0 ? _ref : DEFAULT_SLOW_REQUESTS.defaultMs,
83
+ endpoints: sortEndpointsByLongestMatch((_ref1 = (_options_slowRequests1 = options.slowRequests) === null || _options_slowRequests1 === void 0 ? void 0 : _options_slowRequests1.endpoints) !== null && _ref1 !== void 0 ? _ref1 : DEFAULT_SLOW_REQUESTS.endpoints)
32
84
  };
85
+ let maxSteps = clampMaxSteps((_options_maxSteps = options.maxSteps) !== null && _options_maxSteps !== void 0 ? _options_maxSteps : DEFAULT_MAX_STEPS);
86
+ let defaultJourneyIdleMs = finiteTimeoutMs((_options_defaultJourneyIdleMs = options.defaultJourneyIdleMs) !== null && _options_defaultJourneyIdleMs !== void 0 ? _options_defaultJourneyIdleMs : DEFAULT_JOURNEY_IDLE_MS);
33
87
  function getInFlightSteps(journey) {
34
88
  const out = [];
35
89
  for (const step of inFlight){
@@ -45,6 +99,19 @@ function toName(target) {
45
99
  function leaveStep(step) {
46
100
  inFlight.delete(step);
47
101
  }
102
+ function releaseInFlightSteps(journey) {
103
+ for (const step of [
104
+ ...inFlight
105
+ ]){
106
+ if (step.journey !== journey) {
107
+ continue;
108
+ }
109
+ if (step.durationMs === 0) {
110
+ step.durationMs = Math.round(globalThis.performance.now() - step.startedAt);
111
+ }
112
+ inFlight.delete(step);
113
+ }
114
+ }
48
115
  function clearCountdown(journey) {
49
116
  if (journey.timer != null) {
50
117
  clearTimeout(journey.timer);
@@ -52,9 +119,10 @@ function toName(target) {
52
119
  }
53
120
  }
54
121
  function emit(payload) {
122
+ const safePayload = sanitizeJourneyEvent(payload);
55
123
  for (const sink of sinks){
56
124
  try {
57
- sink(payload);
125
+ sink(safePayload);
58
126
  } catch (unused) {
59
127
  // One sinking throw must not block the remaining sinks.
60
128
  }
@@ -66,17 +134,15 @@ function toName(target) {
66
134
  return;
67
135
  }
68
136
  journey.closed = true;
69
- journeys.delete(journey.name);
70
- clearCountdown(journey);
71
- for (const step of getInFlightSteps(journey)){
72
- if (step.durationMs === 0) {
73
- step.durationMs = Math.round(globalThis.performance.now() - step.startedAt);
74
- }
137
+ if (journeys.get(journey.name) === journey) {
138
+ journeys.delete(journey.name);
75
139
  }
140
+ clearCountdown(journey);
141
+ releaseInFlightSteps(journey);
76
142
  let finalReason = (_journey_reason = journey.reason) !== null && _journey_reason !== void 0 ? _journey_reason : reason;
77
143
  let finalOutcome = outcome;
78
144
  const durationMs = Math.round(globalThis.performance.now() - journey.startedAt);
79
- if (journey.timeoutMs > 0 && durationMs > journey.timeoutMs) {
145
+ if (journey.timeoutExplicit && journey.timeoutMs > 0 && durationMs > journey.timeoutMs) {
80
146
  finalOutcome = 'bad';
81
147
  finalReason = finalReason !== null && finalReason !== void 0 ? finalReason : 'journey-timeout';
82
148
  }
@@ -92,7 +158,9 @@ function toName(target) {
92
158
  httpStatus: s.httpStatus
93
159
  } : {},
94
160
  ...Object.keys(s.attributes).length ? {
95
- attributes: s.attributes
161
+ attributes: {
162
+ ...s.attributes
163
+ }
96
164
  } : {}
97
165
  }));
98
166
  emit({
@@ -102,7 +170,7 @@ function toName(target) {
102
170
  group: journey.group,
103
171
  service: journey.service,
104
172
  outcome: finalOutcome,
105
- ...finalOutcome === 'bad' && finalReason ? {
173
+ ...finalReason ? {
106
174
  reason: finalReason
107
175
  } : {},
108
176
  durationMs,
@@ -111,40 +179,44 @@ function toName(target) {
111
179
  expected: journey.expected
112
180
  } : {},
113
181
  ...Object.keys(journey.tags).length ? {
114
- tags: journey.tags
182
+ tags: {
183
+ ...journey.tags
184
+ }
115
185
  } : {}
116
186
  }
117
187
  });
118
188
  }
119
- function armCountdown(journey) {
120
- if (journey.timeoutMs <= 0) {
189
+ function armCountdown(journey, delayMs = journey.timeoutMs) {
190
+ if (delayMs <= 0) {
121
191
  return;
122
192
  }
123
193
  journey.timer = setTimeout(()=>{
124
- finish(journey, 'bad', 'journey-timeout');
125
- }, journey.timeoutMs);
194
+ if (journey.timeoutExplicit) {
195
+ finish(journey, 'bad', 'journey-timeout');
196
+ return;
197
+ }
198
+ finish(journey, journey.verdict === 'bad' ? 'bad' : 'excluded', 'journey-idle-timeout');
199
+ }, delayMs);
126
200
  }
127
- function resolveRequestTimeoutMs(journey, key) {
128
- const own = longestEndpointMatch(journey.endpoints, key);
129
- if (own === null || own === void 0 ? void 0 : own.timeoutMs) {
130
- return own.timeoutMs;
131
- }
132
- const app = longestEndpointMatch(requestTimeouts.endpoints, key);
133
- if (app === null || app === void 0 ? void 0 : app.timeoutMs) {
134
- return app.timeoutMs;
135
- }
136
- if (journey.requestTimeoutMs != null) {
137
- return journey.requestTimeoutMs;
138
- }
139
- return requestTimeouts.defaultMs;
201
+ function effectiveSlowRequests() {
202
+ var _ref, _overlay_slowRequests;
203
+ return (_ref = (_overlay_slowRequests = overlay.slowRequests) !== null && _overlay_slowRequests !== void 0 ? _overlay_slowRequests : getSharedSlowRequests()) !== null && _ref !== void 0 ? _ref : slowRequests;
140
204
  }
141
205
  function shouldIgnoreRequest(journey, key) {
142
- var _longestEndpointMatch;
143
- const own = longestEndpointMatch(journey.endpoints, key);
144
- if (own) {
145
- return own.ignore === true;
206
+ return resolveEndpointPolicy(journey, effectiveSlowRequests(), key).ignore;
207
+ }
208
+ function trackAttributedKey(step, requestKey) {
209
+ var _step, _attributedRequestKeys;
210
+ if (!requestKey) {
211
+ return;
212
+ }
213
+ (_attributedRequestKeys = (_step = step).attributedRequestKeys) !== null && _attributedRequestKeys !== void 0 ? _attributedRequestKeys : _step.attributedRequestKeys = [];
214
+ if (step.attributedRequestKeys.length >= MAX_ATTRIBUTED_REQUEST_KEYS) {
215
+ return;
216
+ }
217
+ if (!step.attributedRequestKeys.includes(requestKey)) {
218
+ step.attributedRequestKeys.push(requestKey);
146
219
  }
147
- return ((_longestEndpointMatch = longestEndpointMatch(requestTimeouts.endpoints, key)) === null || _longestEndpointMatch === void 0 ? void 0 : _longestEndpointMatch.ignore) === true;
148
220
  }
149
221
  function resolveJourneyName(journey) {
150
222
  if (journey == null) {
@@ -153,54 +225,126 @@ function toName(target) {
153
225
  if (typeof journey === 'string' || !Array.isArray(journey)) {
154
226
  return toName(journey);
155
227
  }
156
- return api.firstOpenJourney(journey.map(toName));
228
+ return api.firstOpenJourneyName(journey.map(toName));
229
+ }
230
+ function configFromTarget(target) {
231
+ if (typeof target === 'string') {
232
+ return undefined;
233
+ }
234
+ if ('config' in target && target.config != null && typeof target.config === 'object') {
235
+ return target.config;
236
+ }
237
+ const rec = target;
238
+ if (typeof rec.name === 'string' && typeof rec.team === 'string' && typeof rec.group === 'string' && typeof rec.service === 'string') {
239
+ return target;
240
+ }
241
+ return undefined;
242
+ }
243
+ function maybeAutoStart(target) {
244
+ if (target == null || typeof target === 'string' || Array.isArray(target)) {
245
+ return;
246
+ }
247
+ const def = configFromTarget(target);
248
+ if (def && !api.getOpenJourney(def.name)) {
249
+ api.startJourney(def);
250
+ }
157
251
  }
158
252
  function createStepHandle(journeyName, step) {
159
253
  return {
160
- name: journeyName,
254
+ journeyName,
161
255
  setAttribute (key, value) {
162
- step.attributes[key] = value;
256
+ setSafeAttribute(step.attributes, key, value);
163
257
  },
164
258
  setTags (tags) {
165
- api.setJourneyTags(journeyName, tags);
259
+ if (step.journey.closed) {
260
+ return;
261
+ }
262
+ assignRecord(step.journey.tags, tags, MAX_TAG_KEYS);
166
263
  },
167
- complete (tags) {
168
- api.completeJourney(journeyName, tags);
264
+ completeJourney (tags) {
265
+ api.completeJourneyState(step.journey, tags);
169
266
  },
170
- fail (reason) {
171
- api.failJourney(journeyName, reason);
267
+ failJourney (reason) {
268
+ api.failJourney(step.journey, reason);
172
269
  },
173
- // Box in JourneyStepTag — raw StepRecord is circular and blows jQuery/axios deep-merge.
174
- tag: ()=>({
175
- journeyStep: new JourneyStepTag(step)
176
- })
270
+ // Box in JourneyStepStamp — raw StepRecord is circular and blows jQuery/axios deep-merge.
271
+ stamp: (scoreOrOpts)=>new JourneyStepStamp(step, resolveStampOpts(scoreOrOpts))
177
272
  };
178
273
  }
179
274
  const api = {
180
- getRequestTimeouts () {
181
- return requestTimeouts;
275
+ get id () {
276
+ return id;
182
277
  },
183
- setRequestTimeouts (next) {
184
- requestTimeouts = {
185
- ...next,
186
- endpoints: sortEndpointsByLongestMatch(next.endpoints)
278
+ set id (next){
279
+ id = next;
280
+ },
281
+ getSlowRequests () {
282
+ return effectiveSlowRequests();
283
+ },
284
+ setSlowRequests (next1) {
285
+ slowRequests = {
286
+ ...next1,
287
+ endpoints: sortEndpointsByLongestMatch(next1.endpoints)
187
288
  };
188
289
  },
189
- setSinks (next) {
290
+ getMaxSteps () {
291
+ return maxSteps;
292
+ },
293
+ setMaxSteps (next1) {
294
+ maxSteps = clampMaxSteps(next1);
295
+ },
296
+ getDefaultJourneyIdleMs () {
297
+ return defaultJourneyIdleMs;
298
+ },
299
+ setDefaultJourneyIdleMs (next1) {
300
+ defaultJourneyIdleMs = finiteTimeoutMs(next1);
301
+ },
302
+ setSinks (next1) {
190
303
  sinks = [
191
- ...next
304
+ ...next1
192
305
  ];
193
306
  },
307
+ trySetConfiguredSinks (next1) {
308
+ if (sinksConfigured) {
309
+ return false;
310
+ }
311
+ sinks = [
312
+ ...next1
313
+ ];
314
+ sinksConfigured = true;
315
+ return true;
316
+ },
194
317
  addSink (sink) {
195
318
  sinks.push(sink);
196
319
  },
197
320
  emit,
321
+ setPolicyOverlay (next1) {
322
+ overlay = {
323
+ ...overlay,
324
+ ...next1
325
+ };
326
+ },
327
+ getAutoAttributeRequests () {
328
+ if (overlay.autoAttributeRequests !== undefined) {
329
+ return overlay.autoAttributeRequests;
330
+ }
331
+ return getSharedAutoAttributeRequests();
332
+ },
333
+ getHttpAbortedRequestsPolicy () {
334
+ var _overlay_httpAbortedRequests;
335
+ return (_overlay_httpAbortedRequests = overlay.httpAbortedRequests) !== null && _overlay_httpAbortedRequests !== void 0 ? _overlay_httpAbortedRequests : getSharedHttpAbortedRequestsPolicy();
336
+ },
337
+ getHttpClientErrorRequestsPolicy () {
338
+ var _overlay_httpClientErrorRequests;
339
+ return (_overlay_httpClientErrorRequests = overlay.httpClientErrorRequests) !== null && _overlay_httpClientErrorRequests !== void 0 ? _overlay_httpClientErrorRequests : getSharedHttpClientErrorRequestsPolicy();
340
+ },
198
341
  getOpenJourney (name) {
199
342
  return journeys.get(name);
200
343
  },
201
344
  getDebugSnapshot () {
202
345
  const active = api.activeJourneyStep();
203
346
  return {
347
+ id: api.id,
204
348
  openJourneys: [
205
349
  ...journeys.values()
206
350
  ].map((journey)=>({
@@ -214,22 +358,36 @@ function toName(target) {
214
358
  ...journey.tags
215
359
  },
216
360
  stepsInFlight: getInFlightSteps(journey).map((step)=>step.name),
217
- steps: journey.steps.map((step)=>({
361
+ steps: journey.steps.map((step)=>{
362
+ var _step_attributedRequestKeys;
363
+ return {
218
364
  name: step.name,
219
365
  outcome: step.outcome,
220
366
  durationMs: step.durationMs,
221
367
  ...step.reason ? {
222
368
  reason: step.reason
369
+ } : {},
370
+ ...((_step_attributedRequestKeys = step.attributedRequestKeys) === null || _step_attributedRequestKeys === void 0 ? void 0 : _step_attributedRequestKeys.length) ? {
371
+ attributedRequestKeys: [
372
+ ...step.attributedRequestKeys
373
+ ]
223
374
  } : {}
224
- }))
375
+ };
376
+ })
225
377
  })),
226
378
  activeStep: active ? {
227
379
  journey: active.journey.name,
228
380
  name: active.name
229
- } : null
381
+ } : null,
382
+ policyOverlay: {
383
+ slowRequests: overlay.slowRequests != null,
384
+ httpAbortedRequests: overlay.httpAbortedRequests != null,
385
+ httpClientErrorRequests: overlay.httpClientErrorRequests != null,
386
+ autoAttributeRequests: overlay.autoAttributeRequests != null
387
+ }
230
388
  };
231
389
  },
232
- firstOpenJourney (names) {
390
+ firstOpenJourneyName (names) {
233
391
  for (const name of names){
234
392
  if (journeys.has(name)) {
235
393
  return name;
@@ -240,41 +398,46 @@ function toName(target) {
240
398
  setJourneyTags (name, tags) {
241
399
  const journey = name ? journeys.get(name) : undefined;
242
400
  if (journey) {
243
- Object.assign(journey.tags, tags);
401
+ assignRecord(journey.tags, tags, MAX_TAG_KEYS);
244
402
  }
245
403
  },
246
- startJourney (def) {
247
- var _def_timeoutMs, _def_tags;
248
- const existing = journeys.get(def.name);
404
+ startJourney (config) {
405
+ const existing = journeys.get(config.name);
249
406
  if (existing) {
250
407
  finish(existing, existing.verdict === 'bad' ? 'bad' : 'excluded');
251
408
  }
409
+ const tags = emptyRecord();
410
+ assignRecord(tags, config.tags, MAX_TAG_KEYS);
411
+ const snapshot = freezeJourneyDef(config, tags);
412
+ const timeout = resolveJourneyTimeoutMs(snapshot.timeoutMs, defaultJourneyIdleMs);
252
413
  const journey = {
253
- name: def.name,
254
- team: def.team,
255
- group: def.group,
256
- service: def.service,
414
+ config: snapshot,
415
+ name: snapshot.name,
416
+ team: snapshot.team,
417
+ group: snapshot.group,
418
+ service: snapshot.service,
257
419
  verdict: 'good',
258
420
  startedAt: globalThis.performance.now(),
259
- timeoutMs: (_def_timeoutMs = def.timeoutMs) !== null && _def_timeoutMs !== void 0 ? _def_timeoutMs : 0,
260
- stepTimeoutMs: def.stepTimeoutMs,
421
+ timeoutMs: timeout.ms,
422
+ timeoutExplicit: timeout.explicit,
423
+ stepTimeoutMs: snapshot.stepTimeoutMs !== undefined ? finiteTimeoutMs(snapshot.stepTimeoutMs) : undefined,
261
424
  reason: null,
262
425
  steps: [],
263
- expected: def.steps ? [
264
- ...def.steps
426
+ expected: snapshot.expected ? [
427
+ ...snapshot.expected
265
428
  ] : null,
266
- tags: {
267
- ...(_def_tags = def.tags) !== null && _def_tags !== void 0 ? _def_tags : {}
268
- },
269
- requestTimeoutMs: def.requestTimeoutMs,
270
- endpoints: sortEndpointsByLongestMatch(def.endpoints),
429
+ tags,
430
+ slowRequestMs: finiteSlowRequestMs(snapshot.slowRequestMs),
431
+ endpoints: snapshot.endpoints,
271
432
  timer: null,
272
- closed: false
433
+ closed: false,
434
+ maxSteps: snapshot.maxSteps !== undefined ? clampMaxSteps(snapshot.maxSteps) : undefined
273
435
  };
274
- journeys.set(def.name, journey);
436
+ journeys.set(config.name, journey);
437
+ journey[JOURNEY_ENGINE] = bindJourneyEngine(api, journey, shouldIgnoreRequest);
275
438
  armCountdown(journey);
276
439
  },
277
- failJourneyNow (journey, reason) {
440
+ failJourney (journey, reason) {
278
441
  var _journey, _reason;
279
442
  if (journey.closed) {
280
443
  return;
@@ -283,42 +446,87 @@ function toName(target) {
283
446
  (_reason = (_journey = journey).reason) !== null && _reason !== void 0 ? _reason : _journey.reason = reason;
284
447
  finish(journey, 'bad');
285
448
  },
286
- failJourney (name, reason) {
449
+ completeJourney (name, tags) {
450
+ if (!name) {
451
+ return;
452
+ }
287
453
  const journey = journeys.get(name);
288
454
  if (journey) {
289
- api.failJourneyNow(journey, reason);
455
+ api.completeJourneyState(journey, tags);
290
456
  }
291
457
  },
292
- completeJourney (name, tags) {
293
- if (!name) {
458
+ completeJourneyState (journey, tags) {
459
+ if (journey.closed) {
294
460
  return;
295
461
  }
296
462
  if (tags) {
297
- api.setJourneyTags(name, tags);
463
+ assignRecord(journey.tags, tags, MAX_TAG_KEYS);
298
464
  }
299
- const journey = journeys.get(name);
300
- if (journey) {
301
- finish(journey, journey.verdict === 'bad' ? 'bad' : 'good');
465
+ finish(journey, journey.verdict === 'bad' ? 'bad' : 'good');
466
+ },
467
+ excludeJourney (journey) {
468
+ if (journey.closed) {
469
+ return;
302
470
  }
471
+ finish(journey, journey.verdict === 'bad' ? 'bad' : 'excluded');
303
472
  },
304
- excludeJourney (name) {
305
- const journey = journeys.get(name);
306
- if (journey) {
307
- finish(journey, journey.verdict === 'bad' ? 'bad' : 'excluded');
473
+ updateOpenJourney (config) {
474
+ const journey = journeys.get(config.name);
475
+ if (!journey || journey.closed) {
476
+ return;
477
+ }
478
+ journey.team = config.team;
479
+ journey.group = config.group;
480
+ journey.service = config.service;
481
+ const timeout = resolveJourneyTimeoutMs(config.timeoutMs, defaultJourneyIdleMs);
482
+ journey.timeoutMs = timeout.ms;
483
+ journey.timeoutExplicit = timeout.explicit;
484
+ journey.stepTimeoutMs = config.stepTimeoutMs !== undefined ? finiteTimeoutMs(config.stepTimeoutMs) : undefined;
485
+ journey.slowRequestMs = finiteSlowRequestMs(config.slowRequestMs);
486
+ journey.endpoints = sortEndpointsByLongestMatch(config.endpoints);
487
+ journey.expected = config.expected ? [
488
+ ...config.expected
489
+ ] : null;
490
+ journey.maxSteps = config.maxSteps !== undefined ? clampMaxSteps(config.maxSteps) : undefined;
491
+ if (config.tags) {
492
+ assignRecord(journey.tags, config.tags, MAX_TAG_KEYS);
493
+ }
494
+ clearCountdown(journey);
495
+ if (journey.timeoutMs <= 0) {
496
+ return;
308
497
  }
498
+ const elapsedMs = globalThis.performance.now() - journey.startedAt;
499
+ const remainingMs = journey.timeoutMs - elapsedMs;
500
+ if (remainingMs <= 0) {
501
+ if (journey.timeoutExplicit) {
502
+ finish(journey, 'bad', 'journey-timeout');
503
+ return;
504
+ }
505
+ finish(journey, journey.verdict === 'bad' ? 'bad' : 'excluded', 'journey-idle-timeout');
506
+ return;
507
+ }
508
+ armCountdown(journey, remainingMs);
309
509
  },
310
510
  activeJourneyStep () {
311
511
  var _inFlight_values_next_value;
512
+ if (!api.getAutoAttributeRequests()) {
513
+ return null;
514
+ }
312
515
  if (inFlight.size !== 1) {
313
516
  return null;
314
517
  }
315
518
  return (_inFlight_values_next_value = inFlight.values().next().value) !== null && _inFlight_values_next_value !== void 0 ? _inFlight_values_next_value : null;
316
519
  },
317
520
  async journeyStep (journey, stepName, fn, opts) {
318
- var _ref, _ref1;
521
+ var _open_maxSteps, _ref, _ref1;
522
+ maybeAutoStart(journey);
319
523
  const journeyName = resolveJourneyName(journey);
320
524
  const open = journeyName ? journeys.get(journeyName) : undefined;
321
525
  if (!open || !journeyName) {
526
+ if (isJourneyDebugEnabled()) {
527
+ // eslint-disable-next-line no-console -- debug-only untraced hint
528
+ console.debug(`[journey] step "${stepName}" ran untraced (no open journey)`);
529
+ }
322
530
  return fn(noopStep);
323
531
  }
324
532
  const step = {
@@ -326,10 +534,15 @@ function toName(target) {
326
534
  startedAt: globalThis.performance.now(),
327
535
  durationMs: 0,
328
536
  outcome: 'good',
329
- attributes: {},
537
+ attributes: emptyRecord(),
330
538
  journey: open
331
539
  };
332
- open.steps.push(step);
540
+ const cap = (_open_maxSteps = open.maxSteps) !== null && _open_maxSteps !== void 0 ? _open_maxSteps : maxSteps;
541
+ if (open.steps.length >= cap) {
542
+ open.tags.steps_truncated = true;
543
+ } else {
544
+ open.steps.push(step);
545
+ }
333
546
  const handle = createStepHandle(journeyName, step);
334
547
  const stepTimeoutMs = (_ref = (_ref1 = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _ref1 !== void 0 ? _ref1 : open.stepTimeoutMs) !== null && _ref !== void 0 ? _ref : 0;
335
548
  let timeoutTimer = null;
@@ -343,19 +556,23 @@ function toName(target) {
343
556
  (_reason = (_step = step).reason) !== null && _reason !== void 0 ? _reason : _step.reason = 'step-timeout';
344
557
  (_timeout_ms = (_step_attributes = step.attributes).timeout_ms) !== null && _timeout_ms !== void 0 ? _timeout_ms : _step_attributes.timeout_ms = stepTimeoutMs;
345
558
  step.durationMs = Math.round(globalThis.performance.now() - step.startedAt);
346
- api.failJourneyNow(open, 'step-timeout');
559
+ api.failJourney(open, 'step-timeout');
347
560
  }, stepTimeoutMs);
348
561
  }
349
562
  enterStep(step);
350
563
  try {
351
564
  return await fn(handle);
352
565
  } catch (err) {
353
- var _step, _reason, _step_attributes, _error;
354
- const message = err instanceof Error ? err.message : String(err);
355
- step.outcome = 'bad';
356
- (_reason = (_step = step).reason) !== null && _reason !== void 0 ? _reason : _step.reason = 'step-error';
357
- (_error = (_step_attributes = step.attributes).error) !== null && _error !== void 0 ? _error : _step_attributes.error = message;
358
- api.failJourneyNow(open, 'step-error');
566
+ if (isCanceledError(err)) {
567
+ /*
568
+ * Attributed abort is scored in onHttpComplete. Unattributed /
569
+ * ignored abort throws must not apply policy (empty requestKey).
570
+ */ } else {
571
+ var _step, _reason;
572
+ step.outcome = 'bad';
573
+ (_reason = (_step = step).reason) !== null && _reason !== void 0 ? _reason : _step.reason = 'step-error';
574
+ api.failJourney(open, 'step-error');
575
+ }
359
576
  throw err;
360
577
  } finally{
361
578
  if (timeoutTimer != null) {
@@ -371,20 +588,24 @@ function toName(target) {
371
588
  await Promise.resolve();
372
589
  return api.journeyStep(journey, stepName, fn, opts);
373
590
  },
591
+ shouldIgnoreRequest,
374
592
  reportBackendRequest (step, status, durationMs, requestKey) {
375
593
  var _step, _reason;
376
- if (!step) {
594
+ if (!(step === null || step === void 0 ? void 0 : step.journey)) {
377
595
  return;
378
596
  }
379
597
  const journey = step.journey;
380
598
  if (journey.closed) {
381
599
  return;
382
600
  }
383
- if (shouldIgnoreRequest(journey, requestKey)) {
601
+ trackAttributedKey(step, requestKey);
602
+ const endpointPolicy = resolveEndpointPolicy(journey, effectiveSlowRequests(), requestKey);
603
+ if (endpointPolicy.ignore) {
384
604
  return;
385
605
  }
386
606
  const failed = status === undefined || status >= 500;
387
- const slow = durationMs > resolveRequestTimeoutMs(journey, requestKey);
607
+ const threshold = endpointPolicy.slowRequestMs;
608
+ const slow = threshold > 0 && durationMs > threshold;
388
609
  if (!failed && !slow) {
389
610
  return;
390
611
  }
@@ -394,11 +615,11 @@ function toName(target) {
394
615
  if (failed && status !== undefined) {
395
616
  step.httpStatus = status;
396
617
  }
397
- api.failJourneyNow(journey, category);
618
+ api.failJourney(journey, category);
398
619
  },
399
- reset (next = {}) {
400
- var _next_sinks, _ref, _ref1;
401
- var _next_requestTimeouts, _next_requestTimeouts1;
620
+ reset (next1 = {}) {
621
+ var _next_sinks, _ref, _ref1, _next_defaultJourneyIdleMs;
622
+ var _next_slowRequests, _next_slowRequests1;
402
623
  for (const journey of [
403
624
  ...journeys.values()
404
625
  ]){
@@ -407,13 +628,17 @@ function toName(target) {
407
628
  }
408
629
  journeys.clear();
409
630
  inFlight.clear();
631
+ overlay = {};
632
+ sinksConfigured = false;
410
633
  sinks = [
411
- ...(_next_sinks = next.sinks) !== null && _next_sinks !== void 0 ? _next_sinks : []
634
+ ...(_next_sinks = next1.sinks) !== null && _next_sinks !== void 0 ? _next_sinks : []
412
635
  ];
413
- requestTimeouts = {
414
- defaultMs: (_ref = (_next_requestTimeouts = next.requestTimeouts) === null || _next_requestTimeouts === void 0 ? void 0 : _next_requestTimeouts.defaultMs) !== null && _ref !== void 0 ? _ref : DEFAULT_REQUEST_TIMEOUTS.defaultMs,
415
- endpoints: sortEndpointsByLongestMatch((_ref1 = (_next_requestTimeouts1 = next.requestTimeouts) === null || _next_requestTimeouts1 === void 0 ? void 0 : _next_requestTimeouts1.endpoints) !== null && _ref1 !== void 0 ? _ref1 : DEFAULT_REQUEST_TIMEOUTS.endpoints)
636
+ slowRequests = {
637
+ defaultMs: (_ref = (_next_slowRequests = next1.slowRequests) === null || _next_slowRequests === void 0 ? void 0 : _next_slowRequests.defaultMs) !== null && _ref !== void 0 ? _ref : DEFAULT_SLOW_REQUESTS.defaultMs,
638
+ endpoints: sortEndpointsByLongestMatch((_ref1 = (_next_slowRequests1 = next1.slowRequests) === null || _next_slowRequests1 === void 0 ? void 0 : _next_slowRequests1.endpoints) !== null && _ref1 !== void 0 ? _ref1 : DEFAULT_SLOW_REQUESTS.endpoints)
416
639
  };
640
+ maxSteps = next1.maxSteps !== undefined ? clampMaxSteps(next1.maxSteps) : DEFAULT_MAX_STEPS;
641
+ defaultJourneyIdleMs = finiteTimeoutMs((_next_defaultJourneyIdleMs = next1.defaultJourneyIdleMs) !== null && _next_defaultJourneyIdleMs !== void 0 ? _next_defaultJourneyIdleMs : DEFAULT_JOURNEY_IDLE_MS);
417
642
  }
418
643
  };
419
644
  return api;
@@ -423,10 +648,62 @@ function toName(target) {
423
648
  * Module-scoped engine used by free helpers. App code does not need to touch this.
424
649
  * Each host/MFE bundle gets its own copy when the package is duplicated.
425
650
  */ export const moduleRuntime = createJourneyRuntime();
651
+ const noopEngine = {
652
+ failJourney () {},
653
+ exclude () {},
654
+ shouldIgnoreRequest () {
655
+ return false;
656
+ },
657
+ reportBackendRequest () {},
658
+ getHttpAbortedRequestsPolicy () {
659
+ return 'continue';
660
+ },
661
+ getHttpClientErrorRequestsPolicy () {
662
+ return 'continue';
663
+ }
664
+ };
665
+ /**
666
+ * True when this bundle can fail/exclude/report the journey: stamped engine, or
667
+ * this runtime's own unstamped object. False for a foreign older-copy journey.
668
+ */ export function canRouteHttpToJourney(journey) {
669
+ return journey[JOURNEY_ENGINE] != null || moduleRuntime.getOpenJourney(journey.name) === journey;
670
+ }
671
+ /**
672
+ * @internal Prefer the creating engine on a journey (MFE `step.stamp()` on a host transport).
673
+ * Falls back to this bundle's module runtime only when this runtime owns the object.
674
+ * Unstamped foreign journeys (older package copies) are a no-op so a same-named
675
+ * host journey is not deleted from this map.
676
+ */ export function resolveJourneyEngine(journey) {
677
+ const stamped = journey[JOURNEY_ENGINE];
678
+ if (stamped) {
679
+ return stamped;
680
+ }
681
+ if (moduleRuntime.getOpenJourney(journey.name) !== journey) {
682
+ return noopEngine;
683
+ }
684
+ return {
685
+ failJourney (reason) {
686
+ moduleRuntime.failJourney(journey, reason);
687
+ },
688
+ exclude () {
689
+ moduleRuntime.excludeJourney(journey);
690
+ },
691
+ shouldIgnoreRequest (key) {
692
+ return moduleRuntime.shouldIgnoreRequest(journey, key);
693
+ },
694
+ reportBackendRequest (step, status, durationMs, requestKey) {
695
+ moduleRuntime.reportBackendRequest(step, status, durationMs, requestKey);
696
+ },
697
+ getHttpAbortedRequestsPolicy () {
698
+ return moduleRuntime.getHttpAbortedRequestsPolicy();
699
+ },
700
+ getHttpClientErrorRequestsPolicy () {
701
+ return moduleRuntime.getHttpClientErrorRequestsPolicy();
702
+ }
703
+ };
704
+ }
426
705
  /**
427
- * @internal Accidentally exported removing it is a breaking change, so it stays
428
- * for compatibility. Not part of the supported public API.
429
- * Reset the module engine (open journeys, ambient steps, sinks, request thresholds).
706
+ * @internal Reset the module engine (open journeys, ambient steps, sinks, slow-request thresholds).
430
707
  * Intended for tests — call in `beforeEach` / `afterEach` instead of manual cleanup.
431
708
  */ export function resetJourney(options) {
432
709
  moduleRuntime.reset(options);