@bymax-one/nest-cache 1.2.0 → 1.2.2

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
@@ -4,6 +4,76 @@ All notable changes to this project are documented in this file. The format is
4
4
  based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.2.2] - 2026-09-04
8
+
9
+ ### Changed
10
+
11
+ - **Nothing you execute changes.** `dist/` built from this tag is byte-for-byte
12
+ identical to 1.2.1 — verified by unpacking the published 1.2.1 tarball and
13
+ diffing it against a fresh build, not asserted. `src/` has not been touched
14
+ since 1.2.1.
15
+
16
+ Two files in an installed copy do differ, and neither is code: the `version`
17
+ field in `package.json`, and this changelog. Upgrading from 1.2.1 changes no
18
+ behaviour; staying on 1.2.1 costs you nothing.
19
+
20
+ The version exists because the repository moved underneath the artifact, and a
21
+ released tag is where that movement is recorded.
22
+
23
+ ### Security
24
+
25
+ - **Three advisory fixes, all in development dependencies.** `browserslist` to
26
+ 4.28.8 (GHSA-73wf-gq98-2v4g, GHSA-c83g-rgw3-j3cx), `fast-uri` to 3.1.6 (four
27
+ advisories at CVSS 7.5) and `qs` to 6.16.0 (GHSA-4mjr-xmp4-gh2g,
28
+ GHSA-x5fp-wj9c-mxmx). All three are reached through `@stryker-mutator/*`, which
29
+ is a devDependency, and this package publishes with `"dependencies": {}`.
30
+
31
+ **No consumer was exposed and none is protected by this release.** The fixes
32
+ protect the build environment. They are listed because "Security" in a changelog
33
+ is read as "upgrade for this", and here that reading would be wrong.
34
+
35
+ Two of the three were already pinned in `overrides` at floors that already
36
+ admitted the fixed versions, and protected nothing: a caret there is a floor
37
+ applied when resolution happens, not a standing instruction to re-resolve. The
38
+ check that works is the resolved version in the lockfile, never the presence of
39
+ an entry.
40
+
41
+ ### Internal
42
+
43
+ - ESLint 9 to 10, with `eslint-plugin-import` replaced by the maintained fork
44
+ `eslint-plugin-import-x`. The 9 line is end-of-life: 9.39.5 is the last release
45
+ and is itself deprecated, and the `maintenance` dist-tag points at it.
46
+ - `import-x/parsers` is now configured, which fixes a rule that had never worked.
47
+ `no-cycle` was declared `error` and reported as active by `--print-config` while
48
+ a real two-file cycle passed with exit 0, because the dependency parser skips in
49
+ silence every extension it cannot map. No cycles exist in `src/`; the gate was
50
+ simply open.
51
+ - `@parcel/watcher`'s build script is recorded as denied, so `pnpm install` stops
52
+ failing on it under pnpm 11.
53
+ - The shared Bymax code-review rules are adopted in `AGENTS.md`, kept in step by
54
+ the `agents-sync` workflow.
55
+
56
+ ## [1.2.1] - 2026-08-21
57
+
58
+ ### Changed
59
+
60
+ - **`RevealedValue`'s hash arm now names each entry `field`, not `name`**
61
+ (`{ field, value }`). Redis's own vocabulary for a hash is field/value —
62
+ `HSET key field value`, `HDEL key field`, and `HGETALL` returns field-value
63
+ pairs — so a generic `name` lost the domain term in the one place a reader
64
+ would check the shape against the server's documentation.
65
+
66
+ This is a **type-level breaking change** shipped as a patch, deliberately and
67
+ with the trade-off stated: `RevealedValue` was published in 1.2.0 roughly an
68
+ hour earlier, no consumer had wired it yet, and correcting a freshly published
69
+ name is cheaper than carrying a permanent vocabulary mismatch or maintaining
70
+ two names for one concept. A consumer that already destructured `name` gets a
71
+ compile error, not a silent behaviour change.
72
+
73
+ The library emits **one** name. Aliasing `field` and `name` together would have
74
+ been two names for one idea, which is the defect this corrects rather than a
75
+ softer way to ship it.
76
+
7
77
  ## [1.2.0] - 2026-08-21
