You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

16 KiB

Database Interfaces Documentation

Author: Matthew Raymer
Version: 1.0.0
Last Updated: 2025-01-21

Overview

The Daily Notification Plugin owns its own SQLite database for storing schedules, cached content, configuration, and execution history. Since the plugin's database is isolated from the host app, the webview accesses this data through TypeScript/Capacitor interfaces.

This document explains how to use these interfaces from TypeScript/JavaScript code in your Capacitor app.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Host App (TypeScript)                     │
│  import { DailyNotification } from '@capacitor-community/...'│
│                                                              │
│  const schedules = await DailyNotification.getSchedules()  │
└──────────────────────┬──────────────────────────────────────┘
                       │ Capacitor Bridge
                       ▼
┌─────────────────────────────────────────────────────────────┐
│              Plugin (Native Android/Kotlin)                 │
│                                                              │
│  @PluginMethod                                              │
│  getSchedules() → Room Database → SQLite                    │
└─────────────────────────────────────────────────────────────┘

Quick Start

import { DailyNotification } from '@capacitor-community/daily-notification';

// Get all enabled notification schedules
const schedules = await DailyNotification.getSchedules({ 
  kind: 'notify', 
  enabled: true 
});

// Get latest cached content
const content = await DailyNotification.getLatestContentCache();

// Create a new schedule
const newSchedule = await DailyNotification.createSchedule({
  kind: 'notify',
  cron: '0 9 * * *',  // Daily at 9 AM
  enabled: true
});

Interface Categories

1. Schedules Management

Schedules represent recurring patterns for fetching content or displaying notifications. These are critical for reboot recovery - Android doesn't persist AlarmManager/WorkManager schedules, so they must be restored from the database.

Get All Schedules

// Get all schedules
const result = await DailyNotification.getSchedules();
const allSchedules = result.schedules;

// Get only enabled notification schedules
const notifyResult = await DailyNotification.getSchedules({ 
  kind: 'notify', 
  enabled: true 
});
const enabledNotify = notifyResult.schedules;

// Get only fetch schedules
const fetchResult = await DailyNotification.getSchedules({ 
  kind: 'fetch' 
});
const fetchSchedules = fetchResult.schedules;

Returns: Promise<{ schedules: Schedule[] }> - Note: Array is wrapped in object due to Capacitor serialization

Get Single Schedule

const schedule = await DailyNotification.getSchedule('notify_1234567890');
if (schedule) {
  console.log(`Next run: ${new Date(schedule.nextRunAt)}`);
}

Returns: Promise<Schedule | null>

Create Schedule

const schedule = await DailyNotification.createSchedule({
  kind: 'notify',
  cron: '0 9 * * *',  // Daily at 9 AM (cron format)
  // OR
  clockTime: '09:00',  // Simple HH:mm format
  enabled: true,
  jitterMs: 60000,  // 1 minute jitter
  backoffPolicy: 'exp'
});

Returns: Promise<Schedule>

Update Schedule

// Update schedule enable state
await DailyNotification.updateSchedule('notify_1234567890', {
  enabled: false
});

// Update next run time
await DailyNotification.updateSchedule('notify_1234567890', {
  nextRunAt: Date.now() + 86400000  // Tomorrow
});

Returns: Promise<Schedule>

Delete Schedule

await DailyNotification.deleteSchedule('notify_1234567890');

Returns: Promise<void>

Enable/Disable Schedule

// Disable schedule
await DailyNotification.enableSchedule('notify_1234567890', false);

// Enable schedule
await DailyNotification.enableSchedule('notify_1234567890', true);

Returns: Promise<void>

Calculate Next Run Time

// Calculate next run from cron expression
const nextRun = await DailyNotification.calculateNextRunTime('0 9 * * *');

// Calculate next run from clockTime
const nextRun2 = await DailyNotification.calculateNextRunTime('09:00');

console.log(`Next run: ${new Date(nextRun)}`);

Returns: Promise<number> (timestamp in milliseconds)

2. Content Cache Management

Content cache stores prefetched content for offline-first display. Each entry has a TTL (time-to-live) for freshness validation.

Get Latest Content Cache

const latest = await DailyNotification.getLatestContentCache();
if (latest) {
  const content = JSON.parse(latest.payload);
  const age = Date.now() - latest.fetchedAt;
  const isFresh = age < (latest.ttlSeconds * 1000);
  
  console.log(`Content age: ${age}ms, Fresh: ${isFresh}`);
}

Returns: Promise<ContentCache | null>

Get Content Cache by ID

const cache = await DailyNotification.getContentCacheById({ 
  id: 'cache_1234567890' 
});

Returns: Promise<ContentCache | null>

Get Content Cache History

// Get last 10 cache entries
const result = await DailyNotification.getContentCacheHistory(10);
const history = result.history;

history.forEach(cache => {
  console.log(`Cache ${cache.id}: ${new Date(cache.fetchedAt)}`);
});

Returns: Promise<{ history: ContentCache[] }>

