From 85188dfd04e082a906ce51e076b75f09556e0ca2 Mon Sep 17 00:00:00 2001 From: Polliog <40077351+Polliog@users.noreply.github.com> Date: Tue, 30 Jun 2026 13:44:43 +0200 Subject: [PATCH] fix: align package exports with documentation across the SDK Audited every package's public exports against the docs (logtide.dev) and fixed all import/usage mismatches found. - @logtide/browser: export LogtideErrorBoundary (React error boundary) as documented for React users; react is an optional peer dependency. @logtide/nextjs/client now re-exports this shared implementation. - @logtide/nuxt: implement the documented useLogtide() auto-imported composable; honor tracesSampleRate and support apiUrl/apiKey config (the module previously dropped them and required a dsn). - @logtide/cli: accept the documented `--path` option and `--apiKey`/`--apiUrl` aliases for `sourcemaps upload`. - @logtide/sdk-node README: import middleware from the /middleware subpath. - @logtide/nuxt README: correct the default service value to 'nuxt'. Bump all packages to 0.11.0. --- CHANGELOG.md | 18 ++++ package.json | 2 +- packages/angular/package.json | 2 +- packages/browser/package.json | 8 +- .../client => browser/src}/error-boundary.tsx | 4 +- packages/browser/src/index.ts | 3 + packages/browser/tests/error-boundary.test.ts | 33 ++++++++ packages/browser/tsconfig.json | 1 + packages/browser/tsup.config.ts | 2 +- packages/cli/package.json | 2 +- packages/cli/src/index.ts | 82 ++++++++++++++----- packages/cli/tests/cli.test.ts | 62 ++++++++++++++ packages/core/package.json | 2 +- packages/elysia/package.json | 2 +- packages/express/package.json | 2 +- packages/fastify/package.json | 2 +- packages/hono/package.json | 2 +- packages/nextjs/package.json | 2 +- packages/nextjs/src/client/index.ts | 2 +- packages/node/README.md | 6 +- packages/node/package.json | 2 +- packages/nuxt/README.md | 2 +- packages/nuxt/package.json | 2 +- packages/nuxt/src/module.ts | 25 +++--- packages/nuxt/src/runtime/client-plugin.ts | 15 +++- packages/nuxt/src/runtime/composables.ts | 24 ++++++ packages/nuxt/src/runtime/server-plugin.ts | 10 ++- packages/nuxt/tests/composables.test.ts | 36 ++++++++ packages/sveltekit/package.json | 2 +- packages/types/package.json | 2 +- pnpm-lock.yaml | 6 ++ 31 files changed, 305 insertions(+), 60 deletions(-) rename packages/{nextjs/src/client => browser/src}/error-boundary.tsx (90%) create mode 100644 packages/browser/tests/error-boundary.test.ts create mode 100644 packages/cli/tests/cli.test.ts create mode 100644 packages/nuxt/src/runtime/composables.ts create mode 100644 packages/nuxt/tests/composables.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b24f0..0a8f562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.11.0] - 2026-06-30 + +Audit of every package's public exports against the documentation, fixing all +import/usage mismatches found (the same class of bug as the missing +`initLogtide` export in 0.10.0). + +### Added + +- **`@logtide/browser`: `LogtideErrorBoundary` export** — the React error boundary documented at logtide.dev/integrations/react (`import { LogtideErrorBoundary } from '@logtide/browser'`) is now actually exported. `react` is declared as an **optional** peer dependency. `@logtide/nextjs/client` now re-exports this single shared implementation instead of its own copy. +- **`@logtide/nuxt`: `useLogtide()` composable** — auto-imported composable (`const { captureLog, captureError, addBreadcrumb } = useLogtide()`) for manual capture, as shown in the docs. Previously documented but not implemented. +- **`@logtide/cli`: `--path` option and `--apiKey` / `--apiUrl` aliases** for `sourcemaps upload` — the documented invocation `logtide sourcemaps upload --path ./dist --release 1.0.0 --apiKey KEY` now works. The directory may be passed either positionally or via `--path`; the canonical `--api-key` / `--api-url` forms continue to work. + +### Fixed + +- **`@logtide/nuxt`: `tracesSampleRate`, `apiUrl` and `apiKey` options are now honored.** The module previously dropped `tracesSampleRate` and ignored `apiUrl`/`apiKey` (it bailed out unless a `dsn` was set), contradicting the README. Configuration via `apiUrl` + `apiKey` (instead of `dsn`) now works on both client and server. +- **`@logtide/sdk-node` README**: the Express/Fastify middleware examples now import from `@logtide/sdk-node/middleware` (where `logTideMiddleware` / `logTideFastifyPlugin` actually live) instead of the package root. +- **`@logtide/nuxt` README**: corrected the documented default `service` value to `'nuxt'`. + ## [0.10.0] - 2026-06-28 ### Added diff --git a/package.json b/package.json index e6c71c5..1a38a31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "0.10.0", + "version": "0.11.0", "scripts": { "build": "pnpm -r --filter @logtide/* build", "test": "pnpm -r --filter @logtide/* test", diff --git a/packages/angular/package.json b/packages/angular/package.json index 980c93d..feee448 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/angular", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide SDK integration for Angular — ErrorHandler, HTTP Interceptor, trace propagation", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/browser/package.json b/packages/browser/package.json index c5dd070..cc1aae1 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/browser", - "version": "0.10.0", + "version": "0.11.0", "description": "Logtide browser SDK — Web Vitals, breadcrumbs, session context, offline resilience", "type": "module", "main": "./dist/index.cjs", @@ -46,15 +46,21 @@ "@logtide/types": "workspace:*" }, "peerDependencies": { + "react": ">=18.0.0", "web-vitals": ">=4.0.0" }, "peerDependenciesMeta": { + "react": { + "optional": true + }, "web-vitals": { "optional": true } }, "devDependencies": { + "@types/react": "^18.3.0", "jsdom": "^28.1.0", + "react": "^18.3.0", "tsup": "^8.5.1", "typescript": "^5.5.4", "web-vitals": "^4.2.4" diff --git a/packages/nextjs/src/client/error-boundary.tsx b/packages/browser/src/error-boundary.tsx similarity index 90% rename from packages/nextjs/src/client/error-boundary.tsx rename to packages/browser/src/error-boundary.tsx index 31a9dd0..c75c77f 100644 --- a/packages/nextjs/src/client/error-boundary.tsx +++ b/packages/browser/src/error-boundary.tsx @@ -13,9 +13,11 @@ interface State { /** * React ErrorBoundary that automatically reports errors to LogTide. * + * Requires `react` (declared as an optional peer dependency). + * * @example * ```tsx - * import { LogtideErrorBoundary } from '@logtide/nextjs/client'; + * import { LogtideErrorBoundary } from '@logtide/browser'; * * Something went wrong}> * diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 040b34f..8c67a3b 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -6,6 +6,9 @@ export { type InitLogtideExtraOptions, } from './init'; +// React error boundary (requires the optional `react` peer dependency) +export { LogtideErrorBoundary } from './error-boundary'; + // Session export { getSessionId, resetSessionId } from './session'; diff --git a/packages/browser/tests/error-boundary.test.ts b/packages/browser/tests/error-boundary.test.ts new file mode 100644 index 0000000..8115472 --- /dev/null +++ b/packages/browser/tests/error-boundary.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { hub } from '@logtide/core'; +import { LogtideErrorBoundary } from '../src/index'; + +describe('LogtideErrorBoundary', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('is exported as a React component class', () => { + expect(LogtideErrorBoundary).toBeDefined(); + expect(typeof LogtideErrorBoundary).toBe('function'); + }); + + it('derives error state from a thrown error', () => { + const err = new Error('boom'); + const state = (LogtideErrorBoundary as any).getDerivedStateFromError(err); + expect(state).toEqual({ error: err }); + }); + + it('reports caught errors to the LogTide hub', () => { + const captureError = vi.spyOn(hub, 'captureError').mockImplementation(() => {}); + const err = new Error('kaboom'); + + const instance = new (LogtideErrorBoundary as any)({ children: null }); + instance.componentDidCatch(err, { componentStack: '\n at Foo' }); + + expect(captureError).toHaveBeenCalledWith( + err, + expect.objectContaining({ mechanism: 'react.error-boundary' }), + ); + }); +}); diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json index 8c0414a..9b5a56e 100644 --- a/packages/browser/tsconfig.json +++ b/packages/browser/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "outDir": "./dist", "rootDir": "./src", + "jsx": "react-jsx", "lib": ["ES2022", "DOM"] }, "include": ["src/**/*"] diff --git a/packages/browser/tsup.config.ts b/packages/browser/tsup.config.ts index 08e2825..3f0805e 100644 --- a/packages/browser/tsup.config.ts +++ b/packages/browser/tsup.config.ts @@ -6,5 +6,5 @@ export default defineConfig({ dts: true, clean: true, sourcemap: true, - external: ['@logtide/types', '@logtide/core', 'web-vitals'], + external: ['@logtide/types', '@logtide/core', 'web-vitals', 'react', 'react/jsx-runtime'], }); diff --git a/packages/cli/package.json b/packages/cli/package.json index a05c01c..0831a04 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/cli", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide CLI — upload source maps and manage releases", "type": "module", "bin": { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 719a39b..83d9e15 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,29 +1,67 @@ import { Command } from 'commander'; import { uploadSourcemaps } from './commands/sourcemaps/upload.js'; -const program = new Command() - .name('logtide') - .description('LogTide CLI') - .version('0.1.0'); +/** + * Normalize camelCase flag aliases to their canonical kebab-case form, so that + * documented invocations using `--apiKey` / `--apiUrl` work alongside the + * canonical `--api-key` / `--api-url`. + */ +export function normalizeArgv(argv: string[]): string[] { + const aliases: Record = { + '--apiKey': '--api-key', + '--apiUrl': '--api-url', + }; + return argv.map((arg) => { + const eq = arg.indexOf('='); + if (eq !== -1) { + const flag = arg.slice(0, eq); + return (aliases[flag] ?? flag) + arg.slice(eq); + } + return aliases[arg] ?? arg; + }); +} + +/** Build the `logtide` CLI program. */ +export function buildProgram(): Command { + const program = new Command() + .name('logtide') + .description('LogTide CLI') + .version('0.1.0'); -const sourcemaps = program - .command('sourcemaps') - .description('Manage source maps'); + const sourcemaps = program + .command('sourcemaps') + .description('Manage source maps'); -sourcemaps - .command('upload ') - .description('Upload source map files to LogTide') - .requiredOption('--release ', 'Release version (e.g., 1.2.3)') - .option('--api-key ', 'LogTide API key (or set LOGTIDE_API_KEY)', process.env.LOGTIDE_API_KEY) - .option('--api-url ', 'LogTide API URL (or set LOGTIDE_API_URL)', process.env.LOGTIDE_API_URL ?? 'https://api.logtide.dev') - .option('--concurrency ', 'Parallel uploads', '5') - .action(async (directory: string, opts: any) => { - await uploadSourcemaps(directory, { - release: opts.release, - apiKey: opts.apiKey, - apiUrl: opts.apiUrl, - concurrency: parseInt(opts.concurrency, 10), + sourcemaps + .command('upload [directory]') + .description('Upload source map files to LogTide') + .option('--path ', 'Directory containing source maps (alternative to the positional argument)') + .requiredOption('--release ', 'Release version (e.g., 1.2.3)') + .option('--api-key ', 'LogTide API key (or set LOGTIDE_API_KEY)', process.env.LOGTIDE_API_KEY) + .option('--api-url ', 'LogTide API URL (or set LOGTIDE_API_URL)', process.env.LOGTIDE_API_URL ?? 'https://api.logtide.dev') + .option('--concurrency ', 'Parallel uploads', '5') + .action(async ( + directory: string | undefined, + opts: { path?: string; release: string; apiKey?: string; apiUrl: string; concurrency: string }, + ) => { + const dir = directory ?? opts.path; + if (!dir) { + console.error('Error: provide a source maps directory (positional argument or --path )'); + process.exit(1); + return; + } + await uploadSourcemaps(dir, { + release: opts.release, + apiKey: opts.apiKey ?? '', + apiUrl: opts.apiUrl, + concurrency: parseInt(opts.concurrency, 10), + }); }); - }); -program.parseAsync(process.argv); + return program; +} + +// Auto-run when executed as the CLI binary (skipped under the test runner). +if (!process.env.VITEST) { + buildProgram().parseAsync(normalizeArgv(process.argv)); +} diff --git a/packages/cli/tests/cli.test.ts b/packages/cli/tests/cli.test.ts new file mode 100644 index 0000000..eccc215 --- /dev/null +++ b/packages/cli/tests/cli.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('../src/commands/sourcemaps/upload.js', () => ({ + uploadSourcemaps: vi.fn().mockResolvedValue(undefined), +})); + +import { uploadSourcemaps } from '../src/commands/sourcemaps/upload.js'; +import { buildProgram, normalizeArgv } from '../src/index.js'; + +const run = (args: string[]) => + buildProgram().parseAsync(['node', 'logtide', ...normalizeArgv(args)]); + +describe('normalizeArgv', () => { + it('maps camelCase flag aliases to canonical kebab-case', () => { + expect(normalizeArgv(['--apiKey', 'K', '--apiUrl', 'U'])).toEqual([ + '--api-key', 'K', '--api-url', 'U', + ]); + }); + + it('handles the --flag=value form', () => { + expect(normalizeArgv(['--apiKey=K'])).toEqual(['--api-key=K']); + }); + + it('leaves unrelated args untouched', () => { + expect(normalizeArgv(['upload', './dist', '--release', '1.0.0'])).toEqual([ + 'upload', './dist', '--release', '1.0.0', + ]); + }); +}); + +describe('sourcemaps upload command', () => { + beforeEach(() => { + vi.mocked(uploadSourcemaps).mockClear(); + }); + + it('accepts the directory as a positional argument', async () => { + await run(['sourcemaps', 'upload', './dist', '--release', '1.0.0', '--api-key', 'K']); + + expect(uploadSourcemaps).toHaveBeenCalledWith( + './dist', + expect.objectContaining({ release: '1.0.0', apiKey: 'K' }), + ); + }); + + it('accepts the directory via --path', async () => { + await run(['sourcemaps', 'upload', '--path', './dist', '--release', '1.0.0', '--api-key', 'K']); + + expect(uploadSourcemaps).toHaveBeenCalledWith( + './dist', + expect.objectContaining({ release: '1.0.0' }), + ); + }); + + it('accepts the documented --apiKey alias', async () => { + await run(['sourcemaps', 'upload', '--path', './dist', '--release', '1.0.0', '--apiKey', 'SECRET']); + + expect(uploadSourcemaps).toHaveBeenCalledWith( + './dist', + expect.objectContaining({ apiKey: 'SECRET' }), + ); + }); +}); diff --git a/packages/core/package.json b/packages/core/package.json index 6fb8bdc..bc7bf91 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/core", - "version": "0.10.0", + "version": "0.11.0", "description": "Core client, hub, scope, transports, and utilities for the LogTide SDK", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/elysia/package.json b/packages/elysia/package.json index f53bd21..fed1c62 100644 --- a/packages/elysia/package.json +++ b/packages/elysia/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/elysia", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide SDK plugin for Elysia — request tracing and error capture via lifecycle hooks", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/express/package.json b/packages/express/package.json index d4d4cee..285c435 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/express", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide SDK middleware for Express — request tracing and error capture", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/fastify/package.json b/packages/fastify/package.json index 7461264..cece6cb 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/fastify", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide SDK plugin for Fastify — request tracing and error capture", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/hono/package.json b/packages/hono/package.json index 8e773d5..0646563 100644 --- a/packages/hono/package.json +++ b/packages/hono/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/hono", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide SDK middleware for Hono — request tracing and error capture", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index f4c0391..1cfdd1d 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/nextjs", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide SDK integration for Next.js — auto error capture, request tracing, and performance spans", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 3800fa3..e42218e 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -1,6 +1,6 @@ import { initLogtide as initBrowserLogtide, type BrowserClientOptions } from '@logtide/browser'; -export { LogtideErrorBoundary } from './error-boundary'; +export { LogtideErrorBoundary } from '@logtide/browser'; export { trackNavigation } from './navigation'; /** diff --git a/packages/node/README.md b/packages/node/README.md index 218d5d0..dc234b4 100644 --- a/packages/node/README.md +++ b/packages/node/README.md @@ -330,7 +330,8 @@ Auto-log all HTTP requests and responses. ```typescript import express from 'express'; -import { LogTideClient, logTideMiddleware } from '@logtide/sdk-node'; +import { LogTideClient } from '@logtide/sdk-node'; +import { logTideMiddleware } from '@logtide/sdk-node/middleware'; const app = express(); const logger = new LogTideClient({ @@ -368,7 +369,8 @@ app.listen(3000); ```typescript import Fastify from 'fastify'; -import { LogTideClient, logTideFastifyPlugin } from '@logtide/sdk-node'; +import { LogTideClient } from '@logtide/sdk-node'; +import { logTideFastifyPlugin } from '@logtide/sdk-node/middleware'; const fastify = Fastify(); const logger = new LogTideClient({ diff --git a/packages/node/package.json b/packages/node/package.json index 3adfa2c..cda7ec7 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/sdk-node", - "version": "0.10.0", + "version": "0.11.0", "description": "Official Node.js SDK for LogTide (logtide.dev) - Self-hosted log management with advanced features: retry logic, circuit breaker, query API, live streaming, and middleware support", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/nuxt/README.md b/packages/nuxt/README.md index d32ab31..3760610 100644 --- a/packages/nuxt/README.md +++ b/packages/nuxt/README.md @@ -73,7 +73,7 @@ All options are set in `nuxt.config.ts` under the `logtide` key: | Option | Type | Default | Description | |--------|------|---------|-------------| | `dsn` | `string` | **required** | DSN string: `https://lp_KEY@host/PROJECT` | -| `service` | `string` | `'nuxt-app'` | Service name for log attribution | +| `service` | `string` | `'nuxt'` | Service name for log attribution | | `environment` | `string` | — | Environment (e.g. `production`, `staging`) | | `release` | `string` | — | Release / version identifier | | `debug` | `boolean` | `false` | Enable debug logging | diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 10c8923..8fb9c5b 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/nuxt", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide SDK integration for Nuxt — auto error capture, request tracing via Nitro hooks", "type": "module", "main": "./dist/module.cjs", diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index b54d476..916f1ea 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -1,4 +1,4 @@ -import { defineNuxtModule, addServerPlugin, addPlugin, createResolver } from '@nuxt/kit'; +import { defineNuxtModule, addServerPlugin, addPlugin, addImports, createResolver } from '@nuxt/kit'; import type { ClientOptions } from '@logtide/types'; export interface ModuleOptions extends Omit {} @@ -14,34 +14,35 @@ export default defineNuxtModule({ service: 'nuxt', }, setup(options, nuxt) { - if (!options.dsn) { - console.warn('[LogTide] No DSN provided — skipping initialization'); + if (!options.dsn && !options.apiUrl) { + console.warn('[LogTide] No DSN (or apiUrl) provided — skipping initialization'); return; } const { resolve } = createResolver(import.meta.url); - // Inject runtime config so plugins can read it - nuxt.options.runtimeConfig.logtide = { + const shared = { dsn: options.dsn, + apiUrl: options.apiUrl, + apiKey: options.apiKey, service: options.service, environment: options.environment, release: options.release, debug: options.debug, + tracesSampleRate: options.tracesSampleRate, }; - nuxt.options.runtimeConfig.public.logtide = { - dsn: options.dsn, - service: options.service, - environment: options.environment, - release: options.release, - debug: options.debug, - }; + // Inject runtime config so plugins can read it + nuxt.options.runtimeConfig.logtide = { ...shared }; + nuxt.options.runtimeConfig.public.logtide = { ...shared }; // Register server plugin (Nitro hooks) addServerPlugin(resolve('./runtime/server-plugin')); // Register client plugin (Vue error handler) addPlugin(resolve('./runtime/client-plugin')); + + // Auto-import the useLogtide() composable for manual capture + addImports({ name: 'useLogtide', from: resolve('./runtime/composables') }); }, }); diff --git a/packages/nuxt/src/runtime/client-plugin.ts b/packages/nuxt/src/runtime/client-plugin.ts index 6c9e3a8..b850bfa 100644 --- a/packages/nuxt/src/runtime/client-plugin.ts +++ b/packages/nuxt/src/runtime/client-plugin.ts @@ -15,11 +15,14 @@ import { defineNuxtPlugin, useRuntimeConfig } from '#app'; */ export default defineNuxtPlugin((nuxtApp) => { const config = useRuntimeConfig().public.logtide as { - dsn: string; + dsn?: string; + apiUrl?: string; + apiKey?: string; service?: string; environment?: string; release?: string; debug?: boolean; + tracesSampleRate?: number; browser?: { webVitals?: boolean; webVitalsSampleRate?: number; @@ -28,11 +31,12 @@ export default defineNuxtPlugin((nuxtApp) => { }; }; - if (!config?.dsn) return; + if (!config?.dsn && !config?.apiUrl) return; + const dsnSource = { dsn: config.dsn, apiUrl: config.apiUrl, apiKey: config.apiKey }; const browserOpts = config.browser ?? {}; const browserIntegrations: Integration[] = []; - const apiUrl = resolveDSN({ dsn: config.dsn }).apiUrl; + const apiUrl = resolveDSN(dsnSource).apiUrl; if (browserOpts.webVitals) { browserIntegrations.push( @@ -63,17 +67,20 @@ export default defineNuxtPlugin((nuxtApp) => { ? (inner: Transport) => new OfflineTransport({ inner, beaconUrl: `${apiUrl}/api/v1/ingest`, - apiKey: resolveDSN({ dsn: config.dsn }).apiKey, + apiKey: resolveDSN(dsnSource).apiKey, debug: config.debug, }) : undefined; hub.init({ dsn: config.dsn, + apiUrl: config.apiUrl, + apiKey: config.apiKey, service: config.service ?? 'nuxt', environment: config.environment, release: config.release, debug: config.debug, + tracesSampleRate: config.tracesSampleRate, transportWrapper, integrations: [ new GlobalErrorIntegration(), diff --git a/packages/nuxt/src/runtime/composables.ts b/packages/nuxt/src/runtime/composables.ts new file mode 100644 index 0000000..7243d81 --- /dev/null +++ b/packages/nuxt/src/runtime/composables.ts @@ -0,0 +1,24 @@ +import { hub } from '@logtide/core'; +import type { Breadcrumb } from '@logtide/types'; + +/** + * Composable for manual LogTide capture from anywhere in a Nuxt app. + * + * Auto-imported by `@logtide/nuxt`, so it is available without an explicit + * import: + * + * @example + * ```ts + * const { captureLog, captureError, addBreadcrumb } = useLogtide(); + * captureLog('info', 'Checkout started', { cartId }); + * ``` + */ +export function useLogtide() { + return { + addBreadcrumb: (breadcrumb: Breadcrumb): void => hub.addBreadcrumb(breadcrumb), + captureLog: (level: string, message: string, metadata?: Record): void => + hub.captureLog(level, message, metadata), + captureError: (error: unknown, metadata?: Record): void => + hub.captureError(error, metadata), + }; +} diff --git a/packages/nuxt/src/runtime/server-plugin.ts b/packages/nuxt/src/runtime/server-plugin.ts index 0b8006f..2adfb35 100644 --- a/packages/nuxt/src/runtime/server-plugin.ts +++ b/packages/nuxt/src/runtime/server-plugin.ts @@ -28,21 +28,27 @@ function breadcrumbsToEvents(scope: Scope): SpanEvent[] { */ export default defineNitroPlugin((nitroApp) => { const config = useRuntimeConfig().logtide as { - dsn: string; + dsn?: string; + apiUrl?: string; + apiKey?: string; service?: string; environment?: string; release?: string; debug?: boolean; + tracesSampleRate?: number; }; - if (!config?.dsn) return; + if (!config?.dsn && !config?.apiUrl) return; hub.init({ dsn: config.dsn, + apiUrl: config.apiUrl, + apiKey: config.apiKey, service: config.service ?? 'nuxt', environment: config.environment, release: config.release, debug: config.debug, + tracesSampleRate: config.tracesSampleRate, integrations: [new ConsoleIntegration(), new GlobalErrorIntegration()], }); diff --git a/packages/nuxt/tests/composables.test.ts b/packages/nuxt/tests/composables.test.ts new file mode 100644 index 0000000..927d40f --- /dev/null +++ b/packages/nuxt/tests/composables.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { hub } from '@logtide/core'; +import { useLogtide } from '../src/runtime/composables'; + +describe('useLogtide', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('exposes addBreadcrumb / captureLog / captureError', () => { + const api = useLogtide(); + expect(typeof api.addBreadcrumb).toBe('function'); + expect(typeof api.captureLog).toBe('function'); + expect(typeof api.captureError).toBe('function'); + }); + + it('forwards captureLog to the hub', () => { + const spy = vi.spyOn(hub, 'captureLog').mockImplementation(() => {}); + useLogtide().captureLog('info', 'hello', { a: 1 }); + expect(spy).toHaveBeenCalledWith('info', 'hello', { a: 1 }); + }); + + it('forwards captureError to the hub', () => { + const spy = vi.spyOn(hub, 'captureError').mockImplementation(() => {}); + const err = new Error('x'); + useLogtide().captureError(err, { mechanism: 'manual' }); + expect(spy).toHaveBeenCalledWith(err, { mechanism: 'manual' }); + }); + + it('forwards addBreadcrumb to the hub', () => { + const spy = vi.spyOn(hub, 'addBreadcrumb').mockImplementation(() => {}); + const bc = { type: 'custom' as const, message: 'bc', timestamp: 1 }; + useLogtide().addBreadcrumb(bc); + expect(spy).toHaveBeenCalledWith(bc); + }); +}); diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 500cbe8..f0ba6f8 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/sveltekit", - "version": "0.10.0", + "version": "0.11.0", "description": "LogTide SDK integration for SvelteKit — handle, handleError, handleFetch hooks", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/types/package.json b/packages/types/package.json index 6d6dd39..a928a01 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@logtide/types", - "version": "0.10.0", + "version": "0.11.0", "description": "Shared type definitions for the LogTide SDK ecosystem", "type": "module", "main": "./dist/index.cjs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88c5670..1691989 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,9 +90,15 @@ importers: specifier: workspace:* version: link:../types devDependencies: + '@types/react': + specifier: ^18.3.0 + version: 18.3.28 jsdom: specifier: ^28.1.0 version: 28.1.0 + react: + specifier: ^18.3.0 + version: 18.3.1 tsup: specifier: ^8.5.1 version: 8.5.1(jiti@2.6.1)(postcss@8.5.8)(tsx@4.21.0)(typescript@5.8.3)(yaml@2.8.3)