@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 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
- [Unreleased]: https://github.com/bymaxone/nest-ai-tokens/compare/v1.0.0...HEAD
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 (see §22 migration notes).
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.
@@ -36,15 +36,26 @@ var CounterValueOutOfRangeError = class extends Error {
36
36
  };
37
37
  var RedisBudgetCounterStore = class {
38
38
  keyPrefix;
39
- source;
40
- connecting = null;
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.source = redis;
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.source !== "string") return Promise.resolve(this.source);
99
- this.connecting ??= this.connect(this.source);
100
- return this.connecting;
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) {
@@ -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.
@@ -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.
@@ -34,15 +34,26 @@ var CounterValueOutOfRangeError = class extends Error {
34
34
  };
35
35
  var RedisBudgetCounterStore = class {
36
36
  keyPrefix;
37
- source;
38
- connecting = null;
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.source = redis;
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.source !== "string") return Promise.resolve(this.source);
97
- this.connecting ??= this.connect(this.source);
98
- return this.connecting;
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.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
+ }