Skip to content

Database Cleanup and Shutdown

The ReactiveDatabase provides a shutdown() method to properly clean up resources and close connections when the database is no longer needed. The shutdown() method is a crucial cleanup mechanism that ensures all database resources are properly released. It:

  • Closes the database connection
  • Cleans up event listeners
  • Runs registered cleanup callbacks
  • Prevents memory leaks and resource wastage

When to Use shutdown()

You should call shutdown() when:

  • Your application is closing/unmounting
  • You no longer want to use the database
typescript
const db = new ReactiveDatabase(config)
// ... use database
await db.shutdown() // Clean up when done

How shutdown() Works

Internal Cleanup Process

  1. Set Shutdown Flag

    • Sets shuttingDown flag to prevent unexpected behavior during cleanup
  2. Execute Cleanup Callbacks

    • Runs all registered cleanup callbacks concurrently
    • Callbacks can be registered by models and relationships
    • Waits for all callbacks to complete
  3. Close Database Connection

    • Closes the Dexie database connection
    • Disables auto-open to prevent reconnection
    • Waits for the 'close' event confirmation
  4. Clear Model Constructors

    • Removes all model constructor references
    • Helps prevent memory leaks
  5. Remove Event Listeners

    • Cleans up error bus listeners
    • Removes all log event listeners
    • Ensures no lingering event handlers

Best Practices

1. Always await shutdown()

The shutdown process is asynchronous, so always await its completion:

typescript
// Good
await db.shutdown()

// Bad - may lead to incomplete cleanup
db.shutdown()

2. Prevent Further Operations

After calling shutdown, the database instance should not be used:

typescript
const db = new ReactiveDatabase(config)
await db.shutdown()

// Don't do this - database is shut down
await db.model('users').find(1) // Will fail

3. Handle Cleanup in Components

In frontend frameworks, implement shutdown in cleanup/unmount hooks:

typescript
import { useEffect } from 'react'
import { ReactiveDatabase } from '@nhtio/web-re-active-record'

function MyComponent() {
  useEffect(() => {
    const db = new ReactiveDatabase(config)
    
    return () => {
      // Cleanup when component unmounts
      await db.shutdown()
    }
  }, [])
  
  return <div>My Component</div>
}
typescript
import { onUnmounted } from 'vue'
import { ReactiveDatabase } from '@nhtio/web-re-active-record'

export default {
  setup() {
    const db = new ReactiveDatabase(config)
    
    onUnmounted(async () => {
      // Cleanup when component unmounts
      await db.shutdown()
    })
    
    return {}
  }
}
typescript
import { ReactiveDatabase } from '@nhtio/web-re-active-record'

export default {
  data() {
    return {
      db: new ReactiveDatabase(config)
    }
  },
  
  beforeUnmount() {
    // Cleanup when component unmounts
    await this.db.shutdown()
  }
}
typescript
import { Component, OnDestroy } from '@angular/core'
import { ReactiveDatabase } from '@nhtio/web-re-active-record'

@Component({
  selector: 'app-my-component',
  template: '<div>My Component</div>'
})
export class MyComponent implements OnDestroy {
  private db: ReactiveDatabase

  constructor() {
    this.db = new ReactiveDatabase(config)
  }

  async ngOnDestroy() {
    // Cleanup when component is destroyed
    await this.db.shutdown()
  }
}

4. Manage Multiple Instances

When working with multiple database instances, shut them down independently:

typescript
const db1 = new ReactiveDatabase(config1)
const db2 = new ReactiveDatabase(config2)

// Shutdown each instance
await Promise.all([
  db1.shutdown(),
  db2.shutdown()
])

Error Handling

The shutdown process is designed to be robust, but you should still handle potential errors:

typescript
try {
  await db.shutdown()
} catch (error) {
  console.error('Error during shutdown:', error)
  // Implement fallback cleanup if needed
}

Impact on Running Operations

When shutdown is called:

  • Ongoing operations are allowed to complete
  • New operations are prevented
  • Cleanup process begins after current operations finish
  • Event listeners are maintained until cleanup is complete

Logging During Shutdown

The shutdown process generates various log events that can be monitored:

typescript
db.logger.on('debug', (msg) => console.debug('Shutdown debug:', msg))
db.logger.on('info', (msg) => console.info('Shutdown info:', msg))
db.logger.on('error', (msg) => console.error('Shutdown error:', msg))

await db.shutdown()
// Debug: Starting database shutdown
// Debug: Running cleanup callbacks
// Debug: Database connection closing
// Info: Database shutdown complete

Static Shutdown Method

ReactiveDatabase provides a static shutdown() method that can close all database connections in the current context:

typescript
import { ReactiveDatabase } from '@nhtio/web-re-active-record'

// Shutdown all database instances
await ReactiveDatabase.shutdown()

This is particularly useful when:

  • You have multiple database instances that need to be cleaned up
  • You're implementing application-wide cleanup
  • You need to ensure all database connections are closed during page unload

How Static Shutdown Works

The static shutdown method:

  1. Tracks all ReactiveDatabase instances created in the current context
  2. When called, attempts to shut down each instance
  3. Continues even if individual shutdowns fail
  4. Returns a Promise that resolves when all shutdowns are complete

Example usage in an application cleanup:

typescript
// Application cleanup
window.addEventListener('unload', () => {
  // Ensure all database connections are closed
  await ReactiveDatabase.shutdown()
})

Using with multiple databases:

typescript
const userDb = new ReactiveDatabase({
  namespace: 'users',
  // ... other config
})

const productDb = new ReactiveDatabase({
  namespace: 'products',
  // ... other config
})

// Later, shutdown all databases at once
await ReactiveDatabase.shutdown()
// Both userDb and productDb are now shut down