{"version":3,"file":"local-only.cjs","sources":["../../src/local-only.ts"],"sourcesContent":["import type {\n  BaseCollectionConfig,\n  CollectionConfig,\n  DeleteMutationFnParams,\n  InferSchemaOutput,\n  InsertMutationFnParams,\n  OperationType,\n  PendingMutation,\n  SyncConfig,\n  UpdateMutationFnParams,\n  UtilsRecord,\n} from './types'\nimport type { Collection } from './collection/index'\nimport type { StandardSchemaV1 } from '@standard-schema/spec'\n\n/**\n * Configuration interface for Local-only collection options\n * @template T - The type of items in the collection\n * @template TSchema - The schema type for validation\n * @template TKey - The type of the key returned by `getKey`\n */\nexport interface LocalOnlyCollectionConfig<\n  T extends object = object,\n  TSchema extends StandardSchemaV1 = never,\n  TKey extends string | number = string | number,\n> extends Omit<\n  BaseCollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils>,\n  `gcTime` | `startSync`\n> {\n  /**\n   * Optional initial data to populate the collection with on creation\n   * This data will be applied during the initial sync process\n   */\n  initialData?: Array<T>\n}\n\n/**\n * Local-only collection utilities type\n */\nexport interface LocalOnlyCollectionUtils extends UtilsRecord {\n  /**\n   * Accepts mutations from a transaction that belong to this collection and persists them.\n   * This should be called in your transaction's mutationFn to persist local-only data.\n   *\n   * @param transaction - The transaction containing mutations to accept\n   * @example\n   * const localData = createCollection(localOnlyCollectionOptions({...}))\n   *\n   * const tx = createTransaction({\n   *   mutationFn: async ({ transaction }) => {\n   *     // Make API call first\n   *     await api.save(...)\n   *     // Then persist local-only mutations after success\n   *     localData.utils.acceptMutations(transaction)\n   *   }\n   * })\n   */\n  acceptMutations: (transaction: {\n    mutations: Array<PendingMutation<Record<string, unknown>>>\n  }) => void\n}\n\ntype LocalOnlyCollectionOptionsResult<\n  T extends object,\n  TKey extends string | number,\n  TSchema extends StandardSchemaV1 | never = never,\n> = CollectionConfig<T, TKey, TSchema, LocalOnlyCollectionUtils> & {\n  utils: LocalOnlyCollectionUtils\n}\n\n/**\n * Creates Local-only collection options for use with a standard Collection\n *\n * This is an in-memory collection that doesn't sync with external sources but uses a loopback sync config\n * that immediately \"syncs\" all optimistic changes to the collection, making them permanent.\n * Perfect for local-only data that doesn't need persistence or external synchronization.\n *\n * **Using with Manual Transactions:**\n *\n * For manual transactions, you must call `utils.acceptMutations()` in your transaction's `mutationFn`\n * to persist changes made during `tx.mutate()`. This is necessary because local-only collections\n * don't participate in the standard mutation handler flow for manual transactions.\n *\n * @template T - The schema type if a schema is provided, otherwise the type of items in the collection\n * @template TKey - The type of the key returned by getKey\n * @param config - Configuration options for the Local-only collection\n * @returns Collection options with utilities including acceptMutations\n *\n * @example\n * // Basic local-only collection\n * const collection = createCollection(\n *   localOnlyCollectionOptions({\n *     getKey: (item) => item.id,\n *   })\n * )\n *\n * @example\n * // Local-only collection with initial data\n * const collection = createCollection(\n *   localOnlyCollectionOptions({\n *     getKey: (item) => item.id,\n *     initialData: [\n *       { id: 1, name: 'Item 1' },\n *       { id: 2, name: 'Item 2' },\n *     ],\n *   })\n * )\n *\n * @example\n * // Local-only collection with mutation handlers\n * const collection = createCollection(\n *   localOnlyCollectionOptions({\n *     getKey: (item) => item.id,\n *     onInsert: async ({ transaction }) => {\n *       console.log('Item inserted:', transaction.mutations[0].modified)\n *       // Custom logic after insert\n *     },\n *   })\n * )\n *\n * @example\n * // Using with manual transactions\n * const localData = createCollection(\n *   localOnlyCollectionOptions({\n *     getKey: (item) => item.id,\n *   })\n * )\n *\n * const tx = createTransaction({\n *   mutationFn: async ({ transaction }) => {\n *     // Use local data in API call\n *     const localMutations = transaction.mutations.filter(m => m.collection === localData)\n *     await api.save({ metadata: localMutations[0]?.modified })\n *\n *     // Persist local-only mutations after API success\n *     localData.utils.acceptMutations(transaction)\n *   }\n * })\n *\n * tx.mutate(() => {\n *   localData.insert({ id: 1, data: 'metadata' })\n *   apiCollection.insert({ id: 2, data: 'main data' })\n * })\n *\n * await tx.commit()\n */\n\n// Overload for when schema is provided\nexport function localOnlyCollectionOptions<\n  T extends StandardSchemaV1,\n  TKey extends string | number = string | number,\n>(\n  config: LocalOnlyCollectionConfig<InferSchemaOutput<T>, T, TKey> & {\n    schema: T\n  },\n): LocalOnlyCollectionOptionsResult<InferSchemaOutput<T>, TKey, T> & {\n  schema: T\n}\n\n// Overload for when no schema is provided\n// the type T needs to be passed explicitly unless it can be inferred from the getKey function in the config\nexport function localOnlyCollectionOptions<\n  T extends object,\n  TKey extends string | number = string | number,\n>(\n  config: LocalOnlyCollectionConfig<T, never, TKey> & {\n    schema?: never // prohibit schema\n  },\n): LocalOnlyCollectionOptionsResult<T, TKey> & {\n  schema?: never // no schema in the result\n}\n\nexport function localOnlyCollectionOptions<\n  T extends object = object,\n  TSchema extends StandardSchemaV1 = never,\n  TKey extends string | number = string | number,\n>(\n  config: LocalOnlyCollectionConfig<T, TSchema, TKey>,\n): LocalOnlyCollectionOptionsResult<T, TKey, TSchema> & {\n  schema?: StandardSchemaV1\n} {\n  const { initialData, onInsert, onUpdate, onDelete, id, ...restConfig } =\n    config\n\n  const collectionId = id ?? crypto.randomUUID()\n\n  // Create the sync configuration with transaction confirmation capability\n  const syncResult = createLocalOnlySync<T, TKey>(initialData)\n\n  /**\n   * Create wrapper handlers that call user handlers first, then confirm transactions\n   * Wraps the user's onInsert handler to also confirm the transaction immediately\n   */\n  const wrappedOnInsert = async (\n    params: InsertMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n  ) => {\n    // Call user handler first if provided\n    let handlerResult\n    if (onInsert) {\n      handlerResult = (await onInsert(params)) ?? {}\n    }\n\n    // Then synchronously confirm the transaction by looping through mutations\n    syncResult.confirmOperationsSync(params.transaction.mutations)\n\n    return handlerResult\n  }\n\n  /**\n   * Wrapper for onUpdate handler that also confirms the transaction immediately\n   */\n  const wrappedOnUpdate = async (\n    params: UpdateMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n  ) => {\n    // Call user handler first if provided\n    let handlerResult\n    if (onUpdate) {\n      handlerResult = (await onUpdate(params)) ?? {}\n    }\n\n    // Then synchronously confirm the transaction by looping through mutations\n    syncResult.confirmOperationsSync(params.transaction.mutations)\n\n    return handlerResult\n  }\n\n  /**\n   * Wrapper for onDelete handler that also confirms the transaction immediately\n   */\n  const wrappedOnDelete = async (\n    params: DeleteMutationFnParams<T, TKey, LocalOnlyCollectionUtils>,\n  ) => {\n    // Call user handler first if provided\n    let handlerResult\n    if (onDelete) {\n      handlerResult = (await onDelete(params)) ?? {}\n    }\n\n    // Then synchronously confirm the transaction by looping through mutations\n    syncResult.confirmOperationsSync(params.transaction.mutations)\n\n    return handlerResult\n  }\n\n  /**\n   * Accepts mutations from a transaction that belong to this collection and persists them\n   */\n  const acceptMutations = (transaction: {\n    mutations: Array<PendingMutation<Record<string, unknown>>>\n  }) => {\n    // Filter mutations that belong to this collection\n    const collectionMutations = transaction.mutations.filter(\n      (m) => m.collection.id === collectionId,\n    )\n\n    if (collectionMutations.length === 0) {\n      return\n    }\n\n    // Persist the mutations through sync\n    syncResult.confirmOperationsSync(\n      collectionMutations as Array<PendingMutation<T>>,\n    )\n  }\n\n  return {\n    ...restConfig,\n    id: collectionId,\n    sync: syncResult.sync,\n    onInsert: wrappedOnInsert,\n    onUpdate: wrappedOnUpdate,\n    onDelete: wrappedOnDelete,\n    utils: {\n      acceptMutations,\n    },\n    startSync: true,\n    gcTime: 0,\n  } as LocalOnlyCollectionOptionsResult<T, TKey, TSchema> & {\n    schema?: StandardSchemaV1\n  }\n}\n\n/**\n * Internal function to create Local-only sync configuration with transaction confirmation\n *\n * This captures the sync functions and provides synchronous confirmation of operations.\n * It creates a loopback sync that immediately confirms all optimistic operations,\n * making them permanent in the collection.\n *\n * @param initialData - Optional array of initial items to populate the collection\n * @returns Object with sync configuration and confirmOperationsSync function\n */\nfunction createLocalOnlySync<T extends object, TKey extends string | number>(\n  initialData?: Array<T>,\n) {\n  // Capture sync functions and collection for transaction confirmation\n  let syncBegin: (() => void) | null = null\n  let syncWrite: ((message: { type: OperationType; value: T }) => void) | null =\n    null\n  let syncCommit: (() => void) | null = null\n  let collection: Collection<T, TKey, LocalOnlyCollectionUtils> | null = null\n\n  const sync: SyncConfig<T, TKey> = {\n    /**\n     * Sync function that captures sync parameters and applies initial data\n     * @param params - Sync parameters containing begin, write, and commit functions\n     * @returns Unsubscribe function (empty since no ongoing sync is needed)\n     */\n    sync: (params) => {\n      const { begin, write, commit, markReady } = params\n\n      // Capture sync functions and collection for later use\n      syncBegin = begin\n      syncWrite = write\n      syncCommit = commit\n      collection = params.collection\n      params.collection._state.isLocalOnly = true\n\n      // Apply initial data if provided\n      if (initialData && initialData.length > 0) {\n        // Mark initial data as local so $origin is 'local' for local-only collections\n        for (const item of initialData) {\n          const key = params.collection.getKeyFromItem(item)\n          params.collection._state.pendingLocalChanges.add(key)\n        }\n\n        begin()\n        initialData.forEach((item) => {\n          write({\n            type: `insert`,\n            value: item,\n          })\n        })\n        commit()\n      }\n\n      // Mark collection as ready since local-only collections are immediately ready\n      markReady()\n\n      // Return empty unsubscribe function - no ongoing sync needed\n      return () => {}\n    },\n    /**\n     * Get sync metadata - returns empty object for local-only collections\n     * @returns Empty metadata object\n     */\n    getSyncMetadata: () => ({}),\n  }\n\n  /**\n   * Synchronously confirms optimistic operations by immediately writing through sync\n   *\n   * This loops through transaction mutations and applies them to move from optimistic to synced state.\n   * It's called after user handlers to make optimistic changes permanent.\n   *\n   * @param mutations - Array of mutation objects from the transaction\n   */\n  const confirmOperationsSync = (mutations: Array<PendingMutation<T>>) => {\n    if (!syncBegin || !syncWrite || !syncCommit) {\n      return // Sync not initialized yet, which is fine\n    }\n\n    // Immediately write back through sync interface\n    syncBegin()\n    mutations.forEach((mutation) => {\n      if (syncWrite) {\n        syncWrite({\n          type: mutation.type,\n          value: mutation.modified,\n        })\n      }\n    })\n    syncCommit()\n  }\n\n  return {\n    sync,\n    confirmOperationsSync,\n    collection,\n  }\n}\n"],"names":[],"mappings":";;AA4KO,SAAS,2BAKd,QAGA;AACA,QAAM,EAAE,aAAa,UAAU,UAAU,UAAU,IAAI,GAAG,eACxD;AAEF,QAAM,eAAe,MAAM,OAAO,WAAA;AAGlC,QAAM,aAAa,oBAA6B,WAAW;AAM3D,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,OACtB,WACG;AAEH,QAAI;AACJ,QAAI,UAAU;AACZ,sBAAiB,MAAM,SAAS,MAAM,KAAM,CAAA;AAAA,IAC9C;AAGA,eAAW,sBAAsB,OAAO,YAAY,SAAS;AAE7D,WAAO;AAAA,EACT;AAKA,QAAM,kBAAkB,CAAC,gBAEnB;AAEJ,UAAM,sBAAsB,YAAY,UAAU;AAAA,MAChD,CAAC,MAAM,EAAE,WAAW,OAAO;AAAA,IAAA;AAG7B,QAAI,oBAAoB,WAAW,GAAG;AACpC;AAAA,IACF;AAGA,eAAW;AAAA,MACT;AAAA,IAAA;AAAA,EAEJ;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH,IAAI;AAAA,IACJ,MAAM,WAAW;AAAA,IACjB,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,OAAO;AAAA,MACL;AAAA,IAAA;AAAA,IAEF,WAAW;AAAA,IACX,QAAQ;AAAA,EAAA;AAIZ;AAYA,SAAS,oBACP,aACA;AAEA,MAAI,YAAiC;AACrC,MAAI,YACF;AACF,MAAI,aAAkC;AACtC,MAAI,aAAmE;AAEvE,QAAM,OAA4B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMhC,MAAM,CAAC,WAAW;AAChB,YAAM,EAAE,OAAO,OAAO,QAAQ,cAAc;AAG5C,kBAAY;AACZ,kBAAY;AACZ,mBAAa;AACb,mBAAa,OAAO;AACpB,aAAO,WAAW,OAAO,cAAc;AAGvC,UAAI,eAAe,YAAY,SAAS,GAAG;AAEzC,mBAAW,QAAQ,aAAa;AAC9B,gBAAM,MAAM,OAAO,WAAW,eAAe,IAAI;AACjD,iBAAO,WAAW,OAAO,oBAAoB,IAAI,GAAG;AAAA,QACtD;AAEA,cAAA;AACA,oBAAY,QAAQ,CAAC,SAAS;AAC5B,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,OAAO;AAAA,UAAA,CACR;AAAA,QACH,CAAC;AACD,eAAA;AAAA,MACF;AAGA,gBAAA;AAGA,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,iBAAiB,OAAO,CAAA;AAAA,EAAC;AAW3B,QAAM,wBAAwB,CAAC,cAAyC;AACtE,QAAI,CAAC,aAAa,CAAC,aAAa,CAAC,YAAY;AAC3C;AAAA,IACF;AAGA,cAAA;AACA,cAAU,QAAQ,CAAC,aAAa;AAC9B,UAAI,WAAW;AACb,kBAAU;AAAA,UACR,MAAM,SAAS;AAAA,UACf,OAAO,SAAS;AAAA,QAAA,CACjB;AAAA,MACH;AAAA,IACF,CAAC;AACD,eAAA;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;;"}