diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..d226e4d --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,24 @@ +export const DEFAULT_API_BASE_URL = "https://api.novu.co"; +export const CHARACTER_LIMIT = 25_000; +export const DEFAULT_PAGE_LIMIT = 20; +export const MAX_PAGE_LIMIT = 100; + +export enum ResponseFormat { + MARKDOWN = "markdown", + JSON = "json", +} + +export function getApiBaseUrl(): string { + return process.env.NOVU_API_URL || DEFAULT_API_BASE_URL; +} + +export function getApiKey(): string { + const key = process.env.NOVU_SECRET_KEY; + if (!key) { + throw new Error( + "NOVU_SECRET_KEY environment variable is required. " + + "Get your API key from the Novu Dashboard: https://dashboard.novu.co" + ); + } + return key; +} diff --git a/src/schemas/common.ts b/src/schemas/common.ts new file mode 100644 index 0000000..6144ff7 --- /dev/null +++ b/src/schemas/common.ts @@ -0,0 +1,52 @@ +import { z } from "zod"; +import { + ResponseFormat, + MAX_PAGE_LIMIT, + DEFAULT_PAGE_LIMIT, +} from "../constants.js"; + +/** Offset-based pagination for v1 API */ +export const PaginationV1Schema = { + limit: z + .number() + .int() + .min(1) + .max(MAX_PAGE_LIMIT) + .default(DEFAULT_PAGE_LIMIT) + .describe("Maximum number of results to return (1-100)"), + offset: z + .number() + .int() + .min(0) + .default(0) + .describe("Number of results to skip for pagination"), +}; + +/** Cursor-based pagination for v2 API (Topics) */ +export const PaginationV2Schema = { + limit: z + .number() + .int() + .min(1) + .max(MAX_PAGE_LIMIT) + .default(DEFAULT_PAGE_LIMIT) + .describe("Maximum number of results to return (1-100)"), + after: z + .string() + .optional() + .describe("Cursor for fetching the next page of results"), + before: z + .string() + .optional() + .describe("Cursor for fetching the previous page of results"), +}; + +/** Response format param */ +export const ResponseFormatSchema = { + response_format: z + .nativeEnum(ResponseFormat) + .default(ResponseFormat.JSON) + .describe( + "Output format: 'json' for structured data or 'markdown' for human-readable" + ), +}; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..8a60e80 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,66 @@ +/** Standard v1 paginated list response wrapper */ +export interface PaginatedListV1 { + data: T[]; + totalCount: number; + pageSize: number; + page: number; + hasMore: boolean; +} + +/** Standard v2 cursor-based paginated response (used by Topics) */ +export interface PaginatedListV2 { + data: T[]; + next: string | null; + previous: string | null; + totalCount: number; + totalCountCapped: boolean; +} + +/** Subscriber shape from Novu API */ +export interface Subscriber { + id?: string; + subscriberId?: string; + firstName?: string | null; + lastName?: string | null; + email?: string | null; + phone?: string | null; + avatar?: string | null; + locale?: string | null; + timezone?: string | null; + data?: Record | null; + isOnline?: boolean | null; + lastOnlineAt?: string | null; + createdAt?: string; + updatedAt?: string; +} + +/** Topic shape */ +export interface Topic { + _id: string; + key: string; + name?: string; + createdAt?: string; + updatedAt?: string; +} + +/** Workflow shape */ +export interface Workflow { + id?: string; + name?: string; + description?: string; + active?: boolean; + draft?: boolean; + tags?: string[]; + steps?: unknown[]; + triggers?: unknown[]; + createdAt?: string; + updatedAt?: string; +} + +/** Trigger event response */ +export interface TriggerResponse { + acknowledged: boolean; + status: string; + error?: string[]; + transactionId?: string; +}