diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ed77e2bf7..9c7358073 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,6 +10,7 @@ # Session Replay Packages (Browser) # Owned by the Session Replay SDK team +/packages/amp-semantic/ @amplitude/session-replay-sdk /packages/session-replay-browser/ @amplitude/session-replay-sdk /packages/plugin-session-replay-browser/ @amplitude/session-replay-sdk /packages/segment-session-replay-plugin/ @amplitude/session-replay-sdk diff --git a/packages/amp-semantic/README.md b/packages/amp-semantic/README.md new file mode 100644 index 000000000..96ebc3121 --- /dev/null +++ b/packages/amp-semantic/README.md @@ -0,0 +1,44 @@ +# @amplitude/amp-semantic + +Package foundation for semantic event capture in Amplitude. This increment establishes +workspace packaging, CommonJS/ESM builds, TypeScript declarations, linting, tests, and +the shared event envelope. It does not collect or emit events. + +## Public contracts + +- `SEMANTIC_VERSION`: payload envelope version (`1`). +- `SemanticIdentity`: `tab_id` and `document_id` correlation fields. +- `SemanticEventEnvelope`: a typed name, timestamp, and payload + containing the caller's fields plus version and identity. + +No event families are defined or registered. The package has no browser listeners, +DOM observers, storage access, identity generation, capture runtime, or Session Replay +integration. It is not yet a usable capture SDK. + +## Incremental implementation + +1. **This PR:** package structure and shared envelope contracts. +2. **Context and visibility:** capture runtime, sampling/caps, identity generation, + recorders, event schemas, Session Replay integration, and a local test page. +3. **Dialog and notice:** rrweb mutation input, DOM enrichment, and feedback recorders. +4. Additional event families in later PRs. + +The existing full implementation remains available in PR #2082. Each event increment +should add its contracts, implementation, documentation, and validation together. + +## Development + +From the repository root: + +```sh +pnpm install +pnpm build +pnpm --filter @amplitude/amp-semantic typecheck +pnpm --filter @amplitude/amp-semantic test --runInBand +pnpm --filter @amplitude/amp-semantic lint +``` + +The package build cleans `lib` before generating `lib/cjs` and `lib/esm`, avoiding stale +recorders when switching between incremental branches. Runtime dependencies are limited +to the workspace-standard TypeScript helper library; event-specific dependencies arrive +with their implementations. diff --git a/packages/amp-semantic/jest.config.js b/packages/amp-semantic/jest.config.js new file mode 100644 index 000000000..49ef4c4ae --- /dev/null +++ b/packages/amp-semantic/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + ...require('../../jest.config.js'), + displayName: '@amplitude/amp-semantic', + rootDir: '.', + testEnvironment: 'node', +}; diff --git a/packages/amp-semantic/package.json b/packages/amp-semantic/package.json new file mode 100644 index 000000000..d2934c285 --- /dev/null +++ b/packages/amp-semantic/package.json @@ -0,0 +1,36 @@ +{ + "name": "@amplitude/amp-semantic", + "version": "0.1.0", + "description": "Shared package foundation for Amplitude semantic event capture", + "license": "MIT", + "main": "lib/cjs/index.js", + "module": "lib/esm/index.js", + "types": "lib/esm/index.d.ts", + "sideEffects": false, + "files": [ + "lib" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "clean": "rimraf lib", + "build": "pnpm clean && pnpm build:es5 && pnpm build:esm", + "build:es5": "tsc -p tsconfig.es5.json", + "build:esm": "tsc -p tsconfig.esm.json", + "test": "jest", + "lint": "eslint '{src,test}/**/*.ts'", + "typecheck": "tsc -p tsconfig.json" + }, + "dependencies": { + "tslib": "^2.4.1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/amplitude/Amplitude-TypeScript.git" + }, + "homepage": "https://github.com/amplitude/Amplitude-TypeScript", + "devDependencies": { + "rimraf": "^3.0.2" + } +} diff --git a/packages/amp-semantic/src/index.ts b/packages/amp-semantic/src/index.ts new file mode 100644 index 000000000..d457e7aea --- /dev/null +++ b/packages/amp-semantic/src/index.ts @@ -0,0 +1,2 @@ +export { SEMANTIC_VERSION } from './types'; +export type { SemanticIdentity, SemanticEventEnvelope } from './types'; diff --git a/packages/amp-semantic/src/types.ts b/packages/amp-semantic/src/types.ts new file mode 100644 index 000000000..e2933821f --- /dev/null +++ b/packages/amp-semantic/src/types.ts @@ -0,0 +1,15 @@ +/** Version of the shared semantic payload envelope. */ +export const SEMANTIC_VERSION = 1; + +/** Correlation identifiers supplied by a future capture host. */ +export interface SemanticIdentity { + readonly tab_id: string; + readonly document_id: string; +} + +/** Internal event envelope, independent of any event family or replay transport. */ +export interface SemanticEventEnvelope { + name: Name; + timestamp: number; + payload: Payload & SemanticIdentity & { v: typeof SEMANTIC_VERSION }; +} diff --git a/packages/amp-semantic/test/index.test.ts b/packages/amp-semantic/test/index.test.ts new file mode 100644 index 000000000..eae407980 --- /dev/null +++ b/packages/amp-semantic/test/index.test.ts @@ -0,0 +1,25 @@ +import { SEMANTIC_VERSION, SemanticEventEnvelope, SemanticIdentity } from '../src'; + +// Exercise the public type exports without defining a production event family. +const identity: SemanticIdentity = { tab_id: 'test-tab', document_id: 'test-document' }; +const event: SemanticEventEnvelope<'test-only', { count: number }> = { + name: 'test-only', + timestamp: 123, + payload: { ...identity, v: SEMANTIC_VERSION, count: 1 }, +}; + +test('exports a versioned envelope contract that preserves the host payload', () => { + expect(JSON.parse(JSON.stringify(event))).toEqual({ + name: 'test-only', + timestamp: 123, + payload: { tab_id: 'test-tab', document_id: 'test-document', v: 1, count: 1 }, + }); +}); + +// These compile-time assertions protect event-name/payload pairing and required metadata. +// @ts-expect-error The event name must match the selected family. +const invalidName: typeof event = { ...event, name: 'different-family' }; +// @ts-expect-error Version and identity are mandatory, even for a payload with no domain fields. +const missingMetadata: SemanticEventEnvelope<'test-only', object> = { name: 'test-only', timestamp: 0, payload: {} }; +void invalidName; +void missingMetadata; diff --git a/packages/amp-semantic/tsconfig.es5.json b/packages/amp-semantic/tsconfig.es5.json new file mode 100644 index 000000000..ed5f1c806 --- /dev/null +++ b/packages/amp-semantic/tsconfig.es5.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "module": "commonjs", + "noEmit": false, + "rootDir": "src", + "outDir": "lib/cjs" + } +} diff --git a/packages/amp-semantic/tsconfig.esm.json b/packages/amp-semantic/tsconfig.esm.json new file mode 100644 index 000000000..34f1a5793 --- /dev/null +++ b/packages/amp-semantic/tsconfig.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "compilerOptions": { + "module": "es2020", + "noEmit": false, + "rootDir": "src", + "outDir": "lib/esm" + } +} diff --git a/packages/amp-semantic/tsconfig.json b/packages/amp-semantic/tsconfig.json new file mode 100644 index 000000000..c8d3ccb65 --- /dev/null +++ b/packages/amp-semantic/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*", "test/**/*"], + "compilerOptions": { + "noEmit": true, + "rootDir": "." + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5b8f3b949..a83152fb7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -304,6 +304,16 @@ importers: specifier: ^4.9.4 version: 4.9.5 + packages/amp-semantic: + dependencies: + tslib: + specifier: ^2.4.1 + version: 2.8.1 + devDependencies: + rimraf: + specifier: ^3.0.2 + version: 3.0.2 + packages/analytics-browser: dependencies: '@amplitude/analytics-core':