gqty
Version:
The No-GraphQL Client for TypeScript
299 lines (296 loc) • 9.18 kB
JavaScript
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 };