Skip to content

Develop#6

Open
y-aithnini wants to merge 15 commits intomasterfrom
develop
Open

Develop#6
y-aithnini wants to merge 15 commits intomasterfrom
develop

Conversation

@y-aithnini
Copy link
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?

y-aithnini and others added 15 commits February 23, 2026 15:55
- Replace GitHub Actions version tags with full commit SHA hashes (v6 -> fd88b7d, v1 -> d304d05)
- Fix SONAR_PROJECT_KEY from LoggingKit to NotificationKit
- Add 'main' branch to PR trigger list for release-check workflow
- Resolves 2 security hotspots (githubactions:S7637) for supply chain security
- Add explicit NotificationDocument type annotations to map callbacks
- Fix mapToRecord signature to accept any type (handles both Map and Record)
- Install mongoose as dev dependency for type checking
- Fixes pre-push typecheck failures
- Add comprehensive instruction files in .github/instructions/
- Includes copilot, testing, bugfix, features, general guidelines
- Standardize documentation across all repositories
- Remove deprecated instruction files from .github/ root
- Consolidate all docs in .github/instructions/ directory
- Improve documentation organization
- Mongoose required for type compilation (infra/repositories/mongoose)
- ts-node required by Jest configuration
- Resolves typecheck and test errors
* implemented decorators and providers

* Add notification and webhook controller tests

* remove in-memory repository and update exports

* removed mongoose

* removed duplicate code for sonarqube

* docs: add comprehensive documentation for testing implementation

* style: fix prettier formatting issues
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 4, 2026

Copy link

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 significantly expands @ciscode/notification-kit by adding a NestJS integration layer (module/controllers/DI tokens), infrastructure adapters (senders, providers, schema reference), and a large test/documentation/CI uplift to support multi-channel notifications as a reusable package.

Changes:

  • Added NestJS module integration (register/registerAsync), controllers (REST + webhook), and DI helpers.
  • Added infra layer exports: sender adapters (Email/SMS/Push), utility providers, and a Mongoose schema reference.
  • Added extensive tests, docs, CI/publishing workflow updates, and a changeset.

Reviewed changes

Copilot reviewed 54 out of 56 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
tsup.config.ts Marks Nest/provider SDKs as externals for library bundling.
tsconfig.json Enables decorators/metadata and adds DOM lib/types for Node fetch usage.
test/test-utils.ts Adds shared mocks/factories for unit/integration tests.
test/smoke.spec.ts Replaces trivial smoke test with export-surface checks.
test/integration.test.ts Adds end-to-end-ish module/service integration coverage.
src/nest/providers.ts Adds provider factory for Nest DI tokens and NotificationService.
src/nest/module.ts Implements NotificationKitModule.register() / registerAsync().
src/nest/module.test.ts Adds tests around module registration and DI wiring.
src/nest/interfaces.ts Defines module options + async options factory interfaces.
src/nest/index.ts Exports Nest layer public surface.
src/nest/decorators.ts Adds convenience @Inject*() decorators for tokens.
src/nest/decorators.test.ts Adds tests for decorators/tokens.
src/nest/controllers/webhook.controller.ts Adds webhook receiver controller for delivery callbacks.
src/nest/controllers/webhook.controller.test.ts Adds webhook controller unit tests.
src/nest/controllers/notification.controller.ts Adds REST controller for send/query/retry/cancel operations.
src/nest/controllers/notification.controller.test.ts Adds REST controller unit tests.
src/nest/constants.ts Defines DI token symbols.
src/infra/senders/sms/vonage.sender.ts Adds Vonage SMS sender adapter (lazy SDK import).
src/infra/senders/sms/twilio.sender.ts Adds Twilio SMS sender adapter (lazy SDK import).
src/infra/senders/sms/aws-sns.sender.ts Adds AWS SNS SMS sender adapter (lazy SDK import).
src/infra/senders/push/onesignal.sender.ts Adds OneSignal push sender using fetch.
src/infra/senders/push/firebase.sender.ts Adds Firebase push sender adapter (lazy SDK import).
src/infra/senders/push/aws-sns-push.sender.ts Adds AWS SNS push sender adapter (lazy SDK import).
src/infra/senders/index.ts Aggregates infra sender exports.
src/infra/senders/email/nodemailer.sender.ts Adds Nodemailer email sender adapter (lazy import).
src/infra/repositories/mongoose/notification.schema.ts Adds a Mongoose schema definition as a reference export.
src/infra/repositories/index.ts Exposes repository schema reference exports.
src/infra/providers/template.provider.ts Adds template engines (Handlebars + simple replacement).
src/infra/providers/providers.test.ts Adds tests for provider utilities (id/datetime/templates/events).
src/infra/providers/index.ts Aggregates provider exports.
src/infra/providers/id-generator.provider.ts Adds UUID/ObjectId/NanoId generators.
src/infra/providers/event-emitter.provider.ts Adds in-memory + console event emitters.
src/infra/providers/datetime.provider.ts Adds datetime provider implementation.
src/infra/index.ts Introduces infra layer barrel export.
src/infra/README.md Expands infra documentation and configuration examples.
src/index.ts Exports core + infra + nest from the root entry.
src/core/notification.service.test.ts Adds broad unit coverage for core service behavior.
src/core/errors.test.ts Adds tests for domain error types.
src/core/dtos.test.ts Adds tests for Zod DTO schemas/helpers.
package.json Adds optional peer meta, dev deps, and retains root-only export map.
jest.config.ts Expands match patterns and adds coverage thresholds/reporters.
README.md Adds comprehensive package docs and usage examples.
CONTRIBUTING.md Updates contribution/testing guidance.
CHANGELOG.md Adds a changelog scaffold and initial entries.
.github/workflows/release-check.yml Updates CI branch targets and Sonar project key + pins actions.
.github/workflows/publish.yml Changes publish trigger/flow and adds provenance publishing.
.github/instructions/testing.instructions.md Adds detailed internal testing guidelines.
.github/instructions/sonarqube_mcp.instructions.md Adds SonarQube MCP usage instructions.
.github/instructions/general.instructions.md Adds general module development guidelines.
.github/instructions/features.instructions.md Adds feature development playbook.
.github/instructions/bugfix.instructions.md Adds bugfix process playbook.
.github/dependabot.yml Adds Dependabot configuration for npm/actions.
.changeset/notificationkit_71368.md Adds changeset describing the feature/test/doc uplift.
.github/copilot-instructions.md Removes previous template copilot instructions file.

