Skip to content

infrastructure adapters#1

Closed
y-aithnini wants to merge 1 commit intomasterfrom
feature/infrastructure-adapters
Closed

infrastructure adapters#1
y-aithnini wants to merge 1 commit intomasterfrom
feature/infrastructure-adapters

Conversation

@y-aithnini
Copy link
Contributor

@y-aithnini y-aithnini commented Feb 23, 2026

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?

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 introduces an infrastructure layer with concrete implementations of the core notification ports (senders, repositories, and utility providers), plus supporting documentation and config updates.

Changes:

  • Add multiple infra senders (email/SMS/push) with lazy-loaded third-party providers.
  • Add infra repositories (Mongoose + in-memory) and utility providers (ID, datetime, templates, events).
  • Update TypeScript/lib config and package metadata/docs to support the new infra layer.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
tsconfig.json Adds DOM lib typings (needed for fetch-based infra).
package.json Adds peerDependenciesMeta for optional infra provider dependencies.
src/infra/index.ts New infra barrel entrypoint.
src/infra/README.md Extensive infra usage docs and examples.
src/infra/senders/index.ts Sender barrel exports.
src/infra/senders/email/nodemailer.sender.ts Nodemailer email sender implementation.
src/infra/senders/sms/twilio.sender.ts Twilio SMS sender implementation.
src/infra/senders/sms/aws-sns.sender.ts AWS SNS SMS sender implementation.
src/infra/senders/sms/vonage.sender.ts Vonage SMS sender implementation.
src/infra/senders/push/firebase.sender.ts FCM push sender implementation.
src/infra/senders/push/onesignal.sender.ts OneSignal push sender implementation using fetch.
src/infra/senders/push/aws-sns-push.sender.ts AWS SNS push sender implementation.
src/infra/repositories/index.ts Repository barrel exports (mongoose + in-memory).
src/infra/repositories/mongoose/notification.schema.ts Mongoose schema definition for notifications.
src/infra/repositories/mongoose/mongoose.repository.ts Mongoose-backed notification repository.
src/infra/repositories/in-memory/in-memory.repository.ts In-memory repository implementation.
src/infra/providers/index.ts Provider barrel exports.
src/infra/providers/datetime.provider.ts Date/time provider implementation.
src/infra/providers/id-generator.provider.ts ID generator implementations (UUID/ObjectId/NanoId).
src/infra/providers/template.provider.ts Template engines (Handlebars + simple replacement).
src/infra/providers/event-emitter.provider.ts Event emitters (in-memory + console).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if (this.handlebars) {
return this.handlebars;
}

Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

import("handlebars") is used without the @ts-expect-error pattern used elsewhere for optional peer deps. If handlebars isn’t installed in the build environment, TypeScript will fail module resolution. To keep this adapter optional and consistent with the other infra adapters, either add the suppression here or add handlebars (and typings) as a dev dependency while listing it as an optional peer dependency for consumers.

Suggested change
// @ts-expect-error - optional peer dependency 'handlebars' may not be installed

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 Feb 23, 2026

Choose a reason for hiding this comment

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

The README examples import adapters from @ciscode/notification-kit/infra, but the package’s exports map currently only exposes . (and tsup only builds src/index.ts). As-is, this subpath won’t resolve for consumers. Either update the package build/exports to publish an ./infra entrypoint, or adjust the docs to match the actual public API surface.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +47
this.model = this.connection.model<NotificationDocument>(
"Notification",
schema,
this.collectionName,
);
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

connection.model("Notification", schema, this.collectionName) uses a fixed model name. If multiple MongooseNotificationRepository instances share the same connection but use different collectionName values, Mongoose will reuse the first compiled model (or throw an overwrite error), leading to queries hitting the wrong collection. Consider incorporating collectionName into the model name (or calling connection.model(name) without a schema when it already exists).

