Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 4 additions & 0 deletions packages/browser-rum-angular/typedoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": ["src/entries/main.ts"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
* <html lang="en">
* <body>
* <DatadogAppRouter />
* {children}
* </body>
* </html>
* )
* }
* ```
*/
export function DatadogAppRouter() {
const pathname = mockable(usePathname)()
const params = mockable(useParams)()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
* <>
* <DatadogPagesRouter />
* <Component {...pageProps} />
* </>
* )
* }
* ```
*/
export function DatadogPagesRouter() {
const router = mockable(useRouter)()
const previousPath = mockable(useRef)<string | null>(null)
Expand Down
46 changes: 45 additions & 1 deletion packages/browser-rum-nextjs/src/domain/nextjsPlugin.ts
Original file line number Diff line number Diff line change
@@ -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<Required<RumPlugin>, 'name' | 'onInit' | 'onRumStart'>

type InitSubscriber = (rumPublicApi: RumPublicApi) => void
Expand All @@ -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: '<DATADOG_APPLICATION_ID>',
* clientToken: '<DATADOG_CLIENT_TOKEN>',
* site: '<DATADOG_SITE>',
* plugins: [nextjsPlugin()],
* // ...
* })
* ```
*/
export function nextjsPlugin(): NextjsPlugin {
return {
name: 'nextjs',
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 4 additions & 0 deletions packages/browser-rum-nextjs/typedoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": ["src/entries/main.ts"]
}
16 changes: 16 additions & 0 deletions packages/browser-rum-nuxt/src/domain/error/addNuxtError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
* <script setup lang="ts">
* import { onErrorCaptured } from 'vue'
* import { addNuxtError } from '@datadog/browser-rum-nuxt'
*
* onErrorCaptured((error, instance, info) => {
* addNuxtError(error, instance, info)
* })
* </script>
* ```
*/
export function addNuxtError(error: unknown, instance: ComponentPublicInstance | null, info: string) {
onRumStart((addError) => {
reportNuxtError(addError, error, instance, info)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions packages/browser-rum-nuxt/src/domain/nuxtPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Required<RumPlugin>, '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
}

Expand All @@ -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: '<DATADOG_APPLICATION_ID>',
* clientToken: '<DATADOG_CLIENT_TOKEN>',
* site: '<DATADOG_SITE>',
* plugins: [
* nuxtRumPlugin({
* router: useRouter(),
* nuxtApp: useNuxtApp(),
* }),
* ],
* })
* },
* })
* ```
*/
export function nuxtRumPlugin(configuration: NuxtPluginConfiguration): NuxtPlugin {
return {
name: 'nuxt',
Expand Down
1 change: 1 addition & 0 deletions packages/browser-rum-nuxt/src/entries/main.ts
Original file line number Diff line number Diff line change
@@ -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'
4 changes: 4 additions & 0 deletions packages/browser-rum-nuxt/typedoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": ["src/entries/main.ts"]
}
4 changes: 4 additions & 0 deletions packages/browser-rum-react/tanstack-router/typedoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": ["../src/entries/tanstackRouter.ts"]
}
6 changes: 6 additions & 0 deletions packages/browser-rum-vue/src/domain/router/vueRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
35 changes: 35 additions & 0 deletions packages/browser-rum-vue/src/domain/vuePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RumPlugin>

/**
* Vue plugin constructor.
*
* @category Main
* @example
* ```ts
* import { datadogRum } from '@datadog/browser-rum'
* import { vuePlugin } from '@datadog/browser-rum-vue'
*
* datadogRum.init({
* applicationId: '<DATADOG_APPLICATION_ID>',
* clientToken: '<DATADOG_CLIENT_TOKEN>',
* site: '<DATADOG_SITE>',
* plugins: [vuePlugin()],
* // ...
* })
* ```
*/
export function vuePlugin(configuration: VuePluginConfiguration = {}): VuePlugin {
return {
name: 'vue',
Expand Down
31 changes: 31 additions & 0 deletions packages/browser-rum-vue/src/entries/vueRouter.ts
Original file line number Diff line number Diff line change
@@ -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: '<DATADOG_APPLICATION_ID>',
* clientToken: '<DATADOG_CLIENT_TOKEN>',
* site: '<DATADOG_SITE>',
* plugins: [vuePlugin({ router: true })],
* // ...
* })
*
* const router = createRouter({
* routes: [
* // ...
* ],
* })
*
* const app = createApp(App)
* app.use(router)
* ```
*/
export { createRouter } from '../domain/router/vueRouter'
12 changes: 12 additions & 0 deletions packages/js-core/api/util.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export function combine<A, B, C, D, E, F, G>(a: A, b: B, c: C, d: D, e: E, f: F,
// @public (undocumented)
export function combine<A, B, C, D, E, F, G, H>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): Combined<Combined<Combined<Combined<Combined<Combined<Combined<A, B>, C>, D>, E>, F>, G>, H>;

// @public
export type Combined<A, B> = A extends null ? B : B extends null ? A : Merged<A, B>;

// @public
export const ConsoleApiName: {
readonly log: "log";
Expand Down Expand Up @@ -155,6 +158,9 @@ export function isValidUrl(url: string): boolean;
// @public
export const isWorkerEnvironment: boolean;

// @public
export type Merged<TDestination, TSource> = 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<D, S>(destination: D, source: S): Merged<D, S>;

Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading