# Lifecycle

Plugins have a lifecycle with setup and teardown phases. Use `.onReady()` for initialization.

## onReady

Called once when the plugin is loaded. Use for subscriptions, timers, and async setup.

```typescript
// src/index.ts
import { definePlugin } from '@majkapp/plugin-kit';

const plugin = definePlugin('my-plugin', 'My Plugin', '1.0.0')
  .pluginRoot(__dirname)

  // Define functions first
  .function('getEvents', { /* ... */ })
  .function('clearEvents', { /* ... */ })

  // Lifecycle hook - called when plugin loads
  .onReady(async (ctx, cleanup) => {
    ctx.logger.info('Plugin initializing...');

    // Perform async setup
    await initializePluginData(ctx);

    // Register cleanup (called when plugin unloads/reloads)
    cleanup(() => {
      ctx.logger.info('Plugin cleaning up');
      // Cleanup code here
    });

    ctx.logger.info('Plugin ready');
  })

  .build();
```

## Event Bus Subscription (Real Example)

From `samples/full-featured/src/index.ts`:

```typescript
// In-memory event log (scoped to plugin instance)
const eventLog: any[] = [];
const maxEvents = 1000;

const plugin = definePlugin('full-featured', 'Full Featured', '1.0.0')
  .pluginRoot(__dirname)

  // Functions that access eventLog
  .function('getEvents', {
    description: 'Get all logged events from the event bus',
    input: { /* ... */ },
    output: { /* ... */ },
    handler: async (input, ctx) => {
      return {
        events: eventLog,
        count: eventLog.length
      };
    }
  })

  .function('clearEvents', {
    description: 'Clear the event log',
    input: { /* ... */ },
    output: { /* ... */ },
    handler: async (input, ctx) => {
      const count = eventLog.length;
      eventLog.length = 0;  // Clear array
      ctx.logger.info(`Cleared ${count} events`);
      return {
        success: true,
        clearedCount: count
      };
    }
  })

  // Subscribe to events when plugin loads
  .onReady(async (ctx, cleanup) => {
    ctx.logger.info('Full Featured plugin ready - subscribing to all events');

    // Subscribe to ALL events from MAJK event bus
    // CRITICAL: Must await the subscription (returns Promise<Subscription>)
    const subscription = await ctx.majk.eventBus.subscribeAll((event: any) => {
      // Build a log entry for each event
      const logEntry = {
        id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
        timestamp: new Date().toISOString(),
        entityType: event.entityType,  // 'conversation', 'todo', 'project', etc.
        eventType: event.type,         // 'created', 'updated', 'deleted'
        entityId: event.entity?.id || 'unknown',
        entitySummary: event.entity ? {
          ...( event.entity.name && { name: event.entity.name }),
          ...( event.entity.title && { title: event.entity.title }),
          ...( event.entity.status && { status: event.entity.status })
        } : {},
        metadata: event.metadata || {},
        fullEntity: event.entity
      };

      // Add to front of array (newest first), enforce max size
      eventLog.unshift(logEntry);
      if (eventLog.length > maxEvents) {
        eventLog.length = maxEvents;  // Truncate old events
      }

      ctx.logger.info(`Event logged: ${event.entityType}.${event.type} - Total: ${eventLog.length}`);
    });

    // ALWAYS register cleanup for subscriptions, timers, etc.
    // This runs when plugin is disabled or reloaded
    cleanup(() => {
      if (subscription && typeof subscription.unsubscribe === 'function') {
        subscription.unsubscribe();
        ctx.logger.info('Unsubscribed from EventBus');
      }
    });
  })

  .build();
```

## Timer Example

```typescript
.onReady(async (ctx, cleanup) => {
  ctx.logger.info('Starting periodic task');

  // Start interval timer
  const intervalId = setInterval(async () => {
    try {
      // Perform periodic task
      const stats = await calculateStats(ctx);
      await ctx.storage.set('last-stats', stats);
      ctx.logger.info('Stats updated', stats);
    } catch (error) {
      ctx.logger.error('Periodic task failed', { error: error.message });
    }
  }, 60000);  // Every 60 seconds

  // Clean up timer on unload
  cleanup(() => {
    clearInterval(intervalId);
    ctx.logger.info('Stopped periodic task');
  });
})
```