Comment on lines +3 to +6
import { NotificationService } from "../core/notification.service";
import { DateTimeProvider as _DateTimeProvider } from "../infra/providers/datetime.provider";
import { UuidGenerator as _UuidGenerator } from "../infra/providers/id-generator.provider";

Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Unused imports DateTimeProvider as _DateTimeProvider and UuidGenerator as _UuidGenerator add noise and may fail linting. Remove them or use them directly (instead of dynamic importing again) if they were intended to force inclusion.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +58
@Post("send")
@HttpCode(HttpStatus.CREATED)
async send(@Body() dto: SendNotificationDto) {
try {
return await this.notificationService.send(dto);
} catch (error) {
if (error instanceof ValidationError) {
throw new BadRequestException(error.message);
}
throw error;
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

These endpoints accept SendNotificationDto/CreateNotificationDto directly from @Body() but there is no runtime validation/parsing (Zod schemas exist in src/core/dtos.ts but NotificationService doesn't call them). As a result, invalid inputs may flow into the core service, and the ValidationError catch here is currently ineffective. Consider validating/parsing with the Zod schemas in the controller (or via a custom Nest pipe) and mapping Zod errors to BadRequestException.

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +78
"peerDependenciesMeta": {
"nodemailer": {
"optional": true
},
"twilio": {
"optional": true
},
"@aws-sdk/client-sns": {
"optional": true
},
"@vonage/server-sdk": {
"optional": true
},
"firebase-admin": {
"optional": true
},
"mongoose": {
"optional": true
},
"handlebars": {
"optional": true
},
"nanoid": {
"optional": true
}
},
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

peerDependenciesMeta marks provider SDKs as optional, but they are not listed in peerDependencies. Many package managers ignore peerDependenciesMeta entries that don't correspond to a declared peer dependency, so consumers won't get correct install-time hints/warnings. Add these optional packages to peerDependencies (with appropriate version ranges) and keep them marked optional via peerDependenciesMeta.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +33
```typescript
import { NodemailerSender } from "@ciscode/notification-kit/infra";

const emailSender = new NodemailerSender({
host: "smtp.gmail.com",
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

This document also uses subpath imports like @ciscode/notification-kit/infra, but the package currently exports only the root entry (exports has only "."). Align the docs with the actual package exports, or add subpath exports if you intend consumers to import infra directly.

Copilot uses AI. Check for mistakes.
Comment on lines 3 to +36
on:
push:
tags:
- "v*.*.*"
branches:
- master
workflow_dispatch:

jobs:
publish:
runs-on: ubuntu-latest

permissions:
contents: read
packages: write
id-token: write

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Validate tag exists on this push
run: |
TAG=$(git describe --exact-match --tags HEAD 2>/dev/null || echo "")
if [[ -z "$TAG" ]]; then
echo "❌ No tag found on HEAD. This push did not include a version tag."
echo "To publish, merge to master with a tag: git tag v1.0.0 && git push origin master --tags"
exit 1
fi
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Invalid tag format: $TAG. Expected: v*.*.*"
exit 1
fi
echo "✅ Valid tag found: $TAG"
echo "TAG_VERSION=$TAG" >> $GITHUB_ENV
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

publish.yml is triggered on every push to master, but then hard-fails if HEAD is not tagged. This will cause repeated failed workflow runs for normal merges. If publishing should only happen on tagged releases, consider restoring a on.push.tags: ["v*.*.*"] trigger (or use workflow_run from a dedicated release workflow) instead of failing after the fact.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +76
```typescript
import { Module } from "@nestjs/common";
import { NotificationKitModule } from "@ciscode/notification-kit";
import { NodemailerSender, MongooseNotificationRepository } from "@ciscode/notification-kit/infra";

@Module({
imports: [
NotificationKitModule.register({
senders: [
new NodemailerSender({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
from: "noreply@example.com",
}),
],
repository: new MongooseNotificationRepository(/* mongoose connection */),
}),
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

The README example imports MongooseNotificationRepository from @ciscode/notification-kit/infra, but this package only exports a Mongoose schema reference (concrete repositories are in separate packages). Update the example to use the external repository package (e.g. @ciscode/notification-kit-mongodb) or remove the repository class from the snippet to avoid misleading consumers.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +30
branches: 70,
functions: 70,
lines: 75,
statements: 75,
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

Jest coverageThreshold is set to 70–75%, but the repo docs/instructions in this PR state an 80%+ target. Align the configured thresholds with the documented standard (or update the docs if 70–75% is the intended target).

Suggested change
branches: 70,
functions: 70,
lines: 75,
statements: 75,
branches: 80,
functions: 80,
lines: 80,
statements: 80,

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +42
@Controller()
export class NotificationController {
private readonly prefix: string;

constructor(
@Inject(NOTIFICATION_SERVICE)
private readonly notificationService: NotificationService,
@Inject(NOTIFICATION_KIT_OPTIONS)
private readonly options: NotificationKitModuleOptions,
) {
this.prefix = options.apiPrefix || "notifications";
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

apiPrefix is assigned to this.prefix but never used by any route decorators, so consumers setting apiPrefix will see no effect. Either remove the option or implement prefixing at the controller level (e.g., fixed @Controller('notifications')) and document that dynamic prefixes are not supported by NestJS without generating controllers dynamically.

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +130
} else {
results.push({
success: false,
notificationId: item.notificationId,
error: String(error),
});
}
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

handleWebhook() returns error: String(error) in the per-item results, which can leak internal error details (and potentially stack traces) to the webhook caller. Consider returning a stable, sanitized error code/message and logging the full error server-side instead.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +62
static registerAsync(options: NotificationKitModuleAsyncOptions): DynamicModule {
const asyncOptionsProvider = this.createAsyncOptionsProvider(options);
const asyncProviders = this.createAsyncProviders(options);

// We can't conditionally load controllers in async mode without the options
// So we'll need to always include them and they can handle being disabled internally
// Or we can create a factory provider that returns empty array
const providersFactory: Provider = {
provide: "NOTIFICATION_PROVIDERS",
useFactory: (moduleOptions: NotificationKitModuleOptions) => {
return createNotificationKitProviders(moduleOptions);
},
inject: [NOTIFICATION_KIT_OPTIONS],
};

const allProviders = [asyncOptionsProvider, ...asyncProviders, providersFactory];
const exports = allProviders.map((p) =>
typeof p === "object" && "provide" in p ? p.provide : p,
);

return {
global: true,
module: NotificationKitModule,
imports: options.imports || [],
controllers: [], // Controllers disabled in async mode for simplicity
providers: allProviders,
exports,
};
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

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

registerAsync() does not actually register the NotificationKit providers (senders/repository/id generator/datetime/service). The providersFactory returns an array of providers, but Nest will not auto-register providers returned from another provider, so NOTIFICATION_SERVICE (and other tokens from createNotificationKitProviders) will be missing in async mode. Refactor async registration to create the same providers as register() (e.g., providers whose useFactory reads from NOTIFICATION_KIT_OPTIONS, plus the NOTIFICATION_SERVICE factory), and ensure the exported tokens include NOTIFICATION_SERVICE and related dependencies.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI commented Mar 9, 2026

@y-aithnini I've opened a new pull request, #7, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Contributor

@Zaiidmo Zaiidmo left a comment

Choose a reason for hiding this comment

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

Fix the merging cinflicts

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.

4 participants