@sanity/sdk 3.1.0 → 3.3.0-rc.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 (86) 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-BGoEGxOq.d.ts} +531 -63
  15. package/dist/authStore-BGoEGxOq.d.ts.map +1 -0
  16. package/dist/{clientStore-gUALQ0qW.js → clientStore-QDrJNTM4.js} +128 -7
  17. package/dist/clientStore-QDrJNTM4.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 +371 -54
  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-DXvuH9h4.d.ts +435 -0
  33. package/dist/topicStore-DXvuH9h4.d.ts.map +1 -0
  34. package/dist/{version-C90rNWaX.js → version-CIhX5ppJ.js} +6 -6
  35. package/dist/{version-C90rNWaX.js.map → version-CIhX5ppJ.js.map} +1 -1
  36. package/package.json +20 -19
  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.test.ts +18 -0
  45. package/src/auth/authStore.ts +15 -0
  46. package/src/auth/authStrategy.ts +2 -0
  47. package/src/auth/handleAuthCallback.test.ts +33 -0
  48. package/src/auth/handleAuthCallback.ts +11 -1
  49. package/src/auth/logout.test.ts +53 -0
  50. package/src/auth/logout.ts +19 -1
  51. package/src/auth/oauth/oauthActions.test.ts +593 -0
  52. package/src/auth/oauth/oauthActions.ts +438 -0
  53. package/src/auth/oauth/oauthAuth.test.ts +229 -0
  54. package/src/auth/oauth/oauthAuth.ts +178 -0
  55. package/src/auth/oauth/pkce.test.ts +44 -0
  56. package/src/auth/oauth/pkce.ts +60 -0
  57. package/src/auth/oauth/types.ts +12 -0
  58. package/src/config/authConfig.ts +18 -0
  59. package/src/dashboard/messageBus/__fixtures__/test-topics.ts +18 -0
  60. package/src/dashboard/messageBus/bus.test.ts +1033 -0
  61. package/src/dashboard/messageBus/bus.ts +1246 -0
  62. package/src/dashboard/messageBus/store.test.ts +142 -0
  63. package/src/dashboard/messageBus/store.ts +74 -0
  64. package/src/dashboard/messageBus/topicStore.test.ts +172 -0
  65. package/src/dashboard/messageBus/topicStore.ts +184 -0
  66. package/src/dashboard/messageBus/topics.test.ts +71 -0
  67. package/src/dashboard/messageBus/topics.ts +260 -0
  68. package/src/document/actions.ts +3 -3
  69. package/src/document/applyDocumentActions.ts +3 -3
  70. package/src/document/documentStore.ts +12 -12
  71. package/src/document/processMutations.ts +58 -22
  72. package/src/installations/installations.test-d.ts +3 -3
  73. package/src/installations/installations.ts +2 -19
  74. package/src/projection/getProjectionState.ts +2 -2
  75. package/src/projection/resolveProjection.ts +2 -2
  76. package/src/query/queryStore.ts +3 -3
  77. package/src/typegen/defineProjection.test.ts +17 -0
  78. package/src/typegen/defineProjection.ts +19 -0
  79. package/src/typegen/groqCompat.ts +122 -0
  80. package/src/typegen/resolve.ts +37 -0
  81. package/src/utils/createGroqSearchFilter.test.ts +8 -0
  82. package/src/utils/createGroqSearchFilter.ts +6 -3
  83. package/dist/authStore-C-OEkGaM.d.ts.map +0 -1
  84. package/dist/clientStore-gUALQ0qW.js.map +0 -1
  85. package/dist/createSanityInstance-DCePpBYW.d.ts.map +0 -1
  86. 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-QDrJNTM4.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-CIhX5ppJ.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";
@@ -302,11 +303,295 @@ function observeOrganizationVerificationState(instance, projectIds) {
302
303
  }), distinctUntilChanged((prev, curr) => prev.error === curr.error));
303
304
  }
