@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +12 -0
  2. package/dist/_exports/_internal.d.ts +163 -0
  3. package/dist/_exports/_internal.d.ts.map +1 -0
  4. package/dist/_exports/_internal.js +332 -0
  5. package/dist/_exports/_internal.js.map +1 -0
  6. package/dist/_exports/agent.d.ts +65 -0
  7. package/dist/_exports/agent.d.ts.map +1 -0
  8. package/dist/_exports/agent.js +71 -0
  9. package/dist/_exports/agent.js.map +1 -0
  10. package/dist/_exports/comlink.d.ts +118 -0
  11. package/dist/_exports/comlink.d.ts.map +1 -0
  12. package/dist/_exports/comlink.js +69 -0
  13. package/dist/_exports/comlink.js.map +1 -0
  14. package/dist/authStore-CtE9bLHW.js +754 -0
  15. package/dist/authStore-CtE9bLHW.js.map +1 -0
  16. package/dist/authStore-DKFX8fn7.d.ts +3179 -0
  17. package/dist/authStore-DKFX8fn7.d.ts.map +1 -0
  18. package/dist/clientStore-BtE2-q2i.js +90 -0
  19. package/dist/clientStore-BtE2-q2i.js.map +1 -0
  20. package/dist/createSanityInstance-DUI1bGIg.d.ts +278 -0
  21. package/dist/createSanityInstance-DUI1bGIg.d.ts.map +1 -0
  22. package/dist/createStateSourceAction-BOKtqFNm.js +300 -0
  23. package/dist/createStateSourceAction-BOKtqFNm.js.map +1 -0
  24. package/dist/getEnv-D5RGkQLu.js +8 -0
  25. package/dist/getEnv-D5RGkQLu.js.map +1 -0
  26. package/dist/getNodeState-CR3__B2D.js +67 -0
  27. package/dist/getNodeState-CR3__B2D.js.map +1 -0
  28. package/dist/index.d.ts +3 -2332
  29. package/dist/index.js +4171 -4332
  30. package/dist/index.js.map +1 -1
  31. package/dist/logger-DAtJb5RQ.js +173 -0
  32. package/dist/logger-DAtJb5RQ.js.map +1 -0
  33. package/dist/object-CxXlSVg7.js +83 -0
  34. package/dist/object-CxXlSVg7.js.map +1 -0
  35. package/dist/queryStore-DPf9sN3V.js +1346 -0
  36. package/dist/queryStore-DPf9sN3V.js.map +1 -0
  37. package/dist/rolldown-runtime-vyAXikos.js +9 -0
  38. package/dist/setCleanupTimeout-BiM6q1KF.js +31 -0
  39. package/dist/setCleanupTimeout-BiM6q1KF.js.map +1 -0
  40. package/dist/telemetryManager-Dq_qCkeI.js +127 -0
  41. package/dist/telemetryManager-Dq_qCkeI.js.map +1 -0
  42. package/dist/version-BChjsi4u.js +9 -0
  43. package/dist/version-BChjsi4u.js.map +1 -0
  44. package/package.json +47 -52
  45. package/src/_exports/_internal.ts +27 -0
  46. package/src/_exports/agent.ts +19 -0
  47. package/src/_exports/comlink.ts +16 -0
  48. package/src/_exports/index.ts +125 -64
  49. package/src/access/checkPermissions.test.ts +55 -0
  50. package/src/access/checkPermissions.ts +54 -0
  51. package/src/agent/agentActions.test.ts +16 -60
  52. package/src/agent/agentActions.ts +25 -38
  53. package/src/applications/applications.test-d.ts +88 -0
  54. package/src/applications/applications.test.ts +130 -0
  55. package/src/applications/applications.ts +367 -0
  56. package/src/auth/authLogger.ts +30 -0
  57. package/src/auth/authStore.test.ts +194 -13
  58. package/src/auth/authStore.ts +58 -26
  59. package/src/auth/getOrganizationVerificationState.test.ts +18 -17
  60. package/src/auth/getOrganizationVerificationState.ts +4 -4
  61. package/src/auth/handleAuthCallback.test.ts +35 -1
  62. package/src/auth/handleAuthCallback.ts +25 -6
  63. package/src/auth/logout.test.ts +75 -2
  64. package/src/auth/logout.ts +23 -4
  65. package/src/auth/refreshStampedToken.test.ts +219 -469
  66. package/src/auth/refreshStampedToken.ts +79 -195
  67. package/src/auth/studioAuth.ts +1 -1
  68. package/src/auth/studioModeAuth.ts +2 -1
  69. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +146 -13
  70. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +37 -6
  71. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +2 -0
  72. package/src/client/clientStore.test.ts +62 -37
  73. package/src/client/clientStore.ts +45 -75
  74. package/src/client/liveEvents.test.ts +151 -0
  75. package/src/client/liveEvents.ts +107 -0
  76. package/src/comlink/controller/actions/destroyController.test.ts +4 -1
  77. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +4 -1
  78. package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
  79. package/src/comlink/controller/actions/getOrCreateController.test.ts +4 -1
  80. package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
  81. package/src/comlink/controller/comlinkControllerStore.test.ts +4 -1
  82. package/src/comlink/node/actions/getOrCreateNode.test.ts +9 -3
  83. package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
  84. package/src/comlink/node/actions/releaseNode.test.ts +7 -4
  85. package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
  86. package/src/comlink/node/getNodeState.test.ts +1 -1
  87. package/src/comlink/node/getNodeState.ts +2 -1
  88. package/src/comments/addonDatasetStore.test.ts +215 -0
  89. package/src/comments/addonDatasetStore.ts +249 -0
  90. package/src/comments/buildCommentThreads.test.ts +83 -0
  91. package/src/comments/buildCommentThreads.ts +54 -0
  92. package/src/comments/commentActions.test.ts +540 -0
  93. package/src/comments/commentActions.ts +555 -0
  94. package/src/comments/commentFieldPath.test.ts +40 -0
  95. package/src/comments/commentFieldPath.ts +44 -0
  96. package/src/comments/commentsConstants.ts +80 -0
  97. package/src/comments/commentsStore.test.ts +419 -0
  98. package/src/comments/commentsStore.ts +483 -0
  99. package/src/comments/normalizeComment.test.ts +116 -0
  100. package/src/comments/normalizeComment.ts +45 -0
  101. package/src/comments/observeComments.test.ts +193 -0
  102. package/src/comments/observeComments.ts +113 -0
  103. package/src/comments/reducers.test.ts +277 -0
  104. package/src/comments/reducers.ts +316 -0
  105. package/src/comments/types.ts +249 -0
  106. package/src/comments/weakenReferences.test.ts +47 -0
  107. package/src/comments/weakenReferences.ts +29 -0
  108. package/src/config/__tests__/handles.test.ts +18 -12
  109. package/src/config/handles.ts +25 -7
  110. package/src/config/sanityConfig.ts +40 -82
  111. package/src/datasets/datasets.test.ts +2 -2
  112. package/src/datasets/datasets.ts +10 -4
  113. package/src/document/actions.test.ts +115 -33
  114. package/src/document/actions.ts +212 -15
  115. package/src/document/applyDocumentActions.test.ts +35 -13
  116. package/src/document/applyDocumentActions.ts +30 -9
  117. package/src/document/documentConstants.ts +15 -0
  118. package/src/document/documentStore.concurrency.test.ts +1306 -0
  119. package/src/document/documentStore.test.ts +753 -273
  120. package/src/document/documentStore.ts +197 -75
  121. package/src/document/events.test.ts +57 -2
  122. package/src/document/events.ts +82 -26
  123. package/src/document/listen.test.ts +56 -0
  124. package/src/document/listen.ts +74 -21
  125. package/src/document/listenerEventOperators.test.ts +311 -0
  126. package/src/document/listenerEventOperators.ts +217 -0
  127. package/src/document/patchOperations.test.ts +76 -2
  128. package/src/document/patchOperations.ts +60 -1
  129. package/src/document/permissions.test.ts +107 -60
  130. package/src/document/permissions.ts +44 -19
  131. package/src/document/processActions/create.ts +138 -0
  132. package/src/document/processActions/delete.ts +100 -0
  133. package/src/document/processActions/discard.ts +63 -0
  134. package/src/document/processActions/edit.ts +154 -0
  135. package/src/document/processActions/processActions.ts +218 -0
  136. package/src/document/processActions/publish.ts +120 -0
  137. package/src/document/processActions/releaseArchive.ts +77 -0
  138. package/src/document/processActions/releaseCreate.ts +59 -0
  139. package/src/document/processActions/releaseDelete.ts +65 -0
  140. package/src/document/processActions/releaseEdit.ts +37 -0
  141. package/src/document/processActions/releasePublish.ts +45 -0
  142. package/src/document/processActions/releaseSchedule.ts +87 -0
  143. package/src/document/processActions/releaseUtil.ts +31 -0
  144. package/src/document/processActions/shared.ts +308 -0
  145. package/src/document/processActions/unpublish.ts +85 -0
  146. package/src/document/processActions.test.ts +1129 -121
  147. package/src/document/reducers.test.ts +772 -64
  148. package/src/document/reducers.ts +205 -51
  149. package/src/document/resourceRules.test.ts +178 -0
  150. package/src/document/resourceRules.ts +117 -0
  151. package/src/document/sharedListener.test.ts +16 -14
  152. package/src/document/sharedListener.ts +24 -8
  153. package/src/document/util.test.ts +46 -0
  154. package/src/document/util.ts +50 -0
  155. package/src/favorites/favorites.test.ts +5 -6
  156. package/src/installations/installations.test-d.ts +73 -0
  157. package/src/installations/installations.test.ts +62 -0
  158. package/src/installations/installations.ts +206 -0
  159. package/src/organization/organization.test-d.ts +36 -0
  160. package/src/organization/organization.test.ts +138 -0
  161. package/src/organization/organization.ts +138 -0
  162. package/src/organizations/organizations.test-d.ts +56 -0
  163. package/src/organizations/organizations.test.ts +150 -0
  164. package/src/organizations/organizations.ts +107 -0
  165. package/src/presence/bifurTransport.test.ts +201 -9
  166. package/src/presence/bifurTransport.ts +98 -14
  167. package/src/presence/paths.test.ts +70 -0
  168. package/src/presence/paths.ts +41 -0
  169. package/src/presence/presenceStore.test.ts +915 -24
  170. package/src/presence/presenceStore.ts +576 -104
  171. package/src/presence/types.ts +175 -3
  172. package/src/preview/previewProjectionUtils.test.ts +9 -22
  173. package/src/preview/previewProjectionUtils.ts +7 -3
  174. package/src/preview/types.ts +0 -15
  175. package/src/project/project.test-d.ts +37 -0
  176. package/src/project/project.test.ts +157 -12
  177. package/src/project/project.ts +138 -25
  178. package/src/projection/getProjectionState.test.ts +18 -20
  179. package/src/projection/getProjectionState.ts +23 -3
  180. package/src/projection/projectionQuery.ts +5 -5
  181. package/src/projection/projectionStore.test.ts +1 -9
  182. package/src/projection/resolveProjection.test.ts +1 -2
  183. package/src/projection/resolveProjection.ts +16 -0
  184. package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
  185. package/src/projection/subscribeToStateAndFetchBatches.ts +32 -23
  186. package/src/projects/projects.test-d.ts +32 -0
  187. package/src/projects/projects.test.ts +108 -42
  188. package/src/projects/projects.ts +63 -27
  189. package/src/query/queryStore.test.ts +259 -123
  190. package/src/query/queryStore.ts +85 -69
  191. package/src/query/reducers.test.ts +0 -42
  192. package/src/query/reducers.ts +2 -18
  193. package/src/releases/getPerspectiveState.test.ts +17 -23
  194. package/src/releases/getPerspectiveState.ts +8 -20
  195. package/src/releases/observeReleases.test.ts +127 -0
  196. package/src/releases/observeReleases.ts +102 -0
  197. package/src/releases/releasesStore.test.ts +100 -53
  198. package/src/releases/releasesStore.ts +69 -44
  199. package/src/releases/utils/sortReleases.test.ts +38 -2
  200. package/src/releases/utils/sortReleases.ts +15 -10
  201. package/src/store/createActionBinder.test.ts +109 -90
  202. package/src/store/createActionBinder.ts +36 -28
  203. package/src/store/createSanityInstance.test.ts +15 -13
  204. package/src/store/createSanityInstance.ts +14 -2
  205. package/src/store/createStateSourceAction.test.ts +64 -2
  206. package/src/store/createStateSourceAction.ts +38 -43
  207. package/src/store/createStoreInstance.test.ts +4 -2
  208. package/src/store/fetcherStore.test-d.ts +121 -0
  209. package/src/store/fetcherStore.test.ts +299 -0
  210. package/src/store/fetcherStore.ts +801 -0
  211. package/src/telemetry/environment.test.ts +119 -0
  212. package/src/telemetry/environment.ts +92 -0
  213. package/src/telemetry/events.ts +42 -0
  214. package/src/telemetry/initTelemetry.test.ts +432 -0
  215. package/src/telemetry/initTelemetry.ts +218 -0
  216. package/src/telemetry/telemetryManager.test.ts +327 -0
  217. package/src/telemetry/telemetryManager.ts +199 -0
  218. package/src/users/reducers.test.ts +3 -1
  219. package/src/users/reducers.ts +5 -6
  220. package/src/users/types.ts +4 -4
  221. package/src/users/usersStore.test.ts +16 -12
  222. package/src/users/usersStore.ts +5 -1
  223. package/src/utils/buildQuery.test.ts +14 -0
  224. package/src/utils/buildQuery.ts +17 -0
  225. package/src/utils/createFetcherStore.test.ts +7 -5
  226. package/src/utils/createFetcherStore.ts +8 -5
  227. package/src/utils/getStagingApiHost.test.ts +21 -0
  228. package/src/utils/getStagingApiHost.ts +14 -0
  229. package/src/utils/ids.test.ts +1 -29
  230. package/src/utils/ids.ts +0 -10
  231. package/src/utils/includeShape.ts +36 -0
  232. package/src/utils/isImportError.test.ts +72 -0
  233. package/src/utils/isImportError.ts +34 -0
  234. package/src/utils/logger.test.ts +12 -0
  235. package/src/utils/logger.ts +8 -3
  236. package/src/utils/object.test.ts +95 -0
  237. package/src/utils/object.ts +142 -0
  238. package/src/utils/setCleanupTimeout.ts +37 -0
  239. package/src/document/processActions.ts +0 -707
  240. package/src/utils/defineIntent.test.ts +0 -477
  241. package/src/utils/defineIntent.ts +0 -244
