diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 0bc3b42..d401a77 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -5,7 +5,7 @@ updates:
schedule:
interval: daily
time: "10:00"
- open-pull-requests-limit: 10
+ open-pull-requests-limit: 20
commit-message:
prefix: "deps"
prefix-development: "deps(dev)"
diff --git a/.github/workflows/js-test-and-release.yml b/.github/workflows/js-test-and-release.yml
index 2c7a14b..359eb97 100644
--- a/.github/workflows/js-test-and-release.yml
+++ b/.github/workflows/js-test-and-release.yml
@@ -9,7 +9,9 @@ on:
permissions:
contents: write
+ id-token: write
packages: write
+ pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }}
diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml
new file mode 100644
index 0000000..bd00f09
--- /dev/null
+++ b/.github/workflows/semantic-pull-request.yml
@@ -0,0 +1,12 @@
+name: Semantic PR
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - edited
+ - synchronize
+
+jobs:
+ main:
+ uses: pl-strflt/.github/.github/workflows/reusable-semantic-pull-request.yml@v0.3
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000..16d65d7
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,13 @@
+name: Close and mark stale issue
+
+on:
+ schedule:
+ - cron: '0 0 * * *'
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ stale:
+ uses: pl-strflt/.github/.github/workflows/reusable-stale-issue.yml@v0.3
diff --git a/.gitignore b/.gitignore
index 1fbd791..7ad9e67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,41 +1,9 @@
-package-lock.json
-yarn.lock
-
-# Logs
-logs
-*.log
-npm-debug.log*
-
-# Runtime data
-pids
-*.pid
-*.seed
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-.nyc_output
-
-# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (http://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directory
node_modules
-
-# Optional npm cache directory
-.npm
-
-# Optional REPL history
-.node_repl_history
-
+build
dist
-lib
-docs
+.docs
+.coverage
+node_modules
+package-lock.json
+yarn.lock
+.vscode
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 1b26874..0000000
--- a/.npmignore
+++ /dev/null
@@ -1 +0,0 @@
-test-*.js
diff --git a/README.md b/README.md
index fa24c75..5133f14 100644
--- a/README.md
+++ b/README.md
@@ -5,54 +5,18 @@
> A set of utilities to help identify IPFS resources on the web
-## Table of contents
-
-- [Install](#install)
- - [Browser `
-```
+1. `pathPattern`/`pathGatewayPattern`/`subdomainGatewayPattern` regex is applied to quickly identify potential candidates
+2. proper CID validation is applied to remove false-positives
-## Usage
+## Example
-```javascript
+```TypeScript
import * as isIPFS from 'is-ipfs'
isIPFS.multihash('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
@@ -134,117 +98,31 @@ isIPFS.peerMultiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQ
isIPFS.peerMultiaddr('/ip4/127.0.0.1/udp/1234') // false (key missing)
```
-## API
-
-A suite of util methods that provides efficient validation.
-
-Detection of IPFS Paths and identifiers in URLs is a two-stage process:
-
-1. `pathPattern`/`pathGatewayPattern`/`subdomainGatewayPattern` regex is applied to quickly identify potential candidates
-2. proper CID validation is applied to remove false-positives
-
-### Content Identifiers
-
-#### `isIPFS.multihash(hash)`
-
-Returns `true` if the provided string or `Uint8Array` is a valid `multihash` or `false` otherwise.
-
-#### `isIPFS.cid(hash)`
-
-Returns `true` if the provided string, `Uint8Array` or [`CID`](https://github.com/multiformats/js-multiformats/#readme) object represents a valid [CID](https://docs.ipfs.io/guides/concepts/cid/) or `false` otherwise.
-
-#### `isIPFS.base32cid(hash)`
-
-Returns `true` if the provided string is a valid `CID` in Base32 encoding or `false` otherwise.
-
-### URLs
-
-#### `isIPFS.url(url)`
-
-Returns `true` if the provided string is a valid IPFS or IPNS url or `false` otherwise.
-
-#### `isIPFS.ipfsUrl(url)`
-
-Returns `true` if the provided string is a valid IPFS url or `false` otherwise.
-
-#### `isIPFS.ipnsUrl(url)`
-
-Returns `true` if the provided string is a valid IPNS url or `false` otherwise.
+# Install
-### Paths
-
-Standalone validation of IPFS Paths: `/ip(f|n)s//..`
-
-#### `isIPFS.path(path)`
-
-Returns `true` if the provided string is a valid IPFS or IPNS path or `false` otherwise.
-
-#### `isIPFS.urlOrPath(path)`
-
-Returns `true` if the provided string is a valid IPFS or IPNS url or path or `false` otherwise.
-
-#### `isIPFS.ipfsPath(path)`
-
-Returns `true` if the provided string is a valid IPFS path or `false` otherwise.
-
-#### `isIPFS.ipnsPath(path)`
-
-Returns `true` if the provided string is a valid IPNS path or `false` otherwise.
-
-#### `isIPFS.cidPath(path)`
-
-Returns `true` if the provided string is a valid "CID path" (IPFS path without `/ipfs/` prefix) or `false` otherwise.
-
-### Subdomains
-
-Validated subdomain convention: `cidv1b32.ip(f|n)s.domain.tld`
-
-#### `isIPFS.subdomain(url)`
-
-Returns `true` if the provided `url` string includes a valid IPFS, looks like IPNS/DNSLink subdomain or `false` otherwise.
-
-#### `isIPFS.ipfsSubdomain(url)`
-
-Returns `true` if the provided `url` string includes a valid IPFS subdomain (case-insensitive CIDv1) or `false` otherwise.
-
-#### `isIPFS.ipnsSubdomain(url)`
-
-Returns `true` if the provided `url` string looks like a valid IPNS subdomain
-(CIDv1 with `libp2p-key` multicodec or something that looks like a FQDN, for example `en.wikipedia-on-ipfs.org.ipns.localhost:8080`) or `false`
-otherwise.
-
-**Note:** `ipnsSubdomain` method works in offline mode: it does not perform
-actual IPNS record lookup over DHT or other content routing method. It may
-return false-positives:
-
-- To ensure IPNS record exists, make a call to `/api/v0/name/resolve?arg=`
-- To ensure DNSLink exists, make a call to `/api/v0/dns?arg=`
-
-### Multiaddrs
-
-Below methods provide basic detection of [multiaddr](https://github.com/multiformats/multiaddr)s: composable and future-proof network addresses.
-
-Complex validation of multiaddr can be built using `isIPFS.multiaddr` and [`mafmt`](https://github.com/multiformats/js-mafmt) library.
-
-#### `isIPFS.multiaddr(addr)`
+```console
+$ npm i is-ipfs
+```
-Returns `true` if the provided `string`, [`Multiaddr`](https://github.com/multiformats/js-multiaddr) or `Uint8Array` represents a valid multiaddr or `false` otherwise.
+## Browser `
+```
-## API Docs
+# API Docs
-
-## License
+# License
Licensed under either of
- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / )
- MIT ([LICENSE-MIT](LICENSE-MIT) / )
-## Contribution
+# Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
diff --git a/package.json b/package.json
index ae224ec..881b146 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,10 @@
"bugs": {
"url": "https://github.com/ipfs-shipyard/is-ipfs/issues"
},
+ "publishConfig": {
+ "access": "public",
+ "provenance": true
+ },
"keywords": [
"dnslink",
"gateway",
@@ -19,10 +23,6 @@
"ipns",
"js-ipfs"
],
- "engines": {
- "node": ">=16.0.0",
- "npm": ">=7.0.0"
- },
"type": "module",
"types": "./dist/src/index.d.ts",
"files": [
@@ -40,6 +40,7 @@
"eslintConfig": {
"extends": "ipfs",
"parserOptions": {
+ "project": true,
"sourceType": "module"
}
},
@@ -129,18 +130,16 @@
]
},
"scripts": {
- "clean": "aegir clean",
- "lint": "aegir lint",
- "dep-check": "aegir dep-check",
- "generate": "protons src/pb/peer.proto src/pb/tags.proto",
"build": "aegir build",
"test": "aegir test",
- "test:chrome": "aegir test -t browser",
+ "test:node": "aegir test -t node --cov",
+ "test:chrome": "aegir test -t browser --cov",
"test:chrome-webworker": "aegir test -t webworker",
"test:firefox": "aegir test -t browser -- --browser firefox",
"test:firefox-webworker": "aegir test -t webworker -- --browser firefox",
- "test:node": "aegir test -t node",
"test:electron-main": "aegir test -t electron-main",
+ "lint": "aegir lint",
+ "dep-check": "aegir dep-check",
"release": "aegir release",
"docs": "aegir docs"
},
@@ -152,9 +151,11 @@
"uint8arrays": "^4.0.2"
},
"devDependencies": {
- "aegir": "^37.10.1"
+ "aegir": "^42.2.3"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=7.0.0"
},
- "browser": {
- "fs": false
- }
+ "sideEffects": false
}
diff --git a/src/index.ts b/src/index.ts
index 0af544a..0d7b9be 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,12 +1,107 @@
-import { base58btc } from 'multiformats/bases/base58'
-import { base32 } from 'multiformats/bases/base32'
-import * as Digest from 'multiformats/hashes/digest'
-import { multiaddr } from '@multiformats/multiaddr'
-import type { Multiaddr } from '@multiformats/multiaddr'
+/**
+ * @packageDocumentation
+ *
+ * A suite of util methods that provides efficient validation.
+ *
+ * Detection of IPFS Paths and identifiers in URLs is a two-stage process:
+ *
+ * 1. `pathPattern`/`pathGatewayPattern`/`subdomainGatewayPattern` regex is applied to quickly identify potential candidates
+ * 2. proper CID validation is applied to remove false-positives
+ *
+ * @example
+ *
+ * ```TypeScript
+ * import * as isIPFS from 'is-ipfs'
+ *
+ * isIPFS.multihash('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
+ * isIPFS.multihash('noop') // false
+ *
+ * isIPFS.cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true (CIDv0)
+ * isIPFS.cid('bafybeiasb5vpmaounyilfuxbd3lryvosl4yefqrfahsb2esg46q6tu6y5q') // true (CIDv1 in Base32)
+ * isIPFS.cid('zdj7WWeQ43G6JJvLWQWZpyHuAMq6uYWRjkBXFad11vE2LHhQ7') // true (CIDv1 in Base58btc)
+ * isIPFS.cid('noop') // false
+ *
+ * isIPFS.base32cid('bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va') // true
+ * isIPFS.base32cid('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false
+ *
+ * isIPFS.url('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
+ * isIPFS.url('https://ipfs.io/ipfs/QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR?filename=guardian.jpg') // true
+ * isIPFS.url('https://ipfs.io/ipns/github.com') // true
+ * isIPFS.url('https://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
+ * isIPFS.url('http://en.wikipedia-on-ipfs.org.ipfs.localhost:8080') // true
+ * isIPFS.url('https://github.com/ipfs/js-ipfs/blob/master/README.md') // false
+ * isIPFS.url('https://google.com') // false
+ *
+ * isIPFS.path('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
+ * isIPFS.path('/ipfs/QmbcBPAwCDxRMB1Qe7CRQmxdrTSkxKwM9y6rZw2FjGtbsb/?weird-filename=test.jpg') // true
+ * isIPFS.path('/ipns/github.com') // true
+ * isIPFS.path('/ipfs/js-ipfs/blob/master/README.md') // false
+ *
+ * isIPFS.urlOrPath('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
+ * isIPFS.urlOrPath('https://ipfs.io/ipns/github.com') // true
+ * isIPFS.urlOrPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
+ * isIPFS.urlOrPath('/ipns/github.com') // true
+ * isIPFS.urlOrPath('https://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
+ * isIPFS.urlOrPath('https://google.com') // false
+ *
+ * isIPFS.ipfsUrl('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
+ * isIPFS.ipfsUrl('https://ipfs.io/ipfs/invalid-hash') // false
+ *
+ * isIPFS.ipnsUrl('https://ipfs.io/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false
+ * isIPFS.ipnsUrl('https://ipfs.io/ipns/github.com') // true
+ *
+ * isIPFS.ipfsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // true
+ * isIPFS.ipfsPath('/ipfs/invalid-hash') // false
+ *
+ * isIPFS.ipnsPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false
+ * isIPFS.ipnsPath('/ipns/github.com') // true
+ *
+ * isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/path/to/file') // true
+ * isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/') // true
+ * isIPFS.cidPath('QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false
+ * isIPFS.cidPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o') // false
+ * isIPFS.cidPath('/ipfs/QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o/file') // false
+ *
+ * isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
+ * isIPFS.subdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true
+ * isIPFS.subdomain('http://www.bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // false
+ * isIPFS.subdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false
+ *
+ * isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.ipfs.dweb.link') // true
+ * isIPFS.ipfsSubdomain('http://bafybeie5gq4jxvzmsym6hjlwxej4rwdoxt7wadqvmmwbqi7r27fclha2va.dweb.link') // false
+ *
+ * isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.ipns.dweb.link') // true
+ * isIPFS.ipnsSubdomain('http://bafybeiabc2xofh6tdi6vutusorpumwcikw3hf3st4ecjugo6j52f6xwc6q.dweb.link') // false
+ * isIPFS.ipnsSubdomain('http://QmcNioXSC1bfJj1dcFErhUfyjFzoX2HodkRccsFFVJJvg8.ipns.dweb.link') // false
+ * isIPFS.ipnsSubdomain('http://en.wikipedia-on-ipfs.org.ipns.localhost:8080') // true (assuming DNSLink)
+ * isIPFS.ipnsSubdomain('http://en-wikipedia--on--ipfs-org.ipns.localhost:8080') // true (assuming inlined DNSLink)
+ * isIPFS.ipnsSubdomain('http://hostname-without-tld-.ipns.dweb.link') // false (not a CID, invalid DNS label)
+ *
+ * isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234') // true
+ * isIPFS.multiaddr('/ip4/127.0.0.1/udp/1234/http') // true
+ * isIPFS.multiaddr('/ip6/::1/udp/1234') // true
+ * isIPFS.multiaddr('ip6/::1/udp/1234') // false
+ * isIPFS.multiaddr('/yoloinvalid/::1/udp/1234') // false
+ *
+ * isIPFS.peerMultiaddr('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true
+ * isIPFS.peerMultiaddr('/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4') // true (legacy notation)
+ * isIPFS.peerMultiaddr('/ip4/127.0.0.1/tcp/1234/ws/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true
+ * isIPFS.peerMultiaddr('/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSoooo4/p2p-circuit/p2p/QmUjNmr8TgJCn1Ao7DvMy4cjoZU15b9bwSCBLE3vwXiwgj') // true
+ * isIPFS.peerMultiaddr('/dnsaddr/bootstrap.libp2p.io') // false (key missing, needs additional DNS lookup to tell if this is valid)
+ * isIPFS.peerMultiaddr('/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN') // true (key present, ip and port can be resolved later)
+ * isIPFS.peerMultiaddr('/ip4/127.0.0.1/udp/1234') // false (key missing)
+ * ```
+ */
+
import * as mafmt from '@multiformats/mafmt'
-import { CID } from 'multiformats/cid'
+import { multiaddr } from '@multiformats/multiaddr'
import { URL } from 'iso-url'
+import { base32 } from 'multiformats/bases/base32'
+import { base58btc } from 'multiformats/bases/base58'
+import { CID } from 'multiformats/cid'
+import * as Digest from 'multiformats/hashes/digest'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
+import type { Multiaddr } from '@multiformats/multiaddr'
export const pathGatewayPattern = /^https?:\/\/[^/]+\/(ip[fn]s)\/([^/?#]+)/
export const pathPattern = /^\/(ip[fn]s)\/([^/?#]+)/
@@ -29,7 +124,7 @@ function isMultihash (hash: Uint8Array | string): boolean {
}
try {
- Digest.decode(base58btc.decode('z' + formatted))
+ Digest.decode(base58btc.decode(`z${formatted}`))
} catch {
return false
}
@@ -96,7 +191,7 @@ function isPeerMultiaddr (input: string | Uint8Array | Multiaddr): boolean {
* @param {number} [protocolMatch=1]
* @param {number} [hashMatch=2]
*/
-function isIpfs (input: string | Uint8Array, pattern: RegExp | string, protocolMatch: number = defaultProtocolMatch, hashMatch: number = defaultHashMath) {
+function isIpfs (input: string | Uint8Array, pattern: RegExp | string, protocolMatch: number = defaultProtocolMatch, hashMatch: number = defaultHashMath): boolean {
const formatted = convertToString(input)
if (formatted === false) {
return false
@@ -130,7 +225,7 @@ function isIpfs (input: string | Uint8Array, pattern: RegExp | string, protocolM
* @param {number} [protocolMatch=1]
* @param {number} [hashMatch=1]
*/
-function isIpns (input: string | Uint8Array, pattern: RegExp | string, protocolMatch: number = defaultProtocolMatch, hashMatch: number = defaultHashMath) {
+function isIpns (input: string | Uint8Array, pattern: RegExp | string, protocolMatch: number = defaultProtocolMatch, hashMatch: number = defaultHashMath): boolean {
const formatted = convertToString(input)
if (formatted === false) {
return false
@@ -183,7 +278,7 @@ function isString (input: any): input is string {
/**
* @param {Uint8Array | string} input
*/
-function convertToString (input: Uint8Array | string) {
+function convertToString (input: Uint8Array | string): string | false {
if (input instanceof Uint8Array) {
return uint8ArrayToString(input, 'base58btc')
}
@@ -195,21 +290,104 @@ function convertToString (input: Uint8Array | string) {
return false
}
-export const ipfsSubdomain = (url: string | Uint8Array) => isIpfs(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch)
-export const ipnsSubdomain = (url: string | Uint8Array) => isIpns(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch)
-export const subdomain = (url: string | Uint8Array) => ipfsSubdomain(url) || ipnsSubdomain(url)
-export const ipfsUrl = (url: string | Uint8Array) => isIpfs(url, pathGatewayPattern) || ipfsSubdomain(url)
-export const ipnsUrl = (url: string | Uint8Array) => isIpns(url, pathGatewayPattern) || ipnsSubdomain(url)
-export const url = (url: string | Uint8Array) => ipfsUrl(url) || ipnsUrl(url) || subdomain(url)
-export const path = (path: string | Uint8Array) => isIpfs(path, pathPattern) || isIpns(path, pathPattern)
+/**
+ * Returns `true` if the provided `url` string includes a valid IPFS subdomain
+ * (case-insensitive CIDv1) or `false` otherwise.
+ */
+export const ipfsSubdomain = (url: string | Uint8Array): boolean => isIpfs(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch)
+
+/**
+ * Returns `true` if the provided `url` string looks like a valid IPNS subdomain
+ * (CIDv1 with `libp2p-key` multicodec or something that looks like a FQDN, for
+ * example `en.wikipedia-on-ipfs.org.ipns.localhost:8080`) or `false` otherwise.
+ *
+ * **Note:** `ipnsSubdomain` method works in offline mode: it does not perform
+ * actual IPNS record lookup over DHT or other content routing method. It may
+ * return false-positives:
+ *
+ * - To ensure IPNS record exists, make a call to `/api/v0/name/resolve?arg=`
+ * - To ensure DNSLink exists, make a call to `/api/v0/dns?arg=`
+ */
+export const ipnsSubdomain = (url: string | Uint8Array): boolean => isIpns(url, subdomainGatewayPattern, subdomainProtocolMatch, subdomainIdMatch)
+
+/**
+ * Returns `true` if the provided `url` string includes a valid IPFS, looks like
+ * an IPNS/DNSLink subdomain or `false` otherwise.
+ */
+export const subdomain = (url: string | Uint8Array): boolean => ipfsSubdomain(url) || ipnsSubdomain(url)
+
+/**
+ * Returns `true` if the provided string is a valid IPFS url or `false`
+ * otherwise.
+ */
+export const ipfsUrl = (url: string | Uint8Array): boolean => isIpfs(url, pathGatewayPattern) || ipfsSubdomain(url)
+
+/**
+ * Returns `true` if the provided string is a valid IPNS url or `false`
+ * otherwise.
+ */
+export const ipnsUrl = (url: string | Uint8Array): boolean => isIpns(url, pathGatewayPattern) || ipnsSubdomain(url)
+
+/**
+ * Returns `true` if the provided string is a valid IPFS or IPNS url or `false`
+ * otherwise.
+ */
+export const url = (url: string | Uint8Array): boolean => ipfsUrl(url) || ipnsUrl(url) || subdomain(url)
+export const path = (path: string | Uint8Array): boolean => isIpfs(path, pathPattern) || isIpns(path, pathPattern)
+/**
+ * Returns `true` if the provided string or `Uint8Array` is a valid `multihash`
+ * or `false` otherwise.
+ */
export { isMultihash as multihash }
+
+/**
+ * Returns `true` if the provided `string`, [`Multiaddr`](https://github.com/multiformats/js-multiaddr)
+ * or `Uint8Array` represents a valid multiaddr or `false` otherwise.
+ */
export { isMultiaddr as multiaddr }
+
+/**
+ * Returns `true` if the provided `string`, [`Multiaddr`](https://github.com/multiformats/js-multiaddr)
+ * or `Uint8Array` represents a valid libp2p peer multiaddr (matching [`P2P`
+ * format from `mafmt`](https://github.com/multiformats/js-mafmt#api)) or
+ * `false` otherwise.
+ */
export { isPeerMultiaddr as peerMultiaddr }
+
+/**
+ * Returns `true` if the provided string, `Uint8Array` or [`CID`](https://github.com/multiformats/js-multiformats/#readme)
+ * object represents a valid [CID](https://docs.ipfs.io/guides/concepts/cid/) or
+ * `false` otherwise.
+ */
export { isCID as cid }
-export const base32cid = (cid: CID | string | Uint8Array) => (isCID(cid) && isBase32EncodedMultibase(cid))
-export const ipfsPath = (path: string | Uint8Array) => isIpfs(path, pathPattern)
-export const ipnsPath = (path: string | Uint8Array) => isIpns(path, pathPattern)
-export const urlOrPath = (x: string | Uint8Array) => url(x) || path(x)
-export const cidPath = (path: string | Uint8Array | CID) => isString(path) && !isCID(path) && isIpfs(`/ipfs/${path}`, pathPattern)
+/**
+ * Returns `true` if the provided string is a valid `CID` in Base32 encoding or
+ * `false` otherwise.
+ */
+export const base32cid = (cid: CID | string | Uint8Array): boolean => (isCID(cid) && isBase32EncodedMultibase(cid))
+
+/**
+ * Returns `true` if the provided string is a valid IPFS or IPNS path or `false`
+ * otherwise.
+ */
+export const ipfsPath = (path: string | Uint8Array): boolean => isIpfs(path, pathPattern)
+
+/**
+ * Returns `true` if the provided string is a valid IPNS path or `false`
+ * otherwise.
+ */
+export const ipnsPath = (path: string | Uint8Array): boolean => isIpns(path, pathPattern)
+
+/**
+ * Returns `true` if the provided string is a valid IPFS or IPNS url or path or
+ * `false` otherwise.
+ */
+export const urlOrPath = (x: string | Uint8Array): boolean => url(x) || path(x)
+
+/**
+ * Returns `true` if the provided string is a valid "CID path" (IPFS path
+ * without `/ipfs/` prefix) or `false` otherwise.
+ */
+export const cidPath = (path: string | Uint8Array | CID): boolean => isString(path) && !isCID(path) && isIpfs(`/ipfs/${path}`, pathPattern)
diff --git a/test/test-cid.spec.ts b/test/test-cid.spec.ts
index 5fbd281..44d5e48 100644
--- a/test/test-cid.spec.ts
+++ b/test/test-cid.spec.ts
@@ -1,9 +1,9 @@
/* eslint-env mocha */
import { expect } from 'aegir/chai'
-import * as isIPFS from '../src/index.js'
import { CID } from 'multiformats/cid'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
+import * as isIPFS from '../src/index.js'
describe('ipfs cid', () => {
it('isIPFS.cid should match a valid CID instance', (done) => {
diff --git a/test/test-multiaddr.spec.ts b/test/test-multiaddr.spec.ts
index 6d6d554..a61f74b 100644
--- a/test/test-multiaddr.spec.ts
+++ b/test/test-multiaddr.spec.ts
@@ -1,9 +1,9 @@
/* eslint-env mocha */
-import { expect } from 'aegir/chai'
import { multiaddr } from '@multiformats/multiaddr'
-import * as isIPFS from '../src/index.js'
+import { expect } from 'aegir/chai'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
+import * as isIPFS from '../src/index.js'
describe('ipfs multiaddr', () => {
it('isIPFS.multiaddr should match a string with valid ip4 multiaddr', (done) => {
diff --git a/test/test-multihash.spec.ts b/test/test-multihash.spec.ts
index a01160e..e43c6f5 100644
--- a/test/test-multihash.spec.ts
+++ b/test/test-multihash.spec.ts
@@ -1,8 +1,8 @@
/* eslint-env mocha */
import { expect } from 'aegir/chai'
-import * as isIPFS from '../src/index.js'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
+import * as isIPFS from '../src/index.js'
describe('ipfs multihash', () => {
it('isIPFS.multihash should match a valid multihash', (done) => {
diff --git a/test/test-path.spec.ts b/test/test-path.spec.ts
index 7cd9d96..6725ae1 100644
--- a/test/test-path.spec.ts
+++ b/test/test-path.spec.ts
@@ -1,8 +1,8 @@
/* eslint-env mocha */
-import * as isIPFS from '../src/index.js'
import { expect } from 'aegir/chai'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
+import * as isIPFS from '../src/index.js'
describe('ipfs path', () => {
it('isIPFS.ipfsPath should match an ipfs path', (done) => {
diff --git a/test/test-subdomain.spec.ts b/test/test-subdomain.spec.ts
index 0846b44..040a250 100644
--- a/test/test-subdomain.spec.ts
+++ b/test/test-subdomain.spec.ts
@@ -1,8 +1,8 @@
/* eslint-env mocha */
-import * as isIPFS from '../src/index.js'
import { expect } from 'aegir/chai'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
+import * as isIPFS from '../src/index.js'
describe('ipfs subdomain', () => {
it('isIPFS.ipfsSubdomain should match a cidv1b32', (done) => {
diff --git a/test/test-url.spec.ts b/test/test-url.spec.ts
index 9329057..bd5bfb3 100644
--- a/test/test-url.spec.ts
+++ b/test/test-url.spec.ts
@@ -1,8 +1,8 @@
/* eslint-env mocha */
import { expect } from 'aegir/chai'
-import * as isIPFS from '../src/index.js'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
+import * as isIPFS from '../src/index.js'
describe('ipfs url', () => {
it('isIPFS.ipfsUrl should match an ipfs url', (done) => {
diff --git a/typedoc.json b/typedoc.json
new file mode 100644
index 0000000..f599dc7
--- /dev/null
+++ b/typedoc.json
@@ -0,0 +1,5 @@
+{
+ "entryPoints": [
+ "./src/index.ts"
+ ]
+}