@sanity/sdk 2.4.0 → 2.6.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 (57) hide show
  1. package/dist/index.d.ts +346 -110
  2. package/dist/index.js +428 -136
  3. package/dist/index.js.map +1 -1
  4. package/package.json +10 -9
  5. package/src/_exports/index.ts +15 -3
  6. package/src/auth/authStore.test.ts +13 -13
  7. package/src/auth/refreshStampedToken.test.ts +16 -16
  8. package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +6 -6
  9. package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +4 -4
  10. package/src/client/clientStore.test.ts +45 -43
  11. package/src/client/clientStore.ts +23 -9
  12. package/src/comlink/controller/actions/destroyController.test.ts +2 -2
  13. package/src/comlink/controller/actions/getOrCreateChannel.test.ts +6 -6
  14. package/src/comlink/controller/actions/getOrCreateController.test.ts +5 -5
  15. package/src/comlink/controller/actions/getOrCreateController.ts +1 -1
  16. package/src/comlink/controller/actions/releaseChannel.test.ts +3 -2
  17. package/src/comlink/controller/comlinkControllerStore.test.ts +4 -4
  18. package/src/comlink/node/actions/getOrCreateNode.test.ts +7 -7
  19. package/src/comlink/node/actions/releaseNode.test.ts +2 -2
  20. package/src/comlink/node/comlinkNodeStore.test.ts +4 -3
  21. package/src/config/loggingConfig.ts +149 -0
  22. package/src/config/sanityConfig.ts +47 -23
  23. package/src/document/actions.ts +11 -7
  24. package/src/document/applyDocumentActions.test.ts +9 -6
  25. package/src/document/applyDocumentActions.ts +9 -49
  26. package/src/document/documentStore.test.ts +128 -115
  27. package/src/document/documentStore.ts +40 -10
  28. package/src/document/permissions.test.ts +9 -9
  29. package/src/document/permissions.ts +17 -7
  30. package/src/document/processActions.test.ts +248 -0
  31. package/src/document/processActions.ts +173 -0
  32. package/src/document/reducers.ts +13 -6
  33. package/src/presence/presenceStore.ts +13 -7
  34. package/src/preview/previewStore.test.ts +10 -2
  35. package/src/preview/previewStore.ts +2 -1
  36. package/src/preview/subscribeToStateAndFetchBatches.test.ts +8 -5
  37. package/src/preview/subscribeToStateAndFetchBatches.ts +9 -3
  38. package/src/projection/projectionStore.test.ts +18 -2
  39. package/src/projection/projectionStore.ts +2 -1
  40. package/src/projection/subscribeToStateAndFetchBatches.test.ts +6 -5
  41. package/src/projection/subscribeToStateAndFetchBatches.ts +9 -3
  42. package/src/query/queryStore.ts +3 -1
  43. package/src/releases/getPerspectiveState.ts +2 -2
  44. package/src/releases/releasesStore.ts +10 -4
  45. package/src/store/createActionBinder.test.ts +8 -6
  46. package/src/store/createActionBinder.ts +54 -28
  47. package/src/store/createSanityInstance.test.ts +85 -1
  48. package/src/store/createSanityInstance.ts +53 -4
  49. package/src/store/createStateSourceAction.test.ts +12 -11
  50. package/src/store/createStateSourceAction.ts +6 -6
  51. package/src/store/createStoreInstance.test.ts +29 -16
  52. package/src/store/createStoreInstance.ts +6 -5
  53. package/src/store/defineStore.test.ts +1 -1
  54. package/src/store/defineStore.ts +12 -7
  55. package/src/utils/logger-usage-example.md +141 -0
  56. package/src/utils/logger.test.ts +757 -0
  57. package/src/utils/logger.ts +537 -0
