From ef541b93a43f617e03ed760e40ccfa708e13620d Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Thu, 20 Aug 2026 12:09:26 +0100 Subject: [PATCH 1/2] 0.3.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 192c18e..76d8b5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jackmisner/utm-toolkit", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jackmisner/utm-toolkit", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "devDependencies": { "@testing-library/react": "^16.0.0", diff --git a/package.json b/package.json index 762a2b7..fce07c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jackmisner/utm-toolkit", - "version": "0.2.0", + "version": "0.3.0", "description": "Capture, store, and append UTM tracking parameters", "type": "module", "main": "./dist/index.cjs", From 9d1eb332757a06d4220889b3bdc6dee981d4aa20 Mon Sep 17 00:00:00 2001 From: Jack Misner Date: Thu, 20 Aug 2026 12:13:06 +0100 Subject: [PATCH 2/2] docs: bring README up to date for 0.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caught during release prep. Three gaps, the first consumer-facing: * No Node requirement stated anywhere. engines.node moved from >=16.0.0 to >=20.0.0 in the toolchain upgrade, and the README's platform section covered browsers only — which is a conspicuous omission now that the package ships a /server entry point. Installation now states the requirement and that nothing below Node 22 is actually exercised, and the platform section covers Node explicitly. * The TypeScript Types list omitted every type added in this release: CaptureReport, UtmRejection, UtmRejectionReason, the sanitizer and PII-filter report types, and the two server types. It reads as an inventory of exported types, so an incomplete one is misleading. * captureUtmParametersWithReport had a prose section but no entry in the API Reference, which is where a reader looks for signatures. Added there, along with onMaxLength, valuePattern and lowercaseValues in the captureUtmParameters options example — the options were documented in the sanitization section but absent from the first place anyone reads. Installation also now lists the three entry points, since /server made the subpath exports worth stating rather than leaving to discovery. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori --- README.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3cee4a4..9eed0ff 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,18 @@ A comprehensive TypeScript library for capturing, storing, and appending UTM tra npm install @jackmisner/utm-toolkit ``` +**Requires Node.js >= 20.** The published bundle has no Node-version-specific code, but the test toolchain does, so CI exercises Node 22, 24 and 26 only — nothing below 22 is verified. React is an optional peer dependency (`>=16.8.0`); the core library works without it. + +Three entry points: + +| Import | Contents | +|---|---| +| `@jackmisner/utm-toolkit` | Capture, sanitize, PII filtering, storage, outbound links | +| `@jackmisner/utm-toolkit/react` | Hook, provider and components (needs React) | +| `@jackmisner/utm-toolkit/server` | DOM-free normalization for ingest endpoints | + +All three ship dual ESM/CJS builds with TypeScript declarations. + ## Quick Start ### Basic Usage (Framework-Agnostic) @@ -133,10 +145,29 @@ const params = captureUtmParameters(url, { stripHtml: true, // Remove < > " ' ` (default: true) stripControlChars: true, // Remove control characters (default: true) maxLength: 200, // Truncate values (default: 200) + onMaxLength: 'truncate', // or 'drop' to discard an over-length value + valuePattern: /^[a-z0-9_-]+$/, // Gate: values not matching become '' }, }); + +// Fold values so LinkedIn and linkedin are one campaign. +// Runs before the sanitize and PII gates, so their patterns see folded input. +const params = captureUtmParameters(url, { lowercaseValues: true }); +``` + +#### `captureUtmParametersWithReport(url?, options?)` + +Same pipeline as `captureUtmParameters`, but reports what was discarded. Use it when `{}` needs to mean something more specific than "no campaign" — see [Telling "No Campaign" Apart From "Campaign Rejected"](#telling-no-campaign-apart-from-campaign-rejected). + +```typescript +const { params, rejected, invalidUrl } = captureUtmParametersWithReport(url, { + piiFiltering: { enabled: true }, +}); +// rejected: [{ key: 'utm_source', reason: 'pii', patternName: 'email' }] ``` +`captureUtmParameters` delegates to this and returns `.params`, so the two cannot drift apart. + #### `storeUtmParameters(params, options?)` Store UTM parameters in browser storage. @@ -877,14 +908,39 @@ import type { TouchType, ValidationResult, UseUtmTrackingReturn, + + // Capture reporting + CaptureReport, + UtmRejection, + UtmRejectionReason, + SanitizeRejection, + SanitizeValueResult, + PiiRejection, + FilterValueResult, } from '@jackmisner/utm-toolkit'; + +// Server entry point +import type { + ServerNormalizeOptions, + ServerNormalizeResult, + UtmRejection, + UtmRejectionReason, +} from '@jackmisner/utm-toolkit/server'; ``` -## Browser Support +## Platform Support + +**Browsers** - All modern browsers (Chrome, Firefox, Safari, Edge) -- Requires `sessionStorage` or `localStorage` support -- SSR-safe (returns empty/null values on server) +- Storage helpers require `sessionStorage` or `localStorage`; capture and outbound link building do not +- SSR-safe (returns empty/null values on the server) + +**Node.js** + +- `engines.node` is `>=20.0.0`; CI exercises 22, 24 and 26 +- The root entry is DOM-free at runtime — every `window` access sits behind a guard — so it imports and runs in Node without a DOM +- `@jackmisner/utm-toolkit/server` is the supported surface for server use: it cannot reach storage or the DOM, and that restriction is enforced by a test rather than only documented ## Migration from Existing Projects