@sanity/sdk 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/_exports/_internal.d.ts +52 -21
  2. package/dist/_exports/_internal.d.ts.map +1 -1
  3. package/dist/_exports/_internal.js +10 -9
  4. package/dist/_exports/_internal.js.map +1 -1
  5. package/dist/_exports/agent.d.ts +7 -7
  6. package/dist/_exports/agent.d.ts.map +1 -1
  7. package/dist/_exports/agent.js +1 -1
  8. package/dist/_exports/comlink.d.ts +9 -9
  9. package/dist/_exports/comlink.d.ts.map +1 -1
  10. package/dist/_exports/comlink.js +3 -2
  11. package/dist/_exports/comlink.js.map +1 -1
  12. package/dist/_exports/dashboard.d.ts +2 -0
  13. package/dist/_exports/dashboard.js +2 -0
  14. package/dist/{authStore-C-OEkGaM.d.ts → authStore-BO2vch3I.d.ts} +516 -63
  15. package/dist/authStore-BO2vch3I.d.ts.map +1 -0
  16. package/dist/{clientStore-gUALQ0qW.js → clientStore-BI4D8HhJ.js} +124 -7
  17. package/dist/clientStore-BI4D8HhJ.js.map +1 -0
  18. package/dist/{createSanityInstance-DCePpBYW.d.ts → createSanityInstance-BQIqyGLX.d.ts} +16 -1
  19. package/dist/createSanityInstance-BQIqyGLX.d.ts.map +1 -0
  20. package/dist/{object-BwynGPRZ.js → createStateSourceAction-DdAhEfnZ.js} +2 -82
  21. package/dist/createStateSourceAction-DdAhEfnZ.js.map +1 -0
  22. package/dist/defineProperty-Bho9Ngsi.js +31 -0
  23. package/dist/{getNodeState-E0hjqFOo.js → getNodeState-B4nG0sjm.js} +3 -2
  24. package/dist/{getNodeState-E0hjqFOo.js.map → getNodeState-B4nG0sjm.js.map} +1 -1
  25. package/dist/index.d.ts +3 -3
  26. package/dist/index.js +335 -51
  27. package/dist/index.js.map +1 -1
  28. package/dist/object-CxXlSVg7.js +83 -0
  29. package/dist/object-CxXlSVg7.js.map +1 -0
  30. package/dist/topicStore-Ct7IFNtY.js +565 -0
  31. package/dist/topicStore-Ct7IFNtY.js.map +1 -0
  32. package/dist/topicStore-D0WqhQGF.d.ts +435 -0
  33. package/dist/topicStore-D0WqhQGF.d.ts.map +1 -0
  34. package/dist/{version-C90rNWaX.js → version-BcRmCiYi.js} +6 -6
  35. package/dist/{version-C90rNWaX.js.map → version-BcRmCiYi.js.map} +1 -1
  36. package/package.json +19 -18
  37. package/src/_exports/_internal.ts +12 -0
  38. package/src/_exports/dashboard.ts +36 -0
  39. package/src/_exports/index.ts +17 -1
  40. package/src/applications/applications.test-d.ts +28 -4
  41. package/src/applications/applications.ts +39 -14
  42. package/src/auth/authMode.test.ts +24 -0
  43. package/src/auth/authMode.ts +8 -3
  44. package/src/auth/authStore.ts +12 -0
  45. package/src/auth/authStrategy.ts +2 -0
  46. package/src/auth/logout.test.ts +45 -0
  47. package/src/auth/logout.ts +1 -0
  48. package/src/auth/oauth/oauthActions.test.ts +457 -0
  49. package/src/auth/oauth/oauthActions.ts +389 -0
  50. package/src/auth/oauth/oauthAuth.test.ts +206 -0
  51. package/src/auth/oauth/oauthAuth.ts +168 -0
  52. package/src/auth/oauth/pkce.test.ts +44 -0
  53. package/src/auth/oauth/pkce.ts +60 -0
  54. package/src/auth/oauth/types.ts +12 -0
  55. package/src/config/authConfig.ts +18 -0
  56. package/src/dashboard/messageBus/__fixtures__/test-topics.ts +18 -0
  57. package/src/dashboard/messageBus/bus.test.ts +1033 -0
  58. package/src/dashboard/messageBus/bus.ts +1246 -0
  59. package/src/dashboard/messageBus/store.test.ts +142 -0
  60. package/src/dashboard/messageBus/store.ts +74 -0
  61. package/src/dashboard/messageBus/topicStore.test.ts +172 -0
  62. package/src/dashboard/messageBus/topicStore.ts +184 -0
  63. package/src/dashboard/messageBus/topics.test.ts +71 -0
  64. package/src/dashboard/messageBus/topics.ts +260 -0
  65. package/src/document/actions.ts +3 -3
  66. package/src/document/applyDocumentActions.ts +3 -3
  67. package/src/document/documentStore.ts +12 -12
  68. package/src/document/processMutations.ts +58 -22
  69. package/src/installations/installations.test-d.ts +3 -3
  70. package/src/installations/installations.ts +2 -19
  71. package/src/projection/getProjectionState.ts +2 -2
  72. package/src/projection/resolveProjection.ts +2 -2
  73. package/src/query/queryStore.ts +3 -3
  74. package/src/typegen/defineProjection.test.ts +17 -0
  75. package/src/typegen/defineProjection.ts +19 -0
  76. package/src/typegen/groqCompat.ts +122 -0
  77. package/src/typegen/resolve.ts +37 -0
  78. package/src/utils/createGroqSearchFilter.test.ts +8 -0
  79. package/src/utils/createGroqSearchFilter.ts +6 -3
  80. package/dist/authStore-C-OEkGaM.d.ts.map +0 -1
  81. package/dist/clientStore-gUALQ0qW.js.map +0 -1
  82. package/dist/createSanityInstance-DCePpBYW.d.ts.map +0 -1
  83. package/dist/object-BwynGPRZ.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
