@tanstack/db-collections
Version:
A collection for (aspirationally) every way of loading your data
148 lines (147 loc) • 4.35 kB
JavaScript
import { QueryObserver } from "@tanstack/query-core";
function queryCollectionOptions(config) {
const {
queryKey,
queryFn,
queryClient,
enabled,
refetchInterval,
retry,
retryDelay,
staleTime,
getKey,
onInsert,
onUpdate,
onDelete,
...baseCollectionConfig
} = config;
if (!queryKey) {
throw new Error(`[QueryCollection] queryKey must be provided.`);
}
if (!queryFn) {
throw new Error(`[QueryCollection] queryFn must be provided.`);
}
if (!queryClient) {
throw new Error(`[QueryCollection] queryClient must be provided.`);
}
if (!getKey) {
throw new Error(`[QueryCollection] getKey must be provided.`);
}
const internalSync = (params) => {
const { begin, write, commit, collection } = params;
const observerOptions = {
queryKey,
queryFn,
enabled,
refetchInterval,
retry,
retryDelay,
staleTime,
structuralSharing: true,
notifyOnChangeProps: `all`
};
const localObserver = new QueryObserver(queryClient, observerOptions);
const actualUnsubscribeFn = localObserver.subscribe((result) => {
if (result.isSuccess) {
const newItemsArray = result.data;
if (!Array.isArray(newItemsArray) || newItemsArray.some((item) => typeof item !== `object`)) {
console.error(
`[QueryCollection] queryFn did not return an array of objects. Skipping update.`,
newItemsArray
);
return;
}
const currentSyncedItems = new Map(collection.syncedData);
const newItemsMap = /* @__PURE__ */ new Map();
newItemsArray.forEach((item) => {
const key = getKey(item);
newItemsMap.set(key, item);
});
begin();
const shallowEqual = (obj1, obj2) => {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
return keys1.every((key) => {
if (typeof obj1[key] === `function`) return true;
if (typeof obj1[key] === `object` && obj1[key] !== null) {
return obj1[key] === obj2[key];
}
return obj1[key] === obj2[key];
});
};
currentSyncedItems.forEach((oldItem, key) => {
const newItem = newItemsMap.get(key);
if (!newItem) {
write({ type: `delete`, value: oldItem });
} else if (!shallowEqual(
oldItem,
newItem
)) {
write({ type: `update`, value: newItem });
}
});
newItemsMap.forEach((newItem, key) => {
if (!currentSyncedItems.has(key)) {
write({ type: `insert`, value: newItem });
}
});
commit();
} else if (result.isError) {
console.error(
`[QueryCollection] Error observing query ${String(queryKey)}:`,
result.error
);
}
});
return async () => {
actualUnsubscribeFn();
await queryClient.cancelQueries({ queryKey });
queryClient.removeQueries({ queryKey });
};
};
const refetch = async () => {
return queryClient.refetchQueries({
queryKey
});
};
const wrappedOnInsert = onInsert ? async (params) => {
const handlerResult = await onInsert(params) ?? {};
const shouldRefetch = handlerResult.refetch !== false;
if (shouldRefetch) {
await refetch();
}
return handlerResult;
} : void 0;
const wrappedOnUpdate = onUpdate ? async (params) => {
const handlerResult = await onUpdate(params) ?? {};
const shouldRefetch = handlerResult.refetch !== false;
if (shouldRefetch) {
await refetch();
}
return handlerResult;
} : void 0;
const wrappedOnDelete = onDelete ? async (params) => {
const handlerResult = await onDelete(params) ?? {};
const shouldRefetch = handlerResult.refetch !== false;
if (shouldRefetch) {
await refetch();
}
return handlerResult;
} : void 0;
return {
...baseCollectionConfig,
getKey,
sync: { sync: internalSync },
onInsert: wrappedOnInsert,
onUpdate: wrappedOnUpdate,
onDelete: wrappedOnDelete,
utils: {
refetch
}
};
}
export {
queryCollectionOptions
};
//# sourceMappingURL=query.js.map