@sanity/sdk 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/_exports/_internal.d.ts +72 -19
  2. package/dist/_exports/_internal.d.ts.map +1 -1
  3. package/dist/_exports/_internal.js +130 -11
  4. package/dist/_exports/_internal.js.map +1 -1
  5. package/dist/_exports/agent.d.ts +7 -7
  6. package/dist/_exports/agent.d.ts.map +1 -1
  7. package/dist/_exports/agent.js +1 -1
  8. package/dist/_exports/comlink.d.ts +9 -9
  9. package/dist/_exports/comlink.d.ts.map +1 -1
  10. package/dist/_exports/comlink.js +2 -2
  11. package/dist/_exports/dashboard.d.ts +2 -0
  12. package/dist/_exports/dashboard.js +2 -0
  13. package/dist/{authStore-C0o1NOxW.d.ts → authStore-BO2vch3I.d.ts} +516 -63
  14. package/dist/authStore-BO2vch3I.d.ts.map +1 -0
  15. package/dist/{authStore-CtE9bLHW.js → clientStore-BI4D8HhJ.js} +390 -15
  16. package/dist/clientStore-BI4D8HhJ.js.map +1 -0
  17. package/dist/{createSanityInstance-DUI1bGIg.d.ts → createSanityInstance-BQIqyGLX.d.ts} +17 -2
  18. package/dist/createSanityInstance-BQIqyGLX.d.ts.map +1 -0
  19. package/dist/{createStateSourceAction-BOKtqFNm.js → createStateSourceAction-DdAhEfnZ.js} +7 -3
  20. package/dist/createStateSourceAction-DdAhEfnZ.js.map +1 -0
  21. package/dist/defineProperty-Bho9Ngsi.js +31 -0
  22. package/dist/{getNodeState-CR3__B2D.js → getNodeState-B4nG0sjm.js} +2 -2
  23. package/dist/{getNodeState-CR3__B2D.js.map → getNodeState-B4nG0sjm.js.map} +1 -1
  24. package/dist/index.d.ts +3 -3
  25. package/dist/index.js +370 -90
  26. package/dist/index.js.map +1 -1
  27. package/dist/topicStore-Ct7IFNtY.js +565 -0
  28. package/dist/topicStore-Ct7IFNtY.js.map +1 -0
  29. package/dist/topicStore-D0WqhQGF.d.ts +435 -0
  30. package/dist/topicStore-D0WqhQGF.d.ts.map +1 -0
  31. package/dist/{queryStore-C7yMHPUI.js → version-BcRmCiYi.js} +48 -16
  32. package/dist/version-BcRmCiYi.js.map +1 -0
  33. package/package.json +19 -18
  34. package/src/_exports/_internal.ts +13 -0
  35. package/src/_exports/dashboard.ts +36 -0
  36. package/src/_exports/index.ts +17 -1
  37. package/src/access/checkPermissions.test.ts +1 -1
  38. package/src/access/checkPermissions.ts +1 -1
  39. package/src/applications/applications.test-d.ts +28 -4
  40. package/src/applications/applications.test.ts +4 -4
  41. package/src/applications/applications.ts +43 -18
  42. package/src/auth/authMode.test.ts +24 -0
  43. package/src/auth/authMode.ts +8 -3
  44. package/src/auth/authStore.ts +12 -0
  45. package/src/auth/authStrategy.ts +2 -0
  46. package/src/auth/handleAuthCallback.test.ts +2 -2
  47. package/src/auth/handleAuthCallback.ts +1 -1
  48. package/src/auth/logout.test.ts +46 -1
  49. package/src/auth/logout.ts +2 -1
  50. package/src/auth/oauth/oauthActions.test.ts +457 -0
  51. package/src/auth/oauth/oauthActions.ts +389 -0
  52. package/src/auth/oauth/oauthAuth.test.ts +206 -0
  53. package/src/auth/oauth/oauthAuth.ts +168 -0
  54. package/src/auth/oauth/pkce.test.ts +44 -0
  55. package/src/auth/oauth/pkce.ts +60 -0
  56. package/src/auth/oauth/types.ts +12 -0
  57. package/src/auth/refreshStampedToken.ts +1 -1
  58. package/src/auth/studioModeAuth.test.ts +1 -1
  59. package/src/auth/studioModeAuth.ts +1 -1
  60. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +2 -2
  61. package/src/auth/subscribeToStateAndFetchCurrentUser.ts +1 -1
  62. package/src/comments/addonDatasetStore.test.ts +2 -2
  63. package/src/comments/addonDatasetStore.ts +2 -2
  64. package/src/comments/commentActions.ts +6 -5
  65. package/src/comments/commentsStore.ts +4 -4
  66. package/src/config/authConfig.ts +18 -0
  67. package/src/dashboard/messageBus/__fixtures__/test-topics.ts +18 -0
  68. package/src/dashboard/messageBus/bus.test.ts +1033 -0
  69. package/src/dashboard/messageBus/bus.ts +1246 -0
  70. package/src/dashboard/messageBus/store.test.ts +142 -0
  71. package/src/dashboard/messageBus/store.ts +74 -0
  72. package/src/dashboard/messageBus/topicStore.test.ts +172 -0
  73. package/src/dashboard/messageBus/topicStore.ts +184 -0
  74. package/src/dashboard/messageBus/topics.test.ts +71 -0
  75. package/src/dashboard/messageBus/topics.ts +260 -0
  76. package/src/document/actions.ts +3 -3
  77. package/src/document/applyDocumentActions.ts +5 -9
  78. package/src/document/documentStore.concurrency.test.ts +2 -1
  79. package/src/document/documentStore.test.ts +19 -18
  80. package/src/document/documentStore.ts +17 -17
  81. package/src/document/permissions.ts +2 -1
  82. package/src/document/processActions.test.ts +2 -1
  83. package/src/document/processMutations.ts +61 -24
  84. package/src/document/reducers.ts +2 -2
  85. package/src/installations/installations.test-d.ts +3 -3
  86. package/src/installations/installations.test.ts +2 -2
  87. package/src/installations/installations.ts +4 -21
  88. package/src/organization/organization.test.ts +2 -2
  89. package/src/organization/organization.ts +1 -1
  90. package/src/organizations/organizations.test.ts +2 -2
  91. package/src/organizations/organizations.ts +1 -1
  92. package/src/presence/presenceStore.test.ts +15 -22
  93. package/src/presence/presenceStore.ts +3 -2
  94. package/src/project/project.test.ts +3 -3
  95. package/src/project/project.ts +1 -1
  96. package/src/projection/getProjectionState.test.ts +6 -4
  97. package/src/projection/getProjectionState.ts +4 -4
  98. package/src/projection/resolveProjection.ts +2 -2
  99. package/src/projects/projects.test.ts +2 -2
  100. package/src/projects/projects.ts +1 -1
  101. package/src/query/queryStore.ts +6 -6
  102. package/src/store/createSanityInstance.test.ts +1 -1
  103. package/src/store/createSanityInstance.ts +4 -4
  104. package/src/store/createStoreInstance.test.ts +0 -5
  105. package/src/telemetry/telemetryManager.test.ts +1 -1
  106. package/src/telemetry/telemetryManager.ts +2 -2
  107. package/src/typegen/defineProjection.test.ts +17 -0
  108. package/src/typegen/defineProjection.ts +19 -0
  109. package/src/typegen/groqCompat.ts +122 -0
  110. package/src/typegen/resolve.ts +37 -0
  111. package/src/users/usersStore.test.ts +1 -1
  112. package/src/users/usersStore.ts +5 -5
  113. package/src/utils/createGroqSearchFilter.test.ts +8 -0
  114. package/src/utils/createGroqSearchFilter.ts +6 -3
  115. package/src/utils/ids.test.ts +40 -15
  116. package/src/utils/ids.ts +47 -2
  117. package/dist/authStore-C0o1NOxW.d.ts.map +0 -1
  118. package/dist/authStore-CtE9bLHW.js.map +0 -1
  119. package/dist/clientStore-BtE2-q2i.js +0 -90
  120. package/dist/clientStore-BtE2-q2i.js.map +0 -1
  121. package/dist/createSanityInstance-DUI1bGIg.d.ts.map +0 -1
  122. package/dist/createStateSourceAction-BOKtqFNm.js.map +0 -1
  123. package/dist/getEnv-D5RGkQLu.js +0 -8
  124. package/dist/getEnv-D5RGkQLu.js.map +0 -1
  125. package/dist/logger-DAtJb5RQ.js +0 -173
  126. package/dist/logger-DAtJb5RQ.js.map +0 -1
  127. package/dist/queryStore-C7yMHPUI.js.map +0 -1
  128. package/dist/rolldown-runtime-vyAXikos.js +0 -9
  129. package/dist/telemetryManager-UIEiMtC0.js +0 -127
  130. package/dist/telemetryManager-UIEiMtC0.js.map +0 -1
  131. package/dist/version-D8RP_Q59.js +0 -9
  132. package/dist/version-D8RP_Q59.js.map +0 -1