- import { D as configureLogging$1, E as getAuthLogger, O as createLogger, S as AuthStateType, T as REQUEST_TAG_PREFIX, b as getTokenFromLocation, c as getDashboardOrganizationId, d as getTokenState, f as setAuthToken, h as getCleanedUrl, i as authStore, l as getIsInDashboardState, m as getAuthCode, n as getClient, o as getAuthState, p as createLoggedInAuthState, r as getClientState, s as getCurrentUserState, u as getLoginUrlState, w as DEFAULT_API_VERSION, y as getDefaultLocation } from "./clientStore-gUALQ0qW.js";
2
- import { a as createStateSourceAction, c as bindActionGlobally, d as isReleasePerspective, f as isCanvasResource, l as defineStore, m as isMediaLibraryResource, o as bindActionByResource, p as isDatasetResource, r as omitProperty, s as bindActionByResourceAndPerspective, t as isDeepEqual } from "./object-BwynGPRZ.js";
3
- import { A as clearPendingTransaction, B as assertDatasetResource, C as getCommentsState, D as toCommentsKeyParts, E as resolveComments, F as rollbackCommentUpdate, G as defineMutation, H as provisionAddonDataset, I as setCommentCreateError, K as randomId, L as setPendingTransaction, M as receiveComment, N as removeCommentById, O as addComment, P as restoreComments, R as normalizeComment, T as resolveCommentThreads, U as COMMENTS_API_VERSION, V as getAddonDatasetState, W as defineFetcher, _ as updateLastLoadMoreRequest, a as resolveQuery, b as commentsStore, c as getAllReleasesState, d as getUsersKey, f as initializeRequest, g as setUsersError, h as setUsersData, j as getCommentsKey, k as applyCommentUpdate, l as addSubscription, m as removeSubscription, o as getPerspectiveState, p as parseUsersKey, q as randomUuid, r as getQueryState, s as getActiveReleasesState, t as CORE_SDK_VERSION, u as cancelRequest, v as PROJECT_API_VERSION, x as getCommentThreadsState, y as USERS_STATE_CLEAR_DELAY, z as toCommentFieldPath } from "./version-C90rNWaX.js";
1
+ import { D as getAuthLogger, E as REQUEST_TAG_PREFIX, O as configureLogging$1, S as AuthStateType, T as DEFAULT_BASE, b as getTokenFromLocation, c as getDashboardOrganizationId, d as getTokenState, f as setAuthToken, h as getCleanedUrl, i as authStore, k as createLogger, l as getIsInDashboardState, m as getAuthCode, n as getClient, o as getAuthState, p as createLoggedInAuthState, r as getClientState, s as getCurrentUserState, u as getLoginUrlState, w as DEFAULT_API_VERSION, y as getDefaultLocation } from "./clientStore-BI4D8HhJ.js";
2
+ import { c as isReleasePerspective, d as isMediaLibraryResource, i as bindActionGlobally, l as isCanvasResource, n as bindActionByResource, o as defineStore, r as bindActionByResourceAndPerspective, t as createStateSourceAction, u as isDatasetResource } from "./createStateSourceAction-DdAhEfnZ.js";
3
+ import { A as clearPendingTransaction, B as assertDatasetResource, C as getCommentsState, D as toCommentsKeyParts, E as resolveComments, F as rollbackCommentUpdate, G as defineMutation, H as provisionAddonDataset, I as setCommentCreateError, K as randomId, L as setPendingTransaction, M as receiveComment, N as removeCommentById, O as addComment, P as restoreComments, R as normalizeComment, T as resolveCommentThreads, U as COMMENTS_API_VERSION, V as getAddonDatasetState, W as defineFetcher, _ as updateLastLoadMoreRequest, a as resolveQuery, b as commentsStore, c as getAllReleasesState, d as getUsersKey, f as initializeRequest, g as setUsersError, h as setUsersData, j as getCommentsKey, k as applyCommentUpdate, l as addSubscription, m as removeSubscription, o as getPerspectiveState, p as parseUsersKey, q as randomUuid, r as getQueryState, s as getActiveReleasesState, t as CORE_SDK_VERSION, u as cancelRequest, v as PROJECT_API_VERSION, x as getCommentThreadsState, y as USERS_STATE_CLEAR_DELAY, z as toCommentFieldPath } from "./version-BcRmCiYi.js";
4
+ import { r as omitProperty, t as isDeepEqual } from "./object-CxXlSVg7.js";
4
5
  import { n as setCleanupTimeout } from "./setCleanupTimeout-BiM6q1KF.js";
