@servicetitan/journey 2.1.0 → 3.0.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/README.md +1 -1
- package/dist/__test-utils__/test-runtime.d.ts.map +1 -1
- package/dist/__test-utils__/test-runtime.js +9 -2
- package/dist/__test-utils__/test-runtime.js.map +1 -1
- package/dist/__tests__/core/sanitize.test.d.ts +2 -0
- package/dist/__tests__/core/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/hardening.test.d.ts +2 -0
- package/dist/__tests__/hardening.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/host-mfe.test.d.ts +2 -0
- package/dist/__tests__/integrations/host-mfe.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/use-journey-step.test.d.ts +2 -0
- package/dist/__tests__/integrations/use-journey-step.test.d.ts.map +1 -0
- package/dist/config.d.ts +57 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +87 -24
- package/dist/config.js.map +1 -1
- package/dist/core/abort-error.d.ts +4 -0
- package/dist/core/abort-error.d.ts.map +1 -0
- package/dist/core/abort-error.js +22 -0
- package/dist/core/abort-error.js.map +1 -0
- package/dist/core/api.d.ts +22 -9
- package/dist/core/api.d.ts.map +1 -1
- package/dist/core/api.js +77 -30
- package/dist/core/api.js.map +1 -1
- package/dist/core/endpoint-policy.d.ts +14 -4
- package/dist/core/endpoint-policy.d.ts.map +1 -1
- package/dist/core/endpoint-policy.js +32 -1
- package/dist/core/endpoint-policy.js.map +1 -1
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/limits.d.ts +29 -0
- package/dist/core/limits.d.ts.map +1 -0
- package/dist/core/limits.js +63 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/request-policy.d.ts +39 -0
- package/dist/core/request-policy.d.ts.map +1 -0
- package/dist/core/request-policy.js +18 -0
- package/dist/core/request-policy.js.map +1 -0
- package/dist/core/runtime.d.ts +65 -15
- package/dist/core/runtime.d.ts.map +1 -1
- package/dist/core/runtime.js +403 -126
- package/dist/core/runtime.js.map +1 -1
- package/dist/core/sanitize.d.ts +23 -0
- package/dist/core/sanitize.d.ts.map +1 -0
- package/dist/core/sanitize.js +143 -0
- package/dist/core/sanitize.js.map +1 -0
- package/dist/core/step-tag.d.ts +25 -6
- package/dist/core/step-tag.d.ts.map +1 -1
- package/dist/core/step-tag.js +56 -10
- package/dist/core/step-tag.js.map +1 -1
- package/dist/core/types.d.ts +73 -25
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +4 -1
- package/dist/core/types.js.map +1 -1
- package/dist/global.d.ts +91 -13
- package/dist/global.d.ts.map +1 -1
- package/dist/global.js +235 -25
- package/dist/global.js.map +1 -1
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -6
- package/dist/index.js.map +1 -1
- package/dist/integrations/axios.d.ts +5 -1
- package/dist/integrations/axios.d.ts.map +1 -1
- package/dist/integrations/axios.js +146 -34
- package/dist/integrations/axios.js.map +1 -1
- package/dist/integrations/fetch.d.ts +3 -2
- package/dist/integrations/fetch.d.ts.map +1 -1
- package/dist/integrations/fetch.js +105 -26
- package/dist/integrations/fetch.js.map +1 -1
- package/dist/integrations/http-report.d.ts +27 -0
- package/dist/integrations/http-report.d.ts.map +1 -0
- package/dist/integrations/http-report.js +123 -0
- package/dist/integrations/http-report.js.map +1 -0
- package/dist/integrations/jquery/index.d.ts +8 -4
- package/dist/integrations/jquery/index.d.ts.map +1 -1
- package/dist/integrations/jquery/index.js +26 -8
- package/dist/integrations/jquery/index.js.map +1 -1
- package/dist/integrations/jquery/instrument.d.ts +14 -5
- package/dist/integrations/jquery/instrument.d.ts.map +1 -1
- package/dist/integrations/jquery/instrument.js +111 -29
- package/dist/integrations/jquery/instrument.js.map +1 -1
- package/dist/integrations/react/context.d.ts +4 -1
- package/dist/integrations/react/context.d.ts.map +1 -1
- package/dist/integrations/react/context.js +4 -1
- package/dist/integrations/react/context.js.map +1 -1
- package/dist/integrations/react/scope.d.ts +14 -3
- package/dist/integrations/react/scope.d.ts.map +1 -1
- package/dist/integrations/react/scope.js +46 -8
- package/dist/integrations/react/scope.js.map +1 -1
- package/dist/integrations/react/stable-config-keys.d.ts +6 -0
- package/dist/integrations/react/stable-config-keys.d.ts.map +1 -0
- package/dist/integrations/react/stable-config-keys.js +21 -0
- package/dist/integrations/react/stable-config-keys.js.map +1 -0
- package/dist/integrations/react/use-journey-step.d.ts +11 -0
- package/dist/integrations/react/use-journey-step.d.ts.map +1 -0
- package/dist/integrations/react/use-journey-step.js +19 -0
- package/dist/integrations/react/use-journey-step.js.map +1 -0
- package/dist/integrations/request-key.d.ts +8 -3
- package/dist/integrations/request-key.d.ts.map +1 -1
- package/dist/integrations/request-key.js +10 -9
- package/dist/integrations/request-key.js.map +1 -1
- package/dist/integrations/soft-skip.d.ts +7 -0
- package/dist/integrations/soft-skip.d.ts.map +1 -0
- package/dist/integrations/soft-skip.js +30 -0
- package/dist/integrations/soft-skip.js.map +1 -0
- package/dist/sinks/console.d.ts.map +1 -1
- package/dist/sinks/console.js +2 -1
- package/dist/sinks/console.js.map +1 -1
- package/dist/sinks/datadog.d.ts +1 -1
- package/dist/sinks/datadog.d.ts.map +1 -1
- package/dist/sinks/datadog.js +16 -20
- package/dist/sinks/datadog.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-utils__/test-runtime.ts +9 -2
- package/src/__tests__/config.test.ts +150 -31
- package/src/__tests__/core/ambient.test.ts +16 -10
- package/src/__tests__/core/api.test.ts +7 -7
- package/src/__tests__/core/define-journey.test.ts +28 -11
- package/src/__tests__/core/requests.test.ts +49 -31
- package/src/__tests__/core/runtime.test.ts +67 -20
- package/src/__tests__/core/sanitize.test.ts +69 -0
- package/src/__tests__/core/step.test.ts +132 -36
- package/src/__tests__/core/timeouts.test.ts +69 -4
- package/src/__tests__/global.test.ts +44 -3
- package/src/__tests__/hardening.test.ts +613 -0
- package/src/__tests__/integrations/axios.test.ts +247 -27
- package/src/__tests__/integrations/expose-app-journey.test.ts +9 -3
- package/src/__tests__/integrations/fetch.test.ts +153 -19
- package/src/__tests__/integrations/host-mfe.test.ts +548 -0
- package/src/__tests__/integrations/jquery.test.ts +209 -34
- package/src/__tests__/integrations/scope.test.tsx +160 -2
- package/src/__tests__/integrations/use-journey-step.test.tsx +43 -0
- package/src/__tests__/sinks/console.test.ts +9 -4
- package/src/__tests__/sinks/datadog.test.ts +47 -0
- package/src/config.ts +162 -38
- package/src/core/abort-error.ts +24 -0
- package/src/core/api.ts +83 -33
- package/src/core/endpoint-policy.ts +46 -4
- package/src/core/index.ts +12 -4
- package/src/core/limits.ts +73 -0
- package/src/core/request-policy.ts +66 -0
- package/src/core/runtime.ts +507 -133
- package/src/core/sanitize.ts +177 -0
- package/src/core/step-tag.ts +82 -12
- package/src/core/types.ts +79 -25
- package/src/global.ts +312 -42
- package/src/index.ts +17 -17
- package/src/integrations/axios.ts +183 -45
- package/src/integrations/fetch.ts +125 -39
- package/src/integrations/http-report.ts +184 -0
- package/src/integrations/jquery/index.ts +34 -10
- package/src/integrations/jquery/instrument.ts +126 -42
- package/src/integrations/react/context.ts +4 -1
- package/src/integrations/react/scope.tsx +59 -9
- package/src/integrations/react/stable-config-keys.ts +28 -0
- package/src/integrations/react/use-journey-step.ts +31 -0
- package/src/integrations/request-key.ts +18 -10
- package/src/integrations/soft-skip.ts +35 -0
- package/src/sinks/console.ts +2 -1
- package/src/sinks/datadog.ts +23 -29
package/src/core/runtime.ts
CHANGED
|
@@ -1,48 +1,84 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
getAutoAttributeRequests as getSharedAutoAttributeRequests,
|
|
3
|
+
getHttpAbortedRequestsPolicy as getSharedHttpAbortedRequestsPolicy,
|
|
4
|
+
getHttpClientErrorRequestsPolicy as getSharedHttpClientErrorRequestsPolicy,
|
|
5
|
+
getSharedSlowRequests,
|
|
6
|
+
isJourneyDebugEnabled,
|
|
7
|
+
} from '../global';
|
|
8
|
+
import { isCanceledError } from './abort-error';
|
|
9
|
+
import {
|
|
10
|
+
resolveEndpointPolicy,
|
|
3
11
|
sortEndpointsByLongestMatch,
|
|
4
|
-
type
|
|
12
|
+
type SlowRequestConfig,
|
|
5
13
|
} from './endpoint-policy';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
import {
|
|
15
|
+
clampMaxSteps,
|
|
16
|
+
DEFAULT_MAX_STEPS,
|
|
17
|
+
DEFAULT_JOURNEY_IDLE_MS,
|
|
18
|
+
finiteSlowRequestMs,
|
|
19
|
+
finiteTimeoutMs,
|
|
20
|
+
MAX_ATTRIBUTED_REQUEST_KEYS,
|
|
21
|
+
MAX_TAG_KEYS,
|
|
22
|
+
resolveJourneyTimeoutMs,
|
|
23
|
+
} from './limits';
|
|
24
|
+
import { assignRecord, emptyRecord, sanitizeJourneyEvent, setSafeAttribute } from './sanitize';
|
|
25
|
+
import { JourneyStepStamp, resolveStampOpts } from './step-tag';
|
|
26
|
+
import type { RequestPolicyConfig } from './request-policy';
|
|
27
|
+
import {
|
|
28
|
+
JOURNEY_ENGINE,
|
|
29
|
+
type JourneyDef,
|
|
30
|
+
type JourneyEngine,
|
|
31
|
+
type JourneyEvent,
|
|
32
|
+
type JourneyNameLike,
|
|
33
|
+
type JourneySink,
|
|
34
|
+
type JourneyState,
|
|
35
|
+
type JourneyStepEvent,
|
|
36
|
+
type JourneyStepOptions,
|
|
37
|
+
type JourneyStepTarget,
|
|
38
|
+
type Outcome,
|
|
39
|
+
type StepHandle,
|
|
40
|
+
type StepRecord,
|
|
41
|
+
type TagValue,
|
|
20
42
|
} from './types';
|
|
21
43
|
|
|
22
|
-
const
|
|
44
|
+
const DEFAULT_SLOW_REQUESTS: SlowRequestConfig = { defaultMs: 4_000, endpoints: [] };
|
|
23
45
|
|
|
24
46
|
const noopStep: StepHandle = {
|
|
25
|
-
|
|
47
|
+
journeyName: null,
|
|
26
48
|
setAttribute() {},
|
|
27
49
|
setTags() {},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
50
|
+
completeJourney() {},
|
|
51
|
+
failJourney() {},
|
|
52
|
+
stamp: () => undefined,
|
|
31
53
|
};
|
|
32
54
|
|
|
33
|
-
function toName(target:
|
|
34
|
-
|
|
55
|
+
function toName(target: JourneyNameLike): string {
|
|
56
|
+
if (typeof target === 'string') {
|
|
57
|
+
return target;
|
|
58
|
+
}
|
|
59
|
+
return 'config' in target ? target.config.name : target.name;
|
|
35
60
|
}
|
|
36
61
|
|
|
37
62
|
/** @internal Private — not part of the public package API. */
|
|
38
63
|
export interface JourneyRuntimeOptions {
|
|
64
|
+
/**
|
|
65
|
+
* DevTools label for this engine copy (`__stJourney.getDebugState().runtimes`).
|
|
66
|
+
* Auto-assigned `runtime-N` from the current `__stJourney` registration count
|
|
67
|
+
* when omitted at `registerJourneyRuntime`.
|
|
68
|
+
*/
|
|
69
|
+
id?: string;
|
|
39
70
|
/** Initial sinks. Defaults to `[]` — package bootstrap installs Datadog on the module runtime. */
|
|
40
71
|
sinks?: readonly JourneySink[];
|
|
41
|
-
|
|
72
|
+
slowRequests?: SlowRequestConfig;
|
|
73
|
+
/** Cap recorded steps; default 50. */
|
|
74
|
+
maxSteps?: number;
|
|
75
|
+
/** Default timeout when JourneyDef.timeoutMs is omitted. 0 disables idle countdown. */
|
|
76
|
+
defaultJourneyIdleMs?: number;
|
|
42
77
|
}
|
|
43
78
|
|
|
44
79
|
/** @internal Read-only snapshot for DevTools (`__stJourney.getDebugState`; empty unless debugging). */
|
|
45
80
|
export interface JourneyRuntimeDebugSnapshot {
|
|
81
|
+
id: string;
|
|
46
82
|
openJourneys: readonly {
|
|
47
83
|
name: string;
|
|
48
84
|
team: string;
|
|
@@ -57,9 +93,32 @@ export interface JourneyRuntimeDebugSnapshot {
|
|
|
57
93
|
outcome: 'good' | 'bad';
|
|
58
94
|
durationMs: number;
|
|
59
95
|
reason?: string;
|
|
96
|
+
attributedRequestKeys?: readonly string[];
|
|
60
97
|
}[];
|
|
61
98
|
}[];
|
|
62
99
|
activeStep: { journey: string; name: string } | null;
|
|
100
|
+
/** Which shared policy knobs this runtime overrode (false = inherit global). */
|
|
101
|
+
policyOverlay: {
|
|
102
|
+
slowRequests: boolean;
|
|
103
|
+
httpAbortedRequests: boolean;
|
|
104
|
+
httpClientErrorRequests: boolean;
|
|
105
|
+
autoAttributeRequests: boolean;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function freezeJourneyDef(config: JourneyDef, tags: Record<string, TagValue>): JourneyDef {
|
|
110
|
+
const endpoints = sortEndpointsByLongestMatch(config.endpoints);
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
...config,
|
|
113
|
+
timeoutMs: config.timeoutMs !== undefined ? finiteTimeoutMs(config.timeoutMs) : undefined,
|
|
114
|
+
stepTimeoutMs:
|
|
115
|
+
config.stepTimeoutMs !== undefined ? finiteTimeoutMs(config.stepTimeoutMs) : undefined,
|
|
116
|
+
slowRequestMs: finiteSlowRequestMs(config.slowRequestMs),
|
|
117
|
+
maxSteps: config.maxSteps !== undefined ? clampMaxSteps(config.maxSteps) : undefined,
|
|
118
|
+
endpoints: endpoints ? Object.freeze([...endpoints]) : undefined,
|
|
119
|
+
expected: config.expected ? Object.freeze([...config.expected]) : undefined,
|
|
120
|
+
tags: Object.freeze({ ...tags }),
|
|
121
|
+
});
|
|
63
122
|
}
|
|
64
123
|
|
|
65
124
|
/**
|
|
@@ -69,25 +128,44 @@ export interface JourneyRuntimeDebugSnapshot {
|
|
|
69
128
|
* bind to the module runtime. Prefer `resetJourney()` in tests.
|
|
70
129
|
*/
|
|
71
130
|
export interface JourneyRuntime {
|
|
72
|
-
|
|
73
|
-
|
|
131
|
+
/** DevTools id for this engine copy. Auto `runtime-N` when registered without an id. */
|
|
132
|
+
readonly id: string;
|
|
133
|
+
getSlowRequests(): SlowRequestConfig;
|
|
134
|
+
setSlowRequests(next: SlowRequestConfig): void;
|
|
135
|
+
getMaxSteps(): number;
|
|
136
|
+
setMaxSteps(next: number): void;
|
|
137
|
+
getDefaultJourneyIdleMs(): number;
|
|
138
|
+
setDefaultJourneyIdleMs(next: number): void;
|
|
74
139
|
|
|
75
140
|
setSinks(next: readonly JourneySink[]): void;
|
|
141
|
+
/** First `configureJourney({ sinks })` on this runtime wins. Returns false if already set. */
|
|
142
|
+
trySetConfiguredSinks(next: readonly JourneySink[]): boolean;
|
|
76
143
|
addSink(sink: JourneySink): void;
|
|
77
144
|
emit(payload: JourneyEvent): void;
|
|
78
145
|
|
|
146
|
+
setPolicyOverlay(next: {
|
|
147
|
+
slowRequests?: SlowRequestConfig;
|
|
148
|
+
httpAbortedRequests?: RequestPolicyConfig;
|
|
149
|
+
httpClientErrorRequests?: RequestPolicyConfig;
|
|
150
|
+
autoAttributeRequests?: boolean;
|
|
151
|
+
}): void;
|
|
152
|
+
getAutoAttributeRequests(): boolean;
|
|
153
|
+
getHttpAbortedRequestsPolicy(): RequestPolicyConfig;
|
|
154
|
+
getHttpClientErrorRequestsPolicy(): RequestPolicyConfig;
|
|
155
|
+
|
|
79
156
|
getOpenJourney(name: string): JourneyState | undefined;
|
|
80
157
|
/** @internal DevTools snapshot of open journeys / ambient step. */
|
|
81
158
|
getDebugSnapshot(): JourneyRuntimeDebugSnapshot;
|
|
82
|
-
|
|
159
|
+
firstOpenJourneyName(names: readonly string[]): string | null;
|
|
83
160
|
setJourneyTags(name: string | null, tags: Record<string, TagValue>): void;
|
|
84
|
-
startJourney(
|
|
85
|
-
|
|
86
|
-
failJourney(name: string, reason: string): void;
|
|
161
|
+
startJourney(config: JourneyDef): void;
|
|
162
|
+
failJourney(journey: JourneyState, reason: string): void;
|
|
87
163
|
completeJourney(name: string | null, tags?: Record<string, TagValue>): void;
|
|
88
|
-
|
|
164
|
+
completeJourneyState(journey: JourneyState, tags?: Record<string, TagValue>): void;
|
|
165
|
+
excludeJourney(journey: JourneyState): void;
|
|
166
|
+
updateOpenJourney(config: JourneyDef): void;
|
|
89
167
|
|
|
90
|
-
activeJourneyStep():
|
|
168
|
+
activeJourneyStep(): StepRecord | null;
|
|
91
169
|
journeyStep<T>(
|
|
92
170
|
journey: JourneyStepTarget,
|
|
93
171
|
stepName: string,
|
|
@@ -102,30 +180,71 @@ export interface JourneyRuntime {
|
|
|
102
180
|
): Promise<T>;
|
|
103
181
|
|
|
104
182
|
reportBackendRequest(
|
|
105
|
-
step:
|
|
183
|
+
step: StepRecord | undefined,
|
|
106
184
|
status: number | undefined,
|
|
107
185
|
durationMs: number,
|
|
108
186
|
requestKey: string
|
|
109
187
|
): void;
|
|
188
|
+
/** True when this request key is ignored for the step's journey (never changes verdict). */
|
|
189
|
+
shouldIgnoreRequest(journey: JourneyState, key: string): boolean;
|
|
110
190
|
|
|
111
191
|
/** Close open journeys, clear ambient steps, restore default timeouts/sinks. */
|
|
112
192
|
reset(options?: JourneyRuntimeOptions): void;
|
|
113
193
|
}
|
|
114
194
|
|
|
195
|
+
function bindJourneyEngine(
|
|
196
|
+
api: JourneyRuntime,
|
|
197
|
+
journey: JourneyState,
|
|
198
|
+
shouldIgnoreRequest: (target: JourneyState, key: string) => boolean
|
|
199
|
+
): JourneyEngine {
|
|
200
|
+
return {
|
|
201
|
+
failJourney(reason) {
|
|
202
|
+
api.failJourney(journey, reason);
|
|
203
|
+
},
|
|
204
|
+
exclude() {
|
|
205
|
+
api.excludeJourney(journey);
|
|
206
|
+
},
|
|
207
|
+
shouldIgnoreRequest(key) {
|
|
208
|
+
return shouldIgnoreRequest(journey, key);
|
|
209
|
+
},
|
|
210
|
+
reportBackendRequest(step, status, durationMs, requestKey) {
|
|
211
|
+
api.reportBackendRequest(step, status, durationMs, requestKey);
|
|
212
|
+
},
|
|
213
|
+
getHttpAbortedRequestsPolicy() {
|
|
214
|
+
return api.getHttpAbortedRequestsPolicy();
|
|
215
|
+
},
|
|
216
|
+
getHttpClientErrorRequestsPolicy() {
|
|
217
|
+
return api.getHttpClientErrorRequestsPolicy();
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
115
222
|
/**
|
|
116
223
|
* @internal Private — not part of the public package API.
|
|
117
224
|
* Build an isolated engine. Rarely needed — apps use the default via free helpers.
|
|
118
225
|
*/
|
|
119
226
|
export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): JourneyRuntime {
|
|
227
|
+
let id = typeof options.id === 'string' && options.id !== '' ? options.id : '';
|
|
120
228
|
const journeys = new Map<string, JourneyState>();
|
|
121
229
|
const inFlight = new Set<StepRecord>();
|
|
122
230
|
let sinks: JourneySink[] = [...(options.sinks ?? [])];
|
|
123
|
-
let
|
|
124
|
-
|
|
231
|
+
let sinksConfigured = false;
|
|
232
|
+
let overlay: {
|
|
233
|
+
slowRequests?: SlowRequestConfig;
|
|
234
|
+
httpAbortedRequests?: RequestPolicyConfig;
|
|
235
|
+
httpClientErrorRequests?: RequestPolicyConfig;
|
|
236
|
+
autoAttributeRequests?: boolean;
|
|
237
|
+
} = {};
|
|
238
|
+
let slowRequests: SlowRequestConfig = {
|
|
239
|
+
defaultMs: options.slowRequests?.defaultMs ?? DEFAULT_SLOW_REQUESTS.defaultMs,
|
|
125
240
|
endpoints: sortEndpointsByLongestMatch(
|
|
126
|
-
options.
|
|
241
|
+
options.slowRequests?.endpoints ?? DEFAULT_SLOW_REQUESTS.endpoints
|
|
127
242
|
),
|
|
128
243
|
};
|
|
244
|
+
let maxSteps = clampMaxSteps(options.maxSteps ?? DEFAULT_MAX_STEPS);
|
|
245
|
+
let defaultJourneyIdleMs = finiteTimeoutMs(
|
|
246
|
+
options.defaultJourneyIdleMs ?? DEFAULT_JOURNEY_IDLE_MS
|
|
247
|
+
);
|
|
129
248
|
|
|
130
249
|
function getInFlightSteps(journey: JourneyState): StepRecord[] {
|
|
131
250
|
const out: StepRecord[] = [];
|
|
@@ -145,6 +264,18 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
145
264
|
inFlight.delete(step);
|
|
146
265
|
}
|
|
147
266
|
|
|
267
|
+
function releaseInFlightSteps(journey: JourneyState): void {
|
|
268
|
+
for (const step of [...inFlight]) {
|
|
269
|
+
if (step.journey !== journey) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (step.durationMs === 0) {
|
|
273
|
+
step.durationMs = Math.round(globalThis.performance.now() - step.startedAt);
|
|
274
|
+
}
|
|
275
|
+
inFlight.delete(step);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
148
279
|
function clearCountdown(journey: JourneyState): void {
|
|
149
280
|
if (journey.timer != null) {
|
|
150
281
|
clearTimeout(journey.timer);
|
|
@@ -153,9 +284,10 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
153
284
|
}
|
|
154
285
|
|
|
155
286
|
function emit(payload: JourneyEvent): void {
|
|
287
|
+
const safePayload = sanitizeJourneyEvent(payload);
|
|
156
288
|
for (const sink of sinks) {
|
|
157
289
|
try {
|
|
158
|
-
sink(
|
|
290
|
+
sink(safePayload);
|
|
159
291
|
} catch {
|
|
160
292
|
// One sinking throw must not block the remaining sinks.
|
|
161
293
|
}
|
|
@@ -167,19 +299,16 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
167
299
|
return;
|
|
168
300
|
}
|
|
169
301
|
journey.closed = true;
|
|
170
|
-
journeys.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
for (const step of getInFlightSteps(journey)) {
|
|
174
|
-
if (step.durationMs === 0) {
|
|
175
|
-
step.durationMs = Math.round(globalThis.performance.now() - step.startedAt);
|
|
176
|
-
}
|
|
302
|
+
if (journeys.get(journey.name) === journey) {
|
|
303
|
+
journeys.delete(journey.name);
|
|
177
304
|
}
|
|
305
|
+
clearCountdown(journey);
|
|
306
|
+
releaseInFlightSteps(journey);
|
|
178
307
|
|
|
179
308
|
let finalReason = journey.reason ?? reason;
|
|
180
309
|
let finalOutcome = outcome;
|
|
181
310
|
const durationMs = Math.round(globalThis.performance.now() - journey.startedAt);
|
|
182
|
-
if (journey.timeoutMs > 0 && durationMs > journey.timeoutMs) {
|
|
311
|
+
if (journey.timeoutExplicit && journey.timeoutMs > 0 && durationMs > journey.timeoutMs) {
|
|
183
312
|
finalOutcome = 'bad';
|
|
184
313
|
finalReason = finalReason ?? 'journey-timeout';
|
|
185
314
|
}
|
|
@@ -191,7 +320,7 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
191
320
|
outcome: s.outcome,
|
|
192
321
|
...(s.reason ? { reason: s.reason } : {}),
|
|
193
322
|
...(s.httpStatus !== undefined ? { httpStatus: s.httpStatus } : {}),
|
|
194
|
-
...(Object.keys(s.attributes).length ? { attributes: s.attributes } : {}),
|
|
323
|
+
...(Object.keys(s.attributes).length ? { attributes: { ...s.attributes } } : {}),
|
|
195
324
|
}));
|
|
196
325
|
|
|
197
326
|
emit({
|
|
@@ -201,48 +330,47 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
201
330
|
group: journey.group,
|
|
202
331
|
service: journey.service,
|
|
203
332
|
outcome: finalOutcome,
|
|
204
|
-
...(
|
|
333
|
+
...(finalReason ? { reason: finalReason } : {}),
|
|
205
334
|
durationMs,
|
|
206
335
|
steps,
|
|
207
336
|
...(journey.expected ? { expected: journey.expected } : {}),
|
|
208
|
-
...(Object.keys(journey.tags).length ? { tags: journey.tags } : {}),
|
|
337
|
+
...(Object.keys(journey.tags).length ? { tags: { ...journey.tags } } : {}),
|
|
209
338
|
},
|
|
210
339
|
});
|
|
211
340
|
}
|
|
212
341
|
|
|
213
|
-
function armCountdown(journey: JourneyState): void {
|
|
214
|
-
if (
|
|
342
|
+
function armCountdown(journey: JourneyState, delayMs: number = journey.timeoutMs): void {
|
|
343
|
+
if (delayMs <= 0) {
|
|
215
344
|
return;
|
|
216
345
|
}
|
|
217
346
|
journey.timer = setTimeout(() => {
|
|
218
|
-
|
|
219
|
-
|
|
347
|
+
if (journey.timeoutExplicit) {
|
|
348
|
+
finish(journey, 'bad', 'journey-timeout');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
finish(journey, journey.verdict === 'bad' ? 'bad' : 'excluded', 'journey-idle-timeout');
|
|
352
|
+
}, delayMs);
|
|
220
353
|
}
|
|
221
354
|
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
if (own?.timeoutMs) {
|
|
225
|
-
return own.timeoutMs;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const app = longestEndpointMatch(requestTimeouts.endpoints, key);
|
|
229
|
-
if (app?.timeoutMs) {
|
|
230
|
-
return app.timeoutMs;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (journey.requestTimeoutMs != null) {
|
|
234
|
-
return journey.requestTimeoutMs;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return requestTimeouts.defaultMs;
|
|
355
|
+
function effectiveSlowRequests(): SlowRequestConfig {
|
|
356
|
+
return overlay.slowRequests ?? getSharedSlowRequests() ?? slowRequests;
|
|
238
357
|
}
|
|
239
358
|
|
|
240
359
|
function shouldIgnoreRequest(journey: JourneyState, key: string): boolean {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
360
|
+
return resolveEndpointPolicy(journey, effectiveSlowRequests(), key).ignore;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function trackAttributedKey(step: StepRecord, requestKey: string): void {
|
|
364
|
+
if (!requestKey) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
step.attributedRequestKeys ??= [];
|
|
368
|
+
if (step.attributedRequestKeys.length >= MAX_ATTRIBUTED_REQUEST_KEYS) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (!step.attributedRequestKeys.includes(requestKey)) {
|
|
372
|
+
step.attributedRequestKeys.push(requestKey);
|
|
244
373
|
}
|
|
245
|
-
return longestEndpointMatch(requestTimeouts.endpoints, key)?.ignore === true;
|
|
246
374
|
}
|
|
247
375
|
|
|
248
376
|
function resolveJourneyName(journey: JourneyStepTarget): string | null {
|
|
@@ -250,53 +378,140 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
250
378
|
return null;
|
|
251
379
|
}
|
|
252
380
|
if (typeof journey === 'string' || !Array.isArray(journey)) {
|
|
253
|
-
return toName(journey as
|
|
381
|
+
return toName(journey as JourneyNameLike);
|
|
382
|
+
}
|
|
383
|
+
return api.firstOpenJourneyName(journey.map(toName));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function configFromTarget(target: JourneyNameLike): JourneyDef | undefined {
|
|
387
|
+
if (typeof target === 'string') {
|
|
388
|
+
return undefined;
|
|
389
|
+
}
|
|
390
|
+
if ('config' in target && target.config != null && typeof target.config === 'object') {
|
|
391
|
+
return target.config;
|
|
392
|
+
}
|
|
393
|
+
const rec = target as {
|
|
394
|
+
name?: unknown;
|
|
395
|
+
team?: unknown;
|
|
396
|
+
group?: unknown;
|
|
397
|
+
service?: unknown;
|
|
398
|
+
};
|
|
399
|
+
if (
|
|
400
|
+
typeof rec.name === 'string' &&
|
|
401
|
+
typeof rec.team === 'string' &&
|
|
402
|
+
typeof rec.group === 'string' &&
|
|
403
|
+
typeof rec.service === 'string'
|
|
404
|
+
) {
|
|
405
|
+
return target as JourneyDef;
|
|
406
|
+
}
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function maybeAutoStart(target: JourneyStepTarget): void {
|
|
411
|
+
if (target == null || typeof target === 'string' || Array.isArray(target)) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const def = configFromTarget(target as JourneyNameLike);
|
|
415
|
+
if (def && !api.getOpenJourney(def.name)) {
|
|
416
|
+
api.startJourney(def);
|
|
254
417
|
}
|
|
255
|
-
return api.firstOpenJourney(journey.map(toName));
|
|
256
418
|
}
|
|
257
419
|
|
|
258
420
|
function createStepHandle(journeyName: string, step: StepRecord): StepHandle {
|
|
259
421
|
return {
|
|
260
|
-
|
|
422
|
+
journeyName,
|
|
261
423
|
setAttribute(key, value) {
|
|
262
|
-
step.attributes
|
|
424
|
+
setSafeAttribute(step.attributes, key, value);
|
|
263
425
|
},
|
|
264
426
|
setTags(tags) {
|
|
265
|
-
|
|
427
|
+
if (step.journey.closed) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
assignRecord(step.journey.tags, tags, MAX_TAG_KEYS);
|
|
266
431
|
},
|
|
267
|
-
|
|
268
|
-
api.
|
|
432
|
+
completeJourney(tags) {
|
|
433
|
+
api.completeJourneyState(step.journey, tags);
|
|
269
434
|
},
|
|
270
|
-
|
|
271
|
-
api.failJourney(
|
|
435
|
+
failJourney(reason) {
|
|
436
|
+
api.failJourney(step.journey, reason);
|
|
272
437
|
},
|
|
273
|
-
// Box in
|
|
274
|
-
|
|
438
|
+
// Box in JourneyStepStamp — raw StepRecord is circular and blows jQuery/axios deep-merge.
|
|
439
|
+
stamp: scoreOrOpts => new JourneyStepStamp(step, resolveStampOpts(scoreOrOpts)),
|
|
275
440
|
};
|
|
276
441
|
}
|
|
277
442
|
|
|
278
443
|
const api: JourneyRuntime = {
|
|
279
|
-
|
|
280
|
-
return
|
|
444
|
+
get id() {
|
|
445
|
+
return id;
|
|
446
|
+
},
|
|
447
|
+
set id(next: string) {
|
|
448
|
+
id = next;
|
|
449
|
+
},
|
|
450
|
+
getSlowRequests() {
|
|
451
|
+
return effectiveSlowRequests();
|
|
281
452
|
},
|
|
282
453
|
|
|
283
|
-
|
|
284
|
-
|
|
454
|
+
setSlowRequests(next) {
|
|
455
|
+
slowRequests = {
|
|
285
456
|
...next,
|
|
286
457
|
endpoints: sortEndpointsByLongestMatch(next.endpoints),
|
|
287
458
|
};
|
|
288
459
|
},
|
|
289
460
|
|
|
461
|
+
getMaxSteps() {
|
|
462
|
+
return maxSteps;
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
setMaxSteps(next) {
|
|
466
|
+
maxSteps = clampMaxSteps(next);
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
getDefaultJourneyIdleMs() {
|
|
470
|
+
return defaultJourneyIdleMs;
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
setDefaultJourneyIdleMs(next) {
|
|
474
|
+
defaultJourneyIdleMs = finiteTimeoutMs(next);
|
|
475
|
+
},
|
|
476
|
+
|
|
290
477
|
setSinks(next) {
|
|
291
478
|
sinks = [...next];
|
|
292
479
|
},
|
|
293
480
|
|
|
481
|
+
trySetConfiguredSinks(next) {
|
|
482
|
+
if (sinksConfigured) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
sinks = [...next];
|
|
486
|
+
sinksConfigured = true;
|
|
487
|
+
return true;
|
|
488
|
+
},
|
|
489
|
+
|
|
294
490
|
addSink(sink) {
|
|
295
491
|
sinks.push(sink);
|
|
296
492
|
},
|
|
297
493
|
|
|
298
494
|
emit,
|
|
299
495
|
|
|
496
|
+
setPolicyOverlay(next) {
|
|
497
|
+
overlay = { ...overlay, ...next };
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
getAutoAttributeRequests() {
|
|
501
|
+
if (overlay.autoAttributeRequests !== undefined) {
|
|
502
|
+
return overlay.autoAttributeRequests;
|
|
503
|
+
}
|
|
504
|
+
return getSharedAutoAttributeRequests();
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
getHttpAbortedRequestsPolicy() {
|
|
508
|
+
return overlay.httpAbortedRequests ?? getSharedHttpAbortedRequestsPolicy();
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
getHttpClientErrorRequestsPolicy() {
|
|
512
|
+
return overlay.httpClientErrorRequests ?? getSharedHttpClientErrorRequestsPolicy();
|
|
513
|
+
},
|
|
514
|
+
|
|
300
515
|
getOpenJourney(name) {
|
|
301
516
|
return journeys.get(name);
|
|
302
517
|
},
|
|
@@ -304,6 +519,7 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
304
519
|
getDebugSnapshot() {
|
|
305
520
|
const active = api.activeJourneyStep();
|
|
306
521
|
return {
|
|
522
|
+
id: api.id,
|
|
307
523
|
openJourneys: [...journeys.values()].map(journey => ({
|
|
308
524
|
name: journey.name,
|
|
309
525
|
team: journey.team,
|
|
@@ -318,13 +534,22 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
318
534
|
outcome: step.outcome,
|
|
319
535
|
durationMs: step.durationMs,
|
|
320
536
|
...(step.reason ? { reason: step.reason } : {}),
|
|
537
|
+
...(step.attributedRequestKeys?.length
|
|
538
|
+
? { attributedRequestKeys: [...step.attributedRequestKeys] }
|
|
539
|
+
: {}),
|
|
321
540
|
})),
|
|
322
541
|
})),
|
|
323
542
|
activeStep: active ? { journey: active.journey.name, name: active.name } : null,
|
|
543
|
+
policyOverlay: {
|
|
544
|
+
slowRequests: overlay.slowRequests != null,
|
|
545
|
+
httpAbortedRequests: overlay.httpAbortedRequests != null,
|
|
546
|
+
httpClientErrorRequests: overlay.httpClientErrorRequests != null,
|
|
547
|
+
autoAttributeRequests: overlay.autoAttributeRequests != null,
|
|
548
|
+
},
|
|
324
549
|
};
|
|
325
550
|
},
|
|
326
551
|
|
|
327
|
-
|
|
552
|
+
firstOpenJourneyName(names) {
|
|
328
553
|
for (const name of names) {
|
|
329
554
|
if (journeys.has(name)) {
|
|
330
555
|
return name;
|
|
@@ -336,39 +561,51 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
336
561
|
setJourneyTags(name, tags) {
|
|
337
562
|
const journey = name ? journeys.get(name) : undefined;
|
|
338
563
|
if (journey) {
|
|
339
|
-
|
|
564
|
+
assignRecord(journey.tags, tags, MAX_TAG_KEYS);
|
|
340
565
|
}
|
|
341
566
|
},
|
|
342
567
|
|
|
343
|
-
startJourney(
|
|
344
|
-
const existing = journeys.get(
|
|
568
|
+
startJourney(config) {
|
|
569
|
+
const existing = journeys.get(config.name);
|
|
345
570
|
if (existing) {
|
|
346
571
|
finish(existing, existing.verdict === 'bad' ? 'bad' : 'excluded');
|
|
347
572
|
}
|
|
348
573
|
|
|
574
|
+
const tags = emptyRecord();
|
|
575
|
+
assignRecord(tags, config.tags, MAX_TAG_KEYS);
|
|
576
|
+
const snapshot = freezeJourneyDef(config, tags);
|
|
577
|
+
const timeout = resolveJourneyTimeoutMs(snapshot.timeoutMs, defaultJourneyIdleMs);
|
|
349
578
|
const journey: JourneyState = {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
579
|
+
config: snapshot,
|
|
580
|
+
name: snapshot.name,
|
|
581
|
+
team: snapshot.team,
|
|
582
|
+
group: snapshot.group,
|
|
583
|
+
service: snapshot.service,
|
|
354
584
|
verdict: 'good',
|
|
355
585
|
startedAt: globalThis.performance.now(),
|
|
356
|
-
timeoutMs:
|
|
357
|
-
|
|
586
|
+
timeoutMs: timeout.ms,
|
|
587
|
+
timeoutExplicit: timeout.explicit,
|
|
588
|
+
stepTimeoutMs:
|
|
589
|
+
snapshot.stepTimeoutMs !== undefined
|
|
590
|
+
? finiteTimeoutMs(snapshot.stepTimeoutMs)
|
|
591
|
+
: undefined,
|
|
358
592
|
reason: null,
|
|
359
593
|
steps: [],
|
|
360
|
-
expected:
|
|
361
|
-
tags
|
|
362
|
-
|
|
363
|
-
endpoints:
|
|
594
|
+
expected: snapshot.expected ? [...snapshot.expected] : null,
|
|
595
|
+
tags,
|
|
596
|
+
slowRequestMs: finiteSlowRequestMs(snapshot.slowRequestMs),
|
|
597
|
+
endpoints: snapshot.endpoints,
|
|
364
598
|
timer: null,
|
|
365
599
|
closed: false,
|
|
600
|
+
maxSteps:
|
|
601
|
+
snapshot.maxSteps !== undefined ? clampMaxSteps(snapshot.maxSteps) : undefined,
|
|
366
602
|
};
|
|
367
|
-
journeys.set(
|
|
603
|
+
journeys.set(config.name, journey);
|
|
604
|
+
journey[JOURNEY_ENGINE] = bindJourneyEngine(api, journey, shouldIgnoreRequest);
|
|
368
605
|
armCountdown(journey);
|
|
369
606
|
},
|
|
370
607
|
|
|
371
|
-
|
|
608
|
+
failJourney(journey, reason) {
|
|
372
609
|
if (journey.closed) {
|
|
373
610
|
return;
|
|
374
611
|
}
|
|
@@ -377,34 +614,81 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
377
614
|
finish(journey, 'bad');
|
|
378
615
|
},
|
|
379
616
|
|
|
380
|
-
|
|
617
|
+
completeJourney(name, tags) {
|
|
618
|
+
if (!name) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
381
621
|
const journey = journeys.get(name);
|
|
382
622
|
if (journey) {
|
|
383
|
-
api.
|
|
623
|
+
api.completeJourneyState(journey, tags);
|
|
384
624
|
}
|
|
385
625
|
},
|
|
386
626
|
|
|
387
|
-
|
|
388
|
-
if (
|
|
627
|
+
completeJourneyState(journey, tags) {
|
|
628
|
+
if (journey.closed) {
|
|
389
629
|
return;
|
|
390
630
|
}
|
|
391
631
|
if (tags) {
|
|
392
|
-
|
|
632
|
+
assignRecord(journey.tags, tags, MAX_TAG_KEYS);
|
|
393
633
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
634
|
+
finish(journey, journey.verdict === 'bad' ? 'bad' : 'good');
|
|
635
|
+
},
|
|
636
|
+
|
|
637
|
+
excludeJourney(journey) {
|
|
638
|
+
if (journey.closed) {
|
|
639
|
+
return;
|
|
397
640
|
}
|
|
641
|
+
finish(journey, journey.verdict === 'bad' ? 'bad' : 'excluded');
|
|
398
642
|
},
|
|
399
643
|
|
|
400
|
-
|
|
401
|
-
const journey = journeys.get(name);
|
|
402
|
-
if (journey) {
|
|
403
|
-
|
|
644
|
+
updateOpenJourney(config) {
|
|
645
|
+
const journey = journeys.get(config.name);
|
|
646
|
+
if (!journey || journey.closed) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
journey.team = config.team;
|
|
650
|
+
journey.group = config.group;
|
|
651
|
+
journey.service = config.service;
|
|
652
|
+
const timeout = resolveJourneyTimeoutMs(config.timeoutMs, defaultJourneyIdleMs);
|
|
653
|
+
journey.timeoutMs = timeout.ms;
|
|
654
|
+
journey.timeoutExplicit = timeout.explicit;
|
|
655
|
+
journey.stepTimeoutMs =
|
|
656
|
+
config.stepTimeoutMs !== undefined
|
|
657
|
+
? finiteTimeoutMs(config.stepTimeoutMs)
|
|
658
|
+
: undefined;
|
|
659
|
+
journey.slowRequestMs = finiteSlowRequestMs(config.slowRequestMs);
|
|
660
|
+
journey.endpoints = sortEndpointsByLongestMatch(config.endpoints);
|
|
661
|
+
journey.expected = config.expected ? [...config.expected] : null;
|
|
662
|
+
journey.maxSteps =
|
|
663
|
+
config.maxSteps !== undefined ? clampMaxSteps(config.maxSteps) : undefined;
|
|
664
|
+
if (config.tags) {
|
|
665
|
+
assignRecord(journey.tags, config.tags, MAX_TAG_KEYS);
|
|
404
666
|
}
|
|
667
|
+
clearCountdown(journey);
|
|
668
|
+
if (journey.timeoutMs <= 0) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const elapsedMs = globalThis.performance.now() - journey.startedAt;
|
|
672
|
+
const remainingMs = journey.timeoutMs - elapsedMs;
|
|
673
|
+
if (remainingMs <= 0) {
|
|
674
|
+
if (journey.timeoutExplicit) {
|
|
675
|
+
finish(journey, 'bad', 'journey-timeout');
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
finish(
|
|
679
|
+
journey,
|
|
680
|
+
journey.verdict === 'bad' ? 'bad' : 'excluded',
|
|
681
|
+
'journey-idle-timeout'
|
|
682
|
+
);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
armCountdown(journey, remainingMs);
|
|
405
686
|
},
|
|
406
687
|
|
|
407
688
|
activeJourneyStep() {
|
|
689
|
+
if (!api.getAutoAttributeRequests()) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
408
692
|
if (inFlight.size !== 1) {
|
|
409
693
|
return null;
|
|
410
694
|
}
|
|
@@ -412,9 +696,14 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
412
696
|
},
|
|
413
697
|
|
|
414
698
|
async journeyStep(journey, stepName, fn, opts) {
|
|
699
|
+
maybeAutoStart(journey);
|
|
415
700
|
const journeyName = resolveJourneyName(journey);
|
|
416
701
|
const open = journeyName ? journeys.get(journeyName) : undefined;
|
|
417
702
|
if (!open || !journeyName) {
|
|
703
|
+
if (isJourneyDebugEnabled()) {
|
|
704
|
+
// eslint-disable-next-line no-console -- debug-only untraced hint
|
|
705
|
+
console.debug(`[journey] step "${stepName}" ran untraced (no open journey)`);
|
|
706
|
+
}
|
|
418
707
|
return fn(noopStep);
|
|
419
708
|
}
|
|
420
709
|
|
|
@@ -423,10 +712,15 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
423
712
|
startedAt: globalThis.performance.now(),
|
|
424
713
|
durationMs: 0,
|
|
425
714
|
outcome: 'good',
|
|
426
|
-
attributes:
|
|
715
|
+
attributes: emptyRecord(),
|
|
427
716
|
journey: open,
|
|
428
717
|
};
|
|
429
|
-
open.
|
|
718
|
+
const cap = open.maxSteps ?? maxSteps;
|
|
719
|
+
if (open.steps.length >= cap) {
|
|
720
|
+
open.tags.steps_truncated = true;
|
|
721
|
+
} else {
|
|
722
|
+
open.steps.push(step);
|
|
723
|
+
}
|
|
430
724
|
|
|
431
725
|
const handle = createStepHandle(journeyName, step);
|
|
432
726
|
|
|
@@ -441,7 +735,7 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
441
735
|
step.reason ??= 'step-timeout';
|
|
442
736
|
step.attributes.timeout_ms ??= stepTimeoutMs;
|
|
443
737
|
step.durationMs = Math.round(globalThis.performance.now() - step.startedAt);
|
|
444
|
-
api.
|
|
738
|
+
api.failJourney(open, 'step-timeout');
|
|
445
739
|
}, stepTimeoutMs);
|
|
446
740
|
}
|
|
447
741
|
|
|
@@ -449,11 +743,16 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
449
743
|
try {
|
|
450
744
|
return await fn(handle);
|
|
451
745
|
} catch (err) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
746
|
+
if (isCanceledError(err)) {
|
|
747
|
+
/*
|
|
748
|
+
* Attributed abort is scored in onHttpComplete. Unattributed /
|
|
749
|
+
* ignored abort throws must not apply policy (empty requestKey).
|
|
750
|
+
*/
|
|
751
|
+
} else {
|
|
752
|
+
step.outcome = 'bad';
|
|
753
|
+
step.reason ??= 'step-error';
|
|
754
|
+
api.failJourney(open, 'step-error');
|
|
755
|
+
}
|
|
457
756
|
throw err;
|
|
458
757
|
} finally {
|
|
459
758
|
if (timeoutTimer != null) {
|
|
@@ -471,20 +770,29 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
471
770
|
return api.journeyStep(journey, stepName, fn, opts);
|
|
472
771
|
},
|
|
473
772
|
|
|
773
|
+
shouldIgnoreRequest,
|
|
774
|
+
|
|
474
775
|
reportBackendRequest(step, status, durationMs, requestKey) {
|
|
475
|
-
if (!step) {
|
|
776
|
+
if (!step?.journey) {
|
|
476
777
|
return;
|
|
477
778
|
}
|
|
478
779
|
const journey = step.journey;
|
|
479
780
|
if (journey.closed) {
|
|
480
781
|
return;
|
|
481
782
|
}
|
|
482
|
-
|
|
783
|
+
trackAttributedKey(step, requestKey);
|
|
784
|
+
const endpointPolicy = resolveEndpointPolicy(
|
|
785
|
+
journey,
|
|
786
|
+
effectiveSlowRequests(),
|
|
787
|
+
requestKey
|
|
788
|
+
);
|
|
789
|
+
if (endpointPolicy.ignore) {
|
|
483
790
|
return;
|
|
484
791
|
}
|
|
485
792
|
|
|
486
793
|
const failed = status === undefined || status >= 500;
|
|
487
|
-
const
|
|
794
|
+
const threshold = endpointPolicy.slowRequestMs;
|
|
795
|
+
const slow = threshold > 0 && durationMs > threshold;
|
|
488
796
|
if (!failed && !slow) {
|
|
489
797
|
return;
|
|
490
798
|
}
|
|
@@ -497,7 +805,7 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
497
805
|
step.httpStatus = status;
|
|
498
806
|
}
|
|
499
807
|
|
|
500
|
-
api.
|
|
808
|
+
api.failJourney(journey, category);
|
|
501
809
|
},
|
|
502
810
|
|
|
503
811
|
reset(next = {}) {
|
|
@@ -507,13 +815,20 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
507
815
|
}
|
|
508
816
|
journeys.clear();
|
|
509
817
|
inFlight.clear();
|
|
818
|
+
overlay = {};
|
|
819
|
+
sinksConfigured = false;
|
|
510
820
|
sinks = [...(next.sinks ?? [])];
|
|
511
|
-
|
|
512
|
-
defaultMs: next.
|
|
821
|
+
slowRequests = {
|
|
822
|
+
defaultMs: next.slowRequests?.defaultMs ?? DEFAULT_SLOW_REQUESTS.defaultMs,
|
|
513
823
|
endpoints: sortEndpointsByLongestMatch(
|
|
514
|
-
next.
|
|
824
|
+
next.slowRequests?.endpoints ?? DEFAULT_SLOW_REQUESTS.endpoints
|
|
515
825
|
),
|
|
516
826
|
};
|
|
827
|
+
maxSteps =
|
|
828
|
+
next.maxSteps !== undefined ? clampMaxSteps(next.maxSteps) : DEFAULT_MAX_STEPS;
|
|
829
|
+
defaultJourneyIdleMs = finiteTimeoutMs(
|
|
830
|
+
next.defaultJourneyIdleMs ?? DEFAULT_JOURNEY_IDLE_MS
|
|
831
|
+
);
|
|
517
832
|
},
|
|
518
833
|
};
|
|
519
834
|
|
|
@@ -527,10 +842,69 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
527
842
|
*/
|
|
528
843
|
export const moduleRuntime: JourneyRuntime = createJourneyRuntime();
|
|
529
844
|
|
|
845
|
+
const noopEngine: JourneyEngine = {
|
|
846
|
+
failJourney() {},
|
|
847
|
+
exclude() {},
|
|
848
|
+
shouldIgnoreRequest() {
|
|
849
|
+
return false;
|
|
850
|
+
},
|
|
851
|
+
reportBackendRequest() {},
|
|
852
|
+
getHttpAbortedRequestsPolicy() {
|
|
853
|
+
return 'continue';
|
|
854
|
+
},
|
|
855
|
+
getHttpClientErrorRequestsPolicy() {
|
|
856
|
+
return 'continue';
|
|
857
|
+
},
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* True when this bundle can fail/exclude/report the journey: stamped engine, or
|
|
862
|
+
* this runtime's own unstamped object. False for a foreign older-copy journey.
|
|
863
|
+
*/
|
|
864
|
+
export function canRouteHttpToJourney(journey: JourneyState): boolean {
|
|
865
|
+
return (
|
|
866
|
+
journey[JOURNEY_ENGINE] != null || moduleRuntime.getOpenJourney(journey.name) === journey
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* @internal Prefer the creating engine on a journey (MFE `step.stamp()` on a host transport).
|
|
872
|
+
* Falls back to this bundle's module runtime only when this runtime owns the object.
|
|
873
|
+
* Unstamped foreign journeys (older package copies) are a no-op so a same-named
|
|
874
|
+
* host journey is not deleted from this map.
|
|
875
|
+
*/
|
|
876
|
+
export function resolveJourneyEngine(journey: JourneyState): JourneyEngine {
|
|
877
|
+
const stamped = journey[JOURNEY_ENGINE];
|
|
878
|
+
if (stamped) {
|
|
879
|
+
return stamped;
|
|
880
|
+
}
|
|
881
|
+
if (moduleRuntime.getOpenJourney(journey.name) !== journey) {
|
|
882
|
+
return noopEngine;
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
failJourney(reason) {
|
|
886
|
+
moduleRuntime.failJourney(journey, reason);
|
|
887
|
+
},
|
|
888
|
+
exclude() {
|
|
889
|
+
moduleRuntime.excludeJourney(journey);
|
|
890
|
+
},
|
|
891
|
+
shouldIgnoreRequest(key) {
|
|
892
|
+
return moduleRuntime.shouldIgnoreRequest(journey, key);
|
|
893
|
+
},
|
|
894
|
+
reportBackendRequest(step, status, durationMs, requestKey) {
|
|
895
|
+
moduleRuntime.reportBackendRequest(step, status, durationMs, requestKey);
|
|
896
|
+
},
|
|
897
|
+
getHttpAbortedRequestsPolicy() {
|
|
898
|
+
return moduleRuntime.getHttpAbortedRequestsPolicy();
|
|
899
|
+
},
|
|
900
|
+
getHttpClientErrorRequestsPolicy() {
|
|
901
|
+
return moduleRuntime.getHttpClientErrorRequestsPolicy();
|
|
902
|
+
},
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
|
|
530
906
|
/**
|
|
531
|
-
* @internal
|
|
532
|
-
* for compatibility. Not part of the supported public API.
|
|
533
|
-
* Reset the module engine (open journeys, ambient steps, sinks, request thresholds).
|
|
907
|
+
* @internal Reset the module engine (open journeys, ambient steps, sinks, slow-request thresholds).
|
|
534
908
|
* Intended for tests — call in `beforeEach` / `afterEach` instead of manual cleanup.
|
|
535
909
|
*/
|
|
536
910
|
export function resetJourney(options?: JourneyRuntimeOptions): void {
|