├── .release-please-manifest.json ├── .github ├── CODEOWNERS ├── actions │ └── setup │ │ └── action.yml ├── workflows │ ├── lint.yml │ ├── nodejs.yml │ ├── release.yml │ └── release_notes.yml ├── scripts │ ├── pr_list.mjs │ ├── util.mjs │ ├── release_notes.mjs │ └── highlights.mjs ├── dependabot.yml └── pull_request_template.md ├── .gitignore ├── .prettierrc.json ├── tsconfig.json ├── release-please-config.json ├── README.md ├── package.json ├── .eslintrc.json ├── src ├── redact.ts └── index.ts ├── test ├── redact.ts └── index.ts ├── HISTORY.md └── LICENSE /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "7.0.0" 3 | } 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Everything 2 | * @mongodb-js/dbx-node-devs 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | node_modules 3 | dist 4 | coverage 5 | .esm-wrapper.mjs 6 | lib/ 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "tabWidth": 2, 4 | "printWidth": 100, 5 | "arrowParens": "avoid", 6 | "trailingComma": "none" 7 | } -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: 'Installs node, driver dependencies, and builds source' 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - uses: actions/setup-node@v4 8 | with: 9 | node-version: 'lts/*' 10 | registry-url: 'https://registry.npmjs.org' 11 | - run: npm install -g npm@latest 12 | shell: bash 13 | - run: npm ci 14 | shell: bash 15 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | test: 9 | name: Lint 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v5 13 | - name: Use Node.js 24 14 | uses: actions/setup-node@v5 15 | with: 16 | node-version: 24 17 | - name: Install Dependencies 18 | run: npm install 19 | - name: Lint 20 | run: npm run lint 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "downlevelIteration": true, 5 | "sourceMap": true, 6 | "strict": true, 7 | "declaration": true, 8 | "removeComments": true, 9 | "target": "es2023", 10 | "lib": [ 11 | "es2023" 12 | ], 13 | "outDir": "./lib", 14 | "moduleResolution": "node", 15 | "module": "commonjs" 16 | }, 17 | "include": [ 18 | "./src/**/*" 19 | ], 20 | "exclude": [ 21 | "./src/**/*.spec.*" 22 | ] 23 | } -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "pull-request-title-pattern": "chore${scope}: release ${version}", 4 | "pull-request-header": "Please run the release_notes action before releasing to generate release highlights", 5 | "packages": { 6 | ".": { 7 | "include-component-in-tag": false, 8 | "changelog-path": "HISTORY.md", 9 | "release-type": "node", 10 | "bump-minor-pre-major": false, 11 | "bump-patch-for-minor-pre-major": false, 12 | "draft": false, 13 | "prerelease": true 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | os: [ubuntu-latest, macos-latest, windows-latest] 12 | node-version: ["20.19.0", 22.x, 24.x, latest] 13 | runs-on: ${{matrix.os}} 14 | steps: 15 | - uses: actions/checkout@v5 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v5 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - name: Install Dependencies 21 | run: npm install 22 | - name: Test 23 | run: npm test 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | workflow_dispatch: {} 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | id-token: write 10 | 11 | name: release 12 | 13 | jobs: 14 | release-please: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - id: release 18 | uses: googleapis/release-please-action@v4 19 | 20 | # If release-please created a release, publish to npm 21 | - if: ${{ steps.release.outputs.release_created }} 22 | uses: actions/checkout@v5 23 | - if: ${{ steps.release.outputs.release_created }} 24 | name: actions/setup 25 | uses: ./.github/actions/setup 26 | - if: ${{ steps.release.outputs.release_created }} 27 | run: npm publish --provenance --tag latest 28 | env: 29 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mongodb-connection-string-url 2 | 3 | MongoDB connection strings, based on the WhatWG URL API 4 | 5 | ```js 6 | import ConnectionString from 'mongodb-connection-string-url'; 7 | 8 | const cs = new ConnectionString('mongodb://localhost'); 9 | cs.searchParams.set('readPreference', 'secondary'); 10 | console.log(cs.href); // 'mongodb://localhost/?readPreference=secondary' 11 | ``` 12 | 13 | ## Deviations from the WhatWG URL package 14 | 15 | - URL parameters are case-insensitive 16 | - The `.host`, `.hostname` and `.port` properties cannot be set, and reading 17 | them does not return meaningful results (and are typed as `never`in TypeScript) 18 | - The `.hosts` property contains a list of all hosts in the connection string 19 | - The `.href` property cannot be set, only read 20 | - There is an additional `.isSRV` property, set to `true` for `mongodb+srv://` 21 | - There is an additional `.clone()` utility method on the prototype 22 | 23 | ## LICENSE 24 | 25 | Apache-2.0 26 | -------------------------------------------------------------------------------- /.github/scripts/pr_list.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import * as url from 'node:url'; 3 | import * as fs from 'node:fs/promises'; 4 | import * as path from 'node:path'; 5 | import { getCurrentHistorySection, output } from './util.mjs'; 6 | 7 | const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); 8 | const historyFilePath = path.join(__dirname, '..', '..', 'HISTORY.md'); 9 | 10 | /** 11 | * @param {string} history 12 | * @returns {string[]} 13 | */ 14 | function parsePRList(history) { 15 | const prRegexp = /node-mongodb-native\/issues\/(?\d+)\)/iu; 16 | return Array.from( 17 | new Set( 18 | history 19 | .split('\n') 20 | .map(line => prRegexp.exec(line)?.groups?.prNum ?? '') 21 | .filter(prNum => prNum !== '') 22 | ) 23 | ); 24 | } 25 | 26 | const historyContents = await fs.readFile(historyFilePath, { encoding: 'utf8' }); 27 | 28 | const currentHistorySection = getCurrentHistorySection(historyContents); 29 | 30 | const prs = parsePRList(currentHistorySection); 31 | 32 | await output('pr_list', prs.join(',')); 33 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | - package-ecosystem: "npm" # See documentation for possible values 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "monthly" 16 | ignore: 17 | # chai is esmodule only. 18 | - dependency-name: "chai" 19 | versions: [">=5.0.0"] 20 | # we ignore TS as a part of quarterly dependency updates. 21 | - dependency-name: "typescript" 22 | - dependency-name: "@types/node" 23 | versions: [">=24.0.0"] 24 | groups: 25 | prod-dependencies: 26 | dependency-type: "production" 27 | applies-to: version-updates 28 | update-types: 29 | - "minor" 30 | - "patch" 31 | development-dependencies: 32 | dependency-type: "development" 33 | applies-to: version-updates 34 | update-types: 35 | - "minor" 36 | - "patch" 37 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | #### Summary of Changes 4 | 5 | 6 | 7 | ##### Notes for Reviewers 8 | 9 | 14 | 15 | #### What is the motivation for this change? 16 | 17 | 21 | 22 | ### Release Highlight 23 | 24 | 31 | 32 | 33 | 34 | ### Release notes highlight 35 | 36 | 37 | 38 | ### Double check the following 39 | 40 | - [ ] Lint is passing (`npm run check:lint`) 41 | - [ ] Self-review completed using the [steps outlined here](https://github.com/mongodb/node-mongodb-native/blob/HEAD/CONTRIBUTING.md#reviewer-guidelines) 42 | - [ ] PR title follows the [correct format](https://www.conventionalcommits.org/en/v1.0.0/): `type(NODE-xxxx)[!]: description` 43 | - Example: `feat(NODE-1234)!: rewriting everything in coffeescript` 44 | - [ ] Changes are covered by tests 45 | - [ ] New TODOs have a related JIRA ticket 46 | -------------------------------------------------------------------------------- /.github/scripts/util.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import * as process from 'node:process'; 3 | import * as fs from 'node:fs/promises'; 4 | 5 | export async function output(key, value) { 6 | const { GITHUB_OUTPUT = '' } = process.env; 7 | const output = `${key}=${value}\n`; 8 | console.log('outputting:', output); 9 | 10 | if (GITHUB_OUTPUT.length === 0) { 11 | // This is always defined in Github actions, and if it is not for some reason, tasks that follow will fail. 12 | // For local testing it's convenient to see what scripts would output without requiring the variable to be defined. 13 | console.log('GITHUB_OUTPUT not defined, printing only'); 14 | return; 15 | } 16 | 17 | const outputFile = await fs.open(GITHUB_OUTPUT, 'a'); 18 | await outputFile.appendFile(output, { encoding: 'utf8' }); 19 | await outputFile.close(); 20 | } 21 | 22 | /** 23 | * @param {string} historyContents 24 | * @returns {string} 25 | */ 26 | export function getCurrentHistorySection(historyContents) { 27 | /** Markdown version header */ 28 | const VERSION_HEADER = /^#.+\(\d{4}-\d{2}-\d{2}\)$/g; 29 | 30 | const historyLines = historyContents.split('\n'); 31 | 32 | // Search for the line with the first version header, this will be the one we're releasing 33 | const headerLineIndex = historyLines.findIndex(line => VERSION_HEADER.test(line)); 34 | if (headerLineIndex < 0) throw new Error('Could not find any version header'); 35 | 36 | console.log('Found markdown header current release', headerLineIndex, ':', historyLines[headerLineIndex]); 37 | 38 | // Search lines starting after the first header, and add back the offset we sliced at 39 | const nextHeaderLineIndex = historyLines 40 | .slice(headerLineIndex + 1) 41 | .findIndex(line => VERSION_HEADER.test(line)) + headerLineIndex + 1; 42 | if (nextHeaderLineIndex < 0) throw new Error(`Could not find previous version header, searched ${headerLineIndex + 1}`); 43 | 44 | console.log('Found markdown header previous release', nextHeaderLineIndex, ':', historyLines[nextHeaderLineIndex]); 45 | 46 | return historyLines.slice(headerLineIndex, nextHeaderLineIndex).join('\n'); 47 | } 48 | -------------------------------------------------------------------------------- /.github/scripts/release_notes.mjs: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | import * as url from 'node:url'; 3 | import * as fs from 'node:fs/promises'; 4 | import * as path from 'node:path'; 5 | import * as process from 'node:process'; 6 | import * as semver from 'semver'; 7 | import { getCurrentHistorySection, output } from './util.mjs'; 8 | 9 | const { HIGHLIGHTS = '' } = process.env; 10 | if (HIGHLIGHTS === '') throw new Error('HIGHLIGHTS cannot be empty'); 11 | 12 | const { highlights } = JSON.parse(HIGHLIGHTS); 13 | 14 | const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); 15 | const historyFilePath = path.join(__dirname, '..', '..', 'HISTORY.md'); 16 | const packageFilePath = path.join(__dirname, '..', '..', 'package.json'); 17 | 18 | const historyContents = await fs.readFile(historyFilePath, { encoding: 'utf8' }); 19 | 20 | const currentHistorySection = getCurrentHistorySection(historyContents); 21 | 22 | const version = semver.parse( 23 | JSON.parse(await fs.readFile(packageFilePath, { encoding: 'utf8' })).version 24 | ); 25 | if (version == null) throw new Error(`could not create semver from package.json`); 26 | 27 | console.log('\n\n--- history entry ---\n\n', currentHistorySection); 28 | 29 | const currentHistorySectionLines = currentHistorySection.split('\n'); 30 | const header = currentHistorySectionLines[0]; 31 | const history = currentHistorySectionLines.slice(1).join('\n').trim(); 32 | 33 | const releaseNotes = `${header} 34 | 35 | The MongoDB Node.js team is pleased to announce version ${version.version} of the \`mongodb-connection-string-url\` package! 36 | 37 | ${highlights} 38 | ${history} 39 | ## Documentation 40 | 41 | * [Changelog](https://github.com/mongodb-js/-connection-string-url/blob/v${version.version}/HISTORY.md) 42 | 43 | We invite you to try the \`mongodb-connection-string-url\` library immediately, and report any issues to the [NODE project](https://jira.mongodb.org/projects/NODE). 44 | `; 45 | 46 | const releaseNotesPath = path.join(process.cwd(), 'release_notes.md'); 47 | 48 | await fs.writeFile( 49 | releaseNotesPath, 50 | `:seedling: A new release!\n---\n${releaseNotes}\n---\n`, 51 | { encoding:'utf8' } 52 | ); 53 | 54 | await output('release_notes_path', releaseNotesPath) 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongodb-connection-string-url", 3 | "version": "7.0.0", 4 | "description": "MongoDB connection strings, based on the WhatWG URL API", 5 | "keywords": [ 6 | "password", 7 | "prompt", 8 | "tty" 9 | ], 10 | "homepage": "https://github.com/mongodb-js/mongodb-connection-string-url", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/mongodb-js/mongodb-connection-string-url.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/mongodb-js/mongodb-connection-string-url/issues" 17 | }, 18 | "main": "lib/index.js", 19 | "types": "lib/index.d.ts", 20 | "exports": { 21 | "require": { 22 | "default": "./lib/index.js", 23 | "types": "./lib/index.d.ts" 24 | }, 25 | "import": { 26 | "default": "./.esm-wrapper.mjs", 27 | "types": "./lib/index.d.ts" 28 | } 29 | }, 30 | "files": [ 31 | "LICENSE", 32 | "lib", 33 | "package.json", 34 | "README.md", 35 | ".esm-wrapper.mjs" 36 | ], 37 | "scripts": { 38 | "lint": "ESLINT_USE_FLAT_CONFIG=false eslint \"{src,test}/**/*.ts\"", 39 | "test": "npm run build && nyc mocha --colors -r ts-node/register test/*.ts", 40 | "build": "npm run compile-ts && gen-esm-wrapper . ./.esm-wrapper.mjs", 41 | "prepack": "npm run build", 42 | "compile-ts": "tsc -p tsconfig.json" 43 | }, 44 | "license": "Apache-2.0", 45 | "devDependencies": { 46 | "@types/chai": "^5.0.1", 47 | "@types/mocha": "^10.0.10", 48 | "@types/node": "^22.9.0", 49 | "@typescript-eslint/eslint-plugin": "^8.39.1", 50 | "@typescript-eslint/parser": "^8.39.1", 51 | "chai": "^4.2.0", 52 | "eslint": "^9.33.0", 53 | "eslint-config-prettier": "^10.1.8", 54 | "eslint-plugin-import": "^2.22.0", 55 | "eslint-plugin-node": "^11.1.0", 56 | "eslint-plugin-prettier": "^5.5.4", 57 | "eslint-plugin-promise": "^7.1.0", 58 | "gen-esm-wrapper": "^1.1.3", 59 | "mocha": "^11.0.1", 60 | "nyc": "^17.1.0", 61 | "ts-node": "^10.9.1", 62 | "typescript": "^5.9.2" 63 | }, 64 | "dependencies": { 65 | "@types/whatwg-url": "^13.0.0", 66 | "whatwg-url": "^14.1.0" 67 | }, 68 | "engines": { 69 | "node": ">=20.19.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /.github/scripts/highlights.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import * as process from 'node:process'; 3 | import { output } from './util.mjs'; 4 | 5 | const { 6 | GITHUB_TOKEN = '', 7 | PR_LIST = '', 8 | REPOSITORY = '' 9 | } = process.env; 10 | if (GITHUB_TOKEN === '') throw new Error('GITHUB_TOKEN cannot be empty'); 11 | if (REPOSITORY === '') throw new Error('REPOSITORY cannot be empty') 12 | 13 | const API_REQ_INFO = { 14 | headers: { 15 | Accept: 'application/vnd.github.v3+json', 16 | 'X-GitHub-Api-Version': '2022-11-28', 17 | Authorization: `Bearer ${GITHUB_TOKEN}` 18 | } 19 | } 20 | 21 | const prs = PR_LIST.split(',').map(pr => { 22 | const prNum = Number(pr); 23 | if (Number.isNaN(prNum)) 24 | throw Error(`expected PR number list: ${PR_LIST}, offending entry: ${pr}`); 25 | return prNum; 26 | }); 27 | 28 | /** @param {number} pull_number */ 29 | async function getPullRequestContent(pull_number) { 30 | const startIndicator = 'RELEASE_HIGHLIGHT_START -->'; 31 | const endIndicator = '