5
- import { t as getNodeState } from "./getNodeState-E0hjqFOo.js";
6
+ import { t as _defineProperty } from "./defineProperty-Bho9Ngsi.js";
7
+ import { t as getNodeState } from "./getNodeState-B4nG0sjm.js";
6
8
  import { ClientError, CorsOriginError } from "@sanity/client";
7
9
  import { EMPTY, NEVER, Observable, Subject, Subscription, auditTime, catchError, combineLatest, concat, concatMap, debounceTime, defer, distinctUntilChanged, filter, first, firstValueFrom, from, fromEvent, groupBy, map, merge, mergeMap, of, pairwise, partition, race, retry, share, shareReplay, skip, startWith, switchMap, take, takeUntil, tap, throttle, throwError, timer, withLatestFrom } from "rxjs";
8
10
  import { isKeySegment, isKeyedObject } from "@sanity/types";
@@ -10,7 +12,6 @@ import { DocumentId, getDraftId, getPublishedId, getVersionId, isDraftId, isPubl
10
12
  import { getIndexForKey, getIndexForKey as getIndexForKey$1, getPathDepth, joinPaths, jsonMatch, jsonMatch as jsonMatch$1, slicePath, slicePath as slicePath$1, stringifyPath, stringifyPath as stringifyPath$1 } from "@sanity/json-match";
11
13
  import { SanityEncoder } from "@sanity/mutate";
12
14
  import { createDocumentLoaderFromClient } from "@sanity/mutate/_unstable_store";
13
- import "groq";
14
15
  import { evaluateSync, parse } from "groq-js";
15
16
  import { catchError as catchError$1, distinctUntilChanged as distinctUntilChanged$1, map as map$1, mergeMap as mergeMap$1, retry as retry$1, scan, share as share$1, switchMap as switchMap$1 } from "rxjs/operators";
16
17
  import { applyPatches, parsePatch } from "@sanity/diff-match-patch";
@@ -387,12 +388,273 @@ const handleAuthCallback = bindActionGlobally(authStore, async ({ state, instanc
387
388
  } catch (error) {
388
389
  throw logger.error("Logout request failed", { error }), error;
389
390
  } finally {
390
- logger.info("User logged out, clearing stored tokens"), state.set("logoutSuccess", { authState: {
391
+ logger.info("User logged out, clearing stored tokens"), state.set("logoutSuccess", {
392
+ authState: {
393
+ type: "logged-out",
394
+ isDestroyingSession: !1
395
+ },
396
+ oauthTokens: void 0
397
+ }), storageArea?.removeItem(storageKey), storageArea?.removeItem(`${storageKey}_last_refresh`);
398
+ }
399
+ });
400
+ /**
401
+ * Encodes a byte array as a base64url string (RFC 4648 §5) with no padding,
402
+ * as required for PKCE `code_verifier`/`code_challenge` values.
403
+ *
404
+ * @internal
405
+ */
406
+ function base64UrlEncode(bytes) {
407
+ let binary = "";
408
+ for (let byte of bytes) binary += String.fromCharCode(byte);
409
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
410
+ }
411
+ function randomString() {
412
+ let bytes = /* @__PURE__ */ new Uint8Array(32);
413
+ return crypto.getRandomValues(bytes), base64UrlEncode(bytes);
414
+ }
415
+ /**
416
+ * Generates a PKCE `code_verifier`
417
+ *
418
+ * @internal
419
+ */
420
+ function generateCodeVerifier() {
421
+ return randomString();
422
+ }
423
+ /**
424
+ * Generates the `state` parameter used to defend against CSRF on the OAuth
425
+ * callback.
426
+ *
427
+ * @internal
428
+ */
429
+ function generateState() {
430
+ return randomString();
431
+ }
432
+ /**
433
+ * Derives the PKCE `code_challenge` from a `code_verifier` using
434
+ * `base64url(sha256(verifier))` (the `S256` method).
435
+ *
436
+ * @internal
437
+ */
438
+ async function generateCodeChallenge(codeVerifier) {
439
+ let data = new TextEncoder().encode(codeVerifier), digest = await crypto.subtle.digest("SHA-256", data);
440
+ return base64UrlEncode(new Uint8Array(digest));
441
+ }
442
+ /** sessionStorage key for the PKCE `code_verifier`. */
443
+ const OAUTH_VERIFIER_KEY = "__sanity_oauth_verifier", OAUTH_STATE_KEY = "__sanity_oauth_state";
444
+ /** Builds the RFC 8707 resource indicator for an organisation. */
445
+ function getResourceIndicator(organizationId) {
446
+ return `urn:io.sanity:organization:${organizationId}`;
447
+ }
448
+ /**
449
+ * Serialises tokens for storage, converting `expiresAt` to an ISO string.
450
+ *
451
+ * @internal
452
+ */
453
+ function serializeTokens(tokens) {
454
+ return JSON.stringify({
455
+ accessToken: tokens.accessToken,
456
+ tokenType: tokens.tokenType,
457
+ expiresIn: tokens.expiresIn,
458
+ expiresAt: tokens.expiresAt.toISOString(),
459
+ ...tokens.refreshToken !== void 0 && { refreshToken: tokens.refreshToken }
460
+ });
461
+ }
462
+ /**
463
+ * Reads the store options, throwing when the instance was not configured for
464
+ * OAuth.
465
+ */
466
+ function getOAuthOptions(state) {
467
+ let { options } = state;
468
+ if (!options.oauth) throw Error("OAuth is not configured on this instance (missing `auth.oauth`).");
469
+ return options;
470
+ }
471
+ /** Creates a client for the (unauthenticated) public OAuth token/revoke calls. */
472
+ function createOAuthClient(options) {
473
+ return options.clientFactory({
474
+ apiVersion: "v1",
475
+ requestTagPrefix: REQUEST_TAG_PREFIX,
476
+ useProjectHostname: !1,
477
+ useCdn: !1,
478
+ ...options.apiHost && { apiHost: options.apiHost }
479
+ });
480
+ }
481
+ /** Converts a token endpoint response to the {@link OAuthTokens} shape. */
482
+ function toOAuthTokens(response) {
483
+ return {
484
+ accessToken: response.access_token,
485
+ tokenType: "bearer",
486
+ expiresIn: response.expires_in,
487
+ expiresAt: new Date(Date.now() + response.expires_in * 1e3),
488
+ ...response.refresh_token !== void 0 && { refreshToken: response.refresh_token }
489
+ };
490
+ }
491
+ /**
492
+ * Starts the OAuth authorization-code + PKCE flow: generates a `code_verifier`,
493
+ * `code_challenge` and `state`, persists the verifier and state to
494
+ * `sessionStorage`, then navigates the browser to the authorize endpoint.
495
+ *
496
+ * @public
497
+ */
498
+ const startOAuthAuthorization = bindActionGlobally(authStore, async ({ state, instance }) => {
499
+ let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), codeVerifier = generateCodeVerifier(), oauthState = generateState(), codeChallenge = await generateCodeChallenge(codeVerifier), session = typeof sessionStorage < "u" ? sessionStorage : void 0;
500
+ session?.setItem(OAUTH_VERIFIER_KEY, codeVerifier), session?.setItem(OAUTH_STATE_KEY, oauthState);
501
+ let authorizeUrl = new URL("/v1/auth/oauth/authorize", options.apiHost ?? "https://api.sanity.io");
502
+ authorizeUrl.searchParams.set("response_type", "code"), authorizeUrl.searchParams.set("client_id", options.oauth.clientId), authorizeUrl.searchParams.set("redirect_uri", options.oauth.redirectUri), authorizeUrl.searchParams.set("state", oauthState), authorizeUrl.searchParams.set("code_challenge", codeChallenge), authorizeUrl.searchParams.set("code_challenge_method", "S256"), authorizeUrl.searchParams.append("resource", getResourceIndicator(options.oauth.organizationId)), logger.info("Starting OAuth authorization"), typeof window < "u" && typeof window.location?.assign == "function" && window.location.assign(authorizeUrl.toString());
503
+ }), handleOAuthCallback = bindActionGlobally(authStore, async ({ state, instance }, locationHref = getDefaultLocation()) => {
504
+ let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), { authState } = state.get();
505
+ if (authState.type === "logging-in" && authState.isExchangingToken) return logger.debug("Skipping OAuth callback - token exchange already in progress"), !1;
506
+ let callbackUrl = new URL(locationHref, DEFAULT_BASE), code = callbackUrl.searchParams.get("code"), returnedState = callbackUrl.searchParams.get("state"), error = callbackUrl.searchParams.get("error"), errorDescription = callbackUrl.searchParams.get("error_description"), session = typeof sessionStorage < "u" ? sessionStorage : void 0, cleanedUrlObj = new URL(locationHref, DEFAULT_BASE);
507
+ for (let param of [
508
+ "code",
509
+ "state",
510
+ "error",
511
+ "error_description"
512
+ ]) cleanedUrlObj.searchParams.delete(param);
513
+ let cleanedUrl = cleanedUrlObj.toString();
514
+ if (error) return logger.warn("OAuth callback returned an error", {
515
+ error,
516
+ errorDescription
517
+ }), clearOAuthArtifacts(session), state.set("oauthCallbackError", { authState: {
518
+ type: "error",
519
+ error: Error(errorDescription ? `${error}: ${errorDescription}` : error)
520
+ } }), cleanedUrl;
521
+ if (!code) return logger.debug("No OAuth code found in callback URL"), !1;
522
+ let storedState = session?.getItem("__sanity_oauth_state") ?? null;
523
+ if (!returnedState || !storedState || returnedState !== storedState) return logger.error("OAuth state mismatch — rejecting callback"), clearOAuthArtifacts(session), state.set("oauthStateMismatch", { authState: {
524
+ type: "error",
525
+ error: /* @__PURE__ */ Error("OAuth state mismatch")
526
+ } }), cleanedUrl;
527
+ let codeVerifier = session?.getItem("__sanity_oauth_verifier") ?? null;
528
+ if (!codeVerifier) return logger.error("OAuth code verifier missing — cannot exchange code"), clearOAuthArtifacts(session), state.set("oauthVerifierMissing", { authState: {
529
+ type: "error",
530
+ error: /* @__PURE__ */ Error("OAuth code verifier missing")
531
+ } }), cleanedUrl;
532
+ logger.info("Exchanging OAuth code for tokens"), state.set("oauthExchange", { authState: {
533
+ type: "logging-in",
534
+ isExchangingToken: !0
535
+ } });
536
+ try {
537
+ let client = createOAuthClient(options), params = new URLSearchParams({
538
+ grant_type: "authorization_code",
539
+ code,
540
+ code_verifier: codeVerifier,
541
+ redirect_uri: options.oauth.redirectUri,
542
+ client_id: options.oauth.clientId,
543
+ resource: getResourceIndicator(options.oauth.organizationId)
544
+ }), tokens = toOAuthTokens(await client.request({
545
+ method: "POST",
546
+ url: "/auth/oauth/token",
547
+ headers: { "content-type": "application/x-www-form-urlencoded" },
548
+ body: params.toString(),
549
+ tag: "oauth.token"
550
+ }));
551
+ return options.storageArea?.setItem(options.storageKey, serializeTokens(tokens)), clearOAuthArtifacts(session), logger.info("OAuth tokens obtained, user logged in"), state.set("oauthLoggedIn", {
552
+ authState: createLoggedInAuthState(tokens.accessToken, null),
553
+ oauthTokens: tokens
554
+ }), cleanedUrl;
555
+ } catch (exchangeError) {
556
+ return logger.error("Failed to exchange OAuth code for tokens", { error: exchangeError }), clearOAuthArtifacts(session), state.set("oauthExchangeError", { authState: {
557
+ type: "error",
558
+ error: exchangeError
559
+ } }), cleanedUrl;
560
+ }
561
+ });
562
+ /**
563
+ * A refresh failure is unrecoverable only when the token endpoint rejects the
564
+ * refresh token itself (a 4xx). Rate-limit (429) and request-timeout (408)
565
+ * responses, 5xx errors and network failures are transient, so the session is
566
+ * kept intact for the caller to retry rather than forcing a logout.
567
+ */
568
+ function isUnrecoverableRefreshError(error) {
569
+ return error instanceof ClientError && error.statusCode !== 408 && error.statusCode !== 429;
570
+ }
571
+ let refreshInFlight = null;
572
+ /**
573
+ * Refreshes the OAuth tokens using the `refresh_token` grant. Concurrent
574
+ * callers share a single in-flight request. An unrecoverable failure (a 4xx
575
+ * rejecting the refresh token) clears the tokens and transitions to
576
+ * `LOGGED_OUT`; transient failures (network, 5xx, rate limits) leave the
577
+ * session intact and rethrow so the caller can retry.
578
+ *
579
+ * @public
580
+ */
581
+ const refreshOAuthTokens = bindActionGlobally(authStore, (context) => refreshInFlight || (refreshInFlight = doRefreshOAuthTokens(context).finally(() => {
582
+ refreshInFlight = null;
583
+ }), refreshInFlight));
584
+ async function doRefreshOAuthTokens({ state, instance }) {
585
+ let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), current = state.get().oauthTokens;
586
+ if (!current?.refreshToken) return logger.warn("No refresh token available — logging out"), options.storageArea?.removeItem(options.storageKey), state.set("oauthRefreshNoToken", {
587
+ authState: {
391
588
  type: "logged-out",
392
589
  isDestroyingSession: !1
393
- } }), storageArea?.removeItem(storageKey), storageArea?.removeItem(`${storageKey}_last_refresh`);
590
+ },
591
+ oauthTokens: void 0
592
+ }), null;
593
+ try {
594
+ let client = createOAuthClient(options), params = new URLSearchParams({
595
+ grant_type: "refresh_token",
596
+ refresh_token: current.refreshToken,
597
+ client_id: options.oauth.clientId,
598
+ resource: getResourceIndicator(options.oauth.organizationId)
599
+ }), tokens = toOAuthTokens(await client.request({
600
+ method: "POST",
601
+ url: "/auth/oauth/token",
602
+ headers: { "content-type": "application/x-www-form-urlencoded" },
603
+ body: params.toString(),
604
+ tag: "oauth.refresh"
605
+ }));
606
+ return tokens.refreshToken ||= current.refreshToken, options.storageArea?.setItem(options.storageKey, serializeTokens(tokens)), logger.info("OAuth tokens refreshed"), state.set("oauthRefreshed", {
607
+ authState: createLoggedInAuthState(tokens.accessToken, null),
608
+ oauthTokens: tokens
609
+ }), tokens;
610
+ } catch (error) {
611
+ throw isUnrecoverableRefreshError(error) ? (logger.error("OAuth token refresh failed — logging out", { error }), options.storageArea?.removeItem(options.storageKey), state.set("oauthRefreshFailed", {
612
+ authState: {
613
+ type: "logged-out",
614
+ isDestroyingSession: !1
615
+ },
616
+ oauthTokens: void 0
617
+ }), error) : (logger.warn("OAuth token refresh failed — keeping session for retry", { error }), error);
394
618
  }
