@mikemajara/notion-cms
Version:
A TypeScript library for using Notion as a headless CMS
686 lines (674 loc) • 27.4 kB
TypeScript
import { DatabaseObjectResponse, PageObjectResponse, PropertyItemObjectResponse, BlockObjectResponse, QueryDatabaseParameters } from '@notionhq/client/build/src/api-endpoints';
import { Client } from '@notionhq/client';
type NotionPropertyType = DatabaseObjectResponse["properties"][string]["type"];
interface DatabaseRecord {
id?: string;
[key: string]: any;
advanced?: {
id: string;
};
raw?: {
id: string;
properties: Record<string, any>;
};
}
interface AdvancedDatabaseRecord extends DatabaseRecord {
}
declare function generateTypes(databaseId: string, outputPath: string, token: string, force?: boolean): Promise<void>;
/**
* Process a Notion page into a record with layered access (simple, advanced, raw)
* @deprecated Use DatabaseService.getRecord() or DatabaseService.getDatabase() instead.
* @param page The Notion page object from the API
* @param fileManager Optional FileManager for file processing (deprecated, ignored)
* @returns A processed record with simple, advanced, and raw access
*/
declare function processNotionRecord(page: PageObjectResponse, fileManager?: any): DatabaseRecord;
/**
* Process multiple Notion pages into records with layered access
* @param pages An array of Notion page objects
* @returns An array of processed records with layered access
* @deprecated Use DatabaseService.processNotionRecords() for the unified processing method with proper file handling and layered access.
*/
declare function processNotionRecords(pages: PageObjectResponse[], fileManager?: any): DatabaseRecord[];
/**
* @deprecated Use processNotionRecord instead which provides advanced and raw access as well
*/
declare function simplifyNotionRecord(page: PageObjectResponse): DatabaseRecord;
/**
* @deprecated Use processNotionRecords instead which provides advanced and raw access as well
*/
declare function simplifyNotionRecords(pages: PageObjectResponse[]): DatabaseRecord[];
/**
* @deprecated Use processNotionRecord instead which provides simplified, advanced and raw access
*/
declare function advancedNotionRecord(page: PageObjectResponse): AdvancedDatabaseRecord;
/**
* @deprecated Use processNotionRecords instead which provides simplified, advanced and raw access
*/
declare function advancedNotionRecords(pages: PageObjectResponse[]): AdvancedDatabaseRecord[];
/**
* Generate types for multiple databases in a single file
* @param databaseIds Array of database IDs to generate types for
* @param outputPath Path where to generate the types file
* @param token Notion API token
* @param force Whether to force overwrite existing files
*/
declare function generateMultipleDatabaseTypes(databaseIds: string[], outputPath: string, token: string, force?: boolean): Promise<void>;
/**
* Configuration options for NotionCMS file management
*/
/**
* Configuration options for debug logging
*/
interface DebugConfig {
/**
* Enable/disable all logging
* @default false
*/
enabled?: boolean;
/**
* Log level - controls what gets logged
* @default "info"
*/
level?: "error" | "warn" | "info" | "debug";
}
/**
* Configuration options for file management
*/
interface FileConfig {
strategy: "direct" | "cache";
storage?: {
type: "local" | "s3-compatible";
path?: string;
endpoint?: string;
bucket?: string;
accessKey?: string;
secretKey?: string;
region?: string;
};
cache?: {
ttl: number;
maxSize: number;
};
}
interface NotionCMSConfig {
files?: FileConfig;
debug?: DebugConfig;
}
/**
* Interface for file information
*/
interface FileInfo {
name: string;
url: string;
type?: "external" | "file";
expiry_time?: string;
}
/**
* FileManager class that handles file processing based on strategy
*/
declare class FileManager {
private strategy;
constructor(config: NotionCMSConfig);
/**
* Check if file caching is enabled
*/
isCacheEnabled(): boolean;
/**
* Process a single file URL
*/
processFileUrl(url: string, fileName: string): Promise<string>;
/**
* Process an array of file information objects
*/
processFileInfoArray(files: FileInfo[]): Promise<FileInfo[]>;
/**
* Extract file URL from Notion file object
*/
extractFileUrl(file: any): string;
/**
* Create FileInfo from Notion file object
*/
createFileInfo(file: any): FileInfo;
}
type SortDirection = "ascending" | "descending";
type LogicalOperator = "and" | "or";
type NotionFieldType = "title" | "rich_text" | "number" | "select" | "multi_select" | "date" | "people" | "files" | "checkbox" | "url" | "email" | "phone_number" | "formula" | "relation" | "rollup" | "created_time" | "created_by" | "last_edited_time" | "last_edited_by" | "status" | "unique_id" | "verification" | "unknown";
/**
* Comprehensive operator mapping for all Notion field types
* Each field type has specific operators that make sense for that data type
*/
type OperatorMap = {
title: "equals" | "does_not_equal" | "contains" | "does_not_contain" | "starts_with" | "ends_with" | "is_empty" | "is_not_empty";
rich_text: "equals" | "does_not_equal" | "contains" | "does_not_contain" | "starts_with" | "ends_with" | "is_empty" | "is_not_empty";
url: "equals" | "does_not_equal" | "contains" | "does_not_contain" | "starts_with" | "ends_with" | "is_empty" | "is_not_empty";
email: "equals" | "does_not_equal" | "contains" | "does_not_contain" | "starts_with" | "ends_with" | "is_empty" | "is_not_empty";
phone_number: "equals" | "does_not_equal" | "contains" | "does_not_contain" | "starts_with" | "ends_with" | "is_empty" | "is_not_empty";
number: "equals" | "does_not_equal" | "greater_than" | "less_than" | "greater_than_or_equal_to" | "less_than_or_equal_to" | "is_empty" | "is_not_empty";
select: "equals" | "does_not_equal" | "is_empty" | "is_not_empty";
multi_select: "contains" | "does_not_contain" | "is_empty" | "is_not_empty";
status: "equals" | "does_not_equal" | "is_empty" | "is_not_empty";
date: "equals" | "before" | "after" | "on_or_before" | "on_or_after" | "is_empty" | "is_not_empty";
created_time: "equals" | "before" | "after" | "on_or_before" | "on_or_after";
last_edited_time: "equals" | "before" | "after" | "on_or_before" | "on_or_after";
checkbox: "equals";
people: "contains" | "does_not_contain" | "is_empty" | "is_not_empty";
relation: "contains" | "does_not_contain" | "is_empty" | "is_not_empty";
created_by: "contains" | "does_not_contain";
last_edited_by: "contains" | "does_not_contain";
files: "is_empty" | "is_not_empty";
formula: "equals" | "does_not_equal" | "contains" | "does_not_contain" | "greater_than" | "less_than" | "greater_than_or_equal_to" | "less_than_or_equal_to" | "is_empty" | "is_not_empty";
rollup: "equals" | "does_not_equal" | "contains" | "does_not_contain" | "greater_than" | "less_than" | "greater_than_or_equal_to" | "less_than_or_equal_to" | "is_empty" | "is_not_empty";
unique_id: "equals" | "does_not_equal" | "greater_than" | "less_than" | "greater_than_or_equal_to" | "less_than_or_equal_to";
verification: "equals" | "before" | "after" | "on_or_before" | "on_or_after";
unknown: "equals" | "does_not_equal" | "is_empty" | "is_not_empty";
};
/**
* Runtime version of OperatorMap for validation purposes
* This allows us to check valid operators at runtime
*/
declare const OPERATOR_MAP: Record<keyof OperatorMap, readonly string[]>;
/**
* Enhanced interface for database field metadata with full type constraints
* Supports all Notion field types with proper option constraints for select fields
*/
interface DatabaseFieldMetadata {
[fieldName: string]: {
type: Exclude<NotionFieldType, "select" | "multi_select">;
} | {
type: "select";
options: readonly string[];
} | {
type: "multi_select";
options: readonly string[];
};
}
/**
* Conditional type utilities for type-safe query building
*/
type FieldTypeFor<K extends keyof M, M extends DatabaseFieldMetadata> = M[K] extends {
type: infer T;
} ? T : never;
type OperatorsFor<K extends keyof M, M extends DatabaseFieldMetadata> = FieldTypeFor<K, M> extends keyof OperatorMap ? OperatorMap[FieldTypeFor<K, M>] : never;
type SelectOptionsFor<K extends keyof M, M extends DatabaseFieldMetadata> = M[K] extends {
type: "select";
options: readonly (infer U)[];
} ? U : M[K] extends {
type: "multi_select";
options: readonly (infer U)[];
} ? U : never;
type ValueTypeMap = {
title: string;
rich_text: string;
number: number;
select: string;
multi_select: string[];
date: Date | string;
people: string[];
files: Array<{
name: string;
url: string;
}>;
checkbox: boolean;
url: string;
email: string;
phone_number: string;
formula: any;
relation: string[];
rollup: any;
created_time: Date | string;
created_by: string;
last_edited_time: Date | string;
last_edited_by: string;
status: string;
unique_id: number;
unknown: any;
};
type ValueTypeFor<K extends keyof M, M extends DatabaseFieldMetadata, T extends DatabaseRecord = DatabaseRecord, O extends OperatorsFor<K, M> = OperatorsFor<K, M>> = O extends "is_empty" | "is_not_empty" ? any : FieldTypeFor<K, M> extends "select" ? SelectOptionsFor<K, M> : FieldTypeFor<K, M> extends "multi_select" ? SelectOptionsFor<K, M> : FieldTypeFor<K, M> extends "people" | "relation" ? O extends "contains" | "does_not_contain" ? string : ValueTypeMap[FieldTypeFor<K, M>] : FieldTypeFor<K, M> extends keyof ValueTypeMap ? ValueTypeMap[FieldTypeFor<K, M>] : any;
/**
* Type-safe filter condition with operator and value validation
*/
interface TypeSafeFilterCondition<K extends keyof M, M extends DatabaseFieldMetadata, T extends DatabaseRecord> {
property: K;
operator: OperatorsFor<K, M>;
value: ValueTypeFor<K, M, T>;
propertyType: FieldTypeFor<K, M>;
}
/**
* Generic filter condition for internal use
*/
interface FilterCondition {
property: string;
operator: string;
value: any;
propertyType?: string;
}
interface QueryResult<T extends DatabaseRecord> {
results: T[];
hasMore: boolean;
nextCursor: string | null;
}
declare class QueryBuilder<T extends DatabaseRecord, M extends DatabaseFieldMetadata = {}> implements PromiseLike<T[] | T | null> {
private client;
private databaseId;
private fieldTypes;
private filterConditions;
private logicalOperator;
private nestedFilters;
private sortOptions;
private pageLimit;
private startCursor?;
private singleMode;
private fileManager?;
private databaseService;
constructor(client: Client, databaseId: string, fieldTypes?: M, fileManager?: FileManager);
/**
* Type-safe filter method with perfect IntelliSense support
*
* Provides:
* 1. Field name suggestions from database metadata
* 2. Operator suggestions based on field type
* 3. Value type validation based on field type and select options
*
* @param property - Database field name (with IntelliSense suggestions)
* @param operator - Valid operator for the field type (with IntelliSense suggestions)
* @param value - Value matching the field type constraints
* @returns QueryBuilder for chaining
*/
filter<K extends keyof M & keyof T & string, O extends OperatorsFor<K, M>>(property: K, operator: O, value: ValueTypeFor<K, M, T, O>): QueryBuilder<T, M>;
/**
* Map our field types to Notion API property types
* @private
*/
private mapFieldTypeToNotionProperty;
/**
* Prepare filter value for Notion API
* @private
*/
private prepareFilterValue;
/**
* Add sorting to the query with type-safe field name suggestions
*
* Supports both single and multiple (nested) sorting. When multiple sorts are applied,
* the first sort takes precedence, then the second, and so on.
*
* All field types support sorting in Notion, including:
* - Text fields (title, rich_text, url, email, phone_number)
* - Number fields
* - Date fields (date, created_time, last_edited_time)
* - Select and multi-select fields
* - Checkbox fields
* - People and relation fields
* - Status fields
* - Formula and rollup fields
*
* @param property - Field name to sort by (with IntelliSense suggestions from database schema)
* @param direction - Sort direction: "ascending" (default) or "descending"
* @returns QueryBuilder for method chaining
*
* @example
* ```typescript
* // Single sort
* query(cms, databaseId)
* .sort("Created Date", "descending")
*
* // Multiple (nested) sorts - priority order matters
* query(cms, databaseId)
* .sort("Priority", "descending") // Primary sort
* .sort("Created Date", "ascending") // Secondary sort
* .sort("Name", "ascending") // Tertiary sort
*
* // Sort by different field types
* query(cms, databaseId)
* .sort("Environment", "ascending") // Select field
* .sort("Estimated Cost", "descending") // Number field
* .sort("Is Active", "descending") // Checkbox field
* ```
*/
sort(property: keyof M & keyof T & string, direction?: SortDirection): QueryBuilder<T, M>;
limit(limit: number): QueryBuilder<T, M>;
startAfter(cursor: string): QueryBuilder<T, M>;
single(): QueryBuilder<T, M>;
maybeSingle(): QueryBuilder<T, M>;
then<TResult1 = T[] | T | null, TResult2 = never>(onfulfilled?: ((value: T[] | T | null) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): PromiseLike<TResult1 | TResult2>;
/**
* Execute the query and return the results
* @private
*/
private execute;
paginate(pageSize?: number): Promise<QueryResult<T>>;
all(): Promise<T[]>;
/**
* Build the Notion API filter from the filter conditions
* @private
*/
private buildFilter;
/**
* Map our operator names to Notion API operator names
* @private
*/
private mapToNotionOperator;
/**
* Get field type from metadata for a given property
* This is used internally for type validation and filter building
*/
getFieldTypeForFilter(property: keyof T & string): NotionFieldType | undefined;
/**
* Type guard to validate if an operator is valid for a given field type
*/
isValidOperatorForField<K extends keyof M>(property: K, operator: string): operator is OperatorsFor<K, M>;
/**
* Validate that a value is appropriate for a given field type
*/
isValidValueForField<K extends keyof M>(property: K, value: any): value is ValueTypeFor<K, M, T>;
/**
* Type guard to validate if a sort property is valid in the field metadata
*/
private isValidSortField;
}
type NotionProperty = PropertyItemObjectResponse;
/**
* Content conversion service for transforming Notion blocks to Markdown and HTML
*/
/**
* Simplified representation of a Notion block
*/
interface SimpleBlock {
id: string;
type: string;
content: any;
children?: SimpleBlock[];
hasChildren: boolean;
}
/**
* Table block content structure
*/
interface TableBlockContent {
tableWidth: number;
hasColumnHeader: boolean;
hasRowHeader: boolean;
}
/**
* Table row cell content structure
*/
interface TableRowCell {
plainText: string;
richText: any[];
}
/**
* Table row block content structure
*/
interface TableRowBlockContent {
cells: TableRowCell[];
}
/**
* Simplified representation of a table block
*/
interface SimpleTableBlock extends SimpleBlock {
type: "table";
content: TableBlockContent;
children: SimpleTableRowBlock[];
}
/**
* Simplified representation of a table row block
*/
interface SimpleTableRowBlock extends SimpleBlock {
type: "table_row";
content: TableRowBlockContent;
}
/**
* Content conversion service for transforming Notion blocks to different formats
*/
declare class ContentConverter {
/**
* Convert page content to Markdown
* @param blocks Array of blocks to convert
* @returns Markdown string
*/
blocksToMarkdown(blocks: SimpleBlock[]): string;
/**
* Convert page content to HTML
* @param blocks Array of blocks to convert
* @returns HTML string
*/
blocksToHtml(blocks: SimpleBlock[]): string;
/**
* Process a group of blocks, handling list grouping and proper spacing
* @param blocks Array of blocks to process
* @param context Current list context
* @returns Processed markdown string
*/
private processBlocksGroup;
/**
* Check if a block type is a list item
*/
private isListItem;
/**
* Extract consecutive list items of the same type
*/
private extractListGroup;
/**
* Process a group of list items with proper numbering and indentation
*/
private processListGroup;
/**
* Convert a single block to Markdown
* @param block The block to convert
* @param context Current list context and nesting information
* @returns Markdown string
*/
private blockToMarkdown;
/**
* Convert a table block to Markdown format
* @param tableBlock The table block to convert
* @param baseIndent Base indentation to apply
* @returns Markdown string
*/
private tableToMarkdown;
/**
* Convert a single block to HTML
* @param block The block to convert
* @param level Nesting level for recursive calls
* @returns HTML string
*/
private blockToHtml;
/**
* Convert a table block to HTML format
* @param tableBlock The table block to convert
* @returns HTML string
*/
private tableToHtml;
/**
* Extract plain text from rich text objects
* @param richText Array of rich text objects
* @returns Plain text string
*/
private extractRichText;
}
/**
* Block processing service for converting Notion blocks to simplified formats
*/
declare class BlockProcessor {
private fileManager;
constructor(fileManager: FileManager);
/**
* Convert a Notion block to a simplified format with file processing
* @param block The Notion block to simplify
* @returns A simplified representation of the block
*/
simplifyBlockAsync(block: BlockObjectResponse): Promise<SimpleBlock>;
/**
* Extract the content from a Notion block based on its type with file processing
* @param block The Notion block to extract content from
* @returns The extracted content in a simplified format
*/
extractBlockContentAsync(block: BlockObjectResponse): Promise<any>;
/**
* Convert a Notion block to a simplified format (legacy sync method)
* @param block The Notion block to simplify
* @returns A simplified representation of the block
*/
simplifyBlock(block: BlockObjectResponse): SimpleBlock;
/**
* Extract the content from a Notion block based on its type (legacy sync method)
* @param block The Notion block to extract content from
* @returns The extracted content in a simplified format
*/
extractBlockContent(block: BlockObjectResponse): any;
/**
* Extract plain text from rich text objects
* @param richText Array of rich text objects
* @returns Plain text string
*/
private extractRichText;
}
/**
* Page content service for retrieving and processing Notion page content
*/
declare class PageContentService {
private client;
private blockProcessor;
constructor(client: Client, blockProcessor: BlockProcessor);
/**
* Retrieve the content blocks of a Notion page
* @param pageId The ID of the Notion page
* @param recursive Whether to recursively fetch nested blocks (default: true)
* @returns A promise that resolves to an array of simplified blocks
*/
getPageContent(pageId: string, recursive?: boolean): Promise<SimpleBlock[]>;
/**
* Fetch blocks for a specific page or block
* @param blockId The ID of the page or block to fetch children for
* @returns A promise that resolves to an array of simplified blocks
*/
private getBlocks;
}
interface QueryOptions {
filter?: QueryDatabaseParameters["filter"];
sorts?: QueryDatabaseParameters["sorts"];
pageSize?: number;
startCursor?: string;
}
/**
* Database service for handling all Notion database operations
*/
declare class DatabaseService {
private client;
private fileManager;
constructor(client: Client, fileManager: FileManager);
/**
* Creates a query builder for a Notion database with type safety
* @param databaseId The ID of the Notion database
* @param fieldMetadata Optional metadata about field types for type-safe operations
* @returns A query builder instance for the specified database
*/
query<T extends DatabaseRecord, M extends DatabaseFieldMetadata = {}>(databaseId: string, fieldMetadata?: M): QueryBuilder<T, M>;
/**
* Get all records from a Notion database with pagination, filtering, and sorting
* Records include all access levels: simple, advanced, and raw
* @param databaseId The ID of the Notion database
* @param options Query options for filtering, sorting, and pagination
* @returns A promise that resolves to an array of records with pagination metadata
*/
getDatabase<T extends DatabaseRecord>(databaseId: string, options?: QueryOptions): Promise<{
results: T[];
nextCursor: string | null;
hasMore: boolean;
}>;
/**
* Get a single record from a database by its ID
* Record includes all access levels: simple, advanced, and raw
* @param pageId The ID of the Notion page/record
* @returns A promise that resolves to the record
*/
getRecord<T extends DatabaseRecord>(pageId: string): Promise<T>;
/**
* Get all records from a database with automatic pagination
* Records include all access levels: simple, advanced, and raw
* @param databaseId The ID of the Notion database
* @param options Query options for filtering and sorting
* @returns A promise that resolves to all records from the database
*/
getAllDatabaseRecords<T extends DatabaseRecord>(databaseId: string, options?: Omit<QueryOptions, "startCursor" | "pageSize">): Promise<T[]>;
/**
* Create a typed filter for a database query
* @param property The property name to filter on
* @param type The type of filter to apply
* @param value The filter value
* @returns A properly formatted filter object
* @deprecated This method uses old filter patterns. Use the new QueryBuilder.filter(property, operator, value) method instead.
* The new method provides type-safe field names, operators, and values with IntelliSense support.
*/
createFilter(property: string, type: string, value: any): QueryDatabaseParameters["filter"];
/**
* Async property value extraction for simple layer API
* @param property Property item from Notion API
* @returns Processed property value for simple layer
*/
getPropertyValue(property: PropertyItemObjectResponse): Promise<any>;
/**
* Async advanced property value extraction for advanced layer API
* @param property Property item from Notion API
* @returns Processed advanced property value with complete metadata
*/
getPropertyValueAdvanced(property: PropertyItemObjectResponse): Promise<any>;
/**
* Unified method to process a single Notion page into a DatabaseRecord
* This is the single source of truth for record processing across the library
* @param page The Notion page object from the API
* @returns A processed record with simple, advanced, and raw access layers
*/
processNotionRecord<T extends DatabaseRecord = DatabaseRecord>(page: PageObjectResponse): Promise<T>;
/**
* Unified method to process multiple Notion pages into DatabaseRecords
* This is the single source of truth for batch record processing across the library
* @param pages Array of Notion page objects from the API
* @returns Array of processed records with simple, advanced, and raw access layers
*/
processNotionRecords<T extends DatabaseRecord = DatabaseRecord>(pages: PageObjectResponse[]): Promise<T[]>;
}
declare class NotionCMS {
private client;
private config;
private fileManager;
private contentConverter;
private blockProcessor;
private pageContentService;
private databaseService;
constructor(token: string, config?: NotionCMSConfig);
/**
* Convert page content to Markdown
* @param blocks Array of blocks to convert
* @returns Markdown string
*/
blocksToMarkdown(blocks: SimpleBlock[]): string;
/**
* Convert page content to HTML
* @param blocks Array of blocks to convert
* @returns HTML string
*/
blocksToHtml(blocks: SimpleBlock[]): string;
/**
* Creates a query builder for a Notion database with type safety
* This is the recommended way to interact with databases
* @param databaseId The ID of the Notion database
* @param fieldMetadata Optional metadata about field types for type-safe operations
* @returns A query builder instance for the specified database
*/
query<T extends DatabaseRecord, M extends DatabaseFieldMetadata = {}>(databaseId: string, fieldMetadata?: M): QueryBuilder<T, M>;
/**
* Get a single record from a database by its ID
* Record includes all access levels: simple, advanced, and raw
* @param pageId The ID of the Notion page/record
* @returns A promise that resolves to the record
*/
getRecord<T extends DatabaseRecord>(pageId: string): Promise<T>;
/**
* Retrieve the content blocks of a Notion page
* @param pageId The ID of the Notion page
* @param recursive Whether to recursively fetch nested blocks (default: true)
* @returns A promise that resolves to an array of simplified blocks
*/
getPageContent(pageId: string, recursive?: boolean): Promise<SimpleBlock[]>;
}
export { type AdvancedDatabaseRecord, BlockProcessor, ContentConverter, type DatabaseFieldMetadata, type DatabaseRecord, DatabaseService, type FieldTypeFor, type FilterCondition, type LogicalOperator, NotionCMS, type NotionCMSConfig, type NotionFieldType, type NotionProperty, type NotionPropertyType, OPERATOR_MAP, type OperatorMap, type OperatorsFor, PageContentService, QueryBuilder, type QueryOptions, type QueryResult, type SelectOptionsFor, type SimpleBlock, type SimpleTableBlock, type SimpleTableRowBlock, type SortDirection, type TableBlockContent, type TableRowBlockContent, type TableRowCell, type TypeSafeFilterCondition, type ValueTypeFor, type ValueTypeMap, advancedNotionRecord, advancedNotionRecords, generateMultipleDatabaseTypes, generateTypes, processNotionRecord, processNotionRecords, simplifyNotionRecord, simplifyNotionRecords };