UNPKG

gqty

Version:

The No-GraphQL Client for TypeScript

389 lines (386 loc) • 12.2 kB
import { flattenObject } from '../Cache/crawl.mjs'; import { isCacheObject } from '../Cache/utils.mjs'; import { GQtyError } from '../Error/index.mjs'; import { parseSchemaType, SchemaUnionsKey } from '../Schema.mjs'; import '../Utils/hash.mjs'; import { isPlainObject } from '../Utils/object.mjs'; import { $meta } from './meta.mjs'; import { createSkeleton, isSkeleton } from './skeleton.mjs'; const verbose = process.env.NODE_ENV !== "production"; const resolve = (accessor, selection, __type) => { var _a, _b; const meta = $meta(accessor); if (!meta) return; const { context } = meta; const { alias, key, isUnion, cacheKeys } = selection; const isNumericSelection = +key === +key; if (cacheKeys.length >= context.depthLimit) { if (verbose) { throw new GQtyError( `Depth limit reached at ${cacheKeys.join( "." )}, ignoring further selections.` ); } return; } const cache = selection.parent === selection.root && key !== "__typename" ? ( // The way we structure the cache for SWR requires special treatment // for 2nd level selections, e.g. query.hello, mutation.update()... etc. ensureCache(cacheKeys[0], cacheKeys[1], meta) ) : Object.defineProperties( { data: void 0, expiresAt: Infinity }, Object.getOwnPropertyDescriptors(meta.cache) ); let data = cache.data; const { pureType, isArray, isNullable } = parseSchemaType(__type); const type = context.schema[pureType]; if (!isUnion && selection.root !== selection && (selection.root !== selection.parent || key === "__typename") && data !== null) { if (Array.isArray(data)) { if (verbose && !isNumericSelection) { console.warn(`[GQty] Accessing arrays with non-numeric key "${key}".`); } data = data[+key]; } else if (typeof data === "object") { data = data[alias != null ? alias : key]; } } if (data === null) { if (!isNullable) { throw new GQtyError(`Cached null for non-nullable type ${pureType}.`); } if (context.scalars[pureType]) { context.select(selection, cache); } else { context.select(selection); } return null; } if (!type) { if (context.scalars[pureType]) { if (cache.expiresAt === -Infinity) { data = (_a = context.cache.get( selection.cacheKeys.join("."), context.cacheOptions )) == null ? void 0 : _a.data; } context.select(selection, { ...cache, data }); return isArray ? Array.isArray(data) ? data : [void 0] : data; } const unions = (_b = context.schema[SchemaUnionsKey]) == null ? void 0 : _b[pureType.slice(1)]; if (unions == null ? void 0 : unions.length) { return createUnionAccessor({ context, cache: meta.cache, possibleTypes: unions, selection }); } throw new GQtyError(`GraphQL type not found: ${pureType}`); } if (data === void 0) { data = createSkeleton(() => ({})); if (isArray && !isNumericSelection) { data = createSkeleton(() => [data]); } if (cacheKeys.length === 2) { const [type2, field] = cacheKeys; context.cache.set({ [type2]: { [field]: data } }); } } if (cacheKeys.length > 2 && cache.data && typeof cache.data === "object" && !Array.isArray(cache.data) && !isNumericSelection) { cache.data[alias != null ? alias : key] = data; } cache.data = data; if (isArray && !isNumericSelection) { return createArrayAccessor({ cache, context, selection, type: { __type: pureType } }); } else { return createObjectAccessor({ cache, context, selection, type: { __type } }); } }; const createUnionAccessor = ({ context, cache, possibleTypes, selection }) => { return new Proxy( Object.fromEntries(possibleTypes.map((__typename) => [__typename, true])), { get(_, __typename) { if (typeof __typename !== "string") return; if (!possibleTypes.includes(__typename)) return; const data = cache.data; if (!isSkeleton(data) && (!isCacheObject(data) || data.__typename !== __typename)) return; const type = context.schema[__typename]; if (!type) return; return createObjectAccessor({ context, cache, selection: selection.getChild(__typename, { isUnion: true }), type: { __type: __typename } }); } } ); }; const objectProxyHandler = { get(currentType, key, proxy) { if (typeof key !== "string") return; if (key === "toJSON") { return () => { var _a; const data = (_a = $meta(proxy)) == null ? void 0 : _a.cache.data; if (typeof data !== "object" || data === null) { return data; } return Object.entries(data).reduce( (prev, [key2, value]) => { if (!isSkeleton(value)) { prev[key2] = value; } return prev; }, {} ); }; } const meta = $meta(proxy); if (!meta) return; if ( // Skip Query, Mutation and Subscription meta.selection.parent !== void 0 && // Prevent infinite recursions !getIdentityFields(meta).includes(key) ) { selectIdentityFields(proxy, currentType); } const targetType = currentType[key]; if (!targetType || typeof targetType !== "object") return; const { __args, __type } = targetType; if (__args) { return (args) => resolve( proxy, meta.selection.getChild( key, args ? { input: { types: __args, values: args } } : {} ), __type ); } return resolve(proxy, meta.selection.getChild(key), __type); }, set(_, key, value, proxy) { var _a, _b, _c; const meta = $meta(proxy); if (typeof key !== "string" || !meta) return false; const { cache, context, selection } = meta; value = (_a = deepMetadata(value)) != null ? _a : value; if (selection.ancestry.length <= 2) { const [type, field] = selection.cacheKeys; if (field) { const data = (_c = (_b = context.cache.get(`${type}.${field}`, context.cacheOptions)) == null ? void 0 : _b.data) != null ? _c : {}; if (!isPlainObject(data)) return false; data[key] = value; context.cache.set({ [type]: { [field]: data } }); } else { context.cache.set({ [type]: { [key]: value } }); } } let result = false; if (isCacheObject(cache.data)) { result = Reflect.set(cache.data, key, value); } for (const [keys, scalar] of flattenObject(value)) { let currentSelection = selection.getChild(key); for (const key2 of keys) { if (!isNaN(Number(key2))) continue; currentSelection = currentSelection.getChild(key2); } context.select(currentSelection, { ...cache, data: scalar }); } return result; } }; const createObjectAccessor = (meta) => { const { cache: { data }, context: { schema }, type: { __type } } = meta; if (data !== void 0 && !isCacheObject(data)) { throw new GQtyError( `Invalid type ${data === null ? "null" : typeof data} for object accessors.` ); } const createAccessor = () => { const type = schema[parseSchemaType(__type).pureType]; if (!type) throw new GQtyError(`Invalid schema type ${__type}.`); const proxy = new Proxy( // `type` here for ownKeys proxy trap type, data ? Object.assign({}, objectProxyHandler, { getOwnPropertyDescriptor: (target, key) => { var _a; return (_a = Reflect.getOwnPropertyDescriptor(data, key)) != null ? _a : Reflect.getOwnPropertyDescriptor(target, key); } }) : objectProxyHandler ); $meta.set(proxy, meta); return proxy; }; return createAccessor(); }; const deepMetadata = (input) => { const data = metadata(input); const stack = /* @__PURE__ */ new Set([data]); const seen = /* @__PURE__ */ new Set(); for (const it of stack) { if (seen.has(it)) continue; seen.add(it); if (Array.isArray(it)) { for (const [k, v] of it.entries()) { if (isObject(v)) { stack.add(it[k] = metadata(v)); } else { stack.add(v); } } } else if (isObject(it)) { for (const [k, v] of Object.entries(it)) { if (isObject(v)) { stack.add(it[k] = metadata(v)); } else { stack.add(v); } } } } return data; function metadata(it) { var _a, _b; return (_b = (_a = $meta(it)) == null ? void 0 : _a.cache.data) != null ? _b : it; } function isObject(it) { return typeof it === "object" && it !== null; } }; const ensureCache = (type, field, { context: { cache, cacheOptions } }) => { if (!cache.get(`${type}.${field}`, cacheOptions)) { cache.set({ [type]: { [field]: void 0 } }); } const { data } = cache.get(`${type}.${field}`, cacheOptions); return { data, get expiresAt() { var _a, _b; return (_b = (_a = cache.get(`${type}.${field}`, cacheOptions)) == null ? void 0 : _a.expiresAt) != null ? _b : -Infinity; }, get swrBefore() { var _a, _b; return (_b = (_a = cache.get(`${type}.${field}`, cacheOptions)) == null ? void 0 : _a.swrBefore) != null ? _b : -Infinity; } }; }; const getIdentityFields = ({ context: { typeKeys }, type: { __type } }) => { var _a; const { pureType } = parseSchemaType(__type); return (_a = typeKeys == null ? void 0 : typeKeys[pureType]) != null ? _a : ["__typename", "id", "_id"]; }; const selectIdentityFields = (accessor, type) => { var _a; if (accessor == null) return; const meta = $meta(accessor); if (!meta) return; const { selection: { parent, isUnion } } = meta; if ((parent == null ? void 0 : parent.key) !== "$on") { accessor.__typename; } const keys = getIdentityFields(meta); for (const key of keys) { if (!type[key]) continue; if (isUnion && ((_a = parent == null ? void 0 : parent.parent) == null ? void 0 : _a.children.has(key))) continue; accessor[key]; } }; const arrayProxyHandler = { get(_, key, proxy) { const meta = $meta(proxy); if (!meta) return; if (key === "toJSON" && !isSkeleton(meta.cache.data)) { return () => { var _a; return (_a = $meta(proxy)) == null ? void 0 : _a.cache.data; }; } const { cache: { data }, selection } = meta; if (!Array.isArray(data)) return; if (typeof key === "string") { if (!Array.isArray(data)) { throw new GQtyError(`Cache data must be an array.`); } if (key === "length") proxy[0]; const numKey = +key; if (!isNaN(numKey) && numKey < data.length) { return resolve(proxy, selection.getChild(numKey), meta.type.__type); } } const value = Reflect.get(data, key); if (typeof value === "function") { return value.bind(proxy); } return value; }, set(_, key, value, proxy) { var _a, _b; if (typeof key === "symbol" || key !== "length" && +key !== +key) { throw new GQtyError(`Invalid array assignment.`); } const meta = $meta(proxy); if (!meta) return false; const { cache: { data } } = meta; if (!Array.isArray(data)) return false; value = (_b = (_a = $meta(value)) == null ? void 0 : _a.cache.data) != null ? _b : value; return Reflect.set(data, key, value); } }; const createArrayAccessor = (meta) => { const { cache, context, selection } = meta; if (!Array.isArray(cache.data)) { if (verbose) { console.warn( "Invalid cache for an array accessor, monkey-patch by wrapping it with an array.", meta, meta.context.cache.toJSON() ); } cache.data = [cache.data]; } if (cache.data.length === 0) { context.select(selection); } const proxy = new Proxy(cache.data, arrayProxyHandler); $meta.set(proxy, meta); return proxy; }; export { createArrayAccessor, createObjectAccessor, createUnionAccessor, deepMetadata, resolve };