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