Save Content Cache

const cached = await DailyNotification.saveContentCache({
  payload: JSON.stringify({
    title: 'Daily Update',
    body: 'Your daily content is ready!',
    data: { /* ... */ }
  }),
  ttlSeconds: 3600,  // 1 hour TTL
  meta: 'fetched_from_api'
});

console.log(`Cached content with ID: ${cached.id}`);

Returns: Promise<ContentCache>

Clear Content Cache

// Clear all cache entries
await DailyNotification.clearContentCacheEntries();

// Clear entries older than 24 hours
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
await DailyNotification.clearContentCacheEntries({ 
  olderThan: oneDayAgo 
});

Returns: Promise<void>

3. Configuration Management

Note: Configuration management methods (getConfig, setConfig, etc.) are currently not implemented in the Kotlin database schema. These will be available once the database consolidation is complete (see android/DATABASE_CONSOLIDATION_PLAN.md). For now, use the Java-based DailyNotificationStorageRoom for configuration storage if needed.

When implemented, these methods will store plugin settings and user preferences with optional TimeSafari DID scoping.

Get Configuration

// Get config by key
const config = await DailyNotification.getConfig('notification_sound_enabled');

if (config) {
  const value = config.configDataType === 'boolean' 
    ? config.configValue === 'true'
    : config.configValue;
  console.log(`Sound enabled: ${value}`);
}

Returns: Promise<Config | null>

Get All Configurations

// Get all configs
const allConfigs = await DailyNotification.getAllConfigs();

// Get configs for specific user
const userConfigs = await DailyNotification.getAllConfigs({
  timesafariDid: 'did:ethr:0x...'
});

// Get configs by type
const pluginConfigs = await DailyNotification.getAllConfigs({
  configType: 'plugin_setting'
});

Returns: Promise<Config[]>

Set Configuration

await DailyNotification.setConfig({
  configType: 'user_preference',
  configKey: 'notification_sound_enabled',
  configValue: 'true',
  configDataType: 'boolean',
  timesafariDid: 'did:ethr:0x...'  // Optional: user-specific
});

Returns: Promise<Config>

Update Configuration

await DailyNotification.updateConfig(
  'notification_sound_enabled',
  'false',
  { timesafariDid: 'did:ethr:0x...' }
);

Returns: Promise<Config>

Delete Configuration

await DailyNotification.deleteConfig('notification_sound_enabled', {
  timesafariDid: 'did:ethr:0x...'
});

Returns: Promise<void>

4. Callbacks Management

Callbacks are executed after fetch/notify events. They can be HTTP endpoints, local handlers, or queue destinations.

Get All Callbacks

// Get all callbacks
const result = await DailyNotification.getCallbacks();
const allCallbacks = result.callbacks;

// Get only enabled callbacks
const enabledResult = await DailyNotification.getCallbacks({ 
  enabled: true 
});
const enabledCallbacks = enabledResult.callbacks;

Returns: Promise<{ callbacks: Callback[] }>

Get Single Callback

const callback = await DailyNotification.getCallback('on_notify_delivered');

Returns: Promise<Callback | null>

Register Callback

await DailyNotification.registerCallbackConfig({
  id: 'on_notify_delivered',
  kind: 'http',
  target: 'https://api.example.com/webhooks/notify',
  headersJson: JSON.stringify({
    'Authorization': 'Bearer token123',
    'Content-Type': 'application/json'
  }),
  enabled: true
});

Returns: Promise<Callback>

Update Callback

await DailyNotification.updateCallback('on_notify_delivered', {
  enabled: false,
  headersJson: JSON.stringify({ 'Authorization': 'Bearer newtoken' })
});

Returns: Promise<Callback>

Delete Callback

await DailyNotification.deleteCallback('on_notify_delivered');

Returns: Promise<void>

Enable/Disable Callback

await DailyNotification.enableCallback('on_notify_delivered', false);

Returns: Promise<void>

5. History/Analytics

History provides execution logs for debugging and analytics.

Get History

// Get last 50 entries
const result = await DailyNotification.getHistory();
const history = result.history;

// Get entries since yesterday
const yesterday = Date.now() - (24 * 60 * 60 * 1000);
const recentResult = await DailyNotification.getHistory({
  since: yesterday,
  limit: 100
});
const recentHistory = recentResult.history;

// Get only fetch executions
const fetchResult = await DailyNotification.getHistory({
  kind: 'fetch',
  limit: 20
});
const fetchHistory = fetchResult.history;

Returns: Promise<{ history: History[] }>

Get History Statistics

const stats = await DailyNotification.getHistoryStats();

console.log(`Total executions: ${stats.totalCount}`);
console.log(`Success rate: ${stats.outcomes.success / stats.totalCount * 100}%`);
console.log(`Fetch executions: ${stats.kinds.fetch}`);
console.log(`Most recent: ${new Date(stats.mostRecent)}`);

Returns: Promise<HistoryStats>

Type Definitions

Schedule

