Skip to content

Develop#18

Merged
Zaiidmo merged 22 commits intomasterfrom
develop
Apr 7, 2026
Merged

Develop#18
Zaiidmo merged 22 commits intomasterfrom
develop

Conversation

@y-aithnini
Copy link
Copy Markdown
Contributor

Summary

  • What does this PR change?

Why

  • Why is this change needed?

Checklist

  • Added/updated tests (if behavior changed)
  • npm run lint passes
  • npm run typecheck passes
  • npm test passes
  • npm run build passes
  • Added a changeset (npx changeset) if this affects consumers

Notes

  • Anything reviewers should pay attention to?

Zaiidmo and others added 19 commits March 12, 2026 10:18
…ckages

- Replace git tag --list strategy with package.json-driven tag validation
  in all 16 publish workflows; use git rev-parse to verify the exact tag
  exists rather than guessing the latest repo-wide tag
- Update error guidance to reflect feat/** → develop → master flow
- Standardize dependabot to npm-only, grouped, monthly cadence across
  all 16 packages; remove github-actions ecosystem updates
- Add missing dependabot.yml to AuthKit-UI, ChartKit-UI, HealthKit,
  HooksKit, paymentkit, StorageKit
* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types
* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types

* feat(COMPT-57): add @Cacheable and @CacheEvict decorators with key interpolation
* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types

* feat(COMPT-57): add @Cacheable and @CacheEvict decorators with key interpolation

* test(COMPT-58): add full test suite with 95%+ coverage across all adapters, service, and decorators

* fix(lint): fix import/order and no-require-imports violations in spec files
…#5)

* docs(COMPT-59): add README, update peer deps, create v0.1.0 changeset

* style: fix Prettier formatting across all files
* fix(ci): fix SonarCloud coverage — use src/**/*.spec.ts instead of test/

* chore: set version to 0.0.1 for initial publish
* fix(ci): fix SonarCloud coverage — use src/**/*.spec.ts instead of test/