8
78
 
9
79
  ### Security
@@ -280,6 +350,8 @@ type or export moved.
280
350
  - Published with npm OIDC provenance — no long-lived tokens
281
351
  - Zero direct runtime dependencies (`dependencies: {}`) — `ioredis` and NestJS via peer deps
282
352
 
353
+ [1.2.2]: https://github.com/bymaxone/nest-cache/compare/v1.2.1...v1.2.2
354
+ [1.2.1]: https://github.com/bymaxone/nest-cache/compare/v1.2.0...v1.2.1
283
355
  [1.2.0]: https://github.com/bymaxone/nest-cache/compare/v1.1.0...v1.2.0
284
356
  [1.1.0]: https://github.com/bymaxone/nest-cache/compare/v1.0.6...v1.1.0
285
357
  [1.0.6]: https://github.com/bymaxone/nest-cache/compare/v1.0.5...v1.0.6
package/README.md CHANGED
@@ -473,6 +473,47 @@ Do not relax this to be helpful. Widening it later is compatible; a leak is not
473
473
 
474
474
  `mode`, `isScanSupported` and `degradedAboveMs` sit **outside** the health union: a cluster deployment that is down should still report that scanning was never going to work.
475
475
 
476
+ ### Reading a value: `revealed` / `withheld` / `missing`
477
+
478
+ `revealValue()` answers with a discriminated union, not a nullable value, because **"I may not tell you" and "there is nothing here" are different answers** and rendering them identically is the defect this whole surface exists to avoid.
479
+
480
+ ```ts
481
+ import type { CacheAdminService } from '@bymax-one/nest-cache/admin'
482
+
483
+ async function describeValue(admin: CacheAdminService, scope: string, key: string) {
484
+ const result = await admin.revealValue(scope, key)
485
+
486
+ switch (result.status) {
487
+ case 'revealed':
488
+ // `result.type` is the Redis type; `result.value` is shaped by it.
489
+ return { type: result.type, value: result.value }
490
+ case 'withheld':
491
+ // The scope declares `isReadable: false`. `origin` explains why, verbatim.
492
+ // Listing, types, TTLs and sizes for this key remain available.
493
+ return { refusedBecause: result.origin }
494
+ case 'missing':
495
+ return { gone: true }
496
+ }
497
+ }
498
+ ```
499
+
500
+ A withheld value is **not an authorization failure**. The caller is allowed to ask; the deployment declared the keyspace unreadable at wiring, and no credential changes that — so serving it as `403` would tell a client that some other permission would unlock it, which is false.
501
+
502
+ The revealed value is shaped by the key's type:
503
+
504
+ | `value.kind` | Shape | Notes |
505
+ | ------------- | ---------------------------------- | ---------------------------------------------------------------------------------------- |
506
+ | `string` | `{ value: string }` | Truncated to `revealStringLimit` |
507
+ | `hash` | `{ fields: { field, value }[] }` | `field`, matching Redis's own vocabulary (`HSET key field value`) |
508
+ | `members` | `{ members: string[] }` | **Sets and lists both.** The enclosing `type` says which — see the ordering caveat below |
509
+ | `scored` | `{ members: { member, score }[] }` | Sorted sets. `score` is a **string**: parsing to a number would round large integers |
510
+ | `unsupported` | `{ type: RedisKeyType }` | Streams and module types — says so, rather than returning an empty value |
511
+
512
+ Every arm except `unsupported` carries `isComplete`. It is deliberately **positive polarity**: an absent boolean reads as `false`, and under a name like `truncated` that default would be the _reassuring_ answer — a value silently claiming nothing was cut.
513
+
514
+ > [!IMPORTANT]
515
+ > **List order is meaningful; set order is not**, and both arrive in the same `members` array. Nothing in the type stops a renderer from sorting either one. Sorting a set for display is fine; sorting a list is a lie about the data — an `LPUSH` queue shown alphabetically misreports what pops next. Gate any client-side ordering on `type === 'set'`.
516
+
476
517
  ### Costs the surface does not hide
