import { Client } from '@notionhq/client' import type { DatabaseObjectResponse, PageObjectResponse, PartialDatabaseObjectResponse, PartialPageObjectResponse, } from '@notionhq/client/build/src/api-endpoints' // Type for Notion database schema export interface NotionDatabaseSchema { id: string title: string properties: NotionProperty[] } export interface NotionProperty { id: string name: string type: string } // Type for a Notion page/record export interface NotionRecord { id: string properties: Record } /** * Create a Notion client with the provided API key */ export function createNotionClient(apiKey: string): Client { return new Client({ auth: apiKey }) } /** * Test connection to Notion API */ export async function testNotionConnection( apiKey: string ): Promise<{ success: boolean; error?: string }> { try { const client = createNotionClient(apiKey) // Try to get the bot user to verify the API key await client.users.me({}) return { success: true } } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to connect to Notion', } } } /** * Get database schema (properties) */ export async function getNotionDatabaseSchema( apiKey: string, databaseId: string ): Promise { const client = createNotionClient(apiKey) const database = await client.databases.retrieve({ database_id: databaseId, }) // Type guard for full database object if (!('properties' in database)) { throw new Error('Could not retrieve database properties') } const db = database as DatabaseObjectResponse const properties: NotionProperty[] = Object.entries(db.properties).map( ([name, prop]) => ({ id: prop.id, name, type: prop.type, }) ) // Get database title const titleProp = db.title?.[0] const title = titleProp?.type === 'text' ? titleProp.plain_text : databaseId return { id: db.id, title, properties, } } /** * Query all records from a Notion database */ export async function queryNotionDatabase( apiKey: string, databaseId: string, limit?: number ): Promise { const client = createNotionClient(apiKey) const records: NotionRecord[] = [] let cursor: string | undefined do { const response = await client.databases.query({ database_id: databaseId, start_cursor: cursor, page_size: 100, }) for (const page of response.results) { if (isFullPage(page)) { records.push({ id: page.id, properties: extractProperties(page.properties), }) if (limit && records.length >= limit) { return records.slice(0, limit) } } } cursor = response.has_more ? response.next_cursor ?? undefined : undefined } while (cursor) return records } /** * Type guard for full page response */ function isFullPage( page: PageObjectResponse | PartialPageObjectResponse | DatabaseObjectResponse | PartialDatabaseObjectResponse ): page is PageObjectResponse { return 'properties' in page && page.object === 'page' } /** * Extract property values from a Notion page */ function extractProperties( properties: PageObjectResponse['properties'] ): Record { const result: Record = {} for (const [name, prop] of Object.entries(properties)) { result[name] = extractPropertyValue(prop) } return result } /** * Extract a single property value */ function extractPropertyValue(prop: PageObjectResponse['properties'][string]): unknown { switch (prop.type) { case 'title': return prop.title.map((t) => t.plain_text).join('') case 'rich_text': return prop.rich_text.map((t) => t.plain_text).join('') case 'number': return prop.number case 'select': return prop.select?.name ?? null case 'multi_select': return prop.multi_select.map((s) => s.name) case 'status': return prop.status?.name ?? null case 'date': return prop.date?.start ?? null case 'checkbox': return prop.checkbox case 'url': return prop.url case 'email': return prop.email case 'phone_number': return prop.phone_number case 'files': return prop.files.map((f) => { if (f.type === 'file') { return f.file.url } else if (f.type === 'external') { return f.external.url } return null }).filter(Boolean) case 'relation': return prop.relation.map((r) => r.id) case 'people': return prop.people.map((p) => { if ('name' in p) { return p.name } return p.id }) case 'created_time': return prop.created_time case 'last_edited_time': return prop.last_edited_time case 'created_by': return 'name' in prop.created_by ? prop.created_by.name : prop.created_by.id case 'last_edited_by': return 'name' in prop.last_edited_by ? prop.last_edited_by.name : prop.last_edited_by.id case 'formula': return extractFormulaValue(prop.formula) case 'rollup': return extractRollupValue(prop.rollup) case 'unique_id': return prop.unique_id.prefix ? `${prop.unique_id.prefix}-${prop.unique_id.number}` : prop.unique_id.number default: return null } } /** * Extract formula value */ function extractFormulaValue( formula: { type: 'string'; string: string | null } | { type: 'number'; number: number | null } | { type: 'boolean'; boolean: boolean | null } | { type: 'date'; date: { start: string } | null } ): unknown { switch (formula.type) { case 'string': return formula.string case 'number': return formula.number case 'boolean': return formula.boolean case 'date': return formula.date?.start ?? null default: return null } } /** * Extract rollup value */ function extractRollupValue( rollup: { type: 'number'; number: number | null; function: string } | { type: 'date'; date: { start: string } | null; function: string } | { type: 'array'; array: Array; function: string } | { type: 'incomplete'; incomplete: Record; function: string } | { type: 'unsupported'; unsupported: Record; function: string } ): unknown { switch (rollup.type) { case 'number': return rollup.number case 'date': return rollup.date?.start ?? null case 'array': return rollup.array default: return null } }