@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.
- package/dist/_exports/_internal.d.ts +12 -4
- package/dist/_exports/_internal.d.ts.map +1 -1
- package/dist/_exports/_internal.js +21 -3
- package/dist/_exports/_internal.js.map +1 -1
- package/dist/_exports/comlink.d.ts +1 -1
- package/dist/_exports/dashboard.d.ts +1 -1
- package/dist/_exports/dashboard.js +1 -1
- package/dist/{authStore-7dZCfKUq.d.ts → applications-903KQTrD.d.ts} +346 -344
- package/dist/applications-903KQTrD.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{topicStore-Ct7IFNtY.js → topicStore-BJ54_X6H.js} +3 -1
- package/dist/topicStore-BJ54_X6H.js.map +1 -0
- package/dist/{topicStore-CAnJsiqI.d.ts → topicStore-VQILEI1R.d.ts} +54 -25
- package/dist/topicStore-VQILEI1R.d.ts.map +1 -0
- package/dist/{version-CYs30C7v.js → version-CYqZgJX1.js} +2 -2
- package/dist/{version-CYs30C7v.js.map → version-CYqZgJX1.js.map} +1 -1
- package/package.json +7 -7
- package/src/_exports/_internal.ts +2 -0
- package/src/applications/applicationOrigin.test.ts +51 -0
- package/src/applications/applicationOrigin.ts +26 -0
- package/src/dashboard/messageBus/bus.test.ts +39 -0
- package/src/dashboard/messageBus/bus.ts +32 -22
- package/src/dashboard/messageBus/topics.test.ts +36 -2
- package/src/dashboard/messageBus/topics.ts +51 -16
- package/src/installations/installations.test-d.ts +11 -0
- package/src/installations/installations.test.ts +6 -4
- package/src/installations/installations.ts +13 -2
- package/dist/authStore-7dZCfKUq.d.ts.map +0 -1
- package/dist/topicStore-CAnJsiqI.d.ts.map +0 -1
- 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
|
+
"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.
|
|
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.
|
|
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.
|
|
74
|
-
"@vitest/coverage-v8": "^5.0.
|
|
73
|
+
"@types/node": "^24.13.5",
|
|
74
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
75
75
|
"eslint": "^10.10.0",
|
|
76
|
-
"oxfmt": "^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.
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, {
|
|
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, {
|
|
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 = {
|
|
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<
|
|
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
|
|
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
|
|
184
|
+
* Names of state topics exposed by message bus methods.
|
|
155
185
|
* @public
|
|
156
186
|
*/
|
|
157
|
-
export type StateTopic = StateTopicsOf<
|
|
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
|
-
|
|
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
|
|
241
|
+
* Names of event topics exposed by message bus methods.
|
|
208
242
|
* @public
|
|
209
243
|
*/
|
|
210
|
-
export type EventTopic = {
|
|
211
|
-
[K in
|
|
212
|
-
}[
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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',
|