@@ -49,7 +49,7 @@ describe('getOrCreateChannel', () => {
49
49
  it('should create a new channel using the controller', () => {
50
50
  const createChannelSpy = vi.spyOn(mockController, 'createChannel')
51
51
 
52
- const channel = getOrCreateChannel({state, instance}, channelConfig)
52
+ const channel = getOrCreateChannel({state, instance, key: null}, channelConfig)
53
53
 
54
54
  expect(createChannelSpy).toHaveBeenCalledWith(channelConfig)
55
55
  expect(channel.on).toBeDefined()
@@ -70,17 +70,17 @@ describe('getOrCreateChannel', () => {
70
70
  channels: new Map(),
71
71
  })
72
72
 
73
- expect(() => getOrCreateChannel({state, instance}, channelConfig)).toThrow(
73
+ expect(() => getOrCreateChannel({state, instance, key: null}, channelConfig)).toThrow(
74
74
  'Controller must be initialized before using or creating channels',
75
75
  )
76
76
  })
77
77
 
78
78
  it('should retrieve channel directly from store once created', () => {
79
- const createdChannel = getOrCreateChannel({state, instance}, channelConfig)
79
+ const createdChannel = getOrCreateChannel({state, instance, key: null}, channelConfig)
80
80
  vi.clearAllMocks() // Clear call counts
81
81
 
82
82
  // Retrieve channel again
83
- const retrievedChannel = getOrCreateChannel({state, instance}, channelConfig)
83
+ const retrievedChannel = getOrCreateChannel({state, instance, key: null}, channelConfig)
84
84
  expect(retrievedChannel).toBeDefined()
85
85
  expect(retrievedChannel).toBe(createdChannel)
86
86
 
@@ -95,10 +95,10 @@ describe('getOrCreateChannel', () => {
95
95
  })
96
96
 
97
97
  it('should throw error when trying to create channel with different options', () => {
98
- getOrCreateChannel({state, instance}, channelConfig)
98
+ getOrCreateChannel({state, instance, key: null}, channelConfig)
99
99
 
100
100
  expect(() =>
101
- getOrCreateChannel({state, instance}, {...channelConfig, connectTo: 'window'}),
101
+ getOrCreateChannel({state, instance, key: null}, {...channelConfig, connectTo: 'window'}),
102
102
  ).toThrow('Channel "test" already exists with different options')
103
103
  })
104
104
  })
@@ -43,7 +43,7 @@ describe('getOrCreateController', () => {
43
43
  const controllerSpy = vi.spyOn(comlink, 'createController')
44
44
  const targetOrigin = 'https://test.sanity.dev'
45
45
 
46
- const controller = getOrCreateController({state, instance}, targetOrigin)
46
+ const controller = getOrCreateController({state, instance, key: null}, targetOrigin)
47
47
 
48
48
  expect(controllerSpy).toHaveBeenCalledWith({targetOrigin})
49
49
  expect(controller).toBeDefined()
@@ -56,8 +56,8 @@ describe('getOrCreateController', () => {
56
56
  const controllerSpy = vi.spyOn(comlink, 'createController')
57
57
  const targetOrigin = 'https://test.sanity.dev'
58
58
 
59
- const firstController = getOrCreateController({state, instance}, targetOrigin)
60
- const secondController = getOrCreateController({state, instance}, targetOrigin)
59
+ const firstController = getOrCreateController({state, instance, key: null}, targetOrigin)
60
+ const secondController = getOrCreateController({state, instance, key: null}, targetOrigin)
61
61
 
62
62
  expect(controllerSpy).toHaveBeenCalledTimes(1)
63
63
  expect(firstController).toBe(secondController)
@@ -68,9 +68,9 @@ describe('getOrCreateController', () => {
68
68
  const targetOrigin = 'https://test.sanity.dev'
69
69
  const targetOrigin2 = 'https://test2.sanity.dev'
70
70
 
71
- const firstController = getOrCreateController({state, instance}, targetOrigin)
71
+ const firstController = getOrCreateController({state, instance, key: null}, targetOrigin)
72
72
  const destroySpy = vi.spyOn(firstController, 'destroy')
73
- const secondController = getOrCreateController({state, instance}, targetOrigin2)
73
+ const secondController = getOrCreateController({state, instance, key: null}, targetOrigin2)
74
74
 
75
75
  expect(controllerSpy).toHaveBeenCalledTimes(2)
76
76
  expect(destroySpy).toHaveBeenCalled()
@@ -21,7 +21,7 @@ export const getOrCreateController = (
21
21
  // if the target origin has changed, we'll create a new controller,
22
22
  // but need to clean up first
23
23
  if (controller) {
24
- destroyController({state, instance})
24
+ destroyController({state, instance, key: undefined})
25
25
  }
26
26
 
27
27
  const newController = createController({targetOrigin})
@@ -19,6 +19,7 @@ const channelConfig = {
19
19
  describe('releaseChannel', () => {
20
20
  let instance: SanityInstance
21
21
  let store: StoreInstance<ComlinkControllerState>
22
+ const key = {name: 'global', projectId: 'test-project-id', dataset: 'test-dataset'}
22
23
 
23
24
  let getOrCreateChannel: (
24
25
  inst: SanityInstance,
@@ -29,14 +30,14 @@ describe('releaseChannel', () => {
29
30
 
30
31
  beforeEach(() => {
31
32
  instance = createSanityInstance({projectId: 'test-project-id', dataset: 'test-dataset'})
32
- store = createStoreInstance(instance, comlinkControllerStore)
33
+ store = createStoreInstance(instance, key, comlinkControllerStore)
33
34
 
34
35
  const bind =
35
36
  <TParams extends unknown[], TReturn>(
36
37
  action: StoreAction<ComlinkControllerState, TParams, TReturn>,
37
38
  ) =>
38
39
  (inst: SanityInstance, ...params: TParams) =>
39
- action({instance: inst, state: store.state}, ...params)
40
+ action({instance: inst, state: store.state, key}, ...params)
40
41
 
41
42
  getOrCreateChannel = bind(unboundGetOrCreateChannel)
42
43
  getOrCreateController = bind(unboundGetOrCreateController)
@@ -31,7 +31,7 @@ describe('comlinkControllerStore', () => {
31
31
 
32
32
  // Create store state directly
33
33
  const state = createStoreState<ComlinkControllerState>(
34
- comlinkControllerStore.getInitialState(instance),
34
+ comlinkControllerStore.getInitialState(instance, null),
35
35
  )
36
36
 
37
37
  const initialState = state.get()
@@ -56,13 +56,13 @@ describe('comlinkControllerStore', () => {
56
56
  vi.mocked(bindActionGlobally).mockImplementation(
57
57
  (_storeDef, action) =>
58
58
  (inst: SanityInstance, ...params: unknown[]) =>
59
- action({instance: inst, state}, ...params),
59
+ action({instance: inst, state, key: {name: 'global'}}, ...params),
60
60
  )
61
61
 
62
62
  const {comlinkControllerStore} = await import('./comlinkControllerStore')
63
63
 
64
64
  // Get the cleanup function from the store
65
- const dispose = comlinkControllerStore.initialize?.({state, instance})
65
+ const dispose = comlinkControllerStore.initialize?.({state, instance, key: null})
66
66
 
67
67
  // Run cleanup
68
68
  dispose?.()
@@ -82,7 +82,7 @@ describe('comlinkControllerStore', () => {
82
82
  })
83
83
 
84
84
  // Get the cleanup function
85
- const cleanup = comlinkControllerStore.initialize?.({state, instance})
85
+ const cleanup = comlinkControllerStore.initialize?.({state, instance, key: null})
86
86
 
87
87
  // Should not throw when no controller exists
88
88
  expect(() => cleanup?.()).not.toThrow()
@@ -40,24 +40,24 @@ describe('getOrCreateNode', () => {
40
40
  })
41
41
 
42
42
  it('should create and start a node', () => {
43
- const node = getOrCreateNode({state, instance}, nodeConfig)
43
+ const node = getOrCreateNode({state, instance, key: null}, nodeConfig)
44
44
 
45
45
  expect(comlink.createNode).toHaveBeenCalledWith(nodeConfig)
46
46
  expect(node.start).toHaveBeenCalled()
47
47
  })
48
48
 
49
49
  it('should store the node in nodeStore', () => {
50
- const node = getOrCreateNode({state, instance}, nodeConfig)
50
+ const node = getOrCreateNode({state, instance, key: null}, nodeConfig)
51
51
 
52
- expect(getOrCreateNode({state, instance}, nodeConfig)).toBe(node)
52
+ expect(getOrCreateNode({state, instance, key: null}, nodeConfig)).toBe(node)
53
53
  })
54
54
 
55
55
  it('should throw error when trying to create node with different options', () => {
56
- getOrCreateNode({state, instance}, nodeConfig)
56
+ getOrCreateNode({state, instance, key: null}, nodeConfig)
57
57
 
58
58
  expect(() =>
59
59
  getOrCreateNode(
60
- {state, instance},
60
+ {state, instance, key: null},
61
61
  {
62
62
  ...nodeConfig,
63
63
  connectTo: 'window',
@@ -74,7 +74,7 @@ describe('getOrCreateNode', () => {
74
74
  return statusUnsubMock
75
75
  })
76
76
 
77
- getOrCreateNode({state, instance}, nodeConfig)
77
+ getOrCreateNode({state, instance, key: null}, nodeConfig)
78
78
 
79
79
  expect(mockNode.onStatus).toHaveBeenCalled()
80
80
  expect(state.get().nodes.get(nodeConfig.name)?.statusUnsub).toBe(statusUnsubMock)
@@ -92,7 +92,7 @@ describe('getOrCreateNode', () => {
92
92
  return statusUnsubMock
93
93
  })
94
94
 
95
- getOrCreateNode({state, instance}, nodeConfig)
95
+ getOrCreateNode({state, instance, key: null}, nodeConfig)
96
96
 
97
97
  // Remove the node entry before triggering the status callback
98
98
  state.get().nodes.delete(nodeConfig.name)
@@ -47,7 +47,7 @@ describe('releaseNode', () => {
47
47
  expect(state.get().nodes.has('test-node')).toBe(true)
48
48
 
49
49
  // Release the node
50
- releaseNode({state, instance}, 'test-node')
50
+ releaseNode({state, instance, key: null}, 'test-node')
51
51
 
52
52
  // Check node is removed
53
53
  expect(mockNode.stop).toHaveBeenCalled()
@@ -64,7 +64,7 @@ describe('releaseNode', () => {
64
64
  })
65
65
  state.set('setup', {nodes})
66
66
 
67
- releaseNode({state, instance}, 'test-node')
67
+ releaseNode({state, instance, key: null}, 'test-node')
68
68
 
69
69
  expect(statusUnsub).toHaveBeenCalled()
70
70
  })
@@ -14,7 +14,7 @@ describe('nodeStore', () => {
14
14
  })
15
15
 
16
16
  it('should have correct initial state', () => {
17
- const initialState = comlinkNodeStore.getInitialState(instance)
17
+ const initialState = comlinkNodeStore.getInitialState(instance, null)
18
18
 
19
19
  expect(initialState.nodes).toBeInstanceOf(Map)
20
20
  expect(initialState.nodes.size).toBe(0)
@@ -25,16 +25,17 @@ describe('nodeStore', () => {
25
25
  stop: vi.fn(),
26
26
  } as unknown as Node<WindowMessage, FrameMessage>
27
27
 
28
- const initialState = comlinkNodeStore.getInitialState(instance)
28
+ const initialState = comlinkNodeStore.getInitialState(instance, null)
29
29
  initialState.nodes.set('test-node', {
30
30
  options: {name: 'test-node', connectTo: 'parent'},
31
31
  node: mockNode,
32
- refCount: 1,
32
+ status: 'idle',
33
33
  })
34
34
 
35
35
  const cleanup = comlinkNodeStore.initialize?.({
36
36
  instance,
37
37
  state: createStoreState(initialState),
38
+ key: null,
38
39
  })
39
40
 
40
41
  cleanup?.()
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Public API for configuring SDK logging
3
+ *
4
+ * @module loggingConfig
5
+ */
6
+
7
+ import {configureLogging as _configureLogging, type LoggerConfig} from '../utils/logger'
8
+
9
+ /**
10
+ * Configure logging for the Sanity SDK
11
+ *
12
+ * This function allows you to control what logs are output by the SDK,
13
+ * making it easier to debug issues in development or production.
14
+ *
15
+ * @remarks
16
+ * **Zero-Config via Environment Variable (Recommended):**
17
+ *
18
+ * The SDK automatically reads the `DEBUG` environment variable, making it
19
+ * easy to enable logging without code changes:
20
+ *
21
+ * ```bash
22
+ * # Enable all SDK logging at debug level
23
+ * DEBUG=sanity:* npm start
24
+ *
25
+ * # Enable specific namespaces
26
+ * DEBUG=sanity:auth,sanity:document npm start
27
+ *
28
+ * # Enable trace level for all namespaces
29
+ * DEBUG=sanity:trace:* npm start
30
+ *
31
+ * # Enable internal/maintainer logs
32
+ * DEBUG=sanity:*:internal npm start
33
+ * ```
34
+ *
35
+ * This matches the pattern used by Sanity CLI and Studio, making it familiar
36
+ * and easy for support teams to help troubleshoot issues.
37
+ *
38
+ * **Programmatic Configuration (Advanced):**
39
+ *
40
+ * For more control (custom handlers, dynamic configuration), call this function
41
+ * explicitly. Programmatic configuration overrides environment variables.
42
+ *
43
+ * **For Application Developers:**
44
+ * Use `info`, `warn`, or `error` levels to see high-level SDK activity
45
+ * without being overwhelmed by internal details.
46
+ *
47
+ * **For SDK Maintainers:**
48
+ * Use `debug` or `trace` levels with `internal: true` to see detailed
49
+ * information about store operations, RxJS streams, and state transitions.
50
+ *
51
+ * **Instance Context:**
52
+ * Logs automatically include instance information (projectId, dataset, instanceId)
53
+ * when available, making it easier to debug multi-instance scenarios:
54
+ * ```
55
+ * [INFO] [auth] [project:abc] [dataset:production] User logged in
56
+ * ```
57
+ *
58
+ * **Available Namespaces:**
59
+ * - `sdk` - SDK initialization, configuration, and lifecycle
60
+ * - `auth` - Authentication and authorization (when instrumented in the future)
61
+ * - And more as logging is added to modules
62
+ *
63
+ * @example Zero-config via environment variable (recommended for debugging)
64
+ * ```bash
65
+ * # Just set DEBUG and run your app - no code changes needed!
66
+ * DEBUG=sanity:* npm start
67
+ * ```
68
+ *
69
+ * @example Programmatic configuration (application developer)
70
+ * ```ts
71
+ * import {configureLogging} from '@sanity/sdk'
72
+ *
73
+ * // Log warnings and errors for auth and document operations
74
+ * configureLogging({
75
+ * level: 'warn',
76
+ * namespaces: ['auth', 'document']
77
+ * })
78
+ * ```
79
+ *
80
+ * @example Programmatic configuration (SDK maintainer)
81
+ * ```ts
82
+ * import {configureLogging} from '@sanity/sdk'
83
+ *
84
+ * // Enable all logs including internal traces
85
+ * configureLogging({
86
+ * level: 'trace',
87
+ * namespaces: ['*'],
88
+ * internal: true
89
+ * })
90
+ * ```
91
+ *
92
+ * @example Custom handler (for testing)
93
+ * ```ts
94
+ * import {configureLogging} from '@sanity/sdk'
95
+ *
96
+ * const logs: string[] = []
97
+ * configureLogging({
98
+ * level: 'info',
99
+ * namespaces: ['*'],
100
+ * handler: {
101
+ * error: (msg) => logs.push(msg),
102
+ * warn: (msg) => logs.push(msg),
103
+ * info: (msg) => logs.push(msg),
104
+ * debug: (msg) => logs.push(msg),
105
+ * trace: (msg) => logs.push(msg),
106
+ * }
107
+ * })
108
+ * ```
109
+ *
110
+ * @public
111
+ */
112
+ export function configureLogging(config: LoggerConfig): void {
113
+ _configureLogging(config)
114
+
115
+ // Always log configuration (bypasses namespace filtering)
116
+ // This ensures users see the message regardless of which namespaces they enable
117
+ const configLevel = config.level || 'warn'
118
+ const shouldLog = ['info', 'debug', 'trace'].includes(configLevel) || configLevel === 'warn'
119
+
120
+ if (shouldLog && config.handler?.info) {
121
+ config.handler.info(`[${new Date().toISOString()}] [INFO] [sdk] Logging configured`, {
122
+ level: configLevel,
123
+ namespaces: config.namespaces || [],
124
+ internal: config.internal || false,
125
+ source: 'programmatic',
126
+ })
127
+ } else if (shouldLog) {
128
+ // eslint-disable-next-line no-console
129
+ console.info(`[${new Date().toISOString()}] [INFO] [sdk] Logging configured`, {
130
+ level: configLevel,
131
+ namespaces: config.namespaces || [],
132
+ internal: config.internal || false,
133
+ source: 'programmatic',
134
+ })
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Re-export types for public API
140
+ * @public
141
+ */
142
+ export type {
143
+ InstanceContext,
144
+ LogContext,
145
+ Logger,
146
+ LoggerConfig,
147
+ LogLevel,
148
+ LogNamespace,
149
+ } from '../utils/logger'
@@ -29,15 +29,24 @@ export interface PerspectiveHandle {
29
29
  * @public
30
30
  */
31
31
  export interface DatasetHandle<TDataset extends string = string, TProjectId extends string = string>
32
- extends ProjectHandle<TProjectId>,
33
- PerspectiveHandle {
32
+ extends ProjectHandle<TProjectId>, PerspectiveHandle {
34
33
  dataset?: TDataset
34
+ /**
35
+ * @beta
36
+ * The name of the source to use for this operation.
37
+ */
38
+ sourceName?: string
39
+ /**
40
+ * @beta
41
+ * Explicit source object to use for this operation.
42
+ */
43
+ source?: DocumentSource
35
44
  }
36
45
 
37
46
  /**
38
47
  * Identifies a specific document type within a Sanity dataset and project.
39
48
  * Includes `projectId`, `dataset`, and `documentType`.
40
- * Optionally includes a `documentId`, useful for referencing a specific document type context, potentially without a specific document ID.
49
+ * Optionally includes a `documentId` and `liveEdit` flag.
41
50
  * @public
42
51
  */
43
52
  export interface DocumentTypeHandle<
@@ -47,6 +56,12 @@ export interface DocumentTypeHandle<
47
56
  > extends DatasetHandle<TDataset, TProjectId> {
48
57
  documentId?: string
49
58
  documentType: TDocumentType
59
+ /**
60
+ * Indicates whether this document uses liveEdit mode.
61
+ * When `true`, the document does not use the draft/published model and edits are applied directly to the document.
62
+ * @see https://www.sanity.io/docs/content-lake/drafts#ca0663a8f002
63
+ */
64
+ liveEdit?: boolean
50
65
  }
51
66
 
52
67
  /**
@@ -80,45 +95,54 @@ export interface SanityConfig extends DatasetHandle, PerspectiveHandle {
80
95
  studioMode?: {
81
96
  enabled: boolean
82
97
  }
83
- }
84
98
 
85
- export const SOURCE_ID = '__sanity_internal_sourceId'
99
+ /**
100
+ * @beta
101
+ * A list of named sources to use for this instance.
102
+ */
103
+ sources?: Record<string, DocumentSource>
104
+ }
86
105
 
87
106
  /**
88
107
  * A document source can be used for querying.
108
+ * This will soon be the default way to identify where you are querying from.
89
109
  *
90
110
  * @beta
91
- * @see datasetSource Construct a document source for a given projectId and dataset.
92
- * @see mediaLibrarySource Construct a document source for a mediaLibraryId.
93
- * @see canvasSource Construct a document source for a canvasId.
94
111
  */
95
- export type DocumentSource = {
96
- [SOURCE_ID]: ['media-library', string] | ['canvas', string] | {projectId: string; dataset: string}
97
- }
112
+ export type DocumentSource = DatasetSource | MediaLibrarySource | CanvasSource
113
+
114
+ /**
115
+ * @beta
116
+ */
117
+ export type DatasetSource = {projectId: string; dataset: string}
118
+
119
+ /**
120
+ * @beta
121
+ */
122
+ export type MediaLibrarySource = {mediaLibraryId: string}
98
123
 
99
124
  /**
100
- * Returns a document source for a projectId and dataset.
101
- *
102
125
  * @beta
103
126
  */
104
- export function datasetSource(projectId: string, dataset: string): DocumentSource {
105
- return {[SOURCE_ID]: {projectId, dataset}}
127
+ export type CanvasSource = {canvasId: string}
128
+
129
+ /**
130
+ * @beta
131
+ */
132
+ export function isDatasetSource(source: DocumentSource): source is DatasetSource {
133
+ return 'projectId' in source && 'dataset' in source
106
134
  }
107
135
 
108
136
  /**
109
- * Returns a document source for a Media Library.
110
- *
111
137
  * @beta
112
138
  */
113
- export function mediaLibrarySource(id: string): DocumentSource {
114
- return {[SOURCE_ID]: ['media-library', id]}
139
+ export function isMediaLibrarySource(source: DocumentSource): source is MediaLibrarySource {
140
+ return 'mediaLibraryId' in source
115
141
  }
116
142
 
117
143
  /**
118
- * Returns a document source for a Canvas.
119
- *
120
144
  * @beta
121
145
  */
122
- export function canvasSource(id: string): DocumentSource {
123
- return {[SOURCE_ID]: ['canvas', id]}
146
+ export function isCanvasSource(source: DocumentSource): source is CanvasSource {
147
+ return 'canvasId' in source
124
148
  }
@@ -143,7 +143,9 @@ export function createDocument<
143
143
  return {
144
144
  type: 'document.create',
145
145
  ...doc,
146
- ...(doc.documentId && {documentId: getPublishedId(doc.documentId)}),
146
+ ...(doc.documentId && {
147
+ documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
148
+ }),
147
149
  ...(initialValue && {initialValue}),
148
150
  }
149
151
  }
@@ -164,7 +166,7 @@ export function deleteDocument<
164
166
  return {
165
167
  type: 'document.delete',
166
168
  ...doc,
167
- documentId: getPublishedId(doc.documentId),
169
+ documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
168
170
  }
169
171
  }
170
172
 
@@ -228,12 +230,14 @@ export function editDocument<
228
230
  doc: DocumentHandle<TDocumentType, TDataset, TProjectId>,
229
231
  patches?: PatchOperations | PatchOperations[] | SanityMutatePatchMutation,
230
232
  ): EditDocumentAction<TDocumentType, TDataset, TProjectId> {
233
+ const documentId = doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId)
234
+
231
235
  if (isSanityMutatePatch(patches)) {
232
236
  const converted = convertSanityMutatePatch(patches) ?? []
233
237
  return {
234
238
  ...doc,
235
239
  type: 'document.edit',
236
- documentId: getPublishedId(doc.documentId),
240
+ documentId,
237
241
  patches: converted,
238
242
  }
239
243
  }
@@ -241,7 +245,7 @@ export function editDocument<
241
245
  return {
242
246
  ...doc,
243
247
  type: 'document.edit',
244
- documentId: getPublishedId(doc.documentId),
248
+ documentId,
245
249
  ...(patches && {patches: Array.isArray(patches) ? patches : [patches]}),
246
250
  }
247
251
  }
@@ -262,7 +266,7 @@ export function publishDocument<
262
266
  return {
263
267
  type: 'document.publish',
264
268
  ...doc,
265
- documentId: getPublishedId(doc.documentId),
269
+ documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
266
270
  }
267
271
  }
268
272
 
@@ -282,7 +286,7 @@ export function unpublishDocument<
282
286
  return {
283
287
  type: 'document.unpublish',
284
288
  ...doc,
285
- documentId: getPublishedId(doc.documentId),
289
+ documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
286
290
  }
287
291
  }
288
292
 
@@ -302,6 +306,6 @@ export function discardDocument<
302
306
  return {
303
307
  type: 'document.discard',
304
308
  ...doc,
305
- documentId: getPublishedId(doc.documentId),
309
+ documentId: doc.liveEdit ? doc.documentId : getPublishedId(doc.documentId),
306
310
  }
307
311
  }
@@ -48,11 +48,11 @@ describe('applyDocumentActions', () => {
48
48
  }
49
49
  state = createStoreState(initialState)
50
50
  instance = createSanityInstance({projectId: 'p', dataset: 'd'})
51
+ const key = {name: 'p.d', projectId: 'p', dataset: 'd'}
51
52
 
52
53
  vi.mocked(bindActionByDataset).mockImplementation(
53
- (_storeDef, action) =>
54
- (instanceParam: SanityInstance, ...params: unknown[]) =>
55
- action({instance: instanceParam, state}, ...params),
54
+ (_storeDef, action) => (instanceParam: SanityInstance, options) =>
55
+ action({instance: instanceParam, state, key}, options),
56
56
  )
57
57
  // Import dynamically to ensure mocks are set up before the module under test is loaded
58
58
  const module = await import('./applyDocumentActions')
@@ -72,7 +72,8 @@ describe('applyDocumentActions', () => {
72
72
  }
73
73
 
74
74
  // Call applyDocumentActions with a fixed transactionId for reproducibility.
75
- const applyPromise = applyDocumentActions(instance, action, {
75
+ const applyPromise = applyDocumentActions(instance, {
76
+ actions: [action],
76
77
  transactionId: 'txn-success',
77
78
  })
78
79
 
@@ -128,7 +129,8 @@ describe('applyDocumentActions', () => {
128
129
  }
129
130
 
130
131
  // Call applyDocumentActions with a fixed transactionId.
131
- const applyPromise = applyDocumentActions(instance, action, {
132
+ const applyPromise = applyDocumentActions(instance, {
133
+ actions: [action],
132
134
  transactionId: 'txn-error',
133
135
  })
134
136
 
@@ -160,7 +162,8 @@ describe('applyDocumentActions', () => {
160
162
  dataset: 'd',
161
163
  }
162
164
  // Call applyDocumentActions with the context using childInstance, but with action requiring parent's config
163
- const applyPromise = applyDocumentActions(childInstance, action, {
165
+ const applyPromise = applyDocumentActions(childInstance, {
166
+ actions: [action],
164
167
  transactionId: 'txn-child-match',
165
168
  })
166
169