304
305
  /**
306
+ * Encodes a byte array as a base64url string (RFC 4648 §5) with no padding,
307
+ * as required for PKCE `code_verifier`/`code_challenge` values.
308
+ *
309
+ * @internal
310
+ */
311
+ function base64UrlEncode(bytes) {
312
+ let binary = "";
313
+ for (let byte of bytes) binary += String.fromCharCode(byte);
314
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
315
+ }
316
+ function randomString() {
317
+ let bytes = /* @__PURE__ */ new Uint8Array(32);
318
+ return crypto.getRandomValues(bytes), base64UrlEncode(bytes);
319
+ }
320
+ /**
321
+ * Generates a PKCE `code_verifier`
322
+ *
323
+ * @internal
324
+ */
325
+ function generateCodeVerifier() {
326
+ return randomString();
327
+ }
328
+ /**
329
+ * Generates the `state` parameter used to defend against CSRF on the OAuth
330
+ * callback.
331
+ *
332
+ * @internal
333
+ */
334
+ function generateState() {
335
+ return randomString();
336
+ }
337
+ /**
338
+ * Derives the PKCE `code_challenge` from a `code_verifier` using
339
+ * `base64url(sha256(verifier))` (the `S256` method).
340
+ *
341
+ * @internal
342
+ */
343
+ async function generateCodeChallenge(codeVerifier) {
344
+ let data = new TextEncoder().encode(codeVerifier), digest = await crypto.subtle.digest("SHA-256", data);
345
+ return base64UrlEncode(new Uint8Array(digest));
346
+ }
347
+ /** sessionStorage key for the PKCE `code_verifier`. */
348
+ const OAUTH_VERIFIER_KEY = "__sanity_oauth_verifier", OAUTH_STATE_KEY = "__sanity_oauth_state", OAUTH_RETURN_TO_KEY = "__sanity_oauth_return_to", OAUTH_CALLBACK_PARAMS = [
349
+ "code",
350
+ "state",
351
+ "error",
352
+ "error_description"
353
+ ];
354
+ /** Strips the OAuth callback params from a URL. */
355
+ function stripOAuthParams(href) {
356
+ let url = new URL(href, DEFAULT_BASE);
357
+ for (let param of OAUTH_CALLBACK_PARAMS) url.searchParams.delete(param);
358
+ return url.toString();
359
+ }
360
+ /** Whether `href` is the registered redirect URI (origin + pathname; query and hash ignored). */
361
+ function isCallbackRoute(href, redirectUri) {
362
+ let current = new URL(href, DEFAULT_BASE), redirect = new URL(redirectUri, DEFAULT_BASE);
363
+ return current.origin === redirect.origin && current.pathname === redirect.pathname;
364
+ }
365
+ /** Builds the RFC 8707 resource indicator for an organisation. */
366
+ function getResourceIndicator(organizationId) {
367
+ return `urn:io.sanity:organization:${organizationId}`;
368
+ }
369
+ /**
370
+ * Serialises tokens for storage, converting `expiresAt` to an ISO string.
371
+ *
372
+ * @internal
373
+ */
374
+ function serializeTokens(tokens) {
375
+ return JSON.stringify({
376
+ accessToken: tokens.accessToken,
377
+ tokenType: tokens.tokenType,
378
+ expiresIn: tokens.expiresIn,
379
+ expiresAt: tokens.expiresAt.toISOString(),
380
+ ...tokens.refreshToken !== void 0 && { refreshToken: tokens.refreshToken }
381
+ });
382
+ }
383
+ /**
384
+ * Reads the store options, throwing when the instance was not configured for
385
+ * OAuth.
386
+ */
387
+ function getOAuthOptions(state) {
388
+ let { options } = state;
389
+ if (!options.oauth) throw Error("OAuth is not configured on this instance (missing `auth.oauth`).");
390
+ return options;
391
+ }
392
+ /** Creates a client for the (unauthenticated) public OAuth token/revoke calls. */
393
+ function createOAuthClient(options) {
394
+ return options.clientFactory({
395
+ apiVersion: "v1",
396
+ requestTagPrefix: REQUEST_TAG_PREFIX,
397
+ useProjectHostname: !1,
398
+ useCdn: !1,
399
+ ...options.apiHost && { apiHost: options.apiHost }
400
+ });
401
+ }
402
+ /** Converts a token endpoint response to the {@link OAuthTokens} shape. */
403
+ function toOAuthTokens(response) {
404
+ return {
405
+ accessToken: response.access_token,
406
+ tokenType: "bearer",
407
+ expiresIn: response.expires_in,
408
+ expiresAt: new Date(Date.now() + response.expires_in * 1e3),
409
+ ...response.refresh_token !== void 0 && { refreshToken: response.refresh_token }
410
+ };
411
+ }
412
+ /**
413
+ * Starts the OAuth authorization-code + PKCE flow: generates a `code_verifier`,
414
+ * `code_challenge` and `state`, persists the verifier and state to
415
+ * `sessionStorage`, then navigates the browser to the authorize endpoint.
416
+ *
417
+ * @public
418
+ */
419
+ const startOAuthAuthorization = bindActionGlobally(authStore, async ({ state, instance }) => {
420
+ let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), codeVerifier = generateCodeVerifier(), oauthState = generateState(), codeChallenge = await generateCodeChallenge(codeVerifier), session = typeof sessionStorage < "u" ? sessionStorage : void 0;
421
+ session?.setItem(OAUTH_VERIFIER_KEY, codeVerifier), session?.setItem(OAUTH_STATE_KEY, oauthState);
422
+ let currentHref = getDefaultLocation();
423
+ if (currentHref !== "http://localhost") {
424
+ let returnTo = isCallbackRoute(currentHref, options.oauth.redirectUri) ? stripOAuthParams(currentHref) : currentHref;
425
+ session?.setItem(OAUTH_RETURN_TO_KEY, returnTo);
426
+ }
427
+ let authorizeUrl = new URL("/v1/auth/oauth/authorize", options.apiHost ?? "https://api.sanity.io");
428
+ 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());
429
+ }), handleOAuthCallback = bindActionGlobally(authStore, async ({ state, instance }, locationHref = getDefaultLocation()) => {
430
+ let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), { authState } = state.get();
431
+ if (authState.type === "logging-in" && authState.isExchangingToken) return logger.debug("Skipping OAuth callback - token exchange already in progress"), !1;
432
+ 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, cleanedUrl = stripOAuthParams(locationHref), rejectCallback = (name, message) => authState.type === "logged-in" ? (logger.warn(`${message} — ignoring callback, session already established`), cleanedUrl) : (logger.error(`${message} — rejecting callback`), clearOAuthArtifacts(session), state.set(name, { authState: {
433
+ type: "error",
434
+ error: Error(message)
435
+ } }), cleanedUrl);
436
+ if (error) return rejectCallback("oauthCallbackError", errorDescription ? `${error}: ${errorDescription}` : error);
437
+ if (!code) return logger.debug("No OAuth code found in callback URL"), !1;
438
+ let storedState = session?.getItem("__sanity_oauth_state") ?? null;
439
+ if (!returnedState || !storedState || returnedState !== storedState) return rejectCallback("oauthStateMismatch", "OAuth state mismatch");
440
+ let codeVerifier = session?.getItem("__sanity_oauth_verifier") ?? null;
441
+ if (!codeVerifier) return rejectCallback("oauthVerifierMissing", "OAuth code verifier missing");
442
+ logger.info("Exchanging OAuth code for tokens"), state.set("oauthExchange", { authState: {
443
+ type: "logging-in",
444
+ isExchangingToken: !0
445
+ } });
446
+ try {
447
+ let client = createOAuthClient(options), params = new URLSearchParams({
448
+ grant_type: "authorization_code",
449
+ code,
450
+ code_verifier: codeVerifier,
451
+ redirect_uri: options.oauth.redirectUri,
452
+ client_id: options.oauth.clientId,
453
+ resource: getResourceIndicator(options.oauth.organizationId)
454
+ }), tokens = toOAuthTokens(await client.request({
455
+ method: "POST",
456
+ url: "/auth/oauth/token",
457
+ headers: { "content-type": "application/x-www-form-urlencoded" },
458
+ body: params.toString(),
459
+ tag: "oauth.token"
460
+ }));
461
+ options.storageArea?.setItem(options.storageKey, serializeTokens(tokens));
462
+ let returnTo = getSameOriginReturnTo(session, options.oauth.redirectUri);
463
+ return clearOAuthArtifacts(session), logger.info("OAuth tokens obtained, user logged in"), state.set("oauthLoggedIn", {
464
+ authState: createLoggedInAuthState(tokens.accessToken, null),
465
+ oauthTokens: tokens
466
+ }), returnTo ?? cleanedUrl;
467
+ } catch (exchangeError) {
468
+ return logger.error("Failed to exchange OAuth code for tokens", { error: exchangeError }), clearOAuthArtifacts(session), state.set("oauthExchangeError", { authState: {
469
+ type: "error",
470
+ error: exchangeError
471
+ } }), cleanedUrl;
472
+ }
473
+ });
474
+ /**
475
+ * A refresh failure is unrecoverable only when the token endpoint rejects the
476
+ * refresh token itself (a 4xx). Rate-limit (429) and request-timeout (408)
477
+ * responses, 5xx errors and network failures are transient, so the session is
478
+ * kept intact for the caller to retry rather than forcing a logout.
479
+ */
480
+ function isUnrecoverableRefreshError(error) {
481
+ return error instanceof ClientError && error.statusCode !== 408 && error.statusCode !== 429;
482
+ }
483
+ let refreshInFlight = null;
484
+ /**
485
+ * Refreshes the OAuth tokens using the `refresh_token` grant. Concurrent
486
+ * callers share a single in-flight request. An unrecoverable failure (a 4xx
487
+ * rejecting the refresh token) clears the tokens and transitions to
488
+ * `LOGGED_OUT`; transient failures (network, 5xx, rate limits) leave the
489
+ * session intact and rethrow so the caller can retry.
490
+ *
491
+ * @public
492
+ */
493
+ const refreshOAuthTokens = bindActionGlobally(authStore, (context) => refreshInFlight || (refreshInFlight = doRefreshOAuthTokens(context).finally(() => {
494
+ refreshInFlight = null;
495
+ }), refreshInFlight));
496
+ async function doRefreshOAuthTokens({ state, instance }) {
497
+ let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), current = state.get().oauthTokens;
498
+ if (!current?.refreshToken) return logger.warn("No refresh token available — logging out"), options.storageArea?.removeItem(options.storageKey), state.set("oauthRefreshNoToken", {
499
+ authState: {
500
+ type: "logged-out",
501
+ isDestroyingSession: !1
502
+ },
503
+ oauthTokens: void 0
504
+ }), null;
505
+ try {
506
+ let client = createOAuthClient(options), params = new URLSearchParams({
507
+ grant_type: "refresh_token",
508
+ refresh_token: current.refreshToken,
509
+ client_id: options.oauth.clientId,
510
+ resource: getResourceIndicator(options.oauth.organizationId)
511
+ }), tokens = toOAuthTokens(await client.request({
512
+ method: "POST",
513
+ url: "/auth/oauth/token",
514
+ headers: { "content-type": "application/x-www-form-urlencoded" },
515
+ body: params.toString(),
516
+ tag: "oauth.refresh"
517
+ }));
518
+ return tokens.refreshToken ||= current.refreshToken, options.storageArea?.setItem(options.storageKey, serializeTokens(tokens)), logger.info("OAuth tokens refreshed"), state.set("oauthRefreshed", {
519
+ authState: createLoggedInAuthState(tokens.accessToken, null),
520
+ oauthTokens: tokens
521
+ }), tokens;
522
+ } catch (error) {
523
+ throw isUnrecoverableRefreshError(error) ? (logger.error("OAuth token refresh failed — logging out", { error }), options.storageArea?.removeItem(options.storageKey), state.set("oauthRefreshFailed", {
524
+ authState: {
525
+ type: "logged-out",
526
+ isDestroyingSession: !1
527
+ },
528
+ oauthTokens: void 0
529
+ }), error) : (logger.warn("OAuth token refresh failed — keeping session for retry", { error }), error);
530
+ }
531
+ }
532
+ /**
533
+ * Revokes the OAuth tokens then clears local storage and transitions to `LOGGED_OUT`.
534
+ * Local state is cleared even if the revoke request fails.
535
+ *
536
+ * @public
537
+ */
538
+ const revokeOAuthTokens = bindActionGlobally(authStore, async ({ state, instance }) => {
539
+ let logger = getAuthLogger(instance), options = getOAuthOptions(state.get()), current = state.get().oauthTokens, token = current?.refreshToken ?? current?.accessToken;
540
+ try {
541
+ if (token) {
542
+ let client = createOAuthClient(options), params = new URLSearchParams({
543
+ token,
544
+ client_id: options.oauth.clientId
545
+ });
546
+ await client.request({
547
+ method: "POST",
548
+ url: "/auth/oauth/revoke",
549
+ headers: { "content-type": "application/x-www-form-urlencoded" },
550
+ body: params.toString(),
551
+ tag: "oauth.revoke"
552
+ }), logger.info("OAuth tokens revoked");
553
+ }
554
+ } catch (error) {
555
+ logger.warn("OAuth revoke request failed — clearing local state anyway", { error });
556
+ } finally {
557
+ options.storageArea?.removeItem(options.storageKey), state.set("oauthRevoked", {
558
+ authState: {
559
+ type: "logged-out",
560
+ isDestroyingSession: !1
561
+ },
562
+ oauthTokens: void 0
563
+ });
564
+ }
565
+ }), getOAuthTokensState = bindActionGlobally(authStore, createStateSourceAction(({ state }) => state.oauthTokens ?? null));
566
+ /** Removes the transient PKCE artifacts from session storage. */
567
+ function clearOAuthArtifacts(session) {
568
+ session?.removeItem(OAUTH_VERIFIER_KEY), session?.removeItem(OAUTH_STATE_KEY), session?.removeItem(OAUTH_RETURN_TO_KEY);
569
+ }
570
+ /**
571
+ * Reads the stashed pre-authorize location, only honouring it when it shares
572
+ * the registered redirect URI's origin so a tampered value cannot redirect
573
+ * off-site. (`origin` includes the scheme, so non-http(s) URLs are rejected.)
574
+ */
575
+ function getSameOriginReturnTo(session, redirectUri) {
576
+ let returnTo = session?.getItem("__sanity_oauth_return_to") ?? null;
577
+ if (!returnTo) return null;
578
+ try {
579
+ return new URL(returnTo).origin === new URL(redirectUri).origin ? returnTo : null;
580
+ } catch {
581
+ return null;
582
+ }
583
+ }
584
+ /**
585
+ * Handles the auth redirect callback. In OAuth mode this delegates to
586
+ * {@link handleOAuthCallback}; otherwise it exchanges the legacy `sid` for a
587
+ * long-lived token.
588
+ *
305
589
  * @public
306
590
  */
