{"_id":"@bymax-one/nest-ai-tokens","_rev":"6-7644527729e9df0729aea56ad0adc3fb","name":"@bymax-one/nest-ai-tokens","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@bymax-one/nest-ai-tokens","version":"1.0.0","keywords":["nestjs","ai","tokens","metering","billing","usage-based-billing","llm","openai","anthropic","gemini","pricing","ledger","wallet","budget","markup"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-ai-tokens@1.0.0","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-ai-tokens#readme","bugs":{"url":"https://github.com/bymaxone/nest-ai-tokens/issues"},"dist":{"shasum":"7ffcd2d84021a4ddfc851c62bdd3a3db0299c635","tarball":"https://registry.npmjs.org/@bymax-one/nest-ai-tokens/-/nest-ai-tokens-1.0.0.tgz","fileCount":26,"integrity":"sha512-5Znb5BSlZn58ewQv3UpvQKA0VAF+Hjk4alsOOUynqqCGKg6PmlpFyOR/3dllZK0VCOD2WdaT/Xpf0UP65OzI6A==","signatures":[{"sig":"MEUCIQCy4LfEIWdzfEQBwS4Tku8tOi8KDvSxrKML3BmO8J0qtQIgNv8gnv8AG/+6e2nYGM+K7BRg4fWb2zC/ww4FOK4fXoQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1112414},"main":"./dist/server/index.cjs","type":"module","_from":"file:bymax-one-nest-ai-tokens-1.0.0.tgz","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./redis":{"import":{"types":"./dist/redis/index.d.ts","default":"./dist/redis/index.mjs"},"require":{"types":"./dist/redis/index.d.cts","default":"./dist/redis/index.cjs"}},"./prices":{"import":{"types":"./dist/prices/index.d.ts","default":"./dist/prices/index.mjs"},"require":{"types":"./dist/prices/index.d.cts","default":"./dist/prices/index.cjs"}},"./prisma":{"import":{"types":"./dist/prisma/index.d.ts","default":"./dist/prisma/index.mjs"},"require":{"types":"./dist/prisma/index.d.cts","default":"./dist/prisma/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","docs:check":"node scripts/check-jsdoc.mjs && tsc --noEmit -p tsconfig.e2e.json","test:watch":"jest --watch","db:generate":"prisma generate","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"bymax.one","email":"bymaxone.core@gmail.com"},"_resolved":"/private/var/folders/zf/_zr7t0ms60x6rcwj5zxk90cr0000gn/T/79c816d735457f3f6ab906e1253ec7cf/bymax-one-nest-ai-tokens-1.0.0.tgz","_integrity":"sha512-5Znb5BSlZn58ewQv3UpvQKA0VAF+Hjk4alsOOUynqqCGKg6PmlpFyOR/3dllZK0VCOD2WdaT/Xpf0UP65OzI6A==","repository":{"url":"git+https://github.com/bymaxone/nest-ai-tokens.git","type":"git"},"_npmVersion":"11.13.0","description":"AI token metering & usage-based billing for NestJS 11: provider usage normalizer (×9), versioned effective-dated pricing, immutable append-only ledger, prepaid wallets, multi-dimension budgets, and markup/margin. Zero runtime dependencies.","directories":{},"sideEffects":false,"_nodeVersion":"24.16.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"redis":["./dist/redis/index.d.cts"],"prices":["./dist/prices/index.d.cts"],"prisma":["./dist/prisma/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.2","tsup":"^8.5.1","eslint":"^9.39.5","prisma":"^7.9.1","ioredis":"^5.4.2","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","fast-check":"^4.9.0","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@prisma/client":"^7.9.1","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@opentelemetry/api":"^1.9.0","@prisma/adapter-pg":"^7.9.1","@arethetypeswrong/cli":"^0.18.2","@nestjs/event-emitter":"^3.0.1","@stryker-mutator/core":"^9","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@testcontainers/postgresql":"^12","@stryker-mutator/jest-runner":"^9","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"rxjs":"^7.8.0","ioredis":"^5.0.0","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@prisma/client":"^6.0.0 || ^7.0.0","reflect-metadata":"^0.2.0","@opentelemetry/api":"^1.9.0","@nestjs/event-emitter":">=2.0.0"},"peerDependenciesMeta":{"rxjs":{"optional":false},"ioredis":{"optional":true},"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"@prisma/client":{"optional":true},"reflect-metadata":{"optional":false},"@opentelemetry/api":{"optional":true},"@nestjs/event-emitter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-ai-tokens_1.0.0_1785845301071_0.6742338687207259","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@bymax-one/nest-ai-tokens","version":"1.0.1","keywords":["nestjs","ai","tokens","metering","billing","usage-based-billing","llm","openai","anthropic","gemini","pricing","ledger","wallet","budget","markup"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-ai-tokens@1.0.1","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-ai-tokens#readme","bugs":{"url":"https://github.com/bymaxone/nest-ai-tokens/issues"},"dist":{"shasum":"85e77141322e18cdfa7aca09b4d92ac381d37063","tarball":"https://registry.npmjs.org/@bymax-one/nest-ai-tokens/-/nest-ai-tokens-1.0.1.tgz","fileCount":26,"integrity":"sha512-e+PB3K9nFqOaJEngbC0uhnezoFvNEECuG6N01uXFxCSdWLIzHDi5/GlnlWBTNtYF8pDXbHpHk6F2R7YuZwovKw==","signatures":[{"sig":"MEUCIDa01aU7O9JUuk6+VfuqFNesvaP7fVMa4h1OiNNbmYp2AiEA40Y3OkQXt/zGRlM1T/BkpxrvD3wMKMedrvYdSxUYHyc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-ai-tokens@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1115013},"main":"./dist/server/index.cjs","pnpm":{"overrides":{"qs":"6.15.2","uuid":"11.1.1","multer":"2.2.0","undici":"^8.9.0","esbuild":"0.28.1","fast-uri":"^3.1.4","protobufjs":"^7.6.5","brace-expansion@1":"^1.1.17","brace-expansion@2":"^2.1.3","brace-expansion@5":"^5.0.8"},"onlyBuiltDependencies":["esbuild","@prisma/client","@prisma/engines","prisma"]},"type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./redis":{"import":{"types":"./dist/redis/index.d.ts","default":"./dist/redis/index.mjs"},"require":{"types":"./dist/redis/index.d.cts","default":"./dist/redis/index.cjs"}},"./prices":{"import":{"types":"./dist/prices/index.d.ts","default":"./dist/prices/index.mjs"},"require":{"types":"./dist/prices/index.d.cts","default":"./dist/prices/index.cjs"}},"./prisma":{"import":{"types":"./dist/prisma/index.d.ts","default":"./dist/prisma/index.mjs"},"require":{"types":"./dist/prisma/index.d.cts","default":"./dist/prisma/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"a2ab8dd4c1ad60bcd2df2d366f48338e14608a13","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"","prepare":"prisma generate","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","docs:check":"node scripts/check-jsdoc.mjs && tsc --noEmit -p tsconfig.e2e.json","test:watch":"jest --watch","db:generate":"prisma generate","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4b66fb5c-82ab-4629-9d3a-3324110b271f"}},"repository":{"url":"git+https://github.com/bymaxone/nest-ai-tokens.git","type":"git"},"_npmVersion":"11.16.0","description":"AI token metering & usage-based billing for NestJS 11: provider usage normalizer (×9), versioned effective-dated pricing, immutable append-only ledger, prepaid wallets, multi-dimension budgets, and markup/margin. Zero runtime dependencies.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"redis":["./dist/redis/index.d.cts"],"prices":["./dist/prices/index.d.cts"],"prisma":["./dist/prisma/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@10.8.1","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.2","tsup":"^8.5.1","eslint":"^9.39.5","prisma":"^7.9.1","ioredis":"^5.4.2","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","fast-check":"^4.9.0","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@prisma/client":"^7.9.1","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@opentelemetry/api":"^1.9.0","@prisma/adapter-pg":"^7.9.1","@arethetypeswrong/cli":"^0.18.2","@nestjs/event-emitter":"^3.0.1","@stryker-mutator/core":"^9","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@testcontainers/postgresql":"^12","@stryker-mutator/jest-runner":"^9","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"rxjs":"^7.8.0","ioredis":"^5.0.0","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@prisma/client":"^6.0.0 || ^7.0.0","reflect-metadata":"^0.2.0","@opentelemetry/api":"^1.9.0","@nestjs/event-emitter":">=2.0.0"},"peerDependenciesMeta":{"rxjs":{"optional":false},"ioredis":{"optional":true},"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"@prisma/client":{"optional":true},"reflect-metadata":{"optional":false},"@opentelemetry/api":{"optional":true},"@nestjs/event-emitter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-ai-tokens_1.0.1_1785858083423_0.3201744727264686","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@bymax-one/nest-ai-tokens","version":"1.0.2","keywords":["nestjs","ai","tokens","metering","billing","usage-based-billing","llm","openai","anthropic","gemini","pricing","ledger","wallet","budget","markup"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-ai-tokens@1.0.2","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-ai-tokens#readme","bugs":{"url":"https://github.com/bymaxone/nest-ai-tokens/issues"},"dist":{"shasum":"50496bf8da71fd1c9a85414e91abbc9b349b2cea","tarball":"https://registry.npmjs.org/@bymax-one/nest-ai-tokens/-/nest-ai-tokens-1.0.2.tgz","fileCount":26,"integrity":"sha512-v7L8yOOrB5+CuncJZ7Odqd6zzdKbyBsHfZjsQH8qiqPn9a0UW5WxABQtb0B204GcSiO7ryVOTW0v83sg8DY8Xw==","signatures":[{"sig":"MEUCIQCNulcRcK7VlDS/0tq693lJ1dnQRTc/jM+yPd12HDXwqAIgPrW7aKx7DLBXlNU1bq1V++hOyQ6lD5NUN3rvwuQPEVk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-ai-tokens@1.0.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1116566},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./redis":{"import":{"types":"./dist/redis/index.d.ts","default":"./dist/redis/index.mjs"},"require":{"types":"./dist/redis/index.d.cts","default":"./dist/redis/index.cjs"}},"./prices":{"import":{"types":"./dist/prices/index.d.ts","default":"./dist/prices/index.mjs"},"require":{"types":"./dist/prices/index.d.cts","default":"./dist/prices/index.cjs"}},"./prisma":{"import":{"types":"./dist/prisma/index.d.ts","default":"./dist/prisma/index.mjs"},"require":{"types":"./dist/prisma/index.d.cts","default":"./dist/prisma/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"afed9b347687db1fd341341482ade465fa93b16b","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"","prepare":"prisma generate","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","docs:check":"node scripts/check-jsdoc.mjs && tsc --noEmit -p tsconfig.e2e.json","test:watch":"jest --watch","db:generate":"prisma generate","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4b66fb5c-82ab-4629-9d3a-3324110b271f"}},"repository":{"url":"git+https://github.com/bymaxone/nest-ai-tokens.git","type":"git"},"_npmVersion":"11.17.0","description":"AI token metering & usage-based billing for NestJS 11: provider usage normalizer (×9), versioned effective-dated pricing, immutable append-only ledger, prepaid wallets, multi-dimension budgets, and markup/margin. Zero runtime dependencies.","directories":{},"sideEffects":false,"_nodeVersion":"24.19.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"redis":["./dist/redis/index.d.cts"],"prices":["./dist/prices/index.d.cts"],"prisma":["./dist/prisma/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.2","tsup":"^8.5.1","eslint":"^9.39.5","prisma":"^7.9.1","ioredis":"^5.4.2","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","fast-check":"^4.9.0","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@prisma/client":"^7.9.1","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@opentelemetry/api":"^1.9.0","@prisma/adapter-pg":"^7.9.1","@arethetypeswrong/cli":"^0.18.2","@nestjs/event-emitter":"^3.0.1","@stryker-mutator/core":"^9","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@testcontainers/postgresql":"^12","@stryker-mutator/jest-runner":"^9","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"rxjs":"^7.8.0","ioredis":"^5.0.0","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@prisma/client":"^6.0.0 || ^7.0.0","reflect-metadata":"^0.2.0","@opentelemetry/api":"^1.9.0","@nestjs/event-emitter":">=2.0.0"},"peerDependenciesMeta":{"rxjs":{"optional":false},"ioredis":{"optional":true},"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"@prisma/client":{"optional":true},"reflect-metadata":{"optional":false},"@opentelemetry/api":{"optional":true},"@nestjs/event-emitter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-ai-tokens_1.0.2_1786015583072_0.9444331151078296","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@bymax-one/nest-ai-tokens","version":"1.0.3","keywords":["nestjs","ai","tokens","metering","billing","usage-based-billing","llm","openai","anthropic","gemini","pricing","ledger","wallet","budget","markup"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-ai-tokens@1.0.3","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-ai-tokens#readme","bugs":{"url":"https://github.com/bymaxone/nest-ai-tokens/issues"},"dist":{"shasum":"5f1971372df4287018c19169404207b6f1630ede","tarball":"https://registry.npmjs.org/@bymax-one/nest-ai-tokens/-/nest-ai-tokens-1.0.3.tgz","fileCount":26,"integrity":"sha512-u9PaLN4E+fDribjElqe/pymegYSymsj6nSeXyPwbWrItRVLxjtsfHFZ6wWscECnlrrQJmpN9YFWNQYa3Yj3KSA==","signatures":[{"sig":"MEYCIQDKWZCANlkYb+/uh4WkkHviwTcU1r3yiUAaD5rN4qd3hgIhAMQAAH1Z8LymT7NOBrWs6+E/eECTBSdq4hRer6mdrIau","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-ai-tokens@1.0.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1118237},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./redis":{"import":{"types":"./dist/redis/index.d.ts","default":"./dist/redis/index.mjs"},"require":{"types":"./dist/redis/index.d.cts","default":"./dist/redis/index.cjs"}},"./prices":{"import":{"types":"./dist/prices/index.d.ts","default":"./dist/prices/index.mjs"},"require":{"types":"./dist/prices/index.d.cts","default":"./dist/prices/index.cjs"}},"./prisma":{"import":{"types":"./dist/prisma/index.d.ts","default":"./dist/prisma/index.mjs"},"require":{"types":"./dist/prisma/index.d.cts","default":"./dist/prisma/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"3a785efea5ae695eb1132037a8e2ce0fec54449b","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"","prepare":"prisma generate","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","docs:check":"node scripts/check-jsdoc.mjs && tsc --noEmit -p tsconfig.e2e.json","test:watch":"jest --watch","db:generate":"prisma generate","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly","mutation:incremental":"stryker run --incremental"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4b66fb5c-82ab-4629-9d3a-3324110b271f"}},"repository":{"url":"git+https://github.com/bymaxone/nest-ai-tokens.git","type":"git"},"_npmVersion":"11.16.0","description":"AI token metering & usage-based billing for NestJS 11: provider usage normalizer (×9), versioned effective-dated pricing, immutable append-only ledger, prepaid wallets, multi-dimension budgets, and markup/margin. Zero runtime dependencies.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"redis":["./dist/redis/index.d.cts"],"prices":["./dist/prices/index.d.cts"],"prisma":["./dist/prisma/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.2","tsup":"^8.5.1","eslint":"^9.39.5","prisma":"^7.9.1","ioredis":"^5.4.2","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","fast-check":"^4.9.0","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@prisma/client":"^7.9.1","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@opentelemetry/api":"^1.9.0","@prisma/adapter-pg":"^7.9.1","@arethetypeswrong/cli":"^0.18.2","@nestjs/event-emitter":"^3.0.1","@stryker-mutator/core":"^9","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@testcontainers/postgresql":"^12","@stryker-mutator/jest-runner":"^9","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"rxjs":"^7.8.0","ioredis":"^5.0.0","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@prisma/client":"^6.0.0 || ^7.0.0","reflect-metadata":"^0.2.0","@opentelemetry/api":"^1.9.0","@nestjs/event-emitter":">=2.0.0"},"peerDependenciesMeta":{"rxjs":{"optional":false},"ioredis":{"optional":true},"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"@prisma/client":{"optional":true},"reflect-metadata":{"optional":false},"@opentelemetry/api":{"optional":true},"@nestjs/event-emitter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-ai-tokens_1.0.3_1786193991908_0.7454974225603044","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@bymax-one/nest-ai-tokens","version":"1.0.4","keywords":["nestjs","ai","tokens","metering","billing","usage-based-billing","llm","openai","anthropic","gemini","pricing","ledger","wallet","budget","markup"],"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","_id":"@bymax-one/nest-ai-tokens@1.0.4","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"homepage":"https://github.com/bymaxone/nest-ai-tokens#readme","bugs":{"url":"https://github.com/bymaxone/nest-ai-tokens/issues"},"dist":{"shasum":"afbb614f26e8d97ccd63607002e55c6751321c47","tarball":"https://registry.npmjs.org/@bymax-one/nest-ai-tokens/-/nest-ai-tokens-1.0.4.tgz","fileCount":26,"integrity":"sha512-TzOM3ADdaV1bezyb/LFjzKYcFtqyLSPgBm8ALW9bdmUi6P0MARBNizY/bF4KscyJpSunqTWDfaSrEpv+VXpiQg==","signatures":[{"sig":"MEUCIHjtrVrAktO9hUD/p5RPRLDM52f56xCyBbvBmeOaJKiqAiEA5tpf9ixFyZtvhokUHK/4OisDpQRGopLAj2JUgDohyAY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-ai-tokens@1.0.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1121604},"main":"./dist/server/index.cjs","type":"module","types":"./dist/server/index.d.cts","module":"./dist/server/index.mjs","engines":{"node":">=24.0.0"},"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./redis":{"import":{"types":"./dist/redis/index.d.ts","default":"./dist/redis/index.mjs"},"require":{"types":"./dist/redis/index.d.cts","default":"./dist/redis/index.cjs"}},"./prices":{"import":{"types":"./dist/prices/index.d.ts","default":"./dist/prices/index.mjs"},"require":{"types":"./dist/prices/index.d.cts","default":"./dist/prices/index.cjs"}},"./prisma":{"import":{"types":"./dist/prisma/index.d.ts","default":"./dist/prisma/index.mjs"},"require":{"types":"./dist/prisma/index.d.cts","default":"./dist/prisma/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./package.json":"./package.json"},"gitHead":"470ed007fd74eeb49fcdf4f568424608cc588f36","scripts":{"lint":"eslint src","size":"node scripts/check-size.mjs","test":"jest","build":"pnpm clean && tsup","clean":"node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"","prepare":"prisma generate","release":"npm publish --provenance --access public","lint:fix":"eslint src --fix","mutation":"stryker run","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:e2e":"jest --config jest.e2e.config.ts","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json","docs:check":"node scripts/check-jsdoc.mjs && tsc --noEmit -p tsconfig.e2e.json","test:watch":"jest --watch","db:generate":"prisma generate","test:cov:all":"jest --config jest.coverage.config.ts --coverage","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","check:published":"node scripts/check-published-surface.mjs","mutation:dry-run":"stryker run --dryRunOnly"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4b66fb5c-82ab-4629-9d3a-3324110b271f"}},"repository":{"url":"git+https://github.com/bymaxone/nest-ai-tokens.git","type":"git"},"_npmVersion":"11.16.0","description":"AI token metering & usage-based billing for NestJS 11: provider usage normalizer (×9), versioned effective-dated pricing, immutable append-only ledger, prepaid wallets, multi-dimension budgets, and markup/margin. Zero runtime dependencies.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/","provenance":true},"typesVersions":{"*":{"redis":["./dist/redis/index.d.cts"],"prices":["./dist/prices/index.d.cts"],"prisma":["./dist/prisma/index.d.cts"],"shared":["./dist/shared/index.d.cts"]}},"_hasShrinkwrap":false,"packageManager":"pnpm@11.20.0","devDependencies":{"jest":"^30.4.2","rxjs":"^7.8.2","tsup":"^8.5.1","eslint":"^9.39.5","prisma":"^7.9.1","ioredis":"^5.4.2","ts-jest":"^29.4.12","ts-node":"^10.9.2","prettier":"^3.9.6","supertest":"^7.2.2","@eslint/js":"^9.39.4","fast-check":"^4.9.0","typescript":"^5.9.3","@types/jest":"^30.0.0","@types/node":"^24.13.3","@nestjs/core":"^11.1.28","@nestjs/common":"^11.1.28","@prisma/client":"^7.9.1","@types/express":"^5.0.6","testcontainers":"^12","@nestjs/testing":"^11.1.28","@types/supertest":"^7.2.1","reflect-metadata":"^0.2.2","typescript-eslint":"^8.65.0","@opentelemetry/api":"^1.9.0","@prisma/adapter-pg":"^7.9.1","@arethetypeswrong/cli":"^0.18.2","@nestjs/event-emitter":"^3.0.1","@stryker-mutator/core":"^9","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@testcontainers/postgresql":"^12","@stryker-mutator/jest-runner":"^9","@stryker-mutator/typescript-checker":"^9"},"peerDependencies":{"rxjs":"^7.8.0","ioredis":"^5.0.0","@nestjs/core":"^11.1.18","@nestjs/common":"^11.0.16","@prisma/client":"^6.0.0 || ^7.0.0","reflect-metadata":"^0.2.0","@opentelemetry/api":"^1.9.0","@nestjs/event-emitter":">=2.0.0"},"peerDependenciesMeta":{"rxjs":{"optional":false},"ioredis":{"optional":true},"@nestjs/core":{"optional":false},"@nestjs/common":{"optional":false},"@prisma/client":{"optional":true},"reflect-metadata":{"optional":false},"@opentelemetry/api":{"optional":true},"@nestjs/event-emitter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nest-ai-tokens_1.0.4_1786381221649_0.6713308231701323","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@bymax-one/nest-ai-tokens","version":"1.1.0","description":"AI token metering & usage-based billing for NestJS 11: provider usage normalizer (×9), versioned effective-dated pricing, immutable append-only ledger, prepaid wallets, multi-dimension budgets, and markup/margin. Zero runtime dependencies.","author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","homepage":"https://github.com/bymaxone/nest-ai-tokens#readme","repository":{"type":"git","url":"git+https://github.com/bymaxone/nest-ai-tokens.git"},"bugs":{"url":"https://github.com/bymaxone/nest-ai-tokens/issues"},"type":"module","sideEffects":false,"exports":{".":{"import":{"types":"./dist/server/index.d.ts","default":"./dist/server/index.mjs"},"require":{"types":"./dist/server/index.d.cts","default":"./dist/server/index.cjs"}},"./shared":{"import":{"types":"./dist/shared/index.d.ts","default":"./dist/shared/index.mjs"},"require":{"types":"./dist/shared/index.d.cts","default":"./dist/shared/index.cjs"}},"./prices":{"import":{"types":"./dist/prices/index.d.ts","default":"./dist/prices/index.mjs"},"require":{"types":"./dist/prices/index.d.cts","default":"./dist/prices/index.cjs"}},"./prisma":{"import":{"types":"./dist/prisma/index.d.ts","default":"./dist/prisma/index.mjs"},"require":{"types":"./dist/prisma/index.d.cts","default":"./dist/prisma/index.cjs"}},"./redis":{"import":{"types":"./dist/redis/index.d.ts","default":"./dist/redis/index.mjs"},"require":{"types":"./dist/redis/index.d.cts","default":"./dist/redis/index.cjs"}},"./package.json":"./package.json"},"scripts":{"build":"pnpm clean && tsup","check:exports":"attw --pack . --profile strict","check:mutants":"node scripts/check-mutation-directives.mjs","check:published":"node scripts/check-published-surface.mjs","check:runtime":"node scripts/check-consumer-runtime.mjs","clean":"node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"","db:generate":"prisma generate","docs:check":"node scripts/check-jsdoc.mjs && tsc --noEmit -p tsconfig.e2e.json","lint":"eslint src","lint:fix":"eslint src --fix","mutation":"stryker run","mutation:full":"node -e \"require('node:fs').rmSync('reports/stryker-incremental.json',{force:true,recursive:true})\" && stryker run","mutation:dry-run":"stryker run --dryRunOnly","prepare":"prisma generate","prepublishOnly":"pnpm clean && pnpm typecheck && pnpm lint && pnpm check:mutants && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published","release":"npm publish --provenance --access public","size":"node scripts/check-size.mjs","test":"jest","test:all":"pnpm test && pnpm test:e2e","test:cov":"jest --coverage","test:cov:all":"jest --config jest.coverage.config.ts --coverage","test:e2e":"jest --config jest.e2e.config.ts","test:watch":"jest --watch","typecheck":"tsc --noEmit && tsc --noEmit -p tsconfig.server.json"},"dependencies":{},"peerDependencies":{"@nestjs/common":"^11.0.16","@nestjs/core":"^11.1.18","reflect-metadata":"^0.2.0","rxjs":"^7.8.0","@prisma/client":"^6.0.0 || ^7.0.0","ioredis":"^6.0.0","@nestjs/event-emitter":">=2.0.0","@opentelemetry/api":"^1.9.0"},"peerDependenciesMeta":{"@nestjs/common":{"optional":false},"@nestjs/core":{"optional":false},"reflect-metadata":{"optional":false},"rxjs":{"optional":false},"@prisma/client":{"optional":true},"ioredis":{"optional":true},"@nestjs/event-emitter":{"optional":true},"@opentelemetry/api":{"optional":true}},"devDependencies":{"@arethetypeswrong/cli":"^0.18.2","@eslint/js":"^9.39.4","@nestjs/common":"^11.1.28","@nestjs/core":"^11.1.28","@nestjs/event-emitter":"^3.0.1","@nestjs/platform-express":"^11.1.28","@nestjs/testing":"^11.1.28","@opentelemetry/api":"^1.9.0","@prisma/adapter-pg":"^7.9.1","@prisma/client":"^7.9.1","@stryker-mutator/core":"^9","@stryker-mutator/jest-runner":"^9","@stryker-mutator/typescript-checker":"^9","@testcontainers/postgresql":"^12","@types/express":"^5.0.6","@types/jest":"^30.0.0","@types/node":"^24.13.3","@types/supertest":"^7.2.1","eslint":"^9.39.5","eslint-config-prettier":"^10.1.8","fast-check":"^4.9.0","ioredis":"^6.0.0","jest":"^30.4.2","prettier":"^3.9.6","prisma":"^7.9.1","reflect-metadata":"^0.2.2","rxjs":"^7.8.2","supertest":"^7.2.2","testcontainers":"^12","ts-jest":"^29.4.12","ts-node":"^10.9.2","tsup":"^8.5.1","typescript":"^5.9.3","typescript-eslint":"^8.65.0"},"keywords":["nestjs","ai","tokens","metering","billing","usage-based-billing","llm","openai","anthropic","gemini","pricing","ledger","wallet","budget","markup"],"packageManager":"pnpm@11.20.0","engines":{"node":">=24.0.0"},"publishConfig":{"access":"public","provenance":true,"registry":"https://registry.npmjs.org/"},"main":"./dist/server/index.cjs","module":"./dist/server/index.mjs","types":"./dist/server/index.d.cts","typesVersions":{"*":{"shared":["./dist/shared/index.d.cts"],"prices":["./dist/prices/index.d.cts"],"prisma":["./dist/prisma/index.d.cts"],"redis":["./dist/redis/index.d.cts"]}},"gitHead":"4e4af5d79f305d3ca111d8b18eed460140437c24","_id":"@bymax-one/nest-ai-tokens@1.1.0","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-cVnt5q/TkL5HBmbn9PE0X4ewWw+XwSlPLSiBWk+xmZa4zxWxafC6Xah5AgyPZHDh/NlxnmuvNLYBhF0Qnm8ZMw==","shasum":"a8106af0725d0ba2d01ca8a0e74bb6d9f1a985f6","tarball":"https://registry.npmjs.org/@bymax-one/nest-ai-tokens/-/nest-ai-tokens-1.1.0.tgz","fileCount":26,"unpackedSize":1122395,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bymax-one%2fnest-ai-tokens@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDODtc/I8NbMWwi+8QeuHCX7I7APr6PSSyg3FwQpp0xXAIhALBwf84g43uEseaI6/hTVjYOoWBbwAqJR+uz53N34OI8"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4b66fb5c-82ab-4629-9d3a-3324110b271f"}},"directories":{},"maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nest-ai-tokens_1.1.0_1786446200524_0.30331248449407555"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T12:08:20.890Z","modified":"2026-08-11T11:03:21.089Z","1.0.0":"2026-08-04T12:08:21.256Z","1.0.1":"2026-08-04T15:41:23.579Z","1.0.2":"2026-08-06T11:26:23.244Z","1.0.3":"2026-08-08T12:59:52.063Z","1.0.4":"2026-08-10T17:00:21.795Z","1.1.0":"2026-08-11T11:03:20.695Z"},"bugs":{"url":"https://github.com/bymaxone/nest-ai-tokens/issues"},"author":{"name":"Bymax One","email":"support@bymax.one"},"license":"MIT","homepage":"https://github.com/bymaxone/nest-ai-tokens#readme","keywords":["nestjs","ai","tokens","metering","billing","usage-based-billing","llm","openai","anthropic","gemini","pricing","ledger","wallet","budget","markup"],"repository":{"type":"git","url":"git+https://github.com/bymaxone/nest-ai-tokens.git"},"description":"AI token metering & usage-based billing for NestJS 11: provider usage normalizer (×9), versioned effective-dated pricing, immutable append-only ledger, prepaid wallets, multi-dimension budgets, and markup/margin. Zero runtime dependencies.","maintainers":[{"name":"bymax.one","email":"bymaxone.core@gmail.com"},{"name":"msalvatti","email":"msalvatti@gmail.com"}],"readme":"<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/%40bymax--one-nest--ai--tokens-000000?style=for-the-badge&logo=nestjs&logoColor=E0234E\" alt=\"@bymax-one/nest-ai-tokens\" />\n</p>\n\n<h1 align=\"center\">@bymax-one/nest-ai-tokens</h1>\n\n<p align=\"center\">\n  <strong>AI token metering and usage-based billing for NestJS</strong><br />\n  <sub>9 Provider Normalizers · Exact bigint nano-USD · Append-only Ledger · Hash-chain Integrity · Prepaid Wallets · Budgets · Streaming · Zero Runtime Dependencies</sub>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@bymax-one/nest-ai-tokens\"><img src=\"https://img.shields.io/npm/v/@bymax-one/nest-ai-tokens?style=flat-square&colorA=000000&colorB=000000\" alt=\"npm version\" /></a>\n  <a href=\"https://www.npmjs.com/package/@bymax-one/nest-ai-tokens\"><img src=\"https://img.shields.io/npm/dm/@bymax-one/nest-ai-tokens?style=flat-square&colorA=000000&colorB=000000\" alt=\"npm downloads\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-ai-tokens/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/github/actions/workflow/status/bymaxone/nest-ai-tokens/ci.yml?branch=main&style=flat-square&colorA=000000&label=CI\" alt=\"CI status\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-ai-tokens/actions/workflows/ci.yml\"><img src=\"https://img.shields.io/badge/coverage-100%25-brightgreen?style=flat-square&colorA=000000\" alt=\"coverage\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-ai-tokens/blob/main/docs/mutation_testing_results.md\"><img src=\"https://img.shields.io/badge/mutation-100%25-brightgreen?style=flat-square&colorA=000000\" alt=\"mutation score\" /></a>\n  <a href=\"https://scorecard.dev/viewer/?uri=github.com/bymaxone/nest-ai-tokens\"><img src=\"https://api.scorecard.dev/projects/github.com/bymaxone/nest-ai-tokens/badge?style=flat-square\" alt=\"OpenSSF Scorecard\" /></a>\n  <a href=\"https://github.com/bymaxone/nest-ai-tokens/blob/main/LICENSE\"><img src=\"https://img.shields.io/github/license/bymaxone/nest-ai-tokens?style=flat-square&colorA=000000&colorB=000000\" alt=\"license\" /></a>\n  <a href=\"https://www.typescriptlang.org/\"><img src=\"https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white\" alt=\"TypeScript\" /></a>\n  <a href=\"https://nodejs.org/\"><img src=\"https://img.shields.io/badge/Node.js-24%2B-339933?style=flat-square&logo=node.js&logoColor=white\" alt=\"Node.js\" /></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/bymaxone/nest-ai-tokens\">GitHub</a> ·\n  <a href=\"https://github.com/bymaxone/nest-ai-tokens/issues\">Issues</a> ·\n  <a href=\"#-quick-start\">Quick Start</a> ·\n  <a href=\"#-api-reference\">API Reference</a> ·\n  <a href=\"https://github.com/bymaxone/nest-ai-tokens-example\">Example App</a>\n</p>\n\n---\n\n## ✨ Overview\n\n`@bymax-one/nest-ai-tokens` meters AI usage and bills it, in-process. Instead of assembling a\nproxy for enforcement, a ledger for accounting, a price table for rating and a wallet for\nprepaid credit — four systems that must agree on what a call cost — you install one library\nand get all of it behind a NestJS guard and interceptor.\n\nThe library has **zero direct dependencies**. NestJS, Prisma, `ioredis`, the event emitter and\nOpenTelemetry all arrive as peer dependencies, and the optional ones are never imported unless\nthe feature that needs them is enabled.\n\n### Why nest-ai-tokens?\n\n- **Enforcement happens in the request.** A guard refuses the call before the handler runs\n  and an interceptor settles the actual usage after it — no proxy to deploy, no sidecar to\n  keep alive, no external hop that can fail open.\n- **Money is exact by construction.** Every persisted amount is `bigint` nano-USD. There is no\n  float arithmetic on a money path, because a rounding difference there is a number a customer\n  is charged.\n- **The ledger is evidence, not state.** Entries are appended and corrections are compensating\n  records; with the optional hash chain on, a row edited in the database stops matching the\n  chain that follows it.\n- **Charging twice is prevented by the database.** The idempotency key carries a unique\n  constraint, and the replay path is that violation being recognized as a conflict — not\n  application care that someone can forget.\n\n---\n\n## 🔥 Features\n\n### 📊 Metering\n\n- ✅ **Nine provider normalizers** — OpenAI Chat, OpenAI Responses, OpenAI-compatible\n  (DeepSeek / xAI / Groq / Azure), Anthropic, Gemini, Bedrock Converse, Mistral, OpenRouter\n  and the Vercel AI SDK, all folded into one usage shape\n- ✅ **Provider-agnostic by construction** — normalizers consume plain objects, so no provider\n  SDK is a dependency and this library never makes the model call itself\n- ✅ **Every billing dimension** — cached tokens, reasoning tokens, audio and image units, and\n  the write/read split providers report separately\n- ✅ **Streaming capture** — `StreamUsageCollector` accumulates chunks and prefers the\n  provider's final usage, falling back to its own count when a stream is cut short\n\n### 💰 Money & Ledger\n\n- ✅ **Exact `bigint` nano-USD** — on every persisted amount; no float arithmetic on a money\n  path\n- ✅ **Point-in-time rating** — an entry is priced at the rate in effect at the call\n  timestamp, and never re-rated by a later price change\n- ✅ **Append-only ledger** — corrections are compensating records that point at what they\n  reverse; nothing is updated in place\n- ✅ **Hash-chain integrity** — optional per-entry chain over the previous posted entry, so a\n  row edited directly in the database is detectable\n- ✅ **Exactly-once accounting** — a unique constraint on the idempotency key; the violation\n  is what the replay path recognizes as a conflict\n- ✅ **Markup as configuration** — a fixed multiplier or a per-call `IMarkupPolicy`, applied\n  in both rating modes, so the resale spread is not application code\n\n### 🛡️ Enforcement\n\n- ✅ **Hold → capture lifecycle** — the guard places a spend hold before the handler runs when\n  an estimate is declared; the interceptor settles it to actuals afterwards\n- ✅ **Multi-dimension budgets** — cap spend, token count and operation count per scope per\n  window (daily / weekly / monthly), hard or soft\n- ✅ **Atomic budget counter** — the optional `RedisBudgetCounterStore` runs check and\n  increment as one Lua script, so two concurrent requests cannot both fit under a cap that\n  holds one\n- ✅ **Prepaid wallets** — append-only grant / debit / refund / adjust entries, with a\n  configurable burn order (expiry, priority, FIFO)\n\n### 🧩 Developer Experience\n\n- ✅ **Zero runtime dependencies** — Prisma, `ioredis`, the event emitter and OpenTelemetry\n  all arrive as peers, and the optional ones are imported only when enabled\n- ✅ **Five subpaths** — `.` server · `./shared` zero-dependency contracts · `./prices` seed\n  data · `./prisma` the PostgreSQL store · `./redis` the budget counter\n- ✅ **Usage reports** — summarize by scope, feature, model or date, with currency conversion\n  and CSV or JSON export\n- ✅ **Typed events** — ten emitted event types over `@nestjs/event-emitter`, optional\n  (`wallet.low_balance` is declared in the catalog and reserved; nothing emits it yet)\n- ✅ **OpenTelemetry** — an optional sink that traces every metering call, carrying model and\n  operation and never prompt or completion text\n- ✅ **Typed end to end** — TypeScript `strict` with `exactOptionalPropertyTypes` and\n  `noUncheckedIndexedAccess`; zero `any`\n\n---\n\n## 📦 Subpath Exports\n\n| Subpath                            | What it contains                                                                   | Runtime deps            |\n| ---------------------------------- | ---------------------------------------------------------------------------------- | ----------------------- |\n| `@bymax-one/nest-ai-tokens`        | Dynamic module, services, guard, interceptor, decorators, collector, ports, errors | NestJS (peer)           |\n| `@bymax-one/nest-ai-tokens/shared` | Normalizers, pure cost math, types, catalogs, error codes                          | Zero                    |\n| `@bymax-one/nest-ai-tokens/prices` | `MODEL_PRICES_SEED` — pinned price snapshot (data-only, large; imported lazily)    | Zero                    |\n| `@bymax-one/nest-ai-tokens/prisma` | `PrismaAiTokensStore` — the official PostgreSQL adapter                            | `@prisma/client` (peer) |\n| `@bymax-one/nest-ai-tokens/redis`  | `RedisBudgetCounterStore` — optional fast budget counter                           | `ioredis` (peer)        |\n\n> The `./shared` subpath is framework-free and edge-safe — use it in frontends, workers, and edge functions that must not pull NestJS.\n> The `./prices` subpath exists so `./shared` stays within the family's tiny-bundle budget.\n\n### Artifact sizes (brotli)\n\n| Artifact                                    | Brotli size | Budget  |\n| ------------------------------------------- | ----------- | ------- |\n| server (`@bymax-one/nest-ai-tokens`)        | 37 KB       | < 40 KB |\n| shared (`@bymax-one/nest-ai-tokens/shared`) | 5 KB        | < 10 KB |\n| prisma (`@bymax-one/nest-ai-tokens/prisma`) | 10 KB       | < 15 KB |\n| redis (`@bymax-one/nest-ai-tokens/redis`)   | 1.1 KB      | < 5 KB  |\n| prices (data-only, exempt from budget)      | 1.3 KB      | exempt  |\n\n---\n\n## 🚀 Quick Start\n\n### 1 — Install\n\n```bash\nnpm i @bymax-one/nest-ai-tokens\n```\n\nRequired peers (if not already present):\n\n```bash\nnpm i @nestjs/common @nestjs/core reflect-metadata rxjs\n# Persistence:\nnpm i @prisma/client\n# Prisma 7 only — the client is opened through a driver adapter:\nnpm i @prisma/adapter-pg\n```\n\n> **Prisma 6 and 7 are both supported.** The adapter talks to PostgreSQL through\n> parameterized raw SQL and never touches a generated model delegate, so the same\n> code runs on either. What differs is how _your application_ builds the client it\n> hands over:\n>\n> ```typescript\n> // Prisma 6\n> const prisma = new PrismaClient({ datasourceUrl: process.env.DATABASE_URL })\n>\n> // Prisma 7 — `datasourceUrl` was removed; open through a driver adapter\n> import { PrismaPg } from '@prisma/adapter-pg'\n> const prisma = new PrismaClient({\n>   adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),\n> })\n> ```\n>\n> Prisma 7 also removed `url` from `datasource` blocks in `schema.prisma`; it\n> moves to a `prisma.config.ts` at your project root. Neither change reaches this\n> library's API — `PrismaAiTokensStore` receives whatever client you built.\n\n### 2 — Register the module\n\n```typescript\nimport { Module } from '@nestjs/common'\nimport { BymaxAiTokensModule } from '@bymax-one/nest-ai-tokens'\nimport { PrismaAiTokensStore } from '@bymax-one/nest-ai-tokens/prisma'\n\n@Module({\n  imports: [\n    BymaxAiTokensModule.forRootAsync({\n      imports: [PrismaModule],\n      inject: [PrismaService],\n      useFactory: (prisma: PrismaService) => ({\n        store: new PrismaAiTokensStore(prisma),\n        // The resale lever — end-users pay 4× provider cost:\n        markup: 4.0,\n        wallets: { creditRateNanoUsd: 5_000_000_000n }, // 1 credit = $5\n        budgets: { defaultPolicy: 'block', alertThresholds: [0.8, 1.0] },\n        pricing: { seedFromSnapshot: true }, // seed price table on first boot\n      }),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### 3 — Record usage (post-hoc, observe-only)\n\n```typescript\nimport { Injectable } from '@nestjs/common'\nimport { MeteringService, providerPresets } from '@bymax-one/nest-ai-tokens'\nimport { deriveIdempotencyKey } from '@bymax-one/nest-ai-tokens/shared'\n\n@Injectable()\nexport class WorkoutAiService {\n  constructor(private readonly metering: MeteringService) {}\n\n  async generate(tenantId: string, userId: string, jobId: string) {\n    const res = await this.openai.chat.completions.create({ model: 'gpt-4o', messages: [] })\n    await this.metering.record({\n      usage: res.usage,\n      preset: providerPresets.openaiChat,\n      context: {\n        tenantId,\n        scope: { type: 'user', id: userId },\n        feature: 'workout.generate',\n        idempotencyKey: deriveIdempotencyKey({ jobId }), // retry-safe\n      },\n    })\n    return res.choices[0]?.message.content\n  }\n}\n```\n\n### 4 — Enforced metering via guard + interceptor\n\n```typescript\nimport { Controller, Post, Get, Body, UseGuards, UseInterceptors, Req } from '@nestjs/common'\nimport {\n  BudgetGuard,\n  MeteringInterceptor,\n  Meter,\n  RequireBudget,\n  MeteringService,\n  toJsonSafe,\n  providerPresets,\n} from '@bymax-one/nest-ai-tokens'\n\n@Controller('ai')\n@UseGuards(BudgetGuard)\n@UseInterceptors(MeteringInterceptor)\nexport class AiController {\n  constructor(private readonly metering: MeteringService) {}\n\n  @Post('summarize')\n  @RequireBudget({ scope: 'tenant', estimate: { tokens: 3_000 } })\n  @Meter({\n    feature: 'doc.summarize',\n    scope: 'tenant',\n    preset: providerPresets.anthropic,\n    exposeHeaders: true,\n  })\n  async summarize(@Body() dto: { content: string }) {\n    // Guard checks budget and places a hold; interceptor settles it with the handler's actual usage.\n    return this.ai.summarize(dto.content) // returns { summary, usage }\n  }\n\n  @Get('me/usage')\n  async myUsage(@Req() req: { user: { tenantId: string; id: string } }) {\n    const status = await this.metering.getStatus(req.user.tenantId, {\n      type: 'user',\n      id: req.user.id,\n    })\n    return toJsonSafe(status) // bigint → decimal strings for JSON serialization\n  }\n}\n```\n\n---\n\n## ⚙️ Configuration\n\n`BymaxAiTokensModule.forRoot(options)` / `.forRootAsync({ useFactory })` accept `BymaxAiTokensModuleOptions`:\n\n| Option                      | Type                                  | Default        | Purpose                                                                       |\n| --------------------------- | ------------------------------------- | -------------- | ----------------------------------------------------------------------------- |\n| `store`                     | `IAiTokensStore`                      | — (required)   | Persistence adapter (use `PrismaAiTokensStore`)                               |\n| `scopeResolver`             | `(ctx) => MeteringContext`            | —              | Resolves the caller's scope from the request (required for guard/interceptor) |\n| `ratingMode`                | `'rate-table' \\| 'provider-reported'` | `'rate-table'` | Default cost rating mode                                                      |\n| `markup`                    | `number \\| IMarkupPolicy`             | `1.0`          | The resale multiplier (e.g. `4.0` = 4× provider cost)                         |\n| `pricing.seedFromSnapshot`  | `boolean`                             | `false`        | Seed `MODEL_PRICES_SEED` into the registry on first boot                      |\n| `wallets.creditRateNanoUsd` | `bigint`                              | —              | Exchange rate: 1 credit = this many nano-USD                                  |\n| `wallets.burnOrder`         | `'expiry' \\| 'priority' \\| 'fifo'`    | `'expiry'`     | Order in which grants are consumed                                            |\n| `budgets.defaultPolicy`     | `'block' \\| 'alert'`                  | `'alert'`      | Default enforcement when a budget is exhausted                                |\n| `budgets.alertThresholds`   | `number[]`                            | `[0.8, 1.0]`   | Soft-threshold events at these fractions                                      |\n| `holds.ttlSeconds`          | `number`                              | `300`          | Spend-hold TTL; the reaper voids expired holds                                |\n| `telemetry`                 | `ITelemetrySink`                      | no-op          | OpenTelemetry sink (pass `OtelTelemetrySink`)                                 |\n\n---\n\n## 🧩 Providers Matrix\n\n| Provider          | API                               | Normalizer                                                                         |\n| ----------------- | --------------------------------- | ---------------------------------------------------------------------------------- |\n| OpenAI            | Chat Completions                  | `normalizeOpenAiChatUsage` / `providerPresets.openaiChat`                          |\n| OpenAI            | Responses API                     | `normalizeOpenAiResponsesUsage` / `providerPresets.openaiResponses`                |\n| OpenAI-compatible | DeepSeek, xAI, Groq, Azure OpenAI | `normalizeOpenAiCompatibleUsage` / `providerPresets.openaiCompatible`              |\n| Anthropic         | Messages API                      | `normalizeAnthropicUsage` / `providerPresets.anthropic`                            |\n| Google            | Gemini / Vertex AI                | `normalizeGeminiUsage` / `providerPresets.gemini`                                  |\n| AWS               | Bedrock Converse                  | `normalizeBedrockConverseUsage` / `providerPresets.bedrockConverse`                |\n| Mistral           | Chat API                          | `normalizeMistralUsage` / `providerPresets.mistral`                                |\n| OpenRouter        | Chat                              | `normalizeOpenRouterUsage` / `providerPresets.openrouter` (provider-reported cost) |\n| Vercel            | AI SDK v5/v6                      | `normalizeVercelAiSdkUsage` / `providerPresets.vercelAiSdk`                        |\n\nAll normalizers are pure functions over plain objects — no provider SDK peer dependency. Pass the raw response object and get back a `NormalizedUsage`.\n\n---\n\n## 💰 Pricing & Markup\n\nThe fundamental difference between \"what the provider charged you\" and \"what you charge your users\" is the **markup multiplier**. This library makes that multiplier a first-class configuration option.\n\n```typescript\n// Configure at module level:\nBymaxAiTokensModule.forRootAsync({\n  useFactory: (prisma) => ({\n    store: new PrismaAiTokensStore(prisma),\n    markup: 4.0, // 4× resale margin\n    wallets: { creditRateNanoUsd: 5_000_000_000n }, // sell credits at $5 each\n  }),\n})\n\n// Example ledger entry:\n// Provider cost: $0.005 (5_000_000n nano-USD)\n// Billed to user: $0.020 (20_000_000n nano-USD, after 4× markup)\n// Margin: $0.015 per call\n```\n\nFor per-tenant or per-feature pricing, implement `IMarkupPolicy`:\n\n```typescript\nclass MyMarkupPolicy implements IMarkupPolicy {\n  resolve(ctx: MeteringContext): number | Promise<number> {\n    return ctx.feature === 'premium.summarize' ? 5.0 : 3.0\n  }\n}\n```\n\nEvery billed amount is stored in both `rawCostNanoUsd` (provider cost) and `billedCostNanoUsd` (after markup), so reports can show provider cost, margin, and customer-facing cost independently.\n\n---\n\n## 👛 Wallets & Budgets\n\n### Prepaid Wallets\n\n```typescript\n// Grant credits (e.g. on subscription renewal):\nawait wallets.grant(\n  { tenantId, ownerType: 'user', ownerId: userId },\n  {\n    amountNanoUsd: 25_000_000_000n, // $25 in credits\n    expiresAt: nextRenewal,\n    idempotencyKey: `allowance:${userId}:${cycle}`,\n    reason: 'monthly plan allowance',\n  },\n)\n\n// Credits are debited automatically when the ledger posts a billed cost.\n// Check balance:\nconst wallet = await wallets.getOrCreate({ tenantId, ownerType: 'user', ownerId: userId })\nconsole.log(wallet.balanceNanoUsd) // current balance\n```\n\n### Multi-Dimension Budgets\n\n```typescript\n// Set a monthly budget (anchored to subscription renewal, not calendar):\nawait budgets.upsertBudget({\n  tenantId,\n  scope: { type: 'user', id: userId },\n  features: ['workout.generate'],\n  limitCount: plan.maxAIGenerationsPerMonth, // count quota\n  limitTokens: plan.aiTokensMonthly, // token quota\n  window: 'month',\n  anchorAt: subscription.renewalDate, // renewal-anchored window\n  softThresholds: [0.8, 1.0],\n  policy: 'block',\n})\n\n// Check status:\nconst status = await metering.getStatus(tenantId, { type: 'user', id: userId })\n// status[].remaining, status[].exhausted, status[].policy\n```\n\n**Unlimited semantics:** no budget row = unlimited. `limit = 0` = hard block. Never use `null` or `0` to mean unlimited.\n\n---\n\n## 🌊 Streaming\n\n```typescript\nconst collector = new StreamUsageCollector({\n  provider: 'openai',\n  model: 'gpt-4o',\n  preset: providerPresets.openaiChat,\n})\n\nconst hold = await metering.hold(ctx, {\n  provider: 'openai',\n  model: 'gpt-4o',\n  operation: 'chat',\n  inputTokens: estimatedInput,\n  maxOutputTokens: 1024,\n})\n\ntry {\n  const stream = await openai.chat.completions.create({\n    model: 'gpt-4o',\n    messages,\n    stream: true,\n    stream_options: { include_usage: true },\n  })\n  for await (const chunk of stream) {\n    collector.push(chunk) // accumulates chunks, watches for the final usage object\n    res.write(chunk.choices[0]?.delta?.content ?? '')\n  }\n} catch (err) {\n  // Aborted or provider error: bill partial usage. capture() is idempotent.\n  await metering.capture(hold, collector)\n  throw err\n}\nawait metering.capture(hold, collector) // settle with provider-final actuals\n```\n\nOn abort, the collector falls back to a tokenizer estimate of the output tokens so the aborted call is still billed for what it produced.\n\n---\n\n## 📊 Reporting & Status\n\n```typescript\n// Usage summary — by feature, for the last 30 days:\nconst summary = await metering.summarize({\n  tenantId,\n  scope: { type: 'user', id: userId },\n  from: thirtyDaysAgo,\n  to: now,\n  groupBy: ['feature', 'model'],\n})\n\n// Export as CSV (streaming):\nconst csv = await reports.export({\n  tenantId,\n  from,\n  to,\n  format: 'csv',\n})\ncsv.pipe(res)\n\n// Access status / remaining budget:\nconst status = await metering.getStatus(tenantId, { type: 'user', id: userId })\nreturn toJsonSafe(status) // bigint → decimal strings\n```\n\n---\n\n## 📣 Events\n\nWhen `@nestjs/event-emitter` is installed and `EventEmitterModule.forRoot()` is registered, the library emits typed events on the NestJS event bus:\n\n| Event                                 | When                                                      |\n| ------------------------------------- | --------------------------------------------------------- |\n| `ai_tokens.usage.recorded`            | A usage record is posted to the ledger                    |\n| `ai_tokens.usage.reversed`            | A record is reversed by a compensating entry              |\n| `ai_tokens.hold.released`             | A hold is released without charging                       |\n| `ai_tokens.budget.threshold_crossed`  | A soft threshold is crossed                               |\n| `ai_tokens.budget.exceeded`           | A hard budget is exhausted                                |\n| `ai_tokens.budget.projected_exceeded` | An estimate would exceed a budget                         |\n| `ai_tokens.wallet.granted`            | Credit is granted to a wallet                             |\n| `ai_tokens.wallet.low_balance`        | _Reserved._ Declared in the catalog; nothing emits it yet |\n| `ai_tokens.wallet.depleted`           | A wallet balance reaches zero                             |\n| `ai_tokens.price.missing`             | No rate is in effect for a model at the call timestamp    |\n| `ai_tokens.audit`                     | An auditable operation completed                          |\n\nAll event payloads use `bigint` nano-USD internally and `string` decimal at JSON boundaries.\n\n---\n\n## 🚨 Error Codes\n\nAll errors are `AiTokensException extends HttpException`. Use the exported `AI_TOKENS_ERROR_CODES` catalog in switch/catch:\n\n| Code                             | HTTP | When                                                                                  |\n| -------------------------------- | ---- | ------------------------------------------------------------------------------------- |\n| `AI_TOKENS_NOT_CONFIGURED`       | 503  | Service invoked before async init completed                                           |\n| `AI_TOKENS_INVALID_CONFIG`       | 500  | Bad markup, negative limits, missing `scopeResolver`, missing fx                      |\n| `AI_TOKENS_UNKNOWN_PROVIDER`     | 400  | No preset/normalizer and usage is not already `NormalizedUsage`                       |\n| `AI_TOKENS_USAGE_MALFORMED`      | 422  | Normalizer could not read required token fields                                       |\n| `AI_TOKENS_PRICE_NOT_FOUND`      | 422  | No effective-dated rate after 6-step resolution chain (strict mode)                   |\n| `AI_TOKENS_FX_REQUIRED`          | 500  | `currency !== 'USD'` with no `fx` resolver                                            |\n| `AI_TOKENS_BUDGET_EXCEEDED`      | 402  | A hard spend budget blocks the call                                                   |\n| `AI_TOKENS_QUOTA_EXCEEDED`       | 429  | A hard token/count quota blocks the call                                              |\n| `AI_TOKENS_INSUFFICIENT_CREDITS` | 402  | Wallet debit failed (balance + overdraft below amount)                                |\n| `AI_TOKENS_HOLD_NOT_FOUND`       | 404  | `capture()`/`release()` on an unknown or cross-tenant hold                            |\n| `AI_TOKENS_HOLD_EXPIRED`         | 410  | `capture()` after the reaper swept the hold (retry via `record()`)                    |\n| `AI_TOKENS_HOLD_ALREADY_SETTLED` | 409  | `capture()` after `release()`                                                         |\n| `AI_TOKENS_IDEMPOTENCY_CONFLICT` | 409  | Same idempotency key, different payload hash; or reversing an already-reversed record |\n| `AI_TOKENS_STREAM_USAGE_MISSING` | 422  | Stream ended without provider usage and no tokenizer fallback available               |\n| `AI_TOKENS_STORE_ERROR`          | 502  | Persistence adapter raised an unmapped error                                          |\n\n---\n\n## 🔢 BigInt & JSON\n\n`bigint` does not survive `JSON.stringify`. Use `toJsonSafe()` at controller boundaries:\n\n```typescript\nimport { toJsonSafe } from '@bymax-one/nest-ai-tokens'\n\n// In your controller:\nreturn toJsonSafe(await metering.getStatus(tenantId, scope))\n// bigint fields (balanceNanoUsd, billedCostNanoUsd, etc.) become decimal strings.\n\n// For display/formatting:\nimport { formatNanoUsd } from '@bymax-one/nest-ai-tokens/shared'\nformatNanoUsd(5_000_000n) // '$0.005000'\nformatNanoUsd(5_000_000n, { currency: 'BRL', fxRateNano: 5_000_000_000n }) // '0.025000 BRL'\n```\n\n---\n\n## 📖 API Reference\n\nThe sections above document each of these with a runnable example. This is the index.\n\n### Services\n\n| Service                | Surface                                                                                                                              |\n| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |\n| `MeteringService`      | `meter<T>` · `record` · `estimateCost` · `hold` · `capture` · `release` · `restoreReleasedHold` · `reverse` · `getStatus`            |\n| `LedgerService`        | `append` · `query` · `findById` · `findByIdempotencyKey` · `findExpiredHolds` · `sumCost` · `transition` · `reverse` · `verifyChain` |\n| `WalletService`        | `getBalance` · `grant` · `debit` · `refund` · `adjust` · `settleAdjustment` · `getEntries` · `reconcile`                             |\n| `BudgetService`        | `upsertBudget` · `removeBudget` · `list` · `status` · `consume` · `release` · `adjust` · `rotateWindow` · `reconcileWindow`          |\n| `PricingService`       | `resolveRate` · `upsertPrice` · `getPriceHistory` · `seedFromSnapshot`                                                               |\n| `UsageReportService`   | `summarize` · `export`                                                                                                               |\n| `StreamUsageCollector` | Accumulates streamed chunks; prefers the provider's final usage over its own count                                                   |\n\n### Request-scoped enforcement\n\n| Class                 | Role                                                                                                                                                                                                      |\n| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `BudgetGuard`         | Check-only gate: refuses the request before the handler runs when a hard budget is exhausted, and — when `@RequireBudget.estimate` is present — places the hold and attaches it to the request            |\n| `MeteringInterceptor` | The capture half: extracts usage from the handler's result, then settles the guard's hold or records post-hoc when there is none. On a handler error it releases the hold and rethrows the original error |\n\n### Subpaths\n\n| Subpath    | Exports                                                                                                                 |\n| ---------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `.`        | Everything above, the DI tokens, the option types and the error catalog                                                 |\n| `./shared` | The zero-dependency contract layer — types, `AI_TOKENS_ERROR_CODES`, event shapes; safe to import from a browser bundle |\n| `./prices` | `MODEL_PRICES_SEED` — a dated price snapshot to seed `PricingService` from                                              |\n| `./prisma` | `PrismaAiTokensStore` — the reference store over PostgreSQL                                                             |\n| `./redis`  | `RedisBudgetCounterStore` — the atomic budget counter                                                                   |\n\n---\n\n## 🏗️ Architecture\n\n```\n                        HTTP request\n                             │\n                             ▼\n                   ┌─────────────────────┐\n                   │     BudgetGuard     │  ← scopeResolver\n                   │  check-only; holds  │    (your VERIFIED auth\n                   │  when an estimate   │     context, never the\n                   │  is declared        │     client's body)\n                   └──────────┬──────────┘\n                              │  refuses here if a hard budget is spent\n                              ▼\n                        your handler\n                     (calls the provider)\n                              │\n                              ▼\n                   ┌─────────────────────┐\n                   │ MeteringInterceptor │\n                   │ extracts usage, then│\n                   │ settles the hold or │\n                   │ records post-hoc    │\n                   └──────────┬──────────┘\n                              │\n                              ▼\n                        normalizers/\n              9 providers → one usage shape\n                              │\n                              ▼\n                          pricing/\n            rate in effect AT the call timestamp\n                    (never re-rated)\n                              │\n                              ▼\n                          markup/\n              multiplier or IMarkupPolicy\n                              │\n                              ▼\n                          ledger/\n             append-only; a correction is a\n             compensating record. Optional hash\n             chain over the previous posted entry\n                              │\n              ┌───────────────┴───────────────┐\n              │                               │\n        wallets/budgets            RedisBudgetCounterStore\n      append-only entries          one Lua incrIfBelow —\n      spend · tokens · count       check and increment\n      per scope per window         cannot interleave\n```\n\n**Storage is an adapter, not a dependency.** `./prisma` is the reference\nimplementation over PostgreSQL and `./redis` accelerates budget counters; both are\npeers you already control. The library defines the contracts.\n\n### Design Principles\n\n| Principle                                      | Description                                                                                                                                       |\n| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |\n| 💵 **Exact money, always**                     | Every persisted amount is `bigint` nano-USD. A float on a billing path is a rounding difference someone is charged for                            |\n| 📜 **Append, never update**                    | The ledger is evidence. A correction is a compensating record that points at what it reverses, so the history of a charge survives the correction |\n| 🔒 **The database prevents the double charge** | A unique constraint on the idempotency key, with the violation recognized as a conflict — not application discipline that can be forgotten        |\n| ⏱️ **Priced when it happened**                 | An entry is rated at the price in effect at the call timestamp and never re-rated, so a price change does not rewrite the past                    |\n| 🚪 **In-request enforcement**                  | Guard and interceptor, in-process: no proxy to deploy, no sidecar to keep alive, no external hop that can fail open                               |\n| 🧊 **Zero runtime dependencies**               | `dependencies` is `{}`, and an optional peer is imported only when the feature that needs it is enabled                                           |\n\n---\n\n## 🔐 Security Model\n\nThis library decides what a customer is charged and holds the record that proves it. Its\nsecurity contract is about arithmetic that cannot drift, a record that cannot be quietly\nrewritten, and text that does not travel.\n\n### Money is `bigint` nano-USD everywhere it is persisted\n\nNot because floats are imprecise in the abstract, but because a rounding difference on a\nbilling path is a number a customer is charged. There is no float arithmetic on a money\npath.\n\n### The ledger cannot be rewritten\n\nEntries are appended; a correction is a compensating record that points at what it\nreverses. With the optional hash chain on, each posted entry carries a hash over its\npredecessor, so a row edited directly in the database stops matching the chain that follows\nit. It is tamper-_evident_, not tamper-proof: pair it with database permissions that forbid\n`UPDATE` on the ledger table.\n\n### Charging twice is prevented by the database\n\nThe idempotency key carries a unique constraint, and the replay path is that constraint\nviolation being recognized as a conflict rather than surfacing as a store error. Application\ncare is not what stands between a retry and a second charge.\n\n### Prompt and completion text never reach the ledger, the events or the telemetry\n\nThe stream collector holds response text only to count it; the OpenTelemetry emitter carries\nmodel, operation, provider and service tier, and never content. The ledger stores no text at\nall.\n\nThere is one place text can be persisted, and it is opt-in: `IContentStore`, the content\nsidecar. A host that enables it stores **masked** text under a short TTL, separate from the\nledger, with a `purge()` that deletes by tenant, record or subject so an erasure request can\nbe honoured without touching the accounting. It is off unless you provide a store — and if\nyou do, text is being written, and where it is written is your implementation.\n\n### Budget checks are atomic\n\nThe Redis counter runs check-and-increment as one Lua script, so two concurrent requests\ncannot both observe room under a cap that only fits one.\n\n### The scope resolver is trusted input\n\n`scopeResolver` decides whose budget and whose wallet a request draws on. It is documented\nas taking the host's **verified** auth context — never the client's body or query. A resolver\nthat reads a tenant id from request input lets a caller bill someone else.\n\n---\n\n## 🛡️ Security Table\n\n| Layer            | Implementation                                                                                                                                                 |\n| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Money            | `bigint` nano-USD on every persisted amount; no float arithmetic on a money path                                                                               |\n| Ledger           | Append-only; corrections are compensating records, never updates                                                                                               |\n| Tamper evidence  | Optional per-entry hash chain over the previous posted entry                                                                                                   |\n| Double charging  | Unique constraint on the idempotency key; the violation is the exactly-once signal                                                                             |\n| Rating           | Point-in-time — an entry is priced at the call timestamp and never re-rated                                                                                    |\n| Telemetry        | Model, operation, provider, service tier; prompt and completion text never emitted                                                                             |\n| Content          | Never in the ledger, events or telemetry. The opt-in `IContentStore` sidecar stores masked text under a short TTL, with `purge()` by tenant, record or subject |\n| Budget races     | One atomic Lua `incrIfBelow` — the check and the increment cannot interleave                                                                                   |\n| Provider surface | Normalizers consume plain objects; no provider SDK dependency, no outbound call made here                                                                      |\n| Supply chain     | `dependencies: {}`; third-party Actions pinned by commit SHA (org-internal reusables by tag); CodeQL, TruffleHog and OpenSSF Scorecard                         |\n\n> [!IMPORTANT]\n> **The hash chain is tamper-_evident_, not tamper-_proof_.** It makes an edited row\n> detectable by anyone who verifies the chain; it does not stop someone with write\n> access from rewriting the chain wholesale. Pair it with database permissions that\n> forbid `UPDATE` on the ledger table.\n\n---\n\n## 🧱 Tech Stack\n\n- **Runtime:** Node.js 24+\n- **Framework:** NestJS 11 (guard + interceptor, `ConfigurableModuleBuilder`, `Symbol()` tokens)\n- **Persistence:** `@prisma/client ^6 || ^7` (peer) over PostgreSQL — both majors supported\n- **Budget acceleration:** `ioredis ^6` (peer), optional\n- **Events:** `@nestjs/event-emitter >=2` (peer), optional\n- **Telemetry:** `@opentelemetry/api ^1.9` (peer), optional\n- **Build:** tsup — ESM + CJS per subpath, with `.d.ts` _and_ `.d.cts` declarations\n- **Tests:** Jest + Testcontainers (PostgreSQL, end-to-end) + Stryker (mutation)\n- **TypeScript:** 5.x strict (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`), zero `any`\n\n---\n\n## 🧪 Testing & Quality\n\nThis library decides what a customer is charged, so the suite is held to a bar beyond \"the\ntests pass\".\n\n- ✅ **100% line coverage** — statements, branches, functions and lines, enforced as a gate\n- ✅ **100% mutation score against a `break: 100` gate** — the gate is the floor a run must\n  clear; the score the suite actually reaches is 100%, verified with\n  [Stryker](https://stryker-mutator.io/) ([report](./docs/mutation_testing_results.md))\n- ✅ **Real PostgreSQL in e2e** — Testcontainers, so the unique constraint that prevents a\n  double charge is exercised against an actual database rather than a mock of one\n- ✅ **Both Prisma majors** — the raw-query violation path is unit-tested for the query engine\n  _and_ the driver adapter, because the peer range admits 6 and 7 and each reports the\n  SQLSTATE in a different place\n- ✅ **Published-artifact gates** — `check:exports` resolves the types the way each module\n  system does, `check:runtime` loads every subpath from the packed tarball in ESM and\n  CommonJS, and `check:published` compiles this README's snippets against `dist/`\n- ✅ **Every suppression carries its reason** — no coverage directives anywhere; each\n  `// Stryker disable` in the production source states, after the `:` Stryker reads it\n  from, why the mutant it silences is unobservable from a unit test (an internal error\n  context, a provider id reached only through integration). `check:mutants` proves the\n  reasons parse, so they reach the mutation report instead of being replaced by\n  Stryker's `Ignored using a comment` fallback\n\n```bash\npnpm test          # unit tests\npnpm test:cov      # unit tests with the 100% coverage gate\npnpm test:e2e      # end-to-end against PostgreSQL (requires Docker)\npnpm mutation      # Stryker mutation testing (break: 100)\npnpm typecheck     # tsc strict check\npnpm lint          # ESLint\npnpm build && pnpm size   # bundle-size budgets\n```\n\n---\n\n## 🤝 Contributing\n\nPull requests are welcome. Please open an issue first for significant changes.\n\n- Read [`docs/technical_specification.md`](./docs/technical_specification.md) for architecture decisions.\n- Run `pnpm test:cov` and `pnpm lint` before opening a PR.\n- Please use Conventional Commits for the message; nothing enforces it here, so it is a convention rather than a gate.\n\n---\n\n## 🔒 Security Policy\n\nIf you discover a security vulnerability, please **do not** open a public issue. Instead, email us\nat **support@bymax.one** with details. We take security seriously and will respond promptly. See\n[`SECURITY.md`](./SECURITY.md) for the full policy.\n\n---\n\n## 📄 License\n\n[MIT](./LICENSE) © [Bymax One](https://github.com/bymaxone)\n\n---\n\n<p align=\"center\">\n  <sub>Built with ❤️ by <a href=\"https://github.com/bymaxone\">Bymax One</a></sub>\n</p>\n","readmeFilename":"README.md"}