Repository navigation
Rationalize the root export surface and scope-method vocabulary #24
Description
Priority
P2 — Medium — finish the stable public/package boundary before v1; not a production correctness blocker.
v0.14.0 grooming verification (2026-08-01)
Rechecked against main@34bd022. The six public-boundary questions below remain open. Draft documentation PR #89 is now conflicting with v0.14.0; its earlier green checks cover an older base, and it still does not resolve this API issue. Keep P2 for the pre-v1 surface decision. No removal, rename, or new subpath is approved by this grooming pass.
Current state through v0.14.0
Most of the original surface-area cleanup has shipped:
- feat: decouple Prometheus metrics adapter #56 and feat: add Datadog metrics adapter #59 moved Prometheus and Datadog integrations to caller-owned subpaths.
- feat: require caller-owned Redis clients #67 removed the lazy Redis factory/constructor mode.
- feat: overlay runtime cache configuration on defaults #68 removed the unused
MissingKeyConfigErrorexport. - feat: decouple GLIDE adapter runtime #81 kept Valkey GLIDE behind an explicit integration subpath.
- feat: add inline getOrLoad API #84 added
getOrLoad()as an intentional everyday root API. - refactor: internalize deterministic ramp sampling #86 removed public ramp-sampler configuration and four related root exports.
- refactor: derive watermark TTLs from active state #88 removed public watermark-TTL configuration and protocol fields.
- feat: require Node.js 22 or newer #94 moved the maintained runtime floor to Node.js 22 without changing runtime API shape.
The root correctly retains backend-neutral serializer, metrics, and semantic Redis contracts needed for dependency injection. Vendor-specific adapters and the Redis protocol use explicit package subpaths.
Draft #89 remains documentation-only and does not resolve this issue. Its current head conflicts with v0.14.0, so it must be reconciled with the grouped shadow configuration and current README before review.
Current evidence:
- Root exports: https://github.com/lan17/DialCache/blob/e06d833ba245706a499d66056fdad15dc1210b68/src/index.ts
- Scope aliases:
Lines 234 to 248 in e06d833
enable<T>(fn: () => Awaitable<T>): Promise<T> { return this.context.enable(fn); } disable<T>(fn: () => Awaitable<T>): Promise<T> { return this.context.disable(fn); } withEnabled<T>(fn: () => Awaitable<T>): Promise<T> { return this.enable(fn); } withDisabled<T>(fn: () => Awaitable<T>): Promise<T> { return this.disable(fn); } - Package subpaths:
Lines 11 to 71 in e06d833
"exports": { ".": { "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }, "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" } }, "./node-redis": { "import": { "types": "./dist/node-redis.d.ts", "default": "./dist/node-redis.js" }, "require": { "types": "./dist/node-redis.d.cts", "default": "./dist/node-redis.cjs" } }, "./datadog": { "import": { "types": "./dist/datadog.d.ts", "default": "./dist/datadog.js" }, "require": { "types": "./dist/datadog.d.cts", "default": "./dist/datadog.cjs" } }, "./prometheus": { "import": { "types": "./dist/prometheus.d.ts", "default": "./dist/prometheus.js" }, "require": { "types": "./dist/prometheus.d.cts", "default": "./dist/prometheus.cjs" } }, "./valkey-glide": { "import": { "types": "./dist/valkey-glide.d.ts", "default": "./dist/valkey-glide.js" }, "require": { "types": "./dist/valkey-glide.d.cts", "default": "./dist/valkey-glide.cjs" } }, "./redis-protocol": { "import": { "types": "./dist/redis-protocol.d.ts", "default": "./dist/redis-protocol.js" }, "require": { "types": "./dist/redis-protocol.d.cts", "default": "./dist/redis-protocol.cjs" } }
Remaining audit items
The v0.11 holistic review found these unresolved public-boundary questions:
DialCacheexposes duplicate scope-method pairs:enable/disableandwithEnabled/withDisabled. Documentation consistently teachesenable/disable.- Prometheus and Datadog subpaths expose both adapter classes and one-line factory functions. Supporting two equivalent construction idioms adds API without adding capability.
- The root exports
RedisConfigfromsrc/internal/redis-cache.ts. The contract may be intentional, but a public type should live behind a public module boundary rather than makinginternalpart of the source-level API architecture. redisClusterHashTag("")returns"{}"; Redis does not treat an empty brace pair as an effective cluster hash tag. The helper should either reject an empty value or cease to be a public supported helper.DialCacheKeyConfig.enabled(ttlSec)enables both local and remote layers at 100%. That shorthand can broaden behavior when Redis is later configured and is less explicit than the normal layer-map API. Decide whether to remove it, rename it to make both layers explicit, or retain it with a strong contract before v1.- The root mixes everyday API with lower-level context, key, metrics, serializer, and semantic Redis contracts. Some are necessary public types—for example,
DialCacheKeyis the input toCacheConfigProvider—while others still need an explicit supported-consumer justification.
The new cached-definition snapshot, runtime key validation, and CommonJS error-identity findings are tracked separately because they are correctness/package-runtime work rather than export-inventory decisions:
- Validate cache-key shapes before canonicalization #97
- Preserve payload-error identity across CommonJS package subpaths #99
- Snapshot cached definition options at registration #101
Recommended simplicity-first direction — decision required
Subject to explicit approval before any public removal or rename:
- Make
enable/disablecanonical and consider removingwithEnabled/withDisabledbefore v1 with a concise migration note. - Pick one construction idiom per metrics integration unless both have a demonstrated consumer need.
- Move any retained public
RedisConfigdefinition out ofinternalwithout introducing another user-facing concept. - Make the hash-tag helper incapable of returning a value that does not provide the advertised Redis Cluster behavior.
- Prefer explicit layer configuration over a broad
enabled()shorthand unless real usage justifies it. - Classify every root export as everyday consumer API, backend-neutral extension/injection contract, or unsupported internal helper.
- Remove or relocate only exports that lack a supported consumer contract.
- Do not create new package subpaths merely to move clutter elsewhere.
- Preserve the existing Prometheus, Datadog, node-redis, Valkey GLIDE, and Redis-protocol boundaries.
Acceptance criteria
- Record the approved canonical scope vocabulary and compatibility policy.
- Decide and document the supported construction idiom for both metrics integrations.
- No public type is sourced from an
internalmodule after the cleanup. - The public hash-tag helper either rejects empty input or is removed/moved; it cannot imply colocation that Redis will not provide.
- Record the approved future of
DialCacheKeyConfig.enabled()with an explicit migration path if it changes. - Classify every root export by supported role.
- Keep normal consumer API and intentional backend-neutral injection contracts accessible without vendor dependencies.
- Add packed ESM/CommonJS declaration coverage for every retained or moved contract.
- Include a migration table for every public removal, rename, or move.
- Reconcile the resulting API reference with docs: add a landing README and searchable reference site #89 if that documentation PR merges first.
- Do not remove an export solely because repository tests do not consume it.
- Obtain explicit approval before public removals, renames, or subpath changes.
Activity