@@ -0,0 +1,565 @@
1
+ import { a as createActionBinder, o as defineStore, t as createStateSourceAction } from "./createStateSourceAction-DdAhEfnZ.js";
2
+ import { n as setCleanupTimeout } from "./setCleanupTimeout-BiM6q1KF.js";
3
+ import { t as _defineProperty } from "./defineProperty-Bho9Ngsi.js";
4
+ import { BehaviorSubject, ReplaySubject, Subject, concat, defer, filter, firstValueFrom, from, map, takeUntil } from "rxjs";
5
+ import "@sanity/types";
6
+ const stateTopic = (seed) => ({
7
+ kind: "state",
8
+ seed
9
+ }), dashboardEvent = {
10
+ kind: "event",
11
+ ownership: { type: "same_app" }
12
+ }, DASHBOARD_TOPIC_MANIFEST = {
13
+ "applications.config": stateTopic(void 0),
14
+ "applications.foreground": stateTopic(void 0),
15
+ "applications.list": stateTopic(void 0),
16
+ "auth.token": stateTopic(void 0),
17
+ "auth.token.refresh": dashboardEvent,
18
+ "navigation.location": stateTopic(void 0),
19
+ "navigation.location.update": dashboardEvent,
20
+ "organizations.current": stateTopic(void 0),
21
+ "panels.mode": stateTopic({
22
+ ok: !0,
23
+ value: null
24
+ }),
25
+ "panels.mode.set": dashboardEvent,
26
+ "preferences.color-scheme": stateTopic(void 0),
27
+ "preferences.dock-locked": stateTopic(void 0),
28
+ "users.current": stateTopic(void 0)
29
+ }, topicMigrations = {};
30
+ /**
31
+ * An error raised by the message bus protocol.
32
+ * @public
33
+ */
34
+ var MessageBusError = class extends Error {
35
+ /** Creates a message bus protocol error. */
36
+ constructor(code, message, options) {
37
+ super(message ?? code, options), _defineProperty(this, "code", void 0), this.name = "MessageBusError", this.code = code;
38
+ }
39
+ };
40
+ const MESSAGE_BUS_KEY = Symbol.for("sanity.os.bus"), MESSAGE_BUS_PROTOCOL_KEY = Symbol.for("sanity.os.protocol"), MESSAGE_BUS_REGISTRY_KEY = Symbol.for("sanity.os.registry"), MESSAGE_BUS_PENDING_REPLY_KEY = Symbol.for("sanity.os.request"), DEFAULT_TIMEOUT_MS = 5e3, NO_VALUE = Symbol.for("sanity.os.no-value");
41
+ function resolveStateSubject(registry, connection, type) {
42
+ if (connection.abort.signal.aborted) throw new MessageBusError("ABORTED");
43
+ let subject = connection.stateSubjects.get(type);
44
+ if (!subject) {
45
+ let topic = registry.topics.get(type);
46
+ topic || console.warn(`[sanity-sdk:message-bus] state topic "${type}" is not declared`);
47
+ let seed = topic?.kind === "state" ? topic.seed : void 0;
48
+ subject = new BehaviorSubject(seed === void 0 ? NO_VALUE : seed), connection.stateSubjects.set(type, subject);
49
+ }
50
+ return subject;
51
+ }
52
+ function toStateSource(values, getCurrent) {
53
+ let source = values;
54
+ source.getCurrent = getCurrent;
55
+ let firstValue;
56
+ return Object.defineProperty(source, "firstValue", { get: () => firstValue ??= firstValueFrom(values) }), source;
57
+ }
58
+ function assertCompatibleTopicManifest(registry, manifest) {
59
+ for (let [type, entry] of Object.entries(manifest)) {
60
+ let existing = registry.topics.get(type);
61
+ if (existing && existing.kind !== entry.kind) throw new MessageBusError("PROTOCOL_MISMATCH", `topic "${type}" is declared "${entry.kind}" but the installed bus knows it as "${existing.kind}"`);
62
+ if (existing?.kind === "event" && entry.kind === "event" && existing.ownership.type !== entry.ownership.type) throw new MessageBusError("OWNERSHIP_MISMATCH", `topic "${type}" has conflicting ownership`);
63
+ }
64
+ }
65
+ function mergeTopicManifest(registry, manifest) {
66
+ assertCompatibleTopicManifest(registry, manifest);
67
+ for (let [type, entry] of Object.entries(manifest)) registry.topics.set(type, entry);
68
+ }
69
+ function resolveEventSubject(registry, type) {
70
+ let subject = registry.eventSubjects.get(type);
71
+ return subject || (subject = new Subject(), registry.eventSubjects.set(type, subject)), subject;
72
+ }
73
+ function settleReply(reply, outcome) {
74
+ reply.settled || (reply.settled = !0, reply.settlePromise ? reply.settlePromise(outcome) : reply.outcomeBeforeAwait = outcome);
75
+ }
76
+ function createEventMessage(appId, moduleId, type, payload, pendingReply) {
77
+ let message = {
78
+ type,
79
+ payload,
80
+ meta: {
81
+ appId,
82
+ moduleId,
83
+ timestamp: Date.now()
84
+ },
85
+ reply: (value) => {
86
+ if (pendingReply.settled) {
87
+ console.warn(`[sanity-sdk:message-bus] reply ignored for "${type}": no waiting caller or already replied`);
88
+ return;
89
+ }
90
+ settleReply(pendingReply, {
91
+ ok: !0,
92
+ value
93
+ });
94
+ },
95
+ get signal() {
96
+ return pendingReply.responderSignal;
97
+ }
98
+ };
99
+ return message[MESSAGE_BUS_PENDING_REPLY_KEY] = pendingReply, message;
100
+ }
101
+ function createReplyPromise(pendingReply, options) {
102
+ return new Promise((resolve, reject) => {
103
+ let settlePromise = (outcome) => outcome.ok ? resolve(outcome.value) : reject(outcome.error);
104
+ if (pendingReply.outcomeBeforeAwait) {
105
+ pendingReply.responderAbort.abort(), settlePromise(pendingReply.outcomeBeforeAwait);
106
+ return;
107
+ }
108
+ if (!options.hadResponderAtEmission) {
109
+ reject(new MessageBusError("NO_RESPONDER"));
110
+ return;
111
+ }
112
+ let timeoutMs = options.timeout === void 0 ? DEFAULT_TIMEOUT_MS : options.timeout, timer, onAbort = () => settleReply(pendingReply, {
113
+ ok: !1,
114
+ error: new MessageBusError("ABORTED")
115
+ });
116
+ pendingReply.settlePromise = (outcome) => {
117
+ timer !== void 0 && clearTimeout(timer), options.signal?.removeEventListener("abort", onAbort), pendingReply.responderAbort.abort(), settlePromise(outcome);
118
+ }, timeoutMs !== null && (timer = setTimeout(() => settleReply(pendingReply, {
119
+ ok: !1,
120
+ error: new MessageBusError("TIMEOUT")
121
+ }), timeoutMs)), options.signal && (options.signal.aborted ? onAbort() : options.signal.addEventListener("abort", onAbort, { once: !0 }));
122
+ });
123
+ }
124
+ function createLazyReply(awaitReply) {
125
+ return {
126
+ then: (onFulfilled, onRejected) => awaitReply().then(onFulfilled, onRejected),
127
+ catch: (onRejected) => awaitReply().then(void 0, onRejected),
128
+ finally: (onFinally) => awaitReply().finally(onFinally)
129
+ };
130
+ }
131
+ function emitEvent(registry, type, payload, options, appId, moduleId) {
132
+ let hadResponderAtEmission = (registry.responderCounts.get(type) ?? 0) > 0, responderAbort = new AbortController(), pendingReply = {
133
+ responderAbort,
134
+ responderSignal: scopeSignal(options?.signal, responderAbort.signal),
135
+ settled: !1
136
+ };
137
+ resolveEventSubject(registry, type).next(createEventMessage(appId, moduleId, type, payload, pendingReply));
138
+ let awaitReply = () => pendingReply.replyPromise ??= createReplyPromise(pendingReply, {
139
+ ...options,
140
+ hadResponderAtEmission
141
+ });
142
+ return createLazyReply(awaitReply);
143
+ }
144
+ const isStateTopic = (registry, type) => registry.topics.get(type)?.kind === "state", canRespond = (registry, ownership, appId) => ownership.type === "any_app" || appId === registry.appId;
145
+ function emit(registry, type, payload, options, appId, moduleId, connectionSignal) {
146
+ if (isStateTopic(registry, type)) throw new MessageBusError("OWNERSHIP_MISMATCH", `Cannot emit state topic "${type}" from app "${appId}". The host writes state to each connection through its client handle. Read it with query() or subscribe().`);
147
+ return emitEvent(registry, type, payload, {
148
+ ...options,
149
+ signal: scopeSignal(options?.signal, registry.resetAbort.signal, connectionSignal)
150
+ }, appId, moduleId);
151
+ }
152
+ function emitState(registry, connection, type, value) {
153
+ let subject = resolveStateSubject(registry, connection, type);
154
+ Object.is(subject.getValue(), value) || subject.next(value);
155
+ }
156
+ function query(source, type, options, connectionSignal, resetSignal) {
157
+ let current = source.getCurrent();
158
+ if (current !== void 0) return Promise.resolve(current);
159
+ let signal = scopeSignal(options?.signal, resetSignal, connectionSignal);
160
+ if (signal.aborted) return Promise.reject(new MessageBusError("ABORTED"));
161
+ let timeoutMs = options?.timeout === void 0 ? DEFAULT_TIMEOUT_MS : options.timeout;
162
+ return new Promise((resolve, reject) => {
163
+ let timer = timeoutMs === null ? void 0 : setTimeout(() => reject(new MessageBusError("TIMEOUT", `query("${type}") timed out`)), timeoutMs), clear = () => {
164
+ timer !== void 0 && clearTimeout(timer);
165
+ }, onAbort = () => {
166
+ clear(), reject(new MessageBusError("ABORTED"));
167
+ };
168
+ signal?.addEventListener("abort", onAbort, { once: !0 }), source.firstValue.then((value) => {
169
+ clear(), signal?.removeEventListener("abort", onAbort), resolve(value);
170
+ }, (error) => {
171
+ clear(), signal?.removeEventListener("abort", onAbort), reject(error);
172
+ });
173
+ });
174
+ }
175
+ function unsubscribeOnAbort(subscription, signal) {
176
+ if (signal) {
177
+ if (signal.aborted) {
178
+ subscription.unsubscribe();
179
+ return;
180
+ }
181
+ signal.addEventListener("abort", () => subscription.unsubscribe(), { once: !0 });
182
+ }
183
+ }
184
+ function scopeSignal(signal, ...lifecycles) {
185
+ return signal ? AbortSignal.any([signal, ...lifecycles]) : AbortSignal.any(lifecycles);
186
+ }
187
+ function invokeResponder(handler, message) {
188
+ let pendingReply = message[MESSAGE_BUS_PENDING_REPLY_KEY], fail = (error) => {
189
+ pendingReply && settleReply(pendingReply, {
190
+ ok: !1,
191
+ error: new MessageBusError("HANDLER_THREW", void 0, { cause: error })
192
+ });
193
+ };
194
+ try {
195
+ let result = handler(message);
196
+ result instanceof Promise && result.catch(fail);
197
+ } catch (error) {
198
+ fail(error);
199
+ }
200
+ }
201
+ function respond(registry, type, handler, options, appId, connectionSignal) {
202
+ let topic = registry.topics.get(type);
203
+ if (topic?.kind === "event" && !canRespond(registry, topic.ownership, appId)) throw new MessageBusError("OWNERSHIP_MISMATCH", `Cannot register a handler for event topic "${type}" from app "${appId}". Only the app that owns this topic can respond to it. Other apps can send it with emit().`);
204
+ registry.responderCounts.set(type, (registry.responderCounts.get(type) ?? 0) + 1);
205
+ let subscription = resolveEventSubject(registry, type).subscribe((message) => invokeResponder(handler, message));
206
+ subscription.add(() => {
207
+ let count = registry.responderCounts.get(type) ?? 0;
208
+ count > 0 && registry.responderCounts.set(type, count - 1);
209
+ }), unsubscribeOnAbort(subscription, scopeSignal(options?.signal, registry.resetAbort.signal, connectionSignal));
210
+ }
211
+ const bundledMigrations = () => new Map(Object.entries(topicMigrations));
212
+ /**
213
+ * Creates an isolated message bus without installing it globally and returns the host's
214
+ * connection to it.
215
+ * @internal
216
+ */
217
+ function createIsolatedMessageBus(appId, config = {}) {
218
+ appId || throwMissingAppId();
219
+ let registry = {
220
+ appId,
221
+ topics: /* @__PURE__ */ new Map(),
222
+ connections: /* @__PURE__ */ new Set(),
223
+ connected$: new Subject(),
224
+ eventSubjects: /* @__PURE__ */ new Map(),
225
+ responderCounts: /* @__PURE__ */ new Map(),
226
+ migrations: config.migrations ?? bundledMigrations(),
227
+ resetAbort: new AbortController(),
228
+ generation: 0
229
+ };
230
+ return mergeTopicManifest(registry, DASHBOARD_TOPIC_MANIFEST), createConnection(registry, {
231
+ appId,
232
+ migrations: config.migrations
233
+ });
234
+ }
235
+ const identityMigration = {
236
+ up: (value) => value,
237
+ down: (value) => value
238
+ };
239
+ function effectiveTopicVersions(migrations) {
240
+ let versions = /* @__PURE__ */ new Map();
241
+ for (let [topic, steps] of migrations) {
242
+ let version = 1;
243
+ for (let step of steps) step.to > version && (version = step.to);
244
+ versions.set(topic, version);
245
+ }
246
+ return versions;
247
+ }
248
+ function migrationTransformAt(steps, version, select) {
249
+ let step = steps?.find((candidate) => candidate.from === version);
250
+ return (step ? select(step) : void 0) ?? identityMigration;
251
+ }
252
+ function migrateVersionedValue(steps, value, from, to, select) {
253
+ if (from === to) return value;
254
+ let current = value;
255
+ if (from < to) for (let version = from; version < to; version++) current = migrationTransformAt(steps, version, select).up(current);
256
+ else for (let version = from; version > to; version--) current = migrationTransformAt(steps, version - 1, select).down(current);
257
+ return current;
258
+ }
259
+ function createTopicCompatibility(installedMigrations, applicationMigrations) {
260
+ let installedVersions = effectiveTopicVersions(installedMigrations), applicationVersions = effectiveTopicVersions(applicationMigrations), applicationVersion = (type) => applicationVersions.get(type) ?? 1, installedVersion = (type) => installedVersions.get(type) ?? 1, migrationChainFor = (type) => (applicationVersion(type) > installedVersion(type) ? applicationMigrations : installedMigrations).get(type), createVersionAdapter = (select) => ({
261
+ toInstalled: (type, value) => migrateVersionedValue(migrationChainFor(type), value, applicationVersion(type), installedVersion(type), select),
262
+ toApplication: (type, value) => migrateVersionedValue(migrationChainFor(type), value, installedVersion(type), applicationVersion(type), select)
263
+ }), stateValueAndEventPayload = createVersionAdapter((step) => step), eventReply = createVersionAdapter((step) => step.reply);
264
+ return {
265
+ toInstalledEmission: stateValueAndEventPayload.toInstalled,
266
+ toApplicationStateValue: stateValueAndEventPayload.toApplication,
267
+ toApplicationEventPayload: stateValueAndEventPayload.toApplication,
268
+ toInstalledEventReply: eventReply.toInstalled,
269
+ toApplicationEventReply: eventReply.toApplication
270
+ };
271
+ }
272
+ function projectCurrent(input, project) {
273
+ let lastInput, lastOutput, cached = !1;
274
+ return () => {
275
+ let current = input();
276
+ if (current !== void 0) return (!cached || current !== lastInput) && (lastInput = current, lastOutput = project(current), cached = !0), lastOutput;
277
+ };
278
+ }
279
+ function createStateSource(subject, project, completeOn) {
280
+ let getCurrent = () => {
281
+ let current = subject.getValue();
282
+ return current === NO_VALUE ? void 0 : current;
283
+ };
284
+ return toStateSource(subject.pipe(filter((value) => value !== NO_VALUE), map(project), takeUntil(completeOn)), projectCurrent(getCurrent, project));
285
+ }
286
+ function migrateEventMessage(message, payload, reply) {
287
+ return {
288
+ type: message.type,
289
+ payload: payload(message.payload),
290
+ meta: message.meta,
291
+ reply: (value) => message.reply(reply(value)),
292
+ get signal() {
293
+ return message.signal;
294
+ }
295
+ };
296
+ }
297
+ function migrateEventReply(result, project) {
298
+ let projected;
299
+ return createLazyReply(() => projected ??= Promise.resolve(result).then(project));
300
+ }
301
+ function createRejectedConnection(connectionError) {
302
+ let throwConnectionError = () => {
303
+ throw connectionError();
304
+ };
305
+ return {
306
+ emit: throwConnectionError,
307
+ query: () => Promise.reject(connectionError()),
308
+ subscribe: throwConnectionError,
309
+ disconnect: () => {}
310
+ };
311
+ }
312
+ /**
313
+ * Connects an application to an isolated message bus.
314
+ * @internal
315
+ */
316
+ function connectApplicationToMessageBus(installedMessageBus, config) {
317
+ config.appId || throwMissingAppId();
318
+ let { appId } = config, installedProtocol = installedMessageBus[MESSAGE_BUS_PROTOCOL_KEY];
319
+ if (installedProtocol !== 1) return console.error(`[sanity-sdk:message-bus] protocol mismatch for "${appId}": installed ${String(installedProtocol)}, this copy speaks 1`), createRejectedConnection(() => new MessageBusError("PROTOCOL_MISMATCH", `installed message bus speaks protocol ${String(installedProtocol)}, this copy speaks 1`));
320
+ let registry = installedMessageBus[MESSAGE_BUS_REGISTRY_KEY];
321
+ if (!registry) return console.error(`[sanity-sdk:message-bus] incompatible message bus for "${appId}"`), createRejectedConnection(() => new MessageBusError("PROTOCOL_MISMATCH", "installed message bus does not expose a compatible registry"));
322
+ try {
323
+ mergeTopicManifest(registry, DASHBOARD_TOPIC_MANIFEST);
324
+ } catch (error) {
325
+ return console.error(`[sanity-sdk:message-bus] topic manifest conflict for "${appId}"`, { error }), createRejectedConnection(() => error);
326
+ }
327
+ return createConnection(registry, config);
328
+ }
329
+ function createClient(registry, record, compatibility) {
330
+ return {
331
+ appId: record.appId,
332
+ moduleId: record.moduleId,
333
+ closed: record.abort.signal,
334
+ emit: (type, value) => {
335
+ if (record.abort.signal.aborted) {
336
+ console.warn(`[sanity-sdk:message-bus] "${type}" not written: connection "${record.moduleId}" has closed`);
337
+ return;
338
+ }
339
+ emitState(registry, record, type, compatibility.toInstalledEmission(type, value));
340
+ }
341
+ };
342
+ }
343
+ function createConnectionsSource(registry, self, compatibility, completeOn) {
344
+ let clients = /* @__PURE__ */ new WeakMap(), clientFor = (record) => {
345
+ let client = clients.get(record);
346
+ return client || (client = createClient(registry, record, compatibility), clients.set(record, client)), client;
347
+ };
348
+ return defer(() => concat(from([...registry.connections]), registry.connected$)).pipe(filter((record) => record !== self), map(clientFor), takeUntil(completeOn));
349
+ }
350
+ function createConnection(registry, config) {
351
+ let { appId } = config, moduleId = config.moduleId ?? appId, connectionAbort = new AbortController(), connectionSignal = connectionAbort.signal, connectionAborted$ = new ReplaySubject(1);
352
+ connectionSignal.addEventListener("abort", () => connectionAborted$.next(), { once: !0 });
353
+ let record = {
354
+ appId,
355
+ moduleId,
356
+ stateSubjects: /* @__PURE__ */ new Map(),
357
+ abort: connectionAbort
358
+ }, compatibility = createTopicCompatibility(registry.migrations, config.migrations ?? bundledMigrations()), isState = (type) => isStateTopic(registry, type), applicationStreams = /* @__PURE__ */ new Map(), streamGeneration = registry.generation, cachedStream = (type, create) => {
359
+ streamGeneration !== registry.generation && (applicationStreams.clear(), streamGeneration = registry.generation);
360
+ let stream = applicationStreams.get(type);
361
+ return stream || (stream = create(), applicationStreams.set(type, stream)), stream;
362
+ }, stateSource = (type) => cachedStream(type, () => createStateSource(resolveStateSubject(registry, record, type), (value) => compatibility.toApplicationStateValue(type, value), connectionAborted$)), eventStream = (type) => cachedStream(type, () => resolveEventSubject(registry, type).pipe(map((message) => compatibility.toApplicationEventPayload(type, message.payload)), takeUntil(connectionAborted$))), throwIfDisconnected = () => {
363
+ if (connectionSignal.aborted) throw new MessageBusError("ABORTED");
364
+ }, connection = {
365
+ emit: (type, payload, options) => (throwIfDisconnected(), migrateEventReply(emit(registry, type, compatibility.toInstalledEmission(type, payload), options, appId, moduleId, connectionSignal), (value) => compatibility.toApplicationEventReply(type, value))),
366
+ query: (type, options) => connectionSignal.aborted ? Promise.reject(new MessageBusError("ABORTED")) : query(stateSource(type), type, options, connectionSignal, registry.resetAbort.signal),
367
+ subscribe: (type, handler, options) => {
368
+ if (throwIfDisconnected(), isState(type)) {
369
+ let source = stateSource(type);
370
+ if (!handler) return source;
371
+ unsubscribeOnAbort(source.subscribe(handler), scopeSignal(options?.signal, registry.resetAbort.signal, connectionSignal));
372
+ return;
373
+ }
374
+ if (!handler) return eventStream(type);
375
+ respond(registry, type, (message) => handler(migrateEventMessage(message, (value) => compatibility.toApplicationEventPayload(type, value), (value) => compatibility.toInstalledEventReply(type, value))), options, appId, connectionSignal);
376
+ },
377
+ disconnect: () => {
378
+ if (!connectionSignal.aborted) {
379
+ connectionAbort.abort();
380
+ for (let subject of record.stateSubjects.values()) subject.complete();
381
+ record.stateSubjects.clear(), registry.connections.delete(record);
382
+ }
383
+ }
384
+ };
385
+ appId === registry.appId && Object.assign(connection, { connections: createConnectionsSource(registry, record, compatibility, connectionAborted$) });
386
+ let instance = connection;
387
+ return instance[MESSAGE_BUS_REGISTRY_KEY] = registry, instance[MESSAGE_BUS_PROTOCOL_KEY] = 1, registry.connections.add(record), registry.connected$.next(record), instance;
388
+ }
389
+ function reset(registry) {
390
+ registry.resetAbort.abort();
391
+ for (let connection of registry.connections) {
392
+ for (let subject of connection.stateSubjects.values()) subject.complete();
393
+ connection.stateSubjects.clear();
394
+ }
395
+ for (let subject of registry.eventSubjects.values()) subject.complete();
396
+ registry.topics.clear(), registry.eventSubjects.clear(), registry.responderCounts.clear(), registry.resetAbort = new AbortController(), registry.generation += 1, mergeTopicManifest(registry, DASHBOARD_TOPIC_MANIFEST);
397
+ }
398
+ function throwMissingAppId() {
399
+ throw new MessageBusError("MISSING_APP_ID", "Cannot initialize the message bus without an app ID. Build the application with the Sanity CLI or pass an app ID when connecting.");
400
+ }
401
+ const resolveAppId = (appId) => appId ?? (typeof __SANITY_APP_ID__ == "string" ? __SANITY_APP_ID__ : void 0);
402
+ function getInstalledMessageBus() {
403
+ let bus = globalThis[MESSAGE_BUS_KEY];
404
+ return typeof bus == "object" && bus && MESSAGE_BUS_REGISTRY_KEY in bus ? bus : void 0;
405
+ }
406
+ /**
407
+ * Returns whether a message bus registry is installed.
408
+ * @internal
409
+ */
410
+ function isMessageBusInstalled() {
411
+ return getInstalledMessageBus() !== void 0;
412
+ }
413
+ /**
414
+ * Connects to the installed message bus, or returns `undefined` when no compatible connection exists.
415
+ * @public
416
+ */
417
+ function connectMessageBus(options = {}) {
418
+ let installedMessageBus = getInstalledMessageBus();
419
+ if (!installedMessageBus) return;
420
+ let appId = resolveAppId(options.appId);
421
+ if (!appId) {
422
+ console.warn("[sanity-sdk:message-bus] cannot connect without an app ID; build with the Sanity CLI or pass appId");
423
+ return;
424
+ }
425
+ let connection = connectApplicationToMessageBus(installedMessageBus, {
426
+ appId,
427
+ moduleId: options.moduleId
428
+ });
429
+ return MESSAGE_BUS_REGISTRY_KEY in connection ? connection : void 0;
430
+ }
431
+ /**
432
+ * Resets the installed message bus for test isolation.
433
+ * @internal
434
+ */
435
+ function resetMessageBus() {
436
+ let installedMessageBus = getInstalledMessageBus();
437
+ installedMessageBus && reset(installedMessageBus[MESSAGE_BUS_REGISTRY_KEY]);
438
+ }
439
+ /**
440
+ * Installs the shared message bus and returns the host's connection, or connects to its
441
+ * existing installation. Only the host calls this; another app's ID is rejected because the
442
+ * connection it would get has no `connections`.
443
+ * @internal
444
+ */
445
+ function installMessageBus(options = {}) {
446
+ let appId = resolveAppId(options.appId) ?? throwMissingAppId(), installedMessageBus = getInstalledMessageBus();
447
+ if (installedMessageBus) {
448
+ let installedAppId = installedMessageBus[MESSAGE_BUS_REGISTRY_KEY].appId;
449
+ if (installedAppId !== appId) throw new MessageBusError("OWNERSHIP_MISMATCH", `Cannot install the message bus as "${appId}": it is already installed by "${installedAppId}". Applications connect with connectMessageBus().`);
450
+ return connectApplicationToMessageBus(installedMessageBus, { appId });
451
+ }
452
+ let globals = globalThis;
453
+ globals[MESSAGE_BUS_KEY] && console.error("[sanity-sdk:message-bus] overwriting an incompatible message bus already installed");
454
+ let host = createIsolatedMessageBus(appId);
455
+ return globals[MESSAGE_BUS_KEY] = host, host;
456
+ }
457
+ /**
458
+ * Returns the message bus connection for this instance, connecting on first call.
459
+ *
460
+ * @remarks
461
+ * One connection is made per {@link SanityInstance}, carrying that window's
462
+ * module identity. The first caller wins; later callers get the cached
463
+ * connection. When no host bus is installed it returns `undefined` without
464
+ * caching, so a later call retries once a host installs the bus.
465
+ * @internal
466
+ */
467
+ const getDashboardMessageBus = createActionBinder((instance) => ({ name: instance.instanceId }))(defineStore({
468
+ name: "dashboardMessageBus",
469
+ getInitialState: () => ({}),
470
+ initialize: ({ state }) => () => state.get().connection?.disconnect()
471
+ }), ({ state }, moduleId) => {
472
+ let current = state.get().connection;
473
+ if (current) return current;
474
+ if (!isMessageBusInstalled()) return;
475
+ let connection = connectMessageBus({ moduleId });
476
+ if (connection) return state.set("connect", { connection }), connection;
477
+ });
478
+ /**
479
+ * Returns the instance's message bus connection, or throws when no dashboard host has
480
+ * installed a bus. `action` names what the caller was about to do, e.g. `read topic "x"`.
481
+ * @internal
482
+ */
483
+ function requireDashboardMessageBus(instance, action) {
484
+ let messageBus = getDashboardMessageBus(instance);
485
+ if (!messageBus) throw Error(`Cannot ${action} without an installed dashboard message bus`);
486
+ return messageBus;
487
+ }
488
+ /**
489
+ * Returns whether a dashboard host has installed a message bus.
490
+ *
491
+ * @remarks
492
+ * Answers "does a host own this session", not "has this instance connected yet". Callers
493
+ * such as the login redirect run before {@link getDashboardMessageBus} has been called for
494
+ * the instance, so this must not depend on the connection state.
495
+ * @internal
496
+ */
497
+ function isDashboardEnvironment() {
498
+ return isMessageBusInstalled();
499
+ }
500
+ /**
501
+ * An error raised when a dashboard state topic reports failure.
502
+ * @public
503
+ */
504
+ var TopicError = class extends Error {
505
+ /** Creates an error for a failed topic. */
506
+ constructor(topic) {
507
+ super(`Topic "${topic}" failed`), _defineProperty(this, "topic", void 0), this.name = "TopicError", this.topic = topic;
508
+ }
509
+ };
510
+ function isTopicResult(value) {
511
+ return typeof value == "object" && !!value && "ok" in value && typeof value.ok == "boolean";
512
+ }
513
+ function unwrapTopicResult(topic, value) {
514
+ if (!isTopicResult(value)) return value;
515
+ if (!value.ok) throw new TopicError(topic);
516
+ return value.value;
517
+ }
518
+ const TOPIC_STATE_CLEAR_DELAY = 1e3, bindActionByInstance = createActionBinder((instance) => ({ name: instance.instanceId })), dashboardTopicsStore = defineStore({
519
+ name: "dashboardTopics",
520
+ getInitialState: () => ({ topics: {} })
521
+ }), getMessageBus = (instance, topic) => requireDashboardMessageBus(instance, `read topic "${topic}"`);
522
+ function getSource(instance, topic) {
523
+ return getMessageBus(instance, topic).subscribe(topic);
524
+ }
525
+ const updateEntry = (topic, update) => (state) => {
526
+ let { [topic]: entry = { subscribers: 0 }, ...rest } = state.topics, next = update(entry);
527
+ return { topics: next.subscribers > 0 ? {
528
+ ...rest,
529
+ [topic]: next
530
+ } : rest };
531
+ }, addSubscriber = (topic) => updateEntry(topic, (entry) => ({
532
+ ...entry,
533
+ subscribers: entry.subscribers + 1
534
+ })), removeSubscriber = (topic) => updateEntry(topic, (entry) => ({
535
+ ...entry,
536
+ subscribers: entry.subscribers - 1
537
+ })), getTopicState = bindActionByInstance(dashboardTopicsStore, createStateSourceAction({
538
+ selector: ({ state, instance }, topic) => {
539
+ let current = getSource(instance, topic).getCurrent(), entry = state.topics[topic];
540
+ if (current === void 0 && entry?.error) throw entry.error;
541
+ return unwrapTopicResult(topic, current);
542
+ },
543
+ onSubscribe: ({ state, instance }, topic) => {
544
+ state.set("addSubscriber", addSubscriber(topic));
545
+ let subscription = getSource(instance, topic).subscribe(() => state.set("publish", updateEntry(topic, (entry) => ({ ...entry }))));
546
+ return () => {
547
+ subscription.unsubscribe(), setCleanupTimeout(() => state.set("removeSubscriber", removeSubscriber(topic)), TOPIC_STATE_CLEAR_DELAY);
548
+ };
549
+ }
550
+ })), resolveTopic = bindActionByInstance(dashboardTopicsStore, ({ state, instance }, topic) => {
551
+ let existing = state.get().topics[topic]?.pending;
552
+ if (existing) return existing;
553
+ let release = () => setCleanupTimeout(() => state.set("removeSubscriber", removeSubscriber(topic)), TOPIC_STATE_CLEAR_DELAY), pending = getMessageBus(instance, topic).query(topic).then(() => state.set("resolved", updateEntry(topic, ({ pending: _pending, ...entry }) => entry)), (error) => state.set("failed", updateEntry(topic, ({ pending: _pending, ...entry }) => ({
554
+ ...entry,
555
+ error
556
+ }))));
557
+ return pending.then(release, release), state.set("query", updateEntry(topic, (entry) => ({
558
+ ...entry,
559
+ pending,
560
+ subscribers: entry.subscribers + 1
561
+ }))), pending;
562
+ });
563
+ export { isDashboardEnvironment as a, connectMessageBus as c, getDashboardMessageBus as i, installMessageBus as l, getTopicState as n, requireDashboardMessageBus as o, resolveTopic as r, MessageBusError as s, TopicError as t, resetMessageBus as u };
564
+
565
+ //# sourceMappingURL=topicStore-Ct7IFNtY.js.map