307
591
  const handleAuthCallback = bindActionGlobally(authStore, async ({ state, instance }, locationHref = getDefaultLocation()) => {
308
- let logger = getAuthLogger(instance), { providedToken, callbackUrl, clientFactory, apiHost, storageArea, storageKey } = state.get().options;
592
+ let logger = getAuthLogger(instance), { providedToken, callbackUrl, clientFactory, apiHost, storageArea, storageKey, oauth } = state.get().options;
309
593
  if (providedToken) return logger.debug("Skipping auth callback - token already provided"), !1;
594
+ if (oauth) return logger.debug("OAuth mode - delegating to handleOAuthCallback"), handleOAuthCallback(instance, locationHref);
310
595
  let { authState } = state.get();
311
596
  if (authState.type === "logging-in" && authState.isExchangingToken) return logger.debug("Skipping auth callback - token exchange already in progress"), !1;
312
597
  let cleanedUrl = getCleanedUrl(locationHref), tokenFromUrl = getTokenFromLocation(locationHref);
@@ -353,7 +638,7 @@ const handleAuthCallback = bindActionGlobally(authStore, async ({ state, instanc
353
638
  } }), cleanedUrl;
354
639
  }
355
640
  }), logout = bindActionGlobally(authStore, async ({ state, instance }) => {
356
- let logger = getAuthLogger(instance), { clientFactory, apiHost, providedToken, storageArea, storageKey } = state.get().options;
641
+ let logger = getAuthLogger(instance), { clientFactory, apiHost, providedToken, storageArea, storageKey, oauth } = state.get().options;
357
642
  if (providedToken) {
358
643
  logger.debug("Skipping logout - token is statically provided");
359
644
  return;
@@ -363,6 +648,13 @@ const handleAuthCallback = bindActionGlobally(authStore, async ({ state, instanc
363
648
  logger.debug("Skipping logout - already in progress");
364
649
  return;
365
650
  }
651
+ if (oauth) {
652
+ logger.debug("OAuth mode - revoking tokens instead of calling /auth/logout"), state.set("loggingOut", { authState: {
653
+ type: "logged-out",
654
+ isDestroyingSession: !0
655
+ } }), await revokeOAuthTokens(instance);
656
+ return;
657
+ }
366
658
  let token = authState.type === "logged-in" && authState.token;
367
659
  try {
368
660
  if (token) {
@@ -387,10 +679,13 @@ const handleAuthCallback = bindActionGlobally(authStore, async ({ state, instanc
387
679
  } catch (error) {
388
680
  throw logger.error("Logout request failed", { error }), error;
389
681
  } finally {
390
- logger.info("User logged out, clearing stored tokens"), state.set("logoutSuccess", { authState: {
391
- type: "logged-out",
392
- isDestroyingSession: !1
393
- } }), storageArea?.removeItem(storageKey), storageArea?.removeItem(`${storageKey}_last_refresh`);
682
+ logger.info("User logged out, clearing stored tokens"), state.set("logoutSuccess", {
683
+ authState: {
684
+ type: "logged-out",
685
+ isDestroyingSession: !1
686
+ },
687
+ oauthTokens: void 0
688
+ }), storageArea?.removeItem(storageKey), storageArea?.removeItem(`${storageKey}_last_refresh`);
394
689
  }
395
690
  });
396
691
  function weaken(node) {
@@ -1228,7 +1523,29 @@ function unsetDeep(input, path) {
1228
1523
  let segment = _segment;
1229
1524
  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
1525
  }
1231
- const patchOperations = {
1526
+ /**
1527
+ * Exposes the patch operations used by {@link processMutations}.
1528
+ *
1529
+ * @remarks
1530
+ * Property order determines execution order in {@link processMutations}:
1531
+ * `ifRevisionID`, `set`, `setIfMissing`, `unset`, `inc`, `dec`, `insert`,
1532
+ * then `diffMatchPatch`. Reordering the properties changes mutation results.
1533
+ * Freezes the collection to prevent callers from replacing the functions
1534
+ * used by the SDK's mutation evaluator.
1535
+ *
1536
+ * @see https://www.sanity.io/docs/http-mutations#5b4db1396e56
1537
+ *
1538
+ * @example
1539
+ * ```ts
1540
+ * const document = {items: [{_key: 'a', title: 'Before'}]}
1541
+ * const updated = patchOperations.set<typeof document>(document, {
1542
+ * 'items[_key=="a"].title': 'After',
1543
+ * })
1544
+ * ```
1545
+ *
1546
+ * @internal
1547
+ */
1548
+ const patchOperations = Object.freeze({
1232
1549
  ifRevisionID,
1233
1550
  set,
1234
1551
  setIfMissing,
@@ -1237,7 +1554,7 @@ const patchOperations = {
1237
1554
  dec,
1238
1555
  insert,
1239
1556
  diffMatchPatch
1240
- };
1557
+ });
1241
1558
  /**
1242
1559
  * Implements ID generation:
1243
1560
  *
@@ -1265,18 +1582,31 @@ function getDocumentIds(selection) {
1265
1582
  return [];
1266
1583
  }
1267
1584
  /**
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`.
1585
+ * Applies mutations to an in-memory set of Sanity documents.
1586
+ * Deleted documents remain in the returned set with a value of `null`.
1587
+ *
1588
+ * Include every document affected by `mutations` in `documents`, using `null`
1589
+ * for documents that do not exist.
1590
+ *
1591
+ * Returns `documents` itself when `mutations` is empty. Otherwise, returns a
1592
+ * shallow copy of that object. The result can reuse document objects and
1593
+ * nested objects from `documents`. Mutating a reused object through the
1594
+ * result also changes the input.
1595
+ *
1596
+ * Sets `_rev` to `transactionId` on created, replaced, and patched documents.
1597
+ * Uses `timestamp` for timestamps unless the mutation preserves them.
1598
+ * Ignores `merge` patches; `inc` and `dec` skip values that are not numbers.
1599
+ * Generates document IDs when omitted or ending in `.`.
1600
+ * Patch operations can generate missing array keys.
1601
+ *
1602
+ * Does not persist documents or run Content Lake's server validation.
1274
1603
  *
1275
- * The given `transactionId` will be used as the resulting `_rev` for documents
1276
- * affected by the given set of mutations.
1604
+ * @throws If a selection uses a query, a `create` targets an existing document,
1605
+ * or a `patch` targets a document that does not exist.
1606
+ * @throws If a patch operation fails, including a revision check failure or
1607
+ * `diffMatchPatch` applied to a value other than a string or `undefined`.
1277
1608
  *
1278
- * If a `timestamp` is given, that will be used as for the relevant `_updatedAt`
1279
- * and `_createdAt` timestamps.
1609
+ * @internal
1280
1610
  */
1281
1611
  function processMutations({ documents, mutations, transactionId, timestamp }) {
1282
1612
  if (!mutations.length) return documents;
@@ -1336,36 +1666,6 @@ function processMutations({ documents, mutations, transactionId, timestamp }) {
1336
1666
  }
1337
1667
  return dataset;
1338
1668
  }
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
1669
  var OutOfSyncError = class extends Error {
1370
1670
  constructor(message, state) {
1371
1671
  super(message), _defineProperty(
@@ -4228,6 +4528,23 @@ function resolveProjection(...args) {
4228
4528
  */
4229
4529
  const _resolveProjection = bindActionByResourceAndPerspective(projectionStore, ({ instance }, options) => firstValueFrom(getProjectionState(instance, options).observable.pipe(filter((state) => !!state?.data))));
4230
4530
  /**
4531
+ * Preserves a projection string's literal type and returns the string unchanged.
4532
+ * Use this import when migrating an app with existing experimental Typegen output.
4533
+ *
4534
+ * Current Typegen does not scan this helper. New or changed projections need an
4535
+ * explicit result type until projection generation is supported.
4536
+ *
4537
+ * @example
4538
+ * ```ts
4539
+ * const preview = defineProjection('{title, "author": author->name}')
4540
+ * ```
4541
+ *
4542
+ * @beta
4543
+ */
4544
+ function defineProjection(projection) {
4545
+ return projection;
4546
+ }
4547
+ /**
4231
4548
  * @public
4232
4549
  * Extracts the project ID from a CorsOriginError message.
4233
4550
  * @param error - The error to extract the project ID from.
@@ -4265,6 +4582,6 @@ function isImportError(error) {
4265
4582
  let message = error.message || "";
4266
4583
  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
4584
  }
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 };
4585
+ 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
4586
 
4270
4587
  //# sourceMappingURL=index.js.map