UNPKG

@trpc/client

Version:

The tRPC client library

1 lines 13.3 kB
{"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":";;;;;;;;;AAoBA,MAAM,wBAAwB;CAC5B,MAAM,IAAI,MACR,yFACF;AACF;;;;;;AAOA,SAAgB,WACd,aACA;CACA,IAAI,eAAiD;CACrD,IAAI,gBAAsD;CAE1D,MAAM,oCAAoC;EACxC,aAAa,aAAoB;EACjC,gBAAgB;EAChB,eAAe;CACjB;;;;CAKA,SAAS,WAAW,OAAkC;EACpD,MAAM,eAA4C,CAAC,CAAC,CAAC;EACrD,IAAI,QAAQ;EACZ,OAAO,MAAM;GACX,MAAM,OAAO,MAAM;GACnB,IAAI,CAAC,MAEH;GAEF,MAAM,YAAY,aAAa,aAAa,SAAS;GAErD,IAAI,KAAK,SAAS;;IAEhB,CAAA,eAAA,KAAK,YAAA,QAAA,iBAAA,KAAA,KAAA,aAAA,KAAA,sBAAS,IAAI,MAAM,SAAS,CAAC;IAClC;IACA;GACF;GAMA,IAJgB,YAAY,SAC1B,UAAU,OAAO,IAAI,CAAC,CAAC,KAAK,OAAO,GAAG,GAAG,CAGjC,GAAG;IACX,UAAU,KAAK,IAAI;IACnB;IACA;GACF;GAEA,IAAI,UAAU,WAAW,GAAG;;IAC1B,CAAA,gBAAA,KAAK,YAAA,QAAA,kBAAA,KAAA,KAAA,cAAA,KAAA,sBAAS,IAAI,MAAM,wCAAwC,CAAC;IACjE;IACA;GACF;GAEA,aAAa,KAAK,CAAC,CAAC;EACtB;EACA,OAAO;CACT;CAEA,SAAS,WAAW;EAClB,MAAM,eAAe,WAAW,YAAa;EAC7C,4BAA4B;EAG5B,KAAK,MAAM,SAAS,cAAc;GAChC,IAAI,CAAC,MAAM,QACT;GAEF,MAAM,QAA6B,EACjC,MACF;GACA,KAAK,MAAM,QAAQ,OACjB,KAAK,QAAQ;GAIf,YAF4B,MAAM,MAAM,MAAM,KAAK,UAAU,MAAM,GAAG,CAEhE,CAAC,CACJ,KAAK,OAAO,WAAW;IACtB,MAAM,QAAQ,IACZ,OAAO,IAAI,OAAO,gBAAgB,UAAU;KAC1C,MAAM,OAAO,MAAM,MAAM;KACzB,IAAI;;MACF,MAAM,QAAQ,MAAM,QAAQ,QAAQ,cAAc;MAElD,CAAA,gBAAA,KAAK,aAAA,QAAA,kBAAA,KAAA,KAAA,cAAA,KAAA,MAAU,KAAK;KACtB,SAAS,OAAO;;MACd,CAAA,gBAAA,KAAK,YAAA,QAAA,kBAAA,KAAA,KAAA,cAAA,KAAA,MAAS,KAAc;KAC9B;KAEA,KAAK,QAAQ;KACb,KAAK,SAAS;KACd,KAAK,UAAU;IACjB,CAAC,CACH;IAEA,KAAK,MAAM,QAAQ,MAAM,OAAO;;KAC9B,CAAA,gBAAA,KAAK,YAAA,QAAA,kBAAA,KAAA,KAAA,cAAA,KAAA,sBAAS,IAAI,MAAM,gBAAgB,CAAC;KACzC,KAAK,QAAQ;IACf;GACF,CAAC,CAAC,CACD,OAAO,UAAU;IAChB,KAAK,MAAM,QAAQ,MAAM,OAAO;;KAC9B,CAAA,gBAAA,KAAK,YAAA,QAAA,kBAAA,KAAA,KAAA,cAAA,KAAA,MAAS,KAAK;KACnB,KAAK,QAAQ;IACf;GACF,CAAC;EACL;CACF;CACA,SAAS,KAAK,KAA4B;;EACxC,MAAM,OAAgC;GACpC,SAAS;GACT;GACA,OAAO;GACP,SAAS;GACT,QAAQ;EACV;EAEA,MAAM,UAAU,IAAI,SAAiB,SAAS,WAAW;;GACvD,KAAK,SAAS;GACd,KAAK,UAAU;GAEf,CAAA,gBAAA,kBAAA,QAAA,kBAAA,KAAA,MAAA,eAAiB,CAAC;GAClB,aAAa,KAAK,IAAI;EACxB,CAAC;EAED,CAAA,iBAAA,mBAAA,QAAA,mBAAA,KAAA,MAAA,gBAAkB,WAAW,QAAQ;EAErC,OAAO;CACT;CAEA,OAAO,EACL,KACF;AACF;;;;;;AC3IA,SAAgB,cACd,MACmB;;CACnB,MAAM,eAAe,uBAAuB,IAAI;CAChD,MAAM,gBAAA,qBAAe,KAAK,kBAAA,QAAA,uBAAA,KAAA,IAAA,qBAAgB;CAC1C,MAAM,YAAA,iBAAW,KAAK,cAAA,QAAA,mBAAA,KAAA,IAAA,iBAAY;CAElC,aAAa;EACX,MAAM,eACJ,SACuC;GACvC,OAAO;IACL,SAAS,UAAU;KACjB,IAAI,iBAAiB,YAAY,aAAa,UAE5C,OAAO;KAET,IAAI,SAAS,SAAS,UACpB,OAAO;KAET,MAAM,OAAO,SAAS,KAAK,OAAO,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG;KACnD,MAAM,SAAS,SAAS,KAAK,OAAO,GAAG,KAAK;KAU5C,OARY,OAAA,eAAA,eAAA,CAAA,GACP,YAAA,GAAA,CAAA,GAAA;MACH;MACA;MACA;MACA,QAAQ;KACV,CAAA,CAES,CAAC,CAAC,UAAU;IACvB;IACA,MAAM,MAAM,UAAU;KACpB,MAAM,OAAO,SAAS,KAAK,OAAO,GAAG,IAAI,CAAC,CAAC,KAAK,GAAG;KACnD,MAAM,SAAS,SAAS,KAAK,OAAO,GAAG,KAAK;KAC5C,MAAM,SAAS,gBAAgB,GAAG,SAAS,KAAK,OAAO,GAAG,MAAM,CAAC;KAEjE,MAAM,MAAM,MAAM,kBAAA,eAAA,eAAA,CAAA,GACb,YAAA,GAAA,CAAA,GAAA;MACH;MACA;MACA;MACA,UAAU;OACR,IAAI,CAAC,KAAK,SACR,OAAO,CAAC;OAEV,IAAI,OAAO,KAAK,YAAY,YAC1B,OAAO,KAAK,QAAQ,EAClB,QAAQ,SACV,CAAC;OAEH,OAAO,KAAK;MACd;MACA;KACF,CAAA,CAAC;KAQD,QAPgB,MAAM,QAAQ,IAAI,IAAI,IAClC,IAAI,OACJ,SAAS,UAAU,IAAI,IAAI,EAAA,CACR,KAAK,UAAU;MACpC,MAAM,IAAI;MACV,MAAM;KACR,EACY;IACd;GACF;EACF;EAKA,MAAM,UAAU;GAAE,OAHJ,WAAW,YAAY,OAAO,CAGtB;GAAG,UAFR,WAAW,YAAY,UAAU,CAElB;EAAE;EAClC,QAAQ,EAAE,SAAS;GACjB,OAAO,YAAY,aAAa;;IAE9B,IAAI,GAAG,SAAS,gBACd,MAAM,IAAI,MACR,sFACF;IAEF,MAAM,KAAK,IAAI,gBAAgB;IAE/B,MAAM,UADS,QAAQ,GAAG,KACJ,CAAC,KAAA,eAAA,eAAA,CAAA,GAClB,EAAA,GAAA,CAAA,GAAA,EACH,QAAQ,iBAAiB,GAAG,QAAQ,GAAG,MAAM,EAAA,CAC/C,CAAC;IAED,IAAI,SAAS;IACb,IAAI,OAAO,KAAA;IACX,QACG,MAAM,QAAQ;KACb,SAAS;KACT,OAAO;KACP,MAAM,cAAc,gBAClB,IAAI,MACJ,aAAa,YAAY,MAC3B;KAEA,IAAI,CAAC,YAAY,IAAI;MACnB,SAAS,MACP,gBAAgB,KAAK,YAAY,OAAO,EACtC,MAAM,IAAI,KACZ,CAAC,CACH;MACA;KACF;KACA,SAAS,KAAK;MACZ,SAAS,IAAI;MACb,QAAQ,YAAY;KACtB,CAAC;KACD,SAAS,SAAS;IACpB,CAAC,CAAC,CACD,OAAO,QAAQ;KACd,SAAS;KACT,SAAS,MACP,gBAAgB,KAAK,KAAK,EACxB,MAAA,SAAA,QAAA,SAAA,KAAA,IAAA,KAAA,IAAM,KAAM,KACd,CAAC,CACH;IACF,CAAC;IAEH,aAAa;KACX,IAAI,CAAC,QACH,GAAG,MAAM;IAEb;GACF,CAAC;EACH;CACF;AACF"}