@sanity/sdk 2.18.0 → 2.20.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.
- package/dist/_exports/_internal.d.ts +2 -2
- package/dist/_exports/_internal.d.ts.map +1 -1
- package/dist/_exports/_internal.js +196 -22
- package/dist/_exports/_internal.js.map +1 -1
- package/dist/authStore-CWTAuieY.js +1072 -0
- package/dist/authStore-CWTAuieY.js.map +1 -0
- package/dist/clientStore-CMzJ7XhZ.js +168 -0
- package/dist/clientStore-CMzJ7XhZ.js.map +1 -0
- package/dist/createGroqSearchFilter-B0NdA-EP.js +1222 -0
- package/dist/createGroqSearchFilter-B0NdA-EP.js.map +1 -0
- package/dist/getEnv-76AjZd_f.js +178 -0
- package/dist/getEnv-76AjZd_f.js.map +1 -0
- package/dist/index.d.ts +2 -1921
- package/dist/index.js +4165 -3711
- package/dist/index.js.map +1 -1
- package/dist/rolldown-runtime-vyAXikos.js +9 -0
- package/dist/telemetryManager-Bd3O8z2S.js +127 -0
- package/dist/telemetryManager-Bd3O8z2S.js.map +1 -0
- package/dist/utils-BMwA8Kg9.d.ts +3334 -0
- package/dist/utils-BMwA8Kg9.d.ts.map +1 -0
- package/dist/version-DL2l07HG.js +9 -0
- package/dist/version-DL2l07HG.js.map +1 -0
- package/package.json +13 -21
- package/src/_exports/_internal.ts +1 -0
- package/src/_exports/index.ts +37 -1
- package/src/comments/addonDatasetStore.test.ts +215 -0
- package/src/comments/addonDatasetStore.ts +249 -0
- package/src/comments/buildCommentThreads.test.ts +83 -0
- package/src/comments/buildCommentThreads.ts +54 -0
- package/src/comments/commentActions.test.ts +540 -0
- package/src/comments/commentActions.ts +555 -0
- package/src/comments/commentFieldPath.test.ts +40 -0
- package/src/comments/commentFieldPath.ts +44 -0
- package/src/comments/commentsConstants.ts +80 -0
- package/src/comments/commentsStore.test.ts +419 -0
- package/src/comments/commentsStore.ts +483 -0
- package/src/comments/normalizeComment.test.ts +116 -0
- package/src/comments/normalizeComment.ts +45 -0
- package/src/comments/observeComments.test.ts +193 -0
- package/src/comments/observeComments.ts +113 -0
- package/src/comments/reducers.test.ts +277 -0
- package/src/comments/reducers.ts +316 -0
- package/src/comments/types.ts +249 -0
- package/src/comments/weakenReferences.test.ts +47 -0
- package/src/comments/weakenReferences.ts +29 -0
- package/src/document/documentConstants.ts +8 -0
- package/src/document/documentStore.concurrency.test.ts +313 -22
- package/src/document/documentStore.test.ts +98 -1
- package/src/document/documentStore.ts +36 -5
- package/src/document/patchOperations.test.ts +27 -0
- package/src/document/patchOperations.ts +10 -1
- package/src/document/permissions.test.ts +3 -1
- package/src/document/reducers.test.ts +133 -0
- package/src/document/reducers.ts +18 -3
- package/src/document/util.test.ts +46 -0
- package/src/document/util.ts +37 -1
- package/src/presence/bifurTransport.test.ts +155 -3
- package/src/presence/bifurTransport.ts +79 -12
- package/src/presence/paths.test.ts +70 -0
- package/src/presence/paths.ts +41 -0
- package/src/presence/presenceStore.test.ts +822 -2
- package/src/presence/presenceStore.ts +518 -102
- package/src/presence/types.ts +175 -3
- package/src/projection/projectionStore.test.ts +0 -8
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts +0 -926
- package/dist/_chunks-dts/createGroqSearchFilter.d.ts.map +0 -1
- package/dist/_chunks-es/_internal.js +0 -133
- package/dist/_chunks-es/_internal.js.map +0 -1
- package/dist/_chunks-es/createGroqSearchFilter.js +0 -1586
- package/dist/_chunks-es/createGroqSearchFilter.js.map +0 -1
- package/dist/_chunks-es/telemetryManager.js +0 -93
- package/dist/_chunks-es/telemetryManager.js.map +0 -1
- package/dist/_chunks-es/version.js +0 -7
- package/dist/_chunks-es/version.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGroqSearchFilter-B0NdA-EP.js","names":["addSubscriber","removeSubscriber","addSubscriber","removeSubscriber"],"sources":["../src/utils/ids.ts","../src/utils/setCleanupTimeout.ts","../src/comments/commentsConstants.ts","../src/comments/addonDatasetStore.ts","../src/comments/commentFieldPath.ts","../src/comments/buildCommentThreads.ts","../src/comments/normalizeComment.ts","../src/comments/observeComments.ts","../src/comments/reducers.ts","../src/comments/commentsStore.ts","../src/users/usersConstants.ts","../src/users/reducers.ts","../src/client/liveEvents.ts","../src/releases/observeReleases.ts","../src/releases/utils/sortReleases.ts","../src/releases/releasesStore.ts","../src/releases/getPerspectiveState.ts","../src/query/queryStoreConstants.ts","../src/query/reducers.ts","../src/query/queryStore.ts","../src/preview/previewConstants.ts","../src/preview/previewProjectionUtils.ts","../src/utils/createGroqSearchFilter.ts"],"sourcesContent":["export function insecureRandomId(): string {\n return Array.from({length: 16}, () => Math.floor(Math.random() * 16).toString(16)).join('')\n}\n","/**\n * Like `setTimeout`, but calls `.unref()` on the timer when running in Node.js.\n *\n * In Node.js, active timers prevent the process from exiting. Cleanup timers\n * (e.g. deferred subscription removal) should not keep the process alive -\n * `.unref()` lets the process exit naturally while still firing the timer if\n * the process happens to still be running.\n *\n * In browsers, `setTimeout` returns a number and has no `.unref()` method,\n * so this is a no-op there.\n */\nexport function setCleanupTimeout(fn: () => void, delay: number): ReturnType<typeof setTimeout> {\n const timer = setTimeout(fn, delay)\n\n // In Node.js, setTimeout returns an object with an `unref()` method.\n // In browsers, it returns a number. We assign to `unknown` and narrow\n // at runtime so this works in both environments without type assertions.\n const t: unknown = timer\n if (typeof t === 'object' && t !== null && 'unref' in t && typeof t.unref === 'function') {\n t.unref()\n }\n\n return timer\n}\n","import {type ListenOptions} from '@sanity/client'\n\n/** Data API version used for reading and writing comments in the addon dataset. */\nexport const COMMENTS_API_VERSION = 'v2025-05-06'\n\n/** Projects API version used to discover and provision the addon dataset. */\nexport const ADDON_DATASET_API_VERSION = 'v2025-02-19'\n\n/**\n * The dataset profile the Studio tags its comments dataset with. Discovery\n * matches on it, so changing it stops the SDK and the Studio sharing comments.\n */\nexport const ADDON_DATASET_PROFILE = 'comments'\n\n/**\n * How long a comment list outlives its last subscriber before being dropped.\n *\n * Long enough that navigating away from a document and straight back reuses the\n * loaded list instead of refetching and re-suspending.\n */\nexport const COMMENTS_STATE_CLEAR_DELAY = 5000\n\n/**\n * Kept identical to the Studio's own listener options\n * (`packages/sanity/src/core/comments/store/useCommentsStore.ts:29-35`), apart\n * from the request tag. `includeResult` is what lets a mutation event carry the\n * changed comment, which is what makes per-comment reconciliation possible\n * rather than refetching the list.\n */\nexport const LISTEN_OPTIONS: ListenOptions = {\n events: ['welcome', 'mutation', 'reconnect'],\n includeResult: true,\n includeAllVersions: true,\n visibility: 'query',\n tag: 'comments.listen',\n}\n\nconst QUERY_PROJECTION = `{\n _createdAt,\n _id,\n _rev,\n authorId,\n contentSnapshot,\n context,\n lastEditedAt,\n message,\n parentCommentId,\n reactions,\n status,\n target,\n threadId\n}`\n\nconst BASE_FILTERS = [`_type == \"comment\"`, `target.document._ref == $documentId`]\nconst VERSION_FILTER = `target.documentVersionId == $documentVersionId`\nconst NO_VERSION_FILTER = `!defined(target.documentVersionId)`\n\n/**\n * Which comments belong to a document, matching the Studio's filter.\n *\n * A release's comments are kept apart from the default ones: asking for a\n * release returns only that release's comments, and asking without one returns\n * only comments with no version at all.\n */\nfunction buildCommentsFilter(documentVersionId?: string): string {\n return [...BASE_FILTERS, documentVersionId ? VERSION_FILTER : NO_VERSION_FILTER].join(' && ')\n}\n\n/** The snapshot query. Newest comment first, as the Studio orders it. */\nexport function buildCommentsQuery(documentVersionId?: string): string {\n return `*[${buildCommentsFilter(documentVersionId)}] ${QUERY_PROJECTION} | order(_createdAt desc)`\n}\n\n/**\n * The listener query. Unprojected on purpose: mutation events carry the whole\n * comment, so there is nothing to trim.\n */\nexport function buildCommentsListenQuery(documentVersionId?: string): string {\n return `*[${buildCommentsFilter(documentVersionId)}]`\n}\n","import {type SanityClient} from '@sanity/client'\nimport {\n catchError,\n distinctUntilChanged,\n EMPTY,\n filter,\n firstValueFrom,\n map,\n type Observable,\n of,\n switchMap,\n take,\n} from 'rxjs'\n\nimport {getClient, getClientState} from '../client/clientStore'\nimport {\n type DatasetResource,\n type DocumentResource,\n isDatasetResource,\n} from '../config/sanityConfig'\nimport {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'\nimport {type SanityInstance} from '../store/createSanityInstance'\nimport {createStateSourceAction, type StateSource} from '../store/createStateSourceAction'\nimport {defineStore, type StoreContext} from '../store/defineStore'\nimport {\n ADDON_DATASET_API_VERSION,\n ADDON_DATASET_PROFILE,\n COMMENTS_API_VERSION,\n} from './commentsConstants'\n\ninterface AddonDatasetStoreState {\n /**\n * `unknown` until discovery answers. `missing` means the project has no\n * comments dataset yet, which is the normal state until someone writes a\n * first comment.\n */\n status: 'unknown' | 'resolved' | 'missing'\n datasetName?: string\n /** In flight provisioning, so concurrent first comments issue one POST. */\n provisioning?: Promise<string>\n}\n\n/**\n * Comments need a project and a dataset. There is nowhere to put them for a\n * media library or a canvas, and no Studio to interoperate with either.\n *\n * @internal\n */\nexport function assertDatasetResource(resource: DocumentResource): DatasetResource {\n if (!isDatasetResource(resource)) {\n throw new Error(\n `Comments are only supported for dataset resources, received: ${JSON.stringify(resource)}`,\n )\n }\n return resource\n}\n\nfunction isMissingOrForbidden(error: unknown): boolean {\n if (typeof error !== 'object' || error === null || !('statusCode' in error)) return false\n const {statusCode} = error as {statusCode?: unknown}\n return statusCode === 403 || statusCode === 404\n}\n\nfunction requestAddonDataset(\n client: SanityClient,\n {projectId, dataset}: DatasetResource,\n): Observable<string | undefined> {\n return client.observable\n .request<{name: string}[] | undefined>({\n uri: `/projects/${projectId}/datasets?datasetProfile=${ADDON_DATASET_PROFILE}&addonFor=${dataset}`,\n tag: 'comments.addon-dataset.list',\n })\n .pipe(map((datasets) => datasets?.[0]?.name))\n}\n\nfunction discover(\n instance: SanityInstance,\n resource: DatasetResource,\n): Observable<string | undefined> {\n return getClientState(instance, {\n apiVersion: ADDON_DATASET_API_VERSION,\n projectId: resource.projectId,\n useProjectHostname: true,\n }).observable.pipe(\n switchMap((client) =>\n requestAddonDataset(client, resource).pipe(\n // A missing dataset and a dataset this user cannot see both mean there\n // are no comments to read. Transient failures stay pending so a new\n // authenticated client can retry discovery.\n catchError((error: unknown) => (isMissingOrForbidden(error) ? of(undefined) : EMPTY)),\n ),\n ),\n )\n}\n\nasync function discoverOnce(\n instance: SanityInstance,\n resource: DatasetResource,\n): Promise<string | undefined> {\n const client = await firstValueFrom(\n getClientState(instance, {\n apiVersion: ADDON_DATASET_API_VERSION,\n projectId: resource.projectId,\n useProjectHostname: true,\n }).observable.pipe(take(1)),\n )\n\n try {\n return await firstValueFrom(requestAddonDataset(client, resource))\n } catch (error) {\n if (isMissingOrForbidden(error)) return undefined\n throw error\n }\n}\n\nconst addonDatasetStore = defineStore<AddonDatasetStoreState, BoundResourceKey>({\n name: 'CommentsAddonDataset',\n getInitialState: () => ({status: 'unknown'}),\n initialize: ({instance, state, key}) => {\n const resource = assertDatasetResource(key.resource)\n\n const subscription = discover(instance, resource).subscribe((datasetName) =>\n state.set(\n 'setAddonDataset',\n datasetName ? {status: 'resolved', datasetName} : {status: 'missing'},\n ),\n )\n\n return () => subscription.unsubscribe()\n },\n})\n\n/**\n * The addon dataset's name: `undefined` while discovery is in flight, `null`\n * when the project has none yet.\n *\n * @internal\n */\nexport const getAddonDatasetState: (\n instance: SanityInstance,\n options: {resource?: DocumentResource},\n) => StateSource<string | null | undefined> = bindActionByResource(\n addonDatasetStore,\n createStateSourceAction(({state}: {state: AddonDatasetStoreState}) => {\n if (state.status === 'unknown') return undefined\n return state.datasetName ?? null\n }),\n)\n\n/**\n * Resolves the addon dataset, creating it if the project has none.\n *\n * Safe to call concurrently: the first call owns the request and later ones\n * await the same promise. Provisioning is a one-time, project-wide side effect,\n * so this is only called from the write path, when we know there is a comment\n * to store.\n *\n * @internal\n */\nexport const provisionAddonDataset: (\n instance: SanityInstance,\n options: {resource?: DocumentResource},\n) => Promise<string> = bindActionByResource(\n addonDatasetStore,\n ({instance, state, key}: StoreContext<AddonDatasetStoreState, BoundResourceKey>) => {\n const current = state.get()\n if (current.datasetName) return Promise.resolve(current.datasetName)\n if (current.provisioning) return current.provisioning\n\n const resource = assertDatasetResource(key.resource)\n\n const provisioning = (async () => {\n // Another user may have provisioned it since this client started up, in\n // which case creating it again would fail. Ask before writing.\n const existing = await discoverOnce(instance, resource)\n if (existing) return existing\n\n const client = getClient(instance, {\n apiVersion: ADDON_DATASET_API_VERSION,\n projectId: resource.projectId,\n useProjectHostname: true,\n })\n\n const response = await client.request<{datasetName?: string} | undefined>({\n uri: `/comments/${resource.dataset}/setup`,\n method: 'POST',\n tag: 'comments.addon-dataset.setup',\n })\n\n if (!response?.datasetName) {\n throw new Error('Creating the comments addon dataset returned no dataset name.')\n }\n\n return response.datasetName\n })()\n .then((datasetName) => {\n state.set('setAddonDataset', {\n status: 'resolved',\n datasetName,\n provisioning: undefined,\n })\n return datasetName\n })\n .catch((error: unknown) => {\n // Clear the in-flight promise so a retry is not permanently stuck on\n // this failure.\n state.set('clearProvisioning', {provisioning: undefined})\n throw error\n })\n\n state.set('startProvisioning', {provisioning})\n\n return provisioning\n },\n)\n\n/**\n * A client pointed at the addon dataset, or `null` while the project has none.\n *\n * Emits again whenever the dataset is discovered or provisioned, and whenever\n * the auth token changes. Long-lived readers must follow it rather than holding\n * a client, because the client store drops every cached client on a token\n * change.\n *\n * @internal\n */\nexport const observeAddonDatasetClient: (\n instance: SanityInstance,\n options: {resource?: DocumentResource},\n) => Observable<SanityClient | null> = bindActionByResource(\n addonDatasetStore,\n ({instance, key}: StoreContext<AddonDatasetStoreState, BoundResourceKey>) => {\n const {projectId} = assertDatasetResource(key.resource)\n\n return getAddonDatasetState(instance, {resource: key.resource}).observable.pipe(\n distinctUntilChanged(),\n filter((datasetName): datasetName is string | null => datasetName !== undefined),\n switchMap((datasetName) =>\n typeof datasetName === 'string'\n ? getClientState(instance, {\n apiVersion: COMMENTS_API_VERSION,\n projectId,\n dataset: datasetName,\n }).observable\n : of(null),\n ),\n )\n },\n)\n","import {stringifyPath} from '@sanity/json-match'\nimport {type Path} from '@sanity/types'\n\n/** A dotted segment GROQ would read as a literal rather than a field name. */\nconst RESERVED_SEGMENT = /\\.(true|false|null)(?=$|[.[])/g\n\n/**\n * One quoted string, or one run of text containing no quote at all. Splitting on\n * this is what keeps the rewrite below out of `[_key==\"...\"]` values.\n */\nconst QUOTED_OR_PLAIN = /\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|[^\"']+/g\n\nfunction quoteReservedFieldSegments(path: string): string {\n return path.replace(QUOTED_OR_PLAIN, (chunk) =>\n chunk.startsWith('\"') || chunk.startsWith(\"'\")\n ? chunk\n : chunk.replace(RESERVED_SEGMENT, '[\"$1\"]'),\n )\n}\n\n/**\n * Normalises a field path to the string form comments are stored in.\n *\n * Comments keep `target.path.field` as a string rather than a path array,\n * because that is what the Studio writes and reading it back has to match\n * exactly. A string given here is parsed and re-stringified, so both a path\n * array and a hand-written string come out in the same canonical form.\n *\n * `undefined` and `[]` both produce `''`. Callers writing a comment reject that\n * rather than passing it on, since a comment has to point at a field.\n *\n * @example\n * ```ts\n * toCommentFieldPath(['body', {_key: 'intro'}, 'content'])\n * // 'body[_key==\"intro\"].content'\n * toCommentFieldPath(undefined)\n * // ''\n * ```\n *\n * @internal\n */\nexport function toCommentFieldPath(path?: string | Path): string {\n return quoteReservedFieldSegments(stringifyPath(path))\n}\n","import {type Comment, type CommentThread} from './types'\n\n/**\n * Groups a flat comment list into threads.\n *\n * A thread's parent is the comment with no `parentCommentId`; replies point at\n * the parent's `id`. Replies whose parent is missing from the input are\n * dropped, which is what happens when a parent is deleted but its replies have\n * not disappeared yet.\n *\n * Threads come back in the order their parents appear in `comments`, so passing\n * the store's newest-first list gives newest-thread-first. Replies within a\n * thread are always oldest first, since that is reading order.\n *\n * @internal\n */\nexport function buildCommentThreads(comments: Comment[]): CommentThread[] {\n const repliesByParent = new Map<string, Comment[]>()\n\n for (const comment of comments) {\n if (!comment.parentCommentId) continue\n const existing = repliesByParent.get(comment.parentCommentId)\n if (existing) {\n existing.push(comment)\n } else {\n repliesByParent.set(comment.parentCommentId, [comment])\n }\n }\n\n const threads: CommentThread[] = []\n\n for (const parentComment of comments) {\n if (parentComment.parentCommentId) continue\n\n // The array is built above and owned here, so sorting in place is safe.\n const replies = (repliesByParent.get(parentComment.id) ?? []).sort((a, b) =>\n a.createdAt.localeCompare(b.createdAt),\n )\n\n const lastReply = replies[replies.length - 1]\n\n threads.push({\n threadId: parentComment.threadId,\n fieldPath: parentComment.fieldPath,\n parentComment,\n replies,\n commentsCount: replies.length + 1,\n status: parentComment.status,\n lastActivityAt: lastReply ? lastReply.createdAt : parentComment.createdAt,\n })\n }\n\n return threads\n}\n","import {type Comment, type StoredComment} from './types'\n\n/**\n * Turns a stored comment into the shape consumers see.\n *\n * This is the whole seam between the storage format and the public API. When\n * comments move to the organization-level store, the three fields that differ\n * are handled here and nothing downstream changes:\n *\n * - the author moves from `authorId` to `_system.createdBy`\n * - `target.document` becomes a global document reference, so `documentId` is\n * parsed out of a `resourceType:resourceId:documentId` string rather than\n * read off `_ref`\n * - `_type` goes from `comment` to `sanity.comment`, and stops being exposed\n *\n * @internal\n */\nexport function normalizeComment(stored: StoredComment): Comment {\n const {target} = stored\n\n return {\n id: stored._id,\n createdAt: stored._createdAt,\n // Omitted rather than emptied when absent, so a consumer can tell \"no\n // author recorded\" from a user whose id happens to be falsy.\n ...(stored.authorId ? {authorId: stored.authorId} : {}),\n message: stored.message,\n threadId: stored.threadId,\n ...(stored.parentCommentId ? {parentCommentId: stored.parentCommentId} : {}),\n status: stored.status,\n ...(stored.lastEditedAt ? {lastEditedAt: stored.lastEditedAt} : {}),\n documentId: target.document._ref,\n documentType: target.documentType,\n fieldPath: target.path?.field ?? '',\n ...(target.path?.selection ? {selection: target.path.selection} : {}),\n ...(stored.contentSnapshot === undefined ? {} : {contentSnapshot: stored.contentSnapshot}),\n // Dropped: the stored `_key`, which only exists to address the array item.\n reactions: (stored.reactions ?? []).map(({shortName, userId, addedAt}) => ({\n shortName,\n userId,\n addedAt,\n })),\n ...(stored._state ? {state: stored._state} : {}),\n }\n}\n","import {type ListenEvent, type SanityClient} from '@sanity/client'\nimport {filter, map, Observable, share, switchMap, take} from 'rxjs'\n\nimport {buildCommentsListenQuery, buildCommentsQuery, LISTEN_OPTIONS} from './commentsConstants'\nimport {type StoredComment} from './types'\n\n/** What the listener saw, in terms the store can apply. */\nexport type CommentsEvent =\n | {type: 'snapshot'; comments: StoredComment[]}\n | {type: 'appear'; comment: StoredComment}\n | {type: 'disappear'; commentId: string}\n | {type: 'update'; comment: StoredComment; transactionId: string}\n | {type: 'error'; error: unknown}\n\nfunction toCommentsEvent(event: ListenEvent<StoredComment>): CommentsEvent | undefined {\n if (event.type !== 'mutation') return undefined\n\n if (event.transition === 'disappear') {\n return {type: 'disappear', commentId: event.documentId}\n }\n\n // `includeResult` is on, so a create or update carries the whole comment. An\n // event without one is nothing we can apply.\n if (!event.result) return undefined\n\n if (event.transition === 'appear') {\n return {type: 'appear', comment: event.result}\n }\n\n return {type: 'update', comment: event.result, transactionId: event.transactionId}\n}\n\n/**\n * Watches one document's comments: a snapshot on connect, then live changes.\n *\n * The Studio awaits its snapshot inside the event handler, which loses any\n * mutation that arrives while the fetch is in flight, because the older\n * snapshot overwrites it. Here the mutations that arrive during a fetch are\n * held and replayed straight after the snapshot, so nothing is dropped.\n *\n * A reconnect produces a fresh `welcome`, which cancels any fetch still running\n * and starts a new one. Reconnects emit nothing themselves: dropping the list\n * while reconnecting would only make readers suspend again for data we still\n * have.\n *\n * @internal\n */\nexport function observeComments(options: {\n client: SanityClient\n documentId: string\n documentVersionId?: string\n}): Observable<CommentsEvent> {\n const {client, documentId, documentVersionId} = options\n\n const params = {\n documentId,\n ...(documentVersionId ? {documentVersionId} : {}),\n }\n\n const events$ = client.observable\n .listen<StoredComment>(buildCommentsListenQuery(documentVersionId), params, LISTEN_OPTIONS)\n .pipe(share())\n\n const mutations$ = events$.pipe(\n map(toCommentsEvent),\n filter((event): event is CommentsEvent => event !== undefined),\n )\n\n return events$.pipe(\n filter((event) => event.type === 'welcome'),\n switchMap(\n () =>\n new Observable<CommentsEvent>((observer) => {\n // Subscribe before the fetch starts so nothing between the two is missed.\n // The array only exists while the snapshot is in flight. Once flushed,\n // live mutations pass through without retaining their history.\n const buffered: CommentsEvent[] = []\n let snapshotReceived = false\n const mutationSubscription = mutations$.subscribe({\n next: (event) => {\n if (snapshotReceived) observer.next(event)\n else buffered.push(event)\n },\n error: (error: unknown) => observer.error(error),\n })\n\n const snapshotSubscription = client.observable\n .fetch<StoredComment[]>(buildCommentsQuery(documentVersionId), params, {\n tag: 'comments.list',\n })\n .pipe(take(1))\n .subscribe({\n next: (comments) => {\n observer.next({type: 'snapshot', comments})\n snapshotReceived = true\n for (const event of buffered) observer.next(event)\n buffered.length = 0\n },\n error: (error: unknown) => {\n observer.next({type: 'error', error})\n observer.complete()\n },\n })\n\n return () => {\n mutationSubscription.unsubscribe()\n snapshotSubscription.unsubscribe()\n buffered.length = 0\n }\n }),\n ),\n )\n}\n","import {omitProperty} from '../utils/object'\nimport {type CommentPostPayload, type StoredComment} from './types'\n\ninterface CommentsEntry {\n /** `undefined` until the first snapshot arrives, which is what suspends readers. */\n comments?: Record<string, StoredComment>\n error?: unknown\n subscribers: string[]\n}\n\nexport interface CommentsStoreState {\n entries: {[key: string]: CommentsEntry | undefined}\n /** Comment creates that have not been confirmed by the server yet. */\n pendingCreates: {[commentId: string]: true | undefined}\n /**\n * The most recent transaction this client started per comment, held only\n * while the write is in flight.\n *\n * The listener echoes our own writes back. Without this, an echo from an\n * earlier transaction could arrive after a later one and undo it on screen.\n * Cleared as soon as the write settles: applying our own echo after that is\n * harmless, because it carries the same data we already show.\n */\n pendingTransactions: {[commentId: string]: string | undefined}\n error?: unknown\n}\n\nexport interface CommentsKeyParts {\n documentId: string\n documentVersionId?: string\n}\n\nexport function getCommentsKey({documentId, documentVersionId}: CommentsKeyParts): string {\n return JSON.stringify([documentId, documentVersionId ?? null])\n}\n\nexport function parseCommentsKey(key: string): CommentsKeyParts {\n const [documentId, documentVersionId] = JSON.parse(key) as [string, string | null]\n return {documentId, ...(documentVersionId ? {documentVersionId} : {})}\n}\n\nexport const addSubscriber =\n (key: string, subscriptionId: string) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entry = prev.entries[key]\n const subscribers = [...(entry?.subscribers ?? []), subscriptionId]\n return {...prev, entries: {...prev.entries, [key]: {...entry, subscribers}}}\n }\n\nexport const removeSubscriber =\n (key: string, subscriptionId: string) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entry = prev.entries[key]\n if (!entry) return prev\n const subscribers = entry.subscribers.filter((id) => id !== subscriptionId)\n if (!subscribers.length) {\n const pendingCreates = {...prev.pendingCreates}\n const pendingTransactions = {...prev.pendingTransactions}\n\n for (const commentId of Object.keys(entry.comments ?? {})) {\n delete pendingCreates[commentId]\n delete pendingTransactions[commentId]\n }\n\n return {\n ...prev,\n entries: omitProperty(prev.entries, key),\n pendingCreates,\n pendingTransactions,\n }\n }\n return {...prev, entries: {...prev.entries, [key]: {...entry, subscribers}}}\n }\n\n/** Replaces an entry's contents with a freshly fetched snapshot. */\nexport const setComments =\n (key: string, comments: StoredComment[]) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entry = prev.entries[key]\n if (!entry) return prev\n const byId = Object.fromEntries(comments.map((comment) => [comment._id, comment]))\n const pendingCreates = {...prev.pendingCreates}\n\n for (const [commentId, localComment] of Object.entries(entry.comments ?? {})) {\n if (Object.hasOwn(byId, commentId)) {\n delete pendingCreates[commentId]\n } else if (Object.hasOwn(pendingCreates, commentId) || localComment._state) {\n // A snapshot can race an in-flight create. Failed creates are also local\n // drafts and must remain available for retry.\n byId[commentId] = localComment\n }\n }\n\n return {\n ...prev,\n entries: {...prev.entries, [key]: {...entry, comments: byId, error: undefined}},\n pendingCreates,\n }\n }\n\nexport const setCommentsError =\n (key: string, error: unknown) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entry = prev.entries[key]\n if (!entry) return prev\n return {...prev, entries: {...prev.entries, [key]: {...entry, error}}}\n }\n\n/** A comment as it arrived from the server, replacing whatever we held. */\nexport const receiveComment =\n (key: string, comment: StoredComment) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entry = prev.entries[key]\n const pendingCreates = omitProperty(prev.pendingCreates, comment._id)\n if (!entry) return {...prev, pendingCreates}\n return {\n ...prev,\n pendingCreates,\n entries: {\n ...prev.entries,\n [key]: {...entry, comments: {...entry.comments, [comment._id]: comment}},\n },\n }\n }\n\n/**\n * A comment we just wrote, shown before the server confirms it.\n *\n * `_createdAt` is assigned by the server, but the list sorts on it, so an\n * optimistic comment needs a stand-in until the real value arrives.\n */\ntype OptimisticComment = CommentPostPayload & {_createdAt?: string; _rev?: string}\n\n/** A create that already failed once is being retried, not written fresh. */\nfunction isRetryingCreate(existing: StoredComment | undefined): boolean {\n const state = existing?._state?.type\n return state === 'createError' || state === 'createRetrying'\n}\n\nfunction mergeOptimisticComment(\n existing: StoredComment | undefined,\n comment: OptimisticComment,\n): StoredComment {\n return {\n ...existing,\n ...comment,\n _createdAt: comment._createdAt ?? existing?._createdAt ?? new Date().toISOString(),\n _rev: comment._rev ?? existing?._rev ?? '',\n ...(isRetryingCreate(existing) ? {_state: {type: 'createRetrying'} as const} : {}),\n }\n}\n\nexport const addComment =\n (key: string, comment: OptimisticComment) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entry = prev.entries[key]\n if (!entry) return prev\n\n const existing =\n entry.comments && Object.hasOwn(entry.comments, comment._id)\n ? entry.comments[comment._id]\n : undefined\n\n return {\n ...prev,\n pendingCreates: {...prev.pendingCreates, [comment._id]: true},\n entries: {\n ...prev.entries,\n [key]: {\n ...entry,\n comments: {\n ...entry.comments,\n [comment._id]: mergeOptimisticComment(existing, comment),\n },\n },\n },\n }\n }\n\n/**\n * Merges a partial update into whichever entry holds the comment.\n *\n * Comment ids are unique, so this touches one entry in practice. Searching by\n * id rather than taking a key means callers editing a comment do not have to\n * say which document it belongs to.\n */\nexport const applyCommentUpdate =\n (commentId: string, patch: Partial<StoredComment>) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entries = {...prev.entries}\n let changed = false\n\n for (const [key, entry] of Object.entries(prev.entries)) {\n const comment = entry?.comments?.[commentId]\n if (!entry || !comment) continue\n changed = true\n entries[key] = {\n ...entry,\n comments: {...entry.comments, [commentId]: {...comment, ...patch}},\n }\n }\n\n return changed ? {...prev, entries} : prev\n }\n\n/** Removes a comment and, when it is a thread parent, its replies. */\nexport const removeCommentById =\n (commentId: string) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entries = {...prev.entries}\n const removedIds = new Set<string>()\n let changed = false\n\n for (const [key, entry] of Object.entries(prev.entries)) {\n if (!entry?.comments) continue\n\n const remaining = Object.fromEntries(\n Object.entries(entry.comments).filter(([id, comment]) => {\n const keep = id !== commentId && comment.parentCommentId !== commentId\n if (!keep) removedIds.add(id)\n return keep\n }),\n )\n\n if (Object.keys(remaining).length === Object.keys(entry.comments).length) continue\n\n changed = true\n entries[key] = {...entry, comments: remaining}\n }\n\n if (!changed) return prev\n\n const pendingCreates = {...prev.pendingCreates}\n const pendingTransactions = {...prev.pendingTransactions}\n for (const id of removedIds) {\n delete pendingCreates[id]\n delete pendingTransactions[id]\n }\n\n return {...prev, entries, pendingCreates, pendingTransactions}\n }\n\nexport const setPendingTransaction =\n (commentId: string, transactionId: string) =>\n (prev: CommentsStoreState): CommentsStoreState => ({\n ...prev,\n pendingTransactions: {...prev.pendingTransactions, [commentId]: transactionId},\n })\n\nexport const clearPendingTransaction =\n (commentId: string) =>\n (prev: CommentsStoreState): CommentsStoreState => ({\n ...prev,\n pendingTransactions: omitProperty(prev.pendingTransactions, commentId),\n })\n\n/** Marks a pending create as failed, unless another request already confirmed it. */\nexport const setCommentCreateError =\n (commentId: string, error: Error) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n if (!Object.hasOwn(prev.pendingCreates, commentId)) return prev\n const next = applyCommentUpdate(commentId, {_state: {type: 'createError', error}})(prev)\n return {...next, pendingCreates: omitProperty(next.pendingCreates, commentId)}\n }\n\n/** Restores an optimistic edit if the failed transaction is still current. */\nexport const rollbackCommentUpdate =\n (commentId: string, transactionId: string, previous: StoredComment) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n if (\n !Object.hasOwn(prev.pendingTransactions, commentId) ||\n prev.pendingTransactions[commentId] !== transactionId\n ) {\n return prev\n }\n\n const entries = Object.fromEntries(\n Object.entries(prev.entries).map(([key, entry]) => {\n if (!entry?.comments?.[commentId]) return [key, entry]\n return [key, {...entry, comments: {...entry.comments, [commentId]: previous}}]\n }),\n )\n\n return {\n ...prev,\n entries,\n pendingTransactions: omitProperty(prev.pendingTransactions, commentId),\n }\n }\n\n/** Restores comments removed optimistically when the server delete fails. */\nexport const restoreComments =\n (removed: Array<{key: string; comments: StoredComment[]}>) =>\n (prev: CommentsStoreState): CommentsStoreState => {\n const entries = {...prev.entries}\n let changed = false\n\n for (const {key, comments} of removed) {\n const entry = entries[key]\n if (!entry) continue\n const missing = comments.filter(\n (comment) => !Object.hasOwn(entry.comments ?? {}, comment._id),\n )\n if (!missing.length) continue\n changed = true\n entries[key] = {\n ...entry,\n comments: Object.fromEntries([\n ...Object.entries(entry.comments ?? {}),\n ...missing.map((comment) => [comment._id, comment] as const),\n ]),\n }\n }\n\n return changed ? {...prev, entries} : prev\n }\n","import {DocumentId, getPublishedId} from '@sanity/id-utils'\nimport {type Path} from '@sanity/types'\nimport {\n catchError,\n distinctUntilChanged,\n EMPTY,\n first,\n firstValueFrom,\n groupBy,\n map,\n mergeMap,\n NEVER,\n Observable,\n pairwise,\n race,\n startWith,\n switchMap,\n tap,\n} from 'rxjs'\n\nimport {type DocumentHandle} from '../config/sanityConfig'\nimport {isReleasePerspective} from '../releases/utils/isReleasePerspective'\nimport {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'\nimport {type SanityInstance} from '../store/createSanityInstance'\nimport {\n createStateSourceAction,\n type SelectorContext,\n type StateSource,\n} from '../store/createStateSourceAction'\nimport {type StoreState} from '../store/createStoreState'\nimport {defineStore, type StoreContext} from '../store/defineStore'\nimport {insecureRandomId} from '../utils/ids'\nimport {setCleanupTimeout} from '../utils/setCleanupTimeout'\nimport {observeAddonDatasetClient} from './addonDatasetStore'\nimport {buildCommentThreads} from './buildCommentThreads'\nimport {toCommentFieldPath} from './commentFieldPath'\nimport {COMMENTS_STATE_CLEAR_DELAY} from './commentsConstants'\nimport {normalizeComment} from './normalizeComment'\nimport {type CommentsEvent, observeComments} from './observeComments'\nimport {\n addSubscriber,\n clearPendingTransaction,\n type CommentsKeyParts,\n type CommentsStoreState,\n getCommentsKey,\n parseCommentsKey,\n receiveComment,\n removeCommentById,\n removeSubscriber,\n setComments,\n setCommentsError,\n} from './reducers'\nimport {type Comment, type CommentStatus, type CommentThread, type StoredComment} from './types'\n\n/**\n * Which comments to read.\n * @beta\n */\nexport interface CommentsOptions extends DocumentHandle {\n /**\n * Narrow to one field. Omit to get every comment on the document.\n */\n fieldPath?: string | Path\n /** Narrow to open or resolved threads. Omit for both. */\n status?: CommentStatus\n}\n\n/** @beta */\nexport interface ResolveCommentsOptions extends CommentsOptions {\n signal?: AbortSignal\n}\n\n/**\n * Fields on {@link CommentsOptions} that are deliberately left out of the key\n * below, because neither changes which comments an option set addresses.\n *\n * `source` is the deprecated alias for `resource` and is folded into it before\n * a key is ever built, so keying on both would give one list two keys.\n * `liveEdit` describes the document rather than its comments, which hang off\n * the published id whether or not drafts exist.\n */\ntype CommentsKeyIrrelevantField = 'source' | 'liveEdit'\n\n/**\n * A stable string standing for one set of read options.\n *\n * Only React needs this: it holds one state source steady across renders and\n * defers swapping to a new one while the previous list is still on screen.\n * `fieldPath` is normalised on the way in, so a path array and the equivalent\n * string address the same list.\n *\n * @internal\n */\nexport function getCommentsOptionsKey(options: CommentsOptions): string {\n return JSON.stringify({\n documentId: options.documentId,\n documentType: options.documentType,\n projectId: options.projectId,\n dataset: options.dataset,\n resource: options.resource,\n perspective: options.perspective,\n fieldPath: options.fieldPath === undefined ? undefined : toCommentFieldPath(options.fieldPath),\n status: options.status,\n // The `Record` half makes a new field on `CommentsOptions` a compile\n // error here unless it is listed above or named as irrelevant. Left to\n // `satisfies CommentsOptions` alone, a forgotten field would just be\n // absent from the key, and a reader would keep the list it had while the\n // caller thought it had asked for a different one.\n } satisfies CommentsOptions &\n Record<Exclude<keyof CommentsOptions, CommentsKeyIrrelevantField>, unknown>)\n}\n\n/** @internal */\nexport function parseCommentsOptionsKey(key: string): CommentsOptions {\n return JSON.parse(key) as CommentsOptions\n}\n\n/**\n * Which comment list an option set addresses.\n *\n * Comments hang off the published id, so a draft and its published document\n * share one list. A release keeps its own.\n */\nexport function toCommentsKeyParts(\n instance: SanityInstance,\n options: CommentsOptions,\n): CommentsKeyParts {\n const perspective = options.perspective ?? instance.config.perspective\n return {\n documentId: getPublishedId(DocumentId(options.documentId)),\n ...(isReleasePerspective(perspective) ? {documentVersionId: perspective.releaseName} : {}),\n }\n}\n\nfunction applyEvent(\n state: StoreState<CommentsStoreState>,\n key: string,\n event: CommentsEvent,\n): void {\n switch (event.type) {\n case 'snapshot':\n state.set('setComments', setComments(key, event.comments))\n return\n case 'appear':\n state.set('receiveComment', receiveComment(key, event.comment))\n return\n case 'disappear':\n state.set('removeComment', removeCommentById(event.commentId))\n return\n case 'error':\n state.set('setCommentsError', setCommentsError(key, event.error))\n return\n case 'update': {\n const pending = state.get().pendingTransactions[event.comment._id]\n\n // Our own writes come back through the listener. When a later transaction\n // is already in flight for this comment, an echo of an earlier one would\n // undo it on screen, so drop it and wait for the one we are expecting.\n if (pending && pending !== event.transactionId) return\n\n state.set('receiveComment', receiveComment(key, event.comment))\n if (pending) {\n state.set('clearPendingTransaction', clearPendingTransaction(event.comment._id))\n }\n }\n }\n}\n\nconst watchSubscribedDocuments = ({\n state,\n instance,\n key,\n}: StoreContext<CommentsStoreState, BoundResourceKey>) => {\n const client$ = observeAddonDatasetClient(instance, {resource: key.resource})\n\n return state.observable\n .pipe(\n map((current) => new Set(Object.keys(current.entries))),\n distinctUntilChanged(\n (a, b) => a.size === b.size && Array.from(b).every((entry) => a.has(entry)),\n ),\n startWith(new Set<string>()),\n pairwise(),\n mergeMap(([previous, current]) => [\n ...Array.from(current)\n .filter((entry) => !previous.has(entry))\n .map((entry) => ({key: entry, added: true})),\n ...Array.from(previous)\n .filter((entry) => !current.has(entry))\n .map((entry) => ({key: entry, added: false})),\n ]),\n groupBy((event) => event.key),\n mergeMap((group$) =>\n group$.pipe(\n switchMap((event) => {\n if (!event.added) return EMPTY\n\n const {documentId, documentVersionId} = parseCommentsKey(group$.key)\n\n return client$.pipe(\n switchMap((client) => {\n if (!client) {\n // A dataset that does not exist holds no comments. Settling on\n // an empty list matters: leaving it unset would suspend\n // readers forever on a project nobody has commented in.\n state.set('setComments', setComments(group$.key, []))\n return EMPTY\n }\n\n return observeComments({client, documentId, documentVersionId}).pipe(\n tap((commentsEvent) => applyEvent(state, group$.key, commentsEvent)),\n // Keep following the addon client after one listener fails.\n // A token refresh or reconnect can then supply a new client.\n catchError((error: unknown) => {\n state.set('setCommentsError', setCommentsError(group$.key, error))\n return EMPTY\n }),\n )\n }),\n )\n }),\n ),\n ),\n )\n .subscribe({error: (error: unknown) => state.set('setError', {error})})\n}\n\nexport const commentsStore = defineStore<CommentsStoreState, BoundResourceKey>({\n name: 'Comments',\n getInitialState: () => ({\n entries: {},\n pendingCreates: {},\n pendingTransactions: {},\n }),\n initialize: (context) => {\n const subscription = watchSubscribedDocuments(context)\n return () => subscription.unsubscribe()\n },\n})\n\n/**\n * Filtered lists and their threads, cached against the comment map they came\n * from.\n *\n * Selectors run on every store change, and a fresh array each time would make\n * `useSyncExternalStore` re-render whenever anything anywhere in the store\n * moved. Keying on the comment map means a change to some other document, or\n * to an unrelated part of the state, hands back the identical array. Entries\n * die with the map they belong to.\n */\nconst normalizedCache = new WeakMap<object, Comment[]>()\nconst filteredCache = new WeakMap<object, Map<string, Comment[]>>()\nconst threadCache = new WeakMap<object, Map<string, CommentThread[]>>()\n\n/**\n * The stored map turned into what consumers see, newest first.\n *\n * Cached alongside the filters below rather than done per read, so the\n * normalised objects keep their identity for as long as the stored map does.\n */\nfunction normalizeAll(commentsById: Record<string, StoredComment>): Comment[] {\n const cached = normalizedCache.get(commentsById)\n if (cached) return cached\n\n // Newest first, matching the query's order. The store keys comments by id, so\n // the order has to be reapplied here.\n const normalized = Object.values(commentsById)\n .map(normalizeComment)\n .sort((a, b) => b.createdAt.localeCompare(a.createdAt))\n\n normalizedCache.set(commentsById, normalized)\n return normalized\n}\n\nfunction filterComments(\n all: Comment[],\n fieldPath: string | undefined,\n status: CommentStatus | undefined,\n): Comment[] {\n let byFilter = filteredCache.get(all)\n if (!byFilter) {\n byFilter = new Map()\n filteredCache.set(all, byFilter)\n }\n\n // `\\0` stands in for \"no field filter\", which is not the same as `''`.\n const cacheKey = `${fieldPath ?? '\\0'}|${status ?? ''}`\n const cached = byFilter.get(cacheKey)\n if (cached) return cached\n\n const filtered = all.filter((comment) => {\n if (status && comment.status !== status) return false\n if (fieldPath === undefined) return true\n return comment.fieldPath === fieldPath\n })\n\n byFilter.set(cacheKey, filtered)\n return filtered\n}\n\nfunction selectComments(\n {state, instance}: SelectorContext<CommentsStoreState>,\n options: CommentsOptions,\n): Comment[] | undefined {\n if (state.error) throw state.error\n\n const entry = state.entries[getCommentsKey(toCommentsKeyParts(instance, options))]\n if (entry?.error) throw entry.error\n if (!entry?.comments) return undefined\n\n return filterComments(\n normalizeAll(entry.comments),\n options.fieldPath === undefined ? undefined : toCommentFieldPath(options.fieldPath),\n options.status,\n )\n}\n\nfunction selectCommentThreads(\n context: SelectorContext<CommentsStoreState>,\n options: CommentsOptions,\n): CommentThread[] | undefined {\n const comments = selectComments(context, {...options, fieldPath: undefined, status: undefined})\n if (comments === undefined) return undefined\n\n let byFilter = threadCache.get(comments)\n if (!byFilter) {\n byFilter = new Map()\n threadCache.set(comments, byFilter)\n }\n\n const fieldPath =\n options.fieldPath === undefined ? undefined : toCommentFieldPath(options.fieldPath)\n const cacheKey = `${fieldPath ?? '\\0'}|${options.status ?? ''}`\n const cached = byFilter.get(cacheKey)\n if (cached) return cached\n\n const threads = buildCommentThreads(comments).filter((thread) => {\n if (options.status && thread.parentComment.status !== options.status) return false\n if (fieldPath === undefined) return true\n return thread.fieldPath === fieldPath\n })\n byFilter.set(cacheKey, threads)\n return threads\n}\n\n/**\n * Every comment on a document, newest first.\n *\n * `undefined` until the first snapshot arrives. Replies are included; use\n * {@link getCommentThreadsState} to read them grouped.\n *\n * @beta\n */\nexport const getCommentsState: (\n instance: SanityInstance,\n options: CommentsOptions,\n) => StateSource<Comment[] | undefined> = bindActionByResource(\n commentsStore,\n createStateSourceAction({\n selector: selectComments,\n onSubscribe: ({state, instance}, options: CommentsOptions) => {\n const key = getCommentsKey(toCommentsKeyParts(instance, options))\n const subscriptionId = insecureRandomId()\n state.set('addSubscriber', addSubscriber(key, subscriptionId))\n\n return () => {\n setCleanupTimeout(\n () => state.set('removeSubscriber', removeSubscriber(key, subscriptionId)),\n COMMENTS_STATE_CLEAR_DELAY,\n )\n }\n },\n }),\n)\n\n/**\n * Threads on a document, newest thread first, each with its replies oldest first.\n *\n * A thread's `status` and `fieldPath` come from its first comment, so filtering\n * by either selects whole threads rather than individual replies.\n *\n * @beta\n */\nexport const getCommentThreadsState: (\n instance: SanityInstance,\n options: CommentsOptions,\n) => StateSource<CommentThread[] | undefined> = bindActionByResource(\n commentsStore,\n createStateSourceAction({\n selector: selectCommentThreads,\n onSubscribe: ({state, instance}, options: CommentsOptions) => {\n const key = getCommentsKey(toCommentsKeyParts(instance, options))\n const subscriptionId = insecureRandomId()\n state.set('addSubscriber', addSubscriber(key, subscriptionId))\n\n return () => {\n setCleanupTimeout(\n () => state.set('removeSubscriber', removeSubscriber(key, subscriptionId)),\n COMMENTS_STATE_CLEAR_DELAY,\n )\n }\n },\n }),\n)\n\n/**\n * Waits for a document's comments to load.\n *\n * Holds a subscriber only while resolving, so a component that suspends on this\n * and then errors before mounting does not strand the list. Throw the promise\n * for Suspense, then read through {@link getCommentsState}.\n *\n * @beta\n */\nexport const resolveComments: (\n instance: SanityInstance,\n options: ResolveCommentsOptions,\n) => Promise<Comment[]> = bindActionByResource(\n commentsStore,\n (\n {state, instance}: StoreContext<CommentsStoreState, BoundResourceKey>,\n {signal, ...options}: ResolveCommentsOptions,\n ) => resolveList(state, instance, options, signal, getCommentsState),\n)\n\n/**\n * Waits for a document's comments to load, grouped into threads.\n * @beta\n */\nexport const resolveCommentThreads: (\n instance: SanityInstance,\n options: ResolveCommentsOptions,\n) => Promise<CommentThread[]> = bindActionByResource(\n commentsStore,\n (\n {state, instance}: StoreContext<CommentsStoreState, BoundResourceKey>,\n {signal, ...options}: ResolveCommentsOptions,\n ) => resolveList(state, instance, options, signal, getCommentThreadsState),\n)\n\nfunction resolveList<T>(\n state: StoreState<CommentsStoreState>,\n instance: SanityInstance,\n options: CommentsOptions,\n signal: AbortSignal | undefined,\n getState: (i: SanityInstance, o: CommentsOptions) => StateSource<T | undefined>,\n): Promise<T> {\n const key = getCommentsKey(toCommentsKeyParts(instance, options))\n const {getCurrent} = getState(instance, options)\n\n // Loading is driven by subscribers, so without one here nothing would ever\n // fetch and this promise would never settle. Holding it only for the duration\n // of the resolve also means a component that suspends and then errors before\n // mounting does not leave a subscriber-less entry behind holding its error.\n const subscriptionId = insecureRandomId()\n state.set('addSubscriber', addSubscriber(key, subscriptionId))\n\n const release = () => state.set('removeSubscriber', removeSubscriber(key, subscriptionId))\n\n const aborted$ = signal\n ? new Observable<never>((observer) => {\n const listener = () => {\n // Release now rather than after the delay: when this was the only\n // reader, dropping the key tears down the listener immediately, which\n // is the point of aborting.\n release()\n observer.error(new DOMException('The operation was aborted.', 'AbortError'))\n }\n signal.addEventListener('abort', listener)\n return () => signal.removeEventListener('abort', listener)\n })\n : NEVER\n\n const resolved$ = state.observable.pipe(\n map(() => getCurrent()),\n first((value): value is T => value !== undefined),\n )\n\n const promise = firstValueFrom(race([resolved$, aborted$]))\n const releaseLater = () => setCleanupTimeout(release, COMMENTS_STATE_CLEAR_DELAY)\n promise.then(releaseLater, releaseLater)\n return promise\n}\n","// NOTE: currently this API is only available on vX\nexport const API_VERSION = 'vX'\nexport const PROJECT_API_VERSION = '2025-07-18'\nexport const USERS_STATE_CLEAR_DELAY = 5000\nexport const DEFAULT_USERS_BATCH_SIZE = 100\n","import {type SanityInstance} from '../store/createSanityInstance'\nimport {omitProperty} from '../utils/object'\nimport {type GetUsersOptions, type SanityUserResponse, type UsersStoreState} from './types'\nimport {DEFAULT_USERS_BATCH_SIZE} from './usersConstants'\n\n/** @internal */\nexport const getUsersKey = (\n instance: SanityInstance,\n {\n resourceType,\n organizationId,\n batchSize = DEFAULT_USERS_BATCH_SIZE,\n projectId = instance.config.projectId,\n userId,\n }: GetUsersOptions = {},\n): string =>\n JSON.stringify({\n resourceType,\n organizationId,\n batchSize,\n projectId,\n userId,\n } satisfies ReturnType<typeof parseUsersKey>)\n\n/** @internal */\nexport const parseUsersKey = (\n key: string,\n): {\n batchSize: number\n resourceType?: 'organization' | 'project'\n projectId?: string\n organizationId?: string\n userId?: string\n} => JSON.parse(key)\n\nexport const addSubscription =\n (subscriptionId: string, key: string) =>\n (prev: UsersStoreState): UsersStoreState => {\n const group = prev.users[key]\n const subscriptions = [...(group?.subscriptions ?? []), subscriptionId]\n return {...prev, users: {...prev.users, [key]: {...group, subscriptions}}}\n }\n\nexport const removeSubscription =\n (subscriptionId: string, key: string) =>\n (prev: UsersStoreState): UsersStoreState => {\n const group = prev.users[key]\n if (!group) return prev\n const subscriptions = group.subscriptions.filter((id) => id !== subscriptionId)\n if (!subscriptions.length) return {...prev, users: omitProperty(prev.users, key)}\n return {...prev, users: {...prev.users, [key]: {...group, subscriptions}}}\n }\n\nexport const setUsersData =\n (key: string, {data, nextCursor, totalCount}: SanityUserResponse) =>\n (prev: UsersStoreState): UsersStoreState => {\n const group = prev.users[key]\n if (!group) return prev\n const users = [...(group.users ?? []), ...data]\n return {...prev, users: {...prev.users, [key]: {...group, users, totalCount, nextCursor}}}\n }\n\nexport const updateLastLoadMoreRequest =\n (timestamp: string, key: string) =>\n (prev: UsersStoreState): UsersStoreState => {\n const group = prev.users[key]\n if (!group) return prev\n return {...prev, users: {...prev.users, [key]: {...group, lastLoadMoreRequest: timestamp}}}\n }\n\nexport const setUsersError =\n (key: string, error: unknown) =>\n (prev: UsersStoreState): UsersStoreState => {\n const group = prev.users[key]\n if (!group) return prev\n return {...prev, users: {...prev.users, [key]: {...group, error}}}\n }\n\nexport const cancelRequest =\n (key: string) =>\n (prev: UsersStoreState): UsersStoreState => {\n const group = prev.users[key]\n if (!group) return prev\n if (group.subscriptions.length) return prev\n return {...prev, users: omitProperty(prev.users, key)}\n }\n\nexport const initializeRequest =\n (key: string) =>\n (prev: UsersStoreState): UsersStoreState => {\n if (prev.users[key]) return prev\n return {...prev, users: {...prev.users, [key]: {subscriptions: []}}}\n }\n","import {\n ConnectionFailedError,\n CorsOriginError,\n DisconnectError,\n type LiveEventMessage,\n} from '@sanity/client'\nimport {catchError, defer, EMPTY, filter, type Observable, retry, switchMap} from 'rxjs'\n\nimport {type DocumentResource} from '../config/sanityConfig'\nimport {type SanityInstance} from '../store/createSanityInstance'\nimport {getClientState} from './clientStore'\n\n/**\n * All live-events subscriptions must use identical connection options\n * (API version, request tag, `includeDrafts`): `@sanity/client` caches the\n * underlying EventSource per URL + options, so divergent options would\n * silently split what should be a single shared connection.\n */\nconst LIVE_EVENTS_API_VERSION = 'v2025-05-06'\n\n/**\n * Delay before re-establishing the live events connection after a\n * server-initiated error (e.g. ChannelError, MessageError). Live errors are\n * retried rather than surfaced because an errored live connection would\n * otherwise poison the consuming store. Plain connection drops never reach\n * this — they are reconnected inside `@sanity/client`.\n *\n * @internal\n */\nexport const LIVE_EVENTS_RETRY_DELAY = 1000\n\n/**\n * Options for {@link observeLiveEvents}.\n * @internal\n */\nexport interface ObserveLiveEventsOptions {\n /** Resource to scope the underlying client and live connection to. */\n resource?: DocumentResource\n /**\n * Called when the connection fails due to a CORS misconfiguration. The\n * error is swallowed (the stream never errors; it idles until a new client\n * is emitted) so consumers can surface it as store state instead of a\n * stream error.\n */\n onCorsError: (error: unknown) => void\n}\n\n/**\n * Emits Live Content API messages for a dataset so consumers can invalidate\n * fetched data via sync tags.\n *\n * All consumers observe the same underlying EventSource connection:\n * `@sanity/client` caches the live-events stream per URL and connection\n * options, and every subscription made through this function uses the same\n * API version and request tag.\n *\n * @internal\n */\nexport function observeLiveEvents(\n instance: SanityInstance,\n {resource, onCorsError}: ObserveLiveEventsOptions,\n): Observable<LiveEventMessage> {\n return getClientState(instance, {\n apiVersion: LIVE_EVENTS_API_VERSION,\n resource,\n }).observable.pipe(\n switchMap((client) =>\n defer(() =>\n client.live.events({includeDrafts: !!client.config().token, tag: 'live-events'}),\n ).pipe(\n catchError((error) => {\n if (error instanceof CorsOriginError) {\n // Swallow CORS errors without bubbling up so that they can be\n // handled by the consumer (e.g. shown via the Cors Error component)\n onCorsError(error)\n return EMPTY\n }\n if (error instanceof DisconnectError) {\n // The server explicitly told this client to stop reconnecting —\n // end live updates without erroring (consumers keep serving data,\n // they just stop receiving live invalidation)\n return EMPTY\n }\n if (\n error instanceof ConnectionFailedError &&\n typeof error.status === 'number' &&\n error.status >= 400 &&\n error.status < 500\n ) {\n // The server rejected the connection with a 4xx (e.g. a 401 from\n // an expired token) — it will keep rejecting, so retrying would\n // reconnect once per second forever. End live updates like a\n // DisconnectError. A ConnectionFailedError without a status stays\n // retryable: it may be a transient network failure.\n return EMPTY\n }\n throw error\n }),\n // Server-initiated live errors (e.g. ChannelError, MessageError) are\n // retried, not surfaced. Dropped connections are already reconnected\n // inside @sanity/client.\n retry({delay: LIVE_EVENTS_RETRY_DELAY}),\n ),\n ),\n filter((e): e is LiveEventMessage => e.type === 'message'),\n )\n}\n","import {type ReleaseDocument, type SyncTag} from '@sanity/client'\nimport {\n BehaviorSubject,\n combineLatest,\n distinctUntilChanged,\n filter,\n map,\n type Observable,\n startWith,\n switchMap,\n tap,\n} from 'rxjs'\n\nimport {getClientState} from '../client/clientStore'\nimport {observeLiveEvents} from '../client/liveEvents'\nimport {type DocumentResource} from '../config/sanityConfig'\nimport {type SanityInstance} from '../store/createSanityInstance'\n\nconst RELEASES_QUERY = 'releases::all()'\nconst RELEASES_API_VERSION = 'v2025-05-06'\n\n/**\n * Options for {@link observeReleases}.\n * @internal\n */\nexport interface ObserveReleasesOptions {\n /** Resource to scope the underlying client and live connection to. */\n resource?: DocumentResource\n /**\n * Called when the live events connection fails due to a CORS\n * misconfiguration. See `observeLiveEvents`.\n */\n onCorsError: (error: unknown) => void\n}\n\n/**\n * Emits every release document (including archived and published), refetching\n * whenever a Live Content API event matches the query's sync tags.\n *\n * This fetches directly through the client store — deliberately not via the\n * query store — because release data is itself an input to query perspective\n * resolution (`getPerspectiveState`); fetching through the query store would\n * make the two stores mutually dependent.\n *\n * @internal\n */\nexport function observeReleases(\n instance: SanityInstance,\n {resource, onCorsError}: ObserveReleasesOptions,\n): Observable<ReleaseDocument[] | undefined> {\n const client$ = getClientState(instance, {\n apiVersion: RELEASES_API_VERSION,\n resource,\n }).observable\n\n return client$.pipe(\n switchMap((client) => {\n const syncTags$ = new BehaviorSubject<SyncTag[] | undefined>(undefined)\n\n // Pair the latest live message with the latest known sync tags so that\n // a message arriving while a fetch is still in flight is re-evaluated\n // (and refetched if it matches) once that fetch's tags land — instead\n // of being dropped and leaving the store stale until the next event.\n const refetchEventId$ = combineLatest([\n observeLiveEvents(instance, {resource, onCorsError}).pipe(startWith(undefined)),\n syncTags$,\n ]).pipe(\n filter(\n ([message, syncTags]) =>\n message === undefined || message.tags.some((tag) => syncTags?.includes(tag)),\n ),\n map(([message]) => message?.id),\n distinctUntilChanged(),\n )\n\n return refetchEventId$.pipe(\n switchMap((lastLiveEventId) =>\n client.observable.fetch<ReleaseDocument[]>(\n RELEASES_QUERY,\n {},\n {\n perspective: 'raw',\n filterResponse: false,\n returnQuery: false,\n lastLiveEventId,\n tag: 'releases',\n },\n ),\n ),\n tap((response) => {\n syncTags$.next(response.syncTags)\n }),\n map((response) => response.result),\n )\n }),\n // Emit synchronously on subscribe (mirroring StateSource semantics, which\n // the releases store previously consumed) so the store can immediately\n // record an empty list: consumers distinguish \"no releases yet\" from\n // \"never loaded\", and the React hooks don't suspend on first load.\n startWith(undefined),\n )\n}\n","import {type ReleaseDocument} from '@sanity/client'\n\n// mirrors the order of the releases in the releases list in Studio\n// https://github.com/sanity-io/sanity/blob/main/packages/sanity/src/core/releases/hooks/utils.ts\nexport function sortReleases(releases: ReleaseDocument[] = []): ReleaseDocument[] {\n // The order should always be:\n // [undecided (sortByCreatedAt), scheduled(sortBy publishAt || metadata.intendedPublishAt), asap(sortByCreatedAt)]\n return [...releases].sort((a, b) => {\n // metadata is optional on a release document; guard against releases that\n // have no metadata at all so sorting can't throw and wipe out the list.\n const aType = a.metadata?.releaseType\n const bType = b.metadata?.releaseType\n\n // undecided are always first, then by createdAt descending\n if (aType === 'undecided' && bType !== 'undecided') {\n return -1\n }\n if (aType !== 'undecided' && bType === 'undecided') {\n return 1\n }\n if (aType === 'undecided' && bType === 'undecided') {\n // Sort by createdAt\n return new Date(b._createdAt).getTime() - new Date(a._createdAt).getTime()\n }\n\n // Scheduled are always at the middle, then by publishAt descending\n if (aType === 'scheduled' && bType === 'scheduled') {\n const aPublishAt = a['publishAt'] || a.metadata?.['intendedPublishAt']\n if (!aPublishAt) {\n return 1\n }\n const bPublishAt = b['publishAt'] || b.metadata?.['intendedPublishAt']\n if (!bPublishAt) {\n return -1\n }\n return new Date(bPublishAt).getTime() - new Date(aPublishAt).getTime()\n }\n\n // ASAP are always last, then by createdAt descending\n if (aType === 'asap' && bType !== 'asap') {\n return 1\n }\n if (aType !== 'asap' && bType === 'asap') {\n return -1\n }\n if (aType === 'asap' && bType === 'asap') {\n // Sort by createdAt\n return new Date(b._createdAt).getTime() - new Date(a._createdAt).getTime()\n }\n\n return 0\n })\n}\n","import {type ReleaseDocument} from '@sanity/client'\nimport {map} from 'rxjs'\n\nimport {type DocumentResource} from '../config/sanityConfig'\nimport {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'\nimport {type SanityInstance} from '../store/createSanityInstance'\nimport {createStateSourceAction, type StateSource} from '../store/createStateSourceAction'\nimport {defineStore, type StoreContext} from '../store/defineStore'\nimport {observeReleases} from './observeReleases'\nimport {sortReleases} from './utils/sortReleases'\n\nconst ARCHIVED_RELEASE_STATES = ['archived', 'published']\nconst STABLE_EMPTY_RELEASES: ReleaseDocument[] = []\n\n/**\n * Lifecycle states a release document can be in. Mirrors the server's\n * `ReleaseState`.\n * @beta\n */\nexport type ReleaseState =\n | 'active'\n | 'archiving'\n | 'unarchiving'\n | 'archived'\n | 'published'\n | 'publishing'\n | 'scheduled'\n | 'scheduling'\n\nexport interface ReleasesStoreState {\n activeReleases?: ReleaseDocument[]\n allReleases?: ReleaseDocument[]\n error?: unknown\n}\n\nexport const releasesStore = defineStore<ReleasesStoreState, BoundResourceKey>({\n name: 'Releases',\n getInitialState: (): ReleasesStoreState => ({\n activeReleases: undefined,\n allReleases: undefined,\n }),\n initialize: (context) => {\n const subscription = subscribeToReleases(context)\n return () => subscription.unsubscribe()\n },\n})\n\n/**\n * Get the active releases from the store.\n * @internal\n */\nconst _getActiveReleasesState = bindActionByResource(\n releasesStore,\n createStateSourceAction({\n selector: ({state}) => {\n if (state.error) throw state.error\n return state.activeReleases\n },\n }),\n)\n\n/**\n * Get the active releases from the store.\n * @internal\n */\nexport const getActiveReleasesState = (\n instance: SanityInstance,\n options?: {resource?: DocumentResource},\n): StateSource<ReleaseDocument[] | undefined> =>\n // bindActionByResource keyFn destructures { resource } from the first param, so pass {} when no options\n _getActiveReleasesState(instance, options ?? {})\n\n/**\n * Get every release in the store, including archived and published.\n * @internal\n */\nconst _getAllReleasesState = bindActionByResource(\n releasesStore,\n createStateSourceAction({\n selector: ({state}) => {\n if (state.error) throw state.error\n return state.allReleases\n },\n }),\n)\n\n/**\n * Get every release in the store, including archived and published.\n * @internal\n */\nexport const getAllReleasesState = (\n instance: SanityInstance,\n options?: {resource?: DocumentResource},\n): StateSource<ReleaseDocument[] | undefined> => _getAllReleasesState(instance, options ?? {})\n\nconst subscribeToReleases = ({\n instance,\n state,\n key: {resource},\n}: StoreContext<ReleasesStoreState, BoundResourceKey>) => {\n const releases$ = observeReleases(instance, {\n resource,\n // Surface CORS errors as store state instead of erroring the stream so\n // they can be handled by the Cors Error component\n onCorsError: (error) => state.set('setError', {error}),\n })\n return (\n releases$\n .pipe(\n map((releases) => {\n // logic here mirrors that of studio:\n // https://github.com/sanity-io/sanity/blob/156e8fa482703d99219f08da7bacb384517f1513/packages/sanity/src/core/releases/store/useActiveReleases.ts#L29\n const sorted = sortReleases(releases ?? STABLE_EMPTY_RELEASES).reverse()\n state.set('setReleases', {\n allReleases: sorted,\n activeReleases: sorted.filter(\n (release) => !ARCHIVED_RELEASE_STATES.includes(release.state),\n ),\n })\n }),\n )\n // live-connection errors are already retried inside observeLiveEvents;\n // a fetch error is terminal and surfaced via setError\n .subscribe({error: (error) => state.set('setError', {error})})\n )\n}\n","import {createSelector} from 'reselect'\n\nimport {type DocumentResource, type PerspectiveHandle} from '../config/sanityConfig'\nimport {bindActionByResource, type BoundStoreAction} from '../store/createActionBinder'\nimport {createStateSourceAction, type SelectorContext} from '../store/createStateSourceAction'\nimport {releasesStore, type ReleasesStoreState} from './releasesStore'\nimport {isReleasePerspective} from './utils/isReleasePerspective'\nimport {sortReleases} from './utils/sortReleases'\n\nconst DEFAULT_PERSPECTIVE = 'drafts'\n\n// Cache for options\nconst optionsCache = new Map<string, Map<string, PerspectiveHandle>>()\n\nconst selectInstancePerspective = (context: SelectorContext<ReleasesStoreState>, _?: unknown) =>\n context.instance.config.perspective\nconst selectActiveReleases = (context: SelectorContext<ReleasesStoreState>) =>\n context.state.activeReleases\nconst selectOptions = (\n _context: SelectorContext<ReleasesStoreState>,\n options: PerspectiveHandle & {projectId?: string; dataset?: string; resource?: DocumentResource},\n) => options\n\nconst memoizedOptionsSelector = createSelector(\n [selectActiveReleases, selectOptions],\n (activeReleases, options) => {\n if (!options || !activeReleases) return options\n\n // Use release document IDs as the cache key\n const releaseIds = activeReleases.map((release) => release._id).join(',')\n let nestedCache = optionsCache.get(releaseIds)\n if (!nestedCache) {\n nestedCache = new Map<string, PerspectiveHandle>()\n optionsCache.set(releaseIds, nestedCache)\n }\n\n const optionsKey = JSON.stringify(options)\n let cachedOptions = nestedCache.get(optionsKey)\n\n if (!cachedOptions) {\n cachedOptions = options\n nestedCache.set(optionsKey, cachedOptions)\n }\n return cachedOptions\n },\n)\n\nconst _getPerspectiveStateSelector = createStateSourceAction({\n selector: createSelector(\n [selectInstancePerspective, selectActiveReleases, memoizedOptionsSelector],\n (instancePerspective, activeReleases, memoizedOptions) => {\n const perspective = memoizedOptions?.perspective ?? instancePerspective ?? DEFAULT_PERSPECTIVE\n\n if (!isReleasePerspective(perspective)) return perspective\n\n // if there are no active releases we can't compute the release perspective\n if (!activeReleases || activeReleases.length === 0) return undefined\n\n const releaseNames = sortReleases(activeReleases).map((release) => release.name)\n const index = releaseNames.findIndex((name) => name === perspective.releaseName)\n\n if (index < 0) {\n throw new Error(`Release \"${perspective.releaseName}\" not found in active releases`)\n }\n\n const filteredReleases = releaseNames.slice(0, index + 1) // Include the release itself\n\n return ['drafts', ...filteredReleases]\n .filter((name) => !perspective.excludedPerspectives?.includes(name))\n .reverse()\n },\n ),\n})\n\ntype OmitFirst<T extends unknown[]> = T extends [unknown, ...infer R] ? R : never\ntype SelectorParams = OmitFirst<Parameters<typeof _getPerspectiveStateSelector>>\ntype BoundGetPerspectiveState = BoundStoreAction<\n ReleasesStoreState,\n SelectorParams,\n ReturnType<typeof _getPerspectiveStateSelector>\n>\n\nconst _boundGetPerspectiveState = bindActionByResource(\n releasesStore,\n _getPerspectiveStateSelector,\n) as BoundGetPerspectiveState\n\n/**\n * Provides a subscribable state source for a \"perspective\" for the Sanity client,\n * which is used to fetch documents as though certain Content Releases are active.\n *\n * @param instance - The Sanity instance to get the perspective for\n * @param options - The options for the perspective -- usually a release name\n *\n * @returns A subscribable perspective value, usually a list of applicable release names,\n * or a single release name / default perspective (such as 'drafts').\n *\n * @public\n */\nexport const getPerspectiveState: BoundGetPerspectiveState = (instance, ...rest) =>\n // bindActionByResource keyFn destructures { resource } from the first param, so pass {} when no options\n _boundGetPerspectiveState(instance, ...(rest.length ? rest : [{}]))\n","/**\n * When a query has no more subscribers, its state is cleaned up and removed\n * from the store. A delay used to prevent re-creating resources when the last\n * subscriber is removed quickly before another one is added. This is helpful\n * when used in a frontend where components may suspend or transition to\n * different views quickly.\n */\nexport const QUERY_STATE_CLEAR_DELAY = 1000\nexport const QUERY_STORE_API_VERSION = 'v2025-05-06'\nexport const QUERY_STORE_DEFAULT_PERSPECTIVE = 'drafts'\n","import {omitProperty} from '../utils/object'\n\ninterface QueryState {\n syncTags?: string[]\n result?: unknown\n error?: unknown\n lastLiveEventId?: string\n subscribers: string[]\n}\n\nexport interface QueryStoreState {\n queries: {[key: string]: QueryState | undefined}\n error?: unknown\n}\n\nexport const setQueryError =\n (key: string, error: unknown) =>\n (prev: QueryStoreState): QueryStoreState => {\n const prevQuery = prev.queries[key]\n if (!prevQuery) return prev\n return {...prev, queries: {...prev.queries, [key]: {...prevQuery, error}}}\n }\n\nexport const setQueryData =\n (key: string, result: unknown, syncTags?: string[]) =>\n (prev: QueryStoreState): QueryStoreState => {\n const prevQuery = prev.queries[key]\n if (!prevQuery) return prev\n return {\n ...prev,\n queries: {...prev.queries, [key]: {...prevQuery, result: result ?? null, syncTags}},\n }\n }\n\nexport const setLastLiveEventId =\n (key: string, lastLiveEventId: string) =>\n (prev: QueryStoreState): QueryStoreState => {\n const prevQuery = prev.queries[key]\n if (!prevQuery) return prev\n return {...prev, queries: {...prev.queries, [key]: {...prevQuery, lastLiveEventId}}}\n }\n\nexport const addSubscriber =\n (key: string, subscriptionId: string) =>\n (prev: QueryStoreState): QueryStoreState => {\n const prevQuery = prev.queries[key]\n const subscribers = [...(prevQuery?.subscribers ?? []), subscriptionId]\n return {...prev, queries: {...prev.queries, [key]: {...prevQuery, subscribers}}}\n }\n\nexport const removeSubscriber =\n (key: string, subscriptionId: string) =>\n (prev: QueryStoreState): QueryStoreState => {\n const prevQuery = prev.queries[key]\n if (!prevQuery) return prev\n const subscribers = prevQuery.subscribers.filter((id) => id !== subscriptionId)\n if (!subscribers.length) return {...prev, queries: omitProperty(prev.queries, key)}\n return {...prev, queries: {...prev.queries, [key]: {...prevQuery, subscribers}}}\n }\n","import {type ResponseQueryOptions} from '@sanity/client'\nimport {type SanityQueryResult} from 'groq'\nimport {\n catchError,\n combineLatest,\n distinctUntilChanged,\n EMPTY,\n filter,\n first,\n firstValueFrom,\n groupBy,\n map,\n mergeMap,\n NEVER,\n Observable,\n of,\n pairwise,\n race,\n share,\n startWith,\n switchMap,\n tap,\n} from 'rxjs'\n\nimport {getClientState} from '../client/clientStore'\nimport {observeLiveEvents} from '../client/liveEvents'\nimport {type DatasetHandle} from '../config/sanityConfig'\nimport {getPerspectiveState} from '../releases/getPerspectiveState'\nimport {isReleasePerspective} from '../releases/utils/isReleasePerspective'\nimport {bindActionByResource, type BoundResourceKey} from '../store/createActionBinder'\nimport {type SanityInstance} from '../store/createSanityInstance'\nimport {\n createStateSourceAction,\n type SelectorContext,\n type StateSource,\n} from '../store/createStateSourceAction'\nimport {type StoreState} from '../store/createStoreState'\nimport {defineStore, type StoreContext} from '../store/defineStore'\nimport {insecureRandomId} from '../utils/ids'\nimport {setCleanupTimeout} from '../utils/setCleanupTimeout'\nimport {\n QUERY_STATE_CLEAR_DELAY,\n QUERY_STORE_API_VERSION,\n QUERY_STORE_DEFAULT_PERSPECTIVE,\n} from './queryStoreConstants'\nimport {\n addSubscriber,\n type QueryStoreState,\n removeSubscriber,\n setLastLiveEventId,\n setQueryData,\n setQueryError,\n} from './reducers'\n\n/**\n * @beta\n */\nexport interface QueryOptions<\n TQuery extends string = string,\n TDataset extends string = string,\n TProjectId extends string = string,\n>\n extends\n Pick<ResponseQueryOptions, 'useCdn' | 'cache' | 'next' | 'cacheMode' | 'tag'>,\n DatasetHandle<TDataset, TProjectId> {\n query: TQuery\n params?: Record<string, unknown>\n}\n\n/**\n * @beta\n */\nexport interface ResolveQueryOptions<\n TQuery extends string = string,\n TDataset extends string = string,\n TProjectId extends string = string,\n> extends QueryOptions<TQuery, TDataset, TProjectId> {\n signal?: AbortSignal\n}\n\nconst EMPTY_ARRAY: never[] = []\n\n/** @beta */\nexport const getQueryKey = (options: QueryOptions): string => JSON.stringify(options)\n/** @beta */\nexport const parseQueryKey = (key: string): QueryOptions => JSON.parse(key)\n\n/**\n * Ensures the query key includes an effective perspective so that\n * implicit differences (e.g. different instance.config.perspective)\n * don't collide in the dataset-scoped store.\n *\n * Since perspectives are unique, we can depend on the release stacks\n * to be correct when we retrieve the results.\n *\n */\nfunction normalizeOptionsWithPerspective(\n instance: SanityInstance,\n options: QueryOptions,\n): QueryOptions {\n if (options.perspective !== undefined) return options\n const instancePerspective = instance.config.perspective\n return {\n ...options,\n perspective:\n instancePerspective !== undefined ? instancePerspective : QUERY_STORE_DEFAULT_PERSPECTIVE,\n }\n}\n\nconst queryStore = defineStore<QueryStoreState, BoundResourceKey>({\n name: 'QueryStore',\n getInitialState: () => ({queries: {}}),\n initialize(context) {\n const subscriptions = [\n listenForNewSubscribersAndFetch(context),\n listenToLiveClientAndSetLastLiveEventIds(context),\n ]\n\n return () => {\n for (const subscription of subscriptions) {\n subscription.unsubscribe()\n }\n }\n },\n})\n\nconst errorHandler = (state: StoreState<{error?: unknown}>) => {\n return (error: unknown): void => state.set('setError', {error})\n}\n\nconst listenForNewSubscribersAndFetch = ({state, instance}: StoreContext<QueryStoreState>) => {\n return state.observable\n .pipe(\n map((s) => new Set(Object.keys(s.queries))),\n distinctUntilChanged((curr, next) => {\n if (curr.size !== next.size) return false\n return Array.from(next).every((i) => curr.has(i))\n }),\n startWith(new Set<string>()),\n pairwise(),\n mergeMap(([curr, next]) => {\n const added = Array.from(next).filter((i) => !curr.has(i))\n const removed = Array.from(curr).filter((i) => !next.has(i))\n\n return [\n ...added.map((key) => ({key, added: true})),\n ...removed.map((key) => ({key, added: false})),\n ]\n }),\n groupBy((i) => i.key),\n mergeMap((group$) =>\n group$.pipe(\n switchMap((e) => {\n if (!e.added) return EMPTY\n\n const lastLiveEventId$ = state.observable.pipe(\n map((s) => s.queries[group$.key]?.lastLiveEventId),\n distinctUntilChanged(),\n )\n const {\n query,\n params,\n projectId,\n dataset,\n tag,\n resource,\n perspective: perspectiveFromOptions,\n ...restOptions\n } = parseQueryKey(group$.key)\n\n // Short-circuit perspective resolution for non-release perspectives to avoid\n // touching the releases store (and its initialization) unnecessarily.\n const perspective$ = isReleasePerspective(perspectiveFromOptions)\n ? getPerspectiveState(instance, {\n perspective: perspectiveFromOptions,\n resource,\n }).observable.pipe(filter(Boolean))\n : of(perspectiveFromOptions ?? QUERY_STORE_DEFAULT_PERSPECTIVE)\n\n const client$ = getClientState(instance, {\n apiVersion: QUERY_STORE_API_VERSION,\n projectId,\n dataset,\n resource,\n }).observable\n\n return combineLatest({\n lastLiveEventId: lastLiveEventId$,\n client: client$,\n perspective: perspective$,\n }).pipe(\n switchMap(({lastLiveEventId, client, perspective}) =>\n client.observable.fetch(query, params, {\n ...restOptions,\n perspective,\n filterResponse: false,\n returnQuery: false,\n lastLiveEventId,\n tag,\n }),\n ),\n tap(({result, syncTags}) => {\n state.set('setQueryData', setQueryData(group$.key, result, syncTags))\n }),\n // Catch inside the per-event stream: erroring the group pipe would\n // complete the group's subscription, and since `groupBy` above never\n // removes its group subjects, re-adding the key would emit into a\n // subject with no subscribers — the key could never be fetched again.\n catchError((error) => {\n state.set('setQueryError', setQueryError(group$.key, error))\n return EMPTY\n }),\n )\n }),\n ),\n ),\n )\n .subscribe({error: errorHandler(state)})\n}\n\nconst listenToLiveClientAndSetLastLiveEventIds = ({\n state,\n instance,\n key: {resource},\n}: StoreContext<QueryStoreState, BoundResourceKey>) => {\n const liveMessages$ = observeLiveEvents(instance, {\n resource,\n // Surface CORS errors as store state (handled by the Cors Error\n // component) instead of erroring the stream: a stream error here would\n // reach the outer subscription's errorHandler, set the store-wide\n // `state.error`, and permanently brick every query selector.\n onCorsError: (error) => state.set('setError', {error}),\n }).pipe(share())\n\n return state.observable\n .pipe(\n mergeMap((s) => Object.entries(s.queries)),\n groupBy(([key]) => key),\n mergeMap((group$) => {\n const syncTags$ = group$.pipe(\n map(([, queryState]) => queryState),\n map((i) => i?.syncTags ?? EMPTY_ARRAY),\n distinctUntilChanged(),\n )\n\n return combineLatest([liveMessages$, syncTags$]).pipe(\n filter(([message, syncTags]) => message.tags.some((tag) => syncTags.includes(tag))),\n tap(([message]) => {\n state.set('setLastLiveEventId', setLastLiveEventId(group$.key, message.id))\n }),\n )\n }),\n )\n .subscribe({error: errorHandler(state)})\n}\n\n/**\n * Returns the state source for a query.\n *\n * This function returns a state source that represents the current result of a GROQ query.\n * Subscribing to the state source will instruct the SDK to fetch the query (if not already fetched)\n * and will keep the query live using the Live content API (considering sync tags) to provide up-to-date results.\n * When the last subscriber is removed, the query state is automatically cleaned up from the store.\n *\n * Note: This functionality is for advanced users who want to build their own framework integrations.\n * Our SDK also provides a React integration (useQuery hook) for convenient usage.\n *\n * Note: Automatic cleanup can interfere with React Suspense because if a component suspends while being the only subscriber,\n * cleanup might occur unexpectedly. In such cases, consider using `resolveQuery` instead.\n *\n * @beta\n */\nexport function getQueryState<\n TQuery extends string = string,\n TDataset extends string = string,\n TProjectId extends string = string,\n>(\n instance: SanityInstance,\n queryOptions: QueryOptions<TQuery, TDataset, TProjectId>,\n): StateSource<SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`> | undefined>\n\n/** @beta */\nexport function getQueryState<TData>(\n instance: SanityInstance,\n queryOptions: QueryOptions,\n): StateSource<TData | undefined>\n\n/** @beta */\nexport function getQueryState(\n instance: SanityInstance,\n queryOptions: QueryOptions,\n): StateSource<unknown>\n\n/** @beta */\nexport function getQueryState(\n ...args: Parameters<typeof _getQueryState>\n): ReturnType<typeof _getQueryState> {\n return _getQueryState(...args)\n}\nconst _getQueryState = bindActionByResource(\n queryStore,\n createStateSourceAction({\n selector: ({state, instance}: SelectorContext<QueryStoreState>, options: QueryOptions) => {\n if (state.error) throw state.error\n const key = getQueryKey(normalizeOptionsWithPerspective(instance, options))\n const queryState = state.queries[key]\n if (queryState?.error) throw queryState.error\n return queryState?.result\n },\n onSubscribe: ({state, instance}, options: QueryOptions) => {\n const subscriptionId = insecureRandomId()\n const key = getQueryKey(normalizeOptionsWithPerspective(instance, options))\n\n state.set('addSubscriber', addSubscriber(key, subscriptionId))\n\n return () => {\n // this runs on unsubscribe\n setCleanupTimeout(\n () => state.set('removeSubscriber', removeSubscriber(key, subscriptionId)),\n QUERY_STATE_CLEAR_DELAY,\n )\n }\n },\n }),\n)\n\n/**\n * Resolves the result of a query without registering a lasting subscriber.\n *\n * This function fetches the result of a GROQ query and returns a promise that resolves with the query result.\n * It registers a temporary subscriber for the lifetime of the resolution (released shortly after the promise\n * settles), so the query state participates in normal cleanup even when no lasting subscriber ever attaches —\n * e.g. when a suspended component errors before committing. This makes it ideal for use with React Suspense,\n * where the returned promise is thrown to delay rendering until the query result becomes available.\n * Once the promise resolves, it is expected that a real subscriber will be added via `getQueryState` to manage ongoing updates.\n *\n * Additionally, an optional AbortSignal can be provided to cancel the query and immediately clear the associated state\n * if there are no other active subscribers.\n *\n * @beta\n */\nexport function resolveQuery<\n TQuery extends string = string,\n TDataset extends string = string,\n TProjectId extends string = string,\n>(\n instance: SanityInstance,\n queryOptions: ResolveQueryOptions<TQuery, TDataset, TProjectId>,\n): Promise<SanityQueryResult<TQuery, `${TProjectId}.${TDataset}`>>\n\n/** @beta */\nexport function resolveQuery<TData>(\n instance: SanityInstance,\n queryOptions: ResolveQueryOptions,\n): Promise<TData>\n/** @beta */\nexport function resolveQuery(...args: Parameters<typeof _resolveQuery>): Promise<unknown> {\n return _resolveQuery(...args)\n}\nconst _resolveQuery = bindActionByResource(\n queryStore,\n ({state, instance}, {signal, ...options}: ResolveQueryOptions) => {\n const normalized = normalizeOptionsWithPerspective(instance, options)\n const {getCurrent} = getQueryState(instance, normalized)\n const key = getQueryKey(normalized)\n\n // Hold a temporary subscription for the lifetime of this resolution so the\n // key participates in normal cleanup. Without one, a fetch that errors\n // while a component is suspended leaves a subscriber-less key behind — the\n // component never commits, so no other subscriber exists — and its stored\n // error would be rethrown on every future mount without ever refetching.\n const subscriptionId = insecureRandomId()\n state.set('addSubscriber', addSubscriber(key, subscriptionId))\n\n const aborted$ = signal\n ? new Observable<void>((observer) => {\n const cleanup = () => {\n signal.removeEventListener('abort', listener)\n }\n\n const listener = () => {\n observer.error(new DOMException('The operation was aborted.', 'AbortError'))\n observer.complete()\n cleanup()\n }\n signal.addEventListener('abort', listener)\n\n return cleanup\n }).pipe(\n catchError((error) => {\n if (error instanceof Error && error.name === 'AbortError') {\n // Release immediately (not after the clear delay) so that when\n // this was the only subscriber, the key removal tears down the\n // in-flight request right away — e.g. when useQuery switches\n // to a different query\n state.set('removeSubscriber', removeSubscriber(key, subscriptionId))\n }\n throw error\n }),\n )\n : NEVER\n\n const resolved$ = state.observable.pipe(\n map(getCurrent),\n first((i) => i !== undefined),\n )\n\n const promise = firstValueFrom(race([resolved$, aborted$]))\n const releaseSubscriber = () => {\n setCleanupTimeout(() => {\n state.set('removeSubscriber', removeSubscriber(key, subscriptionId))\n }, QUERY_STATE_CLEAR_DELAY)\n }\n promise.then(releaseSubscriber, releaseSubscriber)\n return promise\n },\n)\n","/**\n * The fields to check for a title.\n * The order of the items in the array defines the priority.\n *\n * @internal\n */\nexport const TITLE_CANDIDATES = ['title', 'name', 'label', 'heading', 'header', 'caption']\n\n/**\n * The fields to check for a subtitle.\n * The order of the items in the array defines the priority.\n *\n * @internal\n */\nexport const SUBTITLE_CANDIDATES = ['description', 'subtitle', ...TITLE_CANDIDATES]\n\n/**\n * Generates a GROQ projection for preview data without requiring a schema.\n * Uses common field names to make educated guesses about which fields to use.\n *\n * @internal\n */\nexport const PREVIEW_PROJECTION = `{\n // Get all potential title fields\n \"titleCandidates\": {\n ${TITLE_CANDIDATES.map((field) => `\"${field}\": ${field}`).join(',\\n ')}\n },\n // Get all potential subtitle fields\n \"subtitleCandidates\": {\n ${SUBTITLE_CANDIDATES.map((field) => `\"${field}\": ${field}`).join(',\\n ')}\n },\n \"media\": coalesce(\n select(\n defined(asset) => {\"type\": \"image-asset\", \"_ref\": asset._ref},\n defined(image.asset) => {\"type\": \"image-asset\", \"_ref\": image.asset._ref},\n defined(mainImage.asset) => {\"type\": \"image-asset\", \"_ref\": mainImage.asset._ref},\n null\n )\n ),\n _type,\n _id,\n _updatedAt\n}`\n","import {type SanityClient} from '@sanity/client'\nimport {createImageUrlBuilder} from '@sanity/image-url'\n\nimport {getClient} from '../client/clientStore'\nimport {type DocumentResource} from '../config/sanityConfig'\nimport {type SanityInstance} from '../store/createSanityInstance'\nimport {isObject} from '../utils/object'\nimport {SUBTITLE_CANDIDATES, TITLE_CANDIDATES} from './previewConstants'\nimport {type PreviewQueryResult, type PreviewValue} from './types'\n\nconst API_VERSION = 'v2025-05-06'\n\n/**\n * Checks if the provided value has `_ref` property that is a string and starts with `image-`\n */\nfunction hasImageRef<T>(value: unknown): value is T & {_ref: string} {\n return isObject(value) && '_ref' in value && typeof (value as {_ref: unknown})._ref === 'string'\n}\n\n/**\n * Normalizes a media asset to a preview value.\n * Adds a url to a media asset reference using `@sanity/image-url`.\n *\n * @internal\n */\nexport function normalizeMedia(media: unknown, client: SanityClient): PreviewValue['media'] {\n if (!media) return null\n if (!hasImageRef(media)) return null\n\n const builder = createImageUrlBuilder(client)\n const url = builder.image({_ref: media._ref}).url()\n\n return {\n type: 'image-asset',\n _ref: media._ref,\n url,\n }\n}\n\n/**\n * Finds a single field value from a set of candidates based on a priority list of field names.\n * Returns the first non-empty string value found from the candidates matching the priority list order.\n *\n * @internal\n */\nfunction findFirstDefined(\n fieldsToSearch: string[],\n candidates: Record<string, unknown>,\n exclude?: unknown,\n): string | undefined {\n if (!candidates) return undefined\n\n for (const field of fieldsToSearch) {\n const value = candidates[field]\n if (typeof value === 'string' && value.trim() !== '' && value !== exclude) {\n return value\n }\n }\n\n return undefined\n}\n\n/**\n * Transforms a projection result (with titleCandidates, subtitleCandidates, media)\n * into a PreviewValue (with title, subtitle, media).\n *\n * @param projectionResult - The raw projection result from GROQ\n * @param instance - The Sanity instance to use for client configuration\n * @param resource - Data resource for the preview\n * @internal\n */\nexport function transformProjectionToPreview(\n instance: SanityInstance,\n projectionResult: PreviewQueryResult,\n resource?: DocumentResource,\n): PreviewValue {\n const title = findFirstDefined(TITLE_CANDIDATES, projectionResult.titleCandidates)\n const subtitle = findFirstDefined(SUBTITLE_CANDIDATES, projectionResult.subtitleCandidates, title)\n\n // Get a client for the resource (if provided) or use the instance config\n const client = getClient(instance, {\n apiVersion: API_VERSION,\n resource,\n })\n\n return {\n title: String(title || `${projectionResult._type}: ${projectionResult._id}`),\n subtitle: subtitle || undefined,\n media: normalizeMedia(projectionResult.media, client),\n ...(projectionResult._status && {_status: projectionResult._status}),\n }\n}\n","const WILDCARD_TOKEN = '*'\nconst NEGATION_TOKEN = '-'\n// This regex handles simple cases including quoted phrases.\n// More complex query syntaxes might need a more robust parser.\nconst TOKEN_REGEX = /(?:[^\\s\"]+|\"[^\"]*\")+/g\n\n/**\n * @internal\n * Checks if a token starts with the negation character.\n */\nfunction isNegationToken(token: string | undefined): boolean {\n return typeof token !== 'undefined' && token.trim().startsWith(NEGATION_TOKEN)\n}\n\n/**\n * @internal\n * Checks if a token ends with the wildcard character.\n */\nfunction isPrefixToken(token: string | undefined): boolean {\n return typeof token !== 'undefined' && token.trim().endsWith(WILDCARD_TOKEN)\n}\n\n/**\n * @internal\n * Checks if a token is enclosed in double quotes.\n */\nfunction isExactMatchToken(token: string | undefined): boolean {\n // Ensure the token exists, has at least 2 characters, and starts/ends with \"\n return !!token && token.length >= 2 && token.startsWith('\"') && token.endsWith('\"')\n}\n\n/**\n * Creates a GROQ search filter string (`[@] match text::query(\"...\")`)\n * from a raw search query string.\n *\n * It applies wildcard ('*') logic to the last eligible token and escapes\n * double quotes within the search term.\n *\n * If the input query is empty or only whitespace, it returns an empty string.\n *\n * @param query - The raw input search string.\n * @returns The GROQ search filter string, or an empty string.\n * @internal\n */\nexport function createGroqSearchFilter(query: string): string {\n // Trim leading/trailing whitespace from the overall query first\n const trimmedQuery = query.trim()\n if (!trimmedQuery) {\n return '' // Return empty if query is empty or just whitespace\n }\n\n // Extract tokens using the regex\n const tokens = trimmedQuery.match(TOKEN_REGEX) ?? []\n\n // Find the index of the last token eligible for wildcard appending\n const reversedTokens = [...tokens].reverse()\n const reversedIndex = reversedTokens.findIndex(\n (token: string) => !isNegationToken(token) && !isExactMatchToken(token),\n )\n const finalIncrementalTokenIndex = reversedIndex === -1 ? -1 : tokens.length - 1 - reversedIndex\n\n // Get the actual token based on the found index\n const finalIncrementalToken = tokens[finalIncrementalTokenIndex]\n\n const processedTokens = [...tokens]\n // If a suitable token was found and it doesn't already end with a wildcard,\n // apply the wildcard.\n if (finalIncrementalToken !== undefined && !isPrefixToken(finalIncrementalToken)) {\n // Replace the identified token with its wildcarded version\n processedTokens.splice(\n finalIncrementalTokenIndex,\n 1,\n `${finalIncrementalToken}${WILDCARD_TOKEN}`,\n )\n }\n\n // Join the tokens back into a space-separated string\n const wildcardSearch = processedTokens.join(' ')\n\n // Escape double quotes within the final search term for the GROQ query\n const escapedSearch = wildcardSearch.replace(/\"/g, '\\\\\"')\n\n // Construct the final GROQ filter clause\n return `[@] match text::query(\"${escapedSearch}\")`\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
const LOG_LEVEL_PRIORITY = {
|
|
2
|
+
error: 0,
|
|
3
|
+
warn: 1,
|
|
4
|
+
info: 2,
|
|
5
|
+
debug: 3,
|
|
6
|
+
trace: 4
|
|
7
|
+
}, DEFAULT_CONFIG = {
|
|
8
|
+
level: "warn",
|
|
9
|
+
namespaces: [],
|
|
10
|
+
internal: !1,
|
|
11
|
+
timestamps: !0,
|
|
12
|
+
enableInProduction: !1,
|
|
13
|
+
handler: {
|
|
14
|
+
error: console.error.bind(console),
|
|
15
|
+
warn: console.warn.bind(console),
|
|
16
|
+
info: console.info.bind(console),
|
|
17
|
+
debug: console.debug.bind(console),
|
|
18
|
+
trace: console.debug.bind(console)
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Parse DEBUG environment variable for automatic logging configuration
|
|
23
|
+
*
|
|
24
|
+
* Supports patterns similar to Sanity CLI/Studio:
|
|
25
|
+
* - DEBUG=sanity:* (all namespaces, debug level)
|
|
26
|
+
* - DEBUG=sanity:auth,sanity:document (specific namespaces)
|
|
27
|
+
* - DEBUG=sanity:trace:* (all namespaces, trace level)
|
|
28
|
+
* - DEBUG=sanity:*:internal (enable internal logs)
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
function parseDebugEnvVar() {
|
|
33
|
+
if (typeof process > "u" || !process.env?.DEBUG) return null;
|
|
34
|
+
let debug = process.env.DEBUG;
|
|
35
|
+
if (!debug.includes("sanity")) return null;
|
|
36
|
+
let config = {}, levelMatch = debug.match(/sanity:(trace|debug|info|warn|error):/), hasLevelSpecifier = !!levelMatch;
|
|
37
|
+
if (config.level = levelMatch ? levelMatch[1] : "debug", debug === "sanity") config.namespaces = ["*"];
|
|
38
|
+
else if (hasLevelSpecifier && debug.match(/sanity:(trace|debug|info|warn|error):\*/)) config.namespaces = ["*"];
|
|
39
|
+
else if (!hasLevelSpecifier && debug.includes("sanity:*")) config.namespaces = ["*"];
|
|
40
|
+
else {
|
|
41
|
+
let namespaces = debug.split(",").filter((s) => s.includes("sanity:")).map((s) => {
|
|
42
|
+
let cleaned = s.replace(/^sanity:/, "");
|
|
43
|
+
return hasLevelSpecifier && cleaned.match(/^(trace|debug|info|warn|error):/) ? cleaned.split(":").slice(1).join(":") : cleaned.split(":")[0];
|
|
44
|
+
}).filter(Boolean).filter((ns) => ns !== "*");
|
|
45
|
+
namespaces.length > 0 && (config.namespaces = namespaces);
|
|
46
|
+
}
|
|
47
|
+
return debug.includes(":internal") && (config.internal = !0), config;
|
|
48
|
+
}
|
|
49
|
+
const envConfig = parseDebugEnvVar();
|
|
50
|
+
let globalConfig = {
|
|
51
|
+
...DEFAULT_CONFIG,
|
|
52
|
+
...envConfig ?? {}
|
|
53
|
+
};
|
|
54
|
+
/* c8 ignore next 14 */
|
|
55
|
+
envConfig && ([
|
|
56
|
+
"info",
|
|
57
|
+
"debug",
|
|
58
|
+
"trace"
|
|
59
|
+
].includes(globalConfig.level) || globalConfig.level === "warn") && console.info(`[${(/* @__PURE__ */ new Date()).toISOString()}] [INFO] [sdk] Logging auto-configured from DEBUG environment variable`, {
|
|
60
|
+
level: globalConfig.level,
|
|
61
|
+
namespaces: globalConfig.namespaces,
|
|
62
|
+
internal: globalConfig.internal,
|
|
63
|
+
source: "env:DEBUG",
|
|
64
|
+
value: typeof process < "u" ? process.env?.DEBUG : void 0
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Configure the global logging system
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
function configureLogging(config) {
|
|
71
|
+
globalConfig = {
|
|
72
|
+
...globalConfig,
|
|
73
|
+
...config,
|
|
74
|
+
handler: config.handler ?? globalConfig.handler
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Check if logging is enabled in the current environment
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
function isLoggingEnabled() {
|
|
82
|
+
return typeof process < "u" && process.env?.NODE_ENV === "production" ? globalConfig.enableInProduction : !0;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if a namespace is enabled
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
function isNamespaceEnabled(namespace) {
|
|
89
|
+
return isLoggingEnabled() ? globalConfig.namespaces.includes("*") ? !0 : globalConfig.namespaces.includes(namespace) : !1;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if a log level is enabled
|
|
93
|
+
* @internal
|
|
94
|
+
*/
|
|
95
|
+
function isLevelEnabled(level) {
|
|
96
|
+
return isLoggingEnabled() ? LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[globalConfig.level] : !1;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Format a log message with timestamp, namespace, and instance context
|
|
100
|
+
* @internal
|
|
101
|
+
*/
|
|
102
|
+
function formatMessage(namespace, level, message, context) {
|
|
103
|
+
let parts = [];
|
|
104
|
+
if (globalConfig.timestamps) {
|
|
105
|
+
let timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
106
|
+
parts.push(`[${timestamp}]`);
|
|
107
|
+
}
|
|
108
|
+
parts.push(`[${level.toUpperCase()}]`), parts.push(`[${namespace}]`);
|
|
109
|
+
let instanceContext = context?.instanceContext;
|
|
110
|
+
return instanceContext && (instanceContext.projectId && parts.push(`[project:${instanceContext.projectId}]`), instanceContext.dataset && parts.push(`[dataset:${instanceContext.dataset}]`), instanceContext.instanceId && parts.push(`[instance:${instanceContext.instanceId.slice(0, 8)}]`)), parts.push(message), [parts.join(" "), context];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Sanitize context for logging (remove sensitive data)
|
|
114
|
+
* @internal
|
|
115
|
+
* @remarks
|
|
116
|
+
* This performs shallow sanitization only - it redacts top-level keys that contain
|
|
117
|
+
* sensitive names (token, password, secret, apiKey, authorization).
|
|
118
|
+
* Nested sensitive data (e.g., `\{auth: \{token: 'secret'\}\}`) will NOT be redacted.
|
|
119
|
+
* If deep sanitization is needed in the future, this can be enhanced to recursively
|
|
120
|
+
* traverse nested objects.
|
|
121
|
+
*/
|
|
122
|
+
function sanitizeContext(context) {
|
|
123
|
+
if (!context || Object.keys(context).length === 0) return;
|
|
124
|
+
let sanitized = { ...context }, sensitiveKeys = [
|
|
125
|
+
"token",
|
|
126
|
+
"password",
|
|
127
|
+
"secret",
|
|
128
|
+
"apiKey",
|
|
129
|
+
"authorization"
|
|
130
|
+
];
|
|
131
|
+
for (let key of Object.keys(sanitized)) sensitiveKeys.some((sensitive) => key.toLowerCase().includes(sensitive)) && (sanitized[key] = "[REDACTED]");
|
|
132
|
+
return sanitized;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Create a logger for a specific namespace
|
|
136
|
+
* @param namespace - A string identifier for the logging domain (e.g., 'auth', 'document')
|
|
137
|
+
* @param baseContext - Optional base context to include in all log messages
|
|
138
|
+
* @remarks
|
|
139
|
+
* If baseContext includes an `instanceContext` property (with projectId, dataset, instanceId),
|
|
140
|
+
* it will be automatically formatted into the log output for easier debugging of
|
|
141
|
+
* multi-instance scenarios.
|
|
142
|
+
* @public
|
|
143
|
+
*/
|
|
144
|
+
function createLogger(namespace, baseContext) {
|
|
145
|
+
let logAtLevel = (level, message, context) => {
|
|
146
|
+
if (!isNamespaceEnabled(namespace) || !isLevelEnabled(level) || context?.internal && !globalConfig.internal) return;
|
|
147
|
+
let [formatted, finalContext] = formatMessage(namespace, level, message, sanitizeContext({
|
|
148
|
+
...baseContext,
|
|
149
|
+
...context
|
|
150
|
+
}));
|
|
151
|
+
globalConfig.handler[level](formatted, finalContext);
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
namespace,
|
|
155
|
+
error: (message, context) => logAtLevel("error", message, context),
|
|
156
|
+
warn: (message, context) => logAtLevel("warn", message, context),
|
|
157
|
+
info: (message, context) => logAtLevel("info", message, context),
|
|
158
|
+
debug: (message, context) => logAtLevel("debug", message, context),
|
|
159
|
+
trace: (message, context) => logAtLevel("trace", message, {
|
|
160
|
+
...context,
|
|
161
|
+
internal: !0
|
|
162
|
+
}),
|
|
163
|
+
isLevelEnabled: (level) => isNamespaceEnabled(namespace) && isLevelEnabled(level),
|
|
164
|
+
child: (childContext) => createLogger(namespace, {
|
|
165
|
+
...baseContext,
|
|
166
|
+
...childContext
|
|
167
|
+
}),
|
|
168
|
+
getInstanceContext: () => baseContext?.instanceContext
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function getEnv(key) {
|
|
172
|
+
if (import.meta.env) return import.meta.env[key];
|
|
173
|
+
if (typeof process < "u" && process.env) return process.env[key];
|
|
174
|
+
if (typeof window < "u" && window.ENV) return window.ENV?.[key];
|
|
175
|
+
}
|
|
176
|
+
export { configureLogging as n, createLogger as r, getEnv as t };
|
|
177
|
+
|
|
178
|
+
//# sourceMappingURL=getEnv-76AjZd_f.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getEnv-76AjZd_f.js","names":[],"sources":["../src/utils/logger.ts","../src/utils/getEnv.ts"],"sourcesContent":["/**\n * Logging infrastructure for the Sanity SDK\n *\n * Provides multi-level, namespace-based logging for both SDK users and maintainers.\n * In production builds, all logging can be stripped via tree-shaking.\n *\n * @example SDK User\n * ```ts\n * import {configureLogging} from '@sanity/sdk'\n *\n * configureLogging({\n * level: 'info',\n * namespaces: ['auth', 'document']\n * })\n * ```\n *\n * @example SDK Maintainer\n * ```ts\n * configureLogging({\n * level: 'trace',\n * namespaces: ['*'],\n * internal: true\n * })\n * ```\n */\n\n/**\n * Log levels in order of verbosity (least to most)\n * - error: Critical failures that prevent operation\n * - warn: Issues that may cause problems but don't stop execution\n * - info: High-level informational messages (SDK user level)\n * - debug: Detailed debugging information (maintainer level)\n * - trace: Very detailed tracing (maintainer level, includes RxJS streams)\n * @public\n */\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace'\n\n/**\n * Log namespaces organize logs by functional domain\n *\n * @remarks\n * This is an extensible string type. As logging is added to more modules,\n * additional namespaces will be recognized. Currently implemented namespaces\n * will be documented as they are added.\n * @internal\n */\nexport type LogNamespace = string\n\n/**\n * Configuration for the logging system\n * @public\n */\nexport interface LoggerConfig {\n /**\n * Minimum log level to output\n * @defaultValue 'warn'\n */\n level?: LogLevel\n\n /**\n * Namespaces to enable. Use ['*'] for all namespaces\n * @defaultValue []\n * @remarks\n * Available namespaces depend on which modules have logging integrated.\n * Check the SDK documentation for the current list of instrumented modules.\n * @example ['auth', 'document']\n */\n namespaces?: string[]\n\n /**\n * Enable internal/maintainer-level logging\n * Shows RxJS streams, store internals, etc.\n * @defaultValue false\n */\n internal?: boolean\n\n /**\n * Custom log handler (for testing or custom output)\n * @defaultValue console methods\n */\n handler?: LogHandler\n\n /**\n * Enable timestamps in log output\n * @defaultValue true\n */\n timestamps?: boolean\n\n /**\n * Enable in production builds\n * @defaultValue false\n */\n enableInProduction?: boolean\n}\n\n/**\n * Custom log handler interface\n *\n * @internal\n */\nexport interface LogHandler {\n error: (message: string, context?: LogContext) => void\n warn: (message: string, context?: LogContext) => void\n info: (message: string, context?: LogContext) => void\n debug: (message: string, context?: LogContext) => void\n trace: (message: string, context?: LogContext) => void\n}\n\n/**\n * Context object attached to log messages\n *\n * This interface allows you to attach arbitrary contextual data to log messages.\n * The index signature `[key: string]: unknown` enables you to add any custom\n * properties relevant to your log entry (e.g., `userId`, `documentId`, `action`, etc.).\n *\n * **Sensitive data sanitization:**\n * Top-level keys containing sensitive names (`token`, `password`, `secret`, `apiKey`,\n * `authorization`) are automatically redacted to `[REDACTED]` in log output.\n *\n * @example\n * ```ts\n * logger.info('User logged in', {\n * userId: '123', // Custom context\n * action: 'login', // Custom context\n * token: 'secret' // Will be redacted to [REDACTED]\n * })\n * ```\n *\n * @internal\n */\nexport interface LogContext {\n /**\n * Custom context properties that provide additional information about the log entry.\n * Any key-value pairs can be added here (e.g., userId, documentId, requestId, etc.).\n * Keys with sensitive names (token, password, secret, apiKey, authorization) are\n * automatically sanitized.\n */\n [key: string]: unknown\n /** Error object if logging an error */\n error?: Error | unknown\n /** Duration in milliseconds for timed operations */\n duration?: number\n /** Stack trace for debugging */\n stack?: string\n /** Instance context (automatically added when available) */\n instanceContext?: InstanceContext\n}\n\n/**\n * Instance context information automatically added to logs\n * @internal\n */\nexport interface InstanceContext {\n /** Unique instance ID */\n instanceId?: string\n /** Project ID */\n projectId?: string\n /** Dataset name */\n dataset?: string\n}\n\n/**\n * Logger instance for a specific namespace\n * @internal\n */\nexport interface Logger {\n readonly namespace: string\n error: (message: string, context?: LogContext) => void\n warn: (message: string, context?: LogContext) => void\n info: (message: string, context?: LogContext) => void\n debug: (message: string, context?: LogContext) => void\n trace: (message: string, context?: LogContext) => void\n /** Check if a log level is enabled (for performance-sensitive code) */\n isLevelEnabled: (level: LogLevel) => boolean\n /** Create a child logger with extended context */\n child: (context: LogContext) => Logger\n /** Get the instance context if available */\n getInstanceContext: () => InstanceContext | undefined\n}\n\n// Log level priority (lower number = higher priority)\nconst LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n trace: 4,\n}\n\n// Default logging configuration\nconst DEFAULT_CONFIG: Required<LoggerConfig> = {\n level: 'warn',\n namespaces: [],\n internal: false,\n timestamps: true,\n enableInProduction: false,\n handler: {\n // eslint-disable-next-line no-console\n error: console.error.bind(console),\n // eslint-disable-next-line no-console\n warn: console.warn.bind(console),\n // eslint-disable-next-line no-console\n info: console.info.bind(console),\n // eslint-disable-next-line no-console\n debug: console.debug.bind(console),\n // eslint-disable-next-line no-console\n trace: console.debug.bind(console), // trace uses console.debug\n },\n}\n\n/**\n * Parse DEBUG environment variable for automatic logging configuration\n *\n * Supports patterns similar to Sanity CLI/Studio:\n * - DEBUG=sanity:* (all namespaces, debug level)\n * - DEBUG=sanity:auth,sanity:document (specific namespaces)\n * - DEBUG=sanity:trace:* (all namespaces, trace level)\n * - DEBUG=sanity:*:internal (enable internal logs)\n *\n * @internal\n */\nexport function parseDebugEnvVar(): Partial<LoggerConfig> | null {\n if (typeof process === 'undefined' || !process.env?.['DEBUG']) {\n return null\n }\n\n const debug = process.env['DEBUG']\n\n // Only process if it includes 'sanity'\n if (!debug.includes('sanity')) {\n return null\n }\n\n const config: Partial<LoggerConfig> = {}\n\n // Parse level from pattern like \"sanity:trace:*\" or \"sanity:debug:*\"\n const levelMatch = debug.match(/sanity:(trace|debug|info|warn|error):/)\n const hasLevelSpecifier = !!levelMatch\n if (levelMatch) {\n config.level = levelMatch[1] as LogLevel\n } else {\n // Default to debug level if just \"sanity:*\" or \"sanity:namespace\"\n config.level = 'debug'\n }\n\n // Parse namespaces\n if (debug === 'sanity') {\n config.namespaces = ['*']\n } else if (hasLevelSpecifier && debug.match(/sanity:(trace|debug|info|warn|error):\\*/)) {\n // Pattern like \"sanity:trace:*\" - wildcard after level\n config.namespaces = ['*']\n } else if (!hasLevelSpecifier && debug.includes('sanity:*')) {\n // Pattern like \"sanity:*\" - wildcard without level\n config.namespaces = ['*']\n } else {\n // Extract specific namespaces like \"sanity:auth,sanity:document\"\n const namespaces = debug\n .split(',')\n .filter((s) => s.includes('sanity:'))\n .map((s) => {\n // Remove 'sanity:' prefix\n const cleaned = s.replace(/^sanity:/, '')\n // If there's a level specifier, skip it\n if (hasLevelSpecifier && cleaned.match(/^(trace|debug|info|warn|error):/)) {\n // Get the part after the level: \"trace:auth\" -> \"auth\"\n return cleaned.split(':').slice(1).join(':')\n }\n // Otherwise get the first part: \"auth:something\" -> \"auth\"\n return cleaned.split(':')[0]\n })\n .filter(Boolean)\n .filter((ns) => ns !== '*') // Filter out wildcards\n\n if (namespaces.length > 0) {\n config.namespaces = namespaces\n }\n }\n\n // Check for internal flag: DEBUG=sanity:*:internal\n if (debug.includes(':internal')) {\n config.internal = true\n }\n\n return config\n}\n\n// Global configuration - initialized with env var settings if present\nconst envConfig = parseDebugEnvVar()\nlet globalConfig: Required<LoggerConfig> = {\n ...DEFAULT_CONFIG,\n ...(envConfig ?? {}),\n}\n\n// Log that env var configuration was detected (only if DEBUG is set)\n// Note: This runs at module initialization, difficult to test without complex module mocking\n/* c8 ignore next 14 */\nif (envConfig) {\n const shouldLog =\n ['info', 'debug', 'trace'].includes(globalConfig.level) || globalConfig.level === 'warn'\n if (shouldLog) {\n // eslint-disable-next-line no-console\n console.info(\n `[${new Date().toISOString()}] [INFO] [sdk] Logging auto-configured from DEBUG environment variable`,\n {\n level: globalConfig.level,\n namespaces: globalConfig.namespaces,\n internal: globalConfig.internal,\n source: 'env:DEBUG',\n value: typeof process !== 'undefined' ? process.env?.['DEBUG'] : undefined,\n },\n )\n }\n}\n\n/**\n * Configure the global logging system\n * @public\n */\nexport function configureLogging(config: LoggerConfig): void {\n globalConfig = {\n ...globalConfig,\n ...config,\n handler: config.handler ?? globalConfig.handler,\n }\n}\n\n/**\n * Get the current logging configuration\n * @internal\n */\nexport function getLoggingConfig(): Readonly<Required<LoggerConfig>> {\n return globalConfig\n}\n\n/**\n * Reset logging to default configuration\n * @internal\n */\nexport function resetLogging(): void {\n globalConfig = {...DEFAULT_CONFIG}\n}\n\n/**\n * Check if logging is enabled in the current environment\n * @internal\n */\nfunction isLoggingEnabled(): boolean {\n // In production, only log if explicitly enabled\n if (typeof process !== 'undefined' && process.env?.['NODE_ENV'] === 'production') {\n return globalConfig.enableInProduction\n }\n return true\n}\n\n/**\n * Check if a namespace is enabled\n * @internal\n */\nfunction isNamespaceEnabled(namespace: string): boolean {\n if (!isLoggingEnabled()) return false\n if (globalConfig.namespaces.includes('*')) return true\n return globalConfig.namespaces.includes(namespace)\n}\n\n/**\n * Check if a log level is enabled\n * @internal\n */\nfunction isLevelEnabled(level: LogLevel): boolean {\n if (!isLoggingEnabled()) return false\n return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[globalConfig.level]\n}\n\n/**\n * Format a log message with timestamp, namespace, and instance context\n * @internal\n */\nfunction formatMessage(\n namespace: LogNamespace,\n level: LogLevel,\n message: string,\n context?: LogContext,\n): [string, LogContext | undefined] {\n const parts: string[] = []\n\n if (globalConfig.timestamps) {\n const timestamp = new Date().toISOString()\n parts.push(`[${timestamp}]`)\n }\n\n parts.push(`[${level.toUpperCase()}]`)\n parts.push(`[${namespace}]`)\n\n // Add instance context if available\n const instanceContext = context?.instanceContext\n if (instanceContext) {\n if (instanceContext.projectId) {\n parts.push(`[project:${instanceContext.projectId}]`)\n }\n if (instanceContext.dataset) {\n parts.push(`[dataset:${instanceContext.dataset}]`)\n }\n if (instanceContext.instanceId) {\n parts.push(`[instance:${instanceContext.instanceId.slice(0, 8)}]`)\n }\n }\n\n parts.push(message)\n\n return [parts.join(' '), context]\n}\n\n/**\n * Sanitize context for logging (remove sensitive data)\n * @internal\n * @remarks\n * This performs shallow sanitization only - it redacts top-level keys that contain\n * sensitive names (token, password, secret, apiKey, authorization).\n * Nested sensitive data (e.g., `\\{auth: \\{token: 'secret'\\}\\}`) will NOT be redacted.\n * If deep sanitization is needed in the future, this can be enhanced to recursively\n * traverse nested objects.\n */\nfunction sanitizeContext(context?: LogContext): LogContext | undefined {\n if (!context || Object.keys(context).length === 0) return undefined\n\n const sanitized = {...context}\n\n // Remove or redact sensitive fields at the top level\n const sensitiveKeys = ['token', 'password', 'secret', 'apiKey', 'authorization']\n for (const key of Object.keys(sanitized)) {\n if (sensitiveKeys.some((sensitive) => key.toLowerCase().includes(sensitive))) {\n sanitized[key] = '[REDACTED]'\n }\n }\n\n return sanitized\n}\n\n/**\n * Create a logger for a specific namespace\n * @param namespace - A string identifier for the logging domain (e.g., 'auth', 'document')\n * @param baseContext - Optional base context to include in all log messages\n * @remarks\n * If baseContext includes an `instanceContext` property (with projectId, dataset, instanceId),\n * it will be automatically formatted into the log output for easier debugging of\n * multi-instance scenarios.\n * @public\n */\nexport function createLogger(namespace: string, baseContext?: LogContext): Logger {\n const logAtLevel = (level: LogLevel, message: string, context?: LogContext) => {\n // Early return if namespace or level not enabled\n if (!isNamespaceEnabled(namespace)) return\n if (!isLevelEnabled(level)) return\n\n // Skip internal logs if not enabled\n if (context?.['internal'] && !globalConfig.internal) return\n\n const mergedContext = {...baseContext, ...context}\n const sanitized = sanitizeContext(mergedContext)\n const [formatted, finalContext] = formatMessage(namespace, level, message, sanitized)\n\n globalConfig.handler[level](formatted, finalContext)\n }\n\n const logger: Logger = {\n namespace,\n error: (message, context) => logAtLevel('error', message, context),\n warn: (message, context) => logAtLevel('warn', message, context),\n info: (message, context) => logAtLevel('info', message, context),\n debug: (message, context) => logAtLevel('debug', message, context),\n trace: (message, context) => logAtLevel('trace', message, {...context, internal: true}),\n isLevelEnabled: (level) => isNamespaceEnabled(namespace) && isLevelEnabled(level),\n child: (childContext) => createLogger(namespace, {...baseContext, ...childContext}),\n getInstanceContext: () => baseContext?.instanceContext,\n }\n\n return logger\n}\n\n/**\n * Create a performance timer for measuring operation duration\n * @internal\n */\nexport function createTimer(\n namespace: string,\n operation: string,\n): {end: (message?: string, context?: LogContext) => number} {\n const logger = createLogger(namespace)\n const start = performance.now()\n\n return {\n end: (message?: string, context?: LogContext): number => {\n const duration = performance.now() - start\n logger.debug(message ?? `${operation} completed`, {\n ...context,\n operation,\n duration,\n })\n return duration\n },\n }\n}\n\n/**\n * Utility to log RxJS operator execution (for maintainers)\n * Will be exported in future PR when logging is added to stores\n * @internal\n */\n/* c8 ignore next 4 */\nfunction logRxJSOperator(namespace: string, operator: string, context?: LogContext): void {\n const logger = createLogger(namespace)\n logger.trace(`RxJS: ${operator}`, {...context, internal: true, operator})\n}\n\n/**\n * Extract instance context from a SanityInstance for logging\n * Will be exported in future PR when logging is added to stores\n * @param instance - The SanityInstance to extract context from\n * @returns Instance context suitable for logging\n * @internal\n */\n/* c8 ignore next 7 */\nfunction getInstanceContext(instance: {\n instanceId?: string\n config?: {projectId?: string; dataset?: string}\n}): InstanceContext {\n return {\n instanceId: instance.instanceId,\n projectId: instance.config?.projectId,\n dataset: instance.config?.dataset,\n }\n}\n\n// Prevent unused function warnings - these will be exported and used in future PRs\n/* c8 ignore next 2 */\nvoid logRxJSOperator\nvoid getInstanceContext\n","// Local type declaration for Remix\ntype WindowWithEnv = Window &\n typeof globalThis & {\n ENV?: Record<string, unknown>\n }\n\ntype KnownEnvVar = 'DEV' | 'PKG_VERSION'\n\nexport function getEnv(key: KnownEnvVar): unknown {\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n // Vite environment variables\n return (import.meta.env as unknown as Record<string, unknown>)[key]\n } else if (typeof process !== 'undefined' && process.env) {\n // Node.js or server-side environment variables\n return process.env[key]\n } else if (typeof window !== 'undefined' && (window as WindowWithEnv).ENV) {\n // Remix-style client-side environment variables\n return (window as WindowWithEnv).ENV?.[key]\n }\n return undefined\n}\n"],"mappings":"AAqLA,MAAM,qBAA+C;CACnD,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACP,OAAO;AACT,GAGM,iBAAyC;CAC7C,OAAO;CACP,YAAY,CAAC;CACb,UAAU;CACV,YAAY;CACZ,oBAAoB;CACpB,SAAS;EAEP,OAAO,QAAQ,MAAM,KAAK,OAAO;EAEjC,MAAM,QAAQ,KAAK,KAAK,OAAO;EAE/B,MAAM,QAAQ,KAAK,KAAK,OAAO;EAE/B,OAAO,QAAQ,MAAM,KAAK,OAAO;EAEjC,OAAO,QAAQ,MAAM,KAAK,OAAO;CACnC;AACF;;;;;;;;;;;;AAaA,SAAgB,mBAAiD;CAC/D,IAAI,OAAO,UAAY,OAAe,CAAC,QAAQ,KAAM,OACnD,OAAO;CAGT,IAAM,QAAQ,QAAQ,IAAI;CAG1B,IAAI,CAAC,MAAM,SAAS,QAAQ,GAC1B,OAAO;CAGT,IAAM,SAAgC,CAAC,GAGjC,aAAa,MAAM,MAAM,uCAAuC,GAChE,oBAAoB,CAAC,CAAC;CAS5B,IARA,AAIE,OAAO,QAJL,aACa,WAAW,KAGX,SAIb,UAAU,UACZ,OAAO,aAAa,CAAC,GAAG;MACnB,IAAI,qBAAqB,MAAM,MAAM,yCAAyC,GAEnF,OAAO,aAAa,CAAC,GAAG;MACnB,IAAI,CAAC,qBAAqB,MAAM,SAAS,UAAU,GAExD,OAAO,aAAa,CAAC,GAAG;MACnB;EAEL,IAAM,aAAa,MAChB,MAAM,GAAG,CAAC,CACV,QAAQ,MAAM,EAAE,SAAS,SAAS,CAAC,CAAC,CACpC,KAAK,MAAM;GAEV,IAAM,UAAU,EAAE,QAAQ,YAAY,EAAE;GAOxC,OALI,qBAAqB,QAAQ,MAAM,iCAAiC,IAE/D,QAAQ,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,IAGtC,QAAQ,MAAM,GAAG,CAAC,CAAC;EAC5B,CAAC,CAAC,CACD,OAAO,OAAO,CAAC,CACf,QAAQ,OAAO,OAAO,GAAG;EAE5B,AAAI,WAAW,SAAS,MACtB,OAAO,aAAa;CAExB;CAOA,OAJI,MAAM,SAAS,WAAW,MAC5B,OAAO,WAAW,KAGb;AACT;AAGA,MAAM,YAAY,iBAAiB;AACnC,IAAI,eAAuC;CACzC,GAAG;CACH,GAAI,aAAa,CAAC;AACpB;;AAKI,cAEA;CAAC;CAAQ;CAAS;AAAO,CAAC,CAAC,SAAS,aAAa,KAAK,KAAK,aAAa,UAAU,WAGlF,QAAQ,KACN,qBAAI,IAAI,KAAK,EAAA,CAAE,YAAY,EAAE,yEAC7B;CACE,OAAO,aAAa;CACpB,YAAY,aAAa;CACzB,UAAU,aAAa;CACvB,QAAQ;CACR,OAAO,OAAO,UAAY,MAAc,QAAQ,KAAM,QAAW,KAAA;AACnE,CACF;;;;;AAQJ,SAAgB,iBAAiB,QAA4B;CAC3D,eAAe;EACb,GAAG;EACH,GAAG;EACH,SAAS,OAAO,WAAW,aAAa;CAC1C;AACF;;;;;AAsBA,SAAS,mBAA4B;CAKnC,OAHI,OAAO,UAAY,OAAe,QAAQ,KAAM,aAAgB,eAC3D,aAAa,qBAEf;AACT;;;;;AAMA,SAAS,mBAAmB,WAA4B;CAGtD,OAFK,iBAAiB,IAClB,aAAa,WAAW,SAAS,GAAG,IAAU,KAC3C,aAAa,WAAW,SAAS,SAAS,IAFjB;AAGlC;;;;;AAMA,SAAS,eAAe,OAA0B;CAEhD,OADK,iBAAiB,IACf,mBAAmB,UAAU,mBAAmB,aAAa,SADpC;AAElC;;;;;AAMA,SAAS,cACP,WACA,OACA,SACA,SACkC;CAClC,IAAM,QAAkB,CAAC;CAEzB,IAAI,aAAa,YAAY;EAC3B,IAAM,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACzC,MAAM,KAAK,IAAI,UAAU,EAAE;CAC7B;CAGA,AADA,MAAM,KAAK,IAAI,MAAM,YAAY,EAAE,EAAE,GACrC,MAAM,KAAK,IAAI,UAAU,EAAE;CAG3B,IAAM,kBAAkB,SAAS;CAejC,OAdI,oBACE,gBAAgB,aAClB,MAAM,KAAK,YAAY,gBAAgB,UAAU,EAAE,GAEjD,gBAAgB,WAClB,MAAM,KAAK,YAAY,gBAAgB,QAAQ,EAAE,GAE/C,gBAAgB,cAClB,MAAM,KAAK,aAAa,gBAAgB,WAAW,MAAM,GAAG,CAAC,EAAE,EAAE,IAIrE,MAAM,KAAK,OAAO,GAEX,CAAC,MAAM,KAAK,GAAG,GAAG,OAAO;AAClC;;;;;;;;;;;AAYA,SAAS,gBAAgB,SAA8C;CACrE,IAAI,CAAC,WAAW,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW,GAAG;CAEnD,IAAM,YAAY,EAAC,GAAG,QAAO,GAGvB,gBAAgB;EAAC;EAAS;EAAY;EAAU;EAAU;CAAe;CAC/E,KAAK,IAAM,OAAO,OAAO,KAAK,SAAS,GACrC,AAAI,cAAc,MAAM,cAAc,IAAI,YAAY,CAAC,CAAC,SAAS,SAAS,CAAC,MACzE,UAAU,OAAO;CAIrB,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,aAAa,WAAmB,aAAkC;CAChF,IAAM,cAAc,OAAiB,SAAiB,YAAyB;EAM7E,IAJI,CAAC,mBAAmB,SAAS,KAC7B,CAAC,eAAe,KAAK,KAGrB,SAAU,YAAe,CAAC,aAAa,UAAU;EAIrD,IAAM,CAAC,WAAW,gBAAgB,cAAc,WAAW,OAAO,SADhD,gBAAgB;GADX,GAAG;GAAa,GAAG;EACI,CACqC,CAAC;EAEpF,aAAa,QAAQ,MAAM,CAAC,WAAW,YAAY;CACrD;CAcA,OAAO;EAXL;EACA,QAAQ,SAAS,YAAY,WAAW,SAAS,SAAS,OAAO;EACjE,OAAO,SAAS,YAAY,WAAW,QAAQ,SAAS,OAAO;EAC/D,OAAO,SAAS,YAAY,WAAW,QAAQ,SAAS,OAAO;EAC/D,QAAQ,SAAS,YAAY,WAAW,SAAS,SAAS,OAAO;EACjE,QAAQ,SAAS,YAAY,WAAW,SAAS,SAAS;GAAC,GAAG;GAAS,UAAU;EAAI,CAAC;EACtF,iBAAiB,UAAU,mBAAmB,SAAS,KAAK,eAAe,KAAK;EAChF,QAAQ,iBAAiB,aAAa,WAAW;GAAC,GAAG;GAAa,GAAG;EAAY,CAAC;EAClF,0BAA0B,aAAa;CAG7B;AACd;ACrdA,SAAgB,OAAO,KAA2B;CAChD,IAA0C,YAAY,KAEpD,OAAQ,YAAY,IAA2C;CAC1D,IAAI,OAAO,UAAY,OAAe,QAAQ,KAEnD,OAAO,QAAQ,IAAI;CACd,IAAI,OAAO,SAAW,OAAgB,OAAyB,KAEpE,OAAQ,OAAyB,MAAM;AAG3C"}
|