From 1a7a2ce5a1c66af2101997cb75b47d27aee63c02 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Tue, 24 Mar 2026 23:27:15 -0400 Subject: [PATCH 1/9] ci: fix formatting issues --- .github/workflows/ci.yml | 2 +- .github/workflows/publish.yml | 4 ++-- eslint.config.js | 3 +++ package.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7df59d5..4b2e333 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,4 +39,4 @@ jobs: run: npm run format:check - name: Build - run: npm run build \ No newline at end of file + run: npm run build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1d53d0f..587f4de 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,7 +3,7 @@ name: Publish on: push: tags: - - "v*.*.*" + - 'v*.*.*' concurrency: group: publish-${{ github.ref }} @@ -39,4 +39,4 @@ jobs: - name: Publish run: npm publish env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/eslint.config.js b/eslint.config.js index 11c1852..64a4e29 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,6 +3,9 @@ import tsParser from '@typescript-eslint/parser'; import prettier from 'eslint-config-prettier'; export default [ + { + ignores: ['dist', 'node_modules', 'coverage'], + }, { files: ['**/*.ts'], languageOptions: { diff --git a/package.json b/package.json index 7125942..0c3b758 100644 --- a/package.json +++ b/package.json @@ -72,4 +72,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} From db1a2b3fb832fcc5005136d5b507bb98ae9c72c3 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Tue, 24 Mar 2026 23:50:59 -0400 Subject: [PATCH 2/9] test: added test and folder structer --- eslint.config.js | 2 +- package-lock.json | 1197 ++++++++++++++++- package.json | 9 +- src/index.ts | 10 +- src/schemas/{ => auth}/auth.schema.ts | 0 src/schemas/authEvent/schema.test.ts | 93 ++ .../schema.ts} | 2 +- src/schemas/credential/schema.test.ts | 100 ++ .../schema.ts} | 21 +- src/schemas/session/schema.test.ts | 83 ++ .../{sessions.schema.ts => session/schema.ts} | 0 src/schemas/user/schema.test.ts | 159 +++ .../{user.schema.ts => user/schema.ts} | 4 +- vite.config.ts | 7 + 14 files changed, 1669 insertions(+), 18 deletions(-) rename src/schemas/{ => auth}/auth.schema.ts (100%) create mode 100644 src/schemas/authEvent/schema.test.ts rename src/schemas/{authEvent.schema.ts => authEvent/schema.ts} (91%) create mode 100644 src/schemas/credential/schema.test.ts rename src/schemas/{credential.schema.ts => credential/schema.ts} (76%) create mode 100644 src/schemas/session/schema.test.ts rename src/schemas/{sessions.schema.ts => session/schema.ts} (100%) create mode 100644 src/schemas/user/schema.test.ts rename src/schemas/{user.schema.ts => user/schema.ts} (91%) create mode 100644 vite.config.ts diff --git a/eslint.config.js b/eslint.config.js index 64a4e29..b3a8512 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,7 +4,7 @@ import prettier from 'eslint-config-prettier'; export default [ { - ignores: ['dist', 'node_modules', 'coverage'], + ignores: ['dist', 'node_modules', '*.config.ts'], }, { files: ['**/*.ts'], diff --git a/package-lock.json b/package-lock.json index 17bab0a..a310c15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,8 @@ "husky": "^9.1.7", "lint-staged": "^16.4.0", "prettier": "^3.8.1", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.1" }, "engines": { "node": ">=20" @@ -359,6 +360,40 @@ } } }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -518,6 +553,320 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz", + "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz", + "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz", + "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@simple-libs/child-process-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz", @@ -547,6 +896,42 @@ "url": "https://ko-fi.com/dangreen" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", @@ -809,6 +1194,119 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vitest/expect": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz", + "integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz", + "integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz", + "integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz", + "integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.1", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz", + "integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.1", + "@vitest/utils": "4.1.1", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz", + "integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz", + "integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.1", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -905,6 +1403,16 @@ "dev": true, "license": "MIT" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -938,6 +1446,16 @@ "node": ">=6" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -1166,6 +1684,13 @@ "node": ">=18" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", @@ -1251,6 +1776,16 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -1304,6 +1839,13 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1485,7 +2027,17 @@ "node": ">=4.0" } }, - "node_modules/esutils": { + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", @@ -1502,6 +2054,16 @@ "dev": true, "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1609,6 +2171,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1920,6 +2497,279 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "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" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -2064,6 +2914,16 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -2123,6 +2983,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2130,6 +3009,17 @@ "dev": true, "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -2248,6 +3138,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2268,6 +3165,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2358,6 +3284,40 @@ "dev": true, "license": "MIT" }, + "node_modules/rolldown": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz", + "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.11" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-x64": "1.0.0-rc.11", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" + } + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -2394,6 +3354,13 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -2424,6 +3391,30 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -2467,6 +3458,13 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", @@ -2494,6 +3492,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -2507,6 +3515,14 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2552,6 +3568,166 @@ "punycode": "^2.1.0" } }, + "node_modules/vite": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz", + "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.11", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.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 + } + } + }, + "node_modules/vitest": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz", + "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.1", + "@vitest/mocker": "4.1.1", + "@vitest/pretty-format": "4.1.1", + "@vitest/runner": "4.1.1", + "@vitest/snapshot": "4.1.1", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "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.1", + "@vitest/browser-preview": "4.1.1", + "@vitest/browser-webdriverio": "4.1.1", + "@vitest/ui": "4.1.1", + "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/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2568,6 +3744,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 0c3b758..8d1aced 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,9 @@ "format": "prettier --write .", "format:check": "prettier --check .", "prepare": "husky", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "test": "vitest run", + "test:watch": "vitest" }, "lint-staged": { "*.ts": [ @@ -64,7 +66,8 @@ "husky": "^9.1.7", "lint-staged": "^16.4.0", "prettier": "^3.8.1", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "vitest": "^4.1.1" }, "engines": { "node": ">=20" @@ -72,4 +75,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 791aa22..348d80e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -export * from './schemas/user.schema'; -export * from './schemas/credential.schema'; -export * from './schemas/auth.schema'; -export * from './schemas/authEvent.schema'; -export * from './schemas/sessions.schema'; +export * from './schemas/user/schema'; +export * from './schemas/credential/schema'; +export * from './schemas/auth/auth.schema'; +export * from './schemas/authEvent/schema'; +export * from './schemas/session/schema'; diff --git a/src/schemas/auth.schema.ts b/src/schemas/auth/auth.schema.ts similarity index 100% rename from src/schemas/auth.schema.ts rename to src/schemas/auth/auth.schema.ts diff --git a/src/schemas/authEvent/schema.test.ts b/src/schemas/authEvent/schema.test.ts new file mode 100644 index 0000000..8e07f32 --- /dev/null +++ b/src/schemas/authEvent/schema.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect } from 'vitest'; +import { AuthEventSchema } from './schema'; + +const now = new Date().toISOString(); + +describe('AuthEventSchema', () => { + const baseEvent = { + id: 'event_123', + user_id: 'user_123', + type: 'login', + ip_address: '127.0.0.1', + user_agent: 'Mozilla/5.0', + metadata: { + device: 'desktop', + success: true, + }, + created_at: now, + updated_at: now, + }; + + it('parses a valid auth event', () => { + expect(() => AuthEventSchema.parse(baseEvent)).not.toThrow(); + }); + + it('allows nullable optional fields', () => { + const minimal = { + id: 'event_123', + type: 'login', + metadata: null, + created_at: now, + updated_at: now, + }; + + expect(() => AuthEventSchema.parse(minimal)).not.toThrow(); + }); + + it('allows missing optional fields', () => { + const { ...rest } = baseEvent; + + expect(() => AuthEventSchema.parse(rest)).not.toThrow(); + }); + + it('fails if required fields are missing', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...rest } = baseEvent; + + expect(() => AuthEventSchema.parse(rest)).toThrow(); + }); + + it('fails if metadata is not an object or null', () => { + expect(() => + AuthEventSchema.parse({ + ...baseEvent, + metadata: 'invalid', + }), + ).toThrow(); + }); + + it('allows metadata with arbitrary values', () => { + expect(() => + AuthEventSchema.parse({ + ...baseEvent, + metadata: { + string: 'value', + number: 123, + boolean: true, + nested: { key: 'value' }, + array: [1, 2, 3], + }, + }), + ).not.toThrow(); + }); + + it('fails on invalid ISO date', () => { + expect(() => + AuthEventSchema.parse({ + ...baseEvent, + created_at: 'not-a-date', + }), + ).toThrow(); + }); + + it('fails when metadata is undefined (not nullable)', () => { + const { ...rest } = baseEvent; + + expect(() => + AuthEventSchema.parse({ + ...rest, + metadata: undefined, + }), + ).toThrow(); + }); +}); diff --git a/src/schemas/authEvent.schema.ts b/src/schemas/authEvent/schema.ts similarity index 91% rename from src/schemas/authEvent.schema.ts rename to src/schemas/authEvent/schema.ts index a642d56..f9e8f06 100644 --- a/src/schemas/authEvent.schema.ts +++ b/src/schemas/authEvent/schema.ts @@ -1,5 +1,5 @@ import z from 'zod'; -import { IsoDate } from '../shared'; +import { IsoDate } from '../../shared'; export const AuthEventSchema = z.object({ id: z.string(), diff --git a/src/schemas/credential/schema.test.ts b/src/schemas/credential/schema.test.ts new file mode 100644 index 0000000..adca5a0 --- /dev/null +++ b/src/schemas/credential/schema.test.ts @@ -0,0 +1,100 @@ +import { describe, it, expect } from 'vitest'; +import { CredentialSchema } from './schema'; + +const now = new Date().toISOString(); + +describe('CredentialSchema', () => { + const baseCredential = { + id: 'credential_id_base64url', + userId: crypto.randomUUID(), + publicKey: 'public_key_base64url', + counter: 0, + transports: ['internal'], + deviceType: 'singleDevice', + backedUp: false, + friendlyName: 'My Device', + lastUsedAt: now, + platform: 'ios', + browser: 'safari', + deviceInfo: 'iPhone', + createdAt: now, + updatedAt: now, + }; + + it('parses a valid credential', () => { + expect(() => CredentialSchema.parse(baseCredential)).not.toThrow(); + }); + + it('fails when required fields are missing', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...rest } = baseCredential; + + expect(() => CredentialSchema.parse(rest)).toThrow(); + }); + + it('fails when publicKey is not a string', () => { + expect(() => + CredentialSchema.parse({ + ...baseCredential, + publicKey: 123, + }), + ).toThrow(); + }); + + it('allows optional metadata fields to be omitted', () => { + const { ...minimal } = baseCredential; + + expect(() => CredentialSchema.parse(minimal)).not.toThrow(); + }); + + it('allows nullable optional fields', () => { + expect(() => + CredentialSchema.parse({ + ...baseCredential, + friendlyName: null, + platform: null, + browser: null, + deviceInfo: null, + }), + ).not.toThrow(); + }); + + it('allows missing transports', () => { + const { ...rest } = baseCredential; + + expect(() => CredentialSchema.parse(rest)).not.toThrow(); + }); + + it('fails on invalid transport value', () => { + expect(() => + CredentialSchema.parse({ + ...baseCredential, + transports: ['invalid'], + }), + ).toThrow(); + }); + + it('fails on invalid deviceType', () => { + expect(() => + CredentialSchema.parse({ + ...baseCredential, + deviceType: 'invalidType', + }), + ).toThrow(); + }); + + it('fails on invalid ISO date', () => { + expect(() => + CredentialSchema.parse({ + ...baseCredential, + createdAt: 'not-a-date', + }), + ).toThrow(); + }); + + it('allows lastUsedAt to be omitted', () => { + const { ...rest } = baseCredential; + + expect(() => CredentialSchema.parse(rest)).not.toThrow(); + }); +}); diff --git a/src/schemas/credential.schema.ts b/src/schemas/credential/schema.ts similarity index 76% rename from src/schemas/credential.schema.ts rename to src/schemas/credential/schema.ts index 9608562..4f7ee1d 100644 --- a/src/schemas/credential.schema.ts +++ b/src/schemas/credential/schema.ts @@ -1,17 +1,30 @@ import z from 'zod'; -import { IsoDate } from '../shared'; +import { IsoDate } from '../../shared'; + +const TransportSchema = z.enum(['usb', 'ble', 'nfc', 'internal']); + +const DeviceTypeSchema = z.enum(['singleDevice', 'multiDevice']); export const CredentialSchema = z.object({ id: z.string(), - transports: z.array(z.string()).nullable().optional(), - deviceType: z.string().nullable().optional(), - backedUp: z.boolean().nullable().optional(), + userId: z.string(), + + publicKey: z.string(), counter: z.number(), + + transports: z.array(TransportSchema).optional(), + + deviceType: DeviceTypeSchema.optional(), + + backedUp: z.boolean(), + friendlyName: z.string().nullable().optional(), lastUsedAt: IsoDate.nullable().optional(), + platform: z.string().nullable().optional(), browser: z.string().nullable().optional(), deviceInfo: z.string().nullable().optional(), + createdAt: IsoDate, updatedAt: IsoDate.optional(), }); diff --git a/src/schemas/session/schema.test.ts b/src/schemas/session/schema.test.ts new file mode 100644 index 0000000..6bebe3a --- /dev/null +++ b/src/schemas/session/schema.test.ts @@ -0,0 +1,83 @@ +import { describe, it, expect } from 'vitest'; +import { SessionSchema } from './schema'; + +const now = new Date().toISOString(); +const later = new Date(Date.now() + 1000 * 60 * 60).toISOString(); + +describe('SessionSchema', () => { + const baseSession = { + id: 'session_123', + deviceName: 'MacBook Pro', + ipAddress: '127.0.0.1', + userAgent: 'Mozilla/5.0', + lastUsedAt: now, + expiresAt: later, + current: true, + }; + + it('parses a valid session', () => { + expect(() => SessionSchema.parse(baseSession)).not.toThrow(); + }); + + it('allows optional nullable fields to be omitted', () => { + const { ...minimal } = baseSession; + + expect(() => SessionSchema.parse(minimal)).not.toThrow(); + }); + + it('allows optional nullable fields to be null', () => { + expect(() => + SessionSchema.parse({ + ...baseSession, + deviceName: null, + ipAddress: null, + userAgent: null, + }), + ).not.toThrow(); + }); + + it('fails when required fields are missing', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...rest } = baseSession; + + expect(() => SessionSchema.parse(rest)).toThrow(); + }); + + it('fails when current is not a boolean', () => { + expect(() => + SessionSchema.parse({ + ...baseSession, + current: 'true', + }), + ).toThrow(); + }); + + it('fails when lastUsedAt is not a string', () => { + expect(() => + SessionSchema.parse({ + ...baseSession, + lastUsedAt: 123, + }), + ).toThrow(); + }); + + it('fails when expiresAt is not a string', () => { + expect(() => + SessionSchema.parse({ + ...baseSession, + expiresAt: 123, + }), + ).toThrow(); + }); + + it('allows different valid session shapes', () => { + expect(() => + SessionSchema.parse({ + id: 'session_456', + lastUsedAt: now, + expiresAt: later, + current: false, + }), + ).not.toThrow(); + }); +}); diff --git a/src/schemas/sessions.schema.ts b/src/schemas/session/schema.ts similarity index 100% rename from src/schemas/sessions.schema.ts rename to src/schemas/session/schema.ts diff --git a/src/schemas/user/schema.test.ts b/src/schemas/user/schema.test.ts new file mode 100644 index 0000000..f413845 --- /dev/null +++ b/src/schemas/user/schema.test.ts @@ -0,0 +1,159 @@ +import { describe, it, expect } from 'vitest'; +import { UserSchema, CreateUserSchema, UpdateUserSchema } from './schema'; + +const now = new Date().toISOString(); + +describe('UserSchema', () => { + const baseUser = { + id: crypto.randomUUID(), + email: 'test@example.com', + phone: '1234567890', + roles: ['user'], + revoked: false, + emailVerified: true, + phoneVerified: false, + verified: true, + lastLogin: now, + createdAt: now, + updatedAt: now, + }; + + it('parses a valid user', () => { + expect(() => UserSchema.parse(baseUser)).not.toThrow(); + }); + + it('applies defaults when optional fields are missing', () => { + const minimal = { + id: crypto.randomUUID(), + email: 'test@example.com', + phone: '1234567890', + createdAt: now, + }; + + const parsed = UserSchema.parse(minimal); + + expect(parsed.roles).toEqual([]); + expect(parsed.revoked).toBe(false); + expect(parsed.emailVerified).toBe(false); + expect(parsed.phoneVerified).toBe(false); + expect(parsed.verified).toBe(false); + }); + + it('fails on invalid email', () => { + expect(() => + UserSchema.parse({ + ...baseUser, + email: 'invalid-email', + }), + ).toThrow(); + }); + + it('fails on invalid role format', () => { + expect(() => + UserSchema.parse({ + ...baseUser, + roles: ['bad role'], // contains space + }), + ).toThrow(); + }); + + it('allows nullable lastLogin', () => { + expect(() => + UserSchema.parse({ + ...baseUser, + lastLogin: null, + }), + ).not.toThrow(); + }); + + it('allows missing optional fields', () => { + const { ...rest } = baseUser; + + expect(() => UserSchema.parse(rest)).not.toThrow(); + }); +}); + +describe('CreateUserSchema', () => { + it('parses valid input', () => { + expect(() => + CreateUserSchema.parse({ + email: 'test@example.com', + phone: '1234567890', + roles: ['admin'], + }), + ).not.toThrow(); + }); + + it('fails if roles are empty', () => { + expect(() => + CreateUserSchema.parse({ + email: 'test@example.com', + phone: '1234567890', + roles: [], + }), + ).toThrow(); + }); + + it('fails on invalid email', () => { + expect(() => + CreateUserSchema.parse({ + email: 'bad-email', + phone: '1234567890', + roles: ['user'], + }), + ).toThrow(); + }); + + it('fails on invalid role format', () => { + expect(() => + CreateUserSchema.parse({ + email: 'test@example.com', + phone: '1234567890', + roles: ['bad role'], + }), + ).toThrow(); + }); +}); + +describe('UpdateUserSchema', () => { + it('parses valid partial update', () => { + expect(() => + UpdateUserSchema.parse({ + email: 'new@example.com', + }), + ).not.toThrow(); + }); + + it('requires roles if provided and must be non-empty', () => { + expect(() => + UpdateUserSchema.parse({ + roles: [], + }), + ).toThrow(); + }); + + it('fails on short phone number', () => { + expect(() => + UpdateUserSchema.parse({ + phone: '123', + }), + ).toThrow(); + }); + + it('fails on invalid role format', () => { + expect(() => + UpdateUserSchema.parse({ + roles: ['invalid role'], + }), + ).toThrow(); + }); + + it('fails on unknown fields due to strict()', () => { + expect(() => + UpdateUserSchema.parse({ + email: 'test@example.com', + unknownField: true, + }), + ).toThrow(); + }); +}); diff --git a/src/schemas/user.schema.ts b/src/schemas/user/schema.ts similarity index 91% rename from src/schemas/user.schema.ts rename to src/schemas/user/schema.ts index 4a38232..c5f1902 100644 --- a/src/schemas/user.schema.ts +++ b/src/schemas/user/schema.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { IsoDate } from '../shared'; +import { IsoDate } from '../../shared'; export const RoleSchema = z.string().regex(/^(?!.*[_/\\\s])[A-Za-z0-9-]{1,31}$/); @@ -31,6 +31,6 @@ export const UpdateUserSchema = z phone: z.string().min(5).optional(), emailVerified: z.boolean().optional(), phoneVerified: z.boolean().optional(), - roles: z.array(RoleSchema).min(1), + roles: z.array(RoleSchema).min(1).optional(), }) .strict(); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..4ac6027 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + }, +}); From 97855f5eecdabe10964125a86956f9a38acb2b0f Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Wed, 25 Mar 2026 00:15:23 -0400 Subject: [PATCH 3/9] build: tsconfig ignore --- package.json | 7 ++----- tsconfig.json | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 8d1aced..6d1d8c8 100644 --- a/package.json +++ b/package.json @@ -8,10 +8,7 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" - } + ".": "./dist/index.js" }, "files": [ "dist" @@ -75,4 +72,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 1e9faf6..d8f3526 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,5 +10,6 @@ "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true }, - "include": ["src"] + "include": ["src"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] } From 71b5bb1f752754f877b416cfcc0605d8d334807d Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Wed, 25 Mar 2026 00:15:33 -0400 Subject: [PATCH 4/9] 0.1.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a310c15..9b087c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@seamless-auth/types", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@seamless-auth/types", - "version": "0.1.0", + "version": "0.1.1", "license": "AGPL-3.0", "dependencies": { "zod": "^4.3.6" diff --git a/package.json b/package.json index 6d1d8c8..8851304 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@seamless-auth/types", - "version": "0.1.0", + "version": "0.1.1", "description": "Shared TypeScript types and Zod schemas for SeamlessAuth.", "author": "Fells Code, LLC", "license": "AGPL-3.0", From 543d0abde0ca0566e362d64c6368f0243238df51 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Wed, 25 Mar 2026 09:53:44 -0400 Subject: [PATCH 5/9] build: js exports for packages --- eslint.config.js | 28 +++++++++++++++++++--------- package.json | 4 +++- src/index.ts | 10 +++++----- tsconfig.json | 2 ++ 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index b3a8512..6d43b16 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,10 +4,12 @@ import prettier from 'eslint-config-prettier'; export default [ { - ignores: ['dist', 'node_modules', '*.config.ts'], + ignores: ['dist', 'node_modules', 'coverage', '*.config.*'], }, + { - files: ['**/*.ts'], + files: ['src/**/*.ts'], + ignores: ['src/**/*.test.ts'], languageOptions: { parser: tsParser, parserOptions: { @@ -18,16 +20,24 @@ export default [ '@typescript-eslint': tseslint, }, rules: { - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': ['error'], - + '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/no-explicit-any': 'warn', - - 'no-console': 'warn', - - '@typescript-eslint/explicit-function-return-type': 'off', }, }, + { + files: ['src/**/*.test.ts'], + languageOptions: { + parser: tsParser, + }, + plugins: { + '@typescript-eslint': tseslint, + }, + rules: { + '@typescript-eslint/no-unused-vars': 'error', + // no type-aware rules here + }, + }, + prettier, ]; diff --git a/package.json b/package.json index 8851304..96af432 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ ".": "./dist/index.js" }, "files": [ - "dist" + "dist", + "README.md", + "LICENSE" ], "repository": { "type": "git", diff --git a/src/index.ts b/src/index.ts index 348d80e..be5378a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ -export * from './schemas/user/schema'; -export * from './schemas/credential/schema'; -export * from './schemas/auth/auth.schema'; -export * from './schemas/authEvent/schema'; -export * from './schemas/session/schema'; +export * from './schemas/user/schema.js'; +export * from './schemas/credential/schema.js'; +export * from './schemas/auth/auth.schema.js'; +export * from './schemas/authEvent/schema.js'; +export * from './schemas/session/schema.js'; diff --git a/tsconfig.json b/tsconfig.json index d8f3526..1356704 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,8 @@ "module": "ESNext", "target": "ES2020", "moduleResolution": "Node", + "sourceMap": true, + "declarationMap": true, "strict": true, "esModuleInterop": true, "noUncheckedIndexedAccess": true, From ab72272d22f6f3473206ebc78fc4e6d083f28d16 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Wed, 25 Mar 2026 09:53:51 -0400 Subject: [PATCH 6/9] 0.1.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b087c7..b0d82f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@seamless-auth/types", - "version": "0.1.1", + "version": "0.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@seamless-auth/types", - "version": "0.1.1", + "version": "0.1.2", "license": "AGPL-3.0", "dependencies": { "zod": "^4.3.6" diff --git a/package.json b/package.json index 96af432..db85462 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@seamless-auth/types", - "version": "0.1.1", + "version": "0.1.2", "description": "Shared TypeScript types and Zod schemas for SeamlessAuth.", "author": "Fells Code, LLC", "license": "AGPL-3.0", From 6fec34a133f75289f2e2aae4e7e6803ca864f769 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Wed, 25 Mar 2026 12:24:51 -0400 Subject: [PATCH 7/9] fix: add .js to isoDate imports --- src/schemas/authEvent/schema.test.ts | 2 +- src/schemas/authEvent/schema.ts | 2 +- src/schemas/credential/schema.test.ts | 2 +- src/schemas/credential/schema.ts | 2 +- src/schemas/session/schema.test.ts | 2 +- src/schemas/user/schema.test.ts | 2 +- src/schemas/user/schema.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/schemas/authEvent/schema.test.ts b/src/schemas/authEvent/schema.test.ts index 8e07f32..e6bb68a 100644 --- a/src/schemas/authEvent/schema.test.ts +++ b/src/schemas/authEvent/schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { AuthEventSchema } from './schema'; +import { AuthEventSchema } from './schema.js'; const now = new Date().toISOString(); diff --git a/src/schemas/authEvent/schema.ts b/src/schemas/authEvent/schema.ts index f9e8f06..6e3b0d9 100644 --- a/src/schemas/authEvent/schema.ts +++ b/src/schemas/authEvent/schema.ts @@ -1,5 +1,5 @@ import z from 'zod'; -import { IsoDate } from '../../shared'; +import { IsoDate } from '../../shared.js'; export const AuthEventSchema = z.object({ id: z.string(), diff --git a/src/schemas/credential/schema.test.ts b/src/schemas/credential/schema.test.ts index adca5a0..9e1564b 100644 --- a/src/schemas/credential/schema.test.ts +++ b/src/schemas/credential/schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { CredentialSchema } from './schema'; +import { CredentialSchema } from './schema.js'; const now = new Date().toISOString(); diff --git a/src/schemas/credential/schema.ts b/src/schemas/credential/schema.ts index 4f7ee1d..9c9647a 100644 --- a/src/schemas/credential/schema.ts +++ b/src/schemas/credential/schema.ts @@ -1,5 +1,5 @@ import z from 'zod'; -import { IsoDate } from '../../shared'; +import { IsoDate } from '../../shared.js'; const TransportSchema = z.enum(['usb', 'ble', 'nfc', 'internal']); diff --git a/src/schemas/session/schema.test.ts b/src/schemas/session/schema.test.ts index 6bebe3a..cf724e6 100644 --- a/src/schemas/session/schema.test.ts +++ b/src/schemas/session/schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { SessionSchema } from './schema'; +import { SessionSchema } from './schema.js'; const now = new Date().toISOString(); const later = new Date(Date.now() + 1000 * 60 * 60).toISOString(); diff --git a/src/schemas/user/schema.test.ts b/src/schemas/user/schema.test.ts index f413845..be5538a 100644 --- a/src/schemas/user/schema.test.ts +++ b/src/schemas/user/schema.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { UserSchema, CreateUserSchema, UpdateUserSchema } from './schema'; +import { UserSchema, CreateUserSchema, UpdateUserSchema } from './schema.js'; const now = new Date().toISOString(); diff --git a/src/schemas/user/schema.ts b/src/schemas/user/schema.ts index c5f1902..4fa4b67 100644 --- a/src/schemas/user/schema.ts +++ b/src/schemas/user/schema.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { IsoDate } from '../../shared'; +import { IsoDate } from '../../shared.js'; export const RoleSchema = z.string().regex(/^(?!.*[_/\\\s])[A-Za-z0-9-]{1,31}$/); From 68ba5ef8b050d137ef012cd1aa847260c9bd8f9e Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Wed, 25 Mar 2026 12:25:04 -0400 Subject: [PATCH 8/9] 0.1.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index b0d82f9..dd42aa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@seamless-auth/types", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@seamless-auth/types", - "version": "0.1.2", + "version": "0.1.3", "license": "AGPL-3.0", "dependencies": { "zod": "^4.3.6" diff --git a/package.json b/package.json index db85462..f6b54fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@seamless-auth/types", - "version": "0.1.2", + "version": "0.1.3", "description": "Shared TypeScript types and Zod schemas for SeamlessAuth.", "author": "Fells Code, LLC", "license": "AGPL-3.0", From f85865f99d3e00de42b53d3b0976dbc8bdd2cfc3 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Sat, 23 May 2026 21:53:27 -0400 Subject: [PATCH 9/9] feat: scoped role support --- README.md | 3 +++ src/schemas/user/schema.test.ts | 27 +++++++++++++++++++++++++++ src/schemas/user/schema.ts | 5 ++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ce28220..d933e78 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ import { UserSchema } from '@seamless-auth/types'; const user = UserSchema.parse(data); ``` +User role schemas accept plain roles such as `admin` and colon-separated scoped roles such as +`admin:read` and `admin:write`. Whitespace, underscores, slashes, and backslashes are rejected. + ### Infer types ```ts diff --git a/src/schemas/user/schema.test.ts b/src/schemas/user/schema.test.ts index be5538a..1938f03 100644 --- a/src/schemas/user/schema.test.ts +++ b/src/schemas/user/schema.test.ts @@ -57,6 +57,15 @@ describe('UserSchema', () => { ).toThrow(); }); + it('allows scoped role format', () => { + expect(() => + UserSchema.parse({ + ...baseUser, + roles: ['admin:read', 'admin:write'], + }), + ).not.toThrow(); + }); + it('allows nullable lastLogin', () => { expect(() => UserSchema.parse({ @@ -84,6 +93,16 @@ describe('CreateUserSchema', () => { ).not.toThrow(); }); + it('parses scoped roles', () => { + const parsed = CreateUserSchema.parse({ + email: 'test@example.com', + phone: '1234567890', + roles: ['admin:read'], + }); + + expect(parsed.roles).toEqual(['admin:read']); + }); + it('fails if roles are empty', () => { expect(() => CreateUserSchema.parse({ @@ -148,6 +167,14 @@ describe('UpdateUserSchema', () => { ).toThrow(); }); + it('parses scoped role updates', () => { + const parsed = UpdateUserSchema.parse({ + roles: ['admin:write'], + }); + + expect(parsed.roles).toEqual(['admin:write']); + }); + it('fails on unknown fields due to strict()', () => { expect(() => UpdateUserSchema.parse({ diff --git a/src/schemas/user/schema.ts b/src/schemas/user/schema.ts index 4fa4b67..820049b 100644 --- a/src/schemas/user/schema.ts +++ b/src/schemas/user/schema.ts @@ -1,7 +1,10 @@ import { z } from 'zod'; import { IsoDate } from '../../shared.js'; -export const RoleSchema = z.string().regex(/^(?!.*[_/\\\s])[A-Za-z0-9-]{1,31}$/); +export const RoleSchema = z + .string() + .trim() + .regex(/^(?!.*[_/\\\s])(?=.{1,80}$)[A-Za-z0-9-]+(?::[A-Za-z0-9-]+)*$/); export const UserSchema = z.object({ id: z.uuid(),