@@ -0,0 +1,1346 @@
1
+ import { a as defineStore, c as isDatasetResource, i as bindActionGlobally, n as bindActionByResource, o as isReleasePerspective, t as createStateSourceAction } from "./createStateSourceAction-BOKtqFNm.js";
2
+ import { r as omitProperty } from "./object-CxXlSVg7.js";
3
+ import { n as getClient, r as getClientState } from "./clientStore-BtE2-q2i.js";
4
+ import { n as setCleanupTimeout, t as setCleanupInterval } from "./setCleanupTimeout-BiM6q1KF.js";
5
+ import { ConnectionFailedError, CorsOriginError, DisconnectError } from "@sanity/client";
6
+ import { BehaviorSubject, EMPTY, NEVER, Observable, Subscription, catchError, combineLatest, defer, distinctUntilChanged, filter, first, firstValueFrom, groupBy, map, mergeMap, of, pairwise, race, retry, share, startWith, switchMap, take, tap } from "rxjs";
7
+ import "@sanity/types";
8
+ import { DocumentId, getPublishedId } from "@sanity/id-utils";
9
+ import { stringifyPath } from "@sanity/json-match";
10
+ import "groq";
11
+ import { createSelector } from "reselect";
12
+ function insecureRandomId() {
13
+ return Array.from({ length: 16 }, () => Math.floor(Math.random() * 16).toString(16)).join("");
14
+ }
15
+ const definitions = /* @__PURE__ */ new Map(), inflight = /* @__PURE__ */ new Map(), gcTimers = /* @__PURE__ */ new Map(), intervalTimers = /* @__PURE__ */ new Map(), snapshotCache = /* @__PURE__ */ new WeakMap(), PENDING_SNAPSHOT = {
16
+ status: "pending",
17
+ data: void 0,
18
+ error: void 0,
19
+ isFetching: !1,
20
+ dataUpdatedAt: void 0
21
+ }, entryKey = (fetcherName, rawKey) => `${fetcherName}:${rawKey}`, computeTags = (def, data, params) => typeof def.tags == "function" ? def.tags(data, ...params) : def.tags ?? [], isUpdaterFn = (updater) => typeof updater == "function", applyUpdater = (updater, current) => isUpdaterFn(updater) ? updater(current) : updater, needsFetch = (entry, def) => entry.isFetching ? !1 : entry.status !== "success" || entry.forcedStale || Date.now() - (entry.dataUpdatedAt ?? 0) >= (def.staleTime ?? 3e4), asTyped = (entry) => entry, toSnapshot = (entry) => {
22
+ if (!entry) return PENDING_SNAPSHOT;
23
+ let cached = snapshotCache.get(entry);
24
+ if (cached) return cached;
25
+ let snapshot;
26
+ return snapshot = entry.status === "success" ? {
27
+ status: "success",
28
+ data: entry.data,
29
+ error: entry.error,
30
+ isFetching: entry.isFetching,
31
+ dataUpdatedAt: entry.dataUpdatedAt
32
+ } : entry.status === "error" ? {
33
+ status: "error",
34
+ data: void 0,
35
+ error: entry.error,
36
+ isFetching: entry.isFetching,
37
+ dataUpdatedAt: void 0
38
+ } : {
39
+ status: "pending",
40
+ data: void 0,
41
+ error: void 0,
42
+ isFetching: entry.isFetching,
43
+ dataUpdatedAt: void 0
44
+ }, snapshotCache.set(entry, snapshot), snapshot;
45
+ }, cacheStore = defineStore({
46
+ name: "FetcherCache",
47
+ getInitialState: () => ({ entries: {} }),
48
+ initialize: () => () => {
49
+ for (let timer of gcTimers.values()) clearTimeout(timer);
50
+ for (let timer of intervalTimers.values()) clearInterval(timer);
51
+ for (let fetch of inflight.values()) fetch.sub.unsubscribe();
52
+ gcTimers.clear(), intervalTimers.clear(), inflight.clear();
53
+ }
54
+ }), updateEntry = (state, key, actionName, updater) => {
55
+ state.set(actionName, (prev) => {
56
+ let next = updater(prev.entries[key]);
57
+ if (next === prev.entries[key]) return prev;
58
+ let entries = { ...prev.entries };
59
+ return next === void 0 ? delete entries[key] : entries[key] = next, { entries };
60
+ });
61
+ }, baseEntry = (def, key, instance, params) => {
62
+ let seeded = def.initialData?.(instance, ...params);
63
+ return seeded ? {
64
+ fetcherName: def.name,
65
+ key,
66
+ params,
67
+ instance,
68
+ status: "success",
69
+ data: seeded.data,
70
+ error: void 0,
71
+ dataUpdatedAt: seeded.dataUpdatedAt,
72
+ isFetching: !1,
73
+ version: 0,
74
+ forcedStale: !1,
75
+ tags: computeTags(def, seeded.data, params),
76
+ subscriptions: 0
77
+ } : {
78
+ fetcherName: def.name,
79
+ key,
80
+ params,
81
+ instance,
82
+ status: "pending",
83
+ data: void 0,
84
+ error: void 0,
85
+ dataUpdatedAt: void 0,
86
+ isFetching: !1,
87
+ version: 0,
88
+ forcedStale: !1,
89
+ tags: [],
90
+ subscriptions: 0
91
+ };
92
+ }, startFetch = (state, def, instance, params, force) => {
93
+ let key = entryKey(def.name, def.getKey(instance, ...params)), existing = inflight.get(key);
94
+ if (existing && !force) return existing.promise;
95
+ existing && (existing.sub.unsubscribe(), inflight.delete(key)), updateEntry(state, key, "fetchStart", (prev) => ({
96
+ ...prev ?? baseEntry(def, key, instance, params),
97
+ isFetching: !0
98
+ }));
99
+ let startVersion = state.get().entries[key]?.version ?? 0, resolve, reject, promise = new Promise((res, rej) => {
100
+ resolve = res, reject = rej;
101
+ });
102
+ existing && promise.then(existing.resolve, existing.reject);
103
+ let sub = new Subscription();
104
+ return inflight.set(key, {
105
+ sub,
106
+ promise,
107
+ resolve,
108
+ reject,
109
+ startVersion
110
+ }), sub.add(def.fetch(instance)(...params).subscribe({
111
+ next: (data) => {
112
+ let current = state.get().entries[key];
113
+ if (current && current.version !== startVersion) {
114
+ inflight.delete(key), current.isFetching && updateEntry(state, key, "fetchDiscard", (prev) => prev && {
115
+ ...prev,
116
+ isFetching: !1
117
+ }), resolve(current.data), sub.unsubscribe();
118
+ return;
119
+ }
120
+ updateEntry(state, key, "fetchSuccess", (prev) => ({
121
+ ...prev ?? baseEntry(def, key, instance, params),
122
+ status: "success",
123
+ data,
124
+ error: void 0,
125
+ dataUpdatedAt: Date.now(),
126
+ isFetching: !1,
127
+ forcedStale: !1,
128
+ tags: computeTags(def, data, params)
129
+ })), resolve(data);
130
+ },
131
+ error: (error) => {
132
+ updateEntry(state, key, "fetchError", (prev) => prev && (prev.status === "success" ? {
133
+ ...prev,
134
+ error,
135
+ isFetching: !1
136
+ } : {
137
+ ...prev,
138
+ status: "error",
139
+ error,
140
+ isFetching: !1
141
+ })), inflight.delete(key), reject(error);
142
+ },
143
+ complete: () => {
144
+ inflight.delete(key), state.get().entries[key]?.isFetching && updateEntry(state, key, "fetchSettle", (prev) => prev && {
145
+ ...prev,
146
+ isFetching: !1
147
+ });
148
+ }
149
+ })), promise;
150
+ }, detach = (promise) => {
151
+ promise.catch(() => {});
152
+ }, markStale = (state, key) => updateEntry(state, key, "markStale", (prev) => prev && {
153
+ ...prev,
154
+ forcedStale: !0
155
+ }), startInterval = (state, def, instance, params, key) => {
156
+ if (!def.refetchInterval || intervalTimers.has(key)) return;
157
+ let timer = setCleanupInterval(() => {
158
+ if (typeof document < "u" && document.hidden) return;
159
+ let entry = state.get().entries[key];
160
+ entry && entry.subscriptions > 0 && detach(startFetch(state, def, instance, params, !0));
161
+ }, def.refetchInterval);
162
+ intervalTimers.set(key, timer);
163
+ }, scheduleGc = (state, def, key) => {
164
+ if (gcTimers.has(key)) return;
165
+ let entry = state.get().entries[key];
166
+ if (!entry || entry.subscriptions > 0) return;
167
+ let timer = setCleanupTimeout(() => {
168
+ gcTimers.delete(key);
169
+ let fetch = inflight.get(key);
170
+ fetch && (fetch.sub.unsubscribe(), inflight.delete(key)), updateEntry(state, key, "gc", () => void 0);
171
+ }, def.gcTime ?? 3e5);
172
+ gcTimers.set(key, timer);
173
+ }, invalidateByTags = bindActionGlobally(cacheStore, ({ state }, targets) => {
174
+ let refetches = [];
175
+ for (let [key, entry] of Object.entries(state.get().entries)) {
176
+ if (!entry.tags.some((tag) => targets.some((target) => tag.type === target.type && (target.id === void 0 || tag.id === target.id))) || (markStale(state, key), entry.subscriptions <= 0)) continue;
177
+ let def = definitions.get(entry.fetcherName);
178
+ def && refetches.push(startFetch(state, def, entry.instance, entry.params, !0).catch(() => void 0));
179
+ }
180
+ return Promise.all(refetches).then(() => void 0);
181
+ });
182
+ /**
183
+ * Registers a fetcher definition against the shared cache and returns its typed
184
+ * handle.
185
+ *
186
+ * @internal
187
+ */
188
+ function defineFetcher(definition) {
189
+ let def = definition;
190
+ definitions.set(definition.name, def);
191
+ let getState = bindActionGlobally(cacheStore, createStateSourceAction({
192
+ selector: ({ state, instance }, ...params) => {
193
+ let key = entryKey(def.name, def.getKey(instance, ...params)), entry = state.entries[key];
194
+ return toSnapshot(entry && asTyped(entry));
195
+ },
196
+ onSubscribe: ({ state, instance }, ...params) => {
197
+ let key = entryKey(def.name, def.getKey(instance, ...params)), pendingGc = gcTimers.get(key);
198
+ return pendingGc && (clearTimeout(pendingGc), gcTimers.delete(key)), updateEntry(state, key, "subscribe", (prev) => ({
199
+ ...prev ?? baseEntry(def, key, instance, params),
200
+ instance,
201
+ subscriptions: (prev?.subscriptions ?? 0) + 1
202
+ })), startInterval(state, def, instance, params, key), queueMicrotask(() => {
203
+ let entry = state.get().entries[key];
204
+ entry && entry.subscriptions > 0 && needsFetch(entry, def) && detach(startFetch(state, def, instance, params, !1));
205
+ }), () => {
206
+ updateEntry(state, key, "unsubscribe", (prev) => prev && {
207
+ ...prev,
208
+ subscriptions: Math.max(0, prev.subscriptions - 1)
209
+ });
210
+ let entry = state.get().entries[key];
211
+ if (!entry || entry.subscriptions > 0) return;
212
+ let interval = intervalTimers.get(key);
213
+ interval && (clearInterval(interval), intervalTimers.delete(key)), scheduleGc(state, def, key);
214
+ };
215
+ }
216
+ })), resolveState = (instance, ...params) => firstValueFrom(getState(instance, ...params).observable.pipe(filter((snapshot) => snapshot.status !== "pending"), map((snapshot) => {
217
+ if (snapshot.status === "error") throw snapshot.error;
218
+ return snapshot.data;
219
+ })));
220
+ return {
221
+ getState,
222
+ resolveState,
223
+ invalidate: bindActionGlobally(cacheStore, ({ state, instance }, ...params) => {
224
+ let key = entryKey(def.name, def.getKey(instance, ...params));
225
+ markStale(state, key);
226
+ let entry = state.get().entries[key];
227
+ entry && entry.subscriptions > 0 && detach(startFetch(state, def, instance, params, !0));
228
+ }),
229
+ invalidateAll: bindActionGlobally(cacheStore, ({ state }) => {
230
+ let prefix = `${def.name}:`;
231
+ for (let [key, entry] of Object.entries(state.get().entries)) key.startsWith(prefix) && (markStale(state, key), entry.subscriptions > 0 && detach(startFetch(state, def, entry.instance, entry.params, !0)));
232
+ }),
233
+ refetch: bindActionGlobally(cacheStore, ({ state, instance }, ...params) => {
234
+ let key = entryKey(def.name, def.getKey(instance, ...params)), promise = startFetch(state, def, instance, params, !0);
235
+ return scheduleGc(state, def, key), promise;
236
+ }),
237
+ setData: bindActionGlobally(cacheStore, ({ state, instance }, params, updater) => {
238
+ let key = entryKey(def.name, def.getKey(instance, ...params)), previous;
239
+ return updateEntry(state, key, "setData", (prev) => {
240
+ previous = prev;
241
+ let from = asTyped(prev ?? baseEntry(def, key, instance, params)), nextData = applyUpdater(updater, from.data);
242
+ return {
243
+ ...from,
244
+ status: "success",
245
+ data: nextData,
246
+ dataUpdatedAt: from.dataUpdatedAt ?? Date.now(),
247
+ version: from.version + 1,
248
+ tags: computeTags(def, nextData, params)
249
+ };
250
+ }), scheduleGc(state, def, key), { undo: () => updateEntry(state, key, "undoSetData", () => previous) };
251
+ })
252
+ };
253
+ }
254
+ /**
255
+ * Creates a bound mutation from its definition. The returned action resolves at
256
+ * server settlement — not after invalidation refetches, which run as background
257
+ * reconciliation (see {@link MutationResult.invalidated}).
258
+ *
259
+ * @internal
260
+ */
261
+ function defineMutation(definition) {
262
+ return (instance, input) => {
263
+ let undos = [], write = (fetcher, params, updater) => {
264
+ undos.push(fetcher.setData(instance, params, updater).undo);
265
+ };
266
+ return definition.onMutate?.(write, input), firstValueFrom(definition.mutationFn(instance)(input)).then((result) => {
267
+ definition.onSuccess?.(write, result, input);
268
+ let targets = typeof definition.invalidates == "function" ? definition.invalidates(result, input) : definition.invalidates ?? [];
269
+ return {
270
+ data: result,
271
+ invalidated: targets.length ? invalidateByTags(instance, targets) : Promise.resolve()
272
+ };
273
+ }, (error) => {
274
+ for (let i = undos.length - 1; i >= 0; i--) undos[i]();
275
+ throw error;
276
+ });
277
+ };
278
+ }
279
+ /** Data API version used for reading and writing comments in the addon dataset. */
280
+ const COMMENTS_API_VERSION = "v2025-05-06", ADDON_DATASET_API_VERSION = "v2025-02-19", COMMENTS_STATE_CLEAR_DELAY = 5e3, LISTEN_OPTIONS = {
281
+ events: [
282
+ "welcome",
283
+ "mutation",
284
+ "reconnect"
285
+ ],
286
+ includeResult: !0,
287
+ includeAllVersions: !0,
288
+ visibility: "query",
289
+ tag: "comments.listen"
290
+ }, BASE_FILTERS = ["_type == \"comment\"", "target.document._ref == $documentId"];
291
+ /**
292
+ * Which comments belong to a document, matching the Studio's filter.
293
+ *
294
+ * A release's comments are kept apart from the default ones: asking for a
295
+ * release returns only that release's comments, and asking without one returns
296
+ * only comments with no version at all.
297
+ */
298
+ function buildCommentsFilter(documentVersionId) {
299
+ return [...BASE_FILTERS, documentVersionId ? "target.documentVersionId == $documentVersionId" : "!defined(target.documentVersionId)"].join(" && ");
300
+ }
301
+ /** The snapshot query. Newest comment first, as the Studio orders it. */
302
+ function buildCommentsQuery(documentVersionId) {
303
+ return `*[${buildCommentsFilter(documentVersionId)}] {
304
+ _createdAt,
305
+ _id,
306
+ _rev,
307
+ authorId,
308
+ contentSnapshot,
309
+ context,
310
+ lastEditedAt,
311
+ message,
312
+ parentCommentId,
313
+ reactions,
314
+ status,
315
+ target,
316
+ threadId
317
+ } | order(_createdAt desc)`;
318
+ }
319
+ /**
320
+ * The listener query. Unprojected on purpose: mutation events carry the whole
321
+ * comment, so there is nothing to trim.
322
+ */
323
+ function buildCommentsListenQuery(documentVersionId) {
324
+ return `*[${buildCommentsFilter(documentVersionId)}]`;
325
+ }
326
+ /**
327
+ * Comments need a project and a dataset. There is nowhere to put them for a
328
+ * media library or a canvas, and no Studio to interoperate with either.
329
+ *
330
+ * @internal
331
+ */
332
+ function assertDatasetResource(resource) {
333
+ if (!isDatasetResource(resource)) throw Error(`Comments are only supported for dataset resources, received: ${JSON.stringify(resource)}`);
334
+ return resource;
335
+ }
336
+ function isMissingOrForbidden(error) {
337
+ if (typeof error != "object" || !error || !("statusCode" in error)) return !1;
338
+ let { statusCode } = error;
339
+ return statusCode === 403 || statusCode === 404;
340
+ }
341
+ function requestAddonDataset(client, { projectId, dataset }) {
342
+ return client.observable.request({
343
+ uri: `/projects/${projectId}/datasets?datasetProfile=comments&addonFor=${dataset}`,
344
+ tag: "comments.addon-dataset.list"
345
+ }).pipe(map((datasets) => datasets?.[0]?.name));
346
+ }
347
+ function discover(instance, resource) {
348
+ return getClientState(instance, {
349
+ apiVersion: ADDON_DATASET_API_VERSION,
350
+ projectId: resource.projectId,
351
+ useProjectHostname: !0
352
+ }).observable.pipe(switchMap((client) => requestAddonDataset(client, resource).pipe(catchError((error) => isMissingOrForbidden(error) ? of(void 0) : EMPTY))));
353
+ }
354
+ async function discoverOnce(instance, resource) {
355
+ let client = await firstValueFrom(getClientState(instance, {
356
+ apiVersion: ADDON_DATASET_API_VERSION,
357
+ projectId: resource.projectId,
358
+ useProjectHostname: !0
359
+ }).observable.pipe(take(1)));
360
+ try {
361
+ return await firstValueFrom(requestAddonDataset(client, resource));
362
+ } catch (error) {
363
+ if (isMissingOrForbidden(error)) return;
364
+ throw error;
365
+ }
366
+ }
367
+ const addonDatasetStore = defineStore({
368
+ name: "CommentsAddonDataset",
369
+ getInitialState: () => ({ status: "unknown" }),
370
+ initialize: ({ instance, state, key }) => {
371
+ let subscription = discover(instance, assertDatasetResource(key.resource)).subscribe((datasetName) => state.set("setAddonDataset", datasetName ? {
372
+ status: "resolved",
373
+ datasetName
374
+ } : { status: "missing" }));
375
+ return () => subscription.unsubscribe();
376
+ }
377
+ }), getAddonDatasetState = bindActionByResource(addonDatasetStore, createStateSourceAction(({ state }) => {
378
+ if (state.status !== "unknown") return state.datasetName ?? null;
379
+ })), provisionAddonDataset = bindActionByResource(addonDatasetStore, ({ instance, state, key }) => {
380
+ let current = state.get();
381
+ if (current.datasetName) return Promise.resolve(current.datasetName);
382
+ if (current.provisioning) return current.provisioning;
383
+ let resource = assertDatasetResource(key.resource), provisioning = (async () => {
384
+ let existing = await discoverOnce(instance, resource);
385
+ if (existing) return existing;
386
+ let response = await getClient(instance, {
387
+ apiVersion: ADDON_DATASET_API_VERSION,
388
+ projectId: resource.projectId,
389
+ useProjectHostname: !0
390
+ }).request({
391
+ uri: `/comments/${resource.dataset}/setup`,
392
+ method: "POST",
393
+ tag: "comments.addon-dataset.setup"
394
+ });
395
+ if (!response?.datasetName) throw Error("Creating the comments addon dataset returned no dataset name.");
396
+ return response.datasetName;
397
+ })().then((datasetName) => (state.set("setAddonDataset", {
398
+ status: "resolved",
399
+ datasetName,
400
+ provisioning: void 0
401
+ }), datasetName)).catch((error) => {
402
+ throw state.set("clearProvisioning", { provisioning: void 0 }), error;
403
+ });
404
+ return state.set("startProvisioning", { provisioning }), provisioning;
405
+ }), observeAddonDatasetClient = bindActionByResource(addonDatasetStore, ({ instance, key }) => {
406
+ let { projectId } = assertDatasetResource(key.resource);
407
+ return getAddonDatasetState(instance, { resource: key.resource }).observable.pipe(distinctUntilChanged(), filter((datasetName) => datasetName !== void 0), switchMap((datasetName) => typeof datasetName == "string" ? getClientState(instance, {
408
+ apiVersion: COMMENTS_API_VERSION,
409
+ projectId,
410
+ dataset: datasetName
411
+ }).observable : of(null)));
412
+ }), RESERVED_SEGMENT = /\.(true|false|null)(?=$|[.[])/g, QUOTED_OR_PLAIN = /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|[^"']+/g;
413
+ function quoteReservedFieldSegments(path) {
414
+ return path.replace(QUOTED_OR_PLAIN, (chunk) => chunk.startsWith("\"") || chunk.startsWith("'") ? chunk : chunk.replace(RESERVED_SEGMENT, "[\"$1\"]"));
415
+ }
416
+ /**
417
+ * Normalises a field path to the string form comments are stored in.
418
+ *
419
+ * Comments keep `target.path.field` as a string rather than a path array,
420
+ * because that is what the Studio writes and reading it back has to match
421
+ * exactly. A string given here is parsed and re-stringified, so both a path
422
+ * array and a hand-written string come out in the same canonical form.
423
+ *
424
+ * `undefined` and `[]` both produce `''`. Callers writing a comment reject that
425
+ * rather than passing it on, since a comment has to point at a field.
426
+ *
427
+ * @example
428
+ * ```ts
429
+ * toCommentFieldPath(['body', {_key: 'intro'}, 'content'])
430
+ * // 'body[_key=="intro"].content'
431
+ * toCommentFieldPath(undefined)
432
+ * // ''
433
+ * ```
434
+ *
435
+ * @internal
436
+ */
437
+ function toCommentFieldPath(path) {
438
+ return quoteReservedFieldSegments(stringifyPath(path));
439
+ }
440
+ /**
441
+ * Groups a flat comment list into threads.
442
+ *
443
+ * A thread's parent is the comment with no `parentCommentId`; replies point at
444
+ * the parent's `id`. Replies whose parent is missing from the input are
445
+ * dropped, which is what happens when a parent is deleted but its replies have
446
+ * not disappeared yet.
447
+ *
448
+ * Threads come back in the order their parents appear in `comments`, so passing
449
+ * the store's newest-first list gives newest-thread-first. Replies within a
450
+ * thread are always oldest first, since that is reading order.
451
+ *
452
+ * @internal
453
+ */
454
+ function buildCommentThreads(comments) {
455
+ let repliesByParent = /* @__PURE__ */ new Map();
456
+ for (let comment of comments) {
457
+ if (!comment.parentCommentId) continue;
458
+ let existing = repliesByParent.get(comment.parentCommentId);
459
+ existing ? existing.push(comment) : repliesByParent.set(comment.parentCommentId, [comment]);
460
+ }
461
+ let threads = [];
462
+ for (let parentComment of comments) {
463
+ if (parentComment.parentCommentId) continue;
464
+ let replies = (repliesByParent.get(parentComment.id) ?? []).sort((a, b) => a.createdAt.localeCompare(b.createdAt)), lastReply = replies[replies.length - 1];
465
+ threads.push({
466
+ threadId: parentComment.threadId,
467
+ fieldPath: parentComment.fieldPath,
468
+ parentComment,
469
+ replies,
470
+ commentsCount: replies.length + 1,
471
+ status: parentComment.status,
472
+ lastActivityAt: lastReply ? lastReply.createdAt : parentComment.createdAt
473
+ });
474
+ }
475
+ return threads;
476
+ }
477
+ /**
478
+ * Turns a stored comment into the shape consumers see.
479
+ *
480
+ * This is the whole seam between the storage format and the public API. When
481
+ * comments move to the organization-level store, the three fields that differ
482
+ * are handled here and nothing downstream changes:
483
+ *
484
+ * - the author moves from `authorId` to `_system.createdBy`
485
+ * - `target.document` becomes a global document reference, so `documentId` is
486
+ * parsed out of a `resourceType:resourceId:documentId` string rather than
487
+ * read off `_ref`
488
+ * - `_type` goes from `comment` to `sanity.comment`, and stops being exposed
489
+ *
490
+ * @internal
491
+ */
492
+ function normalizeComment(stored) {
493
+ let { target } = stored;
494
+ return {
495
+ id: stored._id,
496
+ createdAt: stored._createdAt,
497
+ ...stored.authorId ? { authorId: stored.authorId } : {},
498
+ message: stored.message,
499
+ threadId: stored.threadId,
500
+ ...stored.parentCommentId ? { parentCommentId: stored.parentCommentId } : {},
501
+ status: stored.status,
502
+ ...stored.lastEditedAt ? { lastEditedAt: stored.lastEditedAt } : {},
503
+ documentId: target.document._ref,
504
+ documentType: target.documentType,
505
+ fieldPath: target.path?.field ?? "",
506
+ ...target.path?.selection ? { selection: target.path.selection } : {},
507
+ ...stored.contentSnapshot === void 0 ? {} : { contentSnapshot: stored.contentSnapshot },
508
+ reactions: (stored.reactions ?? []).map(({ shortName, userId, addedAt }) => ({
509
+ shortName,
510
+ userId,
511
+ addedAt
512
+ })),
513
+ ...stored._state ? { state: stored._state } : {}
514
+ };
515
+ }
516
+ function toCommentsEvent(event) {
517
+ if (event.type === "mutation") {
518
+ if (event.transition === "disappear") return {
519
+ type: "disappear",
520
+ commentId: event.documentId
521
+ };
522
+ if (event.result) return event.transition === "appear" ? {
523
+ type: "appear",
524
+ comment: event.result
525
+ } : {
526
+ type: "update",
527
+ comment: event.result,
528
+ transactionId: event.transactionId
529
+ };
530
+ }
531
+ }
532
+ /**
533
+ * Watches one document's comments: a snapshot on connect, then live changes.
534
+ *
535
+ * The Studio awaits its snapshot inside the event handler, which loses any
536
+ * mutation that arrives while the fetch is in flight, because the older
537
+ * snapshot overwrites it. Here the mutations that arrive during a fetch are
538
+ * held and replayed straight after the snapshot, so nothing is dropped.
539
+ *
540
+ * A reconnect produces a fresh `welcome`, which cancels any fetch still running
541
+ * and starts a new one. Reconnects emit nothing themselves: dropping the list
542
+ * while reconnecting would only make readers suspend again for data we still
543
+ * have.
544
+ *
545
+ * @internal
546
+ */
547
+ function observeComments(options) {
548
+ let { client, documentId, documentVersionId } = options, params = {
549
+ documentId,
550
+ ...documentVersionId ? { documentVersionId } : {}
551
+ }, events$ = client.observable.listen(buildCommentsListenQuery(documentVersionId), params, LISTEN_OPTIONS).pipe(share()), mutations$ = events$.pipe(map(toCommentsEvent), filter((event) => event !== void 0));
552
+ return events$.pipe(filter((event) => event.type === "welcome"), switchMap(() => new Observable((observer) => {
553
+ let buffered = [], snapshotReceived = !1, mutationSubscription = mutations$.subscribe({
554
+ next: (event) => {
555
+ snapshotReceived ? observer.next(event) : buffered.push(event);
556
+ },
557
+ error: (error) => observer.error(error)
558
+ }), snapshotSubscription = client.observable.fetch(buildCommentsQuery(documentVersionId), params, { tag: "comments.list" }).pipe(take(1)).subscribe({
559
+ next: (comments) => {
560
+ observer.next({
561
+ type: "snapshot",
562
+ comments
563
+ }), snapshotReceived = !0;
564
+ for (let event of buffered) observer.next(event);
565
+ buffered.length = 0;
566
+ },
567
+ error: (error) => {
568
+ observer.next({
569
+ type: "error",
570
+ error
571
+ }), observer.complete();
572
+ }
573
+ });
574
+ return () => {
575
+ mutationSubscription.unsubscribe(), snapshotSubscription.unsubscribe(), buffered.length = 0;
576
+ };
577
+ })));
578
+ }
579
+ function getCommentsKey({ documentId, documentVersionId }) {
580
+ return JSON.stringify([documentId, documentVersionId ?? null]);
581
+ }
582
+ function parseCommentsKey(key) {
583
+ let [documentId, documentVersionId] = JSON.parse(key);
584
+ return {
585
+ documentId,
586
+ ...documentVersionId ? { documentVersionId } : {}
587
+ };
588
+ }
589
+ const addSubscriber$1 = (key, subscriptionId) => (prev) => {
590
+ let entry = prev.entries[key], subscribers = [...entry?.subscribers ?? [], subscriptionId];
591
+ return {
592
+ ...prev,
593
+ entries: {
594
+ ...prev.entries,
595
+ [key]: {
596
+ ...entry,
597
+ subscribers
598
+ }
599
+ }
600
+ };
601
+ }, removeSubscriber$1 = (key, subscriptionId) => (prev) => {
602
+ let entry = prev.entries[key];
603
+ if (!entry) return prev;
604
+ let subscribers = entry.subscribers.filter((id) => id !== subscriptionId);
605
+ if (!subscribers.length) {
606
+ let pendingCreates = { ...prev.pendingCreates }, pendingTransactions = { ...prev.pendingTransactions };
607
+ for (let commentId of Object.keys(entry.comments ?? {})) delete pendingCreates[commentId], delete pendingTransactions[commentId];
608
+ return {
609
+ ...prev,
610
+ entries: omitProperty(prev.entries, key),
611
+ pendingCreates,
612
+ pendingTransactions
613
+ };
614
+ }
615
+ return {
616
+ ...prev,
617
+ entries: {
618
+ ...prev.entries,
619
+ [key]: {
620
+ ...entry,
621
+ subscribers
622
+ }
623
+ }
624
+ };
625
+ }, setComments = (key, comments) => (prev) => {
626
+ let entry = prev.entries[key];
627
+ if (!entry) return prev;
628
+ let byId = Object.fromEntries(comments.map((comment) => [comment._id, comment])), pendingCreates = { ...prev.pendingCreates };
629
+ for (let [commentId, localComment] of Object.entries(entry.comments ?? {})) Object.hasOwn(byId, commentId) ? delete pendingCreates[commentId] : (Object.hasOwn(pendingCreates, commentId) || localComment._state) && (byId[commentId] = localComment);
630
+ return {
631
+ ...prev,
632
+ entries: {
633
+ ...prev.entries,
634
+ [key]: {
635
+ ...entry,
636
+ comments: byId,
637
+ error: void 0
638
+ }
639
+ },
640
+ pendingCreates
641
+ };
642
+ }, setCommentsError = (key, error) => (prev) => {
643
+ let entry = prev.entries[key];
644
+ return entry ? {
645
+ ...prev,
646
+ entries: {
647
+ ...prev.entries,
648
+ [key]: {
649
+ ...entry,
650
+ error
651
+ }
652
+ }
653
+ } : prev;
654
+ }, receiveComment = (key, comment) => (prev) => {
655
+ let entry = prev.entries[key], pendingCreates = omitProperty(prev.pendingCreates, comment._id);
656
+ return entry ? {
657
+ ...prev,
658
+ pendingCreates,
659
+ entries: {
660
+ ...prev.entries,
661
+ [key]: {
662
+ ...entry,
663
+ comments: {
664
+ ...entry.comments,
665
+ [comment._id]: comment
666
+ }
667
+ }
668
+ }
669
+ } : {
670
+ ...prev,
671
+ pendingCreates
672
+ };
673
+ };
674
+ /** A create that already failed once is being retried, not written fresh. */
675
+ function isRetryingCreate(existing) {
676
+ let state = existing?._state?.type;
677
+ return state === "createError" || state === "createRetrying";
678
+ }
679
+ function mergeOptimisticComment(existing, comment) {
680
+ return {
681
+ ...existing,
682
+ ...comment,
683
+ _createdAt: comment._createdAt ?? existing?._createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
684
+ _rev: comment._rev ?? existing?._rev ?? "",
685
+ ...isRetryingCreate(existing) ? { _state: { type: "createRetrying" } } : {}
686
+ };
687
+ }
688
+ const addComment = (key, comment) => (prev) => {
689
+ let entry = prev.entries[key];
690
+ if (!entry) return prev;
691
+ let existing = entry.comments && Object.hasOwn(entry.comments, comment._id) ? entry.comments[comment._id] : void 0;
692
+ return {
693
+ ...prev,
694
+ pendingCreates: {
695
+ ...prev.pendingCreates,
696
+ [comment._id]: !0
697
+ },
698
+ entries: {
699
+ ...prev.entries,
700
+ [key]: {
701
+ ...entry,
702
+ comments: {
703
+ ...entry.comments,
704
+ [comment._id]: mergeOptimisticComment(existing, comment)
705
+ }
706
+ }
707
+ }
708
+ };
709
+ }, applyCommentUpdate = (commentId, patch) => (prev) => {
710
+ let entries = { ...prev.entries }, changed = !1;
711
+ for (let [key, entry] of Object.entries(prev.entries)) {
712
+ let comment = entry?.comments?.[commentId];
713
+ !entry || !comment || (changed = !0, entries[key] = {
714
+ ...entry,
715
+ comments: {
716
+ ...entry.comments,
717
+ [commentId]: {
718
+ ...comment,
719
+ ...patch
720
+ }
721
+ }
722
+ });
723
+ }
724
+ return changed ? {
725
+ ...prev,
726
+ entries
727
+ } : prev;
728
+ }, removeCommentById = (commentId) => (prev) => {
729
+ let entries = { ...prev.entries }, removedIds = /* @__PURE__ */ new Set(), changed = !1;
730
+ for (let [key, entry] of Object.entries(prev.entries)) {
731
+ if (!entry?.comments) continue;
732
+ let remaining = Object.fromEntries(Object.entries(entry.comments).filter(([id, comment]) => {
733
+ let keep = id !== commentId && comment.parentCommentId !== commentId;
734
+ return keep || removedIds.add(id), keep;
735
+ }));
736
+ Object.keys(remaining).length !== Object.keys(entry.comments).length && (changed = !0, entries[key] = {
737
+ ...entry,
738
+ comments: remaining
739
+ });
740
+ }
741
+ if (!changed) return prev;
742
+ let pendingCreates = { ...prev.pendingCreates }, pendingTransactions = { ...prev.pendingTransactions };
743
+ for (let id of removedIds) delete pendingCreates[id], delete pendingTransactions[id];
744
+ return {
745
+ ...prev,
746
+ entries,
747
+ pendingCreates,
748
+ pendingTransactions
749
+ };
750
+ }, setPendingTransaction = (commentId, transactionId) => (prev) => ({
751
+ ...prev,
752
+ pendingTransactions: {
753
+ ...prev.pendingTransactions,
754
+ [commentId]: transactionId
755
+ }
756
+ }), clearPendingTransaction = (commentId) => (prev) => ({
757
+ ...prev,
758
+ pendingTransactions: omitProperty(prev.pendingTransactions, commentId)
759
+ }), setCommentCreateError = (commentId, error) => (prev) => {
760
+ if (!Object.hasOwn(prev.pendingCreates, commentId)) return prev;
761
+ let next = applyCommentUpdate(commentId, { _state: {
762
+ type: "createError",
763
+ error
764
+ } })(prev);
765
+ return {
766
+ ...next,
767
+ pendingCreates: omitProperty(next.pendingCreates, commentId)
768
+ };
769
+ }, rollbackCommentUpdate = (commentId, transactionId, previous) => (prev) => {
770
+ if (!Object.hasOwn(prev.pendingTransactions, commentId) || prev.pendingTransactions[commentId] !== transactionId) return prev;
771
+ let entries = Object.fromEntries(Object.entries(prev.entries).map(([key, entry]) => entry?.comments?.[commentId] ? [key, {
772
+ ...entry,
773
+ comments: {
774
+ ...entry.comments,
775
+ [commentId]: previous
776
+ }
777
+ }] : [key, entry]));
778
+ return {
779
+ ...prev,
780
+ entries,
781
+ pendingTransactions: omitProperty(prev.pendingTransactions, commentId)
782
+ };
783
+ }, restoreComments = (removed) => (prev) => {
784
+ let entries = { ...prev.entries }, changed = !1;
785
+ for (let { key, comments } of removed) {
786
+ let entry = entries[key];
787
+ if (!entry) continue;
788
+ let missing = comments.filter((comment) => !Object.hasOwn(entry.comments ?? {}, comment._id));
789
+ missing.length && (changed = !0, entries[key] = {
790
+ ...entry,
791
+ comments: Object.fromEntries([...Object.entries(entry.comments ?? {}), ...missing.map((comment) => [comment._id, comment])])
792
+ });
793
+ }
794
+ return changed ? {
795
+ ...prev,
796
+ entries
797
+ } : prev;
798
+ };
799
+ /**
800
+ * A stable string standing for one set of read options.
801
+ *
802
+ * Only React needs this: it holds one state source steady across renders and
803
+ * defers swapping to a new one while the previous list is still on screen.
804
+ * `fieldPath` is normalised on the way in, so a path array and the equivalent
805
+ * string address the same list.
806
+ *
807
+ * @internal
808
+ */
809
+ function getCommentsOptionsKey(options) {
810
+ return JSON.stringify({
811
+ documentId: options.documentId,
812
+ documentType: options.documentType,
813
+ projectId: options.projectId,
814
+ dataset: options.dataset,
815
+ resource: options.resource,
816
+ perspective: options.perspective,
817
+ fieldPath: options.fieldPath === void 0 ? void 0 : toCommentFieldPath(options.fieldPath),
818
+ status: options.status
819
+ });
820
+ }
821
+ /** @internal */
822
+ function parseCommentsOptionsKey(key) {
823
+ return JSON.parse(key);
824
+ }
825
+ /**
826
+ * Which comment list an option set addresses.
827
+ *
828
+ * Comments hang off the published id, so a draft and its published document
829
+ * share one list. A release keeps its own.
830
+ */
831
+ function toCommentsKeyParts(instance, options) {
832
+ let perspective = options.perspective ?? instance.config.perspective;
833
+ return {
834
+ documentId: getPublishedId(DocumentId(options.documentId)),
835
+ ...isReleasePerspective(perspective) ? { documentVersionId: perspective.releaseName } : {}
836
+ };
837
+ }
838
+ function applyEvent(state, key, event) {
839
+ switch (event.type) {
840
+ case "snapshot":
841
+ state.set("setComments", setComments(key, event.comments));
842
+ return;
843
+ case "appear":
844
+ state.set("receiveComment", receiveComment(key, event.comment));
845
+ return;
846
+ case "disappear":
847
+ state.set("removeComment", removeCommentById(event.commentId));
848
+ return;
849
+ case "error":
850
+ state.set("setCommentsError", setCommentsError(key, event.error));
851
+ return;
852
+ case "update": {
853
+ let pending = state.get().pendingTransactions[event.comment._id];
854
+ if (pending && pending !== event.transactionId) return;
855
+ state.set("receiveComment", receiveComment(key, event.comment)), pending && state.set("clearPendingTransaction", clearPendingTransaction(event.comment._id));
856
+ }
857
+ }
858
+ }
859
+ const watchSubscribedDocuments = ({ state, instance, key }) => {
860
+ let client$ = observeAddonDatasetClient(instance, { resource: key.resource });
861
+ return state.observable.pipe(map((current) => new Set(Object.keys(current.entries))), distinctUntilChanged((a, b) => a.size === b.size && Array.from(b).every((entry) => a.has(entry))), startWith(/* @__PURE__ */ new Set()), pairwise(), mergeMap(([previous, current]) => [...Array.from(current).filter((entry) => !previous.has(entry)).map((entry) => ({
862
+ key: entry,
863
+ added: !0
864
+ })), ...Array.from(previous).filter((entry) => !current.has(entry)).map((entry) => ({
865
+ key: entry,
866
+ added: !1
867
+ }))]), groupBy((event) => event.key), mergeMap((group$) => group$.pipe(switchMap((event) => {
868
+ if (!event.added) return EMPTY;
869
+ let { documentId, documentVersionId } = parseCommentsKey(group$.key);
870
+ return client$.pipe(switchMap((client) => client ? observeComments({
871
+ client,
872
+ documentId,
873
+ documentVersionId
874
+ }).pipe(tap((commentsEvent) => applyEvent(state, group$.key, commentsEvent)), catchError((error) => (state.set("setCommentsError", setCommentsError(group$.key, error)), EMPTY))) : (state.set("setComments", setComments(group$.key, [])), EMPTY)));
875
+ })))).subscribe({ error: (error) => state.set("setError", { error }) });
876
+ }, commentsStore = defineStore({
877
+ name: "Comments",
878
+ getInitialState: () => ({
879
+ entries: {},
880
+ pendingCreates: {},
881
+ pendingTransactions: {}
882
+ }),
883
+ initialize: (context) => {
884
+ let subscription = watchSubscribedDocuments(context);
885
+ return () => subscription.unsubscribe();
886
+ }
887
+ }), normalizedCache = /* @__PURE__ */ new WeakMap(), filteredCache = /* @__PURE__ */ new WeakMap(), threadCache = /* @__PURE__ */ new WeakMap();
888
+ /**
889
+ * The stored map turned into what consumers see, newest first.
890
+ *
891
+ * Cached alongside the filters below rather than done per read, so the
892
+ * normalised objects keep their identity for as long as the stored map does.
893
+ */
894
+ function normalizeAll(commentsById) {
895
+ let cached = normalizedCache.get(commentsById);
896
+ if (cached) return cached;
897
+ let normalized = Object.values(commentsById).map(normalizeComment).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
898
+ return normalizedCache.set(commentsById, normalized), normalized;
899
+ }
900
+ function filterComments(all, fieldPath, status) {
901
+ let byFilter = filteredCache.get(all);
902
+ byFilter || (byFilter = /* @__PURE__ */ new Map(), filteredCache.set(all, byFilter));
903
+ let cacheKey = `${fieldPath ?? "\0"}|${status ?? ""}`, cached = byFilter.get(cacheKey);
904
+ if (cached) return cached;
905
+ let filtered = all.filter((comment) => status && comment.status !== status ? !1 : fieldPath === void 0 || comment.fieldPath === fieldPath);
906
+ return byFilter.set(cacheKey, filtered), filtered;
907
+ }
908
+ function selectComments({ state, instance }, options) {
909
+ if (state.error) throw state.error;
910
+ let entry = state.entries[getCommentsKey(toCommentsKeyParts(instance, options))];
911
+ if (entry?.error) throw entry.error;
912
+ if (entry?.comments) return filterComments(normalizeAll(entry.comments), options.fieldPath === void 0 ? void 0 : toCommentFieldPath(options.fieldPath), options.status);
913
+ }
914
+ function selectCommentThreads(context, options) {
915
+ let comments = selectComments(context, {
916
+ ...options,
917
+ fieldPath: void 0,
918
+ status: void 0
919
+ });
920
+ if (comments === void 0) return;
921
+ let byFilter = threadCache.get(comments);
922
+ byFilter || (byFilter = /* @__PURE__ */ new Map(), threadCache.set(comments, byFilter));
923
+ let fieldPath = options.fieldPath === void 0 ? void 0 : toCommentFieldPath(options.fieldPath), cacheKey = `${fieldPath ?? "\0"}|${options.status ?? ""}`, cached = byFilter.get(cacheKey);
924
+ if (cached) return cached;
925
+ let threads = buildCommentThreads(comments).filter((thread) => options.status && thread.parentComment.status !== options.status ? !1 : fieldPath === void 0 || thread.fieldPath === fieldPath);
926
+ return byFilter.set(cacheKey, threads), threads;
927
+ }
928
+ /**
929
+ * Every comment on a document, newest first.
930
+ *
931
+ * `undefined` until the first snapshot arrives. Replies are included; use
932
+ * {@link getCommentThreadsState} to read them grouped.
933
+ *
934
+ * @beta
935
+ */
936
+ const getCommentsState = bindActionByResource(commentsStore, createStateSourceAction({
937
+ selector: selectComments,
938
+ onSubscribe: ({ state, instance }, options) => {
939
+ let key = getCommentsKey(toCommentsKeyParts(instance, options)), subscriptionId = insecureRandomId();
940
+ return state.set("addSubscriber", addSubscriber$1(key, subscriptionId)), () => {
941
+ setCleanupTimeout(() => state.set("removeSubscriber", removeSubscriber$1(key, subscriptionId)), COMMENTS_STATE_CLEAR_DELAY);
942
+ };
943
+ }
944
+ })), getCommentThreadsState = bindActionByResource(commentsStore, createStateSourceAction({
945
+ selector: selectCommentThreads,
946
+ onSubscribe: ({ state, instance }, options) => {
947
+ let key = getCommentsKey(toCommentsKeyParts(instance, options)), subscriptionId = insecureRandomId();
948
+ return state.set("addSubscriber", addSubscriber$1(key, subscriptionId)), () => {
949
+ setCleanupTimeout(() => state.set("removeSubscriber", removeSubscriber$1(key, subscriptionId)), COMMENTS_STATE_CLEAR_DELAY);
950
+ };
951
+ }
952
+ })), resolveComments = bindActionByResource(commentsStore, ({ state, instance }, { signal, ...options }) => resolveList(state, instance, options, signal, getCommentsState)), resolveCommentThreads = bindActionByResource(commentsStore, ({ state, instance }, { signal, ...options }) => resolveList(state, instance, options, signal, getCommentThreadsState));
953
+ function resolveList(state, instance, options, signal, getState) {
954
+ let key = getCommentsKey(toCommentsKeyParts(instance, options)), { getCurrent } = getState(instance, options), subscriptionId = insecureRandomId();
955
+ state.set("addSubscriber", addSubscriber$1(key, subscriptionId));
956
+ let release = () => state.set("removeSubscriber", removeSubscriber$1(key, subscriptionId)), aborted$ = signal ? new Observable((observer) => {
957
+ let listener = () => {
958
+ release(), observer.error(new DOMException("The operation was aborted.", "AbortError"));
959
+ };
960
+ return signal.addEventListener("abort", listener), () => signal.removeEventListener("abort", listener);
961
+ }) : NEVER, resolved$ = state.observable.pipe(map(() => getCurrent()), first((value) => value !== void 0)), promise = firstValueFrom(race([resolved$, aborted$])), releaseLater = () => setCleanupTimeout(release, COMMENTS_STATE_CLEAR_DELAY);
962
+ return promise.then(releaseLater, releaseLater), promise;
963
+ }
964
+ const PROJECT_API_VERSION = "2025-07-18", USERS_STATE_CLEAR_DELAY = 5e3, getUsersKey = (instance, { resourceType, organizationId, batchSize = 100, projectId = instance.config.projectId, userId } = {}) => JSON.stringify({
965
+ resourceType,
966
+ organizationId,
967
+ batchSize,
968
+ projectId,
969
+ userId
970
+ }), parseUsersKey = (key) => JSON.parse(key), addSubscription = (subscriptionId, key) => (prev) => {
971
+ let group = prev.users[key], subscriptions = [...group?.subscriptions ?? [], subscriptionId];
972
+ return {
973
+ ...prev,
974
+ users: {
975
+ ...prev.users,
976
+ [key]: {
977
+ ...group,
978
+ subscriptions
979
+ }
980
+ }
981
+ };
982
+ }, removeSubscription = (subscriptionId, key) => (prev) => {
983
+ let group = prev.users[key];
984
+ if (!group) return prev;
985
+ let subscriptions = group.subscriptions.filter((id) => id !== subscriptionId);
986
+ return subscriptions.length ? {
987
+ ...prev,
988
+ users: {
989
+ ...prev.users,
990
+ [key]: {
991
+ ...group,
992
+ subscriptions
993
+ }
994
+ }
995
+ } : {
996
+ ...prev,
997
+ users: omitProperty(prev.users, key)
998
+ };
999
+ }, setUsersData = (key, { data, nextCursor, totalCount }) => (prev) => {
1000
+ let group = prev.users[key];
1001
+ if (!group) return prev;
1002
+ let users = [...group.users ?? [], ...data];
1003
+ return {
1004
+ ...prev,
1005
+ users: {
1006
+ ...prev.users,
1007
+ [key]: {
1008
+ ...group,
1009
+ users,
1010
+ totalCount,
1011
+ nextCursor
1012
+ }
1013
+ }
1014
+ };
1015
+ }, updateLastLoadMoreRequest = (timestamp, key) => (prev) => {
1016
+ let group = prev.users[key];
1017
+ return group ? {
1018
+ ...prev,
1019
+ users: {
1020
+ ...prev.users,
1021
+ [key]: {
1022
+ ...group,
1023
+ lastLoadMoreRequest: timestamp
1024
+ }
1025
+ }
1026
+ } : prev;
1027
+ }, setUsersError = (key, error) => (prev) => {
1028
+ let group = prev.users[key];
1029
+ return group ? {
1030
+ ...prev,
1031
+ users: {
1032
+ ...prev.users,
1033
+ [key]: {
1034
+ ...group,
1035
+ error
1036
+ }
1037
+ }
1038
+ } : prev;
1039
+ }, cancelRequest = (key) => (prev) => {
1040
+ let group = prev.users[key];
1041
+ return !group || group.subscriptions.length ? prev : {
1042
+ ...prev,
1043
+ users: omitProperty(prev.users, key)
1044
+ };
1045
+ }, initializeRequest = (key) => (prev) => prev.users[key] ? prev : {
1046
+ ...prev,
1047
+ users: {
1048
+ ...prev.users,
1049
+ [key]: { subscriptions: [] }
1050
+ }
1051
+ };
1052
+ /**
1053
+ * Emits Live Content API messages for a dataset so consumers can invalidate
1054
+ * fetched data via sync tags.
1055
+ *
1056
+ * All consumers observe the same underlying EventSource connection:
1057
+ * `@sanity/client` caches the live-events stream per URL and connection
1058
+ * options, and every subscription made through this function uses the same
1059
+ * API version and request tag.
1060
+ *
1061
+ * @internal
1062
+ */
1063
+ function observeLiveEvents(instance, { resource, onCorsError }) {
1064
+ return getClientState(instance, {
1065
+ apiVersion: "v2025-05-06",
1066
+ resource
1067
+ }).observable.pipe(switchMap((client) => defer(() => client.live.events({
1068
+ includeDrafts: !!client.config().token,
1069
+ tag: "live-events"
1070
+ })).pipe(catchError((error) => {
1071
+ if (error instanceof CorsOriginError) return onCorsError(error), EMPTY;
1072
+ if (error instanceof DisconnectError || error instanceof ConnectionFailedError && typeof error.status == "number" && error.status >= 400 && error.status < 500) return EMPTY;
1073
+ throw error;
1074
+ }), retry({ delay: 1e3 }))), filter((e) => e.type === "message"));
1075
+ }
1076
+ /**
1077
+ * Emits every release document (including archived and published), refetching
1078
+ * whenever a Live Content API event matches the query's sync tags.
1079
+ *
1080
+ * This fetches directly through the client store — deliberately not via the
1081
+ * query store — because release data is itself an input to query perspective
1082
+ * resolution (`getPerspectiveState`); fetching through the query store would
1083
+ * make the two stores mutually dependent.
1084
+ *
1085
+ * @internal
1086
+ */
1087
+ function observeReleases(instance, { resource, onCorsError }) {
1088
+ return getClientState(instance, {
1089
+ apiVersion: "v2025-05-06",
1090
+ resource
1091
+ }).observable.pipe(switchMap((client) => {
1092
+ let syncTags$ = new BehaviorSubject(void 0);
1093
+ return combineLatest([observeLiveEvents(instance, {
1094
+ resource,
1095
+ onCorsError
1096
+ }).pipe(startWith(void 0)), syncTags$]).pipe(filter(([message, syncTags]) => message === void 0 || message.tags.some((tag) => syncTags?.includes(tag))), map(([message]) => message?.id), distinctUntilChanged()).pipe(switchMap((lastLiveEventId) => client.observable.fetch("releases::all()", {}, {
1097
+ perspective: "raw",
1098
+ filterResponse: !1,
1099
+ returnQuery: !1,
1100
+ lastLiveEventId,
1101
+ tag: "releases"
1102
+ })), tap((response) => {
1103
+ syncTags$.next(response.syncTags);
1104
+ }), map((response) => response.result));
1105
+ }), startWith(void 0));
1106
+ }
1107
+ function sortReleases(releases = []) {
1108
+ return [...releases].sort((a, b) => {
1109
+ let aType = a.metadata?.releaseType, bType = b.metadata?.releaseType;
1110
+ if (aType === "undecided" && bType !== "undecided") return -1;
1111
+ if (aType !== "undecided" && bType === "undecided") return 1;
1112
+ if (aType === "undecided" && bType === "undecided") return new Date(b._createdAt).getTime() - new Date(a._createdAt).getTime();
1113
+ if (aType === "scheduled" && bType === "scheduled") {
1114
+ let aPublishAt = a.publishAt || a.metadata?.intendedPublishAt;
1115
+ if (!aPublishAt) return 1;
1116
+ let bPublishAt = b.publishAt || b.metadata?.intendedPublishAt;
1117
+ return bPublishAt ? new Date(bPublishAt).getTime() - new Date(aPublishAt).getTime() : -1;
1118
+ }
1119
+ return aType === "asap" && bType !== "asap" ? 1 : aType !== "asap" && bType === "asap" ? -1 : aType === "asap" && bType === "asap" ? new Date(b._createdAt).getTime() - new Date(a._createdAt).getTime() : 0;
1120
+ });
1121
+ }
1122
+ const ARCHIVED_RELEASE_STATES = ["archived", "published"], STABLE_EMPTY_RELEASES = [], releasesStore = defineStore({
1123
+ name: "Releases",
1124
+ getInitialState: () => ({
1125
+ activeReleases: void 0,
1126
+ allReleases: void 0
1127
+ }),
1128
+ initialize: (context) => {
1129
+ let subscription = subscribeToReleases(context);
1130
+ return () => subscription.unsubscribe();
1131
+ }
1132
+ }), _getActiveReleasesState = bindActionByResource(releasesStore, createStateSourceAction({ selector: ({ state }) => {
1133
+ if (state.error) throw state.error;
1134
+ return state.activeReleases;
1135
+ } })), getActiveReleasesState = (instance, options) => _getActiveReleasesState(instance, options ?? {}), _getAllReleasesState = bindActionByResource(releasesStore, createStateSourceAction({ selector: ({ state }) => {
1136
+ if (state.error) throw state.error;
1137
+ return state.allReleases;
1138
+ } })), getAllReleasesState = (instance, options) => _getAllReleasesState(instance, options ?? {}), subscribeToReleases = ({ instance, state, key: { resource } }) => observeReleases(instance, {
1139
+ resource,
1140
+ onCorsError: (error) => state.set("setError", { error })
1141
+ }).pipe(map((releases) => {
1142
+ let sorted = sortReleases(releases ?? STABLE_EMPTY_RELEASES).reverse();
1143
+ state.set("setReleases", {
1144
+ allReleases: sorted,
1145
+ activeReleases: sorted.filter((release) => !ARCHIVED_RELEASE_STATES.includes(release.state))
1146
+ });
1147
+ })).subscribe({ error: (error) => state.set("setError", { error }) }), optionsCache = /* @__PURE__ */ new Map(), selectInstancePerspective = (context, _) => context.instance.config.perspective, selectActiveReleases = (context) => context.state.activeReleases, selectOptions = (_context, options) => options, memoizedOptionsSelector = createSelector([selectActiveReleases, selectOptions], (activeReleases, options) => {
1148
+ if (!options || !activeReleases) return options;
1149
+ let releaseIds = activeReleases.map((release) => release._id).join(","), nestedCache = optionsCache.get(releaseIds);
1150
+ nestedCache || (nestedCache = /* @__PURE__ */ new Map(), optionsCache.set(releaseIds, nestedCache));
1151
+ let optionsKey = JSON.stringify(options), cachedOptions = nestedCache.get(optionsKey);
1152
+ return cachedOptions || (cachedOptions = options, nestedCache.set(optionsKey, cachedOptions)), cachedOptions;
1153
+ }), _getPerspectiveStateSelector = createStateSourceAction({ selector: createSelector([
1154
+ selectInstancePerspective,
1155
+ selectActiveReleases,
1156
+ memoizedOptionsSelector
1157
+ ], (instancePerspective, activeReleases, memoizedOptions) => {
1158
+ let perspective = memoizedOptions?.perspective ?? instancePerspective ?? "drafts";
1159
+ if (!isReleasePerspective(perspective)) return perspective;
1160
+ if (!activeReleases || activeReleases.length === 0) return;
1161
+ let releaseNames = sortReleases(activeReleases).map((release) => release.name), index = releaseNames.findIndex((name) => name === perspective.releaseName);
1162
+ if (index < 0) throw Error(`Release "${perspective.releaseName}" not found in active releases`);
1163
+ return ["drafts", ...releaseNames.slice(0, index + 1)].filter((name) => !perspective.excludedPerspectives?.includes(name)).reverse();
1164
+ }) }), _boundGetPerspectiveState = bindActionByResource(releasesStore, _getPerspectiveStateSelector), getPerspectiveState = (instance, ...rest) => _boundGetPerspectiveState(instance, ...rest.length ? rest : [{}]), QUERY_STATE_CLEAR_DELAY = 1e3, setQueryError = (key, error) => (prev) => {
1165
+ let prevQuery = prev.queries[key];
1166
+ return prevQuery ? {
1167
+ ...prev,
1168
+ queries: {
1169
+ ...prev.queries,
1170
+ [key]: {
1171
+ ...prevQuery,
1172
+ error
1173
+ }
1174
+ }
1175
+ } : prev;
1176
+ }, setQueryData = (key, result, syncTags) => (prev) => {
1177
+ let prevQuery = prev.queries[key];
1178
+ return prevQuery ? {
1179
+ ...prev,
1180
+ queries: {
1181
+ ...prev.queries,
1182
+ [key]: {
1183
+ ...prevQuery,
1184
+ result: result ?? null,
1185
+ syncTags
1186
+ }
1187
+ }
1188
+ } : prev;
1189
+ }, setLastLiveEventId = (key, lastLiveEventId) => (prev) => {
1190
+ let prevQuery = prev.queries[key];
1191
+ return prevQuery ? {
1192
+ ...prev,
1193
+ queries: {
1194
+ ...prev.queries,
1195
+ [key]: {
1196
+ ...prevQuery,
1197
+ lastLiveEventId
1198
+ }
1199
+ }
1200
+ } : prev;
1201
+ }, addSubscriber = (key, subscriptionId) => (prev) => {
1202
+ let prevQuery = prev.queries[key], subscribers = [...prevQuery?.subscribers ?? [], subscriptionId];
1203
+ return {
1204
+ ...prev,
1205
+ queries: {
1206
+ ...prev.queries,
1207
+ [key]: {
1208
+ ...prevQuery,
1209
+ subscribers
1210
+ }
1211
+ }
1212
+ };
1213
+ }, removeSubscriber = (key, subscriptionId) => (prev) => {
1214
+ let prevQuery = prev.queries[key];
1215
+ if (!prevQuery) return prev;
1216
+ let subscribers = prevQuery.subscribers.filter((id) => id !== subscriptionId);
1217
+ return subscribers.length ? {
1218
+ ...prev,
1219
+ queries: {
1220
+ ...prev.queries,
1221
+ [key]: {
1222
+ ...prevQuery,
1223
+ subscribers
1224
+ }
1225
+ }
1226
+ } : {
1227
+ ...prev,
1228
+ queries: omitProperty(prev.queries, key)
1229
+ };
1230
+ }, EMPTY_ARRAY = [], getQueryKey = (instance, options) => JSON.stringify(normalizeOptionsWithPerspective(instance, options)), parseQueryKey = (key) => JSON.parse(key);
1231
+ /**
1232
+ * Ensures the query key includes an effective perspective so that
1233
+ * implicit differences (e.g. different instance.config.perspective)
1234
+ * don't collide in the dataset-scoped store.
1235
+ *
1236
+ * Since perspectives are unique, we can depend on the release stacks
1237
+ * to be correct when we retrieve the results.
1238
+ *
1239
+ */
1240
+ function normalizeOptionsWithPerspective(instance, options) {
1241
+ if (options.perspective !== void 0) return options;
1242
+ let instancePerspective = instance.config.perspective;
1243
+ return {
1244
+ ...options,
1245
+ perspective: instancePerspective === void 0 ? "drafts" : instancePerspective
1246
+ };
1247
+ }
1248
+ const queryStore = defineStore({
1249
+ name: "QueryStore",
1250
+ getInitialState: () => ({ queries: {} }),
1251
+ initialize(context) {
1252
+ let subscriptions = [listenForNewSubscribersAndFetch(context), listenToLiveClientAndSetLastLiveEventIds(context)];
1253
+ return () => {
1254
+ for (let subscription of subscriptions) subscription.unsubscribe();
1255
+ };
1256
+ }
1257
+ }), errorHandler = (state) => (error) => state.set("setError", { error }), listenForNewSubscribersAndFetch = ({ state, instance }) => state.observable.pipe(map((s) => new Set(Object.keys(s.queries))), distinctUntilChanged((curr, next) => curr.size === next.size && Array.from(next).every((i) => curr.has(i))), startWith(/* @__PURE__ */ new Set()), pairwise(), mergeMap(([curr, next]) => {
1258
+ let added = Array.from(next).filter((i) => !curr.has(i)), removed = Array.from(curr).filter((i) => !next.has(i));
1259
+ return [...added.map((key) => ({
1260
+ key,
1261
+ added: !0
1262
+ })), ...removed.map((key) => ({
1263
+ key,
1264
+ added: !1
1265
+ }))];
1266
+ }), groupBy((i) => i.key), mergeMap((group$) => group$.pipe(switchMap((e) => {
1267
+ if (!e.added) return EMPTY;
1268
+ let lastLiveEventId$ = state.observable.pipe(map((s) => s.queries[group$.key]?.lastLiveEventId), distinctUntilChanged()), { query, params, projectId, dataset, tag, resource, perspective: perspectiveFromOptions, ...restOptions } = parseQueryKey(group$.key), perspective$ = isReleasePerspective(perspectiveFromOptions) ? getPerspectiveState(instance, {
1269
+ perspective: perspectiveFromOptions,
1270
+ resource
1271
+ }).observable.pipe(filter(Boolean)) : of(perspectiveFromOptions ?? "drafts"), client$ = getClientState(instance, {
1272
+ apiVersion: "v2025-05-06",
1273
+ projectId,
1274
+ dataset,
1275
+ resource
1276
+ }).observable;
1277
+ return combineLatest({
1278
+ lastLiveEventId: lastLiveEventId$,
1279
+ client: client$,
1280
+ perspective: perspective$
1281
+ }).pipe(switchMap(({ lastLiveEventId, client, perspective }) => client.observable.fetch(query, params, {
1282
+ ...restOptions,
1283
+ perspective,
1284
+ filterResponse: !1,
1285
+ returnQuery: !1,
1286
+ lastLiveEventId,
1287
+ tag
1288
+ })), tap(({ result, syncTags }) => {
1289
+ state.set("setQueryData", setQueryData(group$.key, result, syncTags));
1290
+ }), catchError((error) => (state.set("setQueryError", setQueryError(group$.key, error)), EMPTY)));
1291
+ })))).subscribe({ error: errorHandler(state) }), listenToLiveClientAndSetLastLiveEventIds = ({ state, instance, key: { resource } }) => {
1292
+ let liveMessages$ = observeLiveEvents(instance, {
1293
+ resource,
1294
+ onCorsError: (error) => state.set("setError", { error })
1295
+ }).pipe(share());
1296
+ return state.observable.pipe(mergeMap((s) => Object.entries(s.queries)), groupBy(([key]) => key), mergeMap((group$) => {
1297
+ let syncTags$ = group$.pipe(map(([, queryState]) => queryState), map((i) => i?.syncTags ?? EMPTY_ARRAY), distinctUntilChanged());
1298
+ return combineLatest([liveMessages$, syncTags$]).pipe(filter(([message, syncTags]) => message.tags.some((tag) => syncTags.includes(tag))), tap(([message]) => {
1299
+ state.set("setLastLiveEventId", setLastLiveEventId(group$.key, message.id));
1300
+ }));
1301
+ })).subscribe({ error: errorHandler(state) });
1302
+ };
1303
+ /** @beta */
1304
+ function getQueryState(...args) {
1305
+ return _getQueryState(...args);
1306
+ }
1307
+ const _getQueryState = bindActionByResource(queryStore, createStateSourceAction({
1308
+ selector: ({ state, instance }, options) => {
1309
+ if (state.error) throw state.error;
1310
+ let key = getQueryKey(instance, options), queryState = state.queries[key];
1311
+ if (queryState?.error) throw queryState.error;
1312
+ return queryState?.result;
1313
+ },
1314
+ onSubscribe: ({ state, instance }, options) => {
1315
+ let subscriptionId = insecureRandomId(), key = getQueryKey(instance, options);
1316
+ return state.set("addSubscriber", addSubscriber(key, subscriptionId)), () => {
1317
+ setCleanupTimeout(() => state.set("removeSubscriber", removeSubscriber(key, subscriptionId)), QUERY_STATE_CLEAR_DELAY);
1318
+ };
1319
+ }
1320
+ }));
1321
+ /** @beta */
1322
+ function resolveQuery(...args) {
1323
+ return _resolveQuery(...args);
1324
+ }
1325
+ const _resolveQuery = bindActionByResource(queryStore, ({ state, instance }, { signal, ...options }) => {
1326
+ let normalized = normalizeOptionsWithPerspective(instance, options), { getCurrent } = getQueryState(instance, normalized), key = getQueryKey(instance, normalized), subscriptionId = insecureRandomId();
1327
+ state.set("addSubscriber", addSubscriber(key, subscriptionId));
1328
+ let aborted$ = signal ? new Observable((observer) => {
1329
+ let cleanup = () => {
1330
+ signal.removeEventListener("abort", listener);
1331
+ }, listener = () => {
1332
+ observer.error(new DOMException("The operation was aborted.", "AbortError")), observer.complete(), cleanup();
1333
+ };
1334
+ return signal.addEventListener("abort", listener), cleanup;
1335
+ }).pipe(catchError((error) => {
1336
+ throw error instanceof Error && error.name === "AbortError" && state.set("removeSubscriber", removeSubscriber(key, subscriptionId)), error;
1337
+ })) : NEVER, resolved$ = state.observable.pipe(map(getCurrent), first((i) => i !== void 0)), promise = firstValueFrom(race([resolved$, aborted$])), releaseSubscriber = () => {
1338
+ setCleanupTimeout(() => {
1339
+ state.set("removeSubscriber", removeSubscriber(key, subscriptionId));
1340
+ }, QUERY_STATE_CLEAR_DELAY);
1341
+ };
1342
+ return promise.then(releaseSubscriber, releaseSubscriber), promise;
1343
+ });
1344
+ export { getCommentsKey as A, getAddonDatasetState as B, parseCommentsOptionsKey as C, addComment as D, toCommentsKeyParts as E, setCommentCreateError as F, insecureRandomId as G, COMMENTS_API_VERSION as H, setPendingTransaction as I, normalizeComment as L, removeCommentById as M, restoreComments as N, applyCommentUpdate as O, rollbackCommentUpdate as P, toCommentFieldPath as R, getCommentsState as S, resolveComments as T, defineFetcher as U, provisionAddonDataset as V, defineMutation as W, PROJECT_API_VERSION as _, getPerspectiveState as a, getCommentThreadsState as b, addSubscription as c, initializeRequest as d, parseUsersKey as f, updateLastLoadMoreRequest as g, setUsersError as h, resolveQuery as i, receiveComment as j, clearPendingTransaction as k, cancelRequest as l, setUsersData as m, getQueryState as n, getActiveReleasesState as o, removeSubscription as p, parseQueryKey as r, getAllReleasesState as s, getQueryKey as t, getUsersKey as u, USERS_STATE_CLEAR_DELAY as v, resolveCommentThreads as w, getCommentsOptionsKey as x, commentsStore as y, assertDatasetResource as z };
1345
+
1346
+ //# sourceMappingURL=queryStore-DPf9sN3V.js.map