477
518
 
478
519
  - **`sampledCount` / `sampledBytes`** are sums over a capped `SCAN`, not measurements of the keyspace. `isComplete` is the fact; the names are the guard.
@@ -391,7 +391,11 @@ exports.CacheAdminService = class CacheAdminService {
391
391
  const kept = all.slice(0, limit);
392
392
  return {
393
393
  kind: "hash",
394
- fields: kept.map(([name, value]) => ({ name, value })),
394
+ // `field`, not `name`: Redis's own vocabulary for a hash is field/value
395
+ // (`HSET key field value`, `HDEL key field`). A generic `name` loses the
396
+ // domain term in the one place a reader would check it against the
397
+ // server's documentation.
398
+ fields: kept.map(([field, value]) => ({ field, value })),
395
399
  isComplete: kept.length === all.length
396
400
  };
397
401
  }
@@ -596,7 +596,7 @@ type RevealedValue = {
596
596
  } | {
597
597
  readonly kind: 'hash';
598
598
  readonly fields: readonly {
599
- readonly name: string;
599
+ readonly field: string;
600
600
  readonly value: string;
601
601
  }[];
602
602
  readonly isComplete: boolean;
@@ -596,7 +596,7 @@ type RevealedValue = {
596
596
  } | {
597
597
  readonly kind: 'hash';
598
598
  readonly fields: readonly {
599
- readonly name: string;
599
+ readonly field: string;
600
600
  readonly value: string;
601
601
  }[];
602
602
  readonly isComplete: boolean;
@@ -389,7 +389,11 @@ var CacheAdminService = class {
389
389
  const kept = all.slice(0, limit);
390
390
  return {
391
391
  kind: "hash",
392
- fields: kept.map(([name, value]) => ({ name, value })),
392
+ // `field`, not `name`: Redis's own vocabulary for a hash is field/value
393
+ // (`HSET key field value`, `HDEL key field`). A generic `name` loses the
394
+ // domain term in the one place a reader would check it against the
395
+ // server's documentation.
396
+ fields: kept.map(([field, value]) => ({ field, value })),
393
397
  isComplete: kept.length === all.length
394
398
  };
395
399
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bymax-one/nest-cache",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Typed Redis cache for NestJS based on ioredis 6, with namespace strategy, Pub/Sub and Lua script management.",
5
5
  "author": "Bymax One <support@bymax.one>",
6
6
  "license": "MIT",
@@ -111,7 +111,7 @@
111
111
  "@arethetypeswrong/cli": "^0.18.5",
112
112
  "@commitlint/cli": "^21.2.1",
113
113
  "@commitlint/config-conventional": "^21.2.0",
114
- "@eslint/js": "^9.39.4",
114
+ "@eslint/js": "^10.0.1",
115
115
  "@nestjs/common": "^11.1.20",
116
116
  "@nestjs/core": "^11.1.20",
117
117
  "@nestjs/testing": "^11.1.20",
@@ -122,10 +122,10 @@
122
122
  "@types/node": "^24",
123
123
  "@typescript-eslint/eslint-plugin": "^8.59.3",
124
124
  "@typescript-eslint/parser": "^8.59.3",
125
- "eslint": "^9.39.4",
125
+ "eslint": "^10.9.1",
126
126
  "eslint-config-prettier": "^10.1.8",
127
- "eslint-import-resolver-typescript": "^4.4.4",
128
- "eslint-plugin-import": "^2.32.0",
127
+ "eslint-import-resolver-typescript": "^4.4.5",
128
+ "eslint-plugin-import-x": "^4.17.1",
129
129
  "eslint-plugin-prettier": "^5.5.5",
130
130
  "eslint-plugin-security": "^4.0.0",
131
131
  "globals": "^17.6.0",