{"version":3,"file":"lifecycle.cjs","sources":["../../../src/collection/lifecycle.ts"],"sourcesContent":["import {\n  CollectionInErrorStateError,\n  CollectionStateError,\n  InvalidCollectionStatusTransitionError,\n} from '../errors'\nimport {\n  safeCancelIdleCallback,\n  safeRequestIdleCallback,\n} from '../utils/browser-polyfills'\nimport { runAllCallbacks } from '../utils/callbacks'\nimport { CleanupQueue } from './cleanup-queue'\nimport type { IdleCallbackDeadline } from '../utils/browser-polyfills'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\nimport type { CollectionConfig, CollectionStatus } from '../types'\nimport type { CollectionEventsManager } from './events'\nimport type { CollectionIndexesManager } from './indexes'\nimport type { CollectionChangesManager } from './changes'\nimport type { CollectionSyncManager } from './sync'\nimport type { CollectionStateManager } from './state'\n\nexport class CollectionLifecycleManager<\n  TOutput extends object = Record<string, unknown>,\n  TKey extends string | number = string | number,\n  TSchema extends StandardSchemaV1 = StandardSchemaV1,\n  TInput extends object = TOutput,\n> {\n  private config: CollectionConfig<TOutput, TKey, TSchema>\n  private id: string\n  private indexes!: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n  private events!: CollectionEventsManager\n  private changes!: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n  private sync!: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n  private state!: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n\n  public status: CollectionStatus = `idle`\n  public hasBeenReady = false\n  public hasReceivedFirstCommit = false\n  public onFirstReadyCallbacks: Array<() => void> = []\n  private idleCallbackId: number | null = null\n  private syncError: unknown\n  private statusRevision = 0\n  private cleaningUp = false\n\n  /**\n   * Creates a new CollectionLifecycleManager instance\n   */\n  constructor(config: CollectionConfig<TOutput, TKey, TSchema>, id: string) {\n    this.config = config\n    this.id = id\n  }\n\n  setDeps(deps: {\n    indexes: CollectionIndexesManager<TOutput, TKey, TSchema, TInput>\n    events: CollectionEventsManager\n    changes: CollectionChangesManager<TOutput, TKey, TSchema, TInput>\n    sync: CollectionSyncManager<TOutput, TKey, TSchema, TInput>\n    state: CollectionStateManager<TOutput, TKey, TSchema, TInput>\n  }) {\n    this.indexes = deps.indexes\n    this.events = deps.events\n    this.changes = deps.changes\n    this.sync = deps.sync\n    this.state = deps.state\n  }\n\n  /**\n   * Validates state transitions to prevent invalid status changes\n   */\n  public validateStatusTransition(\n    from: CollectionStatus,\n    to: CollectionStatus,\n  ): void {\n    if (from === to) {\n      // Allow same state transitions\n      return\n    }\n    const validTransitions: Record<\n      CollectionStatus,\n      Array<CollectionStatus>\n    > = {\n      idle: [`loading`, `error`, `cleaned-up`],\n      loading: [`ready`, `error`, `cleaned-up`],\n      ready: [`cleaned-up`, `error`],\n      error: [`ready`, `cleaned-up`, `idle`],\n      'cleaned-up': [`loading`, `error`],\n    }\n\n    if (!validTransitions[from].includes(to)) {\n      throw new InvalidCollectionStatusTransitionError(from, to, this.id)\n    }\n  }\n\n  /**\n   * Safely update the collection status with validation\n   * @private\n   */\n  public setStatus(\n    newStatus: CollectionStatus,\n    allowReady: boolean = false,\n  ): void {\n    if (newStatus === `ready` && !allowReady) {\n      // setStatus('ready') is an internal method that should not be called directly\n      // Instead, use markReady to transition to ready triggering the necessary events\n      // and side effects.\n      throw new CollectionStateError(\n        `You can't directly call \"setStatus('ready'). You must use markReady instead.`,\n      )\n    }\n    this.validateStatusTransition(this.status, newStatus)\n    const revision = ++this.statusRevision\n    const previousStatus = this.status\n    this.status = newStatus\n\n    // Emit event\n    this.events.emitStatusChange(\n      newStatus,\n      previousStatus,\n      () => this.statusRevision === revision,\n    )\n  }\n\n  /**\n   * Validates that the collection is in a usable state for data operations\n   * @private\n   */\n  public validateCollectionUsable(operation: string): void {\n    switch (this.status) {\n      case `error`:\n        throw new CollectionInErrorStateError(operation, this.id)\n      case `cleaned-up`:\n        // Automatically restart the collection when operations are called on cleaned-up collections\n        this.sync.startSync()\n        break\n    }\n  }\n\n  /**\n   * Mark the collection as ready for use\n   * This is called by sync implementations to explicitly signal that the collection is ready,\n   * providing a more intuitive alternative to using commits for readiness signaling\n   * @private - Should only be called by sync implementations\n   */\n  public markReady(): void {\n    const failure = this.applyReadyTransition()\n    if (failure) throw failure.error\n  }\n\n  /** @internal Capture ready-effect failures while the sync entry completes. */\n  public markReadyDuringSyncStart(): { error: unknown } | undefined {\n    return this.applyReadyTransition()\n  }\n\n  private applyReadyTransition(): { error: unknown } | undefined {\n    this.validateStatusTransition(this.status, `ready`)\n    // A successful initial sync or recovery establishes a ready snapshot.\n    if (this.status === `loading` || this.status === `error`) {\n      this.syncError = undefined\n      const readyRevision = this.statusRevision + 1\n      this.setStatus(`ready`, true)\n\n      // A status listener can synchronously supersede this transition, even\n      // when it restarts the Collection back to ready before returning.\n      if (\n        (this.status as CollectionStatus) !== `ready` ||\n        this.statusRevision !== readyRevision\n      ) {\n        return undefined\n      }\n\n      const readyEffects: Array<() => void> = []\n\n      // Call any registered first ready callbacks (only on first time becoming ready)\n      if (!this.hasBeenReady) {\n        this.hasBeenReady = true\n\n        // Also mark as having received first commit for backwards compatibility\n        if (!this.hasReceivedFirstCommit) {\n          this.hasReceivedFirstCommit = true\n        }\n\n        readyEffects.push(...this.onFirstReadyCallbacks)\n        this.onFirstReadyCallbacks = []\n      }\n      // Notify dependents when markReady is called, after status is set\n      // This ensures live queries get notified when their dependencies become ready\n      readyEffects.push(() => this.changes.emitEmptyReadyEvent())\n      try {\n        runAllCallbacks(readyEffects)\n      } catch (error) {\n        return { error }\n      }\n    }\n    return undefined\n  }\n\n  /** Mark an asynchronous sync failure after sync has started. */\n  public markError(error?: unknown): void {\n    this.validateStatusTransition(this.status, `error`)\n    this.syncError = error\n    this.setStatus(`error`)\n  }\n\n  /** Return the cause supplied by the current sync session, if any. */\n  public getSyncError(): unknown {\n    return this.syncError\n  }\n\n  public assertCanStartSync(): void {\n    if (this.cleaningUp) {\n      throw new CollectionStateError(\n        `Cannot start collection \"${this.id}\" during cleanup. Restart after cleanup() completes.`,\n      )\n    }\n  }\n\n  /**\n   * Start the garbage collection timer\n   * Called when the collection becomes inactive (no subscribers)\n   */\n  public startGCTimer(): void {\n    const gcTime = this.config.gcTime ?? 300000 // 5 minutes default\n\n    // If gcTime is 0, negative, or non-finite (Infinity, -Infinity, NaN), GC is disabled.\n    // Note: setTimeout with Infinity coerces to 0 via ToInt32, causing immediate GC,\n    // so we must explicitly check for non-finite values here.\n    if (gcTime <= 0 || !Number.isFinite(gcTime)) {\n      return\n    }\n\n    CleanupQueue.getInstance().schedule(this, gcTime, () => {\n      if (this.changes.activeSubscribersCount === 0) {\n        // Schedule cleanup during idle time to avoid blocking the UI thread\n        this.scheduleIdleCleanup()\n      }\n    })\n  }\n\n  /**\n   * Cancel the garbage collection timer\n   * Called when the collection becomes active again\n   */\n  public cancelGCTimer(): void {\n    CleanupQueue.getInstance().cancel(this)\n    // Also cancel any pending idle cleanup\n    if (this.idleCallbackId !== null) {\n      safeCancelIdleCallback(this.idleCallbackId)\n      this.idleCallbackId = null\n    }\n  }\n\n  /**\n   * Schedule cleanup to run during browser idle time\n   * This prevents blocking the UI thread during cleanup operations\n   */\n  private scheduleIdleCleanup(): void {\n    // Cancel any existing idle callback\n    if (this.idleCallbackId !== null) {\n      safeCancelIdleCallback(this.idleCallbackId)\n    }\n\n    // Schedule cleanup with a timeout of 1 second\n    // This ensures cleanup happens even if the browser is busy\n    this.idleCallbackId = safeRequestIdleCallback(\n      (deadline) => {\n        // Perform cleanup if we still have no subscribers\n        if (this.changes.activeSubscribersCount === 0) {\n          const cleanupCompleted = this.performCleanup(deadline)\n          // Only clear the callback ID if cleanup actually completed\n          if (cleanupCompleted) {\n            this.idleCallbackId = null\n          }\n        } else {\n          // No need to cleanup, clear the callback ID\n          this.idleCallbackId = null\n        }\n      },\n      { timeout: 1000 },\n    )\n  }\n\n  /**\n   * Perform cleanup operations, optionally in chunks during idle time\n   * @returns true if cleanup was completed, false if it was rescheduled\n   */\n  private performCleanup(deadline?: IdleCallbackDeadline): boolean {\n    // Nested cleanup belongs to this retirement, not a new lifecycle turn.\n    if (this.cleaningUp) return true\n    // If we have a deadline, we can potentially split cleanup into chunks\n    // For now, we'll do all cleanup at once but check if we have time\n    const hasTime =\n      !deadline || deadline.timeRemaining() > 0 || deadline.didTimeout\n\n    if (hasTime) {\n      this.cleaningUp = true\n      try {\n        // Perform all cleanup operations except events\n        this.sync.cleanup()\n        this.state.cleanup()\n        this.changes.cleanup()\n        this.indexes.cleanup()\n\n        CleanupQueue.getInstance().cancel(this)\n\n        this.hasBeenReady = false\n        this.syncError = undefined\n\n        // Cleanup is not readiness. Sync cleanup rejects pending preload callers;\n        // first-ready listeners belong to the discarded run.\n        this.onFirstReadyCallbacks = []\n      } finally {\n        this.cleaningUp = false\n      }\n\n      // Set status to cleaned-up after everything is cleaned up\n      // This fires the status:change event to notify listeners\n      this.setStatus(`cleaned-up`)\n\n      // Active collection subscriptions still depend on lifecycle events.\n      // Once the last subscriber leaves, its GC cleanup clears the handlers.\n      if (this.changes.activeSubscribersCount === 0) {\n        this.events.cleanup()\n      }\n\n      return true\n    } else {\n      // If we don't have time, reschedule for the next idle period\n      this.scheduleIdleCleanup()\n      return false\n    }\n  }\n\n  /**\n   * Register a callback to be executed when the collection first becomes ready\n   * Useful for preloading collections\n   * @param callback Function to call when the collection first becomes ready\n   */\n  public onFirstReady(callback: () => void): () => void {\n    // If already ready, call immediately\n    if (this.hasBeenReady) {\n      callback()\n      return () => {}\n    }\n\n    this.onFirstReadyCallbacks.push(callback)\n    return () => {\n      const index = this.onFirstReadyCallbacks.indexOf(callback)\n      if (index !== -1) {\n        this.onFirstReadyCallbacks.splice(index, 1)\n      }\n    }\n  }\n\n  public cleanup(): void {\n    // Cancel any pending idle cleanup\n    if (this.idleCallbackId !== null) {\n      safeCancelIdleCallback(this.idleCallbackId)\n      this.idleCallbackId = null\n    }\n\n    // Perform cleanup immediately (used when explicitly called)\n    this.performCleanup()\n  }\n}\n"],"names":["InvalidCollectionStatusTransitionError","CollectionStateError","CollectionInErrorStateError","runAllCallbacks","CleanupQueue","safeCancelIdleCallback","safeRequestIdleCallback"],"mappings":"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"}