From 329eab10e3dbf05f6297f02745524a498f72db9f Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Fri, 16 May 2025 21:00:21 -0400 Subject: [PATCH 1/7] Convert to Typescript. Signed-off-by: Dmitri Zagidulin --- .editorconfig | 11 +++ .eslintrc.cjs | 15 +++ .eslintrc.js | 14 --- .github/workflows/main.yml | 50 ++++------ .travis.yml | 11 --- CHANGELOG.md | 5 + build-dist.sh | 16 ---- karma.conf.js | 75 --------------- lib/CachedResolver.js | 71 -------------- lib/index.js | 13 --- package.json | 105 ++++++++++----------- rollup.config.js | 15 --- src/CachedResolver.ts | 106 +++++++++++++++++++++ lib/constants.js => src/constants.ts | 2 +- src/declarations.d.ts | 1 + lib/did-io.js => src/did-io.ts | 133 ++++++++++++++++----------- src/index.ts | 8 ++ test/.eslintrc.js | 8 -- test/did-io.spec.js | 133 +++++++++++++-------------- tsconfig.json | 23 +++++ tsconfig.spec.json | 29 ++++++ 21 files changed, 407 insertions(+), 437 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintrc.cjs delete mode 100644 .eslintrc.js delete mode 100644 .travis.yml delete mode 100755 build-dist.sh delete mode 100644 karma.conf.js delete mode 100644 lib/CachedResolver.js delete mode 100644 lib/index.js delete mode 100644 rollup.config.js create mode 100644 src/CachedResolver.ts rename lib/constants.js => src/constants.ts (99%) create mode 100644 src/declarations.d.ts rename lib/did-io.js => src/did-io.ts (58%) create mode 100644 src/index.ts delete mode 100644 test/.eslintrc.js create mode 100644 tsconfig.json create mode 100644 tsconfig.spec.json 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.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..8439cc2 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,15 @@ +module.exports = { + overrides: [ + { + files: ['*.js', '*.jsx', '*.ts', '*.tsx'], + extends: 'standard-with-typescript', + parserOptions: { + project: './tsconfig.spec.json' + }, + rules: { + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/strict-boolean-expressions': 'off' + } + } + ] +} 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/main.yml b/.github/workflows/main.yml index 76d562a..7333f53 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,43 +3,27 @@ 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 +# test-node: +# runs-on: ubuntu-latest +# strategy: +# matrix: +# node-version: [20.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 lint: runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x] + node-version: [20.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} 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..17d88d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # did-io ChangeLog +## 2.0.0 - + +### Changed +- **BREAKING**: Convert to Typescript. + ## 1.0.1 - 2021-10-01 ### Changed 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/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..9e3008a 100644 --- a/package.json +++ b/package.json @@ -3,80 +3,69 @@ "version": "1.0.2", "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" + "scripts": { + "build": "npm run clear && tsc -d", + "clear": "rimraf dist/*", + "lint": "ts-standard --fix --project tsconfig.spec.json", + "prepare": "npm run build", + "rebuild": "npm run clear && npm run build", + "test": "npm run lint && npm run test-node", + "test-node": "npx tsx --test test/*.spec.ts" }, - "license": "BSD-3-Clause", - "files": [ - "dist", - "lib", - "rollup.config.js", - "build-dist.sh", - "README.md", - "LICENSE" - ], - "main": "dist/index.js", - "module": "dist/esm/index.js", + "type": "module", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "require": "./dist/index.js", - "import": "./dist/esm/index.js" - }, - "./package.json": "./package.json" + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } }, "dependencies": { - "@digitalcredentials/lru-memoize": "^2.1.1" + "@digitalcredentials/lru-memoize": "^2.1.4", + "@digitalcredentials/ssi": "^3.0.5" }, "devDependencies": { - "chai": "^4.3.4", + "@types/chai": "^5.2.1", + "@types/mocha": "^10.0.1", + "@types/node": "^22.14.1", "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", + "mocha": "^10.2.0", "rimraf": "^3.0.2", - "rollup": "^2.47.0", - "webpack": "^5.36.2" + "ts-node": "^10.9.1", + "ts-standard": "^12.0.2", + "typescript": "^5.8.3", + "tsx": "^4.19.4" }, "engines": { - "node": ">=12" + "node": ">=18" }, "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" }, - "eslintConfig": { - "env": { - "mocha": true, - "node": true - } - } + "ts-standard": { + "env": [ "mocha" ], + "ignore": [ + "dist", + "test" + ], + "globals": [ "it"] + }, + "repository": { + "type": "git", + "url": "git+http://github.com/digitalcredentials/did-io.git" + }, + "license": "BSD-3-Clause", + "files": [ + "dist", + "src", + "CHANGELOG.md", + "README.md", + "LICENSE" + ] } 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..04ceee1 --- /dev/null +++ b/src/CachedResolver.ts @@ -0,0 +1,106 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +import { parseDid, IKeyMap } from './did-io.js' +import { LruCache } from '@digitalcredentials/lru-memoize' +import { IDID, IDidDocument, IKeyPair, IPublicKey } from '@digitalcredentials/ssi' + +export interface DidGenerationResult { + didDocument: IDidDocument + keyPairs: IKeyMap + methodFor: ({ purpose }: { purpose: string }) => IKeyPair +} + +export interface DidKeyDriver { + computeId: ( + { keyPair }: { keyPair: IKeyPair } + ) => Promise + + fromKeyPair: ( + { verificationKeyPair, keyAgreementKeyPair }: + { verificationKeyPair?: IKeyPair, keyAgreementKeyPair?: IKeyPair } + ) => DidGenerationResult + + get: ( + { did, url }: { did?: IDID, url?: string } + ) => IDidDocument | IPublicKey + + publicKeyToDidDoc: ( + { publicKeyDescription }: { publicKeyDescription: IKeyPair | IPublicKey } + ) => Promise + + publicMethodFor: ( + { didDocument, purpose }: { didDocument: IDidDocument, purpose: string } + ) => IPublicKey +} + +export class CachedResolver { + _cache + _methods + /** + * @param {object} [options={}] - Options hashmap. + * @param {number} [options.max=100] - Max number of items in the cache. + * @param {number} [options.maxAge=5000] - Max age of a cache item, in ms. + * @param {boolean} [options.updateAgeOnGet=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 expiration date of the entry. + * @param {object} [options.cacheOptions] - Additional `lru-cache` options. + */ + constructor ({ + max = 100, maxAge = 5000, updateAgeOnGet = false, + ...cacheOptions + } = {}) { + this._cache = new LruCache({ max, maxAge, updateAgeOnGet, ...cacheOptions }) + this._methods = new Map() + } + + use (driver: any): void { + const methodName = driver.method + this._methods.set(methodName, 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, url: string }): Promise { + 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 {DidKeyDriver} - DID Method driver. + * @private + */ + _methodForDid (did: IDID | string): DidKeyDriver { + 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/declarations.d.ts b/src/declarations.d.ts new file mode 100644 index 0000000..1052ee2 --- /dev/null +++ b/src/declarations.d.ts @@ -0,0 +1 @@ +declare module '@digitalcredentials/lru-memoize' diff --git a/lib/did-io.js b/src/did-io.ts similarity index 58% rename from lib/did-io.js rename to src/did-io.ts index 6d02b1d..e6772b1 100644 --- a/lib/did-io.js +++ b/src/did-io.ts @@ -1,7 +1,10 @@ /*! * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -import {VERIFICATION_RELATIONSHIPS} from './constants.js'; +import { VERIFICATION_RELATIONSHIPS } from './constants.js' +import { IDID, IDidDocument, IKeyIdOrObject, IKeyPair } from '@digitalcredentials/ssi' + +export type IKeyMap = Record /** * Tests whether this DID Document contains a verification relationship @@ -25,19 +28,22 @@ import {VERIFICATION_RELATIONSHIPS} from './constants.js'; * * @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.'); +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 method = _methodById({ doc, methodId }) + if (!method) { + return false } - const methods = doc[purpose] || []; + // @ts-expect-error + const methods: IKeyIdOrObject[] = doc[purpose] || [] + return !!methods.find(method => { return (typeof method === 'string' && method === methodId) || - (typeof method === 'object' && method.id === methodId); - }); + (typeof method === 'object' && method.id === methodId) + }) } /** @@ -65,38 +71,40 @@ export function approvesMethodFor({doc, methodId, 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) { +export async function initKeys ( + { doc, cryptoLd, keyMap = {} }: { doc: IDidDocument, cryptoLd: any, keyMap?: IKeyMap }): Promise<{ keyPairs: IKeyMap }> { + if (!doc.id) { throw new TypeError( - 'DID Document "id" property is required to initialize keys.'); + 'DID Document "id" property is required to initialize keys.') } - const keyPairs = {}; + const keyPairs: IKeyMap = {} // Set the defaults for the created keys (if needed) - const options = {controller: doc.id}; + const options = { controller: doc.id } - for(const purpose in keyMap) { - if(!VERIFICATION_RELATIONSHIPS.has(purpose)) { - throw new Error(`Unsupported key purpose: "${purpose}".`); + 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.'); + 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}); + key = await cryptoLd.generate({ type: keyMap[purpose], ...options }) } else { // An existing key has been provided - key = keyMap[purpose]; + key = keyMap[purpose] } - this[purpose] = [key.export({publicKey: true})]; - keyPairs[key.id] = key; + // TODO: why was 'this' used here? + // this[purpose] = [key.export({ publicKey: true })] + keyPairs[key.id] = key } - return {keyPairs}; + return { keyPairs } } /** @@ -138,26 +146,28 @@ export async function initKeys({doc, cryptoLd, keyMap = {}} = {}) { * * @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.'); +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 || purpose)) { + throw new TypeError('A method id or purpose is required.') } - if(methodId) { - return _methodById({doc, methodId}); + if (methodId) { + return _methodById({ doc, methodId }) } // Id not given, find the first method by purpose - const [method] = doc[purpose] || []; - if(method && typeof method === 'string') { + // @ts-expect-error + 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 _methodById({ doc, methodId: method }) } - return method; + return method } /** @@ -169,26 +179,39 @@ export function findVerificationMethod({doc, methodId, purpose} = {}) { * * @returns {object} Returns the verification method. */ -export function _methodById({doc, methodId}) { - let result; +export function _methodById ( + { doc, methodId }: { doc: IDidDocument, methodId: string } +): IKeyIdOrObject | undefined { + let result // First, check the 'verificationMethod' bucket, see if it's listed there - if(doc.verificationMethod) { - result = doc.verificationMethod.find(method => method.id === methodId); + 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 = doc[purpose] || []; + for (const purpose of VERIFICATION_RELATIONSHIPS) { + // @ts-expect-error + const methods = doc[purpose] || [] // Iterate through each verification method in 'authentication', etc. - for(const method of methods) { + 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 (typeof method === 'object' && method.id === methodId) { + result = method + break } } - if(result) { - return result; + if (result) { + return result } } } @@ -204,12 +227,12 @@ export function _methodById({doc, methodId}) { * * @returns {{prefix: string}} Returns the method prefix (without `did:`). */ -export function parseDid({did}) { - if(!did) { - throw new TypeError('DID cannot be empty.'); +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(':'); + const prefix = did.split(':').slice(1, 2).join(':') - return {prefix}; + return { prefix } } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..3d84b78 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,8 @@ +/*! + * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. + */ +export * from './did-io.js' + +export { CachedResolver } 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/did-io.spec.js b/test/did-io.spec.js index 78a4d7d..f45d114 100644 --- a/test/did-io.spec.js +++ b/test/did-io.spec.js @@ -1,172 +1,171 @@ /*! * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -import chai from 'chai'; -chai.should(); -const {expect} = chai; +import chai from 'chai' import { findVerificationMethod, approvesMethodFor, parseDid -} from '../'; +} from '../' +chai.should() +const { expect } = chai 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'); - }); -}); + 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; + const did = 'did:ex:123' + let key beforeEach(async () => { - key = {...MOCK_KEY}; - }); + 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; - }); + 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; - }); + 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); - }); + 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); - }); + 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); - }); + 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); - }); + 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); - }); + 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); - }); - }); + const result = findVerificationMethod({ doc, purpose: 'authentication' }) + expect(result).to.eql(MOCK_KEY) + }) + }) describe('approvesMethodFor', () => { - const did = 'did:ex:123'; - let key; + const did = 'did:ex:123' + let key beforeEach(async () => { - key = {...MOCK_KEY}; - }); + 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; - }); + }) + 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; + })).to.be.false - doc.assertionMethod = [key.id]; + doc.assertionMethod = [key.id] expect(approvesMethodFor({ doc, methodId: key.id, purpose: 'authentication' - })).to.be.false; - }); + })).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; - }); - }); -}); - + })).to.be.true + }) + }) +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e33c920 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es2022", + "lib": ["es2020"], + "module": "es6", + "moduleResolution": "node", + "declarationMap": true, + "declaration": true, + "outDir": "dist", + "noImplicitAny": true, + "removeComments": false, + "preserveConstEnums": true, + "baseUrl": ".", + "skipLibCheck": true, + "checkJs": true, + "allowJs": true + }, + "include": [ + "src/**/*" + ], + "exclude": ["test", "node_modules", "dist", ".eslintrc.cjs"] +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..fff677c --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es2022", + "lib": ["es2020"], + "module": "es6", + "moduleResolution": "node", + "declarationMap": true, + "declaration": true, + "outDir": "dist", + "noImplicitAny": true, + "removeComments": false, + "preserveConstEnums": true, + "baseUrl": ".", + "skipLibCheck": true, + "checkJs": true, + "allowJs": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true + }, + "ts-node": { + "files": true + }, + "include": [ + "src/**/*", + "test/**/*.spec.ts" + ], + "exclude": ["node_modules", "dist", ".eslintrc.cjs"] +} From f29fe1e8112c607919818a7fc8bf60ad6a11a81a Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Sat, 17 May 2025 19:46:04 -0400 Subject: [PATCH 2/7] Release v2.0.0-beta.1 Signed-off-by: Dmitri Zagidulin --- package.json | 2 +- src/index.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9e3008a..6d54435 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@digitalcredentials/did-io", - "version": "1.0.2", + "version": "2.0.0-beta.1", "description": "A library for managing DIDs (Decentralized Identifiers) and associated data.", "homepage": "http://github.com/digitalcredentials/did-io", "scripts": { diff --git a/src/index.ts b/src/index.ts index 3d84b78..fc079b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ */ export * from './did-io.js' -export { CachedResolver } from './CachedResolver.js' +export { CachedResolver, DidGenerationResult, DidKeyDriver } + from './CachedResolver.js' export { VERIFICATION_RELATIONSHIPS } from './constants.js' From 57eb13eb825bf6c65def333770646e05a84266de Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Sat, 17 May 2025 20:58:07 -0400 Subject: [PATCH 3/7] Update to latest lru-memoize@3.0.0-beta.1 dep. Signed-off-by: Dmitri Zagidulin --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d54435..d8081cd 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ } }, "dependencies": { - "@digitalcredentials/lru-memoize": "^2.1.4", + "@digitalcredentials/lru-memoize": "^3.0.0-beta.1", "@digitalcredentials/ssi": "^3.0.5" }, "devDependencies": { From 7ed06251f41daa7daec15fa22d0a019895674dee Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Sat, 17 May 2025 20:58:24 -0400 Subject: [PATCH 4/7] Release v2.0.0-beta.2 Signed-off-by: Dmitri Zagidulin --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8081cd..e83d5fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@digitalcredentials/did-io", - "version": "2.0.0-beta.1", + "version": "2.0.0-beta.2", "description": "A library for managing DIDs (Decentralized Identifiers) and associated data.", "homepage": "http://github.com/digitalcredentials/did-io", "scripts": { From 2d3dabd3972ee4be0fd745379d68af1a1732b43b Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Sat, 17 May 2025 21:11:45 -0400 Subject: [PATCH 5/7] Fix DidMethodDriver export. Signed-off-by: Dmitri Zagidulin --- src/CachedResolver.ts | 6 +++--- src/index.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CachedResolver.ts b/src/CachedResolver.ts index 04ceee1..4a32b73 100644 --- a/src/CachedResolver.ts +++ b/src/CachedResolver.ts @@ -11,7 +11,7 @@ export interface DidGenerationResult { methodFor: ({ purpose }: { purpose: string }) => IKeyPair } -export interface DidKeyDriver { +export interface DidMethodDriver { computeId: ( { keyPair }: { keyPair: IKeyPair } ) => Promise @@ -92,10 +92,10 @@ export class CachedResolver { /** * @param {string} did - DID uri. * - * @returns {DidKeyDriver} - DID Method driver. + * @returns {DidMethodDriver} - DID Method driver. * @private */ - _methodForDid (did: IDID | string): DidKeyDriver { + _methodForDid (did: IDID | string): DidMethodDriver { const { prefix } = parseDid({ did }) const method = this._methods.get(prefix) if (!method) { diff --git a/src/index.ts b/src/index.ts index fc079b7..5353650 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ */ export * from './did-io.js' -export { CachedResolver, DidGenerationResult, DidKeyDriver } +export { CachedResolver, DidGenerationResult, DidMethodDriver } from './CachedResolver.js' export { VERIFICATION_RELATIONSHIPS } from './constants.js' From 9c81b5a4d2de9cea249b1cc5109d56fdf416fce4 Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Sat, 17 May 2025 21:12:03 -0400 Subject: [PATCH 6/7] Release v2.0.0-beta.3 Signed-off-by: Dmitri Zagidulin --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e83d5fa..4f97d24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@digitalcredentials/did-io", - "version": "2.0.0-beta.2", + "version": "2.0.0-beta.3", "description": "A library for managing DIDs (Decentralized Identifiers) and associated data.", "homepage": "http://github.com/digitalcredentials/did-io", "scripts": { From e524014353894a542ccc3dbd7024fcee82e7da0b Mon Sep 17 00:00:00 2001 From: Dmitri Zagidulin Date: Tue, 26 May 2026 21:54:26 -0400 Subject: [PATCH 7/7] Convert to TypeScript, update to modern testing infra. Signed-off-by: Dmitri Zagidulin --- .eslintrc.cjs | 15 - .github/workflows/ci.yml | 37 + .github/workflows/issues-to-project.yml | 14 - .github/workflows/main.yml | 35 - .github/workflows/publish.yml | 33 + .gitignore | 94 +- CHANGELOG.md | 8 +- README.md | 52 +- eslint.config.js | 40 + package.json | 93 +- playwright.config.ts | 25 + pnpm-lock.yaml | 1820 ++++++++++++++++++ pnpm-workspace.yaml | 2 + prettier.config.js | 8 + src/CachedResolver.ts | 93 +- src/declarations.d.ts | 1 - src/did-io.ts | 76 +- src/index.ts | 8 +- test/browser/did-io.spec.ts | 10 + test/index.html | 9 + test/node/CachedResolver.test.ts | 122 ++ test/{did-io.spec.js => node/did-io.test.ts} | 93 +- tsconfig.dev.json | 13 + tsconfig.json | 30 +- tsconfig.spec.json | 29 - vite.config.ts | 12 + 26 files changed, 2485 insertions(+), 287 deletions(-) delete mode 100644 .eslintrc.cjs create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/issues-to-project.yml delete mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/publish.yml create mode 100644 eslint.config.js create mode 100644 playwright.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 prettier.config.js delete mode 100644 src/declarations.d.ts create mode 100644 test/browser/did-io.spec.ts create mode 100644 test/index.html create mode 100644 test/node/CachedResolver.test.ts rename test/{did-io.spec.js => node/did-io.test.ts} (61%) create mode 100644 tsconfig.dev.json delete mode 100644 tsconfig.spec.json create mode 100644 vite.config.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 8439cc2..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - overrides: [ - { - files: ['*.js', '*.jsx', '*.ts', '*.tsx'], - extends: 'standard-with-typescript', - parserOptions: { - project: './tsconfig.spec.json' - }, - rules: { - '@typescript-eslint/no-unused-expressions': 'off', - '@typescript-eslint/strict-boolean-expressions': 'off' - } - } - ] -} 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 7333f53..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Node.js CI - -on: [push] - -jobs: -# test-node: -# runs-on: ubuntu-latest -# strategy: -# matrix: -# node-version: [20.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 - lint: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20.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/CHANGELOG.md b/CHANGELOG.md index 17d88d6..a2e14c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,13 @@ ## 2.0.0 - ### Changed -- **BREAKING**: Convert to Typescript. +- 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 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/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..c650d1e --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +import js from '@eslint/js' +import globals from 'globals' +import tseslint from 'typescript-eslint' +import prettierConfig from 'eslint-config-prettier' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist', '**/*.min.js']), + { + files: ['**/*.ts'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + prettierConfig // must be last in extends + ], + languageOptions: { + ecmaVersion: 2022, + globals: { ...globals.browser, ...globals.node }, + parserOptions: { + project: ['./tsconfig.dev.json'] + } + }, + rules: { + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_' + } + ], + '@typescript-eslint/no-explicit-any': 'off', + curly: ['error', 'all'], + 'no-var': 'error', + 'prefer-const': 'error' + } + } +]) diff --git a/package.json b/package.json index 4f97d24..8e8cf77 100644 --- a/package.json +++ b/package.json @@ -1,44 +1,59 @@ { - "name": "@digitalcredentials/did-io", + "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", + "homepage": "https://github.com/interop-alliance/did-io", + "type": "module", "scripts": { - "build": "npm run clear && tsc -d", + "build": "pnpm run clear && tsc", "clear": "rimraf dist/*", - "lint": "ts-standard --fix --project tsconfig.spec.json", - "prepare": "npm run build", - "rebuild": "npm run clear && npm run build", - "test": "npm run lint && npm run test-node", - "test-node": "npx tsx --test test/*.spec.ts" + "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" }, - "type": "module", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", + "files": [ + "dist", + "src", + "CHANGELOG.md", + "README.md", + "LICENSE" + ], "exports": { ".": { "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "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": "^3.0.0-beta.1", - "@digitalcredentials/ssi": "^3.0.5" + "@digitalcredentials/ssi": "^5.4.2", + "@interop/lru-memoize": "^4.0.2" }, "devDependencies": { - "@types/chai": "^5.2.1", - "@types/mocha": "^10.0.1", - "@types/node": "^22.14.1", - "cross-env": "^7.0.3", - "mocha": "^10.2.0", - "rimraf": "^3.0.2", - "ts-node": "^10.9.1", - "ts-standard": "^12.0.2", - "typescript": "^5.8.3", - "tsx": "^4.19.4" - }, - "engines": { - "node": ">=18" + "@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", @@ -46,26 +61,16 @@ "Credential" ], "publishConfig": { - "access": "public" + "access": "public", + "provenance": true }, - "ts-standard": { - "env": [ "mocha" ], - "ignore": [ - "dist", - "test" - ], - "globals": [ "it"] + "packageManager": "pnpm@11.3.0", + "engines": { + "node": ">=24.0" }, "repository": { "type": "git", - "url": "git+http://github.com/digitalcredentials/did-io.git" + "url": "git+https://github.com/interop-alliance/did-io.git" }, - "license": "BSD-3-Clause", - "files": [ - "dist", - "src", - "CHANGELOG.md", - "README.md", - "LICENSE" - ] + "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/src/CachedResolver.ts b/src/CachedResolver.ts index 4a32b73..623c0c6 100644 --- a/src/CachedResolver.ts +++ b/src/CachedResolver.ts @@ -1,9 +1,15 @@ /*! * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -import { parseDid, IKeyMap } from './did-io.js' -import { LruCache } from '@digitalcredentials/lru-memoize' -import { IDID, IDidDocument, IKeyPair, IPublicKey } from '@digitalcredentials/ssi' +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 @@ -12,6 +18,8 @@ export interface DidGenerationResult { } export interface DidMethodDriver { + method: string + computeId: ( { keyPair }: { keyPair: IKeyPair } ) => Promise @@ -21,9 +29,13 @@ export interface DidMethodDriver { { verificationKeyPair?: IKeyPair, keyAgreementKeyPair?: IKeyPair } ) => DidGenerationResult + generate: ( + options?: { [key: string]: unknown } + ) => Promise + get: ( - { did, url }: { did?: IDID, url?: string } - ) => IDidDocument | IPublicKey + options: { did?: IDID | string, url?: string, [key: string]: unknown } + ) => Promise publicKeyToDidDoc: ( { publicKeyDescription }: { publicKeyDescription: IKeyPair | IPublicKey } @@ -34,30 +46,44 @@ export interface DidMethodDriver { ) => IPublicKey } +export interface CachedResolverOptions { + max?: number + ttl?: number + updateAgeOnGet?: boolean + cache?: { memoize: LruCache['memoize'] } + [key: string]: unknown +} + export class CachedResolver { - _cache - _methods + _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.maxAge=5000] - Max age of a cache item, in ms. + * @param {number} [options.ttl=5000] - Max age of a cache item, in ms. * @param {boolean} [options.updateAgeOnGet=false] - When using time-expiring - * entries with `maxAge`, setting this to true will make each entry's + * 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, maxAge = 5000, updateAgeOnGet = false, + max = 100, ttl = 5000, updateAgeOnGet = false, cache, ...cacheOptions - } = {}) { - this._cache = new LruCache({ max, maxAge, updateAgeOnGet, ...cacheOptions }) + }: CachedResolverOptions = {}) { + this._cache = cache ?? new LruCache( + { max, ttl, updateAgeOnGet, ...cacheOptions } as + ConstructorParameters[0] + ) this._methods = new Map() } - use (driver: any): void { - const methodName = driver.method - this._methods.set(methodName, driver) + use (driver: DidMethodDriver): void { + this._methods.set(driver.method, driver) } /** @@ -75,20 +101,43 @@ export class CachedResolver { * @returns {Promise} Resolves with fetched DID * Document or public key node. */ - async get ({ did, url, ...getOptions }: { did: IDID, url: string }): Promise { - did = did || url - if (!did) { + 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(did) + const method = this._methodForDid(didOrUrl) - return this._cache.memoize({ - key: did, - fn: () => method.get({ did, ...getOptions }) + 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. * diff --git a/src/declarations.d.ts b/src/declarations.d.ts deleted file mode 100644 index 1052ee2..0000000 --- a/src/declarations.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module '@digitalcredentials/lru-memoize' diff --git a/src/did-io.ts b/src/did-io.ts index e6772b1..8cec13b 100644 --- a/src/did-io.ts +++ b/src/did-io.ts @@ -2,10 +2,21 @@ * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ import { VERIFICATION_RELATIONSHIPS } from './constants.js' -import { IDID, IDidDocument, IKeyIdOrObject, IKeyPair } from '@digitalcredentials/ssi' +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. @@ -37,10 +48,9 @@ export function approvesMethodFor ( if (!method) { return false } - // @ts-expect-error - const methods: IKeyIdOrObject[] = doc[purpose] || [] + const methods = _methodsForPurpose({ doc, purpose }) - return !!methods.find(method => { + return methods.some(method => { return (typeof method === 'string' && method === methodId) || (typeof method === 'object' && method.id === methodId) }) @@ -72,7 +82,9 @@ export function approvesMethodFor ( * pairs, by key id. */ export async function initKeys ( - { doc, cryptoLd, keyMap = {} }: { doc: IDidDocument, cryptoLd: any, keyMap?: IKeyMap }): Promise<{ keyPairs: IKeyMap }> { + { 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.') @@ -88,19 +100,21 @@ export async function initKeys ( throw new Error(`Unsupported key purpose: "${purpose}".`) } - let key - if (typeof keyMap[purpose] === 'string') { + 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: keyMap[purpose], ...options }) + key = await cryptoLd.generate({ type: entry, ...options }) } else { // An existing key has been provided - key = keyMap[purpose] + key = entry as IKeyPair } - // TODO: why was 'this' used here? - // this[purpose] = [key.export({ publicKey: true })] + if (!key.id) { + throw new Error('Initialized key is missing an "id" property.') + } keyPairs[key.id] = key } @@ -147,11 +161,13 @@ export async function initKeys ( * @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 { + { doc, methodId, purpose }: + { doc: IDidDocument, methodId?: string, purpose?: string } +): IKeyIdOrObject | undefined { if (!doc) { throw new TypeError('A DID Document is required.') } - if (!(methodId || purpose)) { + if (!(methodId ?? purpose)) { throw new TypeError('A method id or purpose is required.') } @@ -160,8 +176,7 @@ export function findVerificationMethod ( } // Id not given, find the first method by purpose - // @ts-expect-error - const [method] = doc[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 }) @@ -200,8 +215,7 @@ export function _methodById ( } for (const purpose of VERIFICATION_RELATIONSHIPS) { - // @ts-expect-error - const methods = doc[purpose] || [] + 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 @@ -217,7 +231,33 @@ export function _methodById ( } /** - * Parses the DID into various component (currently, only cares about prefix). + * 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'}); diff --git a/src/index.ts b/src/index.ts index 5353650..9f8d2c6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,11 @@ */ export * from './did-io.js' -export { CachedResolver, DidGenerationResult, DidMethodDriver } - from './CachedResolver.js' +export { CachedResolver } from './CachedResolver.js' +export type { + DidGenerationResult, + DidMethodDriver, + CachedResolverOptions +} from './CachedResolver.js' export { VERIFICATION_RELATIONSHIPS } from './constants.js' 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/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/did-io.spec.js b/test/node/did-io.test.ts similarity index 61% rename from test/did-io.spec.js rename to test/node/did-io.test.ts index f45d114..107f272 100644 --- a/test/did-io.spec.js +++ b/test/node/did-io.test.ts @@ -1,13 +1,14 @@ /*! * Copyright (c) 2021 Digital Bazaar, Inc. All rights reserved. */ -import chai from 'chai' +import { describe, it, beforeEach, expect } from 'vitest' +import type { IDidDocument } from '@digitalcredentials/ssi' import { - findVerificationMethod, approvesMethodFor, parseDid -} from '../' -chai.should() -const { expect } = chai + findVerificationMethod, + approvesMethodFor, + parseDid +} from '../../src/index.js' const MOCK_KEY = { id: 'did:ex:123#abcd', @@ -17,155 +18,155 @@ const MOCK_KEY = { } describe('parseDid', () => { - it('should return main did method identifier', async () => { + it('should return main did method identifier', () => { const { prefix } = parseDid({ did: 'did:v1:test:nym:abcd' }) - expect(prefix).to.equal('v1') + expect(prefix).toBe('v1') }) }) describe('didIo utility functions', () => { describe('findVerificationMethod', () => { const did = 'did:ex:123' - let key + let key: typeof MOCK_KEY - beforeEach(async () => { + beforeEach(() => { key = { ...MOCK_KEY } }) - it('should return undefined if key is not found by id', async () => { + 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).to.be.undefined + expect(result).toBeUndefined() }) - it('should return undefined if key is not found by purpose', async () => { + 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' })) - .to.be.undefined + .toBeUndefined() expect(findVerificationMethod({ doc, purpose: 'capabilityInvocation' })) - .to.be.undefined + .toBeUndefined() }) - it('should find by id in verificationMethod', async () => { + 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).to.eql(MOCK_KEY) + expect(result).toEqual(MOCK_KEY) }) - it('should find by id if defined in purpose', async () => { + 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).to.eql(MOCK_KEY) + expect(result).toEqual(MOCK_KEY) }) - it('should find by id if referenced', async () => { + 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).to.eql(MOCK_KEY) + expect(result).toEqual(MOCK_KEY) }) - it('should find by purpose in verificationMethod', async () => { + 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).to.eql(MOCK_KEY) + expect(result).toEqual(MOCK_KEY) }) - it('should find by purpose if defined in purpose', async () => { + 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).to.eql(MOCK_KEY) + expect(result).toEqual(MOCK_KEY) }) - it('should find by purpose if referenced', async () => { + 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).to.eql(MOCK_KEY) + expect(result).toEqual(MOCK_KEY) }) }) describe('approvesMethodFor', () => { const did = 'did:ex:123' - let key + let key: typeof MOCK_KEY - beforeEach(async () => { + beforeEach(() => { key = { ...MOCK_KEY } }) - it('should return false if method not in document', async () => { + 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).to.be.false + expect(result).toBe(false) }) - it('should return false if method not approved', async () => { + 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' - })).to.be.false + })).toBe(false) doc.assertionMethod = [key.id] expect(approvesMethodFor({ doc, methodId: key.id, purpose: 'authentication' - })).to.be.false + })).toBe(false) }) - it('should return true if method is approved (referenced)', async () => { + 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' - })).to.be.true + })).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 index e33c920..052e27b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,21 @@ { "compilerOptions": { "strict": true, - "target": "es2022", - "lib": ["es2020"], - "module": "es6", - "moduleResolution": "node", - "declarationMap": true, - "declaration": true, + "target": "ES2022", + "lib": ["ES2022", "DOM"], + "module": "ESNext", + "moduleResolution": "Bundler", "outDir": "dist", - "noImplicitAny": true, - "removeComments": false, - "preserveConstEnums": true, - "baseUrl": ".", + "sourceMap": true, + "declaration": true, + "declarationMap": true, + "allowSyntheticDefaultImports": true, "skipLibCheck": true, - "checkJs": true, - "allowJs": true + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "noUncheckedIndexedAccess": true }, - "include": [ - "src/**/*" - ], - "exclude": ["test", "node_modules", "dist", ".eslintrc.cjs"] + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] } diff --git a/tsconfig.spec.json b/tsconfig.spec.json deleted file mode 100644 index fff677c..0000000 --- a/tsconfig.spec.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "target": "es2022", - "lib": ["es2020"], - "module": "es6", - "moduleResolution": "node", - "declarationMap": true, - "declaration": true, - "outDir": "dist", - "noImplicitAny": true, - "removeComments": false, - "preserveConstEnums": true, - "baseUrl": ".", - "skipLibCheck": true, - "checkJs": true, - "allowJs": true, - "allowSyntheticDefaultImports": true, - "resolveJsonModule": true - }, - "ts-node": { - "files": true - }, - "include": [ - "src/**/*", - "test/**/*.spec.ts" - ], - "exclude": ["node_modules", "dist", ".eslintrc.cjs"] -} 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'] + } + } +})