{"version":3,"file":"httpBatchLink-BMtWxLJV.mjs","names":[],"sources":["../src/internals/dataLoader.ts","../src/links/httpBatchLink.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-non-null-assertion */\n\ntype BatchItem<TKey, TValue> = {\n  aborted: boolean;\n  key: TKey;\n  resolve: ((value: TValue) => void) | null;\n  reject: ((error: Error) => void) | null;\n  batch: Batch<TKey, TValue> | null;\n};\ntype Batch<TKey, TValue> = {\n  items: BatchItem<TKey, TValue>[];\n};\nexport type BatchLoader<TKey, TValue> = {\n  validate: (keys: TKey[]) => boolean;\n  fetch: (keys: TKey[]) => Promise<TValue[] | Promise<TValue>[]>;\n};\n\n/**\n * A function that should never be called unless we messed something up.\n */\nconst throwFatalError = () => {\n  throw new Error(\n    'Something went wrong. Please submit an issue at https://github.com/trpc/trpc/issues/new',\n  );\n};\n\n/**\n * Dataloader that's very inspired by https://github.com/graphql/dataloader\n * Less configuration, no caching, and allows you to cancel requests\n * When cancelling a single fetch the whole batch will be cancelled only when _all_ items are cancelled\n */\nexport function dataLoader<TKey, TValue>(\n  batchLoader: BatchLoader<TKey, TValue>,\n) {\n  let pendingItems: BatchItem<TKey, TValue>[] | null = null;\n  let dispatchTimer: ReturnType<typeof setTimeout> | null = null;\n\n  const destroyTimerAndPendingItems = () => {\n    clearTimeout(dispatchTimer as any);\n    dispatchTimer = null;\n    pendingItems = null;\n  };\n\n  /**\n   * Iterate through the items and split them into groups based on the `batchLoader`'s validate function\n   */\n  function groupItems(items: BatchItem<TKey, TValue>[]) {\n    const groupedItems: BatchItem<TKey, TValue>[][] = [[]];\n    let index = 0;\n    while (true) {\n      const item = items[index];\n      if (!item) {\n        // we're done\n        break;\n      }\n      const lastGroup = groupedItems[groupedItems.length - 1]!;\n\n      if (item.aborted) {\n        // Item was aborted before it was dispatched\n        item.reject?.(new Error('Aborted'));\n        index++;\n        continue;\n      }\n\n      const isValid = batchLoader.validate(\n        lastGroup.concat(item).map((it) => it.key),\n      );\n\n      if (isValid) {\n        lastGroup.push(item);\n        index++;\n        continue;\n      }\n\n      if (lastGroup.length === 0) {\n        item.reject?.(new Error('Input is too big for a single dispatch'));\n        index++;\n        continue;\n      }\n      // Create new group, next iteration will try to add the item to that\n      groupedItems.push([]);\n    }\n    return groupedItems;\n  }\n\n  function dispatch() {\n    const groupedItems = groupItems(pendingItems!);\n    destroyTimerAndPendingItems();\n\n    // Create batches for each group of items\n    for (const items of groupedItems) {\n      if (!items.length) {\n        continue;\n      }\n      const batch: Batch<TKey, TValue> = {\n        items,\n      };\n      for (const item of items) {\n        item.batch = batch;\n      }\n      const promise = batchLoader.fetch(batch.items.map((_item) => _item.key));\n\n      promise\n        .then(async (result) => {\n          await Promise.all(\n            result.map(async (valueOrPromise, index) => {\n              const item = batch.items[index]!;\n              try {\n                const value = await Promise.resolve(valueOrPromise);\n\n                item.resolve?.(value);\n              } catch (cause) {\n                item.reject?.(cause as Error);\n              }\n\n              item.batch = null;\n              item.reject = null;\n              item.resolve = null;\n            }),\n          );\n\n          for (const item of batch.items) {\n            item.reject?.(new Error('Missing result'));\n            item.batch = null;\n          }\n        })\n        .catch((cause) => {\n          for (const item of batch.items) {\n            item.reject?.(cause);\n            item.batch = null;\n          }\n        });\n    }\n  }\n  function load(key: TKey): Promise<TValue> {\n    const item: BatchItem<TKey, TValue> = {\n      aborted: false,\n      key,\n      batch: null,\n      resolve: throwFatalError,\n      reject: throwFatalError,\n    };\n\n    const promise = new Promise<TValue>((resolve, reject) => {\n      item.reject = reject;\n      item.resolve = resolve;\n\n      pendingItems ??= [];\n      pendingItems.push(item);\n    });\n\n    dispatchTimer ??= setTimeout(dispatch);\n\n    return promise;\n  }\n\n  return {\n    load,\n  };\n}\n","import type { AnyRouter, ProcedureType } from '@trpc/server';\nimport { observable } from '@trpc/server/observable';\nimport { transformResult } from '@trpc/server/unstable-core-do-not-import';\nimport type { BatchLoader } from '../internals/dataLoader';\nimport { dataLoader } from '../internals/dataLoader';\nimport { allAbortSignals, raceAbortSignals } from '../internals/signals';\nimport type { NonEmptyArray } from '../internals/types';\nimport { TRPCClientError } from '../TRPCClientError';\nimport type { HTTPBatchLinkOptions } from './HTTPBatchLinkOptions';\nimport type { HTTPResult } from './internals/httpUtils';\nimport {\n  getUrl,\n  jsonHttpRequester,\n  resolveHTTPLinkOptions,\n} from './internals/httpUtils';\nimport type { Operation, TRPCLink } from './types';\n\n/**\n * @see https://trpc.io/docs/client/links/httpBatchLink\n */\nexport function httpBatchLink<TRouter extends AnyRouter>(\n  opts: HTTPBatchLinkOptions<TRouter['_def']['_config']['$types']>,\n): TRPCLink<TRouter> {\n  const resolvedOpts = resolveHTTPLinkOptions(opts);\n  const maxURLLength = opts.maxURLLength ?? Infinity;\n  const maxItems = opts.maxItems ?? Infinity;\n\n  return () => {\n    const batchLoader = (\n      type: ProcedureType,\n    ): BatchLoader<Operation, HTTPResult> => {\n      return {\n        validate(batchOps) {\n          if (maxURLLength === Infinity && maxItems === Infinity) {\n            // escape hatch for quick calcs\n            return true;\n          }\n          if (batchOps.length > maxItems) {\n            return false;\n          }\n          const path = batchOps.map((op) => op.path).join(',');\n          const inputs = batchOps.map((op) => op.input);\n\n          const url = getUrl({\n            ...resolvedOpts,\n            type,\n            path,\n            inputs,\n            signal: null,\n          });\n\n          return url.length <= maxURLLength;\n        },\n        async fetch(batchOps) {\n          const path = batchOps.map((op) => op.path).join(',');\n          const inputs = batchOps.map((op) => op.input);\n          const signal = allAbortSignals(...batchOps.map((op) => op.signal));\n\n          const res = await jsonHttpRequester({\n            ...resolvedOpts,\n            path,\n            inputs,\n            type,\n            headers() {\n              if (!opts.headers) {\n                return {};\n              }\n              if (typeof opts.headers === 'function') {\n                return opts.headers({\n                  opList: batchOps as NonEmptyArray<Operation>,\n                });\n              }\n              return opts.headers;\n            },\n            signal,\n          });\n          const resJSON = Array.isArray(res.json)\n            ? res.json\n            : batchOps.map(() => res.json);\n          const result = resJSON.map((item) => ({\n            meta: res.meta,\n            json: item,\n          }));\n          return result;\n        },\n      };\n    };\n\n    const query = dataLoader(batchLoader('query'));\n    const mutation = dataLoader(batchLoader('mutation'));\n\n    const loaders = { query, mutation };\n    return ({ op }) => {\n      return observable((observer) => {\n        /* istanbul ignore if -- @preserve */\n        if (op.type === 'subscription') {\n          throw new Error(\n            'Subscriptions are unsupported by `httpLink` - use `httpSubscriptionLink` or `wsLink`',\n          );\n        }\n        const ac = new AbortController();\n        const loader = loaders[op.type];\n        const promise = loader.load({\n          ...op,\n          signal: raceAbortSignals(op.signal, ac.signal),\n        });\n\n        let isDone = false;\n        let _res = undefined as HTTPResult | undefined;\n        promise\n          .then((res) => {\n            isDone = true;\n            _res = res;\n            const transformed = transformResult(\n              res.json,\n              resolvedOpts.transformer.output,\n            );\n\n            if (!transformed.ok) {\n              observer.error(\n                TRPCClientError.from(transformed.error, {\n                  meta: res.meta,\n                }),\n              );\n              return;\n            }\n            observer.next({\n              context: res.meta,\n              result: transformed.result,\n            });\n            observer.complete();\n          })\n          .catch((err) => {\n            isDone = true;\n            observer.error(\n              TRPCClientError.from(err, {\n                meta: _res?.meta,\n              }),\n            );\n          });\n\n        return () => {\n          if (!isDone) {\n            ac.abort();\n          }\n        };\n      });\n    };\n  };\n}\n"],"mappings":"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"}