UNPKG

gqty

Version:

The No-GraphQL Client for TypeScript

299 lines (296 loc) • 9.18 kB
import { dedupePromise } from '../Cache/query.mjs'; import { GQtyError } from '../Error/index.mjs'; import { notifyFetch, notifyRetry } from '../Helpers/useMetaStateHack.mjs'; import { buildQuery } from '../QueryBuilder.mjs'; import { isWsClient } from './subscriber.mjs'; import { doRetry } from '../Error/retry.mjs'; const fetchSelections = (selections, { cache, debugger: debug, extensions: customExtensions, fetchOptions, operationName }) => Promise.all( buildQuery(selections, operationName).map( async ({ query, variables, operationName: operationName2, extensions: { type, hash } = {} }) => { if (!type) throw new GQtyError(`Invalid query type: ${type}`); if (!hash) throw new GQtyError(`Expected query hash.`); const queryPayload = { query, variables, operationName: operationName2, extensions: { ...customExtensions, type, hash } }; const promise = dedupePromise( cache, hash, type === "subscription" ? () => doSubscribeOnce(queryPayload, fetchOptions) : () => doFetch(queryPayload, { ...fetchOptions, selections }) ).then(({ data, errors, extensions }) => { const result = { data, extensions: { ...extensions, ...queryPayload.extensions } }; if (errors == null ? void 0 : errors.length) { result.error = GQtyError.fromGraphQLErrors(errors); } return result; }); await (debug == null ? void 0 : debug.dispatch({ cache, request: queryPayload, promise, selections })); return await promise; } ) ); const subsRef = /* @__PURE__ */ new WeakMap(); const subscribeSelections = (selections, fn, { cache, debugger: debug, extensions: customExtensions, fetchOptions: { subscriber }, operationName, onSubscribe, onComplete }) => { const unsubscribers = /* @__PURE__ */ new Set(); Promise.all( buildQuery(selections, operationName).map( async ({ query, variables, operationName: operationName2, extensions: { type, hash } = {} }) => { if (!type) throw new GQtyError(`Invalid query type: ${type}`); if (!hash) throw new GQtyError(`Expected query hash.`); if (type === "subscription" && !subscriber) { throw new GQtyError(`Missing subscriber for subscriptions.`); } const queryPayload = { query, variables, operationName: operationName2, extensions: { ...customExtensions, type, hash } }; let subscriptionId; if (isWsClient(subscriber)) { if (onSubscribe) { subscriber.onSubscribe(onSubscribe); } if (debug) { const unsub = subscriber.on("message", (message) => { var _a; switch (message.type) { case "connection_ack": { break; } case "subscribe": { if (((_a = message.payload.extensions) == null ? void 0 : _a.hash) !== hash) return; subscriptionId = message.id; debug.dispatch({ cache, label: `[id=${subscriptionId}] [create]`, request: queryPayload, selections }); unsub(); break; } } }); } } else { subscriptionId = "EventSource"; onSubscribe == null ? void 0 : onSubscribe(); } const next = ({ data, errors, extensions }) => { if (data === void 0) return; const result = { data, extensions: { ...extensions, ...queryPayload.extensions } }; if (errors == null ? void 0 : errors.length) { result.error = GQtyError.fromGraphQLErrors(errors); } debug == null ? void 0 : debug.dispatch({ cache, label: subscriptionId ? `[id=${subscriptionId}] [data]` : void 0, request: queryPayload, promise: result.error ? Promise.reject(result) : Promise.resolve(result), selections }); fn(result); }; const error = (error2) => { if (error2 == null) { throw new GQtyError(`Subscription sink closed without an error.`); } debug == null ? void 0 : debug.dispatch({ cache, label: subscriptionId ? `[id=${subscriptionId}] [error]` : void 0, request: queryPayload, selections }); fn({ error: error2, extensions: queryPayload.extensions }); }; let dispose; const promise = dedupePromise(cache, hash, () => { return new Promise((complete) => { dispose = subscriber == null ? void 0 : subscriber.subscribe( queryPayload, { next, error(e) { if (Array.isArray(e)) { error(GQtyError.fromGraphQLErrors(e)); } else if (!isCloseEvent(e)) { if (e instanceof Error) { error(GQtyError.create(e)); } else { console.error("Unknown subscription error:", e); } } this.complete(); }, complete() { debug == null ? void 0 : debug.dispatch({ cache, label: subscriptionId ? `[id=${subscriptionId}] [unsubscribe]` : void 0, request: queryPayload, selections }); complete(); } } ); }); }); if (dispose) { subsRef.set(promise, { dispose, count: 1 }); } else if (subsRef.get(promise)) { subsRef.get(promise).count++; } unsubscribers.add(() => { const ref = subsRef.get(promise); if (ref && --ref.count <= 0) { setTimeout(() => { if (ref.count <= 0) { ref.dispose(); } }); } }); return promise; } ) ).finally(() => onComplete == null ? void 0 : onComplete()); return () => { unsubscribers.forEach((fn2) => fn2()); }; }; const doFetch = async (payload, { fetcher, retryPolicy, selections, ...fetchOptions }) => { const doDoFetch = () => fetcher(payload, fetchOptions); try { const promise = doDoFetch(); notifyFetch(promise, selections); return await promise; } catch (error) { if ( // User doesn't want reties !retryPolicy || // Let everything unknown through !(error instanceof Error) || // GQtyErrors are supposed to be terminating error instanceof GQtyError ) { throw error; } return new Promise((resolve, reject) => { doRetry(retryPolicy, { onLastTry: async () => { try { const promise = doDoFetch(); notifyRetry(promise, selections); resolve(await promise); } catch (e) { reject(e); } }, onRetry: async () => { const promise = doDoFetch(); notifyRetry(promise, selections); resolve(await promise); } }); }); } }; const doSubscribeOnce = async ({ query, variables, operationName }, { subscriber }) => { if (!subscriber) { throw new GQtyError(`Subscription client is required for subscritions.`); } return new Promise( (resolve, reject) => { let result; const unsubscribe = subscriber.subscribe( { query, variables: variables != null ? variables : {}, operationName: operationName != null ? operationName : void 0 }, { next(data) { result = data; unsubscribe(); }, error(error) { if (isCloseEvent(error)) { resolve(result); } else if (Array.isArray(error)) { reject(GQtyError.fromGraphQLErrors(error)); } else { reject(error); } }, complete() { if (!result) { throw new GQtyError(`Subscription completed without data`); } resolve(result); } } ); } ); }; const isCloseEvent = (input) => { var _a, _b; const error = input; if (!error || typeof error !== "object") return false; return error.type === "close" && ((_b = (_a = error.target) == null ? void 0 : _a.constructor) == null ? void 0 : _b.name) === "WebSocket" || typeof error.code === "number" && [ 4004, 4005, 4400, 4401, 4403, 4406, 4408, 4409, 4429, 4499, 4500, 4504 ].includes(error.code); }; export { fetchSelections, isCloseEvent, subscribeSelections };