{"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 { 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\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: [`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 previousStatus = this.status\n    this.status = newStatus\n\n    // Emit event\n    this.events.emitStatusChange(newStatus, previousStatus)\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    this.validateStatusTransition(this.status, `ready`)\n    // Can transition to ready from loading state\n    if (this.status === `loading`) {\n      this.setStatus(`ready`, true)\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        const callbacks = [...this.onFirstReadyCallbacks]\n        this.onFirstReadyCallbacks = []\n        callbacks.forEach((callback) => callback())\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      if (this.changes.changeSubscriptions.size > 0) {\n        this.changes.emitEmptyReadyEvent()\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    // 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      // 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\n      // Call any pending onFirstReady callbacks before clearing them.\n      // This ensures preload() promises resolve during cleanup instead of hanging.\n      const callbacks = [...this.onFirstReadyCallbacks]\n      this.onFirstReadyCallbacks = []\n      callbacks.forEach((callback) => {\n        try {\n          callback()\n        } catch (error) {\n          console.error(\n            `${this.config.id ? `[${this.config.id}] ` : ``}Error in onFirstReady callback during cleanup:`,\n            error,\n          )\n        }\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      // Finally, cleanup event handlers after the event has been fired\n      this.events.cleanup()\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  }\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","CleanupQueue","safeCancelIdleCallback","safeRequestIdleCallback"],"mappings":";;;;;AAmBO,MAAM,2BAKX;AAAA;AAAA;AAAA;AAAA,EAkBA,YAAY,QAAkD,IAAY;AAT1E,SAAO,SAA2B;AAClC,SAAO,eAAe;AACtB,SAAO,yBAAyB;AAChC,SAAO,wBAA2C,CAAA;AAClD,SAAQ,iBAAgC;AAMtC,SAAK,SAAS;AACd,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,QAAQ,MAML;AACD,SAAK,UAAU,KAAK;AACpB,SAAK,SAAS,KAAK;AACnB,SAAK,UAAU,KAAK;AACpB,SAAK,OAAO,KAAK;AACjB,SAAK,QAAQ,KAAK;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKO,yBACL,MACA,IACM;AACN,QAAI,SAAS,IAAI;AAEf;AAAA,IACF;AACA,UAAM,mBAGF;AAAA,MACF,MAAM,CAAC,WAAW,SAAS,YAAY;AAAA,MACvC,SAAS,CAAC,SAAS,SAAS,YAAY;AAAA,MACxC,OAAO,CAAC,cAAc,OAAO;AAAA,MAC7B,OAAO,CAAC,cAAc,MAAM;AAAA,MAC5B,cAAc,CAAC,WAAW,OAAO;AAAA,IAAA;AAGnC,QAAI,CAAC,iBAAiB,IAAI,EAAE,SAAS,EAAE,GAAG;AACxC,YAAM,IAAIA,OAAAA,uCAAuC,MAAM,IAAI,KAAK,EAAE;AAAA,IACpE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,UACL,WACA,aAAsB,OAChB;AACN,QAAI,cAAc,WAAW,CAAC,YAAY;AAIxC,YAAM,IAAIC,OAAAA;AAAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AACA,SAAK,yBAAyB,KAAK,QAAQ,SAAS;AACpD,UAAM,iBAAiB,KAAK;AAC5B,SAAK,SAAS;AAGd,SAAK,OAAO,iBAAiB,WAAW,cAAc;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,yBAAyB,WAAyB;AACvD,YAAQ,KAAK,QAAA;AAAA,MACX,KAAK;AACH,cAAM,IAAIC,OAAAA,4BAA4B,WAAW,KAAK,EAAE;AAAA,MAC1D,KAAK;AAEH,aAAK,KAAK,UAAA;AACV;AAAA,IAAA;AAAA,EAEN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQO,YAAkB;AACvB,SAAK,yBAAyB,KAAK,QAAQ,OAAO;AAElD,QAAI,KAAK,WAAW,WAAW;AAC7B,WAAK,UAAU,SAAS,IAAI;AAG5B,UAAI,CAAC,KAAK,cAAc;AACtB,aAAK,eAAe;AAGpB,YAAI,CAAC,KAAK,wBAAwB;AAChC,eAAK,yBAAyB;AAAA,QAChC;AAEA,cAAM,YAAY,CAAC,GAAG,KAAK,qBAAqB;AAChD,aAAK,wBAAwB,CAAA;AAC7B,kBAAU,QAAQ,CAAC,aAAa,SAAA,CAAU;AAAA,MAC5C;AAGA,UAAI,KAAK,QAAQ,oBAAoB,OAAO,GAAG;AAC7C,aAAK,QAAQ,oBAAA;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,eAAqB;AAC1B,UAAM,SAAS,KAAK,OAAO,UAAU;AAKrC,QAAI,UAAU,KAAK,CAAC,OAAO,SAAS,MAAM,GAAG;AAC3C;AAAA,IACF;AAEAC,iBAAAA,aAAa,YAAA,EAAc,SAAS,MAAM,QAAQ,MAAM;AACtD,UAAI,KAAK,QAAQ,2BAA2B,GAAG;AAE7C,aAAK,oBAAA;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMO,gBAAsB;AAC3BA,iBAAAA,aAAa,YAAA,EAAc,OAAO,IAAI;AAEtC,QAAI,KAAK,mBAAmB,MAAM;AAChCC,uBAAAA,uBAAuB,KAAK,cAAc;AAC1C,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAA4B;AAElC,QAAI,KAAK,mBAAmB,MAAM;AAChCA,uBAAAA,uBAAuB,KAAK,cAAc;AAAA,IAC5C;AAIA,SAAK,iBAAiBC,iBAAAA;AAAAA,MACpB,CAAC,aAAa;AAEZ,YAAI,KAAK,QAAQ,2BAA2B,GAAG;AAC7C,gBAAM,mBAAmB,KAAK,eAAe,QAAQ;AAErD,cAAI,kBAAkB;AACpB,iBAAK,iBAAiB;AAAA,UACxB;AAAA,QACF,OAAO;AAEL,eAAK,iBAAiB;AAAA,QACxB;AAAA,MACF;AAAA,MACA,EAAE,SAAS,IAAA;AAAA,IAAK;AAAA,EAEpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,eAAe,UAA0C;AAG/D,UAAM,UACJ,CAAC,YAAY,SAAS,kBAAkB,KAAK,SAAS;AAExD,QAAI,SAAS;AAEX,WAAK,KAAK,QAAA;AACV,WAAK,MAAM,QAAA;AACX,WAAK,QAAQ,QAAA;AACb,WAAK,QAAQ,QAAA;AAEbF,mBAAAA,aAAa,YAAA,EAAc,OAAO,IAAI;AAEtC,WAAK,eAAe;AAIpB,YAAM,YAAY,CAAC,GAAG,KAAK,qBAAqB;AAChD,WAAK,wBAAwB,CAAA;AAC7B,gBAAU,QAAQ,CAAC,aAAa;AAC9B,YAAI;AACF,mBAAA;AAAA,QACF,SAAS,OAAO;AACd,kBAAQ;AAAA,YACN,GAAG,KAAK,OAAO,KAAK,IAAI,KAAK,OAAO,EAAE,OAAO,EAAE;AAAA,YAC/C;AAAA,UAAA;AAAA,QAEJ;AAAA,MACF,CAAC;AAID,WAAK,UAAU,YAAY;AAG3B,WAAK,OAAO,QAAA;AAEZ,aAAO;AAAA,IACT,OAAO;AAEL,WAAK,oBAAA;AACL,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOO,aAAa,UAA4B;AAE9C,QAAI,KAAK,cAAc;AACrB,eAAA;AACA;AAAA,IACF;AAEA,SAAK,sBAAsB,KAAK,QAAQ;AAAA,EAC1C;AAAA,EAEO,UAAgB;AAErB,QAAI,KAAK,mBAAmB,MAAM;AAChCC,uBAAAA,uBAAuB,KAAK,cAAc;AAC1C,WAAK,iBAAiB;AAAA,IACxB;AAGA,SAAK,eAAA;AAAA,EACP;AACF;;"}