UNPKG

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
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