@servicetitan/journey 2.0.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 -0
- package/dist/__test-utils__/test-runtime.js +54 -0
- package/dist/__test-utils__/test-runtime.js.map +1 -0
- package/dist/__tests__/core/ambient.test.d.ts.map +1 -0
- package/dist/__tests__/core/api.test.d.ts +2 -0
- package/dist/__tests__/core/api.test.d.ts.map +1 -0
- package/dist/__tests__/core/define-journey.test.d.ts.map +1 -0
- package/dist/__tests__/core/requests.test.d.ts.map +1 -0
- package/dist/__tests__/core/runtime.test.d.ts.map +1 -0
- package/dist/__tests__/core/sanitize.test.d.ts +2 -0
- package/dist/__tests__/core/sanitize.test.d.ts.map +1 -0
- package/dist/__tests__/core/step.test.d.ts.map +1 -0
- package/dist/__tests__/core/timeouts.test.d.ts.map +1 -0
- package/dist/__tests__/global.test.d.ts +3 -0
- package/dist/__tests__/global.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/axios.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/context.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/expose-app-journey.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/fetch.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/jquery.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/request-key.test.d.ts.map +1 -0
- package/dist/__tests__/integrations/scope.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/__tests__/sinks/console.test.d.ts +2 -0
- package/dist/__tests__/sinks/console.test.d.ts.map +1 -0
- package/dist/__tests__/sinks/datadog.test.d.ts.map +1 -0
- package/dist/__tests__/sinks/sinks.test.d.ts.map +1 -0
- package/dist/config.d.ts +61 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +98 -29
- 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 +43 -0
- package/dist/core/api.d.ts.map +1 -0
- package/dist/core/api.js +122 -0
- package/dist/core/api.js.map +1 -0
- package/dist/{endpoint-policy.d.ts → core/endpoint-policy.d.ts} +14 -4
- package/dist/core/endpoint-policy.d.ts.map +1 -0
- package/dist/core/endpoint-policy.js +70 -0
- package/dist/core/endpoint-policy.js.map +1 -0
- package/dist/core/index.d.ts +6 -5
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +4 -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 +95 -17
- package/dist/core/runtime.d.ts.map +1 -1
- package/dist/core/runtime.js +435 -124
- 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 +31 -6
- package/dist/core/step-tag.d.ts.map +1 -1
- package/dist/core/step-tag.js +62 -9
- package/dist/core/step-tag.js.map +1 -1
- package/dist/core/types.d.ts +76 -26
- 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 +145 -0
- package/dist/global.d.ts.map +1 -0
- package/dist/global.js +326 -0
- package/dist/global.js.map +1 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- 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 +6 -4
- 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 +10 -6
- package/dist/integrations/jquery/index.d.ts.map +1 -1
- package/dist/integrations/jquery/index.js +28 -9
- 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 +48 -9
- 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 +11 -0
- package/dist/sinks/console.d.ts.map +1 -0
- package/dist/sinks/console.js +19 -0
- package/dist/sinks/console.js.map +1 -0
- package/dist/sinks/datadog.d.ts +12 -1
- package/dist/sinks/datadog.d.ts.map +1 -1
- package/dist/sinks/datadog.js +42 -25
- package/dist/sinks/datadog.js.map +1 -1
- package/package.json +2 -2
- package/src/{__mocks__ → __test-utils__}/test-runtime.ts +15 -4
- package/src/__tests__/config.test.ts +158 -42
- package/src/__tests__/{ambient.test.ts → core/ambient.test.ts} +23 -16
- package/src/__tests__/{registry.test.ts → core/api.test.ts} +35 -30
- package/src/__tests__/{define-journey.test.ts → core/define-journey.test.ts} +33 -15
- package/src/__tests__/{requests.test.ts → core/requests.test.ts} +61 -39
- package/src/__tests__/{runtime.test.ts → core/runtime.test.ts} +84 -37
- package/src/__tests__/core/sanitize.test.ts +69 -0
- package/src/__tests__/{step.test.ts → core/step.test.ts} +146 -52
- package/src/__tests__/{timeouts.test.ts → core/timeouts.test.ts} +83 -13
- package/src/__tests__/global.test.ts +154 -0
- package/src/__tests__/hardening.test.ts +613 -0
- package/src/__tests__/integrations/axios.test.ts +616 -0
- package/src/__tests__/{context.test.tsx → integrations/context.test.tsx} +1 -1
- package/src/__tests__/{expose-app-journey.test.ts → integrations/expose-app-journey.test.ts} +11 -5
- package/src/__tests__/{fetch.test.ts → integrations/fetch.test.ts} +159 -25
- package/src/__tests__/integrations/host-mfe.test.ts +548 -0
- package/src/__tests__/integrations/jquery.test.ts +539 -0
- package/src/__tests__/{request-key.test.ts → integrations/request-key.test.ts} +1 -1
- package/src/__tests__/integrations/scope.test.tsx +286 -0
- package/src/__tests__/integrations/use-journey-step.test.tsx +43 -0
- package/src/__tests__/sinks/console.test.ts +103 -0
- package/src/__tests__/{datadog.test.ts → sinks/datadog.test.ts} +109 -5
- package/src/__tests__/{sinks.test.ts → sinks/sinks.test.ts} +13 -12
- package/src/config.ts +173 -41
- package/src/core/abort-error.ts +24 -0
- package/src/core/api.ts +159 -0
- package/src/{endpoint-policy.ts → core/endpoint-policy.ts} +46 -4
- package/src/core/index.ts +16 -17
- package/src/core/limits.ts +73 -0
- package/src/core/request-policy.ts +66 -0
- package/src/core/runtime.ts +560 -137
- package/src/core/sanitize.ts +177 -0
- package/src/core/step-tag.ts +90 -11
- package/src/core/types.ts +82 -26
- package/src/global.ts +466 -0
- package/src/index.ts +31 -18
- package/src/integrations/axios.ts +183 -45
- package/src/integrations/fetch.ts +127 -41
- package/src/integrations/http-report.ts +184 -0
- package/src/integrations/jquery/index.ts +37 -18
- package/src/integrations/jquery/instrument.ts +126 -42
- package/src/integrations/react/context.ts +4 -1
- package/src/integrations/react/scope.tsx +61 -10
- 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 +21 -0
- package/src/sinks/datadog.ts +52 -33
- package/dist/__mocks__/test-runtime.d.ts.map +0 -1
- package/dist/__tests__/ambient.test.d.ts.map +0 -1
- package/dist/__tests__/axios.test.d.ts.map +0 -1
- package/dist/__tests__/context.test.d.ts.map +0 -1
- package/dist/__tests__/datadog.test.d.ts.map +0 -1
- package/dist/__tests__/define-journey.test.d.ts.map +0 -1
- package/dist/__tests__/expose-app-journey.test.d.ts.map +0 -1
- package/dist/__tests__/fetch.test.d.ts.map +0 -1
- package/dist/__tests__/jquery.test.d.ts.map +0 -1
- package/dist/__tests__/registry.test.d.ts +0 -2
- package/dist/__tests__/registry.test.d.ts.map +0 -1
- package/dist/__tests__/request-key.test.d.ts.map +0 -1
- package/dist/__tests__/requests.test.d.ts.map +0 -1
- package/dist/__tests__/runtime.test.d.ts.map +0 -1
- package/dist/__tests__/scope.test.d.ts.map +0 -1
- package/dist/__tests__/sinks.test.d.ts.map +0 -1
- package/dist/__tests__/step.test.d.ts.map +0 -1
- package/dist/__tests__/timeouts.test.d.ts.map +0 -1
- package/dist/core/registry.d.ts +0 -50
- package/dist/core/registry.d.ts.map +0 -1
- package/dist/core/registry.js +0 -102
- package/dist/core/registry.js.map +0 -1
- package/dist/endpoint-policy.d.ts.map +0 -1
- package/dist/endpoint-policy.js +0 -39
- package/dist/endpoint-policy.js.map +0 -1
- package/src/__tests__/axios.test.ts +0 -365
- package/src/__tests__/jquery.test.ts +0 -327
- package/src/__tests__/scope.test.tsx +0 -128
- package/src/core/registry.ts +0 -162
- /package/dist/{__mocks__ → __test-utils__}/test-runtime.d.ts +0 -0
- /package/dist/__tests__/{ambient.test.d.ts → core/ambient.test.d.ts} +0 -0
- /package/dist/__tests__/{define-journey.test.d.ts → core/define-journey.test.d.ts} +0 -0
- /package/dist/__tests__/{requests.test.d.ts → core/requests.test.d.ts} +0 -0
- /package/dist/__tests__/{runtime.test.d.ts → core/runtime.test.d.ts} +0 -0
- /package/dist/__tests__/{step.test.d.ts → core/step.test.d.ts} +0 -0
- /package/dist/__tests__/{timeouts.test.d.ts → core/timeouts.test.d.ts} +0 -0
- /package/dist/__tests__/{axios.test.d.ts → integrations/axios.test.d.ts} +0 -0
- /package/dist/__tests__/{context.test.d.ts → integrations/context.test.d.ts} +0 -0
- /package/dist/__tests__/{expose-app-journey.test.d.ts → integrations/expose-app-journey.test.d.ts} +0 -0
- /package/dist/__tests__/{fetch.test.d.ts → integrations/fetch.test.d.ts} +0 -0
- /package/dist/__tests__/{jquery.test.d.ts → integrations/jquery.test.d.ts} +0 -0
- /package/dist/__tests__/{request-key.test.d.ts → integrations/request-key.test.d.ts} +0 -0
- /package/dist/__tests__/{scope.test.d.ts → integrations/scope.test.d.ts} +0 -0
- /package/dist/__tests__/{datadog.test.d.ts → sinks/datadog.test.d.ts} +0 -0
- /package/dist/__tests__/{sinks.test.d.ts → sinks/sinks.test.d.ts} +0 -0
package/src/core/runtime.ts
CHANGED
|
@@ -1,70 +1,171 @@
|
|
|
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
|
|
5
|
-
} from '
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
type SlowRequestConfig,
|
|
13
|
+
} from './endpoint-policy';
|
|
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 {
|
|
39
|
-
/**
|
|
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;
|
|
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;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @internal Read-only snapshot for DevTools (`__stJourney.getDebugState`; empty unless debugging). */
|
|
80
|
+
export interface JourneyRuntimeDebugSnapshot {
|
|
81
|
+
id: string;
|
|
82
|
+
openJourneys: readonly {
|
|
83
|
+
name: string;
|
|
84
|
+
team: string;
|
|
85
|
+
group: string;
|
|
86
|
+
service: string;
|
|
87
|
+
verdict: Outcome;
|
|
88
|
+
reason: string | null;
|
|
89
|
+
tags: Record<string, TagValue>;
|
|
90
|
+
stepsInFlight: readonly string[];
|
|
91
|
+
steps: readonly {
|
|
92
|
+
name: string;
|
|
93
|
+
outcome: 'good' | 'bad';
|
|
94
|
+
durationMs: number;
|
|
95
|
+
reason?: string;
|
|
96
|
+
attributedRequestKeys?: readonly string[];
|
|
97
|
+
}[];
|
|
98
|
+
}[];
|
|
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
|
+
});
|
|
42
122
|
}
|
|
43
123
|
|
|
44
124
|
/**
|
|
45
125
|
* @internal Private — not part of the public package API.
|
|
46
126
|
* Isolated journey engine (open journeys, ambient steps, sinks, request thresholds).
|
|
47
127
|
* App code should keep using free helpers (`defineJourney`, `journeyStep`, …) — they
|
|
48
|
-
* bind to the
|
|
128
|
+
* bind to the module runtime. Prefer `resetJourney()` in tests.
|
|
49
129
|
*/
|
|
50
130
|
export interface JourneyRuntime {
|
|
51
|
-
|
|
52
|
-
|
|
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;
|
|
53
139
|
|
|
54
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;
|
|
55
143
|
addSink(sink: JourneySink): void;
|
|
56
144
|
emit(payload: JourneyEvent): void;
|
|
57
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
|
+
|
|
58
156
|
getOpenJourney(name: string): JourneyState | undefined;
|
|
59
|
-
|
|
157
|
+
/** @internal DevTools snapshot of open journeys / ambient step. */
|
|
158
|
+
getDebugSnapshot(): JourneyRuntimeDebugSnapshot;
|
|
159
|
+
firstOpenJourneyName(names: readonly string[]): string | null;
|
|
60
160
|
setJourneyTags(name: string | null, tags: Record<string, TagValue>): void;
|
|
61
|
-
startJourney(
|
|
62
|
-
|
|
63
|
-
failJourney(name: string, reason: string): void;
|
|
161
|
+
startJourney(config: JourneyDef): void;
|
|
162
|
+
failJourney(journey: JourneyState, reason: string): void;
|
|
64
163
|
completeJourney(name: string | null, tags?: Record<string, TagValue>): void;
|
|
65
|
-
|
|
164
|
+
completeJourneyState(journey: JourneyState, tags?: Record<string, TagValue>): void;
|
|
165
|
+
excludeJourney(journey: JourneyState): void;
|
|
166
|
+
updateOpenJourney(config: JourneyDef): void;
|
|
66
167
|
|
|
67
|
-
activeJourneyStep():
|
|
168
|
+
activeJourneyStep(): StepRecord | null;
|
|
68
169
|
journeyStep<T>(
|
|
69
170
|
journey: JourneyStepTarget,
|
|
70
171
|
stepName: string,
|
|
@@ -79,30 +180,71 @@ export interface JourneyRuntime {
|
|
|
79
180
|
): Promise<T>;
|
|
80
181
|
|
|
81
182
|
reportBackendRequest(
|
|
82
|
-
step:
|
|
183
|
+
step: StepRecord | undefined,
|
|
83
184
|
status: number | undefined,
|
|
84
185
|
durationMs: number,
|
|
85
186
|
requestKey: string
|
|
86
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;
|
|
87
190
|
|
|
88
191
|
/** Close open journeys, clear ambient steps, restore default timeouts/sinks. */
|
|
89
192
|
reset(options?: JourneyRuntimeOptions): void;
|
|
90
193
|
}
|
|
91
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
|
+
|
|
92
222
|
/**
|
|
93
223
|
* @internal Private — not part of the public package API.
|
|
94
224
|
* Build an isolated engine. Rarely needed — apps use the default via free helpers.
|
|
95
225
|
*/
|
|
96
226
|
export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): JourneyRuntime {
|
|
227
|
+
let id = typeof options.id === 'string' && options.id !== '' ? options.id : '';
|
|
97
228
|
const journeys = new Map<string, JourneyState>();
|
|
98
229
|
const inFlight = new Set<StepRecord>();
|
|
99
230
|
let sinks: JourneySink[] = [...(options.sinks ?? [])];
|
|
100
|
-
let
|
|
101
|
-
|
|
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,
|
|
102
240
|
endpoints: sortEndpointsByLongestMatch(
|
|
103
|
-
options.
|
|
241
|
+
options.slowRequests?.endpoints ?? DEFAULT_SLOW_REQUESTS.endpoints
|
|
104
242
|
),
|
|
105
243
|
};
|
|
244
|
+
let maxSteps = clampMaxSteps(options.maxSteps ?? DEFAULT_MAX_STEPS);
|
|
245
|
+
let defaultJourneyIdleMs = finiteTimeoutMs(
|
|
246
|
+
options.defaultJourneyIdleMs ?? DEFAULT_JOURNEY_IDLE_MS
|
|
247
|
+
);
|
|
106
248
|
|
|
107
249
|
function getInFlightSteps(journey: JourneyState): StepRecord[] {
|
|
108
250
|
const out: StepRecord[] = [];
|
|
@@ -122,6 +264,18 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
122
264
|
inFlight.delete(step);
|
|
123
265
|
}
|
|
124
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
|
+
|
|
125
279
|
function clearCountdown(journey: JourneyState): void {
|
|
126
280
|
if (journey.timer != null) {
|
|
127
281
|
clearTimeout(journey.timer);
|
|
@@ -130,9 +284,10 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
130
284
|
}
|
|
131
285
|
|
|
132
286
|
function emit(payload: JourneyEvent): void {
|
|
287
|
+
const safePayload = sanitizeJourneyEvent(payload);
|
|
133
288
|
for (const sink of sinks) {
|
|
134
289
|
try {
|
|
135
|
-
sink(
|
|
290
|
+
sink(safePayload);
|
|
136
291
|
} catch {
|
|
137
292
|
// One sinking throw must not block the remaining sinks.
|
|
138
293
|
}
|
|
@@ -144,19 +299,16 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
144
299
|
return;
|
|
145
300
|
}
|
|
146
301
|
journey.closed = true;
|
|
147
|
-
journeys.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
for (const step of getInFlightSteps(journey)) {
|
|
151
|
-
if (step.durationMs === 0) {
|
|
152
|
-
step.durationMs = Math.round(globalThis.performance.now() - step.startedAt);
|
|
153
|
-
}
|
|
302
|
+
if (journeys.get(journey.name) === journey) {
|
|
303
|
+
journeys.delete(journey.name);
|
|
154
304
|
}
|
|
305
|
+
clearCountdown(journey);
|
|
306
|
+
releaseInFlightSteps(journey);
|
|
155
307
|
|
|
156
308
|
let finalReason = journey.reason ?? reason;
|
|
157
309
|
let finalOutcome = outcome;
|
|
158
310
|
const durationMs = Math.round(globalThis.performance.now() - journey.startedAt);
|
|
159
|
-
if (journey.timeoutMs > 0 && durationMs > journey.timeoutMs) {
|
|
311
|
+
if (journey.timeoutExplicit && journey.timeoutMs > 0 && durationMs > journey.timeoutMs) {
|
|
160
312
|
finalOutcome = 'bad';
|
|
161
313
|
finalReason = finalReason ?? 'journey-timeout';
|
|
162
314
|
}
|
|
@@ -168,7 +320,7 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
168
320
|
outcome: s.outcome,
|
|
169
321
|
...(s.reason ? { reason: s.reason } : {}),
|
|
170
322
|
...(s.httpStatus !== undefined ? { httpStatus: s.httpStatus } : {}),
|
|
171
|
-
...(Object.keys(s.attributes).length ? { attributes: s.attributes } : {}),
|
|
323
|
+
...(Object.keys(s.attributes).length ? { attributes: { ...s.attributes } } : {}),
|
|
172
324
|
}));
|
|
173
325
|
|
|
174
326
|
emit({
|
|
@@ -178,48 +330,47 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
178
330
|
group: journey.group,
|
|
179
331
|
service: journey.service,
|
|
180
332
|
outcome: finalOutcome,
|
|
181
|
-
...(
|
|
333
|
+
...(finalReason ? { reason: finalReason } : {}),
|
|
182
334
|
durationMs,
|
|
183
335
|
steps,
|
|
184
336
|
...(journey.expected ? { expected: journey.expected } : {}),
|
|
185
|
-
...(Object.keys(journey.tags).length ? { tags: journey.tags } : {}),
|
|
337
|
+
...(Object.keys(journey.tags).length ? { tags: { ...journey.tags } } : {}),
|
|
186
338
|
},
|
|
187
339
|
});
|
|
188
340
|
}
|
|
189
341
|
|
|
190
|
-
function armCountdown(journey: JourneyState): void {
|
|
191
|
-
if (
|
|
342
|
+
function armCountdown(journey: JourneyState, delayMs: number = journey.timeoutMs): void {
|
|
343
|
+
if (delayMs <= 0) {
|
|
192
344
|
return;
|
|
193
345
|
}
|
|
194
346
|
journey.timer = setTimeout(() => {
|
|
195
|
-
|
|
196
|
-
|
|
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);
|
|
197
353
|
}
|
|
198
354
|
|
|
199
|
-
function
|
|
200
|
-
|
|
201
|
-
if (own?.timeoutMs) {
|
|
202
|
-
return own.timeoutMs;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const app = longestEndpointMatch(requestTimeouts.endpoints, key);
|
|
206
|
-
if (app?.timeoutMs) {
|
|
207
|
-
return app.timeoutMs;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (journey.requestTimeoutMs != null) {
|
|
211
|
-
return journey.requestTimeoutMs;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return requestTimeouts.defaultMs;
|
|
355
|
+
function effectiveSlowRequests(): SlowRequestConfig {
|
|
356
|
+
return overlay.slowRequests ?? getSharedSlowRequests() ?? slowRequests;
|
|
215
357
|
}
|
|
216
358
|
|
|
217
359
|
function shouldIgnoreRequest(journey: JourneyState, key: string): boolean {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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);
|
|
221
373
|
}
|
|
222
|
-
return longestEndpointMatch(requestTimeouts.endpoints, key)?.ignore === true;
|
|
223
374
|
}
|
|
224
375
|
|
|
225
376
|
function resolveJourneyName(journey: JourneyStepTarget): string | null {
|
|
@@ -227,58 +378,178 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
227
378
|
return null;
|
|
228
379
|
}
|
|
229
380
|
if (typeof journey === 'string' || !Array.isArray(journey)) {
|
|
230
|
-
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);
|
|
231
417
|
}
|
|
232
|
-
return api.firstOpenJourney(journey.map(toName));
|
|
233
418
|
}
|
|
234
419
|
|
|
235
420
|
function createStepHandle(journeyName: string, step: StepRecord): StepHandle {
|
|
236
421
|
return {
|
|
237
|
-
|
|
422
|
+
journeyName,
|
|
238
423
|
setAttribute(key, value) {
|
|
239
|
-
step.attributes
|
|
424
|
+
setSafeAttribute(step.attributes, key, value);
|
|
240
425
|
},
|
|
241
426
|
setTags(tags) {
|
|
242
|
-
|
|
427
|
+
if (step.journey.closed) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
assignRecord(step.journey.tags, tags, MAX_TAG_KEYS);
|
|
243
431
|
},
|
|
244
|
-
|
|
245
|
-
api.
|
|
432
|
+
completeJourney(tags) {
|
|
433
|
+
api.completeJourneyState(step.journey, tags);
|
|
246
434
|
},
|
|
247
|
-
|
|
248
|
-
api.failJourney(
|
|
435
|
+
failJourney(reason) {
|
|
436
|
+
api.failJourney(step.journey, reason);
|
|
249
437
|
},
|
|
250
|
-
// Box in
|
|
251
|
-
|
|
438
|
+
// Box in JourneyStepStamp — raw StepRecord is circular and blows jQuery/axios deep-merge.
|
|
439
|
+
stamp: scoreOrOpts => new JourneyStepStamp(step, resolveStampOpts(scoreOrOpts)),
|
|
252
440
|
};
|
|
253
441
|
}
|
|
254
442
|
|
|
255
443
|
const api: JourneyRuntime = {
|
|
256
|
-
|
|
257
|
-
return
|
|
444
|
+
get id() {
|
|
445
|
+
return id;
|
|
446
|
+
},
|
|
447
|
+
set id(next: string) {
|
|
448
|
+
id = next;
|
|
449
|
+
},
|
|
450
|
+
getSlowRequests() {
|
|
451
|
+
return effectiveSlowRequests();
|
|
258
452
|
},
|
|
259
453
|
|
|
260
|
-
|
|
261
|
-
|
|
454
|
+
setSlowRequests(next) {
|
|
455
|
+
slowRequests = {
|
|
262
456
|
...next,
|
|
263
457
|
endpoints: sortEndpointsByLongestMatch(next.endpoints),
|
|
264
458
|
};
|
|
265
459
|
},
|
|
266
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
|
+
|
|
267
477
|
setSinks(next) {
|
|
268
478
|
sinks = [...next];
|
|
269
479
|
},
|
|
270
480
|
|
|
481
|
+
trySetConfiguredSinks(next) {
|
|
482
|
+
if (sinksConfigured) {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
sinks = [...next];
|
|
486
|
+
sinksConfigured = true;
|
|
487
|
+
return true;
|
|
488
|
+
},
|
|
489
|
+
|
|
271
490
|
addSink(sink) {
|
|
272
491
|
sinks.push(sink);
|
|
273
492
|
},
|
|
274
493
|
|
|
275
494
|
emit,
|
|
276
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
|
+
|
|
277
515
|
getOpenJourney(name) {
|
|
278
516
|
return journeys.get(name);
|
|
279
517
|
},
|
|
280
518
|
|
|
281
|
-
|
|
519
|
+
getDebugSnapshot() {
|
|
520
|
+
const active = api.activeJourneyStep();
|
|
521
|
+
return {
|
|
522
|
+
id: api.id,
|
|
523
|
+
openJourneys: [...journeys.values()].map(journey => ({
|
|
524
|
+
name: journey.name,
|
|
525
|
+
team: journey.team,
|
|
526
|
+
group: journey.group,
|
|
527
|
+
service: journey.service,
|
|
528
|
+
verdict: journey.verdict,
|
|
529
|
+
reason: journey.reason,
|
|
530
|
+
tags: { ...journey.tags },
|
|
531
|
+
stepsInFlight: getInFlightSteps(journey).map(step => step.name),
|
|
532
|
+
steps: journey.steps.map(step => ({
|
|
533
|
+
name: step.name,
|
|
534
|
+
outcome: step.outcome,
|
|
535
|
+
durationMs: step.durationMs,
|
|
536
|
+
...(step.reason ? { reason: step.reason } : {}),
|
|
537
|
+
...(step.attributedRequestKeys?.length
|
|
538
|
+
? { attributedRequestKeys: [...step.attributedRequestKeys] }
|
|
539
|
+
: {}),
|
|
540
|
+
})),
|
|
541
|
+
})),
|
|
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
|
+
},
|
|
549
|
+
};
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
firstOpenJourneyName(names) {
|
|
282
553
|
for (const name of names) {
|
|
283
554
|
if (journeys.has(name)) {
|
|
284
555
|
return name;
|
|
@@ -290,39 +561,51 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
290
561
|
setJourneyTags(name, tags) {
|
|
291
562
|
const journey = name ? journeys.get(name) : undefined;
|
|
292
563
|
if (journey) {
|
|
293
|
-
|
|
564
|
+
assignRecord(journey.tags, tags, MAX_TAG_KEYS);
|
|
294
565
|
}
|
|
295
566
|
},
|
|
296
567
|
|
|
297
|
-
startJourney(
|
|
298
|
-
const existing = journeys.get(
|
|
568
|
+
startJourney(config) {
|
|
569
|
+
const existing = journeys.get(config.name);
|
|
299
570
|
if (existing) {
|
|
300
571
|
finish(existing, existing.verdict === 'bad' ? 'bad' : 'excluded');
|
|
301
572
|
}
|
|
302
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);
|
|
303
578
|
const journey: JourneyState = {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
579
|
+
config: snapshot,
|
|
580
|
+
name: snapshot.name,
|
|
581
|
+
team: snapshot.team,
|
|
582
|
+
group: snapshot.group,
|
|
583
|
+
service: snapshot.service,
|
|
308
584
|
verdict: 'good',
|
|
309
585
|
startedAt: globalThis.performance.now(),
|
|
310
|
-
timeoutMs:
|
|
311
|
-
|
|
586
|
+
timeoutMs: timeout.ms,
|
|
587
|
+
timeoutExplicit: timeout.explicit,
|
|
588
|
+
stepTimeoutMs:
|
|
589
|
+
snapshot.stepTimeoutMs !== undefined
|
|
590
|
+
? finiteTimeoutMs(snapshot.stepTimeoutMs)
|
|
591
|
+
: undefined,
|
|
312
592
|
reason: null,
|
|
313
593
|
steps: [],
|
|
314
|
-
expected:
|
|
315
|
-
tags
|
|
316
|
-
|
|
317
|
-
endpoints:
|
|
594
|
+
expected: snapshot.expected ? [...snapshot.expected] : null,
|
|
595
|
+
tags,
|
|
596
|
+
slowRequestMs: finiteSlowRequestMs(snapshot.slowRequestMs),
|
|
597
|
+
endpoints: snapshot.endpoints,
|
|
318
598
|
timer: null,
|
|
319
599
|
closed: false,
|
|
600
|
+
maxSteps:
|
|
601
|
+
snapshot.maxSteps !== undefined ? clampMaxSteps(snapshot.maxSteps) : undefined,
|
|
320
602
|
};
|
|
321
|
-
journeys.set(
|
|
603
|
+
journeys.set(config.name, journey);
|
|
604
|
+
journey[JOURNEY_ENGINE] = bindJourneyEngine(api, journey, shouldIgnoreRequest);
|
|
322
605
|
armCountdown(journey);
|
|
323
606
|
},
|
|
324
607
|
|
|
325
|
-
|
|
608
|
+
failJourney(journey, reason) {
|
|
326
609
|
if (journey.closed) {
|
|
327
610
|
return;
|
|
328
611
|
}
|
|
@@ -331,34 +614,81 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
331
614
|
finish(journey, 'bad');
|
|
332
615
|
},
|
|
333
616
|
|
|
334
|
-
|
|
617
|
+
completeJourney(name, tags) {
|
|
618
|
+
if (!name) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
335
621
|
const journey = journeys.get(name);
|
|
336
622
|
if (journey) {
|
|
337
|
-
api.
|
|
623
|
+
api.completeJourneyState(journey, tags);
|
|
338
624
|
}
|
|
339
625
|
},
|
|
340
626
|
|
|
341
|
-
|
|
342
|
-
if (
|
|
627
|
+
completeJourneyState(journey, tags) {
|
|
628
|
+
if (journey.closed) {
|
|
343
629
|
return;
|
|
344
630
|
}
|
|
345
631
|
if (tags) {
|
|
346
|
-
|
|
632
|
+
assignRecord(journey.tags, tags, MAX_TAG_KEYS);
|
|
347
633
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
634
|
+
finish(journey, journey.verdict === 'bad' ? 'bad' : 'good');
|
|
635
|
+
},
|
|
636
|
+
|
|
637
|
+
excludeJourney(journey) {
|
|
638
|
+
if (journey.closed) {
|
|
639
|
+
return;
|
|
351
640
|
}
|
|
641
|
+
finish(journey, journey.verdict === 'bad' ? 'bad' : 'excluded');
|
|
352
642
|
},
|
|
353
643
|
|
|
354
|
-
|
|
355
|
-
const journey = journeys.get(name);
|
|
356
|
-
if (journey) {
|
|
357
|
-
|
|
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);
|
|
358
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);
|
|
359
686
|
},
|
|
360
687
|
|
|
361
688
|
activeJourneyStep() {
|
|
689
|
+
if (!api.getAutoAttributeRequests()) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
362
692
|
if (inFlight.size !== 1) {
|
|
363
693
|
return null;
|
|
364
694
|
}
|
|
@@ -366,9 +696,14 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
366
696
|
},
|
|
367
697
|
|
|
368
698
|
async journeyStep(journey, stepName, fn, opts) {
|
|
699
|
+
maybeAutoStart(journey);
|
|
369
700
|
const journeyName = resolveJourneyName(journey);
|
|
370
701
|
const open = journeyName ? journeys.get(journeyName) : undefined;
|
|
371
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
|
+
}
|
|
372
707
|
return fn(noopStep);
|
|
373
708
|
}
|
|
374
709
|
|
|
@@ -377,10 +712,15 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
377
712
|
startedAt: globalThis.performance.now(),
|
|
378
713
|
durationMs: 0,
|
|
379
714
|
outcome: 'good',
|
|
380
|
-
attributes:
|
|
715
|
+
attributes: emptyRecord(),
|
|
381
716
|
journey: open,
|
|
382
717
|
};
|
|
383
|
-
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
|
+
}
|
|
384
724
|
|
|
385
725
|
const handle = createStepHandle(journeyName, step);
|
|
386
726
|
|
|
@@ -395,7 +735,7 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
395
735
|
step.reason ??= 'step-timeout';
|
|
396
736
|
step.attributes.timeout_ms ??= stepTimeoutMs;
|
|
397
737
|
step.durationMs = Math.round(globalThis.performance.now() - step.startedAt);
|
|
398
|
-
api.
|
|
738
|
+
api.failJourney(open, 'step-timeout');
|
|
399
739
|
}, stepTimeoutMs);
|
|
400
740
|
}
|
|
401
741
|
|
|
@@ -403,11 +743,16 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
403
743
|
try {
|
|
404
744
|
return await fn(handle);
|
|
405
745
|
} catch (err) {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
+
}
|
|
411
756
|
throw err;
|
|
412
757
|
} finally {
|
|
413
758
|
if (timeoutTimer != null) {
|
|
@@ -425,20 +770,29 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
425
770
|
return api.journeyStep(journey, stepName, fn, opts);
|
|
426
771
|
},
|
|
427
772
|
|
|
773
|
+
shouldIgnoreRequest,
|
|
774
|
+
|
|
428
775
|
reportBackendRequest(step, status, durationMs, requestKey) {
|
|
429
|
-
if (!step) {
|
|
776
|
+
if (!step?.journey) {
|
|
430
777
|
return;
|
|
431
778
|
}
|
|
432
779
|
const journey = step.journey;
|
|
433
780
|
if (journey.closed) {
|
|
434
781
|
return;
|
|
435
782
|
}
|
|
436
|
-
|
|
783
|
+
trackAttributedKey(step, requestKey);
|
|
784
|
+
const endpointPolicy = resolveEndpointPolicy(
|
|
785
|
+
journey,
|
|
786
|
+
effectiveSlowRequests(),
|
|
787
|
+
requestKey
|
|
788
|
+
);
|
|
789
|
+
if (endpointPolicy.ignore) {
|
|
437
790
|
return;
|
|
438
791
|
}
|
|
439
792
|
|
|
440
793
|
const failed = status === undefined || status >= 500;
|
|
441
|
-
const
|
|
794
|
+
const threshold = endpointPolicy.slowRequestMs;
|
|
795
|
+
const slow = threshold > 0 && durationMs > threshold;
|
|
442
796
|
if (!failed && !slow) {
|
|
443
797
|
return;
|
|
444
798
|
}
|
|
@@ -451,7 +805,7 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
451
805
|
step.httpStatus = status;
|
|
452
806
|
}
|
|
453
807
|
|
|
454
|
-
api.
|
|
808
|
+
api.failJourney(journey, category);
|
|
455
809
|
},
|
|
456
810
|
|
|
457
811
|
reset(next = {}) {
|
|
@@ -461,13 +815,20 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
461
815
|
}
|
|
462
816
|
journeys.clear();
|
|
463
817
|
inFlight.clear();
|
|
818
|
+
overlay = {};
|
|
819
|
+
sinksConfigured = false;
|
|
464
820
|
sinks = [...(next.sinks ?? [])];
|
|
465
|
-
|
|
466
|
-
defaultMs: next.
|
|
821
|
+
slowRequests = {
|
|
822
|
+
defaultMs: next.slowRequests?.defaultMs ?? DEFAULT_SLOW_REQUESTS.defaultMs,
|
|
467
823
|
endpoints: sortEndpointsByLongestMatch(
|
|
468
|
-
next.
|
|
824
|
+
next.slowRequests?.endpoints ?? DEFAULT_SLOW_REQUESTS.endpoints
|
|
469
825
|
),
|
|
470
826
|
};
|
|
827
|
+
maxSteps =
|
|
828
|
+
next.maxSteps !== undefined ? clampMaxSteps(next.maxSteps) : DEFAULT_MAX_STEPS;
|
|
829
|
+
defaultJourneyIdleMs = finiteTimeoutMs(
|
|
830
|
+
next.defaultJourneyIdleMs ?? DEFAULT_JOURNEY_IDLE_MS
|
|
831
|
+
);
|
|
471
832
|
},
|
|
472
833
|
};
|
|
473
834
|
|
|
@@ -476,14 +837,76 @@ export function createJourneyRuntime(options: JourneyRuntimeOptions = {}): Journ
|
|
|
476
837
|
|
|
477
838
|
/**
|
|
478
839
|
* @internal Private — not part of the public package API.
|
|
479
|
-
*
|
|
840
|
+
* Module-scoped engine used by free helpers. App code does not need to touch this.
|
|
841
|
+
* Each host/MFE bundle gets its own copy when the package is duplicated.
|
|
480
842
|
*/
|
|
481
|
-
export const
|
|
843
|
+
export const moduleRuntime: JourneyRuntime = createJourneyRuntime();
|
|
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
|
+
}
|
|
482
905
|
|
|
483
906
|
/**
|
|
484
|
-
* Reset the
|
|
907
|
+
* @internal Reset the module engine (open journeys, ambient steps, sinks, slow-request thresholds).
|
|
485
908
|
* Intended for tests — call in `beforeEach` / `afterEach` instead of manual cleanup.
|
|
486
909
|
*/
|
|
487
910
|
export function resetJourney(options?: JourneyRuntimeOptions): void {
|
|
488
|
-
|
|
911
|
+
moduleRuntime.reset(options);
|
|
489
912
|
}
|