## Initialize Storage

```typescript
.onReady(async (ctx, cleanup) => {
  ctx.logger.info('Initializing plugin storage');

  // Check if first run
  const initialized = await ctx.storage.get('initialized');

  if (!initialized) {
    // First-time setup
    await ctx.storage.set('initialized', true);
    await ctx.storage.set('sessions', []);
    await ctx.storage.set('settings', {
      theme: 'light',
      notifications: true
    });

    ctx.logger.info('Plugin storage initialized');
  } else {
    ctx.logger.info('Plugin storage already initialized');
  }

  // No cleanup needed for storage initialization
})
```

## Multiple Cleanups

```typescript
.onReady(async (ctx, cleanup) => {
  // Setup 1: Event subscription
  const subscription = await ctx.majk.eventBus.subscribeAll((event) => {
    // Handle event
  });

  cleanup(() => {
    subscription.unsubscribe();
    ctx.logger.info('Unsubscribed from events');
  });

  // Setup 2: Timer
  const timerId = setInterval(() => {
    // Periodic task
  }, 30000);

  cleanup(() => {
    clearInterval(timerId);
    ctx.logger.info('Stopped timer');
  });

  // Setup 3: External connection
  const connection = await connectToExternalService();

  cleanup(async () => {
    await connection.disconnect();
    ctx.logger.info('Disconnected from external service');
  });

  ctx.logger.info('Plugin fully initialized');
})
```

## Error Handling in onReady

```typescript
.onReady(async (ctx, cleanup) => {
  try {
    ctx.logger.info('Plugin initializing...');

    // Attempt initialization
    const subscription = await ctx.majk.eventBus.subscribeAll((event) => {
      // Handle event
    });

    cleanup(() => {
      subscription.unsubscribe();
    });

    ctx.logger.info('Plugin initialized successfully');

  } catch (error) {
    ctx.logger.error('Plugin initialization failed', {
      error: error.message,
      stack: error.stack
    });

    // Plugin will still load, but initialization failed
    // Consider storing error state for functions to check
    await ctx.storage.set('init-error', error.message);
  }
})
```

## When to Use onReady

**Use onReady for:**
- Event bus subscriptions
- Timers and intervals
- External service connections
- Background workers
- Initial data loading
- Storage initialization

**Don't use onReady for:**
- Simple function definitions (no setup needed)
- Synchronous operations (use functions instead)
- Operations that should run per-request (use function handlers)

## Cleanup Patterns

### Pattern 1: Subscription Cleanup

```typescript
cleanup(() => {
  if (subscription && typeof subscription.unsubscribe === 'function') {
    subscription.unsubscribe();
    ctx.logger.info('Unsubscribed');
  }
});
```

### Pattern 2: Timer Cleanup

```typescript
cleanup(() => {
  clearInterval(intervalId);
  clearTimeout(timeoutId);
  ctx.logger.info('Timers cleared');
});
```

### Pattern 3: Async Cleanup

```typescript
cleanup(async () => {
  await connection.close();
  await saveState(ctx);
  ctx.logger.info('Async cleanup complete');
});
```

### Pattern 4: Resource Cleanup

```typescript
cleanup(() => {
  try {
    resource.release();
    ctx.logger.info('Resource released');
  } catch (error) {
    ctx.logger.error('Cleanup failed', { error: error.message });
  }
});
```

## Testing with onReady

onReady is tested through integration tests:

```typescript
// tests/plugin/integration/lifecycle.test.js
import { test } from '@majkapp/plugin-test';
import { loadPlugin, unloadPlugin } from '@majkapp/plugin-test';
import assert from 'assert';

test('plugin initializes event subscription', async () => {
  const plugin = await loadPlugin();

  // Trigger an event
  await triggerTestEvent({ type: 'test', data: {} });

  // Wait briefly for event to be processed
  await new Promise(resolve => setTimeout(resolve, 100));

  // Verify event was logged
  const result = await invoke('getEvents', {}, { context: plugin.context });
  assert.ok(result.events.length > 0);

  // Cleanup
  await unloadPlugin(plugin);
});
```