Copilot uses AI. Check for mistakes.
if (!_recipient.email) {
return {
success: false,
notificationId: "",
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

In the missing-email branch, notificationId is returned as an empty string. Even if the sender can’t know the notification entity id, returning "" makes it harder to correlate failures (and differs from the other senders). At minimum this should be consistent (e.g., return _recipient.id like other branches), and ideally the notification id should be passed in or set by the core service.

Suggested change
notificationId: "",
notificationId: _recipient.id,

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +14
platformApplicationArn: string;
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

AwsSnsPushConfig includes platformApplicationArn, but it’s never used. This is misleading for consumers and may indicate missing functionality (e.g., creating endpoints or scoping publish). Either remove it from the config or incorporate it into the implementation (and update the README example accordingly).

Suggested change
platformApplicationArn: string;
}
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +71
return {
success: true,
notificationId: _recipient.id,
providerMessageId: message["message-id"],
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

notificationId is returned as _recipient.id, which doesn’t align with the core NotificationResult contract (notification entity id). Since this sender doesn’t receive the notification id, the core service should set it (or the sender port should accept the notification id / Notification).

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +18
// Helper to get Schema type at runtime (for Mongoose schema definitions)
const getSchemaTypes = () => {
try {
// @ts-expect-error - mongoose is an optional peer dependency
const mongoose = require("mongoose");
return mongoose.Schema.Types;
} catch {
return { Mixed: {} };
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

getSchemaTypes() uses require("mongoose"). In the ESM build (dist/index.mjs), require is not available, so this will always fall into the catch and return a dummy Mixed: {} even when mongoose is installed. That will make the schema definition invalid at runtime. Suggest using createRequire(import.meta.url) in ESM, or better: avoid this helper entirely and obtain Schema.Types from the actual mongoose instance already available via the connection when building the schema.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +6
// MongoDB/Mongoose repository
export * from "./mongoose/notification.schema";
export * from "./mongoose/mongoose.repository";

// In-memory repository
export * from "./in-memory/in-memory.repository";
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

Exporting the mongoose-backed repository from this barrel means importing @ciscode/notification-kit/infra will pull in types that reference mongoose, forcing consumers to install it even if they only want in-memory adapters. To keep mongoose truly optional, consider splitting exports (e.g., infra/repositories/in-memory vs infra/repositories/mongoose) or using conditional exports so the base infra entrypoint doesn’t reference mongoose types.

Suggested change
// MongoDB/Mongoose repository
export * from "./mongoose/notification.schema";
export * from "./mongoose/mongoose.repository";
// In-memory repository
export * from "./in-memory/in-memory.repository";
// In-memory repository
export * from "./in-memory/in-memory.repository";
// NOTE:
// Mongoose-backed repositories and schemas are intentionally *not* re-exported
// from this barrel to keep `mongoose` optional for consumers that only use
// in-memory adapters. Import Mongoose-specific modules from their dedicated
// paths (e.g. "./mongoose/notification.schema" or "./mongoose/mongoose.repository").

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +13

/**
* ID generator using UUID v4
*/
export class UuidGenerator implements IIdGenerator {
generate(): string {
// Simple UUID v4 implementation
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

UuidGenerator claims to generate UUID v4, but it relies on Math.random(), which is not cryptographically secure and can increase collision risk under load. Since the package targets Node >=20, consider using crypto.randomUUID() (or crypto.getRandomValues) for a standards-compliant UUID v4 implementation.

Suggested change
/**
* ID generator using UUID v4
*/
export class UuidGenerator implements IIdGenerator {
generate(): string {
// Simple UUID v4 implementation
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
import { randomUUID } from "crypto";
/**
* ID generator using UUID v4
*/
export class UuidGenerator implements IIdGenerator {
generate(): string {
// Use Node's built-in cryptographically secure UUID v4 generator
return randomUUID();

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +66
return {
success: true,
notificationId: _recipient.id,
providerMessageId: message.sid,
metadata: {
status: message.status,
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

All the returned NotificationResult.notificationId values are set to _recipient.id, but NotificationResult is defined as the notification ID (see core NotificationResult). Since send() only receives the recipient/content, these senders can’t know the notification entity id, so callers will get the wrong id on success. Suggestion: change the sender port to accept the notification id (or full Notification), or have NotificationService overwrite result.notificationId with notification.id before returning/storing it.

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
5 Security Hotspots
9.8% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@Zaiidmo Zaiidmo closed this Mar 4, 2026
@Zaiidmo Zaiidmo deleted the feature/infrastructure-adapters branch March 4, 2026 20:45
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