395
- });
619
+ }
620
+ /**
621
+ * Revokes the OAuth tokens then clears local storage and transitions to `LOGGED_OUT`.
622
+ * Local state is cleared even if the revoke request fails.
623
+ *
624
+ * @public
625
+ */
626
+ const revokeOAuthTokens = bindActionGlobally(authStore, async ({ state, instance }) => {
627
+ let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), current = state.get().oauthTokens, token = current?.refreshToken ?? current?.accessToken;
628
+ try {
629
+ if (token) {
630
+ let client = createOAuthClient(options), params = new URLSearchParams({
631
+ token,
632
+ client_id: options.oauth.clientId
633
+ });
634
+ await client.request({
635
+ method: "POST",
636
+ url: "/auth/oauth/revoke",
637
+ headers: { "content-type": "application/x-www-form-urlencoded" },
638
+ body: params.toString(),
639
+ tag: "oauth.revoke"
640
+ }), logger.info("OAuth tokens revoked");
641
+ }
642
+ } catch (error) {
643
+ logger.warn("OAuth revoke request failed — clearing local state anyway", { error });
644
+ } finally {
645
+ options.storageArea?.removeItem(options.storageKey), state.set("oauthRevoked", {
646
+ authState: {
647
+ type: "logged-out",
648
+ isDestroyingSession: !1
649
+ },
650
+ oauthTokens: void 0
651
+ });
652
+ }
653
+ }), getOAuthTokensState = bindActionGlobally(authStore, createStateSourceAction(({ state }) => state.oauthTokens ?? null));
654
+ /** Removes the transient PKCE artifacts from session storage. */
655
+ function clearOAuthArtifacts(session) {
656
+ session?.removeItem(OAUTH_VERIFIER_KEY), session?.removeItem(OAUTH_STATE_KEY);
657
+ }
396
658
  function weaken(node) {
397
659
  return Array.isArray(node) ? node.map(weaken) : node && typeof node == "object" ? "_ref" in node ? {
398
660
  ...node,
@@ -1228,7 +1490,29 @@ function unsetDeep(input, path) {
1228
1490
  let segment = _segment;
1229
1491
  return typeof segment == "number" && Array.isArray(input) && (segment = segment < 0 ? input.length + segment : segment), segment in input ? restOfPath.length ? Array.isArray(input) ? input.map((nestedInput, index) => index === segment ? unsetDeep(nestedInput, restOfPath) : nestedInput) : Object.fromEntries(Object.entries(input).map(([key, value]) => key === segment ? [key, unsetDeep(value, restOfPath)] : [key, value])) : Array.isArray(input) ? input.filter((_nestedInput, index) => index !== segment) : Object.fromEntries(Object.entries(input).filter(([key]) => key !== segment.toString())) : input;
1230
1492
  }
1231
- const patchOperations = {
1493
+ /**
1494
+ * Exposes the patch operations used by {@link processMutations}.
1495
+ *
1496
+ * @remarks
1497
+ * Property order determines execution order in {@link processMutations}:
1498
+ * `ifRevisionID`, `set`, `setIfMissing`, `unset`, `inc`, `dec`, `insert`,
1499
+ * then `diffMatchPatch`. Reordering the properties changes mutation results.
1500
+ * Freezes the collection to prevent callers from replacing the functions
1501
+ * used by the SDK's mutation evaluator.
1502
+ *
1503
+ * @see https://www.sanity.io/docs/http-mutations#5b4db1396e56
1504
+ *
1505
+ * @example
1506
+ * ```ts
1507
+ * const document = {items: [{_key: 'a', title: 'Before'}]}
1508
+ * const updated = patchOperations.set<typeof document>(document, {
1509
+ * 'items[_key=="a"].title': 'After',
1510
+ * })
1511
+ * ```
1512
+ *
1513
+ * @internal
1514
+ */
1515
+ const patchOperations = Object.freeze({
1232
1516
  ifRevisionID,
1233
1517
  set,
1234
1518
  setIfMissing,
@@ -1237,7 +1521,7 @@ const patchOperations = {
1237
1521
  dec,
1238
1522
  insert,
1239
1523
  diffMatchPatch
1240
- };
1524
+ });
1241
1525
  /**
1242
1526
  * Implements ID generation:
1243
1527
  *
@@ -1265,18 +1549,31 @@ function getDocumentIds(selection) {
1265
1549
  return [];
1266
1550
  }
1267
1551
  /**
1268
- * Applies the given mutation to the given document set. Note, it is expected
1269
- * that all relevant documents that the mutations affect should be within the
1270
- * given `document` set. If a document does not exist, it should have the value
1271
- * `null`. If a document is deleted as a result of the mutations, it will still
1272
- * have its document ID present in the returns documents, but it will have a
1273
- * value of `null`.
1552
+ * Applies mutations to an in-memory set of Sanity documents.
1553
+ * Deleted documents remain in the returned set with a value of `null`.
1554
+ *
1555
+ * Include every document affected by `mutations` in `documents`, using `null`
1556
+ * for documents that do not exist.
1557
+ *
1558
+ * Returns `documents` itself when `mutations` is empty. Otherwise, returns a
1559
+ * shallow copy of that object. The result can reuse document objects and
1560
+ * nested objects from `documents`. Mutating a reused object through the
1561
+ * result also changes the input.
1274
1562
  *
1275
- * The given `transactionId` will be used as the resulting `_rev` for documents
1276
- * affected by the given set of mutations.
1563
+ * Sets `_rev` to `transactionId` on created, replaced, and patched documents.
1564
+ * Uses `timestamp` for timestamps unless the mutation preserves them.
1565
+ * Ignores `merge` patches; `inc` and `dec` skip values that are not numbers.
1566
+ * Generates document IDs when omitted or ending in `.`.
1567
+ * Patch operations can generate missing array keys.
1277
1568
  *
1278
- * If a `timestamp` is given, that will be used as for the relevant `_updatedAt`
1279
- * and `_createdAt` timestamps.
1569
+ * Does not persist documents or run Content Lake's server validation.
1570
+ *
1571
+ * @throws If a selection uses a query, a `create` targets an existing document,
1572
+ * or a `patch` targets a document that does not exist.
1573
+ * @throws If a patch operation fails, including a revision check failure or
1574
+ * `diffMatchPatch` applied to a value other than a string or `undefined`.
1575
+ *
1576
+ * @internal
1280
1577
  */
1281
1578
  function processMutations({ documents, mutations, transactionId, timestamp }) {
1282
1579
  if (!mutations.length) return documents;
@@ -1336,36 +1633,6 @@ function processMutations({ documents, mutations, transactionId, timestamp }) {
1336
1633
  }
1337
1634
  return dataset;
1338
1635
  }
1339
- function _typeof(o) {
1340
- "@babel/helpers - typeof";
1341
- return _typeof = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(o) {
1342
- return typeof o;
1343
- } : function(o) {
1344
- return o && typeof Symbol == "function" && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
1345
- }, _typeof(o);
1346
- }
1347
- function toPrimitive(t, r) {
1348
- if (_typeof(t) != "object" || !t) return t;
1349
- var e = t[Symbol.toPrimitive];
1350
- if (e !== void 0) {
1351
- var i = e.call(t, r || "default");
1352
- if (_typeof(i) != "object") return i;
1353
- throw TypeError("@@toPrimitive must return a primitive value.");
1354
- }
1355
- return (r === "string" ? String : Number)(t);
1356
- }
1357
- function toPropertyKey(t) {
1358
- var i = toPrimitive(t, "string");
1359
- return _typeof(i) == "symbol" ? i : i + "";
1360
- }
1361
- function _defineProperty(e, r, t) {
1362
- return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
1363
- value: t,
1364
- enumerable: !0,
1365
- configurable: !0,
1366
- writable: !0
1367
- }) : e[r] = t, e;
1368
- }
1369
1636
  var OutOfSyncError = class extends Error {
1370
1637
  constructor(message, state) {
1371
1638
  super(message), _defineProperty(
@@ -4228,6 +4495,23 @@ function resolveProjection(...args) {
4228
4495
  */
4229
4496
  const _resolveProjection = bindActionByResourceAndPerspective(projectionStore, ({ instance }, options) => firstValueFrom(getProjectionState(instance, options).observable.pipe(filter((state) => !!state?.data))));
4230
4497
  /**
4498
+ * Preserves a projection string's literal type and returns the string unchanged.
4499
+ * Use this import when migrating an app with existing experimental Typegen output.
4500
+ *
4501
+ * Current Typegen does not scan this helper. New or changed projections need an
4502
+ * explicit result type until projection generation is supported.
4503
+ *
4504
+ * @example
4505
+ * ```ts
4506
+ * const preview = defineProjection('{title, "author": author->name}')
4507
+ * ```
4508
+ *
4509
+ * @beta
4510
+ */
4511
+ function defineProjection(projection) {
4512
+ return projection;
4513
+ }
4514
+ /**
4231
4515
  * @public
4232
4516
  * Extracts the project ID from a CorsOriginError message.
4233
4517
  * @param error - The error to extract the project ID from.
@@ -4265,6 +4549,6 @@ function isImportError(error) {
4265
4549
  let message = error.message || "";
4266
4550
  return /Loading chunk [\w-]+ failed/i.test(message) || /Failed to fetch dynamically imported module/i.test(message) || /error loading dynamically imported module/i.test(message) || /Import(?:ing)? a module script failed/i.test(message) || /Unable to preload CSS/i.test(message);
4267
4551
  }
4268
- export { AuthStateType, CORE_SDK_VERSION, application, applications, applyDocumentActions, archiveRelease, checkPermissions, configureLogging, createComment, createDatasetHandle, createDocument, createDocumentHandle, createDocumentTypeHandle, createProjectHandle, createRelease, createSanityInstance, datasets, deleteApplication, deleteDocument, deleteRelease, discardDocument, editDocument, editRelease, favorites, getActiveReleasesState, getAllReleasesState, getAuthState, getClient, getClientState, getCommentThreadsState, getCommentsState, getCorsErrorProjectId, getCurrentUserState, getDashboardOrganizationId, getDocumentPresence, getDocumentState, getDocumentSyncStatus, getIndexForKey, getIsInDashboardState, getLoginUrlState, getPathDepth, getPermissionsState, getPerspectiveState, getPresence, getProjectionState, getQueryState, getReleaseDocumentId, getTokenState, getUserState, getUsersState, handleAuthCallback, installation, installations, isCanvasResource, isDatasetResource, isImportError, isMediaLibraryResource, joinPaths, jsonMatch, loadMoreUsers, logout, observeOrganizationVerificationState, organization, organizations, project, projects, publishDocument, publishRelease, removeComment, replyToComment, reportPresence, resolveCommentThreads, resolveComments, resolveDocument, resolvePermissions, resolveProjection, resolveQuery, resolveUser, resolveUsers, scheduleRelease, setAuthToken, setCommentStatus, setFavorite, slicePath, stringifyPath, subscribeDocumentEvents, unarchiveRelease, unpublishDocument, unscheduleRelease, updateApplication, updateComment };
4552
+ export { AuthStateType, CORE_SDK_VERSION, application, applications, applyDocumentActions, archiveRelease, checkPermissions, configureLogging, createComment, createDatasetHandle, createDocument, createDocumentHandle, createDocumentTypeHandle, createProjectHandle, createRelease, createSanityInstance, datasets, defineProjection, deleteApplication, deleteDocument, deleteRelease, discardDocument, editDocument, editRelease, favorites, getActiveReleasesState, getAllReleasesState, getAuthState, getClient, getClientState, getCommentThreadsState, getCommentsState, getCorsErrorProjectId, getCurrentUserState, getDashboardOrganizationId, getDocumentPresence, getDocumentState, getDocumentSyncStatus, getIndexForKey, getIsInDashboardState, getLoginUrlState, getOAuthTokensState, getPathDepth, getPermissionsState, getPerspectiveState, getPresence, getProjectionState, getQueryState, getReleaseDocumentId, getTokenState, getUserState, getUsersState, handleAuthCallback, handleOAuthCallback, installation, installations, isCanvasResource, isDatasetResource, isImportError, isMediaLibraryResource, joinPaths, jsonMatch, loadMoreUsers, logout, observeOrganizationVerificationState, organization, organizations, patchOperations, processMutations, project, projects, publishDocument, publishRelease, refreshOAuthTokens, removeComment, replyToComment, reportPresence, resolveCommentThreads, resolveComments, resolveDocument, resolvePermissions, resolveProjection, resolveQuery, resolveUser, resolveUsers, revokeOAuthTokens, scheduleRelease, setAuthToken, setCommentStatus, setFavorite, slicePath, startOAuthAuthorization, stringifyPath, subscribeDocumentEvents, unarchiveRelease, unpublishDocument, unscheduleRelease, updateApplication, updateComment };
4269
4553
 
4270
4554
  //# sourceMappingURL=index.js.map