@sanity/sdk 3.6.0 → 3.7.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 +2 -2
- package/dist/_exports/_internal.js +1 -1
- package/dist/_exports/dashboard.d.ts +1 -1
- package/dist/{applications-BbObAGpD.d.ts → applications-Bq3Z9Piv.d.ts} +19 -3
- package/dist/{applications-BbObAGpD.d.ts.map → applications-Bq3Z9Piv.d.ts.map} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +74 -29
- package/dist/index.js.map +1 -1
- package/dist/{topicStore-fZDBrwRP.d.ts → topicStore-CkO7ocKC.d.ts} +2 -2
- package/dist/{topicStore-fZDBrwRP.d.ts.map → topicStore-CkO7ocKC.d.ts.map} +1 -1
- package/dist/{version-7QYPY1nU.js → version-CB-4z0ps.js} +2 -2
- package/dist/{version-7QYPY1nU.js.map → version-CB-4z0ps.js.map} +1 -1
- package/package.json +1 -1
- package/src/_exports/index.ts +1 -0
- package/src/auth/authStore.test.ts +32 -0
- package/src/document/documentConstants.ts +11 -0
- package/src/document/documentStore.test.ts +293 -2
- package/src/document/documentStore.ts +90 -43
- package/src/document/events.ts +22 -7
- package/src/document/submissionErrors.test.ts +63 -0
- package/src/document/submissionErrors.ts +51 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {ClientError, CorsOriginError, ServerError} from '@sanity/client'
|
|
2
|
+
import {describe, expect, it} from 'vitest'
|
|
3
|
+
|
|
4
|
+
import {classifySubmissionError} from './submissionErrors'
|
|
5
|
+
|
|
6
|
+
const clientError = (statusCode: number, body: unknown = {}) =>
|
|
7
|
+
new ClientError({statusCode, headers: {}, body})
|
|
8
|
+
|
|
9
|
+
describe('classifySubmissionError', () => {
|
|
10
|
+
it('retries an expired or missing session', () => {
|
|
11
|
+
expect(
|
|
12
|
+
classifySubmissionError(
|
|
13
|
+
clientError(401, {statusCode: 401, error: 'Unauthorized', message: 'Session not found'}),
|
|
14
|
+
),
|
|
15
|
+
).toBe('retry')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('retries server errors, timeouts, rate limits and network failures', () => {
|
|
19
|
+
expect(classifySubmissionError(new ServerError({statusCode: 503, headers: {}, body: {}}))).toBe(
|
|
20
|
+
'retry',
|
|
21
|
+
)
|
|
22
|
+
expect(classifySubmissionError(clientError(408))).toBe('retry')
|
|
23
|
+
expect(classifySubmissionError(clientError(429))).toBe('retry')
|
|
24
|
+
expect(classifySubmissionError(new Error('Request error while attempting to reach'))).toBe(
|
|
25
|
+
'retry',
|
|
26
|
+
)
|
|
27
|
+
expect(classifySubmissionError('not an error')).toBe('retry')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('retries a transaction that hit contention while committing', () => {
|
|
31
|
+
expect(
|
|
32
|
+
classifySubmissionError(
|
|
33
|
+
clientError(409, {
|
|
34
|
+
error: {type: 'transactionConflictError', description: 'transaction conflict'},
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
).toBe('retry')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('treats an already recorded transaction ID as an ack', () => {
|
|
41
|
+
expect(
|
|
42
|
+
classifySubmissionError(
|
|
43
|
+
clientError(409, {
|
|
44
|
+
error: {type: 'transactionAlreadyExistsError', description: 'already exists'},
|
|
45
|
+
}),
|
|
46
|
+
),
|
|
47
|
+
).toBe('accepted')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('reverts transactions the server rejected', () => {
|
|
51
|
+
for (const statusCode of [400, 402, 403, 404, 410, 412, 413, 422]) {
|
|
52
|
+
expect(classifySubmissionError(clientError(statusCode))).toBe('revert')
|
|
53
|
+
}
|
|
54
|
+
expect(
|
|
55
|
+
classifySubmissionError(
|
|
56
|
+
clientError(409, {
|
|
57
|
+
error: {type: 'documentRevisionIDDoesNotMatchError', description: 'revision mismatch'},
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
).toBe('revert')
|
|
61
|
+
expect(classifySubmissionError(new CorsOriginError({projectId: 'p'}))).toBe('revert')
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {ClientError, CorsOriginError} from '@sanity/client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What the document store does with an outgoing transaction whose submission
|
|
5
|
+
* failed.
|
|
6
|
+
*
|
|
7
|
+
* - `retry`: the failure is about the connection, the credentials or
|
|
8
|
+
* contention on the server, not the transaction. Keep the transaction and
|
|
9
|
+
* its optimistic local state, and submit it again.
|
|
10
|
+
* - `accepted`: the server already recorded this transaction ID, so an earlier
|
|
11
|
+
* attempt was committed even though its response never arrived.
|
|
12
|
+
* - `revert`: the server rejected the transaction itself. Submitting it again
|
|
13
|
+
* cannot succeed, so drop it and rebuild local state from the server copy.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export type SubmissionErrorClass = 'retry' | 'accepted' | 'revert'
|
|
18
|
+
|
|
19
|
+
// Mirrors Studio's `TERMINAL_COMMIT_STATUSES` (checkoutPair.ts) without 401. A
|
|
20
|
+
// 401 rejects the credentials (usually an expired session), not the
|
|
21
|
+
// transaction, so the same transaction succeeds once the user re-authenticates.
|
|
22
|
+
const REJECTED_TRANSACTION_STATUS_CODES = new Set([400, 402, 403, 404, 409, 410, 412, 413, 422])
|
|
23
|
+
|
|
24
|
+
function getErrorType(error: ClientError): unknown {
|
|
25
|
+
const details: unknown = error.details
|
|
26
|
+
if (details && typeof details === 'object' && 'type' in details) return details.type
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Classifies a failed transaction submission. Unknown failures (network
|
|
32
|
+
* errors, timeouts, 5xx and 429 responses, and anything that is not a
|
|
33
|
+
* `ClientError`) default to `retry`: wrongly retrying a rejected transaction
|
|
34
|
+
* leaves the document visibly unsynced with the edits still on screen, while
|
|
35
|
+
* wrongly reverting a transient failure silently discards the user's work.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
export function classifySubmissionError(error: unknown): SubmissionErrorClass {
|
|
40
|
+
if (error instanceof CorsOriginError) return 'revert'
|
|
41
|
+
if (!(error instanceof ClientError)) return 'retry'
|
|
42
|
+
if (error.statusCode === 409) {
|
|
43
|
+
const errorType = getErrorType(error)
|
|
44
|
+
if (errorType === 'transactionAlreadyExistsError') return 'accepted'
|
|
45
|
+
// contention while committing, not a verdict on the transaction; the
|
|
46
|
+
// server rolled it back, so the same transaction ID is safe to resend
|
|
47
|
+
if (errorType === 'transactionConflictError') return 'retry'
|
|
48
|
+
}
|
|
49
|
+
if (REJECTED_TRANSACTION_STATUS_CODES.has(error.statusCode)) return 'revert'
|
|
50
|
+
return 'retry'
|
|
51
|
+
}
|