feat: progressive init and readiness API for cloud storage

- Add `initMode` option to all cloud storage adapters (GCS, S3, Azure)
  - 'auto' (default): progressive in cloud, strict locally
  - 'progressive': <200ms cold starts, lazy bucket validation
  - 'strict': blocking validation (current behavior)

- Add cloud environment auto-detection for:
  - Cloud Run (K_SERVICE, K_REVISION)
  - AWS Lambda (AWS_LAMBDA_FUNCTION_NAME)
  - Cloud Functions (FUNCTIONS_TARGET)
  - Azure Functions (AZURE_FUNCTIONS_ENVIRONMENT)

- Add readiness API to Brainy class:
  - `brain.ready`: Promise that resolves when init() completes
  - `brain.isFullyInitialized()`: checks if background tasks done
  - `brain.awaitBackgroundInit()`: waits for background tasks

- Lazy bucket validation on first write (not during init)
- Background count synchronization
- Update AWS/GCP deployment docs with readiness patterns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
David Snelling 2026-01-07 12:51:05 -08:00
parent 19c5e67035
commit d938a6bd4f
10 changed files with 1470 additions and 62 deletions

View file

@ -134,6 +134,12 @@ export class Brainy<T = any> implements BrainyInterface<T> {
private initialized = false
private dimensions?: number
// Ready Promise state (v7.3.0 - Unified readiness API)
// Allows consumers to await brain.ready for initialization completion
private _readyPromise: Promise<void> | null = null
private _readyResolve: (() => void) | null = null
private _readyReject: ((error: Error) => void) | null = null
// Lazy rebuild state (v5.7.7 - Production-scale lazy loading)
// Prevents race conditions when multiple queries trigger rebuild simultaneously
private lazyRebuildInProgress = false
@ -164,6 +170,13 @@ export class Brainy<T = any> implements BrainyInterface<T> {
this.setupDistributedComponents()
}
// Initialize ready Promise (v7.3.0)
// This allows consumers to await brain.ready before using the database
this._readyPromise = new Promise<void>((resolve, reject) => {
this._readyResolve = resolve
this._readyReject = reject
})
// Track this instance for shutdown hooks
Brainy.instances.push(this)
@ -308,7 +321,16 @@ export class Brainy<T = any> implements BrainyInterface<T> {
await embeddingManager.init()
console.log('✅ Embedding engine ready')
}
// v7.3.0: Resolve ready Promise - consumers awaiting brain.ready will now proceed
if (this._readyResolve) {
this._readyResolve()
}
} catch (error) {
// v7.3.0: Reject ready Promise - consumers awaiting brain.ready will receive error
if (this._readyReject) {
this._readyReject(error instanceof Error ? error : new Error(String(error)))
}
throw new Error(`Failed to initialize Brainy: ${error}`)
}
}
@ -378,6 +400,128 @@ export class Brainy<T = any> implements BrainyInterface<T> {
return this.initialized
}
/**
* Promise that resolves when Brainy is fully initialized and ready to use
*
* This Promise is created in the constructor and resolves when init() completes.
* It can be awaited multiple times safely - the result is cached.
*
* **v7.3.0 Feature**: This enables reliable readiness detection for consumers,
* especially in cloud environments where progressive initialization means
* init() returns quickly but background tasks may still be running.
*
* @example Waiting for readiness before API calls
* ```typescript
* const brain = new Brainy({ storage: { type: 'gcs', ... } })
* brain.init() // Fire and forget
*
* // Elsewhere in your code (e.g., API handler)
* await brain.ready
* const results = await brain.find({ query: 'test' })
* ```
*
* @example Server startup pattern
* ```typescript
* const brain = new Brainy()
* await brain.init()
*
* // For health check endpoint
* app.get('/health', async (req, res) => {
* try {
* await brain.ready
* res.json({ status: 'ready' })
* } catch (error) {
* res.status(503).json({ status: 'initializing', error: error.message })
* }
* })
* ```
*
* @returns Promise that resolves when init() completes, or rejects if init fails
*/
get ready(): Promise<void> {
if (!this._readyPromise) {
// This should never happen if constructor ran, but handle gracefully
return Promise.reject(new Error('Brainy not constructed properly'))
}
return this._readyPromise
}
/**
* Check if Brainy is fully initialized including all background tasks
*
* This checks both:
* 1. Basic initialization complete (init() returned)
* 2. Storage background tasks complete (bucket validation, count sync)
*
* Useful for determining if all lazy/progressive initialization is done.
*
* @returns true if all initialization including background tasks is complete
*
* @example Health check with background status
* ```typescript
* app.get('/health', (req, res) => {
* res.json({
* ready: brain.isInitialized,
* fullyInitialized: brain.isFullyInitialized(),
* status: brain.isFullyInitialized() ? 'ready' : 'warming'
* })
* })
* ```
*/
isFullyInitialized(): boolean {
if (!this.initialized) return false
// Check if storage has background init methods (cloud storage adapters)
const storage = this.storage as any
if (typeof storage?.isBackgroundInitComplete === 'function') {
return storage.isBackgroundInitComplete()
}
// Non-cloud storage adapters are fully initialized after init()
return true
}
/**
* Wait for all background initialization tasks to complete
*
* For cloud storage adapters with progressive initialization (v7.3.0+),
* this waits for:
* - Bucket/container validation
* - Count synchronization
* - Any other background tasks
*
* For non-cloud storage, this resolves immediately.
*
* **Use Case**: Call this when you need guaranteed consistency, such as:
* - Before running batch operations
* - Before reporting full system health
* - When transitioning from "initializing" to "ready" status
*
* @returns Promise that resolves when all background tasks complete
*
* @example Ensuring full initialization
* ```typescript
* const brain = new Brainy({ storage: { type: 'gcs', ... } })
* await brain.init() // Fast return in cloud (<200ms)
*
* // Optional: wait for background tasks if needed
* await brain.awaitBackgroundInit()
* console.log('All background tasks complete')
* ```
*/
async awaitBackgroundInit(): Promise<void> {
// Must be initialized first
await this.ready
// Check if storage has background init methods (cloud storage adapters)
const storage = this.storage as any
if (typeof storage?.awaitBackgroundInit === 'function') {
await storage.awaitBackgroundInit()
}
// Non-cloud storage: no background init to wait for
}
// ============= CORE CRUD OPERATIONS =============
/**