* Develop (#7)

* ops: UPDATED publish workflow and dependabot PR limits

* ops (ci): standardize publish validation and dependabot across all packages

- Replace git tag --list strategy with package.json-driven tag validation
  in all 16 publish workflows; use git rev-parse to verify the exact tag
  exists rather than guessing the latest repo-wide tag
- Update error guidance to reflect feat/** → develop → master flow
- Standardize dependabot to npm-only, grouped, monthly cadence across
  all 16 packages; remove github-actions ecosystem updates
- Add missing dependabot.yml to AuthKit-UI, ChartKit-UI, HealthKit,
  HooksKit, paymentkit, StorageKit

* security: added CODEOWNER file for branches security

* ops: updated relese check workflow#

* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters (#1)

* Feature/compt 56 cache module service (#2)

* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types

* Feature/compt 57 cacheable cacheevict decorators (#3)

* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types

* feat(COMPT-57): add @Cacheable and @CacheEvict decorators with key interpolation

* Feature/compt 58 test suite (#4)

* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types

* feat(COMPT-57): add @Cacheable and @CacheEvict decorators with key interpolation

* test(COMPT-58): add full test suite with 95%+ coverage across all adapters, service, and decorators

* fix(lint): fix import/order and no-require-imports violations in spec files

* docs(COMPT-59): add README, update peer deps, create v0.1.0 changeset (#5)

* docs(COMPT-59): add README, update peer deps, create v0.1.0 changeset

* style: fix Prettier formatting across all files

* improvement: replace KEYS with SCAN, fix @Cacheable null-return bug, clean up index exports (#6)

* ci: update release check workflow

* fix(ci): fix SonarCloud coverage — use src/**/*.spec.ts instead of test/ (#8)

* ops: updated release check jobs ]

---------

Co-authored-by: Zaiidmo <zaiidmoumnii@gmail.com>

* chore: set version to 0.0.1 for initial publish

---------

Co-authored-by: Zaiidmo <zaiidmoumnii@gmail.com>
* fix(ci): fix SonarCloud coverage — use src/**/*.spec.ts instead of test/

* Develop (#7)

* ops: UPDATED publish workflow and dependabot PR limits

* ops (ci): standardize publish validation and dependabot across all packages

- Replace git tag --list strategy with package.json-driven tag validation
  in all 16 publish workflows; use git rev-parse to verify the exact tag
  exists rather than guessing the latest repo-wide tag
- Update error guidance to reflect feat/** → develop → master flow
- Standardize dependabot to npm-only, grouped, monthly cadence across
  all 16 packages; remove github-actions ecosystem updates
- Add missing dependabot.yml to AuthKit-UI, ChartKit-UI, HealthKit,
  HooksKit, paymentkit, StorageKit

* security: added CODEOWNER file for branches security

* ops: updated relese check workflow#

* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters (#1)

* Feature/compt 56 cache module service (#2)

* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types

* Feature/compt 57 cacheable cacheevict decorators (#3)

* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types

* feat(COMPT-57): add @Cacheable and @CacheEvict decorators with key interpolation

* Feature/compt 58 test suite (#4)

* feat(COMPT-55): add ICacheStore port and Redis/InMemory adapters

* feat(COMPT-56): add CacheModule, CacheService, and DI tokens

* style: fix Prettier formatting across all files

* style: fix Prettier formatting after develop merge

* fix(lint): fix import order and replace any types with proper NestJS types

* feat(COMPT-57): add @Cacheable and @CacheEvict decorators with key interpolation

* test(COMPT-58): add full test suite with 95%+ coverage across all adapters, service, and decorators

* fix(lint): fix import/order and no-require-imports violations in spec files

* docs(COMPT-59): add README, update peer deps, create v0.1.0 changeset (#5)

* docs(COMPT-59): add README, update peer deps, create v0.1.0 changeset

* style: fix Prettier formatting across all files

* improvement: replace KEYS with SCAN, fix @Cacheable null-return bug, clean up index exports (#6)

* ci: update release check workflow

* fix(ci): fix SonarCloud coverage — use src/**/*.spec.ts instead of test/ (#8)

* ops: updated release check jobs ]

---------

Co-authored-by: Zaiidmo <zaiidmoumnii@gmail.com>

* chore: set version to 0.0.1 for initial publish

---------

Co-authored-by: Zaiidmo <zaiidmoumnii@gmail.com>
* chore: align version to 1.0.0 to match master

* chore: revert version to 0.0.1 as required
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR transitions the repository from the template “ExampleKit” shape into an initial public release of @ciscode/cachekit: a NestJS caching module with pluggable store adapters (memory/Redis), an injectable CacheService, and method decorators for transparent caching/eviction.

Changes:

  • Added CacheKit core: CacheModule (dynamic module), CacheService, ports/adapters, and @Cacheable / @CacheEvict decorators.
  • Added comprehensive Jest test coverage for the new module/service/adapters/utilities and increased global coverage thresholds.
  • Updated packaging, docs, TypeScript/Jest alias configuration, and CI/CD workflows (release checks, publish pipeline, dependabot/codeowners).

Reviewed changes

Copilot reviewed 28 out of 30 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tsconfig.json Adds new path aliases for @ports/* and @adapters/* (and updates utils alias).
src/utils/resolve-cache-key.util.ts Implements {n} placeholder interpolation for cache key templates.
src/utils/resolve-cache-key.util.spec.ts Unit tests for cache key interpolation behavior and edge cases.
src/utils/cache-service-ref.ts Adds a module-scoped singleton reference used by decorators to access CacheService.
src/utils/cache-service-ref.spec.ts Tests singleton behavior and error when accessed before initialization.
src/services/cache.service.ts Implements primary caching API (get/set/delete/clear/has/wrap) over ICacheStore.
src/services/cache.service.spec.ts Tests core service behavior including TTL resolution and wrap() semantics.
src/ports/cache-store.port.ts Defines the ICacheStore abstraction used by adapters and the service.
src/index.ts Updates public exports to CacheKit module/service/decorators/adapters/tokens/port types.
src/decorators/cacheable.decorator.ts Adds cache-aside method decorator with key templating and envelope handling.
src/decorators/cacheable.decorator.spec.ts Tests caching behavior (hit/miss), interpolation, TTL forwarding, and sync/async support.
src/decorators/cache-evict.decorator.ts Adds eviction decorator that deletes a resolved key after successful execution.
src/decorators/cache-evict.decorator.spec.ts Tests eviction behavior, error path (no eviction), interpolation, sync/async support.
src/constants.ts Introduces exported DI tokens (CACHE_STORE, CACHE_MODULE_OPTIONS).
src/cache-kit.module.ts Implements CacheModule.register() / registerAsync() and adapter wiring logic.
src/cache-kit.module.spec.ts Tests module wiring, async registration, and CacheServiceRef initialization.
src/adapters/redis-cache-store.adapter.ts Adds ioredis-backed store adapter with prefix support and SCAN-based clear.
src/adapters/redis-cache-store.adapter.spec.ts Tests Redis adapter using ioredis-mock (including prefix clear semantics).
src/adapters/in-memory-cache-store.adapter.ts Adds Map-backed adapter with lazy TTL eviction and JSON serialization.
src/adapters/in-memory-cache-store.adapter.spec.ts Tests in-memory adapter contract, TTL expiry, and parse-error behavior.
README.md Rewrites README for CacheKit usage, configuration, and API overview.
package.json Renames package/versioning intent, adjusts deps/peers, and adds ioredis-mock dev dependency.
package-lock.json Updates lockfile to reflect dependency/peer changes and added dev deps.
jest.config.ts Updates coverage thresholds and module mappers; adds forceExit.
.github/workflows/release-check.yml Restructures CI into quality/test/build/sonar jobs and adds status reporting.
.github/workflows/publish.yml Switches publish trigger to master push and adds tag/version validation + provenance publish.
.github/workflows/pr-validation.yml Updates Node version used in PR validation.
.github/dependabot.yml Adds dependabot configuration for npm dependencies.
.github/CODEOWNERS Adds repository-wide codeowner rule.
.changeset/compt-59-v0-1-0.md Adds changeset describing the initial public release contents.
Comments suppressed due to low confidence (2)

README.md:287

  • The README still contains the old template sections starting at "### 3. Define DTOs" (ExampleKitModule, class-validator DTOs, etc.). This conflicts with the new CacheKit API and the updated dependencies; remove or replace this leftover template content to avoid misleading consumers.
## 📄 License

MIT © [CisCode](https://github.com/CISCODE-MA)

### 3. Define DTOs

```typescript
// src/dto/create-example.dto.ts
import { IsString, IsNotEmpty } from "class-validator";

export class CreateExampleDto {
  @IsString()
  @IsNotEmpty()
  name: string;
}
**.github/workflows/publish.yml:88**
* The workflow publishes twice (two "Publish to NPM" steps). The second publish will fail with "previously published" (or could race) and makes the job non-idempotent. Keep a single publish step (with provenance if desired).
  - name: Publish to NPM
    run: npm publish --access public --provenance
    env:
      NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

  - name: Publish to NPM
    run: npm publish --access public
    env:
      NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
</details>

Comment on lines +161 to +177
const factoryClass = (options.useClass ?? options.useExisting)!;

const factoryProvider: Provider = options.useClass
? // useClass: let NestJS instantiate a new instance of this class
{ provide: factoryClass, useClass: factoryClass }
: // useExisting: reuse a token already registered elsewhere in the module tree
{ provide: factoryClass, useExisting: options.useExisting };

return [
factoryProvider,
{
// Call createCacheOptions() on the factory instance to get the options
provide: CACHE_MODULE_OPTIONS,
useFactory: (factory: {
createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions;
}) => factory.createCacheOptions(),
inject: [factoryClass],
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createAsyncProviders()'s useExisting branch registers { provide: factoryClass, useExisting: options.useExisting } where factoryClass is options.useExisting, resulting in a self-referential provider (provide: X, useExisting: X). This will break registerAsync({ useExisting: ... }) at runtime; the factory instance should be injected from the existing token without re-providing it (or the provide token should differ).

Suggested change
const factoryClass = (options.useClass ?? options.useExisting)!;
const factoryProvider: Provider = options.useClass
? // useClass: let NestJS instantiate a new instance of this class
{ provide: factoryClass, useClass: factoryClass }
: // useExisting: reuse a token already registered elsewhere in the module tree
{ provide: factoryClass, useExisting: options.useExisting };
return [
factoryProvider,
{
// Call createCacheOptions() on the factory instance to get the options
provide: CACHE_MODULE_OPTIONS,
useFactory: (factory: {
createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions;
}) => factory.createCacheOptions(),
inject: [factoryClass],
if (options.useClass) {
return [
{
// useClass: let NestJS instantiate a new instance of this class
provide: options.useClass,
useClass: options.useClass,
},
{
// Call createCacheOptions() on the factory instance to get the options
provide: CACHE_MODULE_OPTIONS,
useFactory: (factory: {
createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions;
}) => factory.createCacheOptions(),
inject: [options.useClass],
},
{
// Build the store adapter from the resolved options
provide: CACHE_STORE,
useFactory: (resolvedOptions: CacheModuleOptions): ICacheStore =>
createStoreFromOptions(resolvedOptions),
inject: [CACHE_MODULE_OPTIONS],
},
];
}
return [
{
// useExisting: inject a token already registered elsewhere in the module tree
provide: CACHE_MODULE_OPTIONS,
useFactory: (factory: {
createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions;
}) => factory.createCacheOptions(),
inject: [options.useExisting!],

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +98
* Factory function type used by registerAsync's useFactory.
* May return the options synchronously or as a Promise.
*/
export type CacheModuleOptionsFactory = () => Promise<CacheModuleOptions> | CacheModuleOptions;

/**
* Asynchronous configuration options for CacheModule.registerAsync().
* Supports three patterns:
* - useFactory — inline factory function (most common)
* - useClass — instantiate a config class per module
* - useExisting — reuse an already-provided config class
*/
export interface CacheModuleAsyncOptions {
/** Providers whose tokens are passed as arguments to useFactory. */
inject?: Array<InjectionToken | OptionalFactoryDependency>;

/** Inline factory that resolves to CacheModuleOptions. */
useFactory?: (...args: unknown[]) => Promise<CacheModuleOptions> | CacheModuleOptions;

/**
* Class that the module will instantiate to obtain the options.
* The class must implement CacheModuleOptionsFactory.
*/
useClass?: Type<{ createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions }>;

/**
* Re-use an already-provided token (class or value) as the options factory.
* The resolved instance must implement CacheModuleOptionsFactory.
*/
useExisting?: Type<{ createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions }>;
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CacheModuleOptionsFactory type is defined as a function signature, but useClass/useExisting expect an object with createCacheOptions(). This mismatch is confusing for consumers implementing async config classes; align the type alias and the interface expectations (and update the comments) so the contract is unambiguous.

Suggested change
* Factory function type used by registerAsync's useFactory.
* May return the options synchronously or as a Promise.
*/
export type CacheModuleOptionsFactory = () => Promise<CacheModuleOptions> | CacheModuleOptions;
/**
* Asynchronous configuration options for CacheModule.registerAsync().
* Supports three patterns:
* - useFactory inline factory function (most common)
* - useClass instantiate a config class per module
* - useExisting reuse an already-provided config class
*/
export interface CacheModuleAsyncOptions {
/** Providers whose tokens are passed as arguments to useFactory. */
inject?: Array<InjectionToken | OptionalFactoryDependency>;
/** Inline factory that resolves to CacheModuleOptions. */
useFactory?: (...args: unknown[]) => Promise<CacheModuleOptions> | CacheModuleOptions;
/**
* Class that the module will instantiate to obtain the options.
* The class must implement CacheModuleOptionsFactory.
*/
useClass?: Type<{ createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions }>;
/**
* Re-use an already-provided token (class or value) as the options factory.
* The resolved instance must implement CacheModuleOptionsFactory.
*/
useExisting?: Type<{ createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions }>;
* Contract implemented by async configuration providers used with
* registerAsync({ useClass }) and registerAsync({ useExisting }).
* The provider may return the options synchronously or as a Promise.
*/
export interface CacheModuleOptionsFactory {
createCacheOptions(): Promise<CacheModuleOptions> | CacheModuleOptions;
}
/**
* Asynchronous configuration options for CacheModule.registerAsync().
* Supports three patterns:
* - useFactory inline factory function that returns CacheModuleOptions
* - useClass instantiate a config class implementing CacheModuleOptionsFactory
* - useExisting reuse an already-provided provider implementing CacheModuleOptionsFactory
*/
export interface CacheModuleAsyncOptions {
/** Providers whose tokens are passed as arguments to useFactory. */
inject?: Array<InjectionToken | OptionalFactoryDependency>;
/** Inline factory function that resolves to CacheModuleOptions. */
useFactory?: (...args: unknown[]) => Promise<CacheModuleOptions> | CacheModuleOptions;
/**
* Class that the module will instantiate to obtain the options.
* The instantiated class must implement CacheModuleOptionsFactory.
*/
useClass?: Type<CacheModuleOptionsFactory>;
/**
* Re-use an already-provided token (class or value) as the options factory.
* The resolved instance must implement CacheModuleOptionsFactory.
*/
useExisting?: Type<CacheModuleOptionsFactory>;

Copilot uses AI. Check for mistakes.
ttl: 300,
redis: {
client: "redis://localhost:6379",
keyPrefix: "myapp:",
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README examples use keyPrefix: "myapp:" / default "app:", but RedisCacheStore.buildKey() always inserts its own : separator. With a trailing colon this produces double-colons (e.g. myapp::user:1). Update the docs (or normalize in code) to clarify whether keyPrefix should include a trailing :.

Suggested change
keyPrefix: "myapp:",
keyPrefix: "myapp",

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +78
- name: Lint
run: npm run lint --if-present 2>/dev/null || true

- name: Run tests (if present)
run: npm test --if-present
continue-on-error: false
- name: Test
run: npm test --if-present 2>/dev/null || true
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm run lint is effectively ignored (|| true), so the workflow may publish even when lint fails. If lint is intended to be non-blocking, make that explicit; otherwise remove the || true so publishing is gated on lint success.

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +7
// forceExit closes open ioredis handles left by ioredis-mock after tests finish
forceExit: true,
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forceExit: true makes Jest exit even when there are open handles, which can mask real resource leaks and flaky tests. Prefer cleaning up the ioredis-mock instances (e.g., disconnect/quit in afterEach/afterAll) and keep forceExit off if possible.

Suggested change
// forceExit closes open ioredis handles left by ioredis-mock after tests finish
forceExit: true,

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +120
// ── Cache hit ──────────────────────────────────────────────────────
// Read the envelope. null from get() means cache miss; a present envelope
// means hit — even when the stored return value was null or undefined.
const envelope = await cacheService.get<CacheEnvelope>(resolvedKey);
if (envelope !== null && isEnvelope(envelope)) {
return envelope.__v;
}

// ── Cache miss ─────────────────────────────────────────────────────
// Call the original method; wrap in Promise.resolve() to handle both
// sync methods (returns a plain value) and async methods (returns a Promise)
const result = await Promise.resolve(originalMethod.apply(this, args));

// Wrap in an envelope before storing so null/undefined returns are
// preserved and distinguishable from a cache miss on the next call.
await cacheService.set<CacheEnvelope>(resolvedKey, { __v: result }, ttlSeconds);

Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Cacheable claims it can preserve cached undefined values (see the comments and CacheEnvelope doc), but both adapters serialize via JSON.stringify. JSON.stringify({ __v: undefined }) becomes {}, so the __v property is lost and the next read is treated as a cache miss. Either disallow/normalize undefined return values (e.g., convert to null + sentinel) or change the envelope encoding so undefined can round-trip, and add a test for the undefined case.

Copilot uses AI. Check for mistakes.
@y-aithnini y-aithnini closed this Apr 7, 2026
@y-aithnini y-aithnini reopened this Apr 7, 2026
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 7, 2026

@Zaiidmo Zaiidmo merged commit 8ba2fd8 into master Apr 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants