@bymax-one/nest-ai-tokens 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -1
- package/README.md +1 -9
- package/dist/redis/index.cjs +17 -6
- package/dist/redis/index.d.cts +1 -2
- package/dist/redis/index.d.ts +1 -2
- package/dist/redis/index.mjs +17 -6
- package/package.json +48 -25
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,30 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.0.1] - 2026-08-04
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
- The README's `Migration` section, which described migrating an AI cost layer out of a
|
|
16
|
+
named private monorepo. It reached the npm page of a public package, where it named a
|
|
17
|
+
repository its readers cannot see and cannot act on. The normative rule it carried —
|
|
18
|
+
translate `0`/`null` limits to no budget row — is already stated under the budget
|
|
19
|
+
semantics and is unchanged.
|
|
20
|
+
|
|
21
|
+
### Security
|
|
22
|
+
|
|
23
|
+
- The Redis credentials are no longer disclosed when `RedisBudgetCounterStore` is
|
|
24
|
+
serialized. The store kept the live client — or the connection URL it is opened from —
|
|
25
|
+
in a TypeScript `private` property, which is erased at runtime and leaves an enumerable
|
|
26
|
+
own property. A connection URL carries the password inline and an ioredis instance
|
|
27
|
+
carries `options.password` as a plain field, so `JSON.stringify`, object spread and
|
|
28
|
+
`util.inspect` emitted the password in plaintext, which is what a structured logger does
|
|
29
|
+
when it renders its arguments and what an error reporter does when it captures the scope
|
|
30
|
+
of a throw. The source and the in-flight lazy connection both move to ECMAScript private
|
|
31
|
+
fields.
|
|
32
|
+
|
|
33
|
+
Reading on purpose is unchanged and no public type or export moved.
|
|
34
|
+
|
|
11
35
|
## [1.0.0] - 2026-08-03
|
|
12
36
|
|
|
13
37
|
First published release. Everything below ships in it.
|
|
@@ -110,4 +134,5 @@ have regressed from. They are kept because the reasoning is worth having.
|
|
|
110
134
|
---
|
|
111
135
|
|
|
112
136
|
[1.0.0]: https://github.com/bymaxone/nest-ai-tokens/releases/tag/v1.0.0
|
|
113
|
-
[
|
|
137
|
+
[1.0.1]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.0...v1.0.1
|
|
138
|
+
[Unreleased]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.1...HEAD
|
package/README.md
CHANGED
|
@@ -395,7 +395,7 @@ const status = await metering.getStatus(tenantId, { type: 'user', id: userId })
|
|
|
395
395
|
// status[].remaining, status[].exhausted, status[].policy
|
|
396
396
|
```
|
|
397
397
|
|
|
398
|
-
**Unlimited semantics:** no budget row = unlimited. `limit = 0` = hard block. Never use `null` or `0` to mean unlimited
|
|
398
|
+
**Unlimited semantics:** no budget row = unlimited. `limit = 0` = hard block. Never use `null` or `0` to mean unlimited.
|
|
399
399
|
|
|
400
400
|
---
|
|
401
401
|
|
|
@@ -532,14 +532,6 @@ formatNanoUsd(5_000_000n, { currency: 'BRL', fxRateNano: 5_000_000_000n }) // '0
|
|
|
532
532
|
|
|
533
533
|
---
|
|
534
534
|
|
|
535
|
-
## 🚚 Migration
|
|
536
|
-
|
|
537
|
-
If you are migrating the `_commons_/ai/*` layer from `bymax-fitness`, see [docs/technical_specification.md §22](./docs/technical_specification.md) for the full field-by-field mapping table, the zero/null → unlimited translation rules, and backfill notes.
|
|
538
|
-
|
|
539
|
-
Key caution: `bymax-fitness` treats `0` as unlimited on most paths and as blocked on one path. Do not import plan rows verbatim. Translate `0`/`null` limits to **no budget row** (the normative unlimited representation in this library).
|
|
540
|
-
|
|
541
|
-
---
|
|
542
|
-
|
|
543
535
|
## 📖 API Reference
|
|
544
536
|
|
|
545
537
|
The sections above document each of these with a runnable example. This is the index.
|
package/dist/redis/index.cjs
CHANGED
|
@@ -36,15 +36,26 @@ var CounterValueOutOfRangeError = class extends Error {
|
|
|
36
36
|
};
|
|
37
37
|
var RedisBudgetCounterStore = class {
|
|
38
38
|
keyPrefix;
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The live client or the connection URL it is opened from.
|
|
41
|
+
*
|
|
42
|
+
* An ECMAScript private field rather than a TypeScript `private` one, which is
|
|
43
|
+
* erased at runtime: a connection URL carries the Redis password inline, and an
|
|
44
|
+
* ioredis instance carries `options.password` as a plain field. Leaving either
|
|
45
|
+
* enumerable would let anything that serializes this store — a structured
|
|
46
|
+
* logger rendering its arguments, an error reporter capturing the scope of a
|
|
47
|
+
* throw — emit the credentials in plaintext.
|
|
48
|
+
*/
|
|
49
|
+
#source;
|
|
50
|
+
/** The in-flight lazy connection, which resolves to the same credentialed client. */
|
|
51
|
+
#connecting = null;
|
|
41
52
|
/**
|
|
42
53
|
* @param redis A live `ioredis` client, or a connection URL to lazily connect.
|
|
43
54
|
* @param options Optional key prefix.
|
|
44
55
|
*/
|
|
45
56
|
constructor(redis, options = {}) {
|
|
46
57
|
this.keyPrefix = options.keyPrefix ?? "";
|
|
47
|
-
this
|
|
58
|
+
this.#source = redis;
|
|
48
59
|
}
|
|
49
60
|
/**
|
|
50
61
|
* Atomically increment `key` by `amount` iff the result stays `<= limit`, setting
|
|
@@ -95,9 +106,9 @@ var RedisBudgetCounterStore = class {
|
|
|
95
106
|
}
|
|
96
107
|
/** Resolve the live client, lazily connecting a URL via a dynamic `ioredis` import. */
|
|
97
108
|
resolve() {
|
|
98
|
-
if (typeof this
|
|
99
|
-
this
|
|
100
|
-
return this
|
|
109
|
+
if (typeof this.#source !== "string") return Promise.resolve(this.#source);
|
|
110
|
+
this.#connecting ??= this.connect(this.#source);
|
|
111
|
+
return this.#connecting;
|
|
101
112
|
}
|
|
102
113
|
/** Dynamically import `ioredis` and connect the given URL. */
|
|
103
114
|
async connect(url) {
|
package/dist/redis/index.d.cts
CHANGED
|
@@ -54,9 +54,8 @@ declare class CounterValueOutOfRangeError extends Error {
|
|
|
54
54
|
}
|
|
55
55
|
/** The official Redis adapter for the live budget counter port. */
|
|
56
56
|
declare class RedisBudgetCounterStore implements IBudgetCounterStore {
|
|
57
|
+
#private;
|
|
57
58
|
private readonly keyPrefix;
|
|
58
|
-
private readonly source;
|
|
59
|
-
private connecting;
|
|
60
59
|
/**
|
|
61
60
|
* @param redis A live `ioredis` client, or a connection URL to lazily connect.
|
|
62
61
|
* @param options Optional key prefix.
|
package/dist/redis/index.d.ts
CHANGED
|
@@ -54,9 +54,8 @@ declare class CounterValueOutOfRangeError extends Error {
|
|
|
54
54
|
}
|
|
55
55
|
/** The official Redis adapter for the live budget counter port. */
|
|
56
56
|
declare class RedisBudgetCounterStore implements IBudgetCounterStore {
|
|
57
|
+
#private;
|
|
57
58
|
private readonly keyPrefix;
|
|
58
|
-
private readonly source;
|
|
59
|
-
private connecting;
|
|
60
59
|
/**
|
|
61
60
|
* @param redis A live `ioredis` client, or a connection URL to lazily connect.
|
|
62
61
|
* @param options Optional key prefix.
|
package/dist/redis/index.mjs
CHANGED
|
@@ -34,15 +34,26 @@ var CounterValueOutOfRangeError = class extends Error {
|
|
|
34
34
|
};
|
|
35
35
|
var RedisBudgetCounterStore = class {
|
|
36
36
|
keyPrefix;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The live client or the connection URL it is opened from.
|
|
39
|
+
*
|
|
40
|
+
* An ECMAScript private field rather than a TypeScript `private` one, which is
|
|
41
|
+
* erased at runtime: a connection URL carries the Redis password inline, and an
|
|
42
|
+
* ioredis instance carries `options.password` as a plain field. Leaving either
|
|
43
|
+
* enumerable would let anything that serializes this store — a structured
|
|
44
|
+
* logger rendering its arguments, an error reporter capturing the scope of a
|
|
45
|
+
* throw — emit the credentials in plaintext.
|
|
46
|
+
*/
|
|
47
|
+
#source;
|
|
48
|
+
/** The in-flight lazy connection, which resolves to the same credentialed client. */
|
|
49
|
+
#connecting = null;
|
|
39
50
|
/**
|
|
40
51
|
* @param redis A live `ioredis` client, or a connection URL to lazily connect.
|
|
41
52
|
* @param options Optional key prefix.
|
|
42
53
|
*/
|
|
43
54
|
constructor(redis, options = {}) {
|
|
44
55
|
this.keyPrefix = options.keyPrefix ?? "";
|
|
45
|
-
this
|
|
56
|
+
this.#source = redis;
|
|
46
57
|
}
|
|
47
58
|
/**
|
|
48
59
|
* Atomically increment `key` by `amount` iff the result stays `<= limit`, setting
|
|
@@ -93,9 +104,9 @@ var RedisBudgetCounterStore = class {
|
|
|
93
104
|
}
|
|
94
105
|
/** Resolve the live client, lazily connecting a URL via a dynamic `ioredis` import. */
|
|
95
106
|
resolve() {
|
|
96
|
-
if (typeof this
|
|
97
|
-
this
|
|
98
|
-
return this
|
|
107
|
+
if (typeof this.#source !== "string") return Promise.resolve(this.#source);
|
|
108
|
+
this.#connecting ??= this.connect(this.#source);
|
|
109
|
+
return this.#connecting;
|
|
99
110
|
}
|
|
100
111
|
/** Dynamically import `ioredis` and connect the given URL. */
|
|
101
112
|
async connect(url) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bymax-one/nest-ai-tokens",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"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.",
|
|
5
5
|
"author": "Bymax One <support@bymax.one>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,6 +73,31 @@
|
|
|
73
73
|
},
|
|
74
74
|
"./package.json": "./package.json"
|
|
75
75
|
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "pnpm clean && tsup",
|
|
78
|
+
"check:exports": "attw --pack . --profile strict",
|
|
79
|
+
"check:published": "node scripts/check-published-surface.mjs",
|
|
80
|
+
"check:runtime": "node scripts/check-consumer-runtime.mjs",
|
|
81
|
+
"clean": "node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"",
|
|
82
|
+
"db:generate": "prisma generate",
|
|
83
|
+
"docs:check": "node scripts/check-jsdoc.mjs && tsc --noEmit -p tsconfig.e2e.json",
|
|
84
|
+
"lint": "eslint src",
|
|
85
|
+
"lint:fix": "eslint src --fix",
|
|
86
|
+
"mutation": "stryker run",
|
|
87
|
+
"mutation:dry-run": "stryker run --dryRunOnly",
|
|
88
|
+
"mutation:incremental": "stryker run --incremental",
|
|
89
|
+
"prepare": "prisma generate",
|
|
90
|
+
"prepublishOnly": "pnpm clean && pnpm typecheck && pnpm lint && pnpm test:cov:all && pnpm build && pnpm size && pnpm check:published",
|
|
91
|
+
"release": "npm publish --provenance --access public",
|
|
92
|
+
"size": "node scripts/check-size.mjs",
|
|
93
|
+
"test": "jest",
|
|
94
|
+
"test:all": "pnpm test && pnpm test:e2e",
|
|
95
|
+
"test:cov": "jest --coverage",
|
|
96
|
+
"test:cov:all": "jest --config jest.coverage.config.ts --coverage",
|
|
97
|
+
"test:e2e": "jest --config jest.e2e.config.ts",
|
|
98
|
+
"test:watch": "jest --watch",
|
|
99
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json"
|
|
100
|
+
},
|
|
76
101
|
"dependencies": {},
|
|
77
102
|
"peerDependencies": {
|
|
78
103
|
"@nestjs/common": "^11.0.16",
|
|
@@ -146,6 +171,26 @@
|
|
|
146
171
|
"typescript": "^5.9.3",
|
|
147
172
|
"typescript-eslint": "^8.65.0"
|
|
148
173
|
},
|
|
174
|
+
"pnpm": {
|
|
175
|
+
"overrides": {
|
|
176
|
+
"brace-expansion@1": "^1.1.17",
|
|
177
|
+
"brace-expansion@2": "^2.1.3",
|
|
178
|
+
"brace-expansion@5": "^5.0.8",
|
|
179
|
+
"esbuild": "0.28.1",
|
|
180
|
+
"fast-uri": "^3.1.4",
|
|
181
|
+
"multer": "2.2.0",
|
|
182
|
+
"protobufjs": "^7.6.5",
|
|
183
|
+
"qs": "6.15.2",
|
|
184
|
+
"undici": "^8.9.0",
|
|
185
|
+
"uuid": "11.1.1"
|
|
186
|
+
},
|
|
187
|
+
"onlyBuiltDependencies": [
|
|
188
|
+
"esbuild",
|
|
189
|
+
"@prisma/client",
|
|
190
|
+
"@prisma/engines",
|
|
191
|
+
"prisma"
|
|
192
|
+
]
|
|
193
|
+
},
|
|
149
194
|
"keywords": [
|
|
150
195
|
"nestjs",
|
|
151
196
|
"ai",
|
|
@@ -163,6 +208,7 @@
|
|
|
163
208
|
"budget",
|
|
164
209
|
"markup"
|
|
165
210
|
],
|
|
211
|
+
"packageManager": "pnpm@10.8.1",
|
|
166
212
|
"engines": {
|
|
167
213
|
"node": ">=24.0.0"
|
|
168
214
|
},
|
|
@@ -189,28 +235,5 @@
|
|
|
189
235
|
"./dist/redis/index.d.cts"
|
|
190
236
|
]
|
|
191
237
|
}
|
|
192
|
-
},
|
|
193
|
-
"scripts": {
|
|
194
|
-
"build": "pnpm clean && tsup",
|
|
195
|
-
"check:exports": "attw --pack . --profile strict",
|
|
196
|
-
"check:published": "node scripts/check-published-surface.mjs",
|
|
197
|
-
"check:runtime": "node scripts/check-consumer-runtime.mjs",
|
|
198
|
-
"clean": "node -e \"const {rmSync}=require('node:fs');for(const d of ['dist','coverage'])rmSync(d,{recursive:true,force:true})\"",
|
|
199
|
-
"db:generate": "prisma generate",
|
|
200
|
-
"docs:check": "node scripts/check-jsdoc.mjs && tsc --noEmit -p tsconfig.e2e.json",
|
|
201
|
-
"lint": "eslint src",
|
|
202
|
-
"lint:fix": "eslint src --fix",
|
|
203
|
-
"mutation": "stryker run",
|
|
204
|
-
"mutation:dry-run": "stryker run --dryRunOnly",
|
|
205
|
-
"mutation:incremental": "stryker run --incremental",
|
|
206
|
-
"release": "npm publish --provenance --access public",
|
|
207
|
-
"size": "node scripts/check-size.mjs",
|
|
208
|
-
"test": "jest",
|
|
209
|
-
"test:all": "pnpm test && pnpm test:e2e",
|
|
210
|
-
"test:cov": "jest --coverage",
|
|
211
|
-
"test:cov:all": "jest --config jest.coverage.config.ts --coverage",
|
|
212
|
-
"test:e2e": "jest --config jest.e2e.config.ts",
|
|
213
|
-
"test:watch": "jest --watch",
|
|
214
|
-
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.server.json"
|
|
215
238
|
}
|
|
216
|
-
}
|
|
239
|
+
}
|