diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md
index 7c80693633..3367bae905 100644
--- a/docs/CONVENTIONS.md
+++ b/docs/CONVENTIONS.md
@@ -224,3 +224,14 @@ For functions API (startView, addAction), if we want to support new inputs, we c
However, for customer-provided functions (beforeSend, allowTracingUrl), introducing a change in the input could be a breaking change because the customer implementation would not support it. To have more flexibility in those cases, favor an option input parameter over multiple parameters.
_Note_: to avoid unnecessary customer migration, don't update existing APIs until it is needed.
+
+### Document public API exports for the generated API reference
+
+The [API reference site](../typedoc.json) is generated by TypeDoc from JSDoc comments on the symbols re-exported from each package's `src/entries/*.ts`. `packages/browser-rum-react` is the reference example to copy from.
+
+- Every package with a public entry point needs its own `typedoc.json` (see `packages/browser-rum-react/typedoc.json`) pointing at `src/entries/main.ts`. Without it, TypeDoc silently falls back to the built `cjs/entries/main.js` output.
+- Every exported function/interface/type needs a `/** ... */` comment with a `@category Main` (init/config surface) or `@category Error` (error-reporting surface) tag — otherwise TypeDoc dumps it into a catch-all "Other" category instead of a properly named folder.
+- Give plugin constructors and error-reporting functions an `@example` code block showing real usage (copy the pattern from `reactPlugin`/`addReactError`).
+- Mark internal-only types (e.g. the `Plugin` type returned by a plugin constructor) with `@internal` so they're excluded (`excludeInternal` is set in the root `typedoc.json`).
+- A category with exactly one member still renders as a real folder because the root `typedoc.json` sets `"navigation": { "compactFolders": false }` — don't remove that, or single-member categories (e.g. a package with only one `@category Error` export) collapse into an unopenable `Error/functionName` leaf.
+- Run `yarn build:docs:html` and open `generated-docs/index.html` to check the sidebar tree before merging changes to a package's public API.
diff --git a/packages/browser-rum-angular/typedoc.json b/packages/browser-rum-angular/typedoc.json
new file mode 100644
index 0000000000..002b26a53c
--- /dev/null
+++ b/packages/browser-rum-angular/typedoc.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://typedoc.org/schema.json",
+ "entryPoints": ["src/entries/main.ts"]
+}
diff --git a/packages/browser-rum-nextjs/src/domain/nextJSRouter/datadogAppRouter.tsx b/packages/browser-rum-nextjs/src/domain/nextJSRouter/datadogAppRouter.tsx
index 50bc025ead..42d5c5ed62 100644
--- a/packages/browser-rum-nextjs/src/domain/nextJSRouter/datadogAppRouter.tsx
+++ b/packages/browser-rum-nextjs/src/domain/nextJSRouter/datadogAppRouter.tsx
@@ -6,6 +6,28 @@ import { mockable } from '@datadog/browser-core'
import { startNextjsView } from '../nextjsPlugin'
import { computeViewNameFromParams } from './computeViewNameFromParams'
+/**
+ * Component tracking App Router navigations as RUM views. Render it once, near the root of your
+ * layout.
+ *
+ * @category Main
+ * @example
+ * ```tsx
+ * // app/layout.tsx
+ * import { DatadogAppRouter } from '@datadog/browser-rum-nextjs'
+ *
+ * export default function RootLayout({ children }: { children: React.ReactNode }) {
+ * return (
+ *
+ *
+ *
+ * {children}
+ *
+ *
+ * )
+ * }
+ * ```
+ */
export function DatadogAppRouter() {
const pathname = mockable(usePathname)()
const params = mockable(useParams)()
diff --git a/packages/browser-rum-nextjs/src/domain/nextJSRouter/datadogPagesRouter.tsx b/packages/browser-rum-nextjs/src/domain/nextJSRouter/datadogPagesRouter.tsx
index e320dfe951..de5b8855fc 100644
--- a/packages/browser-rum-nextjs/src/domain/nextJSRouter/datadogPagesRouter.tsx
+++ b/packages/browser-rum-nextjs/src/domain/nextJSRouter/datadogPagesRouter.tsx
@@ -3,6 +3,26 @@ import { useRouter } from 'next/router'
import { mockable } from '@datadog/browser-core'
import { startNextjsView } from '../nextjsPlugin'
+/**
+ * Component tracking Pages Router navigations as RUM views. Render it once, in your custom App.
+ *
+ * @category Main
+ * @example
+ * ```tsx
+ * // pages/_app.tsx
+ * import type { AppProps } from 'next/app'
+ * import { DatadogPagesRouter } from '@datadog/browser-rum-nextjs'
+ *
+ * export default function MyApp({ Component, pageProps }: AppProps) {
+ * return (
+ * <>
+ *
+ *
+ * >
+ * )
+ * }
+ * ```
+ */
export function DatadogPagesRouter() {
const router = mockable(useRouter)()
const previousPath = mockable(useRef)(null)
diff --git a/packages/browser-rum-nextjs/src/domain/nextjsPlugin.ts b/packages/browser-rum-nextjs/src/domain/nextjsPlugin.ts
index 6e4f554328..dae26a686c 100644
--- a/packages/browser-rum-nextjs/src/domain/nextjsPlugin.ts
+++ b/packages/browser-rum-nextjs/src/domain/nextjsPlugin.ts
@@ -1,6 +1,13 @@
import { buildUrl } from '@datadog/js-core/util'
import type { RumPlugin, RumPublicApi, StartRumResult } from '@datadog/browser-rum-core'
+/**
+ * Next.js plugin type.
+ *
+ * The plugins API is unstable and experimental, and may change without notice. Please don't use this type directly.
+ *
+ * @internal
+ */
export type NextjsPlugin = Pick, 'name' | 'onInit' | 'onRumStart'>
type InitSubscriber = (rumPublicApi: RumPublicApi) => void
@@ -13,6 +20,28 @@ let lastNavigationUrl: string | undefined
const onRumInitSubscribers: InitSubscriber[] = []
const onRumStartSubscribers: StartSubscriber[] = []
+/**
+ * Next.js plugin constructor.
+ *
+ * @category Main
+ * @example
+ * ```ts
+ * // instrumentation-client.js
+ * import { datadogRum } from '@datadog/browser-rum'
+ * import { nextjsPlugin, onRouterTransitionStart } from '@datadog/browser-rum-nextjs'
+ *
+ * // Only needed for the App Router, so Next.js can call it on client-side navigations
+ * export { onRouterTransitionStart }
+ *
+ * datadogRum.init({
+ * applicationId: '',
+ * clientToken: '',
+ * site: '',
+ * plugins: [nextjsPlugin()],
+ * // ...
+ * })
+ * ```
+ */
export function nextjsPlugin(): NextjsPlugin {
return {
name: 'nextjs',
@@ -44,7 +73,22 @@ export function startNextjsView(viewName: string) {
}
}
-// Must be re-exported from the user's instrumentation-client.ts so we can capture the URL before React renders
+/**
+ * Notifies the plugin that an App Router navigation has started, so the target URL can be
+ * captured before React renders and `window.location` updates.
+ *
+ * Must be re-exported from the user's `instrumentation-client.js` so Next.js can call it on
+ * client-side navigations. Only needed for the App Router; the Pages Router doesn't use it.
+ *
+ * @category Main
+ * @example
+ * ```ts
+ * // instrumentation-client.js
+ * import { onRouterTransitionStart } from '@datadog/browser-rum-nextjs'
+ *
+ * export { onRouterTransitionStart }
+ * ```
+ */
export function onRouterTransitionStart(url: string) {
lastNavigationUrl = url
}
diff --git a/packages/browser-rum-nextjs/typedoc.json b/packages/browser-rum-nextjs/typedoc.json
new file mode 100644
index 0000000000..002b26a53c
--- /dev/null
+++ b/packages/browser-rum-nextjs/typedoc.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://typedoc.org/schema.json",
+ "entryPoints": ["src/entries/main.ts"]
+}
diff --git a/packages/browser-rum-nuxt/src/domain/error/addNuxtError.ts b/packages/browser-rum-nuxt/src/domain/error/addNuxtError.ts
index d54cb7de97..fdb9bac661 100644
--- a/packages/browser-rum-nuxt/src/domain/error/addNuxtError.ts
+++ b/packages/browser-rum-nuxt/src/domain/error/addNuxtError.ts
@@ -2,6 +2,22 @@ import type { ComponentPublicInstance } from 'vue'
import { onRumStart } from '../nuxtPlugin'
import { reportNuxtError } from './setupNuxtErrorHandling'
+/**
+ * Add a Nuxt error to the RUM session.
+ *
+ * @category Error
+ * @example
+ * ```vue
+ *
+ * ```
+ */
export function addNuxtError(error: unknown, instance: ComponentPublicInstance | null, info: string) {
onRumStart((addError) => {
reportNuxtError(addError, error, instance, info)
diff --git a/packages/browser-rum-nuxt/src/domain/error/setupNuxtErrorHandling.ts b/packages/browser-rum-nuxt/src/domain/error/setupNuxtErrorHandling.ts
index 63a63d8904..c99640f006 100644
--- a/packages/browser-rum-nuxt/src/domain/error/setupNuxtErrorHandling.ts
+++ b/packages/browser-rum-nuxt/src/domain/error/setupNuxtErrorHandling.ts
@@ -3,6 +3,12 @@ import type { ComponentInternalInstance, ComponentPublicInstance, App } from 'vu
import { clocksNow } from '@datadog/js-core/time'
import { callMonitored, createHandlingStack } from '@datadog/browser-core'
+/**
+ * The subset of the Nuxt app instance (as returned by `useNuxtApp()`) needed to wire up
+ * automatic error reporting.
+ *
+ * @category Main
+ */
export interface NuxtApp {
vueApp: App
hook(name: 'app:error', callback: (err: unknown) => void): void
diff --git a/packages/browser-rum-nuxt/src/domain/nuxtPlugin.ts b/packages/browser-rum-nuxt/src/domain/nuxtPlugin.ts
index 3df34e51ac..75a6384ac9 100644
--- a/packages/browser-rum-nuxt/src/domain/nuxtPlugin.ts
+++ b/packages/browser-rum-nuxt/src/domain/nuxtPlugin.ts
@@ -4,10 +4,30 @@ import { startTrackingNuxtViews } from './router/nuxtRouter'
import type { NuxtApp } from './error/setupNuxtErrorHandling'
import { reportNuxtError, setupNuxtErrorHandling } from './error/setupNuxtErrorHandling'
+/**
+ * Nuxt plugin type.
+ *
+ * The plugins API is unstable and experimental, and may change without notice. Please don't use this type directly.
+ *
+ * @internal
+ */
export type NuxtPlugin = Pick, 'name' | 'onInit' | 'onRumStart' | 'getConfigurationTelemetry'>
+/**
+ * Nuxt plugin configuration.
+ *
+ * @category Main
+ */
export interface NuxtPluginConfiguration {
+ /**
+ * The Vue Router instance used by the Nuxt application, used to automatically track route
+ * changes as RUM views.
+ */
router: Router
+ /**
+ * The Nuxt app instance, used to automatically report Vue component errors and Nuxt startup
+ * errors caught by the `app:error` hook. Optional, but recommended.
+ */
nuxtApp?: NuxtApp
}
@@ -20,6 +40,35 @@ let globalAddError: StartRumResult['addError'] | undefined
const onRumInitSubscribers: InitSubscriber[] = []
const onRumStartSubscribers: StartSubscriber[] = []
+/**
+ * Nuxt plugin constructor.
+ *
+ * @category Main
+ * @example
+ * ```ts
+ * import { datadogRum } from '@datadog/browser-rum'
+ * import { nuxtRumPlugin } from '@datadog/browser-rum-nuxt'
+ * import { defineNuxtPlugin, useNuxtApp, useRouter } from 'nuxt/app'
+ *
+ * export default defineNuxtPlugin({
+ * name: 'datadog-rum',
+ * enforce: 'pre',
+ * setup() {
+ * datadogRum.init({
+ * applicationId: '',
+ * clientToken: '',
+ * site: '',
+ * plugins: [
+ * nuxtRumPlugin({
+ * router: useRouter(),
+ * nuxtApp: useNuxtApp(),
+ * }),
+ * ],
+ * })
+ * },
+ * })
+ * ```
+ */
export function nuxtRumPlugin(configuration: NuxtPluginConfiguration): NuxtPlugin {
return {
name: 'nuxt',
diff --git a/packages/browser-rum-nuxt/src/entries/main.ts b/packages/browser-rum-nuxt/src/entries/main.ts
index 626a11787c..1d93eb47fc 100644
--- a/packages/browser-rum-nuxt/src/entries/main.ts
+++ b/packages/browser-rum-nuxt/src/entries/main.ts
@@ -1,3 +1,4 @@
export type { NuxtPlugin, NuxtPluginConfiguration } from '../domain/nuxtPlugin'
export { nuxtRumPlugin } from '../domain/nuxtPlugin'
export { addNuxtError } from '../domain/error/addNuxtError'
+export type { NuxtApp } from '../domain/error/setupNuxtErrorHandling'
diff --git a/packages/browser-rum-nuxt/typedoc.json b/packages/browser-rum-nuxt/typedoc.json
new file mode 100644
index 0000000000..002b26a53c
--- /dev/null
+++ b/packages/browser-rum-nuxt/typedoc.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://typedoc.org/schema.json",
+ "entryPoints": ["src/entries/main.ts"]
+}
diff --git a/packages/browser-rum-react/tanstack-router/typedoc.json b/packages/browser-rum-react/tanstack-router/typedoc.json
new file mode 100644
index 0000000000..457638a80a
--- /dev/null
+++ b/packages/browser-rum-react/tanstack-router/typedoc.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://typedoc.org/schema.json",
+ "entryPoints": ["../src/entries/tanstackRouter.ts"]
+}
diff --git a/packages/browser-rum-vue/src/domain/router/vueRouter.ts b/packages/browser-rum-vue/src/domain/router/vueRouter.ts
index c2338eb5ec..b8614c79ba 100644
--- a/packages/browser-rum-vue/src/domain/router/vueRouter.ts
+++ b/packages/browser-rum-vue/src/domain/router/vueRouter.ts
@@ -2,6 +2,12 @@ import { createRouter as originalCreateRouter } from 'vue-router'
import type { RouterOptions, Router } from 'vue-router'
import { startVueRouterView } from './startVueRouterView'
+/**
+ * Use this function in place of `vue-router` `createRouter`. Every time a route is
+ * rendered, a new RUM view is created.
+ *
+ * @see https://router.vuejs.org/api/interfaces/RouterOptions.html
+ */
export function createRouter(options: RouterOptions): Router {
const router = originalCreateRouter(options)
diff --git a/packages/browser-rum-vue/src/domain/vuePlugin.ts b/packages/browser-rum-vue/src/domain/vuePlugin.ts
index 5f22d5b9ae..66b4be3866 100644
--- a/packages/browser-rum-vue/src/domain/vuePlugin.ts
+++ b/packages/browser-rum-vue/src/domain/vuePlugin.ts
@@ -10,12 +10,47 @@ type StartSubscriber = (addError: StartRumResult['addError']) => void
const onRumInitSubscribers: InitSubscriber[] = []
const onRumStartSubscribers: StartSubscriber[] = []
+/**
+ * Vue plugin configuration.
+ *
+ * @category Main
+ */
export interface VuePluginConfiguration {
+ /**
+ * Enable Vue Router integration. Make sure to use `createRouter()` from
+ * {@link @datadog/browser-rum-vue/vue-router-v4! | @datadog/browser-rum-vue/vue-router-v4}
+ * to create the router.
+ */
router?: boolean
}
+/**
+ * Vue plugin type.
+ *
+ * The plugins API is unstable and experimental, and may change without notice. Please don't use this type directly.
+ *
+ * @internal
+ */
export type VuePlugin = Required
+/**
+ * Vue plugin constructor.
+ *
+ * @category Main
+ * @example
+ * ```ts
+ * import { datadogRum } from '@datadog/browser-rum'
+ * import { vuePlugin } from '@datadog/browser-rum-vue'
+ *
+ * datadogRum.init({
+ * applicationId: '',
+ * clientToken: '',
+ * site: '',
+ * plugins: [vuePlugin()],
+ * // ...
+ * })
+ * ```
+ */
export function vuePlugin(configuration: VuePluginConfiguration = {}): VuePlugin {
return {
name: 'vue',
diff --git a/packages/browser-rum-vue/src/entries/vueRouter.ts b/packages/browser-rum-vue/src/entries/vueRouter.ts
index 2d0e34525b..00901e8d88 100644
--- a/packages/browser-rum-vue/src/entries/vueRouter.ts
+++ b/packages/browser-rum-vue/src/entries/vueRouter.ts
@@ -1 +1,32 @@
+/**
+ * Vue Router v4 integration.
+ *
+ * @packageDocumentation
+ * @example
+ * ```ts
+ * import { createApp } from 'vue'
+ * import { datadogRum } from '@datadog/browser-rum'
+ * import { vuePlugin } from '@datadog/browser-rum-vue'
+ *
+ * // ⚠️ Use "createRouter" from `@datadog/browser-rum-vue/vue-router-v4` instead of `vue-router`
+ * import { createRouter } from '@datadog/browser-rum-vue/vue-router-v4'
+ *
+ * datadogRum.init({
+ * applicationId: '',
+ * clientToken: '',
+ * site: '',
+ * plugins: [vuePlugin({ router: true })],
+ * // ...
+ * })
+ *
+ * const router = createRouter({
+ * routes: [
+ * // ...
+ * ],
+ * })
+ *
+ * const app = createApp(App)
+ * app.use(router)
+ * ```
+ */
export { createRouter } from '../domain/router/vueRouter'
diff --git a/packages/js-core/api/util.api.md b/packages/js-core/api/util.api.md
index 32e2528ad6..1866ed6289 100644
--- a/packages/js-core/api/util.api.md
+++ b/packages/js-core/api/util.api.md
@@ -28,6 +28,9 @@ export function combine(a: A, b: B, c: C, d: D, e: E, f: F,
// @public (undocumented)
export function combine(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): Combined, C>, D>, E>, F>, G>, H>;
+// @public
+export type Combined = A extends null ? B : B extends null ? A : Merged;
+
// @public
export const ConsoleApiName: {
readonly log: "log";
@@ -155,6 +158,9 @@ export function isValidUrl(url: string): boolean;
// @public
export const isWorkerEnvironment: boolean;
+// @public
+export type Merged = TSource extends undefined ? TDestination : TDestination extends undefined ? TSource : TSource extends any[] ? TDestination extends any[] ? TDestination & TSource : TSource : TSource extends object ? TDestination extends object ? TDestination extends any[] ? TSource : TDestination & TSource : TSource : TSource;
+
// @public
export function mergeInto(destination: D, source: S): Merged;
@@ -200,6 +206,12 @@ export interface ProfilerConstructor {
new (options: ProfilerInitOptions): Profiler;
}
+// @internal (undocumented)
+export interface ProfilerEventMap {
+ // (undocumented)
+ samplebufferfull: SampleBufferFullEvent;
+}
+
// @public
export interface ProfilerFrame {
readonly column?: number;
diff --git a/packages/js-core/src/entries/util.ts b/packages/js-core/src/entries/util.ts
index f5446dc452..6c7484b216 100644
--- a/packages/js-core/src/entries/util.ts
+++ b/packages/js-core/src/entries/util.ts
@@ -21,6 +21,7 @@ export type {
ProfilerResource,
ProfilerTrace,
ProfilerInitOptions,
+ ProfilerEventMap,
SampleBufferFullEvent,
Profiler,
ProfilerConstructor,
diff --git a/packages/js-core/src/util/globalObject.ts b/packages/js-core/src/util/globalObject.ts
index 82f5c90899..45919763d0 100644
--- a/packages/js-core/src/util/globalObject.ts
+++ b/packages/js-core/src/util/globalObject.ts
@@ -139,7 +139,7 @@ export interface CookieStore extends EventTarget {
listener: (ev: CookieStoreEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): void
- /** @inheritdoc EventTarget.addEventListener */
+ /** Adds a listener for any other event type, untyped. */
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
@@ -151,7 +151,7 @@ export interface CookieStore extends EventTarget {
listener: (ev: CookieStoreEventMap[K]) => any,
options?: boolean | EventListenerOptions
): void
- /** @inheritdoc EventTarget.removeEventListener */
+ /** Removes a listener for any other event type, untyped. */
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
@@ -220,7 +220,7 @@ export interface SampleBufferFullEvent extends Event {
}
/** @internal */
-interface ProfilerEventMap {
+export interface ProfilerEventMap {
samplebufferfull: SampleBufferFullEvent
}
@@ -238,7 +238,7 @@ export interface Profiler extends EventTarget {
listener: (ev: ProfilerEventMap[K]) => any,
options?: boolean | AddEventListenerOptions
): void
- /** @inheritdoc EventTarget.addEventListener */
+ /** Adds a listener for any other event type, untyped. */
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
@@ -250,7 +250,7 @@ export interface Profiler extends EventTarget {
listener: (ev: ProfilerEventMap[K]) => any,
options?: boolean | EventListenerOptions
): void
- /** @inheritdoc EventTarget.removeEventListener */
+ /** Removes a listener for any other event type, untyped. */
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
diff --git a/packages/js-core/src/util/mergeInto.ts b/packages/js-core/src/util/mergeInto.ts
index 1cef0ef25c..e6cb6e1a6a 100644
--- a/packages/js-core/src/util/mergeInto.ts
+++ b/packages/js-core/src/util/mergeInto.ts
@@ -1,6 +1,10 @@
import { getType } from './typeUtils'
-type Merged =
+/**
+ * The resulting type of deeply merging `TSource` into `TDestination`, as performed by
+ * {@link mergeInto} and {@link combine}.
+ */
+export type Merged =
// case 1 - source is undefined - return destination
TSource extends undefined
? TDestination
@@ -90,7 +94,10 @@ export function deepClone(value: T): T {
return mergeInto(undefined, value) as T
}
-type Combined = A extends null ? B : B extends null ? A : Merged
+/**
+ * The resulting type of deeply merging `A` and `B`, as returned by {@link combine}.
+ */
+export type Combined = A extends null ? B : B extends null ? A : Merged
/**
* Performs a non-mutating deep merge of two or more values.
diff --git a/typedoc.json b/typedoc.json
index 241889309e..8b8e3ef4b5 100644
--- a/typedoc.json
+++ b/typedoc.json
@@ -3,6 +3,7 @@
"packages/*",
"packages/browser-rum-react/react-router-v6",
"packages/browser-rum-react/react-router-v7",
+ "packages/browser-rum-react/tanstack-router",
"packages/browser-rum-vue/vue-router-v4"
],
"entryPointStrategy": "packages",
@@ -25,8 +26,23 @@
"hideGenerator": true,
"includeHierarchySummary": true,
"navigation": {
- "includeCategories": true
+ "includeCategories": true,
+ "compactFolders": false
},
+ "highlightLanguages": [
+ "bash",
+ "console",
+ "css",
+ "html",
+ "javascript",
+ "json",
+ "jsonc",
+ "json5",
+ "yaml",
+ "tsx",
+ "typescript",
+ "vue"
+ ],
"packageOptions": {
"excludeInternal": true,