@sanity/sdk 2.9.0 → 2.11.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/_chunks-dts/utils.d.ts +295 -69
- package/dist/_chunks-es/_internal.js +3 -14
- package/dist/_chunks-es/_internal.js.map +1 -1
- package/dist/_chunks-es/createGroqSearchFilter.js +129 -59
- package/dist/_chunks-es/createGroqSearchFilter.js.map +1 -1
- package/dist/_chunks-es/version.js +1 -1
- package/dist/_exports/_internal.d.ts +16 -2
- package/dist/_exports/_internal.js +3 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +275 -149
- package/dist/index.js.map +1 -1
- package/package.json +11 -15
- package/src/_exports/_internal.ts +1 -0
- package/src/_exports/index.ts +33 -2
- package/src/agent/agentActions.ts +21 -25
- package/src/client/clientStore.test.ts +24 -60
- package/src/client/clientStore.ts +49 -56
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/node/actions/getOrCreateNode.test.ts +5 -2
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +3 -3
- package/src/config/sanityConfig.ts +72 -13
- package/src/document/applyDocumentActions.test.ts +7 -7
- package/src/document/applyDocumentActions.ts +5 -5
- package/src/document/documentStore.test.ts +68 -62
- package/src/document/documentStore.ts +33 -38
- package/src/document/processActions.ts +2 -2
- package/src/document/reducers.ts +4 -4
- package/src/document/sharedListener.ts +5 -7
- package/src/organization/organization.test-d.ts +102 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +166 -0
- package/src/organizations/organizations.test-d.ts +77 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +132 -0
- package/src/presence/bifurTransport.test.ts +46 -6
- package/src/presence/bifurTransport.ts +13 -1
- package/src/presence/presenceStore.test.ts +101 -5
- package/src/presence/presenceStore.ts +96 -24
- package/src/preview/getPreviewState.ts +1 -1
- package/src/preview/previewProjectionUtils.test.ts +4 -4
- package/src/preview/previewProjectionUtils.ts +6 -7
- package/src/preview/resolvePreview.ts +5 -1
- package/src/project/project.test-d.ts +93 -0
- package/src/project/project.test.ts +108 -10
- package/src/project/project.ts +152 -26
- package/src/projection/getProjectionState.ts +4 -4
- package/src/projection/projectionStore.test.ts +2 -2
- package/src/projection/resolveProjection.ts +2 -2
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +11 -15
- package/src/projects/projects.test-d.ts +38 -0
- package/src/projects/projects.test.ts +104 -38
- package/src/projects/projects.ts +74 -14
- package/src/query/queryStore.test.ts +12 -12
- package/src/query/queryStore.ts +10 -11
- package/src/query/reducers.ts +3 -3
- package/src/releases/getPerspectiveState.ts +5 -5
- package/src/releases/releasesStore.test.ts +6 -6
- package/src/releases/releasesStore.ts +9 -9
- package/src/store/createActionBinder.test.ts +31 -31
- package/src/store/createActionBinder.ts +43 -38
- package/src/store/createSanityInstance.ts +5 -6
- package/src/telemetry/devMode.test.ts +8 -0
- package/src/telemetry/devMode.ts +10 -9
- package/src/telemetry/initTelemetry.test.ts +0 -17
- package/src/telemetry/initTelemetry.ts +2 -12
- package/src/users/reducers.ts +3 -4
- package/src/utils/createFetcherStore.ts +6 -4
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
|
@@ -67,7 +67,7 @@ let instance: SanityInstance
|
|
|
67
67
|
let instance1: SanityInstance
|
|
68
68
|
let instance2: SanityInstance
|
|
69
69
|
|
|
70
|
-
const
|
|
70
|
+
const resource = {projectId: 'p', dataset: 'd'}
|
|
71
71
|
const source1 = {projectId: 'p', dataset: 'd1'}
|
|
72
72
|
const source2 = {projectId: 'p', dataset: 'd2'}
|
|
73
73
|
|
|
@@ -99,7 +99,7 @@ it('creates, edits, and publishes a document', async () => {
|
|
|
99
99
|
// Create a new document
|
|
100
100
|
const {appeared} = await applyDocumentActions(instance, {
|
|
101
101
|
actions: [createDocument(doc)],
|
|
102
|
-
|
|
102
|
+
resource,
|
|
103
103
|
})
|
|
104
104
|
expect(appeared).toContain(getDraftId(documentId))
|
|
105
105
|
|
|
@@ -109,7 +109,7 @@ it('creates, edits, and publishes a document', async () => {
|
|
|
109
109
|
// Edit the document – add a title
|
|
110
110
|
await applyDocumentActions(instance, {
|
|
111
111
|
actions: [editDocument(doc, {set: {title: 'My First Article'}})],
|
|
112
|
-
|
|
112
|
+
resource,
|
|
113
113
|
})
|
|
114
114
|
currentDoc = documentState.getCurrent()
|
|
115
115
|
expect(currentDoc?.title).toEqual('My First Article')
|
|
@@ -117,7 +117,7 @@ it('creates, edits, and publishes a document', async () => {
|
|
|
117
117
|
// Publish the document; the resulting transactionId is used as the new _rev
|
|
118
118
|
const {transactionId, submitted} = await applyDocumentActions(instance, {
|
|
119
119
|
actions: [publishDocument(doc)],
|
|
120
|
-
|
|
120
|
+
resource,
|
|
121
121
|
})
|
|
122
122
|
await submitted()
|
|
123
123
|
currentDoc = documentState.getCurrent()
|
|
@@ -144,7 +144,7 @@ it('creates a document with initial values', async () => {
|
|
|
144
144
|
count: 42,
|
|
145
145
|
}),
|
|
146
146
|
],
|
|
147
|
-
|
|
147
|
+
resource,
|
|
148
148
|
})
|
|
149
149
|
expect(appeared).toContain(getDraftId(documentId))
|
|
150
150
|
|
|
@@ -177,7 +177,7 @@ it('edits existing documents', async () => {
|
|
|
177
177
|
|
|
178
178
|
await applyDocumentActions(instance, {
|
|
179
179
|
actions: [editDocument(doc, {set: {title: 'updated title'}})],
|
|
180
|
-
|
|
180
|
+
resource,
|
|
181
181
|
})
|
|
182
182
|
expect(state.getCurrent()).toMatchObject({
|
|
183
183
|
_id: getDraftId(documentId),
|
|
@@ -208,11 +208,11 @@ it('sets optimistic changes synchronously', async () => {
|
|
|
208
208
|
|
|
209
209
|
// then the actions are synchronous
|
|
210
210
|
expect(state1.getCurrent()).toBeNull()
|
|
211
|
-
applyDocumentActions(instance1, {actions: [createDocument(doc1)],
|
|
211
|
+
applyDocumentActions(instance1, {actions: [createDocument(doc1)], resource: source1})
|
|
212
212
|
expect(state1.getCurrent()).toMatchObject({_id: getDraftId(doc1.documentId)})
|
|
213
213
|
const actionResult1Promise = applyDocumentActions(instance1, {
|
|
214
214
|
actions: [editDocument(doc1, {set: {title: 'initial title'}})],
|
|
215
|
-
|
|
215
|
+
resource: source1,
|
|
216
216
|
})
|
|
217
217
|
expect(state1.getCurrent()?.title).toBe('initial title')
|
|
218
218
|
|
|
@@ -233,7 +233,7 @@ it('sets optimistic changes synchronously', async () => {
|
|
|
233
233
|
// synchronous for state 2
|
|
234
234
|
const actionResult2Promise = applyDocumentActions(instance2, {
|
|
235
235
|
actions: [editDocument(doc2, {set: {title: 'updated title'}})],
|
|
236
|
-
|
|
236
|
+
resource: source2,
|
|
237
237
|
})
|
|
238
238
|
expect(state2.getCurrent()?.title).toBe('updated title')
|
|
239
239
|
// async for state 1
|
|
@@ -256,7 +256,7 @@ it('propagates changes between two instances', async () => {
|
|
|
256
256
|
const state2Unsubscribe = state2.subscribe()
|
|
257
257
|
|
|
258
258
|
// Create the document from instance1.
|
|
259
|
-
await applyDocumentActions(instance1, {actions: [createDocument(doc)],
|
|
259
|
+
await applyDocumentActions(instance1, {actions: [createDocument(doc)], resource: source1}).then(
|
|
260
260
|
(r) => r.submitted(),
|
|
261
261
|
)
|
|
262
262
|
|
|
@@ -268,7 +268,7 @@ it('propagates changes between two instances', async () => {
|
|
|
268
268
|
// Now, edit the document from instance2.
|
|
269
269
|
await applyDocumentActions(instance2, {
|
|
270
270
|
actions: [editDocument(doc, {set: {title: 'Hello world!'}})],
|
|
271
|
-
|
|
271
|
+
resource: source2,
|
|
272
272
|
}).then((r) => r.submitted())
|
|
273
273
|
|
|
274
274
|
const updated1 = state1.getCurrent()
|
|
@@ -296,19 +296,19 @@ it('handles concurrent edits and resolves conflicts', async () => {
|
|
|
296
296
|
createDocument(doc),
|
|
297
297
|
editDocument(doc, {set: {title: 'The quick brown fox jumps over the lazy dog'}}),
|
|
298
298
|
],
|
|
299
|
-
|
|
299
|
+
resource,
|
|
300
300
|
}).then((res) => res.submitted())
|
|
301
301
|
|
|
302
302
|
// Both instances now issue an edit simultaneously.
|
|
303
303
|
const p1 = applyDocumentActions(instance1, {
|
|
304
304
|
actions: [editDocument(doc, {set: {title: 'The quick brown fox jumps over the lazy cat'}})],
|
|
305
|
-
|
|
305
|
+
resource: source1,
|
|
306
306
|
}).then((r) => r.submitted())
|
|
307
307
|
const p2 = applyDocumentActions(instance2, {
|
|
308
308
|
actions: [
|
|
309
309
|
editDocument(doc, {set: {title: 'The quick brown elephant jumps over the lazy dog'}}),
|
|
310
310
|
],
|
|
311
|
-
|
|
311
|
+
resource: source2,
|
|
312
312
|
}).then((r) => r.submitted())
|
|
313
313
|
|
|
314
314
|
// Wait for both actions to complete (or reject).
|
|
@@ -331,10 +331,10 @@ it('unpublishes and discards a document', async () => {
|
|
|
331
331
|
const unsubscribe = documentState.subscribe()
|
|
332
332
|
|
|
333
333
|
// Create and publish the document.
|
|
334
|
-
await applyDocumentActions(instance, {actions: [createDocument(doc)],
|
|
334
|
+
await applyDocumentActions(instance, {actions: [createDocument(doc)], resource})
|
|
335
335
|
const afterPublish = await applyDocumentActions(instance, {
|
|
336
336
|
actions: [publishDocument(doc)],
|
|
337
|
-
|
|
337
|
+
resource,
|
|
338
338
|
})
|
|
339
339
|
const publishedDoc = documentState.getCurrent()
|
|
340
340
|
expect(publishedDoc).toMatchObject({
|
|
@@ -343,13 +343,13 @@ it('unpublishes and discards a document', async () => {
|
|
|
343
343
|
})
|
|
344
344
|
|
|
345
345
|
// Unpublish the document (which should delete the published version and create a draft).
|
|
346
|
-
await applyDocumentActions(instance, {actions: [unpublishDocument(doc)],
|
|
346
|
+
await applyDocumentActions(instance, {actions: [unpublishDocument(doc)], resource})
|
|
347
347
|
const afterUnpublish = documentState.getCurrent()
|
|
348
348
|
// In our mock implementation the _id remains the same but the published copy is removed.
|
|
349
349
|
expect(afterUnpublish?._id).toEqual(getDraftId(documentId))
|
|
350
350
|
|
|
351
351
|
// Discard the draft (which deletes the draft version).
|
|
352
|
-
await applyDocumentActions(instance, {actions: [discardDocument(doc)],
|
|
352
|
+
await applyDocumentActions(instance, {actions: [discardDocument(doc)], resource})
|
|
353
353
|
const afterDiscard = documentState.getCurrent()
|
|
354
354
|
expect(afterDiscard).toBeNull()
|
|
355
355
|
|
|
@@ -365,13 +365,13 @@ it('deletes a document', async () => {
|
|
|
365
365
|
|
|
366
366
|
await applyDocumentActions(instance, {
|
|
367
367
|
actions: [createDocument(doc), publishDocument(doc)],
|
|
368
|
-
|
|
368
|
+
resource,
|
|
369
369
|
})
|
|
370
370
|
const docValue = documentState.getCurrent()
|
|
371
371
|
expect(docValue).toBeDefined()
|
|
372
372
|
|
|
373
373
|
// Delete the document.
|
|
374
|
-
await applyDocumentActions(instance, {actions: [deleteDocument(doc)],
|
|
374
|
+
await applyDocumentActions(instance, {actions: [deleteDocument(doc)], resource})
|
|
375
375
|
const afterDelete = documentState.getCurrent()
|
|
376
376
|
expect(afterDelete).toBeNull()
|
|
377
377
|
|
|
@@ -387,7 +387,7 @@ it('cleans up document state when there are no subscribers', async () => {
|
|
|
387
387
|
const unsubscribe = documentState.subscribe()
|
|
388
388
|
|
|
389
389
|
// Create a document.
|
|
390
|
-
await applyDocumentActions(instance, {actions: [createDocument(doc)],
|
|
390
|
+
await applyDocumentActions(instance, {actions: [createDocument(doc)], resource})
|
|
391
391
|
expect(documentState.getCurrent()).toBeDefined()
|
|
392
392
|
|
|
393
393
|
// Unsubscribe from the document.
|
|
@@ -414,7 +414,7 @@ it('fetches documents if there are no active subscriptions for the actions appli
|
|
|
414
414
|
// transaction for this action has been accepted by the server
|
|
415
415
|
const setNewTitle = applyDocumentActions(instance, {
|
|
416
416
|
actions: [editDocument(doc, {set: {title: 'new title'}})],
|
|
417
|
-
|
|
417
|
+
resource,
|
|
418
418
|
})
|
|
419
419
|
expect(getCurrent()?.title).toBeUndefined()
|
|
420
420
|
expect(getDocumentSyncStatus(instance, doc).getCurrent()).toBe(false)
|
|
@@ -425,12 +425,12 @@ it('fetches documents if there are no active subscriptions for the actions appli
|
|
|
425
425
|
// there is an active subscriber now so the edits are synchronous
|
|
426
426
|
applyDocumentActions(instance, {
|
|
427
427
|
actions: [editDocument(doc, {set: {title: 'updated title'}})],
|
|
428
|
-
|
|
428
|
+
resource,
|
|
429
429
|
})
|
|
430
430
|
expect(getCurrent()?.title).toBe('updated title')
|
|
431
431
|
applyDocumentActions(instance, {
|
|
432
432
|
actions: [editDocument(doc, {set: {title: 'updated title!'}})],
|
|
433
|
-
|
|
433
|
+
resource,
|
|
434
434
|
})
|
|
435
435
|
expect(getCurrent()?.title).toBe('updated title!')
|
|
436
436
|
|
|
@@ -439,7 +439,7 @@ it('fetches documents if there are no active subscriptions for the actions appli
|
|
|
439
439
|
// await submitted in order to test that there is no subscriptions
|
|
440
440
|
const result = await applyDocumentActions(instance, {
|
|
441
441
|
actions: [editDocument(doc, {set: {title: 'updated title'}})],
|
|
442
|
-
|
|
442
|
+
resource,
|
|
443
443
|
})
|
|
444
444
|
await result.submitted()
|
|
445
445
|
|
|
@@ -448,7 +448,7 @@ it('fetches documents if there are no active subscriptions for the actions appli
|
|
|
448
448
|
|
|
449
449
|
const setNewNewTitle = applyDocumentActions(instance, {
|
|
450
450
|
actions: [editDocument(doc, {set: {title: 'new new title'}})],
|
|
451
|
-
|
|
451
|
+
resource,
|
|
452
452
|
})
|
|
453
453
|
// now we'll have to await again
|
|
454
454
|
expect(getCurrent()?.title).toBe(undefined)
|
|
@@ -463,15 +463,18 @@ it('batches edit transaction into one outgoing transaction', async () => {
|
|
|
463
463
|
const unsubscribe = getDocumentState(instance, doc).subscribe()
|
|
464
464
|
|
|
465
465
|
// this creates its own transaction
|
|
466
|
-
applyDocumentActions(instance, {actions: [createDocument(doc)],
|
|
466
|
+
applyDocumentActions(instance, {actions: [createDocument(doc)], resource})
|
|
467
467
|
|
|
468
468
|
// these get batched into one
|
|
469
|
-
applyDocumentActions(instance, {actions: [editDocument(doc, {set: {title: 'name!'}})],
|
|
470
|
-
applyDocumentActions(instance, {actions: [editDocument(doc, {set: {title: 'name!!'}})],
|
|
471
|
-
applyDocumentActions(instance, {
|
|
469
|
+
applyDocumentActions(instance, {actions: [editDocument(doc, {set: {title: 'name!'}})], resource})
|
|
470
|
+
applyDocumentActions(instance, {actions: [editDocument(doc, {set: {title: 'name!!'}})], resource})
|
|
471
|
+
applyDocumentActions(instance, {
|
|
472
|
+
actions: [editDocument(doc, {set: {title: 'name!!!'}})],
|
|
473
|
+
resource,
|
|
474
|
+
})
|
|
472
475
|
const res = await applyDocumentActions(instance, {
|
|
473
476
|
actions: [editDocument(doc, {set: {title: 'name!!!!'}})],
|
|
474
|
-
|
|
477
|
+
resource,
|
|
475
478
|
})
|
|
476
479
|
await res.submitted()
|
|
477
480
|
|
|
@@ -503,7 +506,7 @@ it('submits liveEdit document edits through observable.mutate', async () => {
|
|
|
503
506
|
|
|
504
507
|
const result = await applyDocumentActions(instance, {
|
|
505
508
|
actions: [editDocument(liveDoc, {set: {title: 'patched via mutate'}})],
|
|
506
|
-
|
|
509
|
+
resource,
|
|
507
510
|
})
|
|
508
511
|
await result.submitted()
|
|
509
512
|
|
|
@@ -539,7 +542,7 @@ it('provides the consistency status via `getDocumentSyncStatus`', async () => {
|
|
|
539
542
|
const unsubscribe = syncStatus.subscribe()
|
|
540
543
|
expect(syncStatus.getCurrent()).toBe(true)
|
|
541
544
|
|
|
542
|
-
const applied = applyDocumentActions(instance, {actions: [createDocument(doc)],
|
|
545
|
+
const applied = applyDocumentActions(instance, {actions: [createDocument(doc)], resource})
|
|
543
546
|
expect(syncStatus.getCurrent()).toBe(false)
|
|
544
547
|
|
|
545
548
|
const createResult = await applied
|
|
@@ -550,15 +553,15 @@ it('provides the consistency status via `getDocumentSyncStatus`', async () => {
|
|
|
550
553
|
|
|
551
554
|
applyDocumentActions(instance, {
|
|
552
555
|
actions: [editDocument(doc, {set: {title: 'initial name'}})],
|
|
553
|
-
|
|
556
|
+
resource,
|
|
554
557
|
})
|
|
555
558
|
expect(syncStatus.getCurrent()).toBe(false)
|
|
556
559
|
|
|
557
560
|
applyDocumentActions(instance, {
|
|
558
561
|
actions: [editDocument(doc, {set: {title: 'updated name'}})],
|
|
559
|
-
|
|
562
|
+
resource,
|
|
560
563
|
})
|
|
561
|
-
const publishResult = applyDocumentActions(instance, {actions: [publishDocument(doc)],
|
|
564
|
+
const publishResult = applyDocumentActions(instance, {actions: [publishDocument(doc)], resource})
|
|
562
565
|
expect(syncStatus.getCurrent()).toBe(false)
|
|
563
566
|
await publishResult.then((res) => res.submitted())
|
|
564
567
|
expect(syncStatus.getCurrent()).toBe(true)
|
|
@@ -576,7 +579,7 @@ it('reverts failed outgoing transaction locally', async () => {
|
|
|
576
579
|
|
|
577
580
|
const revertedEventPromise = new Promise<TransactionRevertedEvent>((resolve) => {
|
|
578
581
|
const unsubscribe = subscribeDocumentEvents(instance, {
|
|
579
|
-
|
|
582
|
+
resource,
|
|
580
583
|
eventHandler: (e) => {
|
|
581
584
|
if (e.type === 'reverted') {
|
|
582
585
|
resolve(e)
|
|
@@ -592,11 +595,11 @@ it('reverts failed outgoing transaction locally', async () => {
|
|
|
592
595
|
const {getCurrent, subscribe} = getDocumentState(instance, doc)
|
|
593
596
|
const unsubscribe = subscribe()
|
|
594
597
|
|
|
595
|
-
await applyDocumentActions(instance, {actions: [createDocument(doc)],
|
|
596
|
-
applyDocumentActions(instance, {actions: [editDocument(doc, {set: {title: 'the'}})],
|
|
598
|
+
await applyDocumentActions(instance, {actions: [createDocument(doc)], resource})
|
|
599
|
+
applyDocumentActions(instance, {actions: [editDocument(doc, {set: {title: 'the'}})], resource})
|
|
597
600
|
applyDocumentActions(instance, {
|
|
598
601
|
actions: [editDocument(doc, {set: {title: 'the quick'}})],
|
|
599
|
-
|
|
602
|
+
resource,
|
|
600
603
|
})
|
|
601
604
|
|
|
602
605
|
// this edit action is simulated to fail from the backend and will be reverted
|
|
@@ -604,16 +607,16 @@ it('reverts failed outgoing transaction locally', async () => {
|
|
|
604
607
|
actions: [editDocument(doc, {set: {title: 'the quick brown'}})],
|
|
605
608
|
transactionId: 'force-revert',
|
|
606
609
|
disableBatching: true,
|
|
607
|
-
|
|
610
|
+
resource,
|
|
608
611
|
})
|
|
609
612
|
|
|
610
613
|
applyDocumentActions(instance, {
|
|
611
614
|
actions: [editDocument(doc, {set: {title: 'the quick brown fox'}})],
|
|
612
|
-
|
|
615
|
+
resource,
|
|
613
616
|
})
|
|
614
617
|
await applyDocumentActions(instance, {
|
|
615
618
|
actions: [editDocument(doc, {set: {title: 'the quick brown fox jumps'}})],
|
|
616
|
-
|
|
619
|
+
resource,
|
|
617
620
|
}).then((e) => e.submitted())
|
|
618
621
|
|
|
619
622
|
await expect(revertedEventPromise).resolves.toMatchObject({
|
|
@@ -633,7 +636,7 @@ it('reverts failed outgoing transaction locally', async () => {
|
|
|
633
636
|
// check that we can still edit after recovering from the error
|
|
634
637
|
applyDocumentActions(instance, {
|
|
635
638
|
actions: [editDocument(doc, {set: {title: 'TEST the quick fox jumps'}})],
|
|
636
|
-
|
|
639
|
+
resource,
|
|
637
640
|
})
|
|
638
641
|
expect(getCurrent()?.title).toBe('TEST the quick fox jumps')
|
|
639
642
|
|
|
@@ -644,7 +647,7 @@ it('reverts failed outgoing transaction locally', async () => {
|
|
|
644
647
|
it('removes a queued transaction if it fails to apply', async () => {
|
|
645
648
|
const actionErrorEventPromise = new Promise<ActionErrorEvent>((resolve) => {
|
|
646
649
|
const unsubscribe = subscribeDocumentEvents(instance, {
|
|
647
|
-
|
|
650
|
+
resource,
|
|
648
651
|
eventHandler: (e) => {
|
|
649
652
|
if (e.type === 'error') {
|
|
650
653
|
resolve(e)
|
|
@@ -661,7 +664,7 @@ it('removes a queued transaction if it fails to apply', async () => {
|
|
|
661
664
|
await expect(
|
|
662
665
|
applyDocumentActions(instance, {
|
|
663
666
|
actions: [editDocument(doc, {set: {title: "can't set"}})],
|
|
664
|
-
|
|
667
|
+
resource,
|
|
665
668
|
}),
|
|
666
669
|
).rejects.toThrowError(/Cannot edit document/)
|
|
667
670
|
|
|
@@ -672,8 +675,11 @@ it('removes a queued transaction if it fails to apply', async () => {
|
|
|
672
675
|
})
|
|
673
676
|
|
|
674
677
|
// editing should still work after though (no crashing)
|
|
675
|
-
await applyDocumentActions(instance, {actions: [createDocument(doc)],
|
|
676
|
-
applyDocumentActions(instance, {
|
|
678
|
+
await applyDocumentActions(instance, {actions: [createDocument(doc)], resource})
|
|
679
|
+
applyDocumentActions(instance, {
|
|
680
|
+
actions: [editDocument(doc, {set: {title: 'can set!'}})],
|
|
681
|
+
resource,
|
|
682
|
+
})
|
|
677
683
|
|
|
678
684
|
expect(state.getCurrent()?.title).toBe('can set!')
|
|
679
685
|
|
|
@@ -693,7 +699,7 @@ it('returns allowed true when no permission errors occur', async () => {
|
|
|
693
699
|
})
|
|
694
700
|
const state = getDocumentState(instance, doc)
|
|
695
701
|
const unsubscribe = state.subscribe()
|
|
696
|
-
await applyDocumentActions(instance, {actions: [createDocument(doc)],
|
|
702
|
+
await applyDocumentActions(instance, {actions: [createDocument(doc)], resource}).then((r) =>
|
|
697
703
|
r.submitted(),
|
|
698
704
|
)
|
|
699
705
|
|
|
@@ -718,7 +724,7 @@ it("should reject applying the action if a precondition isn't met", async () =>
|
|
|
718
724
|
const doc = createDocumentHandle({documentId: 'does-not-exist', documentType: 'article'})
|
|
719
725
|
|
|
720
726
|
await expect(
|
|
721
|
-
applyDocumentActions(instance, {actions: [deleteDocument(doc)],
|
|
727
|
+
applyDocumentActions(instance, {actions: [deleteDocument(doc)], resource}),
|
|
722
728
|
).rejects.toThrow('The document you are trying to delete does not exist.')
|
|
723
729
|
})
|
|
724
730
|
|
|
@@ -729,7 +735,7 @@ it("should reject applying the action if a permission isn't met", async () => {
|
|
|
729
735
|
vi.mocked(client.request).mockResolvedValue(datasetAcl)
|
|
730
736
|
|
|
731
737
|
await expect(
|
|
732
|
-
applyDocumentActions(instance, {actions: [createDocument(doc)],
|
|
738
|
+
applyDocumentActions(instance, {actions: [createDocument(doc)], resource}),
|
|
733
739
|
).rejects.toThrow('You do not have permission to create a draft for document "does-not-exist".')
|
|
734
740
|
})
|
|
735
741
|
|
|
@@ -768,11 +774,11 @@ it('fetches dataset ACL and updates grants in the document store state', async (
|
|
|
768
774
|
})
|
|
769
775
|
})
|
|
770
776
|
|
|
771
|
-
it('fetches ACL for
|
|
777
|
+
it('fetches ACL for MediaLibraryResource', async () => {
|
|
772
778
|
const mediaLibraryInstance = createSanityInstance({
|
|
773
779
|
projectId: 'p',
|
|
774
780
|
dataset: 'd',
|
|
775
|
-
|
|
781
|
+
resources: {
|
|
776
782
|
'media-library': {mediaLibraryId: 'test-media-library'},
|
|
777
783
|
},
|
|
778
784
|
})
|
|
@@ -785,18 +791,18 @@ it('fetches ACL for MediaLibrarySource', async () => {
|
|
|
785
791
|
|
|
786
792
|
const result = await resolvePermissions(mediaLibraryInstance, {
|
|
787
793
|
actions: [createDocument(doc)],
|
|
788
|
-
|
|
794
|
+
resource: mediaLibrarySource,
|
|
789
795
|
})
|
|
790
796
|
|
|
791
797
|
expect(result).toEqual({allowed: true})
|
|
792
798
|
mediaLibraryInstance.dispose()
|
|
793
799
|
})
|
|
794
800
|
|
|
795
|
-
it('fetches ACL for
|
|
801
|
+
it('fetches ACL for CanvasResource', async () => {
|
|
796
802
|
const canvasInstance = createSanityInstance({
|
|
797
803
|
projectId: 'p',
|
|
798
804
|
dataset: 'd',
|
|
799
|
-
|
|
805
|
+
resources: {
|
|
800
806
|
canvas: {canvasId: 'test-canvas'},
|
|
801
807
|
},
|
|
802
808
|
})
|
|
@@ -809,7 +815,7 @@ it('fetches ACL for CanvasSource', async () => {
|
|
|
809
815
|
|
|
810
816
|
const result = await resolvePermissions(canvasInstance, {
|
|
811
817
|
actions: [createDocument(doc)],
|
|
812
|
-
|
|
818
|
+
resource: canvasSource,
|
|
813
819
|
})
|
|
814
820
|
|
|
815
821
|
expect(result).toEqual({allowed: true})
|
|
@@ -826,7 +832,7 @@ it('returns a promise that resolves when a document has been loaded in the store
|
|
|
826
832
|
const oneOffInstance = createSanityInstance({projectId: 'p', dataset: 'd'})
|
|
827
833
|
const result = await applyDocumentActions(oneOffInstance, {
|
|
828
834
|
actions: [createDocument(doc), editDocument(doc, {set: {title: 'initial title'}})],
|
|
829
|
-
|
|
835
|
+
resource,
|
|
830
836
|
})
|
|
831
837
|
await result.submitted() // wait till submitted to server before resolving
|
|
832
838
|
|
|
@@ -840,7 +846,7 @@ it('returns a promise that resolves when a document has been loaded in the store
|
|
|
840
846
|
|
|
841
847
|
it('emits an event for each action after an outgoing transaction has been accepted', async () => {
|
|
842
848
|
const handler = vi.fn()
|
|
843
|
-
const unsubscribe = subscribeDocumentEvents(instance, {
|
|
849
|
+
const unsubscribe = subscribeDocumentEvents(instance, {resource, eventHandler: handler})
|
|
844
850
|
|
|
845
851
|
const documentId = DocumentId(crypto.randomUUID())
|
|
846
852
|
const doc = createDocumentHandle({documentId, documentType: 'article'})
|
|
@@ -852,7 +858,7 @@ it('emits an event for each action after an outgoing transaction has been accept
|
|
|
852
858
|
editDocument(doc, {set: {title: 'new name'}}),
|
|
853
859
|
publishDocument(doc),
|
|
854
860
|
],
|
|
855
|
-
|
|
861
|
+
resource,
|
|
856
862
|
}).then((e) => e.submitted())
|
|
857
863
|
expect(handler).toHaveBeenCalledTimes(4)
|
|
858
864
|
|
|
@@ -863,7 +869,7 @@ it('emits an event for each action after an outgoing transaction has been accept
|
|
|
863
869
|
editDocument(doc, {set: {title: 'updated name'}}),
|
|
864
870
|
discardDocument(doc),
|
|
865
871
|
],
|
|
866
|
-
|
|
872
|
+
resource,
|
|
867
873
|
}).then((e) => e.submitted())
|
|
868
874
|
expect(handler).toHaveBeenCalledTimes(9)
|
|
869
875
|
|
|
@@ -879,7 +885,7 @@ it('emits an event for each action after an outgoing transaction has been accept
|
|
|
879
885
|
[{type: 'accepted', outgoing: {transactionId: tnx2.transactionId}}],
|
|
880
886
|
])
|
|
881
887
|
|
|
882
|
-
await applyDocumentActions(instance, {actions: [deleteDocument(doc)],
|
|
888
|
+
await applyDocumentActions(instance, {actions: [deleteDocument(doc)], resource})
|
|
883
889
|
|
|
884
890
|
unsubscribe()
|
|
885
891
|
})
|
|
@@ -29,15 +29,15 @@ import {
|
|
|
29
29
|
import {type ClientOptions, getClientState} from '../client/clientStore'
|
|
30
30
|
import {
|
|
31
31
|
type DocumentHandle,
|
|
32
|
-
type
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
type DocumentResource,
|
|
33
|
+
isCanvasResource,
|
|
34
|
+
isDatasetResource,
|
|
35
|
+
isMediaLibraryResource,
|
|
36
36
|
} from '../config/sanityConfig'
|
|
37
37
|
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
38
38
|
import {
|
|
39
|
-
|
|
40
|
-
type
|
|
39
|
+
bindActionByResource,
|
|
40
|
+
type BoundResourceKey,
|
|
41
41
|
type StoreAction,
|
|
42
42
|
} from '../store/createActionBinder'
|
|
43
43
|
import {type SanityInstance} from '../store/createSanityInstance'
|
|
@@ -122,15 +122,15 @@ export interface DocumentState {
|
|
|
122
122
|
unverifiedRevisions?: {[TTransactionId in string]?: UnverifiedDocumentRevision}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
export const documentStore = defineStore<DocumentStoreState,
|
|
125
|
+
export const documentStore = defineStore<DocumentStoreState, BoundResourceKey>({
|
|
126
126
|
name: 'Document',
|
|
127
|
-
getInitialState: (instance, {
|
|
127
|
+
getInitialState: (instance, {resource}) => ({
|
|
128
128
|
documentStates: {},
|
|
129
129
|
// these can be emptied on refetch
|
|
130
130
|
queued: [],
|
|
131
131
|
applied: [],
|
|
132
|
-
sharedListener: createSharedListener(instance,
|
|
133
|
-
fetchDocument: createFetchDocument(instance,
|
|
132
|
+
sharedListener: createSharedListener(instance, resource),
|
|
133
|
+
fetchDocument: createFetchDocument(instance, resource),
|
|
134
134
|
events: new Subject(),
|
|
135
135
|
}),
|
|
136
136
|
initialize(context) {
|
|
@@ -198,7 +198,7 @@ export function getDocumentState(
|
|
|
198
198
|
return _getDocumentState(...args)
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
const _getDocumentState =
|
|
201
|
+
const _getDocumentState = bindActionByResource(
|
|
202
202
|
documentStore,
|
|
203
203
|
createStateSourceAction({
|
|
204
204
|
selector: ({state: {error, documentStates}}, options: DocumentOptions<string | undefined>) => {
|
|
@@ -255,7 +255,7 @@ export function resolveDocument(
|
|
|
255
255
|
): Promise<SanityDocument | null> {
|
|
256
256
|
return _resolveDocument(...args)
|
|
257
257
|
}
|
|
258
|
-
const _resolveDocument =
|
|
258
|
+
const _resolveDocument = bindActionByResource(
|
|
259
259
|
documentStore,
|
|
260
260
|
({instance}, docHandle: DocumentHandle<string, string, string>) => {
|
|
261
261
|
return firstValueFrom(
|
|
@@ -268,7 +268,7 @@ const _resolveDocument = bindActionBySource(
|
|
|
268
268
|
)
|
|
269
269
|
|
|
270
270
|
/** @beta */
|
|
271
|
-
export const getDocumentSyncStatus =
|
|
271
|
+
export const getDocumentSyncStatus = bindActionByResource(
|
|
272
272
|
documentStore,
|
|
273
273
|
createStateSourceAction({
|
|
274
274
|
selector: (
|
|
@@ -300,12 +300,12 @@ export const getDocumentSyncStatus = bindActionBySource(
|
|
|
300
300
|
)
|
|
301
301
|
|
|
302
302
|
type PermissionsStateOptions = {
|
|
303
|
-
|
|
303
|
+
resource?: DocumentResource
|
|
304
304
|
actions: DocumentAction[]
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
/** @beta */
|
|
308
|
-
export const getPermissionsState =
|
|
308
|
+
export const getPermissionsState = bindActionByResource(
|
|
309
309
|
documentStore,
|
|
310
310
|
createStateSourceAction({
|
|
311
311
|
selector: calculatePermissions,
|
|
@@ -320,7 +320,7 @@ export const getPermissionsState = bindActionBySource(
|
|
|
320
320
|
)
|
|
321
321
|
|
|
322
322
|
/** @beta */
|
|
323
|
-
export const resolvePermissions =
|
|
323
|
+
export const resolvePermissions = bindActionByResource(
|
|
324
324
|
documentStore,
|
|
325
325
|
({instance}, options: PermissionsStateOptions) => {
|
|
326
326
|
return firstValueFrom(
|
|
@@ -330,9 +330,9 @@ export const resolvePermissions = bindActionBySource(
|
|
|
330
330
|
)
|
|
331
331
|
|
|
332
332
|
/** @beta */
|
|
333
|
-
export const subscribeDocumentEvents =
|
|
333
|
+
export const subscribeDocumentEvents = bindActionByResource(
|
|
334
334
|
documentStore,
|
|
335
|
-
({state}, options: {
|
|
335
|
+
({state}, options: {resource?: DocumentResource; eventHandler: (e: DocumentEvent) => void}) => {
|
|
336
336
|
const {events} = state.get()
|
|
337
337
|
const subscription = events.subscribe(options.eventHandler)
|
|
338
338
|
return () => subscription.unsubscribe()
|
|
@@ -341,7 +341,7 @@ export const subscribeDocumentEvents = bindActionBySource(
|
|
|
341
341
|
|
|
342
342
|
const subscribeToQueuedAndApplyNextTransaction = ({
|
|
343
343
|
state,
|
|
344
|
-
}: StoreContext<DocumentStoreState,
|
|
344
|
+
}: StoreContext<DocumentStoreState, BoundResourceKey>) => {
|
|
345
345
|
const {events} = state.get()
|
|
346
346
|
return state.observable
|
|
347
347
|
.pipe(
|
|
@@ -372,8 +372,8 @@ const subscribeToQueuedAndApplyNextTransaction = ({
|
|
|
372
372
|
const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
373
373
|
state,
|
|
374
374
|
instance,
|
|
375
|
-
key: {
|
|
376
|
-
}: StoreContext<DocumentStoreState,
|
|
375
|
+
key: {resource},
|
|
376
|
+
}: StoreContext<DocumentStoreState, BoundResourceKey>) => {
|
|
377
377
|
const {events} = state.get()
|
|
378
378
|
|
|
379
379
|
return state.observable
|
|
@@ -396,8 +396,7 @@ const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
|
396
396
|
withLatestFrom(
|
|
397
397
|
getClientState(instance, {
|
|
398
398
|
apiVersion: API_VERSION,
|
|
399
|
-
|
|
400
|
-
source: source && !isDatasetSource(source) ? source : undefined,
|
|
399
|
+
resource,
|
|
401
400
|
}).observable,
|
|
402
401
|
),
|
|
403
402
|
concatMap(([outgoing, client]) => {
|
|
@@ -450,7 +449,7 @@ const subscribeToAppliedAndSubmitNextTransaction = ({
|
|
|
450
449
|
}
|
|
451
450
|
|
|
452
451
|
const subscribeToSubscriptionsAndListenToDocuments = (
|
|
453
|
-
context: StoreContext<DocumentStoreState,
|
|
452
|
+
context: StoreContext<DocumentStoreState, BoundResourceKey>,
|
|
454
453
|
) => {
|
|
455
454
|
const {state} = context
|
|
456
455
|
const {events} = state.get()
|
|
@@ -518,23 +517,19 @@ const subscribeToSubscriptionsAndListenToDocuments = (
|
|
|
518
517
|
const subscribeToClientAndFetchDatasetAcl = ({
|
|
519
518
|
instance,
|
|
520
519
|
state,
|
|
521
|
-
key: {
|
|
522
|
-
}: StoreContext<DocumentStoreState,
|
|
523
|
-
const clientOptions: ClientOptions = {apiVersion: API_VERSION}
|
|
524
|
-
// TODO: remove in v3 when we're ready for everything to be queried via source
|
|
525
|
-
if (source && !isDatasetSource(source)) {
|
|
526
|
-
clientOptions.source = source
|
|
527
|
-
}
|
|
520
|
+
key: {resource},
|
|
521
|
+
}: StoreContext<DocumentStoreState, BoundResourceKey>) => {
|
|
522
|
+
const clientOptions: ClientOptions = {apiVersion: API_VERSION, resource}
|
|
528
523
|
|
|
529
524
|
let uri: string
|
|
530
|
-
if (
|
|
531
|
-
uri = `/projects/${
|
|
532
|
-
} else if (
|
|
533
|
-
uri = `/media-libraries/${
|
|
534
|
-
} else if (
|
|
535
|
-
uri = `/canvases/${
|
|
525
|
+
if (resource && isDatasetResource(resource)) {
|
|
526
|
+
uri = `/projects/${resource.projectId}/datasets/${resource.dataset}/acl`
|
|
527
|
+
} else if (resource && isMediaLibraryResource(resource)) {
|
|
528
|
+
uri = `/media-libraries/${resource.mediaLibraryId}/acl`
|
|
529
|
+
} else if (resource && isCanvasResource(resource)) {
|
|
530
|
+
uri = `/canvases/${resource.canvasId}/acl`
|
|
536
531
|
} else {
|
|
537
|
-
throw new Error(`Received invalid
|
|
532
|
+
throw new Error(`Received invalid resource: ${JSON.stringify(resource)}`)
|
|
538
533
|
}
|
|
539
534
|
|
|
540
535
|
return getClientState(instance, clientOptions)
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
type SanityDocument,
|
|
8
8
|
} from '@sanity/types'
|
|
9
9
|
import {evaluateSync, type ExprNode} from 'groq-js'
|
|
10
|
-
import {isEqual} from 'lodash-es'
|
|
11
10
|
|
|
12
11
|
import {isReleasePerspective} from '../releases/utils/isReleasePerspective'
|
|
12
|
+
import {isDeepEqual} from '../utils/object'
|
|
13
13
|
import {type DocumentAction} from './actions'
|
|
14
14
|
import {type Grant} from './permissions'
|
|
15
15
|
import {type DocumentSet, getId, processMutations} from './processMutations'
|
|
@@ -568,7 +568,7 @@ export function processActions({
|
|
|
568
568
|
|
|
569
569
|
// Before proceeding, verify that the working draft is identical to the base draft.
|
|
570
570
|
// TODO: is it enough just to check for the _rev or nah?
|
|
571
|
-
if (!
|
|
571
|
+
if (!isDeepEqual(workingDraft, baseDraft)) {
|
|
572
572
|
throw new ActionError({
|
|
573
573
|
documentId,
|
|
574
574
|
transactionId,
|