## Best Practices

### 1. Always Register Cleanup

```typescript
// Good: Cleanup registered
.onReady(async (ctx, cleanup) => {
  const subscription = await ctx.majk.eventBus.subscribeAll(handler);

  cleanup(() => {
    subscription.unsubscribe();
  });
})

// Bad: No cleanup - memory leak!
.onReady(async (ctx, cleanup) => {
  const subscription = await ctx.majk.eventBus.subscribeAll(handler);
  // Missing cleanup!
})
```

### 2. Log Initialization Steps

```typescript
.onReady(async (ctx, cleanup) => {
  ctx.logger.info('Plugin initializing...');

  const subscription = await ctx.majk.eventBus.subscribeAll(handler);
  ctx.logger.info('Event subscription created');

  cleanup(() => {
    subscription.unsubscribe();
    ctx.logger.info('Event subscription cleaned up');
  });

  ctx.logger.info('Plugin initialization complete');
})
```

### 3. Handle Errors Gracefully

```typescript
.onReady(async (ctx, cleanup) => {
  try {
    const subscription = await ctx.majk.eventBus.subscribeAll(handler);

    cleanup(() => {
      try {
        subscription.unsubscribe();
      } catch (error) {
        ctx.logger.error('Cleanup error', { error: error.message });
      }
    });
  } catch (error) {
    ctx.logger.error('Initialization error', { error: error.message });
    // Store error state for functions to check
    await ctx.storage.set('init-error', error.message);
  }
})
```

## Complete Example

```typescript
// src/index.ts
import { definePlugin } from '@majkapp/plugin-kit';

const eventLog: any[] = [];
const maxEvents = 500;
let statsTimer: NodeJS.Timeout;

const plugin = definePlugin('my-plugin', 'My Plugin', '1.0.0')
  .pluginRoot(__dirname)

  .function('getEvents', {
    description: 'Get logged events',
    input: { /* ... */ },
    output: { /* ... */ },
    handler: async () => ({ events: eventLog })
  })

  .function('getStats', {
    description: 'Get cached statistics',
    input: { /* ... */ },
    output: { /* ... */ },
    handler: async (input, ctx) => {
      return await ctx.storage.get('cached-stats') || { count: 0 };
    }
  })

  .onReady(async (ctx, cleanup) => {
    ctx.logger.info('My Plugin initializing...');

    // 1. Initialize storage
    const initialized = await ctx.storage.get('initialized');
    if (!initialized) {
      await ctx.storage.set('initialized', true);
      await ctx.storage.set('cached-stats', { count: 0, timestamp: Date.now() });
      ctx.logger.info('Storage initialized');
    }

    // 2. Subscribe to events
    try {
      const subscription = await ctx.majk.eventBus.subscribeAll((event) => {
        eventLog.unshift({
          id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
          timestamp: new Date().toISOString(),
          type: event.type,
          entityType: event.entityType
        });

        if (eventLog.length > maxEvents) {
          eventLog.length = maxEvents;
        }
      });

      cleanup(() => {
        subscription.unsubscribe();
        ctx.logger.info('Event subscription cleaned up');
      });

      ctx.logger.info('Event subscription active');
    } catch (error) {
      ctx.logger.error('Event subscription failed', { error: error.message });
    }

    // 3. Start periodic stats update
    statsTimer = setInterval(async () => {
      try {
        const stats = {
          count: eventLog.length,
          timestamp: Date.now()
        };
        await ctx.storage.set('cached-stats', stats);
        ctx.logger.debug('Stats updated', stats);
      } catch (error) {
        ctx.logger.error('Stats update failed', { error: error.message });
      }
    }, 60000);  // Every 60 seconds

    cleanup(() => {
      clearInterval(statsTimer);
      ctx.logger.info('Stats timer stopped');
    });

    ctx.logger.info('My Plugin ready');
  })

  .build();

export = plugin;
```

## Next Steps

Run `npx @majkapp/plugin-kit --context` - ctx API for onReady
Run `npx @majkapp/plugin-kit --testing` - Test plugin lifecycle
Run `npx @majkapp/plugin-kit --services` - Organize functions into services
