@sanity/sdk
Version:
1,158 lines • 229 kB
JavaScript
import { createClient } from "@sanity/client";
import { Observable, share, map, distinctUntilChanged, skip, filter, exhaustMap, from, timer, switchMap, takeWhile, firstValueFrom, fromEvent, EMPTY, defer, asapScheduler, combineLatest, of, concatMap, withLatestFrom, concat, throwError, first as first$1, Subject, takeUntil, partition, merge, shareReplay, tap as tap$1, catchError as catchError$1, startWith as startWith$1, pairwise as pairwise$1, groupBy as groupBy$1, mergeMap as mergeMap$1, throttle, race, NEVER, Subscription, retry, debounceTime as debounceTime$1 } from "rxjs";
import { devtools } from "zustand/middleware";
import { createStore } from "zustand/vanilla";
import { pick, omit, isEqual, isObject } from "lodash-es";
import { first, switchMap as switchMap$1, groupBy, mergeMap, startWith, pairwise, filter as filter$1, map as map$1, delay, tap, catchError, scan, share as share$1, take, debounceTime } from "rxjs/operators";
import { createController, createNode } from "@sanity/comlink";
import { createSelector } from "reselect";
import { SanityEncoder } from "@sanity/mutate";
import { getPublishedId as getPublishedId$1 } from "@sanity/client/csm";
import { jsonMatch, stringifyPath, slicePath, getIndexForKey } from "@sanity/json-match";
import { getIndexForKey as getIndexForKey2, getPathDepth, joinPaths, jsonMatch as jsonMatch2, slicePath as slicePath2, stringifyPath as stringifyPath2 } from "@sanity/json-match";
import { diffValue } from "@sanity/diff-patch";
import { applyPatches, parsePatch } from "@sanity/diff-match-patch";
import { isKeySegment, isKeyedObject } from "@sanity/types";
import { createDocumentLoaderFromClient } from "@sanity/mutate/_unstable_store";
import { SDK_CHANNEL_NAME, SDK_NODE_NAME } from "@sanity/message-protocol";
import { fromUrl } from "@sanity/bifur-client";
var AuthStateType = /* @__PURE__ */ ((AuthStateType2) => (AuthStateType2.LOGGED_IN = "logged-in", AuthStateType2.LOGGING_IN = "logging-in", AuthStateType2.ERROR = "error", AuthStateType2.LOGGED_OUT = "logged-out", AuthStateType2))(AuthStateType || {});
function getPublishedId(id) {
const draftsPrefix = "drafts.";
return id.startsWith(draftsPrefix) ? id.slice(draftsPrefix.length) : id;
}
function getDraftId(id) {
const draftsPrefix = "drafts.";
return id.startsWith(draftsPrefix) ? id : `${draftsPrefix}${id}`;
}
function insecureRandomId() {
return Array.from({ length: 16 }, () => Math.floor(Math.random() * 16).toString(16)).join("");
}
function createSanityInstance(config = {}) {
const instanceId = crypto.randomUUID(), disposeListeners = /* @__PURE__ */ new Map(), disposed = { current: !1 }, instance = {
instanceId,
config,
isDisposed: () => disposed.current,
dispose: () => {
disposed.current || (disposed.current = !0, disposeListeners.forEach((listener) => listener()), disposeListeners.clear());
},
onDispose: (cb) => {
const listenerId = insecureRandomId();
return disposeListeners.set(listenerId, cb), () => {
disposeListeners.delete(listenerId);
};
},
getParent: () => {
},
createChild: (next) => Object.assign(
createSanityInstance({
...config,
...next,
...config.auth === next.auth ? config.auth : config.auth && next.auth && { auth: { ...config.auth, ...next.auth } }
}),
{ getParent: () => instance }
),
match: (targetConfig) => {
if (Object.entries(pick(targetConfig, "auth", "projectId", "dataset")).every(
([key, value]) => config[key] === value
))
return instance;
const parent = instance.getParent();
if (parent) return parent.match(targetConfig);
}
};
return instance;
}
function getEnv(key) {
if (typeof import.meta < "u" && import.meta.env)
return import.meta.env[key];
if (typeof process < "u" && process.env)
return process.env[key];
if (typeof window < "u" && window.ENV)
return window.ENV?.[key];
}
function createStoreState(initialState, devToolsOptions) {
const store = createStore()(devtools(() => initialState, devToolsOptions));
return {
get: store.getState,
set: (actionKey, updatedState) => {
const currentState = store.getState(), nextState = typeof updatedState == "function" ? updatedState(currentState) : updatedState;
currentState !== nextState && store.setState(nextState, !1, actionKey);
},
observable: new Observable((observer) => {
const emit = () => observer.next(store.getState());
emit();
const unsubscribe = store.subscribe(emit);
return () => unsubscribe();
})
};
}
function createStoreInstance(instance, { name, getInitialState: getInitialState2, initialize }) {
const state = createStoreState(getInitialState2(instance), {
enabled: !!getEnv("DEV"),
name: `${name}-${instance.config.projectId}.${instance.config.dataset}`
}), dispose = initialize?.({ state, instance }), disposed = { current: !1 };
return {
state,
dispose: () => {
disposed.current || (disposed.current = !0, dispose?.());
},
isDisposed: () => disposed.current
};
}
function createActionBinder(keyFn) {
const instanceRegistry = /* @__PURE__ */ new Map(), storeRegistry = /* @__PURE__ */ new Map();
return function(storeDefinition, action) {
return function(instance, ...params) {
const keySuffix = keyFn(instance.config), compositeKey = storeDefinition.name + (keySuffix ? `:${keySuffix}` : "");
let instances = instanceRegistry.get(compositeKey);
instances || (instances = /* @__PURE__ */ new Set(), instanceRegistry.set(compositeKey, instances)), instances.has(instance.instanceId) || (instances.add(instance.instanceId), instance.onDispose(() => {
instances.delete(instance.instanceId), instances.size === 0 && (storeRegistry.get(compositeKey)?.dispose(), storeRegistry.delete(compositeKey), instanceRegistry.delete(compositeKey));
}));
let storeInstance = storeRegistry.get(compositeKey);
return storeInstance || (storeInstance = createStoreInstance(instance, storeDefinition), storeRegistry.set(compositeKey, storeInstance)), action({ instance, state: storeInstance.state }, ...params);
};
};
}
const bindActionByDataset = createActionBinder(({ projectId, dataset }) => {
if (!projectId || !dataset)
throw new Error("This API requires a project ID and dataset configured.");
return `${projectId}.${dataset}`;
}), bindActionGlobally = createActionBinder(() => "global");
function createStateSourceAction(options) {
const selector = typeof options == "function" ? options : options.selector, subscribeHandler = options && "onSubscribe" in options ? options.onSubscribe : void 0, isEqual2 = options && "isEqual" in options ? options.isEqual ?? Object.is : Object.is, selectorContextCache = /* @__PURE__ */ new WeakMap();
function stateSourceAction(context, ...params) {
const { state, instance } = context, getCurrent = () => {
const currentState = state.get();
if (typeof currentState != "object" || currentState === null)
throw new Error(
`Expected store state to be an object but got "${typeof currentState}" instead`
);
let instanceCache = selectorContextCache.get(currentState);
instanceCache || (instanceCache = /* @__PURE__ */ new WeakMap(), selectorContextCache.set(currentState, instanceCache));
let selectorContext = instanceCache.get(instance);
return selectorContext || (selectorContext = { state: currentState, instance }, instanceCache.set(instance, selectorContext)), selector(selectorContext, ...params);
}, subscribe = (onStoreChanged) => {
const cleanup = subscribeHandler?.(context, ...params), subscription = state.observable.pipe(
// Derive value from current state
map(getCurrent),
// Filter unchanged values using custom equality check
distinctUntilChanged(isEqual2),
// Skip initial emission since we only want changes
skip(1)
).subscribe({
next: () => onStoreChanged?.(),
// Propagate selector errors to both subscription types
error: () => onStoreChanged?.()
});
return () => {
subscription.unsubscribe(), cleanup?.();
};
}, observable = new Observable((observer) => {
const emitCurrent = () => {
try {
observer.next(getCurrent());
} catch (error) {
observer.error(error);
}
};
return emitCurrent(), subscribe(emitCurrent);
}).pipe(share());
return {
getCurrent,
subscribe,
observable
};
}
return stateSourceAction;
}
const DEFAULT_BASE = "http://localhost", AUTH_CODE_PARAM = "sid", DEFAULT_API_VERSION$1 = "2021-06-07", REQUEST_TAG_PREFIX = "sanity.sdk.auth", REFRESH_INTERVAL = 12 * 60 * 60 * 1e3, LOCK_NAME = "sanity-token-refresh-lock";
function getLastRefreshTime(storageArea, storageKey) {
try {
const data = storageArea?.getItem(`${storageKey}_last_refresh`), parsed = data ? parseInt(data, 10) : 0;
return isNaN(parsed) ? 0 : parsed;
} catch {
return 0;
}
}
function setLastRefreshTime(storageArea, storageKey) {
try {
storageArea?.setItem(`${storageKey}_last_refresh`, Date.now().toString());
} catch {
}
}
function getNextRefreshDelay(storageArea, storageKey) {
const lastRefresh = getLastRefreshTime(storageArea, storageKey);
if (!lastRefresh) return 0;
const now = Date.now(), nextRefreshTime = lastRefresh + REFRESH_INTERVAL;
return Math.max(0, nextRefreshTime - now);
}
function createTokenRefreshStream(token, clientFactory, apiHost) {
return new Observable((subscriber) => {
const subscription = clientFactory({
apiVersion: DEFAULT_API_VERSION$1,
requestTagPrefix: "token-refresh",
useProjectHostname: !1,
useCdn: !1,
token,
ignoreBrowserTokenWarning: !0,
...apiHost && { apiHost }
}).observable.request({
uri: "auth/refresh-token",
method: "POST",
body: {
token
}
}).subscribe(subscriber);
return () => subscription.unsubscribe();
});
}
async function acquireTokenRefreshLock(refreshFn, storageArea, storageKey) {
if (!navigator.locks)
return console.warn("Web Locks API not supported. Proceeding with uncoordinated refresh."), await refreshFn(), setLastRefreshTime(storageArea, storageKey), !0;
try {
return await navigator.locks.request(LOCK_NAME, { mode: "exclusive" }, async (lock) => {
if (!lock) return !1;
for (; ; ) {
const delay2 = getNextRefreshDelay(storageArea, storageKey);
delay2 > 0 && await new Promise((resolve) => setTimeout(resolve, delay2));
try {
await refreshFn(), setLastRefreshTime(storageArea, storageKey);
} catch (error) {
console.error("Token refresh failed within lock:", error);
}
await new Promise((resolve) => setTimeout(resolve, REFRESH_INTERVAL));
}
}) === !0;
} catch (error) {
return console.error("Failed to request token refresh lock:", error), !1;
}
}
function shouldRefreshToken(lastRefresh) {
return lastRefresh ? Date.now() - lastRefresh >= REFRESH_INTERVAL : !0;
}
const refreshStampedToken = ({ state }) => {
const { clientFactory, apiHost, storageArea, storageKey } = state.get().options;
return state.observable.pipe(
map((storeState) => ({
authState: storeState.authState,
dashboardContext: storeState.dashboardContext
})),
filter(
(storeState) => storeState.authState.type === AuthStateType.LOGGED_IN
),
distinctUntilChanged(
(prev, curr) => prev.authState.type === curr.authState.type && prev.authState.token === curr.authState.token && // Only care about token for distinctness here
prev.dashboardContext === curr.dashboardContext
),
// Make distinctness check explicit
filter((storeState) => storeState.authState.token.includes("-st")),
// Ensure we only try to refresh stamped tokens
exhaustMap((storeState) => {
const performRefresh = async () => {
const currentState = state.get();
if (currentState.authState.type !== AuthStateType.LOGGED_IN)
throw new Error("User logged out before refresh could complete");
const currentToken = currentState.authState.token, response = await firstValueFrom(
createTokenRefreshStream(currentToken, clientFactory, apiHost)
);
state.set("setRefreshStampedToken", (prev) => ({
authState: prev.authState.type === AuthStateType.LOGGED_IN ? { ...prev.authState, token: response.token } : prev.authState
})), storageArea?.setItem(storageKey, JSON.stringify({ token: response.token }));
};
return storeState.dashboardContext ? new Observable((subscriber) => {
const visibilityHandler = () => {
const currentState = state.get();
document.visibilityState === "visible" && currentState.authState.type === AuthStateType.LOGGED_IN && shouldRefreshToken(currentState.authState.lastTokenRefresh) && createTokenRefreshStream(
currentState.authState.token,
clientFactory,
apiHost
).subscribe({
next: (response) => {
state.set("setRefreshStampedToken", (prev) => ({
authState: prev.authState.type === AuthStateType.LOGGED_IN ? {
...prev.authState,
token: response.token,
lastTokenRefresh: Date.now()
} : prev.authState
})), subscriber.next(response);
},
error: (error) => subscriber.error(error)
});
}, timerSubscription = timer(REFRESH_INTERVAL, REFRESH_INTERVAL).pipe(
filter(() => document.visibilityState === "visible"),
switchMap(() => {
const currentState = state.get().authState;
if (currentState.type !== AuthStateType.LOGGED_IN)
throw new Error("User logged out before refresh could complete");
return createTokenRefreshStream(currentState.token, clientFactory, apiHost);
})
).subscribe({
next: (response) => {
state.set("setRefreshStampedToken", (prev) => ({
authState: prev.authState.type === AuthStateType.LOGGED_IN ? {
...prev.authState,
token: response.token,
lastTokenRefresh: Date.now()
} : prev.authState
})), subscriber.next(response);
},
error: (error) => subscriber.error(error)
});
return document.addEventListener("visibilitychange", visibilityHandler), () => {
document.removeEventListener("visibilitychange", visibilityHandler), timerSubscription.unsubscribe();
};
}).pipe(
takeWhile(() => state.get().authState.type === AuthStateType.LOGGED_IN),
map((response) => ({ token: response.token }))
) : from(acquireTokenRefreshLock(performRefresh, storageArea, storageKey)).pipe(
filter((hasLock) => hasLock),
map(() => {
const currentState = state.get().authState;
if (currentState.type !== AuthStateType.LOGGED_IN)
throw new Error("User logged out before refresh could complete");
return { token: currentState.token };
})
);
})
).subscribe({
next: (response) => {
state.set("setRefreshStampedToken", (prev) => ({
authState: prev.authState.type === AuthStateType.LOGGED_IN ? {
...prev.authState,
token: response.token,
lastTokenRefresh: Date.now()
} : prev.authState
})), storageArea?.setItem(storageKey, JSON.stringify({ token: response.token }));
},
error: (error) => {
state.set("setRefreshStampedTokenError", { authState: { type: AuthStateType.ERROR, error } });
}
});
};
function getAuthCode(callbackUrl, locationHref) {
const loc = new URL(locationHref, DEFAULT_BASE), callbackLocation = callbackUrl ? new URL(callbackUrl, DEFAULT_BASE) : void 0, callbackLocationMatches = callbackLocation ? loc.pathname.toLowerCase().startsWith(callbackLocation.pathname.toLowerCase()) : !0;
let authCode = new URLSearchParams(loc.hash.slice(1)).get(AUTH_CODE_PARAM) || new URLSearchParams(loc.search).get(AUTH_CODE_PARAM);
if (!authCode) {
const contextParam = new URLSearchParams(loc.search).get("_context");
if (contextParam)
try {
const parsedContext = JSON.parse(contextParam);
parsedContext && typeof parsedContext == "object" && typeof parsedContext.sid == "string" && parsedContext.sid && (authCode = parsedContext.sid);
} catch {
}
}
return authCode && callbackLocationMatches ? authCode : null;
}
function getTokenFromLocation(locationHref) {
const loc = new URL(locationHref);
return new URLSearchParams(loc.hash.slice(1)).get("token") || null;
}
function getTokenFromStorage(storageArea, storageKey) {
if (!storageArea) return null;
const item = storageArea.getItem(storageKey);
if (item === null) return null;
try {
const parsed = JSON.parse(item);
if (typeof parsed != "object" || parsed === null || !("token" in parsed) || typeof parsed.token != "string")
throw new Error("Invalid stored auth data structure");
return parsed.token;
} catch {
return storageArea.removeItem(storageKey), null;
}
}
function getStorageEvents() {
return typeof window < "u" && typeof window.addEventListener == "function" ? fromEvent(window, "storage") : EMPTY;
}
function getDefaultStorage() {
try {
return typeof localStorage < "u" && typeof localStorage.getItem == "function" ? localStorage : void 0;
} catch {
return;
}
}
function getDefaultLocation() {
try {
return typeof location > "u" ? DEFAULT_BASE : typeof location.href == "string" ? location.href : DEFAULT_BASE;
} catch {
return DEFAULT_BASE;
}
}
function getCleanedUrl(locationUrl) {
const loc = new URL(locationUrl);
return loc.hash = "", loc.searchParams.delete("sid"), loc.searchParams.delete("url"), loc.toString();
}
async function checkForCookieAuth(projectId, clientFactory) {
if (!projectId) return !1;
try {
return typeof (await clientFactory({
projectId,
useCdn: !1
}).request({
uri: "/users/me",
withCredentials: !0,
tag: "users.get-current"
}))?.id == "string";
} catch {
return !1;
}
}
function getStudioTokenFromLocalStorage(storageArea, projectId) {
if (!storageArea || !projectId) return null;
const studioStorageKey = `__studio_auth_token_${projectId}`;
return getTokenFromStorage(storageArea, studioStorageKey) || null;
}
const subscribeToStateAndFetchCurrentUser = ({
state
}) => {
const { clientFactory, apiHost } = state.get().options;
return state.observable.pipe(
map(({ authState }) => authState),
filter(
(authState) => authState.type === AuthStateType.LOGGED_IN && !authState.currentUser
),
map((authState) => authState.token),
distinctUntilChanged()
).pipe(
map(
(token) => clientFactory({
apiVersion: DEFAULT_API_VERSION$1,
requestTagPrefix: REQUEST_TAG_PREFIX,
token,
ignoreBrowserTokenWarning: !0,
useProjectHostname: !1,
useCdn: !1,
...apiHost && { apiHost }
})
),
switchMap(
(client) => client.observable.request({ uri: "/users/me", method: "GET" })
)
).subscribe({
next: (currentUser) => {
state.set("setCurrentUser", (prev) => ({
authState: prev.authState.type === AuthStateType.LOGGED_IN ? { ...prev.authState, currentUser } : prev.authState
}));
},
error: (error) => {
state.set("setError", { authState: { type: AuthStateType.ERROR, error } });
}
});
}, subscribeToStorageEventsAndSetToken = ({
state
}) => {
const { storageArea, storageKey } = state.get().options;
return defer(getStorageEvents).pipe(
filter(
(e3) => e3.storageArea === storageArea && e3.key === storageKey
),
map(() => getTokenFromStorage(storageArea, storageKey)),
distinctUntilChanged()
).subscribe((token) => {
state.set("updateTokenFromStorageEvent", {
authState: token ? { type: AuthStateType.LOGGED_IN, token, currentUser: null } : { type: AuthStateType.LOGGED_OUT, isDestroyingSession: !1 }
});
});
};
let tokenRefresherRunning = !1;
const authStore = {
name: "Auth",
getInitialState(instance) {
const {
apiHost,
callbackUrl,
providers: customProviders,
token: providedToken,
clientFactory = createClient,
initialLocationHref = getDefaultLocation()
} = instance.config.auth ?? {};
let storageArea = instance.config.auth?.storageArea;
const storageKey = "__sanity_auth_token";
let loginDomain = "https://www.sanity.io";
try {
apiHost && new URL(apiHost).hostname.endsWith(".sanity.work") && (loginDomain = "https://www.sanity.work");
} catch {
}
const loginUrl = new URL("/login", loginDomain);
loginUrl.searchParams.set("origin", getCleanedUrl(initialLocationHref)), loginUrl.searchParams.set("type", "stampedToken"), loginUrl.searchParams.set("withSid", "true");
let dashboardContext = {}, isInDashboard = !1;
try {
const contextParam = new URL(initialLocationHref).searchParams.get("_context");
if (contextParam) {
const parsedContext = JSON.parse(contextParam);
parsedContext && typeof parsedContext == "object" && Object.keys(parsedContext).length > 0 && (delete parsedContext.sid, dashboardContext = parsedContext, isInDashboard = !0);
}
} catch (err) {
console.error("Failed to parse dashboard context from initial location:", err);
}
isInDashboard || (storageArea = storageArea ?? getDefaultStorage());
let token, authMethod;
instance.config.studioMode?.enabled ? (token = getStudioTokenFromLocalStorage(storageArea, instance.config.projectId), token ? authMethod = "localstorage" : checkForCookieAuth(instance.config.projectId, clientFactory).then((isCookieAuthEnabled) => {
isCookieAuthEnabled && (authMethod = "cookie");
})) : (token = getTokenFromStorage(storageArea, storageKey), token && (authMethod = "localstorage"));
let authState;
return providedToken ? authState = { type: AuthStateType.LOGGED_IN, token: providedToken, currentUser: null } : getAuthCode(callbackUrl, initialLocationHref) || getTokenFromLocation(initialLocationHref) ? authState = { type: AuthStateType.LOGGING_IN, isExchangingToken: !1 } : token && !isInDashboard ? authState = { type: AuthStateType.LOGGED_IN, token, currentUser: null } : authState = { type: AuthStateType.LOGGED_OUT, isDestroyingSession: !1 }, {
authState,
dashboardContext,
options: {
apiHost,
loginUrl: loginUrl.toString(),
callbackUrl,
customProviders,
providedToken,
clientFactory,
initialLocationHref,
storageKey,
storageArea,
authMethod
}
};
},
initialize(context) {
const subscriptions = [];
return subscriptions.push(subscribeToStateAndFetchCurrentUser(context)), context.state.get().options?.storageArea && subscriptions.push(subscribeToStorageEventsAndSetToken(context)), tokenRefresherRunning || (tokenRefresherRunning = !0, subscriptions.push(refreshStampedToken(context))), () => {
for (const subscription of subscriptions)
subscription.unsubscribe();
};
}
}, getCurrentUserState = bindActionGlobally(
authStore,
createStateSourceAction(
({ state: { authState } }) => authState.type === AuthStateType.LOGGED_IN ? authState.currentUser : null
)
), getTokenState = bindActionGlobally(
authStore,
createStateSourceAction(
({ state: { authState } }) => authState.type === AuthStateType.LOGGED_IN ? authState.token : null
)
), getAuthMethodState = bindActionGlobally(
authStore,
createStateSourceAction(({ state: { options } }) => options.authMethod)
), getLoginUrlState = bindActionGlobally(
authStore,
createStateSourceAction(({ state: { options } }) => options.loginUrl)
), getAuthState = bindActionGlobally(
authStore,
createStateSourceAction(({ state: { authState } }) => authState)
), getDashboardOrganizationId$1 = bindActionGlobally(
authStore,
createStateSourceAction(({ state: { dashboardContext } }) => dashboardContext?.orgId)
), getIsInDashboardState = bindActionGlobally(
authStore,
createStateSourceAction(
({ state: { dashboardContext } }) => (
// Check if dashboardContext exists and is not empty
!!dashboardContext && Object.keys(dashboardContext).length > 0
)
)
), setAuthToken = bindActionGlobally(authStore, ({ state }, token) => {
const currentAuthState = state.get().authState;
token ? (currentAuthState.type !== AuthStateType.LOGGED_IN || currentAuthState.token !== token) && state.set("setToken", {
authState: {
type: AuthStateType.LOGGED_IN,
token,
// Keep existing user or set to null? Setting to null forces refetch.
// Keep existing user to avoid unnecessary refetches if user data is still valid.
currentUser: currentAuthState.type === AuthStateType.LOGGED_IN ? currentAuthState.currentUser : null
}
}) : currentAuthState.type !== AuthStateType.LOGGED_OUT && state.set("setToken", {
authState: { type: AuthStateType.LOGGED_OUT, isDestroyingSession: !1 }
});
});
function compareProjectOrganization(projectId, projectOrganizationId, currentDashboardOrgId) {
return projectOrganizationId !== currentDashboardOrgId ? {
error: `Project ${projectId} belongs to Organization ${projectOrganizationId ?? "unknown"}, but the Dashboard has Organization ${currentDashboardOrgId} selected`
} : { error: null };
}
const DEFAULT_API_VERSION = "2024-11-12", DEFAULT_REQUEST_TAG_PREFIX = "sanity.sdk", allowedKeys = Object.keys({
apiHost: null,
useCdn: null,
token: null,
perspective: null,
proxy: null,
withCredentials: null,
timeout: null,
maxRetries: null,
dataset: null,
projectId: null,
scope: null,
apiVersion: null,
requestTagPrefix: null,
useProjectHostname: null,
"~experimental_resource": null
}), DEFAULT_CLIENT_CONFIG = {
apiVersion: DEFAULT_API_VERSION,
useCdn: !1,
ignoreBrowserTokenWarning: !0,
allowReconfigure: !1,
requestTagPrefix: DEFAULT_REQUEST_TAG_PREFIX
}, clientStore = {
name: "clientStore",
getInitialState: (instance) => ({
clients: {},
token: getTokenState(instance).getCurrent()
}),
initialize(context) {
const subscription = listenToToken(context), authMethodSubscription = listenToAuthMethod(context);
return () => {
subscription.unsubscribe(), authMethodSubscription.unsubscribe();
};
}
}, listenToToken = ({ instance, state }) => getTokenState(instance).observable.subscribe((token) => {
state.set("setTokenAndResetClients", { token, clients: {} });
}), listenToAuthMethod = ({ instance, state }) => getAuthMethodState(instance).observable.subscribe((authMethod) => {
state.set("setAuthMethod", { authMethod });
}), getClientConfigKey = (options) => JSON.stringify(pick(options, ...allowedKeys)), getClient = bindActionGlobally(
clientStore,
({ state, instance }, options) => {
const disallowedKeys = Object.keys(options).filter((key2) => !allowedKeys.includes(key2));
if (disallowedKeys.length > 0) {
const listFormatter = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
throw new Error(
`The client options provided contains unsupported properties: ${listFormatter.format(disallowedKeys)}. Allowed keys are: ${listFormatter.format(allowedKeys)}.`
);
}
const tokenFromState = state.get().token, { clients, authMethod } = state.get(), projectId = options.projectId ?? instance.config.projectId, dataset = options.dataset ?? instance.config.dataset, apiHost = options.apiHost ?? instance.config.auth?.apiHost, effectiveOptions = {
...DEFAULT_CLIENT_CONFIG,
...(options.scope === "global" || !projectId) && { useProjectHostname: !1 },
token: authMethod === "cookie" ? void 0 : tokenFromState ?? void 0,
...options,
...projectId && { projectId },
...dataset && { dataset },
...apiHost && { apiHost }
};
effectiveOptions.token === null || typeof effectiveOptions.token > "u" ? (delete effectiveOptions.token, authMethod === "cookie" && (effectiveOptions.withCredentials = !0)) : delete effectiveOptions.withCredentials;
const key = getClientConfigKey(effectiveOptions);
if (clients[key]) return clients[key];
const client = createClient(effectiveOptions);
return state.set("addClient", (prev) => ({ clients: { ...prev.clients, [key]: client } })), client;
}
), getClientState = bindActionGlobally(
clientStore,
createStateSourceAction(({ instance }, options) => getClient(instance, options))
);
function createFetcherStore({
name,
fetcher: getObservable,
getKey,
fetchThrottleInternal = 1e3,
stateExpirationDelay = 5e3
}) {
const store = {
name,
getInitialState: () => ({
stateByParams: {}
}),
initialize: (context) => {
const subscription = subscribeToSubscriptionsAndFetch(context);
return () => subscription.unsubscribe();
}
}, subscribeToSubscriptionsAndFetch = ({
state
}) => state.observable.pipe(
// Map the state to an array of [serialized, entry] pairs.
switchMap$1((s2) => {
const entries = Object.entries(s2.stateByParams);
return entries.length > 0 ? from(entries) : EMPTY;
}),
// Group by the serialized key.
groupBy(([key]) => key),
mergeMap(
(group$) => group$.pipe(
// Emit an initial value for pairwise comparisons.
startWith([group$.key, void 0]),
pairwise(),
// Trigger only when the subscriptions array grows.
filter$1(([[, prevEntry], [, currEntry]]) => {
const prevSubs = prevEntry?.subscriptions ?? [];
return (currEntry?.subscriptions ?? []).length > prevSubs.length;
}),
map$1(([, [, currEntry]]) => currEntry),
// Only trigger if we haven't fetched recently.
filter$1((entry) => {
const lastFetch = entry?.lastFetchInitiatedAt;
return lastFetch ? Date.now() - new Date(lastFetch).getTime() >= fetchThrottleInternal : !0;
}),
switchMap$1((entry) => entry ? (state.set("setLastFetchInitiatedAt", (prev) => ({
stateByParams: {
...prev.stateByParams,
[entry.key]: {
...entry,
...prev.stateByParams[entry.key],
lastFetchInitiatedAt: (/* @__PURE__ */ new Date()).toISOString()
}
}
})), getObservable(entry.instance)(...entry.params).pipe(
// the `createStateSourceAction` util requires the update
// to
delay(0, asapScheduler),
tap(
(data) => state.set("setData", (prev) => ({
stateByParams: {
...prev.stateByParams,
[entry.key]: {
...omit(entry, "error"),
...omit(prev.stateByParams[entry.key], "error"),
data
}
}
}))
),
catchError((error) => (state.set("setError", (prev) => ({
stateByParams: {
...prev.stateByParams,
[entry.key]: {
...entry,
...prev.stateByParams[entry.key],
error
}
}
})), EMPTY))
)) : EMPTY)
)
)
).subscribe({
error: (error) => state.set("setError", { error })
}), getState = bindActionGlobally(
store,
createStateSourceAction({
selector: ({
instance,
state: { stateByParams, error }
}, ...params) => {
if (error) throw error;
const key = getKey(instance, ...params), entry = stateByParams[key];
if (entry?.error) throw entry.error;
return entry?.data;
},
onSubscribe: ({ instance, state }, ...params) => {
const subscriptionId = insecureRandomId(), key = getKey(instance, ...params);
return state.set("addSubscription", (prev) => ({
stateByParams: {
...prev.stateByParams,
[key]: {
...prev.stateByParams[key],
instance,
key,
params: prev.stateByParams[key]?.params || params,
subscriptions: [...prev.stateByParams[key]?.subscriptions || [], subscriptionId]
}
}
})), () => {
setTimeout(() => {
state.set("removeSubscription", (prev) => {
const entry = prev.stateByParams[key];
if (!entry) return prev;
const newSubs = (entry.subscriptions || []).filter((id) => id !== subscriptionId);
return newSubs.length === 0 ? { stateByParams: omit(prev.stateByParams, key) } : {
stateByParams: {
...prev.stateByParams,
[key]: {
...entry,
subscriptions: newSubs
}
}
};
});
}, stateExpirationDelay);
};
}
})
), resolveState = bindActionGlobally(
store,
({ instance }, ...params) => firstValueFrom(getState(instance, ...params).observable.pipe(first((i2) => i2 !== void 0)))
);
return { getState, resolveState };
}
const API_VERSION$5 = "v2025-02-19", project = createFetcherStore({
name: "Project",
getKey: (instance, options) => {
const projectId = options?.projectId ?? instance.config.projectId;
if (!projectId)
throw new Error("A projectId is required to use the project API.");
return projectId;
},
fetcher: (instance) => (options = {}) => {
const projectId = options.projectId ?? instance.config.projectId;
return getClientState(instance, {
apiVersion: API_VERSION$5,
scope: "global",
projectId
}).observable.pipe(
switchMap(
(client) => client.observable.projects.getById(
// non-null assertion is fine with the above throwing
projectId ?? instance.config.projectId
)
)
);
}
}), getProjectState = project.getState, resolveProject = project.resolveState, getDashboardOrganizationId = bindActionGlobally(
authStore,
createStateSourceAction(({ state: { dashboardContext } }) => dashboardContext?.orgId)
);
function observeOrganizationVerificationState(instance, projectIds) {
const dashboardOrgId$ = getDashboardOrganizationId(instance).observable.pipe(distinctUntilChanged()), projectOrgIdObservables = projectIds.map(
(id) => getProjectState(instance, { projectId: id }).observable.pipe(
map((project2) => ({ projectId: id, orgId: project2?.organizationId ?? null })),
// Ensure we only proceed if the orgId is loaded, distinct prevents unnecessary checks
distinctUntilChanged((prev, curr) => prev.orgId === curr.orgId)
)
), allProjectOrgIds$ = projectOrgIdObservables.length > 0 ? combineLatest(projectOrgIdObservables) : of([]);
return combineLatest([dashboardOrgId$, allProjectOrgIds$]).pipe(
switchMap(([dashboardOrgId, projectOrgDataArray]) => {
if (!dashboardOrgId || projectOrgDataArray.length === 0)
return of({ error: null });
for (const projectData of projectOrgDataArray) {
if (!projectData.orgId)
continue;
const result = compareProjectOrganization(
projectData.projectId,
projectData.orgId,
dashboardOrgId
);
if (result.error)
return of(result);
}
return of({ error: null });
}),
// Only emit when the overall error status actually changes
distinctUntilChanged((prev, curr) => prev.error === curr.error)
);
}
const handleAuthCallback = bindActionGlobally(
authStore,
async ({ state }, locationHref = getDefaultLocation()) => {
const { providedToken, callbackUrl, clientFactory, apiHost, storageArea, storageKey } = state.get().options;
if (providedToken) return !1;
const { authState } = state.get();
if (authState.type === AuthStateType.LOGGING_IN && authState.isExchangingToken) return !1;
const cleanedUrl = getCleanedUrl(locationHref), tokenFromUrl = getTokenFromLocation(locationHref);
if (tokenFromUrl)
return state.set("setTokenFromUrl", {
authState: { type: AuthStateType.LOGGED_IN, token: tokenFromUrl, currentUser: null }
}), cleanedUrl;
const authCode = getAuthCode(callbackUrl, locationHref);
if (!authCode) return !1;
const parsedUrl = new URL(locationHref);
let dashboardContext = {};
try {
const contextParam = parsedUrl.searchParams.get("_context");
if (contextParam) {
const parsedContext = JSON.parse(contextParam);
parsedContext && typeof parsedContext == "object" && (delete parsedContext.sid, dashboardContext = parsedContext);
}
} catch (err) {
console.error("Failed to parse dashboard context:", err);
}
state.set("exchangeSessionForToken", {
authState: { type: AuthStateType.LOGGING_IN, isExchangingToken: !0 },
dashboardContext
});
try {
const client = clientFactory({
apiVersion: DEFAULT_API_VERSION$1,
requestTagPrefix: REQUEST_TAG_PREFIX,
useProjectHostname: !1,
useCdn: !1,
...apiHost && { apiHost }
}), { token } = await client.request({
method: "GET",
uri: "/auth/fetch",
query: { sid: authCode },
tag: "fetch-token"
});
return storageArea?.setItem(storageKey, JSON.stringify({ token })), state.set("setToken", { authState: { type: AuthStateType.LOGGED_IN, token, currentUser: null } }), cleanedUrl;
} catch (error) {
return state.set("exchangeSessionForTokenError", { authState: { type: AuthStateType.ERROR, error } }), cleanedUrl;
}
}
), logout = bindActionGlobally(authStore, async ({ state }) => {
const { clientFactory, apiHost, providedToken, storageArea, storageKey } = state.get().options;
if (providedToken) return;
const { authState } = state.get();
if (authState.type === AuthStateType.LOGGED_OUT && authState.isDestroyingSession) return;
const token = authState.type === AuthStateType.LOGGED_IN && authState.token;
try {
token && (state.set("loggingOut", {
authState: { type: AuthStateType.LOGGED_OUT, isDestroyingSession: !0 }
}), await clientFactory({
token,
requestTagPrefix: REQUEST_TAG_PREFIX,
apiVersion: DEFAULT_API_VERSION$1,
...apiHost && { apiHost },
useProjectHostname: !1,
useCdn: !1
}).request({ uri: "/auth/logout", method: "POST" }));
} finally {
state.set("logoutSuccess", {
authState: { type: AuthStateType.LOGGED_OUT, isDestroyingSession: !1 }
}), storageArea?.removeItem(storageKey), storageArea?.removeItem(`${storageKey}_last_refresh`);
}
}), destroyController$1 = ({ state }) => {
const { controller } = state.get();
controller && (controller.destroy(), state.set("destroyController", {
controller: null,
channels: /* @__PURE__ */ new Map()
}));
}, getOrCreateChannel$1 = ({ state }, options) => {
const controller = state.get().controller;
if (!controller)
throw new Error("Controller must be initialized before using or creating channels");
const channels = state.get().channels, existing = channels.get(options.name);
if (existing) {
if (!isEqual(existing.options, options))
throw new Error(`Channel "${options.name}" already exists with different options`);
return state.set("incrementChannelRefCount", {
channels: new Map(channels).set(options.name, {
...existing,
refCount: existing.refCount + 1
})
}), existing.channel.start(), existing.channel;
}
const channel = controller.createChannel(options);
return channel.start(), state.set("createChannel", {
channels: new Map(channels).set(options.name, {
channel,
options,
refCount: 1
})
}), channel;
}, getOrCreateController$1 = ({ state, instance }, targetOrigin) => {
const { controller, controllerOrigin } = state.get();
if (controller && controllerOrigin === targetOrigin)
return controller;
controller && destroyController$1({ state });
const newController = createController({ targetOrigin });
return state.set("initializeController", {
controllerOrigin: targetOrigin,
controller: newController
}), newController;
}, releaseChannel$1 = ({ state }, name) => {
const channels = state.get().channels, channelEntry = channels.get(name);
if (channelEntry) {
const newRefCount = channelEntry.refCount === 0 ? 0 : channelEntry.refCount - 1;
newRefCount === 0 ? (channelEntry.channel.stop(), channels.delete(name), state.set("releaseChannel", { channels: new Map(channels) })) : state.set("releaseChannel", {
channels: new Map(channels).set(name, {
...channelEntry,
refCount: newRefCount
})
});
}
}, comlinkControllerStore = {
name: "connectionStore",
getInitialState: () => ({
controller: null,
controllerOrigin: null,
channels: /* @__PURE__ */ new Map()
}),
initialize({ instance }) {
return () => {
destroyController(instance);
};
}
}, destroyController = bindActionGlobally(
comlinkControllerStore,
destroyController$1
), getOrCreateChannel = bindActionGlobally(
comlinkControllerStore,
getOrCreateChannel$1
), getOrCreateController = bindActionGlobally(
comlinkControllerStore,
getOrCreateController$1
), releaseChannel = bindActionGlobally(comlinkControllerStore, releaseChannel$1), getOrCreateNode$1 = ({ state }, options) => {
const nodes = state.get().nodes, existing = nodes.get(options.name);
if (existing) {
if (!isEqual(existing.options, options))
throw new Error(`Node "${options.name}" already exists with different options`);
return existing.node.start(), existing.node;
}
const node = createNode(options);
node.start();
const statusUnsub = node.onStatus((status) => {
const currentNodes = state.get().nodes, currentEntry = currentNodes.get(options.name);
if (!currentEntry) return;
const updatedEntry = {
...currentEntry,
status
};
state.set("updateNodeStatus", {
nodes: new Map(currentNodes).set(options.name, updatedEntry)
});
}), entry = {
node,
options,
status: "idle",
statusUnsub
};
return nodes.set(options.name, entry), state.set("createNode", { nodes }), node;
}, releaseNode$1 = ({ state }, name) => {
const nodes = state.get().nodes, existing = nodes.get(name);
if (existing) {
existing.statusUnsub && existing.statusUnsub(), existing.node.stop(), nodes.delete(name), state.set("removeNode", { nodes });
return;
}
}, comlinkNodeStore = {
name: "nodeStore",
getInitialState: () => ({
nodes: /* @__PURE__ */ new Map(),
subscriptions: /* @__PURE__ */ new Map()
}),
initialize({ state }) {
return () => {
state.get().nodes.forEach(({ node }) => {
node.stop();
});
};
}
}, releaseNode = bindActionGlobally(comlinkNodeStore, releaseNode$1), getOrCreateNode = bindActionGlobally(comlinkNodeStore, getOrCreateNode$1), NODE_RELEASE_TIME = 5e3, selectNode = (context, nodeInput) => context.state.nodes.get(nodeInput.name), getNodeState = bindActionGlobally(
comlinkNodeStore,
createStateSourceAction({
selector: createSelector([selectNode], (nodeEntry) => nodeEntry?.status === "connected" ? {
node: nodeEntry.node,
status: nodeEntry.status
} : void 0),
onSubscribe: ({ state, instance }, nodeInput) => {
const nodeName = nodeInput.name, subscriberId = Symbol("comlink-node-subscriber");
getOrCreateNode(instance, nodeInput);
let subs = state.get().subscriptions.get(nodeName);
return subs || (subs = /* @__PURE__ */ new Set(), state.get().subscriptions.set(nodeName, subs)), subs.add(subscriberId), () => {
setTimeout(() => {
const activeSubs = state.get().subscriptions.get(nodeName);
activeSubs && (activeSubs.delete(subscriberId), activeSubs.size === 0 && (state.get().subscriptions.delete(nodeName), releaseNode(instance, nodeName)));
}, NODE_RELEASE_TIME);
};
}
})
);
function createDocumentHandle(handle) {
return handle;
}
function createDocumentTypeHandle(handle) {
return handle;
}
function createProjectHandle(handle) {
return handle;
}
function createDatasetHandle(handle) {
return handle;
}
const API_VERSION$4 = "v2025-02-19", datasets = createFetcherStore({
name: "Datasets",
getKey: (instance, options) => {
const projectId = options?.projectId ?? instance.config.projectId;
if (!projectId)
throw new Error("A projectId is required to use the project API.");
return projectId;
},
fetcher: (instance) => (options) => getClientState(instance, {
apiVersion: API_VERSION$4,
// non-null assertion is fine because we check above
projectId: options?.projectId ?? instance.config.projectId,
useProjectHostname: !0
}).observable.pipe(switchMap((client) => client.observable.datasets.list()))
}), getDatasetsState = datasets.getState, resolveDatasets = datasets.resolveState, isSanityMutatePatch = (value) => !(typeof value != "object" || !value || !("type" in value) || typeof value.type != "string" || value.type !== "patch" || !("id" in value) || typeof value.id != "string" || !("patches" in value) || !Array.isArray(value.patches));
function createDocument(doc) {
return {
type: "document.create",
...doc,
...doc.documentId && { documentId: getPublishedId(doc.documentId) }
};
}
function deleteDocument(doc) {
return {
type: "document.delete",
...doc,
documentId: getPublishedId(doc.documentId)
};
}
function convertSanityMutatePatch(sanityPatchMutation) {
return SanityEncoder.encode(sanityPatchMutation).map((i2) => {
const copy = { ...i2.patch };
return "id" in copy && delete copy.id, copy;
});
}
function editDocument(doc, patches) {
if (isSanityMutatePatch(patches)) {
const converted = convertSanityMutatePatch(patches) ?? [];
return {
...doc,
type: "document.edit",
documentId: getPublishedId(doc.documentId),
patches: converted
};
}
return {
...doc,
type: "document.edit",
documentId: getPublishedId(doc.documentId),
...patches && { patches: Array.isArray(patches) ? patches : [patches] }
};
}
function publishDocument(doc) {
return {
type: "document.publish",
...doc,
documentId: getPublishedId(doc.documentId)
};
}
function unpublishDocument(doc) {
return {
type: "document.unpublish",
...doc,
documentId: getPublishedId(doc.documentId)
};
}
function discardDocument(doc) {
return {
type: "document.discard",
...doc,
documentId: getPublishedId(doc.documentId)
};
}
const DOCUMENT_STATE_CLEAR_DELAY = 1e3, INITIAL_OUTGOING_THROTTLE_TIME = 1e3, API_VERSION$3 = "v2025-05-06";
function generateArrayKey(length = 12) {
const numBytes = Math.ceil(length / 2), bytes = crypto.getRandomValues(new Uint8Array(numBytes));
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, length);
}
function memoize(fn) {
const cache = /* @__PURE__ */ new WeakMap();
return (input) => {
if (!input || typeof input != "object") return fn(input);
const cached = cache.get(input);
if (cached) return cached;
const result = fn(input);
return cache.set(input, result), result;
};
}
const ensureArrayKeysDeep = memoize((input) => {
if (!input || typeof input != "object") return input;
if (Array.isArray(input))
return !input.length || typeof input[0] != "object" || input.every(isKeyedObject) ? input : input.map((item) => !item || typeof item != "object" ? item : isKeyedObject(item) ? ensureArrayKeysDeep(item) : { ...ensureArrayKeysDeep(item), _key: generateArrayKey() });
const entries = Object.entries(input).map(([key, value]) => [key, ensureArrayKeysDeep(value)]);
return entries.every(([key, value]) => input[key] === value) ? input : Object.fromEntries(entries);
});
function set(input, pathExpressionValues) {
const result = Object.entries(pathExpressionValues).flatMap(
([pathExpression, replacementValue]) => Array.from(jsonMatch(input, pathExpression)).map((matchEntry) => ({
...matchEntry,
replacementValue
}))
).reduce((acc, { path, replacementValue }) => setDeep(acc, path, replacementValue), input);
return ensureArrayKeysDeep(result);
}
function setIfMissing(input, pathExpressionValues) {
const result = Object.entries(pathExpressionValues).flatMap(([pathExpression, replacementValue]) => Array.from(jsonMatch(input, pathExpression)).map((matchEntry) => ({
...matchEntry,
replacementValue
}))).filter((matchEntry) => matchEntry.value === null || matchEntry.value === void 0).reduce((acc, { path, replacementValue }) => setDeep(acc, path, replacementValue), input);
return ensureArrayKeysDeep(result);
}
function unset(input, pathExpressions) {
const result = pathExpressions.flatMap((pathExpression) => Array.from(jsonMatch(input, pathExpression))).reverse().reduce((acc, { path }) => unsetDeep(acc, path), input);
return ensureArrayKeysDeep(result);
}
function insert(input, { items, ...insertPatch }) {
let operation, pathExpression;
if ("before" in insertPatch ? (operation = "before", pathExpression = insertPatch.before) : "after" in insertPatch ? (operation = "after", pathExpression = insertPatch.after) : "replace" in insertPatch && (operation = "replace", pathExpression = insertPatch.replace), !operation || typeof pathExpression != "string" || !pathExpression.length) return input;
const arrayPath = slicePath(pathExpression, 0, -1)