npm package: @keithce/astro-notion-db
Description: TypeScript library for working with astrological events in Notion database
Author: Keith
Latest version: 2.9.0

# Astro Notion Database Library

A comprehensive TypeScript library for working with astrological events stored in a Notion database. Provides strict typing, parsing utilities, filtering capabilities, and page decorators that exclusively work with official Notion API data.

## Features

- **Strict Notion API Adherence**: Only processes data directly provided by the official Notion API
- **Type Safety**: Full TypeScript support with strict typing for all Notion API structures
- **Page Decorators**: Self-contained pages with computed properties based on Notion API data
- **Flexible Filtering**: Query and filter events with a fluent API
- **Timeframe Helpers**: Built-in functions for retrieving events by standard time periods
- **Validation**: Comprehensive validation for data integrity
- **Zero Dependencies**: Lightweight library with no external runtime dependencies

## Notion API Strict Adherence

This library exclusively processes, manipulates, and represents data elements as structured and provided by the official Notion API for a single Notion page. It does not introduce, infer, or utilize any additional data sources, metadata, or contextual information beyond what the Notion API explicitly returns.

### Key Principles

- **Direct API Mapping**: All types and interfaces directly correspond to Notion API specifications
- **No Inferred Data**: No computed or derived data that isn't based on actual API responses
- **Property Preservation**: All Notion API properties are preserved exactly as provided
- **Type Safety**: Strong typing ensures only valid Notion API data structures are used

## Installation

```bash
npm install @keithce/astro-notion-db
```

## Basic Usage

### Parsing Notion API Pages

```typescript
import { parseAstroEventPage, parseAstroEventPages } from '@keithce/astro-notion-db';

// Parse a raw Notion API page response
const page = parseAstroEventPage(notionApiPage);

// Access core Notion API properties
console.log(page.id); // "example-event-123"
console.log(page.url); // "https://notion.so/example-event-123"
console.log(page.created_time); // "2024-01-15T10:00:00.000Z"
console.log(page.archived); // false
console.log(page.in_trash); // false

// Access Notion API metadata
console.log(page.icon?.emoji); // "🌙"
console.log(page.parent.type); // "database_id"
console.log(page.parent.database_id); // "database-123"
```

### Working with Notion API Properties

```typescript
// Access computed properties based on Notion API data
console.log(page.displayTitle); // "Mercury Retrograde in Pisces"
console.log(page.displayDate); // "Feb 15 - Mar 10, 2024"
console.log(page.isActive); // true/false

// Get astrological summary from Notion API data
const summary = page.getAstrologicalSummary();
// "Planets/Events: Mercury | Zodiac Signs: Pisces | Event Type: Mercury Course | Date: Feb 15 - Mar 10, 2024"

// Direct property access using Notion API property IDs
const nameValue = page.getPropertyValue('title');
const signsValue = page.getPropertyValue('HC%3Fx');
const planetsValue = page.getPropertyValue('LHB%3E');
const eventTypeValue = page.getPropertyValue('kly%5E');
const dateValue = page.getPropertyValue('qegM');

// Get formatted display values
const nameDisplay = page.getPropertyDisplayValue('title');
const signsDisplay = page.getPropertyDisplayValue('HC%3Fx');
```

## Complete Working Examples

### Example 1: Parsing Notion API Database Query Results

```typescript
import { parseAstroEventPage, parseAstroEventPages } from '@keithce/astro-notion-db';
import type { NotionPage, NotionClientDatabaseResponse } from '@keithce/astro-notion-db';

// Example Notion API response structure
const exampleNotionResponse: NotionClientDatabaseResponse = {
  object: "list",
  results: [
    {
      object: "page",
      id: "229513a9-eab9-811d-b8cf-de6b0fda0d65",
      created_time: "2025-07-07T22:28:00.000Z",
      last_edited_time: "2025-07-11T18:49:00.000Z",
      created_by: {
        object: "user",
        id: "873887d4-7cf1-410a-b7a2-efd659a420f7"
      },
      last_edited_by: {
        object: "user",
        id: "873887d4-7cf1-410a-b7a2-efd659a420f7"
      },
      cover: null,
      icon: {
        type: "emoji",
        emoji: "🌞"
      },
      parent: {
        type: "database_id",
        database_id: "214513a9-eab9-80da-b8ea-f1d8e65cea6e"
      },
      archived: false,
      in_trash: false,
      properties: {
        "Sign the Planet is in": {
          id: "HC%3Fx",
          type: "multi_select",
          multi_select: [
            {
              id: "d4e6ca88-9ae9-4240-aa79-40910ad5b85c",
              name: "Virgo",
              color: "green"
            }
          ]
        },
        "Planet/Event": {
          id: "LHB%3E",
          type: "multi_select",
          multi_select: [
            {
              id: "d47a4b6c-b98e-479a-a0ce-7a512374c754",
              name: "Sun",
              color: "yellow"
            },
            {
              id: "8cb30a1f-273c-4354-8e34-60e2b8138b96",
              name: "Where Planets Are (Range)",
              color: "blue"
            }
          ]
        },
        "Event Type (Notification)": {
          id: "kly%5E",
          type: "select",
          select: {
            id: "db79aa9e-aa73-4309-b97a-98aaf09beb8a",
            name: "No Notification (range)",
            color: "brown"
          }
        },
        "Date Range": {
          id: "qegM",
          type: "date",
          date: {
            start: "2025-09-16",
            end: "2025-10-17",
            time_zone: null
          }
        },
        "Name": {
          id: "title",
          type: "title",
          title: [
            {
              type: "text",
              text: {
                content: "Sun is in Virgo",
                link: null
              },
              annotations: {
                bold: false,
                italic: false,
                strikethrough: false,
                underline: false,
                code: false,
                color: "default"
              },
              plain_text: "Sun is in Virgo",
              href: null
            }
          ]
        }
      },
      url: "https://www.notion.so/Sun-is-in-Virgo-229513a9eab9811db8cfde6b0fda0d65",
      public_url: null
    }
  ],
  has_more: false
};

// Parse a complete Notion API database query response
function parseNotionDatabaseResponse(response: typeof exampleNotionResponse) {
  console.log('Parsing Notion API database response...\n');
  
  const parsedPages = response.results.map((rawPage: NotionPage, index: number) => {
    try {
      console.log(`Processing page ${index + 1}: ${rawPage.id}`);
      
      // Parse the individual page
      const parsedPage = parseAstroEventPage(rawPage as NotionPage);
      
      return {
        success: true,
        page: parsedPage,
        error: null
      };
      
    } catch (error) {
      console.error(`Failed to parse page ${index + 1}:`, error);
      return {
        success: false,
        page: null,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  });
  
  // Filter successful parses
  const successfulPages = parsedPages.filter(result => result.success);
  const failedPages = parsedPages.filter(result => !result.success);
  
  console.log(`\nParsing Results:`);
  console.log(`✅ Successfully parsed: ${successfulPages.length} pages`);
  console.log(`❌ Failed to parse: ${failedPages.length} pages`);
  
  if (failedPages.length > 0) {
    console.log('\nFailed pages:');
    failedPages.forEach((result, index) => {
      console.log(`  ${index + 1}. Error: ${result.error}`);
    });
  }
  
  return {
    totalPages: response.results.length,
    successfulPages: successfulPages.map(result => result.page),
    failedPages,
    parsedPages
  };
}

// Example usage
const result = parseNotionDatabaseResponse(exampleNotionResponse);
```

### Example 2: Standardized Parsing with Error Handling

```typescript
// Example usage of the new standardized parseAstroEventPages function
function demonstrateStandardizedParsing() {
  console.log('=== Standardized Parsing Demo ===\n');
  
  // Example 1: Valid response
  console.log('1. Parsing valid Notion client response:');
  const validResult = parseAstroEventPages(exampleNotionResponse);
  console.log(`Status: ${validResult.status}`);
  console.log(`Message: ${validResult.message}`);
  console.log(`Data: ${validResult.data ? `${validResult.data.length} pages parsed` : 'null'}`);
  console.log();
  
  // Example 2: Invalid response structure
  console.log('2. Parsing invalid response structure:');
  const invalidResponse = { object: 'invalid' } as unknown as NotionClientDatabaseResponse;
  const invalidResult = parseAstroEventPages(invalidResponse);
  console.log(`Status: ${invalidResult.status}`);
  console.log(`Message: ${invalidResult.message}`);
  console.log(`Data: ${invalidResult.data ? 'present' : 'null'}`);
  console.log();
  
  // Example 3: Empty results
  console.log('3. Parsing empty results:');
  const emptyResponse: NotionClientDatabaseResponse = {
    object: 'list',
    results: [],
    has_more: false
  };
  const emptyResult = parseAstroEventPages(emptyResponse);
  console.log(`Status: ${emptyResult.status}`);
  console.log(`Message: ${emptyResult.message}`);
  console.log(`Data: ${emptyResult.data ? `${emptyResult.data.length} pages` : 'null'}`);
  console.log();
  
  return {
    validResult,
    invalidResult,
    emptyResult
  };
}
```

### Example 3: Displaying Astrological Event Information

```typescript
// Example usage: Parse and display astrological event information
function displayAstroEventInfo(parsedPage: ReturnType<typeof parseAstroEventPage>) {
  console.log('\n=== Astrological Event Information ===');
  console.log(`Title: ${parsedPage.displayTitle}`);
  console.log(`Date Range: ${parsedPage.displayDate}`);
  console.log(`Currently Active: ${parsedPage.isActive ? 'Yes' : 'No'}`);
  console.log(`Summary: ${parsedPage.getAstrologicalSummary()}`);
  
  console.log('\nDetailed Properties:');
  console.log(`- Event Name: ${parsedPage.properties.name.value}`);
  console.log(`- Zodiac Signs: ${parsedPage.properties.signThePlanetIsIn.value.join(', ')}`);
  console.log(`- Planets/Events: ${parsedPage.properties.planetEvent.value.join(', ')}`);
  console.log(`- Notification Type: ${parsedPage.properties.eventTypeNotification.value || 'None'}`);
  
  if (parsedPage.properties.dateRange.value) {
    const { start, end, time_zone } = parsedPage.properties.dateRange.value;
    console.log(`- Start Date: ${start.toISOString().split('T')[0]}`);
    if (end) {
      console.log(`- End Date: ${end.toISOString().split('T')[0]}`);
    }
    if (time_zone) {
      console.log(`- Time Zone: ${time_zone}`);
    }
  }
  
  console.log('\nRaw Notion API Properties Available:');
  // Get all property names from the raw page properties
  const propertyNames = Object.keys(parsedPage.getPropertyValue('') || {});
  propertyNames.forEach(propName => {
    const displayValue = parsedPage.getPropertyDisplayValue(propName);
    console.log(`  - ${propName}: ${displayValue}`);
  });
}

// Use the parsed data
if (result.successfulPages.length > 0) {
  displayAstroEventInfo(result.successfulPages[0]!);
}
```

## Timeframe Helper Functions

The library provides convenient helper functions to retrieve events based on standard time periods:

### Basic Timeframe Functions

```typescript
import { 
  getEventsToday,
  getEventsThisWeek,
  getEventsNextWeek,
  getEventsThisMonth,
  getEventsNextMonth,
  groupEventsByMonth,
  getEventsInDateRange,
} from '@keithce/astro-notion-db';

// Parse multiple Notion API pages
const events = notionApiPages.map(page => parseAstroEventPage(page));

// Get events happening today
const todayEvents = getEventsToday(events);
console.log(`Events today: ${todayEvents.length}`);

// Get events happening this week (Monday to Sunday)
const thisWeekEvents = getEventsThisWeek(events);
console.log(`Events this week: ${thisWeekEvents.length}`);

// Get events happening next week
const nextWeekEvents = getEventsNextWeek(events);
console.log(`Events next week: ${nextWeekEvents.length}`);

// Get events happening this month
const thisMonthEvents = getEventsThisMonth(events);
console.log(`Events this month: ${thisMonthEvents.length}`);

// Get events happening next month
const nextMonthEvents = getEventsNextMonth(events);
console.log(`Events next month: ${nextMonthEvents.length}`);
```

### Advanced Timeframe Operations

```typescript
// Group events by month for easy organization
const eventsByMonth = groupEventsByMonth(events);
for (const [month, monthEvents] of eventsByMonth) {
  console.log(`${month}: ${monthEvents.length} events`);
  monthEvents.forEach(event => {
    console.log(`  - ${event.displayTitle}`);
  });
}

// Get events in a custom date range
import { parseISO } from 'date-fns';

const customStart = parseISO('2024-02-01');
const customEnd = parseISO('2024-02-28');
const customRangeEvents = getEventsInDateRange(events, customStart, customEnd);
console.log(`Events in custom range: ${customRangeEvents.length}`);

// Chain timeframe helpers with other utilities
const activeThisWeek = getEventsThisWeek(events).filter(event => event.isActive);
const sortedNextMonth = getEventsNextMonth(events).sort((a, b) => {
  const aDate = a.properties.dateRange.value?.start || new Date(0);
  const bDate = b.properties.dateRange.value?.start || new Date(0);
  return aDate.getTime() - bDate.getTime();
});
```

### Complete Timeframe Helper Example

```typescript
// Sample Notion API page data for demonstration
const sampleNotionPages = [
  {
    object: 'page' as const,
    id: 'event-1',
    url: 'https://notion.so/event-1',
    public_url: null,
    created_time: '2024-01-15T10:00:00.000Z',
    last_edited_time: '2024-01-15T10:00:00.000Z',
    created_by: { object: 'user' as const, id: 'user-1' },
    last_edited_by: { object: 'user' as const, id: 'user-1' },
    cover: null,
    icon: null,
    parent: { type: 'database_id' as const, database_id: 'database-1' },
    archived: false,
    in_trash: false,
    properties: {
      'title': {
        id: 'title',
        type: 'title',
        title: [{ 
          type: 'text',
          text: { content: 'Mercury Retrograde in Pisces', link: null },
          annotations: {
            bold: false,
            italic: false,
            strikethrough: false,
            underline: false,
            code: false,
            color: 'default',
          },
          plain_text: 'Mercury Retrograde in Pisces',
          href: null,
        }]
      },
      'HC%3Fx': {
        id: 'HC%3Fx',
        type: 'multi_select',
        multi_select: [{ id: 'option-1', name: 'Pisces', color: 'blue' }]
      },
      'LHB%3E': {
        id: 'LHB%3E',
        type: 'multi_select',
        multi_select: [{ id: 'option-2', name: 'Mercury', color: 'yellow' }]
      },
      'kly%5E': {
        id: 'kly%5E',
        type: 'select',
        select: { id: 'option-3', name: 'Mercury Course', color: 'green' }
      },
      'qegM': {
        id: 'qegM',
        type: 'date',
        date: {
          start: new Date().toISOString(), // Today
          end: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() // 7 days from now
        }
      }
    }
  }
];

// Demonstrate timeframe helper functions
function demonstrateTimeframeHelpers() {
  console.log('=== Timeframe Helper Functions Demo ===\n');
  
  // Parse the sample pages into AstroEventPage objects
  const events = sampleNotionPages.map(page => parseAstroEventPage(page));
  
  console.log(`Total events: ${events.length}\n`);
  
  // Get events happening today
  const todayEvents = getEventsToday(events);
  console.log(`Events happening today: ${todayEvents.length}`);
  todayEvents.forEach(event => {
    console.log(`  - ${event.displayTitle} (${event.displayDate})`);
  });
  console.log();
  
  // Get events happening this week
  const thisWeekEvents = getEventsThisWeek(events);
  console.log(`Events happening this week: ${thisWeekEvents.length}`);
  thisWeekEvents.forEach(event => {
    console.log(`  - ${event.displayTitle} (${event.displayDate})`);
  });
  console.log();
  
  // Group events by month
  const eventsByMonth = groupEventsByMonth(events);
  console.log('Events grouped by month:');
  for (const [month, monthEvents] of eventsByMonth) {
    console.log(`  ${month}: ${monthEvents.length} events`);
    monthEvents.forEach(event => {
      console.log(`    - ${event.displayTitle}`);
    });
  }
  console.log();
}

// Example of chaining timeframe helpers with other utilities
function demonstrateChainedOperations() {
  console.log('=== Chained Operations Demo ===\n');
  
  const events = sampleNotionPages.map(page => parseAstroEventPage(page));
  
  // Get this week's events and filter for active ones
  const activeThisWeek = getEventsThisWeek(events).filter(event => event.isActive);
  console.log(`Active events this week: ${activeThisWeek.length}`);
  
  // Get next month's events and sort by date
  const nextMonthEvents = getEventsNextMonth(events);
  const sortedNextMonth = nextMonthEvents.sort((a, b) => {
    const aDate = a.properties.dateRange.value?.start || new Date(0);
    const bDate = b.properties.dateRange.value?.start || new Date(0);
    return aDate.getTime() - bDate.getTime();
  });
  
  console.log(`Next month's events (sorted):`);
  sortedNextMonth.forEach(event => {
    console.log(`  - ${event.displayTitle} (${event.displayDate})`);
  });
  
  // Group events by month and show only those with multiple events
  const eventsByMonth = groupEventsByMonth(events);
  const busyMonths = Array.from(eventsByMonth.entries())
    .filter(([, monthEvents]) => monthEvents.length > 1);
  
  console.log(`\nMonths with multiple events:`);
  busyMonths.forEach(([month, monthEvents]) => {
    console.log(`  ${month}: ${monthEvents.length} events`);
  });
}
```

### Timeframe Helper Details

| Function                                   | Description                                               | Returns                         |
| ------------------------------------------ | --------------------------------------------------------- | ------------------------------- |
| `getEventsToday(events)`                   | Events that start, end, or are active on the current date | `AstroEventPage[]`              |
| `getEventsThisWeek(events)`                | Events during the current week (Monday to Sunday)         | `AstroEventPage[]`              |
| `getEventsNextWeek(events)`                | Events during the next week (Monday to Sunday)            | `AstroEventPage[]`              |
| `getEventsThisMonth(events)`               | Events during the current month                           | `AstroEventPage[]`              |
| `getEventsNextMonth(events)`               | Events during the next month                              | `AstroEventPage[]`              |
| `groupEventsByMonth(events)`               | Groups events by month (YYYY-MM format)                   | `Map<string, AstroEventPage[]>` |
| `getEventsInDateRange(events, start, end)` | Events within a custom date range                         | `AstroEventPage[]`              |

### Raw Notion API Access

```typescript
// Access raw Notion API data exactly as provided
const rawPage = exampleNotionResponse.results[0];

// Direct property access
const titleProperty = rawPage.properties['title'];
console.log(titleProperty.title[0].plain_text); // "Sun is in Virgo"

const signProperty = rawPage.properties['HC%3Fx'];
console.log(signProperty.multi_select[0].name); // "Virgo"

const dateProperty = rawPage.properties['qegM'];
console.log(dateProperty.date.start); // "2025-09-16T00:00:00.000Z"
```

## Notion API Data Structure

The library works with the exact structure provided by the Notion API:

### Page Object
```typescript
interface NotionPage {
  object: 'page';
  id: string;
  url: string;
  public_url?: string | null;
  created_time: string;
  last_edited_time: string;
  created_by?: { object: 'user'; id: string };
  last_edited_by?: { object: 'user'; id: string };
  cover: null | { type: 'external' | 'file'; external?: { url: string }; file?: { url: string; expiry_time: string } };
  icon: null | { type: 'emoji' | 'external' | 'file'; emoji?: string; external?: { url: string }; file?: { url: string; expiry_time: string } };
  parent: { type: 'database_id' | 'page_id' | 'workspace'; database_id?: string; page_id?: string; workspace?: boolean };
  archived: boolean;
  in_trash: boolean;
  properties: Record<string, NotionProperty>;
}
```

### Property Types
```typescript
interface NotionProperty {
  id: string;
  type: string;
  [key: string]: any;
}
```

### Page Decorator
```typescript
interface AstroEventPage extends NotionPage {
  // All Notion API properties preserved exactly
  properties: AstroEventProperties; // Parsed astrological properties
  
  // Computed properties based only on Notion API data
  displayTitle: string;
  displayDate: string;
  isActive: boolean;
  
  // Methods for working with Notion API data
  getPropertyValue(propertyName: string): any;
  getPropertyDisplayValue(propertyName: string): string;
  getAstrologicalSummary(): string;
}
```

## Astrological Event Properties

The library parses specific Notion database properties for astrological events:

### Supported Property Types
- **Title**: Page name (e.g., "Mercury Retrograde in Pisces")
- **Multi-select**: Zodiac signs and planets/events
- **Select**: Event type notifications
- **Date**: Event date ranges with timezone support

### Property Mapping
```typescript
// Actual Notion database property IDs
const propertyMapping = {
  'title': 'title', // Page name
  'Sign the Planet is in': 'HC%3Fx', // Multi-select
  'Planet/Event': 'LHB%3E', // Multi-select
  'Event Type (Notification)': 'kly%5E', // Select
  'Date Range': 'qegM', // Date
};
```

## Advanced Usage

### Working with Multiple Pages

```typescript
import { parseAstroEventPage } from '@keithce/astro-notion-db';

// Parse multiple Notion API pages
const pages = notionApiPages.map(page => 
  parseAstroEventPage(page)
);

// Filter based on Notion API data
const activeEvents = pages.filter(page => page.isActive);
const mercuryEvents = pages.filter(page => 
  page.properties.planetEvent.value.includes('Mercury')
);
```

### Property Validation

```typescript
// Validate that properties exist in Notion API response
function validateNotionPage(page: NotionPage): boolean {
  const requiredProperties = ['title', 'HC%3Fx', 'LHB%3E', 'kly%5E', 'qegM'];
  
  return requiredProperties.every(propId => 
    page.properties[propId] !== undefined
  );
}
```

### Error Handling

```typescript
import { AstroEventParseError } from '@keithce/astro-notion-db';

try {
  const page = parseAstroEventPage(notionApiPage);
} catch (error) {
  if (error instanceof AstroEventParseError) {
    console.error('Failed to parse Notion API page:', error.message);
    console.error('Cause:', error.cause);
  }
}
```

## Type Safety

All functionality is fully typed with strict Notion API adherence:

```typescript
import type { 
  AstroEventPage, 
  NotionPage, 
  NotionProperty 
} from '@keithce/astro-notion-db';

// Type-safe property access
function processNotionPage(page: NotionPage) {
  const titleProperty = page.properties['title'];
  const titleText = titleProperty.title[0].plain_text; // Type-safe
  
  // Type-safe page usage
  const astroPage: AstroEventPage = parseAstroEventPage(page);
  const summary = astroPage.getAstrologicalSummary(); // Type-safe
}
```

## Migration from Previous Versions

If you're upgrading from a previous version that included inferred data:

```typescript
// Old way (with inferred data)
const oldPage = parseAstroEventPage(rawNotionPage);
console.log(oldPage.metadata.title); // Inferred data

// New way (strict Notion API)
const page = parseAstroEventPage(notionApiPage);
console.log(page.displayTitle); // Based only on Notion API data
console.log(page.properties.name.value); // Direct API data
```

## Examples

The README now contains comprehensive working examples that demonstrate all the library's functionality. You can copy and paste these examples directly into your code to get started quickly.

### Quick Start Example

```typescript
import { parseAstroEventPage, getEventsToday } from '@keithce/astro-notion-db';

// Parse a single Notion API page
const page = parseAstroEventPage(notionApiPage);

// Get events happening today
const events = [page]; // Your array of parsed pages
const todayEvents = getEventsToday(events);

console.log(`Events today: ${todayEvents.length}`);
todayEvents.forEach(event => {
  console.log(`- ${event.displayTitle} (${event.displayDate})`);
});
```

## API Reference

### AstroEventPage Properties

| Property           | Type                   | Description                             |
| ------------------ | ---------------------- | --------------------------------------- |
| `object`           | `'page'`               | Notion API object type                  |
| `id`               | `string`               | Unique page identifier from Notion API  |
| `url`              | `string`               | Notion page URL                         |
| `public_url`       | `string \| null`       | Public URL if available                 |
| `created_time`     | `string`               | ISO timestamp from Notion API           |
| `last_edited_time` | `string`               | ISO timestamp from Notion API           |
| `created_by`       | `User \| undefined`    | User who created the page               |
| `last_edited_by`   | `User \| undefined`    | User who last edited the page           |
| `cover`            | `Cover \| null`        | Page cover from Notion API              |
| `icon`             | `Icon \| null`         | Page icon from Notion API               |
| `parent`           | `Parent`               | Parent object from Notion API           |
| `archived`         | `boolean`              | Archive status from Notion API          |
| `in_trash`         | `boolean`              | Trash status from Notion API            |
| `properties`       | `AstroEventProperties` | Parsed astrological properties          |
| `displayTitle`     | `string`               | Computed title from Notion API data     |
| `displayDate`      | `string`               | Formatted date from Notion API data     |
| `isActive`         | `boolean`              | Active status based on Notion API dates |

### Property Methods

| Method                    | Parameters             | Returns  | Description                            |
| ------------------------- | ---------------------- | -------- | -------------------------------------- |
| `getPropertyValue`        | `propertyName: string` | `any`    | Get raw property value from Notion API |
| `getPropertyDisplayValue` | `propertyName: string` | `string` | Get formatted property value           |
| `getAstrologicalSummary`  | None                   | `string` | Generate summary from Notion API data  |

### Timeframe Helper Functions

| Function               | Parameters                                         | Returns                         | Description                     |
| ---------------------- | -------------------------------------------------- | ------------------------------- | ------------------------------- |
| `getEventsToday`       | `events: AstroEventPage[]`                         | `AstroEventPage[]`              | Get events happening today      |
| `getEventsThisWeek`    | `events: AstroEventPage[]`                         | `AstroEventPage[]`              | Get events happening this week  |
| `getEventsNextWeek`    | `events: AstroEventPage[]`                         | `AstroEventPage[]`              | Get events happening next week  |
| `getEventsThisMonth`   | `events: AstroEventPage[]`                         | `AstroEventPage[]`              | Get events happening this month |
| `getEventsNextMonth`   | `events: AstroEventPage[]`                         | `AstroEventPage[]`              | Get events happening next month |
| `groupEventsByMonth`   | `events: AstroEventPage[]`                         | `Map<string, AstroEventPage[]>` | Group events by month           |
| `getEventsInDateRange` | `events: AstroEventPage[], start: Date, end: Date` | `AstroEventPage[]`              | Get events in custom date range |

## Standardized Parsing with Error Handling

The library now provides a robust, standardized parsing function that accepts official Notion client database responses and returns a consistent JSON response format with comprehensive error handling.

### New Standardized Parser

```typescript
import { parseAstroEventPages } from '@keithce/astro-notion-db';
import type { NotionClientDatabaseResponse, ParseResponse, AstroEventPage } from '@keithce/astro-notion-db';

// Parse a Notion client database response
const result: ParseResponse<AstroEventPage[]> = parseAstroEventPages(notionClientResponse);

// Check the result
if (result.status === 'success') {
  console.log(`Successfully parsed ${result.data?.length} pages`);
  console.log(result.message);
  
  // Use the parsed data
  result.data?.forEach(page => {
    console.log(page.displayTitle);
  });
} else {
  console.error('Parsing failed:', result.message);
  console.log('Data:', result.data); // null on error
}
```

### Response Format

The `parseAstroEventPages()` function returns a standardized response object:

```typescript
interface ParseResponse<T> {
  data: T | null;           // Parsed data if successful, null if error
  status: 'success' | 'error'; // Indicates parsing success or failure
  message: string;          // Detailed status or error message
}
```

### Input Validation

The function performs comprehensive validation:

1. **Response Structure**: Validates the Notion client response format
2. **Page Structure**: Validates each page object structure
3. **Required Properties**: Ensures all required astrological event properties are present
4. **Property Types**: Validates property types and structures

### Error Handling

The function handles various error scenarios:

- **Invalid Response Structure**: Returns error for malformed Notion responses
- **Missing Properties**: Detailed error messages for missing required properties
- **Partial Failures**: Continues parsing other pages if some fail
- **Type Errors**: Comprehensive type checking with helpful error messages
- **Runtime Errors**: Catches and reports unexpected errors

### Example Error Scenarios

```typescript
// Invalid response structure
const invalidResult = parseAstroEventPages({ object: 'invalid' } as any);
// Returns: { status: 'error', data: null, message: 'Invalid Notion client response structure' }

// Missing required properties
const incompleteResult = parseAstroEventPages(incompleteResponse);
// Returns: { status: 'error', data: null, message: 'Failed to parse any pages. Errors: Index 0: Missing required property: HC%3Fx' }

// Mixed success/failure
const mixedResult = parseAstroEventPages(mixedResponse);
// Returns: { status: 'success', data: [parsedPages], message: 'Successfully parsed 2 pages. 1 pages failed to parse: Index 1: Missing required property: LHB%3E' }
```

### Backward Compatibility

The legacy function is still available for backward compatibility:

```typescript
import { parseAstroEventPagesLegacy } from '@keithce/astro-notion-db';

const pages = parseAstroEventPagesLegacy(rawPagesArray);
```

### Type Safety

The function is fully typed with official Notion SDK compatibility:

```typescript
import type { NotionClientDatabaseResponse } from '@keithce/astro-notion-db';

// Compatible with @notionhq/client SDK response
const response: NotionClientDatabaseResponse = await notion.databases.query({
  database_id: 'your-database-id'
});

const result = parseAstroEventPages(response);
```

## Core Functionality

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Ensure all changes strictly adhere to Notion API specifications
5. Add tests for new functionality
6. Submit a pull request

## License

MIT License - see LICENSE file for details.