@sanity/sdk 3.3.0-rc.1 → 3.4.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/_exports/_internal.d.ts +12 -4
  2. package/dist/_exports/_internal.d.ts.map +1 -1
  3. package/dist/_exports/_internal.js +21 -3
  4. package/dist/_exports/_internal.js.map +1 -1
  5. package/dist/_exports/comlink.d.ts +1 -1
  6. package/dist/_exports/dashboard.d.ts +1 -1
  7. package/dist/_exports/dashboard.js +1 -1
  8. package/dist/{authStore-7dZCfKUq.d.ts → applications-903KQTrD.d.ts} +346 -344
  9. package/dist/applications-903KQTrD.d.ts.map +1 -0
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/{topicStore-Ct7IFNtY.js → topicStore-BJ54_X6H.js} +3 -1
  14. package/dist/topicStore-BJ54_X6H.js.map +1 -0
  15. package/dist/{topicStore-CAnJsiqI.d.ts → topicStore-VQILEI1R.d.ts} +54 -25
  16. package/dist/topicStore-VQILEI1R.d.ts.map +1 -0
  17. package/dist/{version-CYs30C7v.js → version-CYqZgJX1.js} +2 -2
  18. package/dist/{version-CYs30C7v.js.map → version-CYqZgJX1.js.map} +1 -1
  19. package/package.json +7 -7
  20. package/src/_exports/_internal.ts +2 -0
  21. package/src/applications/applicationOrigin.test.ts +51 -0
  22. package/src/applications/applicationOrigin.ts +26 -0
  23. package/src/dashboard/messageBus/bus.test.ts +39 -0
  24. package/src/dashboard/messageBus/bus.ts +32 -22
  25. package/src/dashboard/messageBus/topics.test.ts +36 -2
  26. package/src/dashboard/messageBus/topics.ts +51 -16
  27. package/src/installations/installations.test-d.ts +11 -0
  28. package/src/installations/installations.test.ts +6 -4
  29. package/src/installations/installations.ts +13 -2
  30. package/dist/authStore-7dZCfKUq.d.ts.map +0 -1
  31. package/dist/topicStore-CAnJsiqI.d.ts.map +0 -1
  32. package/dist/topicStore-Ct7IFNtY.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/sdk",
3
- "version": "3.3.0-rc.1",
3
+ "version": "3.4.0-rc.0",
4
4
  "private": false,
5
5
  "description": "Sanity SDK",
6
6
  "keywords": [
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@sanity/bifur-client": "^1.0.0",
48
- "@sanity/client": "^8.6.1",
48
+ "@sanity/client": "^8.6.2",
49
49
  "@sanity/comlink": "^4.0.3",
50
50
  "@sanity/diff-match-patch": "^3.2.0",
51
51
  "@sanity/diff-patch": "^6.0.0",
@@ -55,7 +55,7 @@
55
55
  "@sanity/message-protocol": "^0.24.0",
56
56
  "@sanity/mutate": "^0.18.2",
57
57
  "@sanity/telemetry": "^1.1.0",
58
- "@sanity/types": "^6.13.2",
58
+ "@sanity/types": "^6.14.1",
59
59
  "groq": "^6.13.2",
60
60
  "groq-js": "^2.0.0",
61
61
  "reselect": "^5.1.1",
@@ -70,14 +70,14 @@
70
70
  "@repo/tsconfig": "0.0.1",
71
71
  "@sanity/browserslist-config": "^1.0.5",
72
72
  "@sanity/pkg-utils": "^13.0.0",
73
- "@types/node": "^24.13.4",
74
- "@vitest/coverage-v8": "^5.0.0",
73
+ "@types/node": "^24.13.5",
74
+ "@vitest/coverage-v8": "^5.0.1",
75
75
  "eslint": "^10.10.0",
76
- "oxfmt": "^0.67.0",
76
+ "oxfmt": "^0.68.0",
77
77
  "rolldown": "^1.2.8",
78
78
  "typescript": "^6.0.3",
79
79
  "vite": "^8.3.0",
80
- "vitest": "^5.0.0"
80
+ "vitest": "^5.0.1"
81
81
  },
82
82
  "browserslist": "extends @sanity/browserslist-config",