interface Schedule {
  id: string;
  kind: 'fetch' | 'notify';
  cron?: string;              // Cron expression (e.g., "0 9 * * *")
  clockTime?: string;         // HH:mm format (e.g., "09:00")
  enabled: boolean;
  lastRunAt?: number;         // Timestamp (ms)
  nextRunAt?: number;        // Timestamp (ms)
  jitterMs: number;
  backoffPolicy: string;     // 'exp', etc.
  stateJson?: string;
}

ContentCache

interface ContentCache {
  id: string;
  fetchedAt: number;         // Timestamp (ms)
  ttlSeconds: number;
  payload: string;            // JSON string or base64
  meta?: string;
}

Config

interface Config {
  id: string;
  timesafariDid?: string;
  configType: string;
  configKey: string;
  configValue: string;
  configDataType: string;     // 'string' | 'boolean' | 'integer' | etc.
  isEncrypted: boolean;
  createdAt: number;         // Timestamp (ms)
  updatedAt: number;          // Timestamp (ms)
}

Callback

interface Callback {
  id: string;
  kind: 'http' | 'local' | 'queue';
  target: string;
  headersJson?: string;
  enabled: boolean;
  createdAt: number;         // Timestamp (ms)
}

History

interface History {
  id: number;
  refId: string;
  kind: 'fetch' | 'notify' | 'callback' | 'boot_recovery';
  occurredAt: number;        // Timestamp (ms)
  durationMs?: number;
  outcome: string;            // 'success' | 'failure' | etc.
  diagJson?: string;
}

Common Patterns

Pattern 1: Check Schedule Status

async function checkScheduleStatus() {
  const result = await DailyNotification.getSchedules({ enabled: true });
  const schedules = result.schedules;
  
  for (const schedule of schedules) {
    if (schedule.nextRunAt) {
      const nextRun = new Date(schedule.nextRunAt);
      const now = new Date();
      const timeUntil = nextRun.getTime() - now.getTime();
      
      console.log(`${schedule.kind} schedule ${schedule.id}:`);
      console.log(`  Next run: ${nextRun}`);
      console.log(`  Time until: ${Math.round(timeUntil / 1000 / 60)} minutes`);
    }
  }
}

Pattern 2: Verify Content Freshness

async function isContentFresh(): Promise<boolean> {
  const cache = await DailyNotification.getLatestContentCache();
  
  if (!cache) {
    return false;  // No content available
  }
  
  const age = Date.now() - cache.fetchedAt;
  const ttlMs = cache.ttlSeconds * 1000;
  
  return age < ttlMs;
}

Pattern 3: Update User Preferences

async function updateUserPreferences(did: string, preferences: Record<string, any>) {
  for (const [key, value] of Object.entries(preferences)) {
    await DailyNotification.setConfig({
      timesafariDid: did,
      configType: 'user_preference',
      configKey: key,
      configValue: String(value),
      configDataType: typeof value === 'boolean' ? 'boolean' : 'string'
    });
  }
}

Pattern 4: Monitor Execution Health

async function checkExecutionHealth() {
  const stats = await DailyNotification.getHistoryStats();
  const recentResult = await DailyNotification.getHistory({ 
    since: Date.now() - (24 * 60 * 60 * 1000)  // Last 24 hours
  });
  const recent = recentResult.history;
  
  const successCount = recent.filter(h => h.outcome === 'success').length;
  const failureCount = recent.filter(h => h.outcome === 'failure').length;
  const successRate = successCount / recent.length;
  
  console.log(`24h Success Rate: ${(successRate * 100).toFixed(1)}%`);
  console.log(`Successes: ${successCount}, Failures: ${failureCount}`);
  
  return successRate > 0.9;  // Healthy if > 90% success rate
}

Error Handling

All methods return Promises and can reject with errors:

try {
  const schedule = await DailyNotification.getSchedule('invalid_id');
  if (!schedule) {
    console.log('Schedule not found');
  }
} catch (error) {
  console.error('Error accessing database:', error);
  // Handle error - database might be unavailable, etc.
}

Thread Safety

All database operations are executed on background threads (Kotlin Dispatchers.IO). Methods are safe to call from any thread in your TypeScript code.

Implementation Status

Implemented

  • Schedule management (CRUD operations)
  • Content cache management (CRUD operations)
  • Callback management (CRUD operations)
  • History/analytics (read operations)

⚠️ Pending Database Consolidation

  • Configuration management (Config table exists in Java DB, needs to be added to Kotlin schema)
  • See android/DATABASE_CONSOLIDATION_PLAN.md for full consolidation plan

Return Format Notes

Important: Capacitor serializes arrays wrapped in JSObject. Methods that return arrays will return them in this format:

  • getSchedules(){ schedules: Schedule[] }
  • getCallbacks(){ callbacks: Callback[] }
  • getHistory(){ history: History[] }
  • getContentCacheHistory(){ history: ContentCache[] }

This is due to Capacitor's serialization mechanism. Always access the array property from the returned object.