Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
098439d
chore(api-service, worker, ws): upgrade to nest js v11
scopsy Apr 10, 2026
321f3ae
Use string types for enum ApiProperty metadata
scopsy Apr 10, 2026
2c609cf
fix:
scopsy Apr 10, 2026
e6b4846
fix(api): unblock novu-v2 e2e (mocha flags, setup, swagger, testing d…
cursoragent Apr 10, 2026
18f794e
Update .source
scopsy Apr 11, 2026
e6e7bab
Merge branch 'next' into upgrade-nestjs-version
scopsy Apr 11, 2026
59a996a
כןס
scopsy Apr 11, 2026
a7a5694
Use static imports for ee-billing in e2e tests
scopsy Apr 11, 2026
b7db4e1
Condense EE billing imports and trim whitespace
scopsy Apr 11, 2026
fc46706
Update create-environment-request.dto.ts
scopsy Apr 11, 2026
211622f
Test config, forwardRef, and spec cleanups
scopsy Apr 11, 2026
d4879be
Update run-novu-v2-e2e-shard.cjs
scopsy Apr 11, 2026
ead9f70
fix(api, application-generic): restore api unit tests and preference …
cursoragent Apr 11, 2026
11a746e
chore(source): sync enterprise submodule with next (017a588)
cursoragent Apr 11, 2026
f1949f2
chore(source): point enterprise submodule at KeylessStrategy Nest 11 fix
cursoragent Apr 11, 2026
1441066
fix(api-service,ws,worker,shared): CORS delegate typing, safe WS toke…
cursoragent Apr 11, 2026
8b72d5e
Refactor internal-sdk models and request params
scopsy Apr 11, 2026
241780d
Introduce creation source enums; make source optional
scopsy Apr 11, 2026
7278941
Update ui-schema-property.dto.ts
scopsy Apr 11, 2026
ae73bb1
Add UiComponentEnum, export and disable enum patch
scopsy Apr 11, 2026
d6a6571
Update uischemaproperty.ts
scopsy Apr 11, 2026
b1d9ee0
Refactor preferences, WS token & Swagger enums
scopsy Apr 11, 2026
5f06329
Update swagger.controller.ts
scopsy Apr 11, 2026
ba7ec04
fix
scopsy Apr 11, 2026
77c967b
Merge branch 'next' into upgrade-nestjs-version
scopsy Apr 11, 2026
83110fb
Update run-novu-v2-e2e-shard.cjs
scopsy Apr 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .source
24 changes: 12 additions & 12 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@
"@aws-sdk/client-secrets-manager": "^3.971.0",
"@godaddy/terminus": "^4.12.1",
"@google-cloud/storage": "^6.2.3",
"@nestjs/axios": "3.0.3",
"@nestjs/common": "10.4.18",
"@nestjs/core": "10.4.18",
"@nestjs/jwt": "10.2.0",
"@nestjs/passport": "10.0.3",
"@nestjs/platform-express": "10.4.18",
"@nestjs/swagger": "7.4.0",
"@nestjs/terminus": "10.2.3",
"@nestjs/throttler": "6.2.1",
"@nestjs/axios": "4.0.1",
"@nestjs/common": "11.1.18",
"@nestjs/core": "11.1.18",
"@nestjs/jwt": "11.0.2",
"@nestjs/passport": "11.0.5",
"@nestjs/platform-express": "11.1.18",
"@nestjs/swagger": "11.2.6",
"@nestjs/terminus": "11.1.1",
"@nestjs/throttler": "6.5.0",
Comment on lines +49 to +57
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== nest-raven usage in apps/api =="
rg -n -C2 "from ['\"]nest-raven['\"]|\\bRaven(Module|Interceptor)\\b" apps/api || true

echo
python - <<'PY'
import json, pathlib
pkg = json.loads(pathlib.Path("apps/api/package.json").read_text())
for name in ("@nestjs/common", "@nestjs/core", "nest-raven", "@sentry/nestjs"):
    value = pkg.get("dependencies", {}).get(name) or pkg.get("devDependencies", {}).get(name)
    print(f"{name}: {value}")
PY

echo
npm view "nest-raven@10.1.0" peerDependencies --json
npm view "@sentry/nestjs@8.49.0" peerDependencies --json

Repository: novuhq/novu

Length of output: 1060


Remove the unused nest-raven dependency; it doesn't support NestJS 11.

The nest-raven@10.1.0 package has a peer dependency constraint of @nestjs/common@^10.0.0, which excludes NestJS 11. Since nest-raven is not imported anywhere in the codebase, it should be removed to avoid a broken peer dependency after this upgrade.

Additionally, @sentry/nestjs@8.49.0 also does not yet support NestJS 11 (max supported is v10). Verify that Sentry integration is either disabled or plan a migration to a compatible version before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/package.json` around lines 49 - 57, Remove the unused nest-raven
dependency from apps/api/package.json (delete the "nest-raven@10.1.0" entry)
because it requires `@nestjs/common` v10 and will break peer deps after upgrading
to NestJS 11; also check for any imports/usages of nest-raven in the codebase
(search for "nest-raven") and delete them if present. For Sentry, locate any
references to "@sentry/nestjs" (package.json and any imports like SentryModule
or SentryService) and either disable the Sentry integration or plan/make a
migration to a version that supports NestJS 11 before merging. Ensure
package.json and lockfile are updated (run install) after removing the
dependency.

"@novu/api": "workspace:*",
"@novu/application-generic": "workspace:*",
"@novu/dal": "workspace:*",
Expand Down Expand Up @@ -129,9 +129,9 @@
},
"devDependencies": {
"@faker-js/faker": "^6.0.0",
"@nestjs/cli": "10.4.5",
"@nestjs/schematics": "10.1.4",
"@nestjs/testing": "10.4.18",
"@nestjs/cli": "11.0.17",
"@nestjs/schematics": "11.0.10",
"@nestjs/testing": "11.1.18",
"@stoplight/spectral-cli": "^6.15.0",
"@swc-node/register": "1.10.10",
"@types/async": "^3.2.1",
Expand Down
2 changes: 1 addition & 1 deletion apps/api/scripts/generate-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/
import fs from 'node:fs';
import path from 'node:path';
import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins';
import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins/plugin-metadata-generator';
import { ReadonlyVisitor } from '@nestjs/swagger/dist/plugin';

const tsconfigPath = 'tsconfig.build.json';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class CommunityUserAuthGuard extends AuthGuard([PassportStrategyEnum.JWT,
this.logger.setContext(this.constructor.name);
}

getAuthenticateOptions(context: ExecutionContext): IAuthModuleOptions<any> {
getAuthenticateOptions(context: ExecutionContext): IAuthModuleOptions {
const request = context.switchToHttp().getRequest();
const authorizationHeader = request.headers.authorization;

Expand Down
31 changes: 16 additions & 15 deletions apps/api/src/app/auth/services/passport/apikey.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,25 @@ export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
private readonly inMemoryLRUCacheService: InMemoryLRUCacheService
) {
super(
{ header: HttpRequestHeaderKeysEnum.AUTHORIZATION, prefix: `${ApiAuthSchemeEnum.API_KEY} ` },
true,
async (apikey: string, verified: (err: Error | null, user?: UserSessionData | false) => void) => {
try {
const user = await this.validateApiKey(apikey);
{
header: HttpRequestHeaderKeysEnum.AUTHORIZATION,
prefix: `${ApiAuthSchemeEnum.API_KEY} `,
},
false
);
}

if (!user) {
return verified(null, false);
}
async validate(apiKey: string): Promise<UserSessionData | false> {
const user = await this.validateApiKey(apiKey);

addNewRelicTraceAttributes(user);
if (!user) {

return verified(null, user);
} catch (err) {
return verified(err as Error, false);
}
}
);
return false;
}

addNewRelicTraceAttributes(user);

return user;
}

private async validateApiKey(apiKey: string): Promise<UserSessionData | null> {
Expand Down
7 changes: 3 additions & 4 deletions apps/api/src/app/contexts/dtos/create-context-request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsValidContextData } from '@novu/application-generic';
import { CONTEXT_IDENTIFIER_REGEX, ContextData, ContextId, ContextType } from '@novu/shared';
import { IsDefined, IsOptional, IsString, Matches, MaxLength, MinLength } from 'class-validator';
Expand Down Expand Up @@ -37,11 +37,10 @@ export class CreateContextRequestDto {
})
id: ContextId;

@ApiProperty({
@ApiPropertyOptional({
description: 'Optional custom data to associate with this context.',
example: { tenantName: 'Acme Corp', region: 'us-east-1', settings: { theme: 'dark' } },
required: false,
type: 'object',
type: Object,
additionalProperties: true,
})
@IsOptional()
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/app/contexts/dtos/update-context-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ export class UpdateContextRequestDto {
@ApiProperty({
description: 'Custom data to associate with this context. Replaces existing data.',
example: { tenantName: 'Acme Corp', region: 'us-east-1', settings: { theme: 'dark' } },
required: true,
type: 'object',
type: Object,
additionalProperties: true,
})
@IsDefined()
Expand Down
17 changes: 6 additions & 11 deletions apps/api/src/app/events/dtos/trigger-event-request.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,13 @@ export class WorkflowToStepControlValuesDto {
* @type {Record<stepId, Data>}
* @optional
*/
@ApiProperty({
@ApiPropertyOptional({
description: 'A mapping of step IDs to their corresponding data.',
type: 'object',
type: Object,
additionalProperties: {
type: 'object',
additionalProperties: true,
},
required: false,
})
steps?: Record<string, Record<string, unknown>>;
}
Expand Down Expand Up @@ -229,12 +228,11 @@ export class TriggerEventRequestDto {
@IsDefined()
name: string;

@ApiProperty({
@ApiPropertyOptional({
description: `The payload object is used to pass additional custom information that could be
used to render the workflow, or perform routing rules based on it.
This data will also be available when fetching the notifications feed from the API to display certain parts of the UI.`,
type: 'object',
required: false,
type: Object,
additionalProperties: true,
example: {
comment_id: 'string',
Expand Down Expand Up @@ -262,7 +260,6 @@ export class TriggerEventRequestDto {
},
},
type: TriggerOverrides,
required: false,
})
@IsObject()
@IsOptional()
Expand Down Expand Up @@ -314,29 +311,27 @@ export class TriggerEventRequestDto {
@IsOptional()
transactionId?: string;

@ApiProperty({
@ApiPropertyOptional({
description: `It is used to display the Avatar of the provided actor's subscriber id or actor object.
If a new actor object is provided, we will create a new subscriber in our system`,
oneOf: [
{ type: 'string', description: 'Unique identifier of a subscriber in your systems' },
{ $ref: getSchemaPath(SubscriberPayloadDto) },
],
required: false,
})
@IsOptional()
@ValidateIf((_, value) => typeof value !== 'string')
@ValidateNested()
@Type(() => SubscriberPayloadDto)
actor?: TriggerRecipientSubscriber;

@ApiProperty({
@ApiPropertyOptional({
description: `It is used to specify a tenant context during trigger event.
Existing tenants will be updated with the provided details.`,
oneOf: [
{ type: 'string', description: 'Unique identifier of a tenant in your system' },
{ $ref: getSchemaPath(TenantPayloadDto) },
],
required: false,
})
@IsOptional()
@ValidateIf((_, value) => typeof value !== 'string')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ export class TriggerEventToAllRequestDto {
text: 'string',
},
},
type: 'object',
type: Object,
description: `The payload object is used to pass additional information that
could be used to render the template, or perform routing rules based on it.
For In-App channel, payload data are also available in <Inbox />`,
required: true,
additionalProperties: true,
})
@IsObject()
Expand All @@ -45,7 +44,6 @@ export class TriggerEventToAllRequestDto {
type: 'object',
additionalProperties: true,
},
required: false,
})
@IsObject()
@IsOptional()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger';
import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath, type ApiPropertyOptions } from '@nestjs/swagger';
import { ChannelTypeEnum } from '@novu/shared';
import { Type } from 'class-transformer';
import { IsOptional, IsString, ValidateNested } from 'class-validator';
Expand All @@ -23,7 +23,7 @@ export class GenerateLayoutPreviewResponseDto {

@ApiPropertyOptional({
description: 'The payload schema that was used to generate the preview payload example',
type: 'object',
type: Object,
nullable: true,
additionalProperties: true,
})
Expand All @@ -32,7 +32,7 @@ export class GenerateLayoutPreviewResponseDto {

@ApiProperty({
description: 'Preview result',
type: 'object',
type: Object,
oneOf: [
{
properties: {
Expand All @@ -41,7 +41,7 @@ export class GenerateLayoutPreviewResponseDto {
},
},
],
})
} as ApiPropertyOptions)
result: {
type: ChannelTypeEnum.EMAIL;
preview?: EmailLayoutRenderOutput;
Expand Down
12 changes: 4 additions & 8 deletions apps/api/src/app/notifications/dtos/activities-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,7 @@ export class ActivityNotificationJobResponseDto {

@ApiPropertyOptional({
description: 'Optional context object for additional error details.',
type: 'object',
required: false,
type: Object,
additionalProperties: true,
example: {
workflowId: 'some_wf_id',
Expand Down Expand Up @@ -430,8 +429,7 @@ export class ActivityNotificationResponseDto {

@ApiPropertyOptional({
description: 'Payload of the notification',
type: 'object',
required: false,
type: Object,
additionalProperties: true,
})
payload?: Record<string, unknown>; // Added to align with NotificationEntity
Expand All @@ -444,16 +442,14 @@ export class ActivityNotificationResponseDto {

@ApiPropertyOptional({
description: 'Controls associated with the notification',
type: 'object',
required: false,
type: Object,
additionalProperties: true,
})
controls?: Record<string, unknown>; // Added to align with NotificationEntity

@ApiPropertyOptional({
description: 'To field for subscriber definition',
type: 'object',
required: false,
type: Object,
additionalProperties: true,
})
to?: Record<string, unknown>; // Added to align with NotificationEntity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ export class SubscriptionPreferenceDto {

@ApiPropertyOptional({
description: 'Optional condition using JSON Logic rules',
required: false,
type: 'object',
type: Object,
additionalProperties: true,
example: { and: [{ '===': [{ var: 'tier' }, 'premium'] }] },
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,17 @@ export class TopicSubscriberIdentifierDto {
}

export class BasePreferenceDto {
@ApiProperty({
@ApiPropertyOptional({
description: 'Whether the preference is enabled. Used when condition is not provided.',
required: false,
type: Boolean,
example: true,
})
@IsOptional()
enabled?: boolean;
Comment thread
scopsy marked this conversation as resolved.

@ApiProperty({
@ApiPropertyOptional({
description: 'Optional condition using JSON Logic rules',
required: false,
type: 'object',
type: Object,
additionalProperties: true,
example: { and: [{ '===': [{ var: 'tier' }, 'premium'] }] },
})
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/app/shared/framework/response.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface Response<T> {
@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context, next: CallHandler): Observable<Response<T>> {
if (context.getType() === 'graphql') return next.handle();
if ((context.getType() as string) === 'graphql') return next.handle();

return next.handle().pipe(
map((data) => {
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/app/shared/framework/user.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export interface SubscriberSession extends SubscriberEntity {
}

export const SubscriberSession = createParamDecorator((data, ctx) => {
const req = ctx.getType() === 'graphql' ? ctx.getArgs()[2].req : ctx.switchToHttp().getRequest();
const req =
(ctx.getType() as string) === 'graphql' ? ctx.getArgs()[2].req : ctx.switchToHttp().getRequest();

if (req.user) {
return req.user;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, type ApiPropertyOptions } from '@nestjs/swagger';
import { InboxNotificationDto } from '../../inbox/dtos/inbox-notification.dto';
import type { NotificationFilter } from '../../inbox/utils/types';

Expand All @@ -17,7 +17,7 @@ export class GetSubscriberNotificationsResponseDto {

@ApiProperty({
description: 'The filter applied to the notifications',
type: 'object',
})
type: Object,
} as ApiPropertyOptions)
filter: NotificationFilter;
}
12 changes: 5 additions & 7 deletions apps/api/src/app/widgets/dtos/feeds-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,29 +207,27 @@ export class NotificationFeedItemDto implements INotificationDto {
})
status: 'sent' | 'error' | 'warning';

@ApiProperty({
@ApiPropertyOptional({
description: 'The payload that was used to send the notification trigger.',
type: 'object',
type: Object,
additionalProperties: true,
required: false,
example: { key: 'value' },
})
payload?: Record<string, unknown>;

@ApiPropertyOptional({
description: 'The data sent with the notification.',
type: 'object',
type: Object,
nullable: true,
example: { key: 'value' },
additionalProperties: true,
})
data?: Record<string, unknown> | null;

@ApiProperty({
@ApiPropertyOptional({
description: 'Provider-specific overrides used when triggering the notification.',
type: 'object',
type: Object,
additionalProperties: true,
required: false,
example: { overrideKey: 'overrideValue' },
})
overrides?: Record<string, unknown>;
Expand Down
Loading
Loading