83
83
  "scripts": {
@@ -1,3 +1,4 @@
1
+ export {getApplicationOrigin} from '../applications/applicationOrigin'
1
2
  export {isStudioConfig} from '../auth/authMode'
2
3
  export {
3
4
  type ApiErrorBody,
@@ -17,6 +18,7 @@ export {
17
18
  isDashboardEnvironment,
18
19
  requireDashboardMessageBus,
19
20
  } from '../dashboard/messageBus/store'
21
+ export type {ApplicationStatus, ApplicationStatusUpdate} from '../dashboard/messageBus/topics'
20
22
  export {getTopicState, resolveTopic} from '../dashboard/messageBus/topicStore'
21
23
  export {PREVIEW_PROJECTION} from '../preview/previewConstants'
22
24
  export {transformProjectionToPreview} from '../preview/previewProjectionUtils'
@@ -0,0 +1,51 @@
1
+ import {afterEach, describe, expect, it, vi} from 'vitest'
2
+
3
+ import {getApplicationOrigin} from './applicationOrigin'
4
+ import {type ApplicationBase} from './applications'
5
+
6
+ const application: ApplicationBase = {
7
+ id: 'application-1',
8
+ type: 'coreApp',
9
+ title: 'Inbox',
10
+ name: 'inbox',
11
+ reference: 'sanity/inbox',
12
+ icon: null,
13
+ isSingleton: true,
14
+ visibility: 'default',
15
+ slug: 'inbox',
16
+ externalUrl: null,
17
+ organizationId: 'organization-1',
18
+ createdAt: '2026-01-01T00:00:00.000Z',
19
+ updatedAt: '2026-01-02T00:00:00.000Z',
20
+ }
21
+
22
+ describe('getApplicationOrigin', () => {
23
+ afterEach(() => vi.unstubAllGlobals())
24
+
25
+ it('derives singleton and organization origins from the slug', () => {
26
+ expect(getApplicationOrigin(application)).toBe('https://inbox-apps-organization-1.sanity.run')
27
+ expect(getApplicationOrigin({...application, isSingleton: false, slug: 'canvas'})).toBe(
28
+ 'https://canvas.sanity.studio',
29
+ )
30
+ })
31
+
32
+ it('uses the staging domains when the host sets the runtime flag', () => {
33
+ vi.stubGlobal('__SANITY_STAGING__', true)
34
+ expect(getApplicationOrigin(application)).toBe(
35
+ 'https://inbox-apps-organization-1.run.sanity.work',
36
+ )
37
+ expect(getApplicationOrigin({...application, isSingleton: false})).toBe(
38
+ 'https://inbox.studio.sanity.work',
39
+ )
40
+ })
41
+
42
+ it('prefers the external URL origin and returns null without slug or URL', () => {
43
+ expect(
44
+ getApplicationOrigin({
45
+ ...application,
46
+ externalUrl: 'https://apps.example.com/external/index.html',
47
+ }),
48
+ ).toBe('https://apps.example.com')
49
+ expect(getApplicationOrigin({...application, slug: null})).toBeNull()
50
+ })
51
+ })
@@ -0,0 +1,26 @@
1
+ import {type ApplicationBase} from './applications'
2
+
3
+ type SanityGlobal = typeof globalThis & {__SANITY_STAGING__?: boolean}
4
+
5
+ /**
6
+ * The origin an application is served from, or `null` when it has neither an external URL nor
7
+ * a slug to derive one from. Callers treat `null` as "nothing to load" rather than failing the
8
+ * whole list on one bad record.
9
+ *
10
+ * @internal
11
+ */
12
+ export function getApplicationOrigin(application: ApplicationBase): string | null {
13
+ if (application.externalUrl !== null) return new URL(application.externalUrl).origin
14
+ if (application.slug === null) return null
15
+
16
+ // Read at runtime, not via a bundler define: a remote is built once and runs in whichever
17
+ // host page loaded it, and the host sets this flag. Mirrors workbench's `getSanityEnv`.
18
+ const staging = (globalThis as SanityGlobal).__SANITY_STAGING__ === true
19
+ if (application.isSingleton) {
20
+ const domain = staging ? 'run.sanity.work' : 'sanity.run'
21
+ return `https://${application.slug}-apps-${application.organizationId}.${domain}`
22
+ }
23
+
24
+ const domain = staging ? 'studio.sanity.work' : 'sanity.studio'
25
+ return `https://${application.slug}.${domain}`
26
+ }
@@ -500,6 +500,45 @@ describe('application connections', () => {
500
500
  expect(app.subscribe('auth.token').getCurrent()).toBe('trusted')
501
501
  })
502
502
 
503
+ it('delivers application status events to the host and rejects app responders', () => {
504
+ const statusHost = createMessageBus('dashboard')
505
+ const application = connectApplicationToMessageBus(statusHost, {
506
+ appId: 'favorites',
507
+ })
508
+ const responder = vi.fn()
509
+
510
+ statusHost.subscribe('applications.status.update', responder)
511
+ expect(() => application.subscribe('applications.status.update', vi.fn())).toThrowError(
512
+ expect.objectContaining({code: 'OWNERSHIP_MISMATCH'}),
513
+ )
514
+
515
+ application.emit('applications.status.update', {
516
+ name: 'list',
517
+ value: {label: 'Syncing'},
518
+ })
519
+ application.emit('applications.status.update', {
520
+ name: 'list',
521
+ value: {label: null},
522
+ })
523
+
524
+ expect(responder).toHaveBeenNthCalledWith(
525
+ 1,
526
+ expect.objectContaining({
527
+ type: 'applications.status.update',
528
+ payload: {name: 'list', value: {label: 'Syncing'}},
529
+ meta: expect.objectContaining({appId: 'favorites'}),
530
+ }),
531
+ )
532
+ expect(responder).toHaveBeenNthCalledWith(
533
+ 2,
534
+ expect.objectContaining({
535
+ type: 'applications.status.update',
536
+ payload: {name: 'list', value: {label: null}},
537
+ meta: expect.objectContaining({appId: 'favorites'}),
538
+ }),
539
+ )
540
+ })
541
+
503
542
  it('rejects state emits from every connection, including the host', () => {
504
543
  const host = createMessageBus('dashboard')
505
544
  const panel = {
@@ -18,6 +18,7 @@ import {type Application} from '../../applications/applications'
18
18
  import {
19
19
  DASHBOARD_TOPIC_MANIFEST,
20
20
  type EventTopic,
21
+ type MessageBusTopics,
21
22
  type PayloadOf,
22
23
  type ReplyOf,
23
24
  type StateTopic,
@@ -25,6 +26,7 @@ import {
25
26
  type TopicMigration,
26
27
  topicMigrations,
27
28
  type TopicName,
29
+ type Topics,
28
30
  type ValueOf,
29
31
  } from './topics'
30
32
 
@@ -78,9 +80,9 @@ export interface MessageBusMeta {
78
80
  * A message delivered to an event topic responder.
79
81
  * @public
80
82
  */
81
- export interface MessageBusMessage<T, R = never> {
83
+ export interface MessageBusMessage<T, R = never, K extends PropertyKey = TopicName> {
82
84
  /** The topic that carries the message. */
83
- type: TopicName
85
+ type: K
84
86
  /** The event payload. */
85
87
  payload: T
86
88
  /** The message provenance. */
@@ -148,39 +150,42 @@ export interface MessageBusEmitResult<R> extends PromiseLike<R> {
148
150
  * {@link MessageBusClient.emit}; a connection only ever reads its own values.
149
151
  * @public
150
152
  */
151
- export interface MessageBus {
153
+ export interface MessageBus<TTopics = MessageBusTopics> {
152
154
  /** Emits an event topic and provides its reply when awaited. */
153
- emit<K extends EventTopic>(
155
+ emit<K extends EventTopic<TTopics>>(
154
156
  type: K,
155
- ...rest: PayloadOf<K> extends void
157
+ ...rest: PayloadOf<K, TTopics> extends void
156
158
  ? [payload?: void, options?: MessageBusEmitOptions]
157
- : [payload: PayloadOf<K>, options?: MessageBusEmitOptions]
158
- ): MessageBusEmitResult<ReplyOf<K>>
159
+ : [payload: PayloadOf<K, TTopics>, options?: MessageBusEmitOptions]
160
+ ): MessageBusEmitResult<ReplyOf<K, TTopics>>
159
161
  /** Reads the current or next value of a state topic. */
160
- query<K extends StateTopic>(type: K, options?: MessageBusQueryOptions): Promise<ValueOf<K>>
162
+ query<K extends StateTopic<TTopics>>(
163
+ type: K,
164
+ options?: MessageBusQueryOptions,
165
+ ): Promise<ValueOf<K, TTopics>>
161
166
  /** Runs a responder for each event until its signal aborts. */
162
- subscribe<K extends EventTopic>(
167
+ subscribe<K extends EventTopic<TTopics>>(
163
168
  type: K,
164
- handler: (message: MessageBusMessage<PayloadOf<K>, ReplyOf<K>>) => void,
169
+ handler: (message: MessageBusMessage<PayloadOf<K, TTopics>, ReplyOf<K, TTopics>, K>) => void,
165
170
  options?: MessageBusAbortOptions,
166
171
  ): void
167
172
  /** Runs a handler for each state value until its signal aborts. */
168
- subscribe<K extends StateTopic>(
173
+ subscribe<K extends StateTopic<TTopics>>(
169
174
  type: K,
170
- handler: (value: ValueOf<K>) => void,
175
+ handler: (value: ValueOf<K, TTopics>) => void,
171
176
  options?: MessageBusAbortOptions,
172
177
  ): void
173
178
  /** Returns a state topic as a `MessageBusStateSource`. */
174
- subscribe<K extends StateTopic>(type: K): MessageBusStateSource<ValueOf<K>>
179
+ subscribe<K extends StateTopic<TTopics>>(type: K): MessageBusStateSource<ValueOf<K, TTopics>>
175
180
  /** Returns an event topic as an observable of its payloads. */
176
- subscribe<K extends EventTopic>(type: K): Observable<PayloadOf<K>>
181
+ subscribe<K extends EventTopic<TTopics>>(type: K): Observable<PayloadOf<K, TTopics>>
177
182
  }
178
183
 
179
184
  /**
180
185
  * A message bus connection that can be torn down independently of its siblings.
181
186
  * @public
182
187
  */
183
- export interface MessageBusConnection extends MessageBus {
188
+ export interface MessageBusConnection<TTopics = MessageBusTopics> extends MessageBus<TTopics> {
184
189
  /**
185
190
  * Tears down this connection: pending requests reject `ABORTED`, subscriptions
186
191
  * complete, and later `emit`, `query`, and `subscribe` calls fail with `ABORTED`.
@@ -214,7 +219,7 @@ export interface MessageBusClient {
214
219
  * a snapshot to each new client.
215
220
  * @public
216
221
  */
217
- export interface MessageBusHost extends MessageBusConnection {
222
+ export interface MessageBusHost<TTopics = MessageBusTopics> extends MessageBusConnection<TTopics> {
218
223
  /**
219
224
  * Every open connection other than this one first, then each new one as it joins. The same
220
225
  * {@link MessageBusClient} object is handed out for a connection across subscriptions, so it
@@ -641,7 +646,7 @@ export function createIsolatedMessageBus(
641
646
  config: {
642
647
  migrations?: ReadonlyMap<string, readonly TopicMigration[]>
643
648
  } = {},
644
- ): MessageBusHost {
649
+ ): MessageBusHost<Topics> {
645
650
  if (!appId) throwMissingAppId()
646
651
 
647
652
  const registry: MessageBusRegistry = {
@@ -658,7 +663,10 @@ export function createIsolatedMessageBus(
658
663
 
659
664
  mergeTopicManifest(registry, DASHBOARD_TOPIC_MANIFEST)
660
665
 
661
- return createConnection(registry, {appId, migrations: config.migrations}) as MessageBusHost
666
+ return createConnection(registry, {
667
+ appId,
668
+ migrations: config.migrations,
669
+ }) as MessageBusHost<Topics>
662
670
  }
663
671
 
664
672
  type TopicVersionAdapter = {
@@ -863,7 +871,7 @@ export interface ConnectApplicationToMessageBusOptions {
863
871
  export function connectApplicationToMessageBus(
864
872
  installedMessageBus: MessageBus,
865
873
  config: ConnectApplicationToMessageBusOptions,
866
- ): MessageBusConnection {
874
+ ): MessageBusConnection<Topics> {
867
875
  if (!config.appId) throwMissingAppId()
868
876
 
869
877
  const {appId} = config
@@ -902,7 +910,7 @@ export function connectApplicationToMessageBus(
902
910
  return createRejectedConnection(() => error)
903
911
  }
904
912
 
905
- return createConnection(registry, config)
913
+ return createConnection(registry, config) as MessageBusConnection<Topics>
906
914
  }
907
915
 
908
916
  // Hands the host the same client object for a connection across `connections` subscriptions.
@@ -1202,7 +1210,7 @@ export interface InstallMessageBusOptions {
1202
1210
  * connection it would get has no `connections`.
1203
1211
  * @internal
1204
1212
  */
1205
- export function installMessageBus(options: InstallMessageBusOptions = {}): MessageBusHost {
1213
+ export function installMessageBus(options: InstallMessageBusOptions = {}): MessageBusHost<Topics> {
1206
1214
  const appId = resolveAppId(options.appId) ?? throwMissingAppId()
1207
1215
  const installedMessageBus = getInstalledMessageBus()
1208
1216
  if (installedMessageBus) {
@@ -1214,7 +1222,9 @@ export function installMessageBus(options: InstallMessageBusOptions = {}): Messa
1214
1222
  `Cannot install the message bus as "${appId}": it is already installed by "${installedAppId}". Applications connect with connectMessageBus().`,
1215
1223
  )
1216
1224
  }
1217
- return connectApplicationToMessageBus(installedMessageBus, {appId}) as MessageBusHost
1225
+ return connectApplicationToMessageBus(installedMessageBus, {
1226
+ appId,
1227
+ }) as MessageBusHost<Topics>
1218
1228
  }
1219
1229
 
1220
1230
  const globals = globalThis as {[MESSAGE_BUS_KEY]?: MessageBus}
@@ -1,13 +1,18 @@
1
- import {describe, expectTypeOf, it} from 'vitest'
1
+ import {describe, expect, expectTypeOf, it} from 'vitest'
2
2
 
3
3
  import {type Application, type ApplicationInclude} from '../../applications/applications'
4
4
  import {type MessageBus} from './bus'
5
5
  import {
6
6
  type ApplicationConfig,
7
7
  type ApplicationConfigAppType,
8
+ type ApplicationStatusUpdate,
9
+ DASHBOARD_TOPIC_MANIFEST,
10
+ type DashboardTopics,
11
+ type EventTopic,
8
12
  type PayloadOf,
9
13
  type ReplyOf,
10
14
  type StateTopic,
15
+ type TopicName,
11
16
  type TopicResult,
12
17
  type ValueOf,
13
18
  } from './topics'
@@ -28,7 +33,26 @@ describe('dashboard topic types', () => {
28
33
  expectTypeOf<Parameters<MessageBus['query']>[0]>().toEqualTypeOf<StateTopic>()
29
34
  })
30
35
 
36
+ it('keeps application status internal', () => {
37
+ expectTypeOf<ApplicationStatusUpdate>().toEqualTypeOf<{
38
+ name: string
39
+ value: {label: string | null}
40
+ }>()
41
+ expect(DASHBOARD_TOPIC_MANIFEST['applications.status.update']).toEqual({
42
+ kind: 'event',
43
+ ownership: {type: 'same_app'},
44
+ })
45
+ expectTypeOf<'applications.status.update'>().toMatchTypeOf<TopicName>()
46
+ expectTypeOf<'applications.status.update'>().not.toMatchTypeOf<EventTopic>()
47
+ expectTypeOf<PayloadOf<'applications.status.update'>>().toEqualTypeOf<ApplicationStatusUpdate>()
48
+ expectTypeOf<ReplyOf<'applications.status.update'>>().toBeNever()
49
+ })
50
+
31
51
  it('exposes application state values', () => {
52
+ expectTypeOf<ValueOf<'applications.base-path'>>().toEqualTypeOf<TopicResult<string>>()
53
+ expectTypeOf<ValueOf<'applications.foreground', DashboardTopics>>().toEqualTypeOf<
54
+ Application['id'] | null
55
+ >()
32
56
  expectTypeOf<ValueOf<'applications.config'>>().toEqualTypeOf<ApplicationConfig[] | null>()
33
57
  expectTypeOf<ValueOf<'applications.list'>>().toEqualTypeOf<TopicResult<
34
58
  Application<ApplicationInclude>[]
@@ -60,12 +84,22 @@ describe('dashboard topic types', () => {
60
84
  })
61
85
 
62
86
  it('requires payloads only for events that declare one', () => {
63
- const messageBus = {emit: () => undefined} as unknown as MessageBus
87
+ const messageBus = {
88
+ emit: () => undefined,
89
+ query: () => undefined,
90
+ subscribe: () => undefined,
91
+ } as unknown as MessageBus
64
92
 
65
93
  messageBus.emit('auth.token.refresh')
66
94
  messageBus.emit('auth.token.refresh', undefined, {timeout: null})
67
95
  messageBus.emit('navigation.location.update', {url: '/'})
68
96
  // @ts-expect-error navigation.location.update requires a payload
69
97
  messageBus.emit('navigation.location.update')
98
+ // @ts-expect-error applications.status.update is internal
99
+ messageBus.emit('applications.status.update', {name: 'media', value: {label: null}})
100
+ // @ts-expect-error applications.status.update is internal
101
+ messageBus.subscribe('applications.status.update')
102
+ // @ts-expect-error applications.status.update is internal
103
+ messageBus.subscribe('applications.status.update', () => {})
70
104
  })
71
105
  })
@@ -31,6 +31,23 @@ export interface ApplicationConfig extends RemoteModuleRef {
31
31
  readonly appType: ApplicationConfigAppType
32
32
  }
33
33
 
34
+ /**
35
+ * A label rendered for an application interface; `null` clears it.
36
+ * @internal
37
+ */
38
+ export type ApplicationStatus = {label: string | null}
39
+
40
+ /**
41
+ * Updates the status rendered for an application interface.
42
+ * @internal
43
+ */
44
+ export type ApplicationStatusUpdate = {
45
+ /** The application interface name. */
46
+ name: string
47
+ /** The status to render. */
48
+ value: ApplicationStatus
49
+ }
50
+
34
51
  /**
35
52
  * Declares a topic that stores and replays its current value.
36
53
  * @public
@@ -83,12 +100,19 @@ export type NavigationLocation = NavigationTarget & {
83
100
  * @public
84
101
  */
85
102
  export interface DashboardTopics {
103
+ /**
104
+ * The base path for an application.
105
+ *
106
+ * An unknown application returns `{ok: false}`.
107
+ */
108
+ 'applications.base-path': StateTopicDef<TopicResult<string>>
86
109
  /** The available application configuration modules. */
87
110
  'applications.config': StateTopicDef<ApplicationConfig[] | null>
88
111
  /** The foreground application ID, or `null` on dashboard-level routes. */
89
112
  'applications.foreground': StateTopicDef<Application['id'] | null>
90
113
  /** The dashboard applications available to the current user. */
91
114
  'applications.list': StateTopicDef<TopicResult<Application<ApplicationInclude>[]> | null>
115
+ 'applications.status.update': EventTopicDef<ApplicationStatusUpdate>
92
116
  /**
93
117
  * The session token for the reading connection, or `null` while signed out. The host
94
118
  * writes it to each connection separately, so one application never sees another's token.
@@ -130,7 +154,7 @@ export interface DashboardTopics {
130
154
  /** Whether the dashboard dock is pinned open. */
131
155
  'preferences.dock-locked': StateTopicDef<boolean>
132
156
  /** The signed-in user, or `null` while signed out. */
133
- 'users.current': StateTopicDef<Pick<CurrentUser, 'id' | 'name' | 'email' | 'profileImage'> | null>
157
+ 'users.current': StateTopicDef<CurrentUser | null>
134
158
  }
135
159
 
136
160
  /**
@@ -140,21 +164,27 @@ export interface DashboardTopics {
140
164
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- Declaration merging extends the SDK manifest.
141
165
  export interface Topics extends DashboardTopics {}
142
166
 
167
+ /**
168
+ * Hides provisional topics from the public message bus methods.
169
+ * @internal
170
+ */
171
+ export type MessageBusTopics = Omit<Topics, 'applications.status.update'>
172
+
143
173
  /**
144
174
  * Every declared topic name.
145
175
  * @public
146
176
  */
147
- export type TopicName = keyof Topics
177
+ export type TopicName<TTopics = Topics> = keyof TTopics
148
178
 
149
179
  type StateTopicsOf<T> = {
150
180
  [K in keyof T]: T[K] extends {kind: 'state'} ? K : never
151
181
  }[keyof T]
152
182
 
153
183
  /**
154
- * Names of all declared state topics.
184
+ * Names of state topics exposed by message bus methods.
155
185
  * @public
156
186
  */
157
- export type StateTopic = StateTopicsOf<Topics>
187
+ export type StateTopic<TTopics = MessageBusTopics> = StateTopicsOf<TTopics>
158
188
 
159
189
  type TopicOwnership = {readonly type: 'same_app'} | {readonly type: 'any_app'}
160
190
 
@@ -169,16 +199,20 @@ const stateTopic = <const V>(seed: V) => ({kind: 'state', seed}) as const
169
199
  // `same_app` restricts responding to the application that installed the bus.
170
200
  const dashboardEvent = {kind: 'event', ownership: {type: 'same_app'}} as const
171
201
 
202
+ type DashboardTopicManifest = {
203
+ readonly [K in keyof DashboardTopics]: TopicManifestEntry<DashboardTopics[K]>
204
+ }
205
+
172
206
  /**
173
207
  * Defines the runtime kind, ownership, and initial value of dashboard topics.
174
208
  * @internal
175
209
  */
176
- export const DASHBOARD_TOPIC_MANIFEST: {
177
- readonly [K in keyof DashboardTopics]: TopicManifestEntry<DashboardTopics[K]>
178
- } = {
210
+ export const DASHBOARD_TOPIC_MANIFEST: DashboardTopicManifest = {
211
+ 'applications.base-path': stateTopic(undefined),
179
212
  'applications.config': stateTopic(undefined),
180
213
  'applications.foreground': stateTopic(undefined),
181
214
  'applications.list': stateTopic(undefined),
215
+ 'applications.status.update': dashboardEvent,
182
216
  'auth.token': stateTopic(undefined),
183
217
  'auth.token.refresh': dashboardEvent,
184
218
  'navigation.location': stateTopic(undefined),
@@ -204,32 +238,33 @@ export type TopicManifest = Readonly<
204
238
  >
205
239
 
206
240
  /**
207
- * Names of all declared event topics.
241
+ * Names of event topics exposed by message bus methods.
208
242
  * @public
209
243
  */
210
- export type EventTopic = {
211
- [K in TopicName]: Topics[K] extends {kind: 'event'} ? K : never
212
- }[TopicName]
244
+ export type EventTopic<TTopics = MessageBusTopics> = {
245
+ [K in keyof TTopics]: TTopics[K] extends {kind: 'event'} ? K : never
246
+ }[keyof TTopics]
213
247
 
214
248
  /**
215
249
  * The value type of a state topic.
216
250
  * @public
217
251
  */
218
- export type ValueOf<K extends StateTopic> = Topics[K] extends StateTopicDef<infer T> ? T : never
252
+ export type ValueOf<K extends StateTopic<TTopics>, TTopics = Topics> =
253
+ TTopics[K] extends StateTopicDef<infer T> ? T : never
219
254
 
220
255
  /**
221
256
  * The payload type of an event topic.
222
257
  * @public
223
258
  */
224
- export type PayloadOf<K extends EventTopic> =
225
- Topics[K] extends EventTopicDef<infer P, infer _R> ? P : never
259
+ export type PayloadOf<K extends EventTopic<TTopics>, TTopics = Topics> =
260
+ TTopics[K] extends EventTopicDef<infer P, infer _R> ? P : never
226
261
 
227
262
  /**
228
263
  * The reply type of an event topic (`never` if it declares none).
229
264
  * @public
230
265
  */
231
- export type ReplyOf<K extends EventTopic> =
232
- Topics[K] extends EventTopicDef<infer _P, infer R> ? R : never
266
+ export type ReplyOf<K extends EventTopic<TTopics>, TTopics = Topics> =
267
+ TTopics[K] extends EventTopicDef<infer _P, infer R> ? R : never
233
268
 
234
269
  /**
235
270
  * Converts a topic value between 2 adjacent versions.
@@ -27,6 +27,7 @@ test('Installation — no includes: only the base shape', () => {
27
27
  expectTypeOf<Installation>().toEqualTypeOf<InstallationBase>()
28
28
  expectTypeOf<Installation<never>>().toEqualTypeOf<InstallationBase>()
29
29
  expectTypeOf<Extract<keyof Installation, IncludeKeys>>().toEqualTypeOf<never>()
30
+ expectTypeOf<Extract<keyof Installation['application'], 'config'>>().toEqualTypeOf<never>()
30
31
  })
31
32
 
32
33
  test('InstallationBase — the application sub-object carries name and reference', () => {
@@ -53,11 +54,21 @@ test('Installation — multiple tokens add each field', () => {
53
54
  expectTypeOf<Extract<keyof Both, 'activeConfig'>>().toEqualTypeOf<never>()
54
55
  })
55
56
 
57
+ test('Installation — module federation manifest include adds the manifest', () => {
58
+ type Item = Installation<'config.mfManifest'>
59
+ expectTypeOf<Item['application']['config']['mfManifest']>().toEqualTypeOf<unknown>()
60
+ expectTypeOf<
61
+ KeyModifier<Item['application']['config'], 'mfManifest'>
62
+ >().toEqualTypeOf<'optional'>()
63
+ expectTypeOf<Extract<keyof Item, IncludeKeys>>().toEqualTypeOf<never>()
64
+ })
65
+
56
66
  test('Installation — a wide include array makes the fields optional', () => {
57
67
  type Wide = Installation<InstallationInclude>
58
68
  expectTypeOf<KeyModifier<Wide, 'access'>>().toEqualTypeOf<'optional'>()
59
69
  expectTypeOf<KeyModifier<Wide, 'interfaces'>>().toEqualTypeOf<'optional'>()
60
70
  expectTypeOf<KeyModifier<Wide, 'activeConfig'>>().toEqualTypeOf<'optional'>()
71
+ expectTypeOf<KeyModifier<Wide['application'], 'config'>>().toEqualTypeOf<'optional'>()
61
72
  })
62
73
 
63
74
  test('installations.resolveState — resolves the wide list envelope', () => {
@@ -31,7 +31,7 @@ describe('installations', () => {
31
31
 
32
32
  const result = await installations.resolveState(instance, {
33
33
  organizationId: 'org1',
34
- include: ['interfaces'],
34
+ include: ['interfaces', 'config.mfManifest'],
35
35
  })
36
36
 
37
37
  expect(result).toEqual(response)
@@ -41,7 +41,7 @@ describe('installations', () => {
41
41
  })
42
42
  expect(request).toHaveBeenCalledWith({
43
43
  url: '/installations',
44
- query: {organizationId: 'org1', include: 'interfaces'},
44
+ query: {organizationId: 'org1', include: 'config.mfManifest,interfaces'},
45
45
  tag: 'installations.list',
46
46
  })
47
47
  })
@@ -50,12 +50,14 @@ describe('installations', () => {
50
50
  const inst = {id: 'inst1', applicationId: 'app1'}
51
51
  request.mockReturnValue(of(inst))
52
52
 
53
- const result = await installation.resolveState(instance, 'inst1')
53
+ const result = await installation.resolveState(instance, 'inst1', {
54
+ include: ['config.mfManifest'],
55
+ })
54
56
 
55
57
  expect(result).toEqual(inst)
56
58
  expect(request).toHaveBeenCalledWith({
57
59
  url: '/installations/inst1',
58
- query: {},
60
+ query: {include: 'config.mfManifest'},
59
61
  tag: 'installations.get',
60
62
  })
61
63
  })
@@ -1,6 +1,6 @@
1
1
  import {switchMap} from 'rxjs'
2
2
 
3
- import {type ApplicationInterface} from '../applications/applications'
3
+ import {type Application, type ApplicationInterface} from '../applications/applications'
4
4
  import {getClientState} from '../client/clientStore'
5
5
  import {defineFetcher} from '../store/fetcherStore'
6
6
  import {buildQuery} from '../utils/buildQuery'
@@ -16,7 +16,7 @@ const API_VERSION = 'vX'
16
16
  * @see https://www.sanity.io/docs/http-reference/applications-api
17
17
  * @public
18
18
  */
19
- export type InstallationInclude = 'activeConfig' | 'access' | 'interfaces'
19
+ export type InstallationInclude = 'activeConfig' | 'access' | 'interfaces' | 'config.mfManifest'
20
20
 
21
21
  /**
22
22
  * A resource an installation accesses, embedded when `access` is included.
@@ -80,6 +80,17 @@ export interface InstallationBase {
80
80
  * @public
81
81
  */
82
82
  export type Installation<Include extends InstallationInclude = never> = InstallationBase &
83
+ Included<
84
+ InstallationInclude,
85
+ 'config.mfManifest',
86
+ Include,
87
+ {
88
+ application: InstallationBase['application'] &
89
+ ([InstallationInclude] extends [Include]
90
+ ? Partial<Pick<Application<'config.mfManifest'>, 'config'>>
91
+ : Pick<Application<'config.mfManifest'>, 'config'>)
92
+ }
93
+ > &
83
94
  Included<
84
95
  InstallationInclude,
85
96
  'activeConfig',