diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..2b604fa --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,3 @@ +# Copilot Instructions + +Read and follow the repository guidance in [../AGENTS.md](../AGENTS.md). diff --git a/.github/instructions/ci-cd.instructions.md b/.github/instructions/ci-cd.instructions.md new file mode 100644 index 0000000..984b926 --- /dev/null +++ b/.github/instructions/ci-cd.instructions.md @@ -0,0 +1,39 @@ +--- +applyTo: '.github/workflows/**,release.config.js,package.json,rollup.config.js,typedoc.json' +name: ts-sdp CI/CD +description: Use when reviewing checks, builds, documentation generation, package entry points, or semantic-release behavior. +--- + +# CI/CD Instructions + +## Pull-request checks + +- Read `.github/workflows/pull-request-checks.yml` before changing or describing CI. +- Pull requests install with Yarn, then run `yarn test:lint` and `yarn test:coverage`. +- Prettier, spelling, build, TypeScript validation, docs generation, and the aggregate `yarn test` command are not current pull-request gates. +- Do not claim a check is CI-enforced unless the workflow invokes it. + +## Build and package outputs + +- Rollup builds ESM and CommonJS JavaScript plus bundled TypeScript declarations from `src/index.ts`. +- Keep `package.json` `main`, `module`, `types`, and `exports` aligned with Rollup outputs. +- The npm package includes only `dist/**/*`. +- The package has no runtime `dependencies`; review any proposed runtime package as a compatibility and supply-chain change. +- Generated API files belong under `docs/api/`. +- Cleanup must not delete maintained files under `docs/contributing/` or `docs/knowledge-base/`. + +## Release + +- Pushes to `main` install with Yarn, run `yarn build`, and invoke semantic-release. +- Conventional Commits on `main` determine whether semantic-release publishes and which version increment applies. +- The release configuration publishes to the public npm registry and commits configured release assets. +- The current release workflow builds but does not run tests or generate TypeDoc output. Document that gap accurately. +- Treat changes to parser behavior, serialization, `src/index.ts`, exported signatures, entry points, declarations, or runtime dependencies as compatibility-sensitive. + +## Failure triage and safety + +- Fix deterministic lint, test, type, documentation, or build failures. Do not rerun them hoping for a different result. +- Retry only failures supported by evidence of transient runner, registry, or network problems. +- Treat a release failure on `main` as a coordinated delivery issue. +- Never expose workflow tokens, npm credentials, or secret values. +- Do not run semantic-release or npm publishing locally unless the user explicitly requests a coordinated release. diff --git a/.github/instructions/code-review.instructions.md b/.github/instructions/code-review.instructions.md new file mode 100644 index 0000000..8fe8cad --- /dev/null +++ b/.github/instructions/code-review.instructions.md @@ -0,0 +1,31 @@ +--- +applyTo: 'src/**/*.ts,README.md,AGENTS.md,docs/knowledge-base/**/*.md' +name: ts-sdp Code Review +description: Use when reviewing SDP parsing, models, munging, public exports, tests, or architecture guidance. +--- + +# Code Review Instructions + +## Priorities + +1. Supported and unsupported SDP lines continue to parse and serialize without accidental data loss. +2. Parser registration order and custom `Grammar` extensions remain deterministic. +3. Lines route to the correct session, media, ICE, codec, or fallback collection. +4. Munging helpers keep payload references, secondary codecs, wildcard feedback, and media scope consistent. +5. Changes to `src/index.ts`, exported signatures, accepted syntax, output ordering, errors, package entry points, or declarations include compatibility and semantic-version analysis. +6. Protocol edge cases have focused line tests, model tests, or corpus-based round-trip coverage. + +## Checks + +- Each `Line` parser returns `undefined` for non-matches and emits a complete line from `toSdpLine()`. +- New line parsers are registered under the correct line type and in an intentional order. +- Unknown lines and custom line types remain available through `otherLines` and survive serialization. +- Session-level and media-level fields serialize in the order defined by their model's `toLines()`. +- Audio/video and application media behavior remains separated where their models differ. +- Codec lines validate numeric or wildcard payload references through `CodecStore`. +- Codec removal handles associated secondary payload types, and feedback changes cover per-codec and wildcard storage. +- Public export changes are intentional and documented. +- JSDoc remains complete where ESLint requires it. Comments explain protocol constraints or surprising behavior rather than restating code. +- Documentation claims are verified against source, tests, package metadata, build configuration, and release configuration. +- Test SDP contains no real ICE credentials, network identifiers, fingerprints, or other sensitive session data. +- No secrets, credentials, private URLs, certificates, `.env` values, or local absolute paths appear in the diff. diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md new file mode 100644 index 0000000..28721a5 --- /dev/null +++ b/.github/instructions/testing.instructions.md @@ -0,0 +1,35 @@ +--- +applyTo: 'src/**/*.spec.ts,src/sdp-corpus/**' +name: ts-sdp Tests +description: Use when writing or reviewing co-located Jest tests and SDP corpus fixtures. +--- + +# Testing Instructions + +## Framework and location + +- Jest runs through `ts-jest` in the environment configured by `jest.config.js`. Check `package.json` and `jest.config.js` instead of pinning tool versions in guidance. +- Keep tests next to source as `src/**/*.spec.ts`. +- Keep reusable SDP fixtures under `src/sdp-corpus/`. +- Use `yarn test:unit` for focused runtime feedback and `yarn test:coverage` for the pull-request test command. +- Run `yarn transpile:validate` when changing exports, parser types, line classes, model contracts, or munging signatures. + +## Test patterns + +- Name the top-level `describe` after the line, model, parser flow, or munging behavior under test. +- Use behavior-focused `it('should ...')` descriptions. +- Test both successful parsing and a representative non-match for line parsers. +- Assert `toSdpLine()` output for new or changed line models. +- Use corpus round trips when ordering and preservation across a complete SDP matter. +- Keep tests independent. Avoid mutating `DefaultSdpGrammar` unless the test is specifically verifying global parser registration. +- Use synthetic, non-sensitive SDP values. Never copy real ICE credentials, fingerprints, addresses, or user session captures. + +## Required coverage by change type + +- Grammar changes: parser order, custom parser behavior, fallback handling, and non-match behavior. +- Line changes: accepted forms, rejected forms, field extraction, and serialization. +- Session or media model changes: `addLine()` routing and `toLines()` ordering. +- Codec changes: numeric and wildcard payload references, codec aggregation, secondary codec relationships, and serialization. +- Munging changes: both `Sdp` and media-description entry points where supported, matching and non-matching cases, and return flags. +- Public type changes: TypeScript validation plus runtime tests where behavior also changes. +- Regression fixes: the smallest fixture or inline SDP that fails before the fix and passes after it. diff --git a/.github/skills/pr-description/SKILL.md b/.github/skills/pr-description/SKILL.md new file mode 100644 index 0000000..7d1a535 --- /dev/null +++ b/.github/skills/pr-description/SKILL.md @@ -0,0 +1,66 @@ +--- +name: pr-description +description: Draft accurate ts-sdp pull-request descriptions from the repository template, committed changes, and observed test evidence. +--- + +# PR Description + +Create a concise, reader-friendly pull-request description that helps reviewers understand why the change matters, what behavior changes, and how it was verified. + +## Sources + +Read these before drafting: + +1. `.github/pull_request_template.md` +2. The complete committed diff against the pull-request base branch +3. All branch commits +4. Test output supplied by the author or produced in the current session +5. Public issues or documentation linked by the author + +Repository files and observed results are authoritative. Do not invent motivation, test evidence, issue links, screenshots, SDP compatibility, or release claims. + +## Workflow + +1. Confirm the base branch. Use `main` when no other base is specified. +2. Review the complete branch diff, not only the latest commit. +3. Check `git status`. If uncommitted changes exist, warn the author and exclude them from the pull-request description until committed. +4. Identify effects on grammar registration, line parsing, serialization order, session and media models, codec or ICE handling, munging helpers, public exports, declarations, package formats, runtime dependencies, and release behavior. +5. Ask what manual testing was performed. Request the SDP scenario and result when relevant. If no manual testing was needed, ask the author to confirm why. +6. Ask only for facts that cannot be derived, such as a public issue link, screenshots, or the Generative AI disclosure category. +7. Produce the completed repository template without removing headings or policy checkboxes. Do not reproduce private-only links in newly drafted text. + +## Description rules + +- Under `Description`, start with two to four short bullets that explain the developer or user outcome. +- Lead with why the change is useful, then explain what changed to achieve it. +- Use plain language, active voice, and short sentences. Explain an unavoidable technical term the first time it appears. +- Prefer outcomes such as “documentation commands cannot remove contributor guides” over implementation phrases such as “isolate generated output.” +- Describe behavior and developer outcomes instead of listing changed files or repeating the diff. +- Include implementation details only when they help a reviewer evaluate correctness or compatibility. +- State important non-effects directly, such as “This does not change runtime behavior or the public API.” +- Add a short `Testing` subsection under `Description` with only commands and manual checks that actually ran. +- Summarize warnings or partial results in plain language and identify whether they existed before the change. +- When manual testing was not needed, explain why in one sentence. +- Call out accepted SDP forms, round-trip behavior, public API impact, declaration changes, package compatibility, dependency changes, migration steps, or semantic-version impact only when supported by the diff. +- Preserve the `This change implements...`, breaking-change, certification, and Generative AI sections. +- Keep checkboxes unchecked when evidence or author input is missing. +- Never mark the test certification checkbox without evidence. +- Never select a Generative AI disclosure category for the author. +- Do not add a dedicated risk section. +- Use only public links in this public repository. + +## Wording example + +Avoid: + +> Preserve maintained documentation by isolating generated TypeDoc output and extend formatting and spelling checks to the new documentation. + +Prefer: + +> Keep generated API documentation separate from contributor guides so documentation commands cannot remove maintained content. Include the new guides in formatting and spell checks. + +## Output + +Return one Markdown block that can be pasted into GitHub. + +After the block, list unresolved author questions separately. Do not put `TBD` placeholders in an otherwise final description. diff --git a/.prettierignore b/.prettierignore index 0e80a3c..986d352 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,6 @@ # package.json is formatted by package managers, so we ignore it here -package.json \ No newline at end of file +package.json + +# Generated API documentation and the repository policy template are maintained separately +docs/api/ +.github/pull_request_template.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4634e6c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,175 @@ +# AGENTS.md + +## Project Overview + +`@webex/ts-sdp` is a public TypeScript library for parsing, manipulating, and serializing Session Description Protocol (SDP). It provides typed models and helpers for working with session and media descriptions. + +Start with [README.md](README.md) for usage and [docs/knowledge-base/README.md](docs/knowledge-base/README.md) for architecture, public boundaries, build outputs, testing, and release behavior. + +## General Guidelines + +- Be direct, analytical, and evidence-based. +- Derive behavior from `src/`, co-located tests, SDP corpus fixtures, and checked-in configuration. +- Treat `src/index.ts` as the public source export boundary and `package.json` as the authority for package entry points and runtime dependencies. +- Read the knowledge base before broad architecture searches. +- When guidance conflicts, current source and repository configuration win. + +## Agent Rules + +1. Cite repository paths, config keys, tests, or stable public links for factual claims. +2. Use public sources only. Do not add private issue links, documentation hosts, consumer chains, or enterprise-only skills. +3. Never commit secrets, credentials, private keys, certificate material, or decrypted `.env` content. +4. Do not add absolute local paths to committed files. +5. Create commits or push changes only when the user explicitly asks. + +### Committing files + +- Stage explicit paths only. Do not use `git add .` or `git add -A`. +- Stage only files changed for the current task. +- Before committing, inspect `git status` and the staged diff for secrets, keys, certificates, local paths, generated output, or unrelated changes. + +### Knowledge base + +The curated knowledge base under `docs/knowledge-base/` is maintained separately from generated API documentation under `docs/api/`. + +- Read it for parser flow, model ownership, public exports, compatibility boundaries, and delivery behavior. +- Verify implementation details against source, tests, package metadata, and configuration. +- Ask before adding articles beyond the maintained index and architecture overview. +- Keep public repository content limited to publicly verifiable facts and links. + +## Repository Layout + +| Path | Purpose | +| ---------------------- | ------------------------------------------------------ | +| `src/index.ts` | Public source exports | +| `src/parser.ts` | Grammar registration, line parsing, and model assembly | +| `src/lines/` | Typed SDP line models | +| `src/model/` | Session, media, codec, ICE, and block models | +| `src/munge.ts` | Helpers for changing parsed SDP models | +| `src/**/*.spec.ts` | Co-located Jest unit tests | +| `src/sdp-corpus/` | SDP fixtures used for parsing and round-trip tests | +| `rollup.config.js` | ESM, CommonJS, and declaration builds | +| `.github/workflows/` | Pull-request checks and main-branch publishing | +| `docs/knowledge-base/` | Maintained architecture guidance | +| `docs/api/` | Generated TypeDoc output | + +## Setup + +Use the Node version selected by `.nvmrc` and the Yarn version pinned by `package.json`. + +```bash +nvm use +yarn install --frozen-lockfile +yarn prepare +``` + +Do not use npm for dependency installation. `package.json` sets `engines.npm` to `please-use-yarn`. + +## Development Commands + +Run commands from the repository root. + +| Command | Purpose | +| ------------------------- | ----------------------------------------------------------------------------------- | +| `yarn build` | Clean generated output and build ESM, CommonJS, and declaration artifacts | +| `yarn watch` | Run the Rollup build in watch mode | +| `yarn transpile:validate` | Type-check with TypeScript without emitting files | +| `yarn test:unit` | Run Jest unit tests | +| `yarn test:coverage` | Run Jest with coverage | +| `yarn test:lint` | Run ESLint on TypeScript source | +| `yarn test:prettier` | Check source and maintained Markdown formatting | +| `yarn test:spelling` | Spellcheck maintained source and documentation | +| `yarn docs` | Replace generated TypeDoc output under `docs/api/` while preserving maintained docs | +| `yarn fix` | Apply configured Prettier and ESLint fixes | + +`yarn test` runs the build and every `test:*` script. Run `yarn transpile:validate` separately because the aggregate script does not include it. + +## Coding Conventions + +### TypeScript + +- TypeScript strict mode, `noImplicitAny`, `strictNullChecks`, and `noImplicitReturns` are enabled. +- The compiler targets ES2015 and emits ESNext modules for Rollup. +- Keep parser functions deterministic and return `undefined` when a line parser does not match. +- Preserve unsupported SDP lines through the fallback and `otherLines` paths unless a compatibility change is intentional. +- Avoid `any` unless an existing boundary requires it and the reason is documented. + +### Formatting, comments, and JSDoc + +- Prettier uses a 100-character print width, single quotes, two-space indentation, and ES5 trailing commas. +- ESLint uses Airbnb, TypeScript, Jest, JSDoc, and Prettier rules. +- JSDoc is required for functions, classes, and methods unless a narrow existing suppression applies. +- Comments explain intent, protocol constraints, or counterintuitive behavior. Do not narrate obvious code or change history. +- Keep public API descriptions concise and include required `@param` and `@returns` tags. + +### SDP behavior + +- Every `Line` subclass owns parsing from its line value and serialization to a complete SDP line. +- Parser registration order matters because parsers for one line type are attempted in insertion order. +- `parseToModel` starts a new media block at each `MediaLine` and supports audio, video, and application media types. +- Model `toLines()` ordering is part of serialization behavior. Review ordering changes as compatibility-sensitive. +- Codec lines must reference a payload type declared by the media line, except wildcard RTCP feedback. +- Munging helpers must update related structures consistently, such as secondary codec payload types. + +## Public API and Compatibility + +`src/index.ts` re-exports the parser, typed lines, models, munging helpers, regex helpers, and utilities. Changes to those exports, exported signatures, serialization output, accepted grammar, error behavior, or package entry points can affect consumers and semantic versioning. + +The package publishes ESM, CommonJS, and bundled declaration entry points from `dist/`. It has no runtime `dependencies`; build and test packages are development-only. + +Unknown input lines are preserved internally and serialized through `otherLines`, but `UnknownLine` is not exported by `src/lines/index.ts`. Treat that fallback and the lack of subpath exports as compatibility boundaries. + +See [the architecture overview](docs/knowledge-base/architecture/ts-sdp-overview.md) for the parser pipeline, model graph, extension points, and key modules. + +## Testing + +- Jest runs through `ts-jest` in the jsdom environment configured by `jest.config.js`. +- Tests are co-located as `src/**/*.spec.ts`. +- Parser tests use `src/sdp-corpus/` fixtures to check browser-produced SDP, custom grammar parsers, media-level connection lines, wildcard feedback, and round-trip behavior. +- Line tests cover regex parsing and serialization for individual SDP forms. +- Model and munging tests cover codec associations, wildcard feedback, secondary codec removal, candidate filtering, and media-specific behavior. +- Add a regression test for every parser, line-ordering, model-routing, munging, or public behavior change. +- Run `yarn transpile:validate` for exported type or model changes in addition to Jest. + +Path-scoped detail lives in [.github/instructions/testing.instructions.md](.github/instructions/testing.instructions.md). + +## Code Review Priorities + +1. Preserve parse-to-serialize round trips for supported and unsupported lines. +2. Preserve parser ordering and custom `Grammar` extension behavior. +3. Route session, media, ICE, codec, and fallback lines to the correct model. +4. Keep munging helpers synchronized with payload references, wildcard feedback, and media scope. +5. Treat exports, declarations, entry points, accepted SDP syntax, output ordering, and thrown errors as compatibility-sensitive. +6. Require focused unit coverage and corpus cases for protocol edge cases. + +Path-scoped detail lives in [.github/instructions/code-review.instructions.md](.github/instructions/code-review.instructions.md). + +## CI/CD + +- Pull requests install with Yarn, then run `yarn test:lint` and `yarn test:coverage` in GitHub Actions. +- Prettier, spelling, TypeScript validation, docs generation, and the aggregate `yarn test` command are not current pull-request gates. +- Pushes to `main` install dependencies, run `yarn build`, and invoke semantic-release. +- semantic-release analyzes Conventional Commits, publishes the public npm package, updates the changelog and package metadata, and commits configured release assets. +- Do not run semantic-release or publish locally unless the user explicitly requests a coordinated release. + +Path-scoped detail lives in [.github/instructions/ci-cd.instructions.md](.github/instructions/ci-cd.instructions.md). + +## Pull Requests + +- Follow [docs/contributing/GIT_CONVENTIONS.md](docs/contributing/GIT_CONVENTIONS.md) for branches and Conventional Commits. +- Use [.github/skills/pr-description/SKILL.md](.github/skills/pr-description/SKILL.md) to draft the existing pull-request template from the complete committed diff and observed test evidence. +- Call out grammar, serialization, model, munging, public API, declaration, package-format, or semantic-version impact when applicable. +- Preserve the template headings and leave Generative AI disclosure choices to the author. + +## Security and Public Scope + +- Do not add secrets, tokens, credentials, private keys, certificates, `.env` values, private URLs, or absolute local paths. +- Do not copy private issue, documentation, CI, or consumer information into this public repository. +- Treat SDP text as potentially sensitive because it can contain network addresses, ICE credentials, and fingerprints. Do not add real session captures, credentials, or identifying data to tests or logs. +- Keep dependency and workflow changes deterministic through `yarn.lock` and review package-source changes carefully. + +## Maintaining This Guidance + +Update this guidance when public exports, parser/model boundaries, scripts, Node or Yarn requirements, tests, build outputs, workflows, or release behavior change. + +If guidance disagrees with current source or configuration, fix the guidance rather than preserving stale assumptions. diff --git a/README.md b/README.md index 72e73f7..0817db2 100644 --- a/README.md +++ b/README.md @@ -2,75 +2,93 @@ `ts-sdp` is a library which allows for parsing, manipulation, and serialization of SDP. -### Examples -##### Parsing +## Examples + +### Parsing + To parse an SDP string, just call the `parse` method. + ```typescript const sdp = parse(sdpOffer); ``` + Note: the given SDP string must terminate each line with either `\r`, `\r\n` or `\n`. -##### Manipulation -Once parsed, the SDP can be manipulated. The model of the parsed object is as follows: -![image](https://sqbu-github.cisco.com/storage/user/8795/files/82d641f2-6446-41ce-89b0-aa3de492a83a) +### Manipulation + +Once parsed, the SDP can be manipulated through its session and media block models. See the [architecture overview](docs/knowledge-base/architecture/ts-sdp-overview.md) for the model graph and ownership boundaries. + +There are munging helper functions available in `src/munge.ts`. For example: -There are munging helper functions available in the `munge.ts` file. For example: ```typescript removeCodec(parsedSdp, 'vp8'); disableRtcpFbValue(parsedSdp, 'goog-remb'); ``` -Manipulation can also be done manually: -```typescript -TODO: more examples -``` -##### Serialization +### Serialization + When done, the SDP can be serialized to a string like so: + ```typescript const sdpStr = sdp.toString(); ``` -#### Grammar -The library currently contains much of the [RGC4566 grammar](https://datatracker.ietf.org/doc/html/rfc4566), in addition to some additional attributes. Any lines without specific parser implementations are still parsed via the `UnknownLine` type. +## Grammar -The grammar is extendable to allow for parsing custom attributes. First, a subclass of `Line` must be defined: -```typescript - class CustomLine extends Line { - value: number; +The library currently contains much of the [RFC 4566 grammar](https://datatracker.ietf.org/doc/html/rfc4566), in addition to some WebRTC-oriented attributes. Any lines without specific parser implementations are preserved by the internal unknown-line fallback. - private static regex = /^foo:([0-9]+)$/; +The grammar is extendable to allow for parsing custom attributes. First, a subclass of `Line` must be defined: - constructor(value: number) { - super(); - this.value = value; - } +```typescript +class CustomLine extends Line { + value: number; - static fromSdpLine(line: string): CustomLine | undefined { - if (!CustomLine.regex.test(line)) { - return undefined; - } + private static regex = /^foo:([0-9]+)$/; - const tokens = line.match(CustomLine.regex) as RegExpMatchArray; - const value = parseInt(tokens[1], 10); - return new CustomLine(value); - } + constructor(value: number) { + super(); + this.value = value; + } - toSdpLine(): string { - return `a=foo:${this.value}`; + static fromSdpLine(line: string): CustomLine | undefined { + if (!CustomLine.regex.test(line)) { + return undefined; } + + const tokens = line.match(CustomLine.regex) as RegExpMatchArray; + const value = parseInt(tokens[1], 10); + return new CustomLine(value); } + + toSdpLine(): string { + return `a=foo:${this.value}`; + } +} ``` + Then the parser must be added to the grammar under the appropriate `LineType`: + ```typescript DefaultSdpGrammar.addParser('a', CustomLine.fromSdpLine); // Result will contain a 'CustomLine' instance in whichever SdpBlock the attribute appears. const result = parse(sdp); ``` + Any implementation of `Grammar` can also be passed to the `parse` method: + ```typescript class MyCustomGrammar extends Grammar { - ... + constructor() { + super(); + this.addParser('a', CustomLine.fromSdpLine); + } } const myGrammar = new MyCustomGrammar(); -const result = parse(sdp, myGrammar); +const result = parse('a=foo:42\n', myGrammar); ``` + +## Contributing and AI-assisted development + +- Read [AGENTS.md](AGENTS.md) for setup, verified commands, coding conventions, testing boundaries, and AI-agent guidance. +- Follow [docs/contributing/GIT_CONVENTIONS.md](docs/contributing/GIT_CONVENTIONS.md) for branch and commit conventions. +- Start with the [knowledge base](docs/knowledge-base/README.md) for parser, model, package, and release architecture. diff --git a/cspell.json b/cspell.json index 549bbf8..c30378f 100644 --- a/cspell.json +++ b/cspell.json @@ -53,13 +53,16 @@ "remb", "rohit", "rport", + "rtcp", "rtcpfb", "rtpmap", "sandboxed", "SAVPF", + "sctp", "sdp", "sdps", "SSDK", + "ssrc", "ssrcs", "tfjs", "TIAS", diff --git a/docs/contributing/GIT_CONVENTIONS.md b/docs/contributing/GIT_CONVENTIONS.md new file mode 100644 index 0000000..ba763c3 --- /dev/null +++ b/docs/contributing/GIT_CONVENTIONS.md @@ -0,0 +1,43 @@ +# Branch and Commit Conventions + +Use these rules for branch names and commit messages. Pull-request description guidance lives in [the PR description skill](../../.github/skills/pr-description/SKILL.md). + +## Branch names + +The repository does not currently enforce a branch-name format. Existing remote branches include both `/` and standalone topic patterns. + +- Start new work from the current `main` branch. +- Prefer `/` for contributor branches. +- Keep names lowercase, short, and hyphen-separated. +- Describe the change rather than copying a long issue title. + +Example: + +```text +developer/parser-round-trip +``` + +## Commit messages + +Commit messages must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). The local Husky commit-message hook runs commitlint with the conventional configuration, and semantic-release analyzes commits that reach `main`. + +Use this format: + +```text +(): +``` + +Common types: + +- `feat`: new behavior that normally produces a minor release +- `fix`: corrected behavior that normally produces a patch release +- `docs`: documentation-only change +- `refactor`: internal restructuring without a behavior change +- `test`: test-only change +- `chore`, `ci`, or `build`: maintenance and delivery work + +Keep the subject direct and lowercase. Use the body when the reason or tradeoff is not clear from the subject. + +Mark an intentional breaking change with `!` after the type or scope, or add a `BREAKING CHANGE:` footer. Breaking changes can produce a major release. + +semantic-release reads commits merged into `main`. Do not assume the pull-request title alone controls the published version. diff --git a/docs/knowledge-base/README.md b/docs/knowledge-base/README.md new file mode 100644 index 0000000..bcde9df --- /dev/null +++ b/docs/knowledge-base/README.md @@ -0,0 +1,15 @@ +# Knowledge Base + +Public, repository-local architecture and onboarding guidance for `@webex/ts-sdp`. Read this index before broad searches about parser flow, grammar extension, model ownership, munging, package boundaries, or release behavior. + +| Link | What you get | +| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | +| [architecture/ts-sdp-overview.md](architecture/ts-sdp-overview.md) | Parsing and serialization flow, grammar extension, line and block models, munging helpers, public exports, builds, tests, and releases | +| [README.md](../../README.md) | Package usage, grammar extension examples, and development links | +| [src/index.ts](../../src/index.ts) | Authoritative public source exports | + +Generated API documentation belongs under `docs/api/`. Cleanup commands must preserve this knowledge base and `docs/contributing/`. + +Keep articles short and verify implementation details against current source, tests, package metadata, and configuration. Use only public links and publicly verifiable relationships. + +New articles belong under `architecture/` or `questions/` and must be linked here. Agents should ask before capturing additional repeatable knowledge. diff --git a/docs/knowledge-base/architecture/ts-sdp-overview.md b/docs/knowledge-base/architecture/ts-sdp-overview.md new file mode 100644 index 0000000..6c7a52c --- /dev/null +++ b/docs/knowledge-base/architecture/ts-sdp-overview.md @@ -0,0 +1,165 @@ +# ts-sdp Architecture + +## Scope + +`@webex/ts-sdp` provides typed parsing, manipulation, and serialization for Session Description Protocol (SDP). It models session-level data and supported audio, video, and application media sections. The implementation covers a useful subset of [RFC 4566](https://datatracker.ietf.org/doc/html/rfc4566) plus additional attributes used during WebRTC media negotiation. + +This page describes only boundaries visible in the public repository. + +## Parse and serialization flow + +```mermaid +flowchart TD + input["parse(sdp, grammar)"] --> parseLines["parseToLines(sdp, grammar)"] + parseLines --> split["Split input and discard separators"] + split --> lineParts["Read line type and value"] + lineParts --> parsers["Try registered parsers in order"] + parsers --> matched{"Parser matched?"} + matched -->|Yes| typedLine["Append parsed Line"] + matched -->|No| unknownLine["Append UnknownLine from raw input"] + typedLine --> lines["Line array"] + unknownLine --> lines + lines --> parseModel["parseToModel(lines)"] + parseModel --> session["Add session lines to SessionDescription"] + parseModel --> media["Create and fill media blocks"] + session --> sdpModel["Sdp model"] + media --> sdpModel + sdpModel --> serialize["Sdp.toString()"] + serialize --> orderedLines["Collect each block's toLines() output"] + orderedLines --> output["Serialize each line and add SDP line endings"] +``` + +`parse()` in `src/parser.ts` composes two public stages: + +1. `parseToLines()` splits text, derives the one-character line type, and passes the value after `x=` to parsers registered for that type. +2. `parseToModel()` starts with the session block, creates a media block at each `MediaLine`, and routes later lines into that block. + +`Sdp.toString()` asks the session and media blocks for ordered lines and calls each line's `toSdpLine()`. It joins the serialized lines with CRLF and adds CRLF after the final line. CRLF means carriage return plus line feed (`\r\n`), the standard two-character line ending used by the serializer. + +The serializer produces the model's canonical ordering. Tests that need semantic round trips compare line membership rather than requiring input order to remain unchanged. + +## Grammar and custom parsers + +`Grammar` owns a `Map` from SDP line type to an ordered list of parser functions. `addParser()` appends, so registration order is significant when more than one parser could recognize the same value. + +The default grammar registers session fields, media lines, and supported attributes such as codec maps, RTCP feedback, ICE data, bundle groups, simulcast, and SSRC information. `DefaultSdpGrammar` is a shared instance. + +Consumers can extend parsing in either of two ways: + +- Add a parser to `DefaultSdpGrammar` when the extension should affect subsequent default parsing in that runtime. +- Create a `Grammar`, register the desired parsers, and pass it to `parse()` when isolation is required. + +A custom parser receives the line value without the `x=` prefix and returns a `Line` subclass or `undefined`. The subclass must return a complete SDP line from `toSdpLine()`. + +When every registered parser returns `undefined`, `parseToLines()` creates an internal `UnknownLine` from the complete raw line. `UnknownLine` is intentionally absent from `src/lines/index.ts`, but the resulting line remains in a block's `otherLines` collection and serializes unchanged. + +## Line and block models + +```mermaid +flowchart TD + sdp["Sdp"] + sdp --> session["SessionDescription: session-level fields"] + sdp --> mediaList["MediaDescription array"] + mediaList --> media["MediaDescription: shared media fields and IceInfo"] + media --> av["AvMediaDescription: audio, video, and codecs"] + media --> application["ApplicationMediaDescription: data-channel settings"] + av --> codecs["CodecStore containing CodecInfo models"] + session --> lines["Typed Line objects"] + media --> lines +``` + +`Sdp` contains one session description and an array of media descriptions. The shared `MediaDescription` model provides common media and ICE fields, while its two concrete models add audio/video codec data or application data-channel settings. Session and media blocks both collect typed `Line` objects and expose `addLine()` and `toLines()` through the `SdpBlock` interface. + +### Lines + +Concrete classes under `src/lines/` pair a static `fromSdpLine()` parser with `toSdpLine()` serialization. `src/lines/index.ts` is the public export entry point for supported line classes and related types. + +`PayloadTypeRef` represents numeric codec payload types and the wildcard used by RTCP feedback. `CodecStore` keeps wildcard feedback separate from per-codec feedback so wildcard semantics survive serialization. + +### Session block + +`SessionDescription` stores modeled session fields such as version, origin, name, connection, timing, bandwidth, and bundle groups. Unhandled or custom lines go to `otherLines`. + +### Media blocks + +`MediaDescription` owns fields shared by all supported media types, including media type, port, protocol, MID, ICE information, fingerprint, setup, bandwidth, connection, content, and fallback lines. + +`parseToModel()` creates: + +- `AvMediaDescription` for `audio` and `video`, with payload types, codecs, RTP header extensions, RID, simulcast, direction, RTCP mux, SSRC, and SSRC groups. +- `ApplicationMediaDescription` for `application`, with string formats, SCTP port, and maximum message size. + +Other media types currently throw an `Unhandled media type` error. Expanding that set changes accepted input behavior and requires compatibility analysis. + +### Codec and ICE composition + +`AvMediaDescription` initializes one `CodecInfo` for every payload type on its media line. `CodecStore` routes RTP map, format parameters, and RTCP feedback to the matching codec and rejects unknown numeric payload references. Wildcard RTCP feedback is valid without a numeric codec. + +`CodecInfo` combines the codec name, clock rate, encoding parameters, format parameters, feedback, and an optional `apt` relationship to a primary payload type. + +`IceInfo` groups username fragments, passwords, options, and candidates. It participates in the same `SdpBlock` protocol as session and media models. + +## Munging helpers + +`src/munge.ts` provides operations over parsed models: + +- `disableRtcpFbValue()`, `disableRemb()`, and `disableTwcc()` remove feedback across audio/video media or one `AvMediaDescription`. +- `removeCodec()` removes matching codecs case-insensitively and delegates to `removePt()`, which also removes secondary codecs linked through `apt`. +- `retainCodecs()` and `retainCodecsByCodecName()` filter codecs on one audio/video block and report whether anything changed. +- `retainCandidates()` and `retainCandidatesByTransportType()` filter ICE candidates across all media in an `Sdp` or one media block and report whether anything changed. + +The media scopes differ intentionally: codec and RTCP helpers operate on `avMedia`, while candidate helpers operate on every media block. Preserve that distinction unless an API change is explicit. + +## Public exports and compatibility boundaries + +`src/index.ts` re-exports: + +- parser functions, `Grammar`, and `DefaultSdpGrammar` +- supported line classes and related types +- session, media, codec, ICE, and block models +- munging helpers +- regex helpers and utilities + +The package exposes one root entry point and no subpath exports. Compatibility-sensitive changes include: + +- adding, removing, renaming, or changing exported symbols +- changing accepted line syntax or parser precedence +- changing fallback preservation or model routing +- changing serialization order or line formatting +- changing thrown errors for unsupported media or invalid payload references +- changing ESM, CommonJS, declaration paths, or runtime dependencies + +## Build formats and runtime dependencies + +`rollup.config.js` builds from `src/index.ts`: + +- ESM JavaScript under `dist/esm/` +- CommonJS JavaScript under `dist/cjs/` +- bundled declarations under `dist/types/` + +`package.json` maps `import`, `require`, and top-level type resolution to those outputs and publishes only `dist/**/*`. + +There are no runtime `dependencies` in `package.json`. Rollup, TypeScript, Jest, linting, documentation, and release packages are development dependencies. + +## Testing + +Jest uses `ts-jest`, runs in jsdom, and treats `src/` as its root. Tests are co-located as `*.spec.ts`. + +Coverage is distributed across: + +- individual line parsing and serialization +- session, media, codec, and payload models +- parser composition and custom grammar registration +- browser-produced and focused SDP corpus fixtures +- codec, feedback, and candidate munging +- unknown and wildcard round trips + +Pull-request GitHub Actions currently run lint and Jest coverage. Formatting, spelling, TypeScript validation, build, and docs checks remain local checks unless the workflow changes. + +## Release behavior + +Pushes to `main` trigger the package workflow. It installs with Yarn, builds the package, and runs semantic-release. + +`release.config.js` uses the Conventional Commits preset to analyze commits and generate release notes. It publishes `@webex/ts-sdp` to the public npm registry and commits configured release assets such as the changelog, package metadata, maintained/generated docs, and lockfile. + +The workflow currently does not run the Jest suite or generate TypeDoc before semantic-release. Documentation and CI guidance must describe that current behavior rather than implying broader release gates. diff --git a/package.json b/package.json index 5017f56..97f0ff6 100644 --- a/package.json +++ b/package.json @@ -13,17 +13,17 @@ "build": "run-s clean compile", "clean": "yarn transpile:clean", "compile": "rollup -c ./rollup.config.js", - "docs": "rimraf ./docs/* && typedoc", + "docs": "rimraf ./docs/api && typedoc", "fix": "run-s fix:*", - "fix:prettier": "prettier \"src/**/*.ts\" --write", + "fix:prettier": "prettier \"{src,docs,.github}/**/*.{ts,md}\" \"{README,AGENTS}.md\" --write", "fix:lint": "eslint src --ext .ts --fix", "prepare": "husky install", "release": "semantic-release", "test": "run-s build test:*", "test:coverage": "jest --coverage", "test:lint": "eslint src --ext .ts --max-warnings=0", - "test:prettier": "prettier \"src/**/*.ts\" --list-different", - "test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"", + "test:prettier": "prettier \"{src,docs,.github}/**/*.{ts,md}\" \"{README,AGENTS}.md\" --list-different", + "test:spelling": "cspell \"{README.md,AGENTS.md,.github/**/*.md,docs/**/*.md,src/**/*.ts}\"", "test:unit": "jest", "transpile": "tsc", "transpile:clean": "rimraf ./dist", diff --git a/typedoc.json b/typedoc.json index 23d69f2..8a0c1cb 100644 --- a/typedoc.json +++ b/typedoc.json @@ -1,5 +1,5 @@ { "entryPoints": ["src"], "githubPages": false, - "out": "docs" + "out": "docs/api" }