sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
341 lines (340 loc) • 14.9 kB
JavaScript
import { jsx, Fragment } from "react/jsx-runtime";
import { c } from "react-compiler-runtime";
import { applySourceDocuments, getPublishedId } from "@sanity/client/csm";
import { createConnectionMachine } from "@sanity/comlink";
import { createCompatibilityActors } from "@sanity/presentation-comlink";
import isEqual from "fast-deep-equal";
import { useReducer, useState, useDeferredValue, useEffect, memo, startTransition } from "react";
import { useProjectId, useDataset, RELEASES_STUDIO_CLIENT_OPTIONS, isReleasePerspective, useClient } from "sanity";
import { useEffectEvent } from "use-effect-event";
import { LOADER_QUERY_GC_INTERVAL, API_VERSION, MIN_LOADER_QUERY_LISTEN_HEARTBEAT_INTERVAL } from "./presentation.mjs";
import { toPlainText } from "@portabletext/react";
import { isPortableTextBlock } from "@portabletext/toolkit";
function reducer$1(state, event) {
switch (event.type) {
case "message":
return {
...state,
messages: [...state.messages, event]
};
case "reconnect":
case "restart":
return {
...state,
messages: [],
resets: state.resets + 1
};
case "welcome":
return state;
default:
throw Error(`Unknown event: ${// oxlint-disable-next-line no-explicit-any
event.type}`, {
cause: event
});
}
}
const initialState$1 = {
messages: [],
resets: 0
};
function useLiveEvents(client) {
const $ = c(3), [state, dispatch] = useReducer(reducer$1, initialState$1), [error, setError] = useState(null);
if (error !== null)
throw error;
let t0, t1;
return $[0] !== client.live ? (t0 = () => {
const subscription = client.live.events({
includeDrafts: !0,
tag: "presentation-loader"
}).subscribe({
next: dispatch,
error: (err) => setError(err instanceof Error ? err : new Error("Unexpected error in useLiveEvents", {
cause: err
}))
});
return () => subscription.unsubscribe();
}, t1 = [client.live], $[0] = client.live, $[1] = t0, $[2] = t1) : (t0 = $[1], t1 = $[2]), useEffect(t0, t1), useDeferredValue(state);
}
const mapChangedValue = (changedValue, {
previousValue
}) => {
if (typeof previousValue == "string") {
if (typeof changedValue == "number")
return `${changedValue}`;
if (Array.isArray(changedValue)) {
if (changedValue.length === 0)
return "";
if (changedValue.some((node) => typeof node == "object" && isPortableTextBlock(node)))
return toPlainText(changedValue);
}
}
return changedValue;
};
function getQueryCacheKey(perspective, query, params) {
return `${perspective}:${query}:${JSON.stringify(params)}`;
}
function gc(state) {
if (state.queries.size < 1)
return state;
const now = Date.now();
if (!Array.from(state.heartbeats.values()).some((entry) => entry.heartbeat !== !1 && now > entry.receivedAt + entry.heartbeat))
return state;
const nextHeartbeats = /* @__PURE__ */ new Map(), nextQueries = /* @__PURE__ */ new Map();
for (const [key, entry] of state.heartbeats.entries())
entry.heartbeat !== !1 && now > entry.receivedAt + entry.heartbeat || (nextHeartbeats.set(key, entry), nextQueries.set(key, state.queries.get(key)));
return {
...state,
queries: nextQueries,
heartbeats: nextHeartbeats
};
}
function queryListen(state, {
payload
}) {
const key = getQueryCacheKey(payload.perspective, payload.query, payload.params), data = {
query: payload.query,
params: payload.params,
perspective: payload.perspective
}, nextHeartbeats = new Map(state.heartbeats);
nextHeartbeats.set(key, {
receivedAt: Date.now(),
heartbeat: payload.heartbeat
});
let nextQueries = state.queries;
return (!state.queries.has(key) || !isEqual(state.queries.get(key), data)) && (nextQueries = new Map(state.queries), nextQueries.set(key, data)), {
heartbeats: nextHeartbeats,
queries: nextQueries
};
}
function reducer(state, action) {
switch (action.type) {
case "query-listen":
return queryListen(state, action);
case "gc":
return gc(state);
default:
throw Error(`Unknown action: ${// oxlint-disable-next-line no-explicit-any
action.type}`, {
cause: action
});
}
}
const initialState = {
queries: /* @__PURE__ */ new Map(),
heartbeats: /* @__PURE__ */ new Map()
};
function useLiveQueries() {
const $ = c(4), [state, dispatch] = useReducer(reducer, initialState);
let t0, t1;
$[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = () => {
const interval = setInterval(() => dispatch({
type: "gc"
}), LOADER_QUERY_GC_INTERVAL);
return () => clearInterval(interval);
}, t1 = [], $[0] = t0, $[1] = t1) : (t0 = $[0], t1 = $[1]), useEffect(t0, t1);
const queries = useDeferredValue(state.queries);
let t2;
return $[2] !== queries ? (t2 = [queries, dispatch], $[2] = queries, $[3] = t2) : t2 = $[3], t2;
}
function LiveQueries(props) {
const $ = c(28), {
controller,
perspective: activePerspective,
onLoadersConnection,
onDocumentsOnPage
} = props, [comlink, setComlink] = useState(), [liveQueries, liveQueriesDispatch] = useLiveQueries(), projectId = useProjectId(), dataset = useDataset();
let t0, t1;
$[0] !== controller || $[1] !== dataset || $[2] !== liveQueriesDispatch || $[3] !== onDocumentsOnPage || $[4] !== onLoadersConnection || $[5] !== projectId ? (t0 = () => {
if (controller) {
const nextComlink = controller.createChannel({
name: "presentation",
connectTo: "loaders",
heartbeat: !0
}, createConnectionMachine().provide({
actors: createCompatibilityActors()
}));
return setComlink(nextComlink), nextComlink.onStatus(onLoadersConnection), nextComlink.on("loader/documents", (data) => {
data.projectId === projectId && data.dataset === dataset && onDocumentsOnPage("loaders", data.perspective, data.documents);
}), nextComlink.on("loader/query-listen", (data_0) => {
if (data_0.projectId === projectId && data_0.dataset === dataset) {
if (typeof data_0.heartbeat == "number" && data_0.heartbeat < MIN_LOADER_QUERY_LISTEN_HEARTBEAT_INTERVAL)
throw new Error(`Loader query listen heartbeat interval must be at least ${MIN_LOADER_QUERY_LISTEN_HEARTBEAT_INTERVAL}ms`);
liveQueriesDispatch({
type: "query-listen",
payload: {
perspective: data_0.perspective,
query: data_0.query,
params: data_0.params,
heartbeat: data_0.heartbeat ?? !1
}
});
}
}), nextComlink.start();
}
return _temp;
}, t1 = [controller, dataset, liveQueriesDispatch, onDocumentsOnPage, onLoadersConnection, projectId], $[0] = controller, $[1] = dataset, $[2] = liveQueriesDispatch, $[3] = onDocumentsOnPage, $[4] = onLoadersConnection, $[5] = projectId, $[6] = t0, $[7] = t1) : (t0 = $[6], t1 = $[7]), useEffect(t0, t1);
let t2;
$[8] !== activePerspective ? (t2 = isReleasePerspective(activePerspective) ? RELEASES_STUDIO_CLIENT_OPTIONS : {
apiVersion: API_VERSION
}, $[8] = activePerspective, $[9] = t2) : t2 = $[9];
const studioClient = useClient(t2);
let t3, t4;
$[10] !== studioClient ? (t4 = studioClient.withConfig({
resultSourceMap: "withKeyArraySelector"
}), $[10] = studioClient, $[11] = t4) : t4 = $[11], t3 = t4;
const client = t3;
let t5, t6;
$[12] !== activePerspective || $[13] !== comlink || $[14] !== dataset || $[15] !== projectId ? (t5 = () => {
comlink && comlink.post("loader/perspective", {
projectId,
dataset,
perspective: activePerspective
});
}, t6 = [comlink, activePerspective, projectId, dataset], $[12] = activePerspective, $[13] = comlink, $[14] = dataset, $[15] = projectId, $[16] = t5, $[17] = t6) : (t5 = $[16], t6 = $[17]), useEffect(t5, t6);
const liveDocument = useDeferredValue(props.liveDocument), liveEvents = useLiveEvents(client);
let t7;
$[18] !== liveQueries ? (t7 = [...liveQueries.entries()], $[18] = liveQueries, $[19] = t7) : t7 = $[19];
let t8;
return $[20] !== client || $[21] !== comlink || $[22] !== dataset || $[23] !== liveDocument || $[24] !== liveEvents || $[25] !== projectId || $[26] !== t7 ? (t8 = /* @__PURE__ */ jsx(Fragment, { children: t7.map((t9) => {
const [key, t10] = t9, {
query,
params,
perspective
} = t10;
return /* @__PURE__ */ jsx(QuerySubscription, { projectId, dataset, perspective, query, params, comlink, client, liveDocument, liveEventsMessages: liveEvents.messages }, `${liveEvents.resets}:${key}`);
}) }), $[20] = client, $[21] = comlink, $[22] = dataset, $[23] = liveDocument, $[24] = liveEvents, $[25] = projectId, $[26] = t7, $[27] = t8) : t8 = $[27], t8;
}
function _temp() {
}
function QuerySubscriptionComponent(props) {
const $ = c(20), {
projectId,
dataset,
perspective,
query,
client,
liveDocument,
params,
comlink,
liveEventsMessages
} = props, {
result,
resultSourceMap,
syncTags: tags
} = useQuerySubscription({
client,
liveDocument,
params,
perspective,
query,
liveEventsMessages
}) || {};
let t0;
$[0] !== dataset || $[1] !== projectId ? (t0 = (comlink_0, perspective_0, query_0, params_0, result_0, resultSourceMap_0, tags_0) => {
comlink_0?.post("loader/query-change", {
projectId,
dataset,
perspective: perspective_0,
query: query_0,
params: params_0,
result: result_0,
resultSourceMap: resultSourceMap_0,
tags: tags_0
});
}, $[0] = dataset, $[1] = projectId, $[2] = t0) : t0 = $[2];
const handleQueryChange = useEffectEvent(t0);
let t1;
$[3] !== comlink || $[4] !== handleQueryChange || $[5] !== params || $[6] !== perspective || $[7] !== query || $[8] !== result || $[9] !== resultSourceMap || $[10] !== tags ? (t1 = () => {
resultSourceMap && handleQueryChange(comlink, perspective, query, params, result, resultSourceMap, tags);
}, $[3] = comlink, $[4] = handleQueryChange, $[5] = params, $[6] = perspective, $[7] = query, $[8] = result, $[9] = resultSourceMap, $[10] = tags, $[11] = t1) : t1 = $[11];
let t2;
return $[12] !== comlink || $[13] !== params || $[14] !== perspective || $[15] !== query || $[16] !== result || $[17] !== resultSourceMap || $[18] !== tags ? (t2 = [comlink, params, perspective, query, result, resultSourceMap, tags], $[12] = comlink, $[13] = params, $[14] = perspective, $[15] = query, $[16] = result, $[17] = resultSourceMap, $[18] = tags, $[19] = t2) : t2 = $[19], useEffect(t1, t2), null;
}
const QuerySubscription = memo(QuerySubscriptionComponent);
QuerySubscription.displayName = "Memo(QuerySubscription)";
function useQuerySubscription(props) {
const $ = c(30), {
liveDocument,
client,
query,
params,
perspective,
liveEventsMessages
} = props, [result, setResult] = useState(null), [resultSourceMap, setResultSourceMap] = useState(null), [syncTags, setSyncTags] = useState(void 0);
let t0;
$[0] !== liveEventsMessages ? (t0 = () => new Set(liveEventsMessages.map(_temp2)), $[0] = liveEventsMessages, $[1] = t0) : t0 = $[1];
const [skipEventIds] = useState(t0);
let t1;
if ($[2] !== liveEventsMessages || $[3] !== skipEventIds || $[4] !== syncTags) {
let t22;
$[6] !== skipEventIds ? (t22 = (msg_0) => !skipEventIds.has(msg_0.id), $[6] = skipEventIds, $[7] = t22) : t22 = $[7];
const recentLiveEvents = liveEventsMessages.filter(t22);
let t32;
$[8] !== syncTags ? (t32 = (msg_1) => msg_1.tags.some((tag) => syncTags?.includes(tag)), $[8] = syncTags, $[9] = t32) : t32 = $[9], t1 = recentLiveEvents.findLast(t32), $[2] = liveEventsMessages, $[3] = skipEventIds, $[4] = syncTags, $[5] = t1;
} else
t1 = $[5];
const lastLiveEventId = t1?.id, [error, setError] = useState(null);
if (error)
throw error;
let t2, t3;
$[10] !== client || $[11] !== lastLiveEventId || $[12] !== params || $[13] !== perspective || $[14] !== query ? (t2 = () => {
const controller = new AbortController();
return client.fetch(query, params, {
lastLiveEventId,
tag: "presentation-loader",
signal: controller.signal,
perspective,
filterResponse: !1,
returnQuery: !1
}).then((response) => {
startTransition(() => {
setResult((prev) => isEqual(prev, response.result) ? prev : response.result), setResultSourceMap((prev_0) => isEqual(prev_0, response.resultSourceMap) ? prev_0 : response.resultSourceMap), setSyncTags((prev_1) => isEqual(prev_1, response.syncTags) ? prev_1 : response.syncTags);
});
}).catch((err) => {
(typeof err != "object" || err?.name !== "AbortError") && setError(err);
}), () => {
controller.abort();
};
}, t3 = [client, lastLiveEventId, params, perspective, query], $[10] = client, $[11] = lastLiveEventId, $[12] = params, $[13] = perspective, $[14] = query, $[15] = t2, $[16] = t3) : (t2 = $[15], t3 = $[16]), useEffect(t2, t3);
let t4;
bb0: {
if (liveDocument && resultSourceMap) {
let t52;
$[17] !== liveDocument || $[18] !== perspective || $[19] !== result || $[20] !== resultSourceMap ? (t52 = turboChargeResultIfSourceMap(liveDocument, result, perspective, resultSourceMap), $[17] = liveDocument, $[18] = perspective, $[19] = result, $[20] = resultSourceMap, $[21] = t52) : t52 = $[21];
let t6;
$[22] !== resultSourceMap || $[23] !== syncTags || $[24] !== t52 ? (t6 = {
result: t52,
resultSourceMap,
syncTags
}, $[22] = resultSourceMap, $[23] = syncTags, $[24] = t52, $[25] = t6) : t6 = $[25], t4 = t6;
break bb0;
}
let t5;
$[26] !== result || $[27] !== resultSourceMap || $[28] !== syncTags ? (t5 = {
result,
resultSourceMap,
syncTags
}, $[26] = result, $[27] = resultSourceMap, $[28] = syncTags, $[29] = t5) : t5 = $[29], t4 = t5;
}
return t4;
}
function _temp2(msg) {
return msg.id;
}
function turboChargeResultIfSourceMap(liveDocument, result, perspective, resultSourceMap) {
if (perspective === "raw")
throw new Error("turboChargeResultIfSourceMap does not support raw perspective");
return applySourceDocuments(result, resultSourceMap, (sourceDocument) => (
// If _projectId is set, it's a cross dataset reference and we should skip it
!sourceDocument._projectId && liveDocument?._id && getPublishedId(liveDocument._id) === getPublishedId(sourceDocument._id) ? typeof liveDocument._id == "string" && typeof sourceDocument._type == "string" ? liveDocument : {
...liveDocument,
_id: liveDocument._id || sourceDocument._id,
_type: liveDocument._type || sourceDocument._type
} : null
), mapChangedValue, perspective);
}
export {
LiveQueries as default,
turboChargeResultIfSourceMap
};
//# sourceMappingURL=LiveQueries.mjs.map