From 3f76e73c67b144be2ad6f5c3f26bd2405aebb168 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 9 Jul 2026 13:36:58 +0200 Subject: [PATCH 1/2] feat(js): Add `_BASE` attribute key exports for dynamic attribute keys --- .../sentry-conventions/src/attributes.ts | 80 +++++++++++++++++++ scripts/generate_attributes.ts | 17 ++++ 2 files changed, 97 insertions(+) diff --git a/javascript/sentry-conventions/src/attributes.ts b/javascript/sentry-conventions/src/attributes.ts index eb1fc62c..f5dc1501 100644 --- a/javascript/sentry-conventions/src/attributes.ts +++ b/javascript/sentry-conventions/src/attributes.ts @@ -2120,6 +2120,11 @@ export type BROWSER_WEB_VITAL_CLS_REPORT_EVENT_TYPE = string; */ export const BROWSER_WEB_VITAL_CLS_SOURCE_KEY = 'browser.web_vital.cls.source.'; +/** + * Base key for {@link BROWSER_WEB_VITAL_CLS_SOURCE_KEY}. Use with a dynamic suffix, e.g. `${BROWSER_WEB_VITAL_CLS_SOURCE_KEY_BASE}.${key}`. + */ +export const BROWSER_WEB_VITAL_CLS_SOURCE_KEY_BASE = 'browser.web_vital.cls.source'; + /** * Type for {@link BROWSER_WEB_VITAL_CLS_SOURCE_KEY} browser.web_vital.cls.source. */ @@ -3188,6 +3193,11 @@ export type CLS_TYPE = number; */ export const CLS_SOURCE_KEY = 'cls.source.'; +/** + * Base key for {@link CLS_SOURCE_KEY}. Use with a dynamic suffix, e.g. `${CLS_SOURCE_KEY_BASE}.${key}`. + */ +export const CLS_SOURCE_KEY_BASE = 'cls.source'; + /** * Type for {@link CLS_SOURCE_KEY} cls.source. */ @@ -3682,6 +3692,11 @@ export type DB_OPERATION_NAME_TYPE = string; */ export const DB_QUERY_PARAMETER_KEY = 'db.query.parameter.'; +/** + * Base key for {@link DB_QUERY_PARAMETER_KEY}. Use with a dynamic suffix, e.g. `${DB_QUERY_PARAMETER_KEY_BASE}.${key}`. + */ +export const DB_QUERY_PARAMETER_KEY_BASE = 'db.query.parameter'; + /** * Type for {@link DB_QUERY_PARAMETER_KEY} db.query.parameter. */ @@ -5232,6 +5247,11 @@ export type FCP_TYPE = number; */ export const FLAG_EVALUATION_KEY = 'flag.evaluation.'; +/** + * Base key for {@link FLAG_EVALUATION_KEY}. Use with a dynamic suffix, e.g. `${FLAG_EVALUATION_KEY_BASE}.${key}`. + */ +export const FLAG_EVALUATION_KEY_BASE = 'flag.evaluation'; + /** * Type for {@link FLAG_EVALUATION_KEY} flag.evaluation. */ @@ -7431,6 +7451,11 @@ export type HTTP_REQUEST_FETCH_START_TYPE = number; */ export const HTTP_REQUEST_HEADER_KEY = 'http.request.header.'; +/** + * Base key for {@link HTTP_REQUEST_HEADER_KEY}. Use with a dynamic suffix, e.g. `${HTTP_REQUEST_HEADER_KEY_BASE}.${key}`. + */ +export const HTTP_REQUEST_HEADER_KEY_BASE = 'http.request.header'; + /** * Type for {@link HTTP_REQUEST_HEADER_KEY} http.request.header. */ @@ -7783,6 +7808,11 @@ export type HTTP_RESPONSE_HEADER_CONTENT_LENGTH_TYPE = string; */ export const HTTP_RESPONSE_HEADER_KEY = 'http.response.header.'; +/** + * Base key for {@link HTTP_RESPONSE_HEADER_KEY}. Use with a dynamic suffix, e.g. `${HTTP_RESPONSE_HEADER_KEY_BASE}.${key}`. + */ +export const HTTP_RESPONSE_HEADER_KEY_BASE = 'http.response.header'; + /** * Type for {@link HTTP_RESPONSE_HEADER_KEY} http.response.header. */ @@ -8952,6 +8982,11 @@ export type MCP_PROTOCOL_VERSION_TYPE = string; */ export const MCP_REQUEST_ARGUMENT_KEY = 'mcp.request.argument.'; +/** + * Base key for {@link MCP_REQUEST_ARGUMENT_KEY}. Use with a dynamic suffix, e.g. `${MCP_REQUEST_ARGUMENT_KEY_BASE}.${key}`. + */ +export const MCP_REQUEST_ARGUMENT_KEY_BASE = 'mcp.request.argument'; + /** * Type for {@link MCP_REQUEST_ARGUMENT_KEY} mcp.request.argument. */ @@ -9285,6 +9320,11 @@ export type MCP_TRANSPORT_TYPE = string; */ export const MDC_KEY = 'mdc.'; +/** + * Base key for {@link MDC_KEY}. Use with a dynamic suffix, e.g. `${MDC_KEY_BASE}.${key}`. + */ +export const MDC_KEY_BASE = 'mdc'; + /** * Type for {@link MDC_KEY} mdc. */ @@ -10676,6 +10716,11 @@ export type OTEL_STATUS_DESCRIPTION_TYPE = string; */ export const PARAMS_KEY = 'params.'; +/** + * Base key for {@link PARAMS_KEY}. Use with a dynamic suffix, e.g. `${PARAMS_KEY_BASE}.${key}`. + */ +export const PARAMS_KEY_BASE = 'params'; + /** * Type for {@link PARAMS_KEY} params. */ @@ -10943,6 +10988,11 @@ export type PROCESS_RUNTIME_VERSION_TYPE = string; */ export const QUERY_KEY = 'query.'; +/** + * Base key for {@link QUERY_KEY}. Use with a dynamic suffix, e.g. `${QUERY_KEY_BASE}.${key}`. + */ +export const QUERY_KEY_BASE = 'query'; + /** * Type for {@link QUERY_KEY} query. */ @@ -11011,6 +11061,11 @@ export type RELEASE_TYPE = string; */ export const REMIX_ACTION_FORM_DATA_KEY = 'remix.action_form_data.'; +/** + * Base key for {@link REMIX_ACTION_FORM_DATA_KEY}. Use with a dynamic suffix, e.g. `${REMIX_ACTION_FORM_DATA_KEY_BASE}.${key}`. + */ +export const REMIX_ACTION_FORM_DATA_KEY_BASE = 'remix.action_form_data'; + /** * Type for {@link REMIX_ACTION_FORM_DATA_KEY} remix.action_form_data. */ @@ -11325,6 +11380,11 @@ export type RUNTIME_VERSION_TYPE = string; */ export const SCORE_KEY = 'score.'; +/** + * Base key for {@link SCORE_KEY}. Use with a dynamic suffix, e.g. `${SCORE_KEY_BASE}.${key}`. + */ +export const SCORE_KEY_BASE = 'score'; + /** * Type for {@link SCORE_KEY} score. */ @@ -11348,6 +11408,11 @@ export type SCORE_KEY_TYPE = number; */ export const SCORE_RATIO_KEY = 'score.ratio.'; +/** + * Base key for {@link SCORE_RATIO_KEY}. Use with a dynamic suffix, e.g. `${SCORE_RATIO_KEY_BASE}.${key}`. + */ +export const SCORE_RATIO_KEY_BASE = 'score.ratio'; + /** * Type for {@link SCORE_RATIO_KEY} score.ratio. */ @@ -11390,6 +11455,11 @@ export type SCORE_TOTAL_TYPE = number; */ export const SCORE_WEIGHT_KEY = 'score.weight.'; +/** + * Base key for {@link SCORE_WEIGHT_KEY}. Use with a dynamic suffix, e.g. `${SCORE_WEIGHT_KEY_BASE}.${key}`. + */ +export const SCORE_WEIGHT_KEY_BASE = 'score.weight'; + /** * Type for {@link SCORE_WEIGHT_KEY} score.weight. */ @@ -12030,6 +12100,11 @@ export type SENTRY_MOBILE_TYPE = boolean; */ export const SENTRY_MODULE_KEY = 'sentry.module.'; +/** + * Base key for {@link SENTRY_MODULE_KEY}. Use with a dynamic suffix, e.g. `${SENTRY_MODULE_KEY_BASE}.${key}`. + */ +export const SENTRY_MODULE_KEY_BASE = 'sentry.module'; + /** * Type for {@link SENTRY_MODULE_KEY} sentry.module. */ @@ -13726,6 +13801,11 @@ export type URL_PATH_TYPE = string; */ export const URL_PATH_PARAMETER_KEY = 'url.path.parameter.'; +/** + * Base key for {@link URL_PATH_PARAMETER_KEY}. Use with a dynamic suffix, e.g. `${URL_PATH_PARAMETER_KEY_BASE}.${key}`. + */ +export const URL_PATH_PARAMETER_KEY_BASE = 'url.path.parameter'; + /** * Type for {@link URL_PATH_PARAMETER_KEY} url.path.parameter. */ diff --git a/scripts/generate_attributes.ts b/scripts/generate_attributes.ts index 8c1d3d82..2b833b09 100644 --- a/scripts/generate_attributes.ts +++ b/scripts/generate_attributes.ts @@ -175,6 +175,15 @@ function writeToJs(attributesDir: string, attributeFiles: string[]) { individualConstants += ' */\n'; individualConstants += `export const ${constantName} = '${key}';\n\n`; + if (has_dynamic_suffix) { + const keyBase = getDynamicSuffixBase(key); + const constantNameBase = `${constantName}_BASE`; + individualConstants += '/**\n'; + individualConstants += ` * Base key for {@link ${constantName}}. Use with a dynamic suffix, e.g. \`\${${constantNameBase}}.\${key}\`.\n`; + individualConstants += ' */\n'; + individualConstants += `export const ${constantNameBase} = '${keyBase}';\n\n`; + } + // Generate type constant individualConstants += '/**\n'; individualConstants += ` * Type for {@link ${constantName}} ${key}\n`; @@ -208,6 +217,14 @@ const constantNameMemo = new Map(); const constantNameInnerMemo = new Map(); const usedConstantNames = new Set(); +function getDynamicSuffixBase(key: string): string { + const suffix = '.'; + if (!key.endsWith(suffix)) { + throw new Error(`Expected dynamic suffix attribute key to end with "${suffix}", got "${key}"`); + } + return key.slice(0, -suffix.length); +} + // Computes a constant name for an attribute without regard for deprecation status. function getConstantNameInner(key: string): string { if (constantNameInnerMemo.has(key)) { From 8f4c3ae7d19c82d0dd2dc87d8289f80f1ab4ce7b Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 9 Jul 2026 13:49:06 +0200 Subject: [PATCH 2/2] handle deprecations --- .../sentry-conventions/src/attributes.ts | 4 +++ scripts/generate_attributes.ts | 29 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/javascript/sentry-conventions/src/attributes.ts b/javascript/sentry-conventions/src/attributes.ts index f5dc1501..5a540642 100644 --- a/javascript/sentry-conventions/src/attributes.ts +++ b/javascript/sentry-conventions/src/attributes.ts @@ -3195,6 +3195,8 @@ export const CLS_SOURCE_KEY = 'cls.source.'; /** * Base key for {@link CLS_SOURCE_KEY}. Use with a dynamic suffix, e.g. `${CLS_SOURCE_KEY_BASE}.${key}`. + * + * @deprecated Use {@link BROWSER_WEB_VITAL_CLS_SOURCE_KEY_BASE} (browser.web_vital.cls.source) instead - The CLS source is now recorded as a browser.web_vital.cls.source. attribute. */ export const CLS_SOURCE_KEY_BASE = 'cls.source'; @@ -10990,6 +10992,8 @@ export const QUERY_KEY = 'query.'; /** * Base key for {@link QUERY_KEY}. Use with a dynamic suffix, e.g. `${QUERY_KEY_BASE}.${key}`. + * + * @deprecated Use {@link URL_QUERY} (url.query) instead - Instead of sending items individually in query., they should be sent all together with url.query. */ export const QUERY_KEY_BASE = 'query'; diff --git a/scripts/generate_attributes.ts b/scripts/generate_attributes.ts index 2b833b09..c3a8f704 100644 --- a/scripts/generate_attributes.ts +++ b/scripts/generate_attributes.ts @@ -156,12 +156,7 @@ function writeToJs(attributesDir: string, attributeFiles: string[]) { // Deprecation if (deprecation) { - individualConstants += ' *\n'; - let replacement = ''; - if (deprecation.replacement) { - replacement = `Use {@link ${getConstantName(deprecation.replacement, false)}} (${deprecation.replacement}) instead`; - } - individualConstants += ` * @deprecated ${replacement}${deprecation.reason ? ` - ${deprecation.reason}` : ''}\n`; + individualConstants += formatDeprecationJsdoc(deprecation, allAttributes, false); } // Example @@ -180,6 +175,9 @@ function writeToJs(attributesDir: string, attributeFiles: string[]) { const constantNameBase = `${constantName}_BASE`; individualConstants += '/**\n'; individualConstants += ` * Base key for {@link ${constantName}}. Use with a dynamic suffix, e.g. \`\${${constantNameBase}}.\${key}\`.\n`; + if (deprecation) { + individualConstants += formatDeprecationJsdoc(deprecation, allAttributes, true); + } individualConstants += ' */\n'; individualConstants += `export const ${constantNameBase} = '${keyBase}';\n\n`; } @@ -225,6 +223,25 @@ function getDynamicSuffixBase(key: string): string { return key.slice(0, -suffix.length); } +function formatDeprecationJsdoc( + deprecation: NonNullable, + allAttributes: Array<{ key: string; attributeJson: AttributeJson }>, + isBase: boolean, +): string { + let replacement = ''; + if (deprecation.replacement) { + const replacementAttr = allAttributes.find((attr) => attr.key === deprecation.replacement); + const replacementConstantName = getConstantName(deprecation.replacement, false); + const replacementHasDynamicSuffix = !!replacementAttr?.attributeJson.has_dynamic_suffix; + const replacementLink = + isBase && replacementHasDynamicSuffix ? `${replacementConstantName}_BASE` : replacementConstantName; + const replacementDisplay = + isBase && replacementHasDynamicSuffix ? getDynamicSuffixBase(deprecation.replacement) : deprecation.replacement; + replacement = `Use {@link ${replacementLink}} (${replacementDisplay}) instead`; + } + return ` *\n * @deprecated ${replacement}${deprecation.reason ? ` - ${deprecation.reason}` : ''}\n`; +} + // Computes a constant name for an attribute without regard for deprecation status. function getConstantNameInner(key: string): string { if (constantNameInnerMemo.has(key)) {