diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a1c0c00 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index a83cce5..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - root: true, - extends: [ - 'eslint-config-digitalbazaar', - 'eslint-config-digitalbazaar/jsdoc' - ], - env: { - node: true - }, - rules: { - 'jsdoc/check-examples': 'off' - }, - ignorePatterns: ['dist/'] -}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..026d53b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v6 + with: + node-version: '24' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm run lint + + - name: Build + run: pnpm run build + + - name: Test (Node) + run: pnpm run test-node + + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps chromium + + - name: Test (Browser) + run: pnpm run test-browser diff --git a/.github/workflows/issues-to-project.yml b/.github/workflows/issues-to-project.yml deleted file mode 100644 index f0a2c40..0000000 --- a/.github/workflows/issues-to-project.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: move new, edited, reopened issues to DCC Engineering project - -on: - issues: - types: [ opened, edited, reopened ] -jobs: - add-to-project: - name: Add issue to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/digitalcredentials/projects/14 - github-token: ${{ secrets.PROJECTS_ACCESS_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 76d562a..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Node.js CI - -on: [push] - -jobs: - test-node: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - name: Run test with Node.js ${{ matrix.node-version }} - run: npm run test-node - env: - CI: true - test-karma: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - name: Run karma tests - run: npm run test-karma - env: - CI: true - lint: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [14.x] - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - name: Run eslint - run: npm run lint diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..b8ffa52 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,33 @@ +name: Publish + +on: + release: + types: [published] + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # required for npm provenance + steps: + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v6 + + - uses: actions/setup-node@v6 + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org' + + - name: Update npm for trusted publishing + run: npm install -g npm@latest # trusted publishing needs npm >= 11.5.1 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm run build + + - name: Publish to npm + run: npm publish diff --git a/.gitignore b/.gitignore index 3c3d232..fa36891 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,81 @@ -*.sw[op] -*~ -.cproject -.project -.c9 -*.sublime-project -*.sublime-workspace -.DS_Store -.settings -coverage -node_modules -v8.log -.c9revisions -npm-debug.log +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + package-lock.json -.nyc_output +yarn.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +/.nyc_output +/coverage + + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# TernJS port file +.tern-port + +# Editor files +*~ +*.sw[nop] +/.vscode + +# Output dist + +# Playwright +playwright-report/ +test-results/ + +# Vite +.vite/ + +# MacOS +.DS_Store diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 20b2378..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: node_js -node_js: - - "12" - - "14" -sudo: false -script: - - npm run test -notifications: - email: - on_success: change - on_failure: change diff --git a/CHANGELOG.md b/CHANGELOG.md index ed8bb88..a2e14c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # did-io ChangeLog +## 2.0.0 - + +### Changed +- Forked from `@digitalbazaar/did-io@1.0.0`. +- **BREAKING**: Convert to TypeScript. +- **BREAKING**: Update to latest `lru-cache` package, deprecate `maxAge` option + (uses `ttl` instead). + +### Added +- Import `generate()` and a pass-through `cache` param from `@digitalbazaar/did-io@2.0.0`. + ## 1.0.1 - 2021-10-01 ### Changed diff --git a/README.md b/README.md index 686dcca..fadfde3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# Selective DID Resolver Client _(@digitalcredentials/did-io)_ +# Selective DID Resolver Client _(@interop/did-io)_ -[![Node.js CI](https://github.com/digitalcredentials/did-io/workflows/Node.js%20CI/badge.svg)](https://github.com/digitalcredentials/did-io/actions?query=workflow%3A%22Node.js+CI%22) -[![NPM Version](https://img.shields.io/npm/v/@digitalcredentials/did-io.svg)](https://npm.im/@digitalcredentials/did-io) +[![Node.js CI](https://github.com/interop-alliance/did-io/workflows/CI/badge.svg)](https://github.com/interop-alliance/did-io/actions?query=workflow%3A%22CI%22) +[![NPM Version](https://img.shields.io/npm/v/@interop/did-io.svg)](https://npm.im/@interop/did-io) -> A [DID](https://w3c.github.io/did-core) (Decentralized Identifier) resolver library for Javascript, TypeScript and ReactNative. +> A DID (Decentralized Identifier) resolver library for Node, browser, and React Native. ## Table of Contents @@ -31,12 +31,12 @@ See also (related specs): ## Install -Requires Node.js 12+ +Requires Node.js 20+ To install locally (for development): ``` -git clone https://github.com/digitalcredentials/did-io.git +git clone https://github.com/interop-alliance/did-io.git cd did-io npm install ``` @@ -44,7 +44,7 @@ npm install To install as a dependency in another project, add this to your `package.json`: ``` -"@digitalcredentials/did-io": "^X.x.x" +"@interop/did-io": "^X.x.x" ``` ## Usage @@ -58,10 +58,10 @@ To install as a dependency in another project, add this to your `package.json`: ### Using the CachedResolver to `get()` DID documents and keys ```js -import {CachedResolver} from '@digitalcredentials/did-io'; +import {CachedResolver} from '@interop/did-io'; // You can pass cache options to the constructor (see Cache Management below) -const resolver = new CachedResolver({max: 100}); // defaults to 100 +const resolver = new CachedResolver({ max: 100 }); // defaults to 100 ``` On its own, the resolver does not know how to fetch or resolve any DID methods. @@ -70,16 +70,12 @@ Support for each one has to be enabled explicitly. It uses a is loaded via `.use(driver)`. ```js -import * as didKey from '@digitalcredentials/did-method-key'; -import * as didVeresOne from 'did-veres-one'; +import * as didKey from '@interop/did-method-key'; const didKeyDriver = didKey.driver(); -// Dev / testnet / live modes -const didVeresOneDriver = didVeresOne.driver({mode: 'dev'}); -// Enable resolver to use the did:key and did:v1 methods for cached fetching. +// Enable resolver to use the did:key method for cached fetching. resolver.use(didKeyDriver); -resolver.use(didVeresOneDriver); ``` After enabling individual DID methods, you can `get()` individual @@ -150,9 +146,9 @@ methodFor({purpose: 'assertionMethod'}); ### Using CachedResolver as a `documentLoader` One of the most common uses of DIDs and their public keys is for cryptographic -operations such as signing and verifying signatures of -[Verifiable Credentials](https://github.com/digitalcredentials/vc-js) and -[other documents](https://github.com/digitalcredentials/jsonld-signatures), and for +operations such as signing and verifying signatures of +[Verifiable Credentials](https://github.com/interop-alliance/vc) and +[other documents](https://github.com/interop-alliance/jsonld-signatures), and for [encrypting and decrypting objects](https://github.com/digitalbazaar/minimal-cipher). For these and other Linked Data Security operations, a `documentLoader` function @@ -165,7 +161,7 @@ fetching DID Documents of supported DID methods, retrieving public keys, and so on. You can use an initialized `CachedResolver` instance when constructing a -`documentLoader` for your use case (to handle DID and DID key resolution for +`documentLoader` for your use case (to handle DID and DID key resolution for installed methods). For example: ```js @@ -175,7 +171,7 @@ resolver.use(didMethodDriver2); const documentLoader = async url => { // Handle other static document and contexts here... - + // Use CachedResolver to fetch did: links. if(url && url.startsWith('did:')) { // this will handle both DIDs and key IDs for the 2 installed drivers @@ -191,27 +187,25 @@ const documentLoader = async url => { ### Cache management -CachedResolver uses [`lru-memoize`](https://github.com/digitalcredentials/lru-memoize) -to [memoize](https://en.wikipedia.org/wiki/Memoization) `get()` promises +CachedResolver uses [`lru-memoize`](https://github.com/interop/lru-memoize) +to [memoize](https://en.wikipedia.org/wiki/Memoization) `get()` promises (as opposed to just the results of the operations), which helps in high-concurrency use cases. (And that library in turn uses [`lru-cache`](https://www.npmjs.com/package/lru-cache) under the hood.) The `CachedResolver` constructor passes any options given to it through to -the `lru-cache` constructor, so see that repo for the full list of cache +the `lru-cache` constructor, so, see that repo for the full list of cache management options. Commonly used ones include: * `max` (default: 100) - maximum size of the cache. -* `maxAge` (default: 5 sec/5000 ms) - maximum age of an item in ms. -* `updateAgeOnGet` (default: `false`) - When using time-expiring entries with - `maxAge`, setting this to true will make each entry's effective time update to - the current time whenever it is retrieved from cache, thereby extending the +* `ttl` (default: 5 sec/5000 ms) - maximum age of an item in ms. +* `updateAgeOnGet` (default: `false`) - When using time-expiring entries with + `ttl`, setting this to true will make each entry's effective time update to + the current time whenever it is retrieved from cache, thereby extending the expiration date of the entry. ## Contribute -See [the contribute file](https://github.com/digitalbazaar/bedrock/blob/master/CONTRIBUTING.md)! - PRs accepted. If editing the Readme, please conform to the diff --git a/build-dist.sh b/build-dist.sh deleted file mode 100755 index a2889e3..0000000 --- a/build-dist.sh +++ /dev/null @@ -1,16 +0,0 @@ -mkdir ./dist/esm -cat >dist/esm/index.js <dist/esm/package.json <} Resolves with fetched DID Document. - */ - async get({did, url, ...getOptions} = {}) { - did = did || url; - if(!did) { - throw new TypeError('A string "did" or "url" parameter is required.'); - } - - const method = this._methodForDid(did); - - return this._cache.memoize({ - key: did, - fn: () => method.get({did, ...getOptions}) - }); - } - - /** - * @param {string} did - DID uri. - * - * @returns {object} - DID Method driver. - * @private - */ - _methodForDid(did) { - const {prefix} = parseDid({did}); - const method = this._methods.get(prefix); - if(!method) { - throw new Error(`Driver for DID ${did} not found.`); - } - return method; - } -} diff --git a/lib/did-io.js b/lib/did-io.js deleted file mode 100644 index 6d02b1d..0000000 --- a/lib/did-io.js +++ /dev/null @@ -1,215 +0,0 @@ -/*! - * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. - */ -import {VERIFICATION_RELATIONSHIPS} from './constants.js'; - -/** - * Tests whether this DID Document contains a verification relationship - * between the subject and a method id, for a given purpose. - * - * @example - * didDocument.approvesMethodFor({ - * methodId: 'did:ex:1234#abcd', purpose: 'authentication' - * }); - * // -> true - * @example - * didDocument.approvesMethodFor({ - * methodId: 'did:ex:1234#abcd', purpose: 'assertionMethod' - * }); - * // -> false - * - * @param {object} options - Options hashmap. - * @param {object} options.doc - DID Document. - * @param {string} options.methodId - Verification method id (a uri). - * @param {string} options.purpose - E.g. 'authentication', etc. - * - * @returns {boolean} Returns whether a method id is authorized for purpose. - */ -export function approvesMethodFor({doc, methodId, purpose}) { - if(!(methodId && purpose)) { - throw new Error('A method id and purpose is required.'); - } - const method = _methodById({doc, methodId}); - if(!method) { - return false; - } - const methods = doc[purpose] || []; - return !!methods.find(method => { - return (typeof method === 'string' && method === methodId) || - (typeof method === 'object' && method.id === methodId); - }); -} - -/** - * Initializes the DID Document's keys/proof methods. - * - * @example - * didDocument.id = 'did:ex:123'; - * const {keyPairs} = await initKeys({ - * cryptoLd, - * keyMap: { - * capabilityInvocation: someExistingKey, - * authentication: 'Ed25519VerificationKey2020', - * assertionMethod: 'Ed25519VerificationKey2020', - * keyAgreement: 'X25519KeyAgreementKey2019' - * } - * });. - * - * @param {object} options - Options hashmap. - * @param {object} options.doc - DID Document. - * @typedef {object} CryptoLD - * @param {CryptoLD} [options.cryptoLd] - CryptoLD driver instance, - * initialized with the key types this DID Document intends to support. - * @param {object} [options.keyMap] - Map of keys (or key types) by purpose. - * - * @returns {Promise<{keyPairs: object}>} A hashmap of public/private key - * pairs, by key id. - */ -export async function initKeys({doc, cryptoLd, keyMap = {}} = {}) { - if(!doc.id) { - throw new TypeError( - 'DID Document "id" property is required to initialize keys.'); - } - - const keyPairs = {}; - - // Set the defaults for the created keys (if needed) - const options = {controller: doc.id}; - - for(const purpose in keyMap) { - if(!VERIFICATION_RELATIONSHIPS.has(purpose)) { - throw new Error(`Unsupported key purpose: "${purpose}".`); - } - - let key; - if(typeof keyMap[purpose] === 'string') { - if(!cryptoLd) { - throw new Error('Please provide an initialized CryptoLD instance.'); - } - key = await cryptoLd.generate({type: keyMap[purpose], ...options}); - } else { - // An existing key has been provided - key = keyMap[purpose]; - } - - this[purpose] = [key.export({publicKey: true})]; - keyPairs[key.id] = key; - } - - return {keyPairs}; -} - -/** - * Finds a verification method for a given methodId or purpose. - * - * If a method id is given, returns the object for that method (for example, - * returns the public key definition for that id). - * - * If a purpose (verification relationship) is given, returns the first - * available verification method for that purpose. - * - * If no method is found (for the given id or purpose), returns undefined. - * - * @example - * findVerificationMethod({doc, methodId: 'did:ex:123#abcd'}); - * // -> - * { - * id: 'did:ex:123#abcd', - * controller: 'did:ex:123', - * type: 'Ed25519VerificationKey2020', - * publicKeyMultibase: '...' - * } - * @example - * didDocument.findVerificationMethod({doc, purpose: 'authentication'}); - * // -> - * { - * id: 'did:ex:123#abcd', - * controller: 'did:ex:123', - * type: 'Ed25519VerificationKey2020', - * publicKeyMultibase: '...' - * } - * @param {object} options - Options hashmap. - * @param {object} options.doc - DID Document. - * - * One of the following is required. - * @param {string} [options.methodId] - Verification method id. - * @param {string} [options.purpose] - Method purpose (verification - * relationship). - * - * @returns {object} Returns the verification method, or undefined if not found. - */ -export function findVerificationMethod({doc, methodId, purpose} = {}) { - if(!doc) { - throw new TypeError('A DID Document is required.'); - } - if(!(methodId || purpose)) { - throw new TypeError('A method id or purpose is required.'); - } - - if(methodId) { - return _methodById({doc, methodId}); - } - - // Id not given, find the first method by purpose - const [method] = doc[purpose] || []; - if(method && typeof method === 'string') { - // This is a reference, not the full method, attempt to find it - return _methodById({doc, methodId: method}); - } - - return method; -} - -/** - * Finds a verification method for a given id and returns it. - * - * @param {object} options - Options hashmap. - * @param {object} options.doc - DID Document. - * @param {string} options.methodId - Verification method id. - * - * @returns {object} Returns the verification method. - */ -export function _methodById({doc, methodId}) { - let result; - - // First, check the 'verificationMethod' bucket, see if it's listed there - if(doc.verificationMethod) { - result = doc.verificationMethod.find(method => method.id === methodId); - } - - for(const purpose of VERIFICATION_RELATIONSHIPS) { - const methods = doc[purpose] || []; - // Iterate through each verification method in 'authentication', etc. - for(const method of methods) { - // Only return it if the method is defined, not referenced - if(typeof method === 'object' && method.id === methodId) { - result = method; - break; - } - } - if(result) { - return result; - } - } -} - -/** - * Parses the DID into various component (currently, only cares about prefix). - * - * @example - * parseDid({did: 'did:v1:test:nym'}); - * // -> {prefix: 'v1'} - * - * @param {string} did - DID uri. - * - * @returns {{prefix: string}} Returns the method prefix (without `did:`). - */ -export function parseDid({did}) { - if(!did) { - throw new TypeError('DID cannot be empty.'); - } - - const prefix = did.split(':').slice(1, 2).join(':'); - - return {prefix}; -} diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index f99bb49..0000000 --- a/lib/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. - */ -export { - approvesMethodFor, - findVerificationMethod, - initKeys, - parseDid -} from './did-io.js'; - -export {CachedResolver} from './CachedResolver.js'; - -export {VERIFICATION_RELATIONSHIPS} from './constants.js'; diff --git a/package.json b/package.json index 52724fa..8e8cf77 100644 --- a/package.json +++ b/package.json @@ -1,82 +1,76 @@ { - "name": "@digitalcredentials/did-io", - "version": "1.0.2", + "name": "@interop/did-io", + "version": "2.0.0-beta.3", "description": "A library for managing DIDs (Decentralized Identifiers) and associated data.", - "homepage": "http://github.com/digitalcredentials/did-io", - "repository": { - "type": "git", - "url": "http://github.com/digitalcredentials/did-io" + "homepage": "https://github.com/interop-alliance/did-io", + "type": "module", + "scripts": { + "build": "pnpm run clear && tsc", + "clear": "rimraf dist/*", + "dev": "vite", + "fix": "eslint --fix src test && pnpm run format", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "lint": "eslint src test", + "prepare": "pnpm run build", + "rebuild": "pnpm run clear && pnpm run build", + "test": "pnpm run lint && pnpm run test-node && pnpm run test-browser", + "test-browser": "playwright test", + "test-node": "vitest run", + "test-coverage": "vitest run --coverage" }, - "license": "BSD-3-Clause", "files": [ "dist", - "lib", - "rollup.config.js", - "build-dist.sh", + "src", + "CHANGELOG.md", "README.md", "LICENSE" ], - "main": "dist/index.js", - "module": "dist/esm/index.js", "exports": { ".": { - "require": "./dist/index.js", - "import": "./dist/esm/index.js" - }, - "./package.json": "./package.json" + "types": "./dist/index.d.ts", + "react-native": "./dist/index.js", + "import": "./dist/index.js" + } }, + "module": "dist/index.js", + "browser": "dist/index.js", + "types": "dist/index.d.ts", + "sideEffects": false, "dependencies": { - "@digitalcredentials/lru-memoize": "^2.1.1" + "@digitalcredentials/ssi": "^5.4.2", + "@interop/lru-memoize": "^4.0.2" }, "devDependencies": { - "chai": "^4.3.4", - "cross-env": "^7.0.3", - "eslint": "^7.25.0", - "esm": "^3.2.25", - "eslint-config-digitalbazaar": "^2.8.0", - "eslint-plugin-jsdoc": "^33.1.0", - "karma": "^6.3.2", - "karma-babel-preprocessor": "^8.0.1", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^3.1.0", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-sourcemap-loader": "^0.3.8", - "karma-webpack": "^5.0.0", - "mocha": "^8.3.2", - "mocha-lcov-reporter": "^1.3.0", - "multibase": "^4.0.4", - "multicodec": "^3.0.1", - "nyc": "^15.1.0", - "rimraf": "^3.0.2", - "rollup": "^2.47.0", - "webpack": "^5.36.2" - }, - "engines": { - "node": ">=12" + "@eslint/js": "^10.0.1", + "@playwright/test": "^1.60.0", + "@types/node": "^25.9.1", + "@vitest/coverage-v8": "^4.1.7", + "eslint": "^10.4.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.6.0", + "prettier": "^3.8.3", + "rimraf": "^6.1.3", + "typescript": "^5.5.0", + "typescript-eslint": "^8.59.4", + "vite": "^8.0.14", + "vitest": "^4.1.7" }, "keywords": [ "Decentralized", "DID", "Credential" ], - "scripts": { - "rollup": "rollup -c rollup.config.js", - "build": "npm run clear && npm run rollup && ./build-dist.sh", - "clear": "rimraf dist/ && mkdir dist", - "prepare": "npm run build", - "prepack": "npm run build", - "rebuild": "npm run clear && npm run build", - "test": "npm run lint && npm run test-node && npm run test-karma", - "test-node": "cross-env NODE_ENV=test mocha -r esm --preserve-symlinks -t 30000 -A -R ${REPORTER:-spec} test/**/*.spec.js", - "test-karma": "karma start karma.conf.js", - "lint": "eslint .", - "preversion": "npm test" + "publishConfig": { + "access": "public", + "provenance": true }, - "eslintConfig": { - "env": { - "mocha": true, - "node": true - } - } + "packageManager": "pnpm@11.3.0", + "engines": { + "node": ">=24.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/interop-alliance/did-io.git" + }, + "license": "BSD-3-Clause" } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..2215ac3 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,25 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './test/browser', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:5173', + trace: 'on-first-retry' + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + } + ], + webServer: { + command: 'pnpm run dev', + url: 'http://localhost:5173/test/index.html', + reuseExistingServer: !process.env.CI + } +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..641a6fa --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1820 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@digitalcredentials/ssi': + specifier: ^5.4.2 + version: 5.4.2 + '@interop/lru-memoize': + specifier: ^4.0.2 + version: 4.0.2 + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.4.0) + '@playwright/test': + specifier: ^1.60.0 + version: 1.60.0 + '@types/node': + specifier: ^25.9.1 + version: 25.9.1 + '@vitest/coverage-v8': + specifier: ^4.1.7 + version: 4.1.7(vitest@4.1.7) + eslint: + specifier: ^10.4.0 + version: 10.4.0 + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@10.4.0) + globals: + specifier: ^17.6.0 + version: 17.6.0 + prettier: + specifier: ^3.8.3 + version: 3.8.3 + rimraf: + specifier: ^6.1.3 + version: 6.1.3 + typescript: + specifier: ^5.5.0 + version: 5.9.3 + typescript-eslint: + specifier: ^8.59.4 + version: 8.60.0(eslint@10.4.0)(typescript@5.9.3) + vite: + specifier: ^8.0.14 + version: 8.0.14(@types/node@25.9.1) + vitest: + specifier: ^4.1.7 + version: 4.1.7(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@25.9.1)) + +packages: + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@digitalcredentials/ssi@5.4.2': + resolution: {integrity: sha512-/rNw47tbC8661tNzVBbKfmX6jaZT+NE7PhPkaFzHdMIzzY8knGotiPqp+8UjjWcQTkzXrqeu1AktRSAnEF+IFw==} + engines: {node: '>=20.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@interop/lru-memoize@4.0.2': + resolution: {integrity: sha512-5rn5uPzP2T0fVsPZ42MSi9QXYg5WWtepRmZXN1wL4zx/o2NW4Rm+ZnkfqTSqMPLaLuzfUhoJGFxsY0rnvcz7wQ==} + engines: {node: '>=24.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.132.0': + resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} + engines: {node: '>=18'} + hasBin: true + + '@rolldown/binding-android-arm64@1.0.2': + resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.2': + resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.2': + resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.2': + resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.2': + resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.2': + resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.2': + resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.2': + resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.2': + resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.2': + resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.2': + resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.2': + resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.2': + resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@25.9.1': + resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + + '@typescript-eslint/eslint-plugin@8.60.0': + resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.60.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.60.0': + resolution: {integrity: sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.60.0': + resolution: {integrity: sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.60.0': + resolution: {integrity: sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.60.0': + resolution: {integrity: sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.60.0': + resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.60.0': + resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.60.0': + resolution: {integrity: sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.60.0': + resolution: {integrity: sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.60.0': + resolution: {integrity: sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/coverage-v8@4.1.7': + resolution: {integrity: sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==} + peerDependencies: + '@vitest/browser': 4.1.7 + vitest: 4.1.7 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.7': + resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} + + '@vitest/mocker@4.1.7': + resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.7': + resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} + + '@vitest/runner@4.1.7': + resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} + + '@vitest/snapshot@4.1.7': + resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} + + '@vitest/spy@4.1.7': + resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} + + '@vitest/utils@4.1.7': + resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.2: + resolution: {integrity: sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.4.0: + resolution: {integrity: sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lru-cache@11.5.0: + resolution: {integrity: sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==} + engines: {node: 20 || >=22} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} + engines: {node: 20 || >=22} + hasBin: true + + rolldown@1.0.2: + resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.2: + resolution: {integrity: sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.60.0: + resolution: {integrity: sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite@8.0.14: + resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.7: + resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.7 + '@vitest/browser-preview': 4.1.7 + '@vitest/browser-webdriverio': 4.1.7 + '@vitest/coverage-istanbul': 4.1.7 + '@vitest/coverage-v8': 4.1.7 + '@vitest/ui': 4.1.7 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@1.0.2': {} + + '@digitalcredentials/ssi@5.4.2': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.0)': + dependencies: + eslint: 10.4.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.4.0)': + optionalDependencies: + eslint: 10.4.0 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.1': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@interop/lru-memoize@4.0.2': + dependencies: + lru-cache: 11.5.0 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.132.0': {} + + '@playwright/test@1.60.0': + dependencies: + playwright: 1.60.0 + + '@rolldown/binding-android-arm64@1.0.2': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.2': + optional: true + + '@rolldown/binding-darwin-x64@1.0.2': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.2': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.2': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.2': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.2': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.2': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.2': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@25.9.1': + dependencies: + undici-types: 7.24.6 + + '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0)(typescript@5.9.3))(eslint@10.4.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/type-utils': 8.60.0(eslint@10.4.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 + eslint: 10.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.60.0(eslint@10.4.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 + debug: 4.4.3 + eslint: 10.4.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.60.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.60.0': + dependencies: + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 + + '@typescript-eslint/tsconfig-utils@8.60.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.60.0(eslint@10.4.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0)(typescript@5.9.3) + debug: 4.4.3 + eslint: 10.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.60.0': {} + + '@typescript-eslint/typescript-estree@8.60.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.0(eslint@10.4.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + eslint: 10.4.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.60.0': + dependencies: + '@typescript-eslint/types': 8.60.0 + eslint-visitor-keys: 5.0.1 + + '@vitest/coverage-v8@4.1.7(vitest@4.1.7)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.7 + ast-v8-to-istanbul: 1.0.2 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.3 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.7(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@25.9.1)) + + '@vitest/expect@4.1.7': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.7(vite@8.0.14(@types/node@25.9.1))': + dependencies: + '@vitest/spy': 4.1.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.14(@types/node@25.9.1) + + '@vitest/pretty-format@4.1.7': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.7': + dependencies: + '@vitest/utils': 4.1.7 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.7': + dependencies: + '@vitest/pretty-format': 4.1.7 + '@vitest/utils': 4.1.7 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.7': {} + + '@vitest/utils@4.1.7': + dependencies: + '@vitest/pretty-format': 4.1.7 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.2: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + + balanced-match@4.0.4: {} + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + chai@6.2.2: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + detect-libc@2.1.2: {} + + es-module-lexer@2.1.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@10.4.0): + dependencies: + eslint: 10.4.0 + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.4.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + + globals@17.6.0: {} + + has-flag@4.0.0: {} + + html-escaper@2.0.2: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-tokens@10.0.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lru-cache@11.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.3: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.1 + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minipass@7.1.3: {} + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + natural-compare@1.4.0: {} + + obug@2.1.1: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.0 + minipass: 7.1.3 + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + playwright-core@1.60.0: {} + + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.8.3: {} + + punycode@2.3.1: {} + + rimraf@6.1.3: + dependencies: + glob: 13.0.6 + package-json-from-dist: 1.0.1 + + rolldown@1.0.2: + dependencies: + '@oxc-project/types': 0.132.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.2 + '@rolldown/binding-darwin-arm64': 1.0.2 + '@rolldown/binding-darwin-x64': 1.0.2 + '@rolldown/binding-freebsd-x64': 1.0.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 + '@rolldown/binding-linux-arm64-gnu': 1.0.2 + '@rolldown/binding-linux-arm64-musl': 1.0.2 + '@rolldown/binding-linux-ppc64-gnu': 1.0.2 + '@rolldown/binding-linux-s390x-gnu': 1.0.2 + '@rolldown/binding-linux-x64-gnu': 1.0.2 + '@rolldown/binding-linux-x64-musl': 1.0.2 + '@rolldown/binding-openharmony-arm64': 1.0.2 + '@rolldown/binding-wasm32-wasi': 1.0.2 + '@rolldown/binding-win32-arm64-msvc': 1.0.2 + '@rolldown/binding-win32-x64-msvc': 1.0.2 + + semver@7.8.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.1.0: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinybench@2.9.0: {} + + tinyexec@1.2.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tslib@2.8.1: + optional: true + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.60.0(eslint@10.4.0)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0)(typescript@5.9.3))(eslint@10.4.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0)(typescript@5.9.3) + eslint: 10.4.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@7.24.6: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite@8.0.14(@types/node@25.9.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.2 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.9.1 + fsevents: 2.3.3 + + vitest@4.1.7(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(vite@8.0.14(@types/node@25.9.1)): + dependencies: + '@vitest/expect': 4.1.7 + '@vitest/mocker': 4.1.7(vite@8.0.14(@types/node@25.9.1)) + '@vitest/pretty-format': 4.1.7 + '@vitest/runner': 4.1.7 + '@vitest/snapshot': 4.1.7 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.14(@types/node@25.9.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.9.1 + '@vitest/coverage-v8': 4.1.7(vitest@4.1.7) + transitivePeerDependencies: + - msw + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..f544de5 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +minimumReleaseAgeExclude: + - '@interop/lru-memoize@4.0.2' diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..6bf1311 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,8 @@ +export default { + arrowParens: 'avoid', + bracketSameLine: false, + bracketSpacing: true, + semi: false, + singleQuote: true, + trailingComma: 'none' +} diff --git a/rollup.config.js b/rollup.config.js deleted file mode 100644 index 0909f3c..0000000 --- a/rollup.config.js +++ /dev/null @@ -1,15 +0,0 @@ -import pkg from './package.json'; - -export default [ - { - input: './lib/index.js', - output: [ - { - dir: 'dist', - format: 'cjs', - preserveModules: true - } - ], - external: Object.keys(pkg.dependencies).concat(['crypto', 'util']) - } -]; diff --git a/src/CachedResolver.ts b/src/CachedResolver.ts new file mode 100644 index 0000000..623c0c6 --- /dev/null +++ b/src/CachedResolver.ts @@ -0,0 +1,155 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +import { parseDid } from './did-io.js' +import type { IKeyMap } from './did-io.js' +import { LruCache } from '@interop/lru-memoize' +import type { + IDID, + IDidDocument, + IKeyPair, + IPublicKey +} from '@digitalcredentials/ssi' + +export interface DidGenerationResult { + didDocument: IDidDocument + keyPairs: IKeyMap + methodFor: ({ purpose }: { purpose: string }) => IKeyPair +} + +export interface DidMethodDriver { + method: string + + computeId: ( + { keyPair }: { keyPair: IKeyPair } + ) => Promise + + fromKeyPair: ( + { verificationKeyPair, keyAgreementKeyPair }: + { verificationKeyPair?: IKeyPair, keyAgreementKeyPair?: IKeyPair } + ) => DidGenerationResult + + generate: ( + options?: { [key: string]: unknown } + ) => Promise + + get: ( + options: { did?: IDID | string, url?: string, [key: string]: unknown } + ) => Promise + + publicKeyToDidDoc: ( + { publicKeyDescription }: { publicKeyDescription: IKeyPair | IPublicKey } + ) => Promise + + publicMethodFor: ( + { didDocument, purpose }: { didDocument: IDidDocument, purpose: string } + ) => IPublicKey +} + +export interface CachedResolverOptions { + max?: number + ttl?: number + updateAgeOnGet?: boolean + cache?: { memoize: LruCache['memoize'] } + [key: string]: unknown +} + +export class CachedResolver { + _cache: { memoize: LruCache['memoize'] } + _methods: Map + /** + * @param {object} [options={}] - Options hashmap. + * @param {number} [options.max=100] - Max number of items in the cache. + * @param {number} [options.ttl=5000] - Max age of a cache item, in ms. + * @param {boolean} [options.updateAgeOnGet=false] - When using time-expiring + * entries with `ttl`, setting this to true will make each entry's + * effective time update to the current time whenever it is retrieved from + * cache, thereby extending the expiration date of the entry. + * @param {object} [options.cache] - A custom cache instance to use instead of + * creating a default `LruCache`. Must implement `memoize({ key, fn })`. + * Useful when the app already has an `LruCache` configured with custom + * settings, or uses a different compatible cache implementation. + * @param {object} [options.cacheOptions] - Additional `lru-cache` options. + */ + constructor ({ + max = 100, ttl = 5000, updateAgeOnGet = false, cache, + ...cacheOptions + }: CachedResolverOptions = {}) { + this._cache = cache ?? new LruCache( + { max, ttl, updateAgeOnGet, ...cacheOptions } as + ConstructorParameters[0] + ) + this._methods = new Map() + } + + use (driver: DidMethodDriver): void { + this._methods.set(driver.method, driver) + } + + /** + * Gets the DID Document, by selecting a registered driver based on the DID + * prefix (DID method). + * Either `did` or `url` param is required. + * + * @param {object} options - Options hashmap. + * @param {string} [options.did] - DID uri. + * @param {string} [options.url] - Typically, a key ID or other DID-related + * url. This is used to improve code readability. + * @param {object} [options.getOptions] - Options passed through to the + * driver's get() operation. + * + * @returns {Promise} Resolves with fetched DID + * Document or public key node. + */ + async get ( + { did, url, ...getOptions }: + { did?: IDID | string, url?: string, [key: string]: unknown } + ): Promise { + const didOrUrl = did ?? url + if (!didOrUrl) { + throw new TypeError('A string "did" or "url" parameter is required.') + } + + const method = this._methodForDid(didOrUrl) + + return this._cache.memoize({ + key: didOrUrl, + fn: async () => await method.get({ did: didOrUrl, ...getOptions }) + }) + } + + /** + * Generates a new DID Document and corresponding keys, by selecting a + * registered driver based on the DID method name. + * + * @param {object} options - Options hashmap. + * @param {string} options.method - DID method id (e.g. 'key', 'v1', 'web'). + * @param {object} [options.args] - Options passed through to the DID driver. + * + * @returns {Promise} + */ + async generate ( + { method, ...args }: { method: string, [key: string]: unknown } + ): Promise { + const driver = this._methods.get(method) + if (!driver) { + throw new Error(`Driver for DID method "${method}" not found.`) + } + return driver.generate(args) + } + + /** + * @param {string} did - DID uri. + * + * @returns {DidMethodDriver} - DID Method driver. + * @private + */ + _methodForDid (did: IDID | string): DidMethodDriver { + const { prefix } = parseDid({ did }) + const method = this._methods.get(prefix) + if (!method) { + throw new Error(`Driver for DID ${did} not found.`) + } + return method + } +} diff --git a/lib/constants.js b/src/constants.ts similarity index 99% rename from lib/constants.js rename to src/constants.ts index 91e15cb..329650b 100644 --- a/lib/constants.js +++ b/src/constants.ts @@ -16,4 +16,4 @@ export const VERIFICATION_RELATIONSHIPS = new Set([ 'capabilityDelegation', 'capabilityInvocation', 'keyAgreement' -]); +]) diff --git a/src/did-io.ts b/src/did-io.ts new file mode 100644 index 0000000..8cec13b --- /dev/null +++ b/src/did-io.ts @@ -0,0 +1,278 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +import { VERIFICATION_RELATIONSHIPS } from './constants.js' +import type { + IDID, + IDidDocument, + IKeyIdOrObject, + IKeyPair +} from '@digitalcredentials/ssi' + +export type IKeyMap = Record + +/** + * Map of keys (or key type names) by verification relationship, used as input + * when initializing a DID Document's keys. + */ +export type IKeyMapInput = Record + +/** + * Tests whether this DID Document contains a verification relationship + * between the subject and a method id, for a given purpose. + * + * @example + * didDocument.approvesMethodFor({ + * methodId: 'did:ex:1234#abcd', purpose: 'authentication' + * }); + * // -> true + * @example + * didDocument.approvesMethodFor({ + * methodId: 'did:ex:1234#abcd', purpose: 'assertionMethod' + * }); + * // -> false + * + * @param {object} options - Options hashmap. + * @param {object} options.doc - DID Document. + * @param {string} options.methodId - Verification method id (a uri). + * @param {string} options.purpose - E.g. 'authentication', etc. + * + * @returns {boolean} Returns whether a method id is authorized for purpose. + */ +export function approvesMethodFor ( + { doc, methodId, purpose }: { doc: IDidDocument, methodId: string, purpose: string }): boolean { + if (!(methodId && purpose)) { + throw new Error('A method id and purpose is required.') + } + const method = _methodById({ doc, methodId }) + if (!method) { + return false + } + const methods = _methodsForPurpose({ doc, purpose }) + + return methods.some(method => { + return (typeof method === 'string' && method === methodId) || + (typeof method === 'object' && method.id === methodId) + }) +} + +/** + * Initializes the DID Document's keys/proof methods. + * + * @example + * didDocument.id = 'did:ex:123'; + * const {keyPairs} = await initKeys({ + * cryptoLd, + * keyMap: { + * capabilityInvocation: someExistingKey, + * authentication: 'Ed25519VerificationKey2020', + * assertionMethod: 'Ed25519VerificationKey2020', + * keyAgreement: 'X25519KeyAgreementKey2019' + * } + * });. + * + * @param {object} options - Options hashmap. + * @param {object} options.doc - DID Document. + * @typedef {object} CryptoLD + * @param {CryptoLD} [options.cryptoLd] - CryptoLD driver instance, + * initialized with the key types this DID Document intends to support. + * @param {object} [options.keyMap] - Map of keys (or key types) by purpose. + * + * @returns {Promise<{keyPairs: object}>} A hashmap of public/private key + * pairs, by key id. + */ +export async function initKeys ( + { doc, cryptoLd, keyMap = {} }: + { doc: IDidDocument, cryptoLd?: any, keyMap?: IKeyMapInput } +): Promise<{ keyPairs: IKeyMap }> { + if (!doc.id) { + throw new TypeError( + 'DID Document "id" property is required to initialize keys.') + } + + const keyPairs: IKeyMap = {} + + // Set the defaults for the created keys (if needed) + const options = { controller: doc.id } + + for (const purpose in keyMap) { + if (!VERIFICATION_RELATIONSHIPS.has(purpose)) { + throw new Error(`Unsupported key purpose: "${purpose}".`) + } + + const entry = keyMap[purpose] + let key: IKeyPair + if (typeof entry === 'string') { + if (!cryptoLd) { + throw new Error('Please provide an initialized CryptoLD instance.') + } + key = await cryptoLd.generate({ type: entry, ...options }) + } else { + // An existing key has been provided + key = entry as IKeyPair + } + + if (!key.id) { + throw new Error('Initialized key is missing an "id" property.') + } + keyPairs[key.id] = key + } + + return { keyPairs } +} + +/** + * Finds a verification method for a given methodId or purpose. + * + * If a method id is given, returns the object for that method (for example, + * returns the public key definition for that id). + * + * If a purpose (verification relationship) is given, returns the first + * available verification method for that purpose. + * + * If no method is found (for the given id or purpose), returns undefined. + * + * @example + * findVerificationMethod({doc, methodId: 'did:ex:123#abcd'}); + * // -> + * { + * id: 'did:ex:123#abcd', + * controller: 'did:ex:123', + * type: 'Ed25519VerificationKey2020', + * publicKeyMultibase: '...' + * } + * @example + * didDocument.findVerificationMethod({doc, purpose: 'authentication'}); + * // -> + * { + * id: 'did:ex:123#abcd', + * controller: 'did:ex:123', + * type: 'Ed25519VerificationKey2020', + * publicKeyMultibase: '...' + * } + * @param {object} options - Options hashmap. + * @param {object} options.doc - DID Document. + * + * One of the following is required. + * @param {string} [options.methodId] - Verification method id. + * @param {string} [options.purpose] - Method purpose (verification + * relationship). + * + * @returns {object} Returns the verification method, or undefined if not found. + */ +export function findVerificationMethod ( + { doc, methodId, purpose }: + { doc: IDidDocument, methodId?: string, purpose?: string } +): IKeyIdOrObject | undefined { + if (!doc) { + throw new TypeError('A DID Document is required.') + } + if (!(methodId ?? purpose)) { + throw new TypeError('A method id or purpose is required.') + } + + if (methodId) { + return _methodById({ doc, methodId }) + } + + // Id not given, find the first method by purpose + const [method] = _methodsForPurpose({ doc, purpose }) + if (method && typeof method === 'string') { + // This is a reference, not the full method, attempt to find it + return _methodById({ doc, methodId: method }) + } + + return method +} + +/** + * Finds a verification method for a given id and returns it. + * + * @param {object} options - Options hashmap. + * @param {object} options.doc - DID Document. + * @param {string} options.methodId - Verification method id. + * + * @returns {object} Returns the verification method. + */ +export function _methodById ( + { doc, methodId }: { doc: IDidDocument, methodId: string } +): IKeyIdOrObject | undefined { + let result + + // First, check the 'verificationMethod' bucket, see if it's listed there + let unlistedPurposeMethods: IKeyIdOrObject[] | undefined + if (doc.verificationMethod) { + unlistedPurposeMethods = Array.isArray(doc.verificationMethod) + ? doc.verificationMethod + : [doc.verificationMethod] + result = unlistedPurposeMethods.find((method: IKeyIdOrObject) => { + return (typeof method === 'string' && method === methodId) || + (typeof method === 'object' && method.id === methodId) + }) + if (result) { + return result + } + } + + for (const purpose of VERIFICATION_RELATIONSHIPS) { + const methods = _methodsForPurpose({ doc, purpose }) + // Iterate through each verification method in 'authentication', etc. + for (const method of methods) { + // Only return it if the method is defined, not referenced + if (typeof method === 'object' && method.id === methodId) { + result = method + break + } + } + if (result) { + return result + } + } +} + +/** + * Reads a DID Document's verification relationship (by purpose) and normalizes + * it to an array of verification methods (key ids or key objects). The DID Core + * data model allows each relationship to be either a single value or an array. + * + * @param {object} options - Options hashmap. + * @param {object} options.doc - DID Document. + * @param {string} [options.purpose] - Verification relationship (e.g. + * 'authentication'). + * + * @returns {IKeyIdOrObject[]} The methods for that purpose (empty if none). + */ +function _methodsForPurpose ( + { doc, purpose }: { doc: IDidDocument, purpose?: string } +): IKeyIdOrObject[] { + if (!purpose) { + return [] + } + const value = (doc as unknown as + Record)[purpose] + if (value == null) { + return [] + } + return Array.isArray(value) ? value : [value] +} + +/** + * Parses the DID into various components (currently, only cares about prefix). + * + * @example + * parseDid({did: 'did:v1:test:nym'}); + * // -> {prefix: 'v1'} + * + * @param {string} did - DID uri. + * + * @returns {{prefix: string}} Returns the method prefix (without `did:`). + */ +export function parseDid ({ did }: { did: IDID | string }): { prefix: string } { + if (!did) { + throw new TypeError('DID cannot be empty.') + } + + const prefix = did.split(':').slice(1, 2).join(':') + + return { prefix } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..9f8d2c6 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,13 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +export * from './did-io.js' + +export { CachedResolver } from './CachedResolver.js' +export type { + DidGenerationResult, + DidMethodDriver, + CachedResolverOptions +} from './CachedResolver.js' + +export { VERIFICATION_RELATIONSHIPS } from './constants.js' diff --git a/test/.eslintrc.js b/test/.eslintrc.js deleted file mode 100644 index f76d0f3..0000000 --- a/test/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - env: { - mocha: true - }, - globals: { - should: true - } -}; diff --git a/test/browser/did-io.spec.ts b/test/browser/did-io.spec.ts new file mode 100644 index 0000000..72c4087 --- /dev/null +++ b/test/browser/did-io.spec.ts @@ -0,0 +1,10 @@ +import { test, expect } from '@playwright/test' + +test('did-io utilities work in browser', async ({ page }) => { + await page.goto('/test/index.html') + const result = await page.evaluate(async () => { + const { parseDid } = await import('/src/index.ts') + return parseDid({ did: 'did:v1:test:nym:abcd' }).prefix + }) + expect(result).toBe('v1') +}) diff --git a/test/did-io.spec.js b/test/did-io.spec.js deleted file mode 100644 index 78a4d7d..0000000 --- a/test/did-io.spec.js +++ /dev/null @@ -1,172 +0,0 @@ -/*! - * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. - */ -import chai from 'chai'; -chai.should(); -const {expect} = chai; - -import { - findVerificationMethod, approvesMethodFor, parseDid -} from '../'; - -const MOCK_KEY = { - id: 'did:ex:123#abcd', - controller: 'did:ex:123', - type: 'Ed25519VerificationKey2020', - publicKeyMultibase: '...' -}; - -describe('parseDid', () => { - it('should return main did method identifier', async () => { - const {prefix} = parseDid({did: 'did:v1:test:nym:abcd'}); - expect(prefix).to.equal('v1'); - }); -}); - -describe('didIo utility functions', () => { - describe('findVerificationMethod', () => { - const did = 'did:ex:123'; - let key; - - beforeEach(async () => { - key = {...MOCK_KEY}; - }); - - it('should return undefined if key is not found by id', async () => { - const doc = { - id: did, - verificationMethod: [key], - authentication: [key.id] - }; - - const result = findVerificationMethod({doc, methodId: 'a key id'}); - expect(result).to.be.undefined; - }); - - it('should return undefined if key is not found by purpose', async () => { - const doc = { - id: did, - authentication: [key], - assertionMethod: [] - }; - - expect(findVerificationMethod({doc, purpose: 'assertionMethod'})) - .to.be.undefined; - expect(findVerificationMethod({doc, purpose: 'capabilityInvocation'})) - .to.be.undefined; - }); - - it('should find by id in verificationMethod', async () => { - const doc = { - id: did, - verificationMethod: [key], - authentication: [key.id] - }; - - const result = findVerificationMethod({doc, methodId: key.id}); - expect(result).to.eql(MOCK_KEY); - }); - - it('should find by id if defined in purpose', async () => { - const doc = { - id: did, - authentication: [key] - }; - - const result = findVerificationMethod({doc, methodId: key.id}); - expect(result).to.eql(MOCK_KEY); - }); - - it('should find by id if referenced', async () => { - const doc = { - id: did, - authentication: [key.id], - assertionMethod: [key] - }; - - const result = findVerificationMethod({doc, methodId: key.id}); - expect(result).to.eql(MOCK_KEY); - }); - - it('should find by purpose in verificationMethod', async () => { - const doc = { - id: did, - verificationMethod: [key], - authentication: [key.id] - }; - - const result = findVerificationMethod({doc, purpose: 'authentication'}); - expect(result).to.eql(MOCK_KEY); - }); - - it('should find by purpose if defined in purpose', async () => { - const doc = { - id: did, - authentication: [key] - }; - - const result = findVerificationMethod({doc, purpose: 'authentication'}); - expect(result).to.eql(MOCK_KEY); - }); - - it('should find by purpose if referenced', async () => { - const doc = { - id: did, - authentication: [key.id], - assertionMethod: [key] - }; - - const result = findVerificationMethod({doc, purpose: 'authentication'}); - expect(result).to.eql(MOCK_KEY); - }); - }); - - describe('approvesMethodFor', () => { - const did = 'did:ex:123'; - let key; - - beforeEach(async () => { - key = {...MOCK_KEY}; - }); - - it('should return false if method not in document', async () => { - const doc = { - id: did - }; - - const result = approvesMethodFor({ - doc, methodId: key.id, purpose: 'authentication' - }); - expect(result).to.be.false; - }); - - it('should return false if method not approved', async () => { - const doc = { - id: did, - verificationMethod: [key] - }; - - expect(approvesMethodFor({ - doc, methodId: key.id, purpose: 'authentication' - })).to.be.false; - - doc.assertionMethod = [key.id]; - expect(approvesMethodFor({ - doc, methodId: key.id, purpose: 'authentication' - })).to.be.false; - }); - - it('should return true if method is approved (referenced)', async () => { - const doc = { - id: did, - verificationMethod: [key], - authentication: [key.id] - }; - - expect(approvesMethodFor({ - doc, methodId: key.id, purpose: 'authentication' - })).to.be.true; - }); - }); -}); - diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..a63087c --- /dev/null +++ b/test/index.html @@ -0,0 +1,9 @@ + + + + + + Dev + + + diff --git a/test/node/CachedResolver.test.ts b/test/node/CachedResolver.test.ts new file mode 100644 index 0000000..7a282bf --- /dev/null +++ b/test/node/CachedResolver.test.ts @@ -0,0 +1,122 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +import { describe, it, beforeEach, expect, vi } from 'vitest' +import type { IDidDocument } from '@digitalcredentials/ssi' + +import { CachedResolver } from '../../src/index.js' +import type { DidMethodDriver } from '../../src/index.js' + +const MOCK_DID = 'did:ex:1234' +const MOCK_DID_DOCUMENT = { id: MOCK_DID } as unknown as IDidDocument + +function makeDriver (method = 'ex'): DidMethodDriver { + return { + method, + computeId: vi.fn(), + fromKeyPair: vi.fn(), + generate: vi.fn(async () => ({ + didDocument: MOCK_DID_DOCUMENT, + keyPairs: {}, + methodFor: vi.fn() + })), + get: vi.fn(async () => MOCK_DID_DOCUMENT), + publicKeyToDidDoc: vi.fn(), + publicMethodFor: vi.fn() + } as unknown as DidMethodDriver +} + +function cacheOptions (resolver: CachedResolver): { ttl: number, max: number } { + return (resolver._cache as unknown as { + options: { ttl: number, max: number } + }).options +} + +describe('CachedResolver', () => { + describe('constructor', () => { + it('should default the cache ttl', () => { + const resolver = new CachedResolver() + expect(cacheOptions(resolver).ttl).toBe(5000) + }) + + it('should use an explicit ttl', () => { + const resolver = new CachedResolver({ ttl: 60000 }) + expect(cacheOptions(resolver).ttl).toBe(60000) + }) + + it('should use an explicit max', () => { + const resolver = new CachedResolver({ max: 50 }) + expect(cacheOptions(resolver).max).toBe(50) + }) + + it('should use an injected custom cache', () => { + const cache = { memoize: vi.fn() } + const resolver = new CachedResolver({ cache }) + expect(resolver._cache).toBe(cache) + }) + }) + + describe('use()', () => { + it('should register a driver by method name', () => { + const resolver = new CachedResolver() + const driver = makeDriver('ex') + resolver.use(driver) + expect(resolver._methods.get('ex')).toBe(driver) + }) + }) + + describe('get()', () => { + let resolver: CachedResolver + let driver: DidMethodDriver + + beforeEach(() => { + resolver = new CachedResolver() + driver = makeDriver('ex') + resolver.use(driver) + }) + + it('should resolve via the registered driver', async () => { + const result = await resolver.get({ did: MOCK_DID }) + expect(result).toEqual(MOCK_DID_DOCUMENT) + expect(driver.get).toHaveBeenCalledOnce() + }) + + it('should accept a url alias for did', async () => { + const result = await resolver.get({ url: MOCK_DID }) + expect(result).toEqual(MOCK_DID_DOCUMENT) + }) + + it('should return the cached result on a second call', async () => { + await resolver.get({ did: MOCK_DID }) + await resolver.get({ did: MOCK_DID }) + expect(driver.get).toHaveBeenCalledOnce() + }) + + it('should throw if neither did nor url is given', async () => { + await expect(resolver.get({})).rejects.toThrow(TypeError) + }) + + it('should throw if no driver is registered for the method', async () => { + await expect(resolver.get({ did: 'did:nope:1234' })) + .rejects.toThrow(/not found/) + }) + }) + + describe('generate()', () => { + it('should delegate to the registered driver', async () => { + const resolver = new CachedResolver() + const driver = makeDriver('ex') + resolver.use(driver) + + const result = await resolver.generate({ method: 'ex', keyType: 'k' }) + expect(result.didDocument).toEqual(MOCK_DID_DOCUMENT) + expect(driver.generate).toHaveBeenCalledWith({ keyType: 'k' }) + }) + + it('should throw if no driver is registered for the method', async () => { + const resolver = new CachedResolver() + await expect(resolver.generate({ method: 'nope' })) + .rejects.toThrow(/not found/) + }) + }) +}) diff --git a/test/node/did-io.test.ts b/test/node/did-io.test.ts new file mode 100644 index 0000000..107f272 --- /dev/null +++ b/test/node/did-io.test.ts @@ -0,0 +1,172 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +import { describe, it, beforeEach, expect } from 'vitest' +import type { IDidDocument } from '@digitalcredentials/ssi' + +import { + findVerificationMethod, + approvesMethodFor, + parseDid +} from '../../src/index.js' + +const MOCK_KEY = { + id: 'did:ex:123#abcd', + controller: 'did:ex:123', + type: 'Ed25519VerificationKey2020', + publicKeyMultibase: '...' +} + +describe('parseDid', () => { + it('should return main did method identifier', () => { + const { prefix } = parseDid({ did: 'did:v1:test:nym:abcd' }) + expect(prefix).toBe('v1') + }) +}) + +describe('didIo utility functions', () => { + describe('findVerificationMethod', () => { + const did = 'did:ex:123' + let key: typeof MOCK_KEY + + beforeEach(() => { + key = { ...MOCK_KEY } + }) + + it('should return undefined if key is not found by id', () => { + const doc = { + id: did, + verificationMethod: [key], + authentication: [key.id] + } as unknown as IDidDocument + + const result = findVerificationMethod({ doc, methodId: 'a key id' }) + expect(result).toBeUndefined() + }) + + it('should return undefined if key is not found by purpose', () => { + const doc = { + id: did, + authentication: [key], + assertionMethod: [] + } as unknown as IDidDocument + + expect(findVerificationMethod({ doc, purpose: 'assertionMethod' })) + .toBeUndefined() + expect(findVerificationMethod({ doc, purpose: 'capabilityInvocation' })) + .toBeUndefined() + }) + + it('should find by id in verificationMethod', () => { + const doc = { + id: did, + verificationMethod: [key], + authentication: [key.id] + } as unknown as IDidDocument + + const result = findVerificationMethod({ doc, methodId: key.id }) + expect(result).toEqual(MOCK_KEY) + }) + + it('should find by id if defined in purpose', () => { + const doc = { + id: did, + authentication: [key] + } as unknown as IDidDocument + + const result = findVerificationMethod({ doc, methodId: key.id }) + expect(result).toEqual(MOCK_KEY) + }) + + it('should find by id if referenced', () => { + const doc = { + id: did, + authentication: [key.id], + assertionMethod: [key] + } as unknown as IDidDocument + + const result = findVerificationMethod({ doc, methodId: key.id }) + expect(result).toEqual(MOCK_KEY) + }) + + it('should find by purpose in verificationMethod', () => { + const doc = { + id: did, + verificationMethod: [key], + authentication: [key.id] + } as unknown as IDidDocument + + const result = findVerificationMethod({ doc, purpose: 'authentication' }) + expect(result).toEqual(MOCK_KEY) + }) + + it('should find by purpose if defined in purpose', () => { + const doc = { + id: did, + authentication: [key] + } as unknown as IDidDocument + + const result = findVerificationMethod({ doc, purpose: 'authentication' }) + expect(result).toEqual(MOCK_KEY) + }) + + it('should find by purpose if referenced', () => { + const doc = { + id: did, + authentication: [key.id], + assertionMethod: [key] + } as unknown as IDidDocument + + const result = findVerificationMethod({ doc, purpose: 'authentication' }) + expect(result).toEqual(MOCK_KEY) + }) + }) + + describe('approvesMethodFor', () => { + const did = 'did:ex:123' + let key: typeof MOCK_KEY + + beforeEach(() => { + key = { ...MOCK_KEY } + }) + + it('should return false if method not in document', () => { + const doc = { + id: did + } as unknown as IDidDocument + + const result = approvesMethodFor({ + doc, methodId: key.id, purpose: 'authentication' + }) + expect(result).toBe(false) + }) + + it('should return false if method not approved', () => { + const doc = { + id: did, + verificationMethod: [key] + } as unknown as IDidDocument & { assertionMethod?: string[] } + + expect(approvesMethodFor({ + doc, methodId: key.id, purpose: 'authentication' + })).toBe(false) + + doc.assertionMethod = [key.id] + expect(approvesMethodFor({ + doc, methodId: key.id, purpose: 'authentication' + })).toBe(false) + }) + + it('should return true if method is approved (referenced)', () => { + const doc = { + id: did, + verificationMethod: [key], + authentication: [key.id] + } as unknown as IDidDocument + + expect(approvesMethodFor({ + doc, methodId: key.id, purpose: 'authentication' + })).toBe(true) + }) + }) +}) diff --git a/tsconfig.dev.json b/tsconfig.dev.json new file mode 100644 index 0000000..2db7379 --- /dev/null +++ b/tsconfig.dev.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": [ + "src/**/*", + "test/**/*.ts", + "vite.config.ts", + "playwright.config.ts" + ], + "exclude": ["node_modules", "dist"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..052e27b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "strict": true, + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "ESNext", + "moduleResolution": "Bundler", + "outDir": "dist", + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noUncheckedIndexedAccess": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..bcab9d5 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/node/**/*.test.ts', 'src/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov'], + include: ['src/**/*.ts'] + } + } +})