Skip to content

Commit cbfc2cd

Browse files
committed
feat(listen)! enable resume by default
1 parent 742693d commit cbfc2cd

File tree

5 files changed

+37
-66
lines changed

5 files changed

+37
-66
lines changed

src/data/listen.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import {
1212
type OpenEvent,
1313
type ReconnectEvent,
1414
type ResetEvent,
15-
type ResumableListenEventNames,
16-
type ResumableListenOptions,
1715
type WelcomeBackEvent,
1816
type WelcomeEvent,
1917
} from '../types'
@@ -37,7 +35,6 @@ const possibleOptions = [
3735
'includeAllVersions',
3836
'visibility',
3937
'effectFormat',
40-
'enableResume',
4138
'tag',
4239
]
4340

@@ -56,10 +53,7 @@ const defaultOptions = {
5653
*/
5754
export type MapListenEventNamesToListenEvents<
5855
R extends Record<string, Any> = Record<string, Any>,
59-
Events extends (ResumableListenEventNames | ListenEventName)[] = (
60-
| ResumableListenEventNames
61-
| ListenEventName
62-
)[],
56+
Events extends ListenEventName[] = ListenEventName[],
6357
> = Events extends (infer E)[]
6458
? E extends 'welcome'
6559
? WelcomeEvent
@@ -87,13 +81,13 @@ export type MapListenEventNamesToListenEvents<
8781
*/
8882
export type ListenEventFromOptions<
8983
R extends Record<string, Any> = Record<string, Any>,
90-
Opts extends ListenOptions | ResumableListenOptions | undefined = undefined,
91-
> = Opts extends ListenOptions | ResumableListenOptions
92-
? Opts['events'] extends (ResumableListenEventNames | ListenEventName)[]
84+
Opts extends ListenOptions | undefined = undefined,
85+
> = Opts extends ListenOptions
86+
? Opts['events'] extends ListenEventName[]
9387
? MapListenEventNamesToListenEvents<R, Opts['events']>
9488
: // fall back to ListenEvent if opts events is present, but we can't infer the literal event names
9589
ListenEvent<R>
96-
: MutationEvent<R>
90+
: MutationEvent<R> | ResetEvent
9791

9892
/**
9993
* Set up a listener that will be notified when mutations occur on documents matching the provided query/filter.
@@ -107,7 +101,7 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
107101
this: SanityClient | ObservableSanityClient,
108102
query: string,
109103
params?: ListenParams,
110-
): Observable<MutationEvent<R>>
104+
): Observable<MutationEvent<R> | ResetEvent>
111105
/**
112106
* Set up a listener that will be notified when mutations occur on documents matching the provided query/filter.
113107
*
@@ -118,7 +112,7 @@ export function _listen<R extends Record<string, Any> = Record<string, Any>>(
118112
*/
119113
export function _listen<
120114
R extends Record<string, Any> = Record<string, Any>,
121-
Opts extends ListenOptions | ResumableListenOptions = ListenOptions,
115+
Opts extends ListenOptions = ListenOptions,
122116
>(
123117
this: SanityClient | ObservableSanityClient,
124118
query: string,
@@ -139,14 +133,16 @@ export function _listen<
139133
const tag = opts.tag && requestTagPrefix ? [requestTagPrefix, opts.tag].join('.') : opts.tag
140134
const options = {...defaults(opts, defaultOptions), tag}
141135
const listenOpts = pick(options, possibleOptions)
142-
const qs = encodeQueryString({query, params, options: {tag, ...listenOpts}})
136+
const qs = encodeQueryString({query, params, options: {tag, ...listenOpts, enableResume: true}})
143137

144138
const uri = `${url}${_getDataUrl(this, 'listen', qs)}`
145139
if (uri.length > MAX_URL_LENGTH) {
146140
return throwError(() => new Error('Query too large for listener'))
147141
}
148142

149-
const listenFor = (options.events ? options.events : ['mutation']) satisfies Opts['events']
143+
const listenFor = (
144+
options.events ? options.events : ['mutation', 'reset']
145+
) satisfies Opts['events']
150146

151147
const esOptions: EventSourceInit & {headers?: Record<string, string>} = {}
152148
if (withCredentials) {

src/types.ts

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,20 +1176,16 @@ export type ListenEventName =
11761176
| 'welcome'
11771177
/** The listener has been disconnected, and a reconnect attempt is scheduled */
11781178
| 'reconnect'
1179+
/** The listener has reconnected and successfully resumed from where it left off */
1180+
| 'welcomeback'
1181+
/** The listener can't be resumed or otherwise need to reset its local state */
1182+
| 'reset'
11791183
/**
11801184
* The listener connection has been established
11811185
* note: it's usually a better option to use the 'welcome' event
11821186
*/
11831187
| 'open'
11841188

1185-
/** @public */
1186-
export type ResumableListenEventNames =
1187-
| ListenEventName
1188-
/** The listener has reconnected and successfully resumed from where it left off */
1189-
| 'welcomeback'
1190-
/** The listener can't be resumed or otherwise need to reset its local state */
1191-
| 'reset'
1192-
11931189
/** @public */
11941190
export type ListenParams = {[key: string]: Any}
11951191

@@ -1240,8 +1236,7 @@ export interface ListenOptions {
12401236
visibility?: 'transaction' | 'query'
12411237

12421238
/**
1243-
* Array of event names to include in the observable. By default, only mutation events are included.
1244-
* Note: `welcomeback` and `reset` events requires `enableResume: true`
1239+
* Array of event names to include in the observable. By default, only mutation and reset events are included.
12451240
* @defaultValue `['mutation']`
12461241
*/
12471242
events?: ListenEventName[]
@@ -1263,32 +1258,6 @@ export interface ListenOptions {
12631258
* @defaultValue `undefined`
12641259
*/
12651260
tag?: string
1266-
1267-
/**
1268-
* If this is enabled, the client will normally resume events upon reconnect
1269-
* Note that you should also handle to `reset`-events and handle the case where the backend is unable to resume.
1270-
* @beta
1271-
* @defaultValue `false`
1272-
*/
1273-
enableResume?: false
1274-
}
1275-
1276-
/** @public */
1277-
export interface ResumableListenOptions extends Omit<ListenOptions, 'events' | 'enableResume'> {
1278-
/**
1279-
* If this is enabled, the client will normally resume events upon reconnect
1280-
* Note that you should also subscribe to `reset`-events and handle the case where the backend is unable to resume
1281-
* @beta
1282-
* @defaultValue `false`
1283-
*/
1284-
enableResume: true
1285-
1286-
/**
1287-
* Array of event names to include in the observable. By default, only mutation events are included.
1288-
*
1289-
* @defaultValue `['mutation']`
1290-
*/
1291-
events?: ResumableListenEventNames[]
12921261
}
12931262

12941263
/** @public */

test/client.test.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,9 @@ describe('client', async () => {
653653
].join('\n')
654654

655655
nock(`https://${apiHost}`)
656-
.get('/v1/media-libraries/res-id/listen?query=foo.bar&includeResult=true')
656+
.get(
657+
'/v1/media-libraries/res-id/listen?query=foo.bar&includeResult=true&enableResume=true',
658+
)
657659
.reply(200, response, {
658660
'cache-control': 'no-cache',
659661
'content-type': 'text/event-stream; charset=utf-8',
@@ -665,7 +667,7 @@ describe('client', async () => {
665667
'foo.bar',
666668
),
667669
)
668-
expect(evt.result).toEqual(doc)
670+
expect(evt.type === 'mutation' && evt.result).toEqual(doc)
669671
},
670672
)
671673
})
@@ -3665,15 +3667,15 @@ describe('client', async () => {
36653667
].join('\n')
36663668

36673669
nock(projectHost())
3668-
.get('/v1/data/listen/foo?query=foo.bar&includeResult=true')
3670+
.get('/v1/data/listen/foo?query=foo.bar&includeResult=true&enableResume=true')
36693671
.reply(200, response, {
36703672
'cache-control': 'no-cache',
36713673
'content-type': 'text/event-stream; charset=utf-8',
36723674
'transfer-encoding': 'chunked',
36733675
})
36743676

36753677
const evt = await firstValueFrom(getClient().listen('foo.bar'))
3676-
expect(evt.result).toEqual(doc)
3678+
expect(evt.type === 'mutation' && evt.result).toEqual(doc)
36773679
})
36783680

36793681
test('listeners connect to listen endpoint with request tag, emits events', async () => {
@@ -3695,7 +3697,7 @@ describe('client', async () => {
36953697

36963698
nock(projectHost())
36973699
.get(
3698-
'/v1/data/listen/foo?tag=sfcraft.checkins&query=*%5B_type%20%3D%3D%20%22checkin%22%5D&includeResult=true',
3700+
'/v1/data/listen/foo?tag=sfcraft.checkins&query=*%5B_type%20%3D%3D%20%22checkin%22%5D&includeResult=true&enableResume=true',
36993701
)
37003702
.reply(200, response, {
37013703
'cache-control': 'no-cache',
@@ -3728,7 +3730,7 @@ describe('client', async () => {
37283730

37293731
nock(projectHost())
37303732
.get(
3731-
'/v1/data/listen/foo?tag=sf.craft.checkins&query=*%5B_type%20%3D%3D%20%22checkin%22%5D&includeResult=true',
3733+
'/v1/data/listen/foo?tag=sf.craft.checkins&query=*%5B_type%20%3D%3D%20%22checkin%22%5D&includeResult=true&enableResume=true',
37323734
)
37333735
.reply(200, response, {
37343736
'cache-control': 'no-cache',
@@ -3762,7 +3764,7 @@ describe('client', async () => {
37623764

37633765
let didRequest = false
37643766
nock(projectHost())
3765-
.get('/v1/data/listen/foo?query=foo.bar&includeResult=true')
3767+
.get('/v1/data/listen/foo?query=foo.bar&includeResult=true&enableResume=true')
37663768
.reply(() => {
37673769
didRequest = true
37683770
return [200, response]
@@ -3789,7 +3791,7 @@ describe('client', async () => {
37893791

37903792
let requestCount = 0
37913793
nock(projectHost())
3792-
.get('/v1/data/listen/foo?query=foo.bar&includeResult=true')
3794+
.get('/v1/data/listen/foo?query=foo.bar&includeResult=true&enableResume=true')
37933795
.twice()
37943796
.reply(() => {
37953797
requestCount++

test/listen.test-d.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import {describe, expectTypeOf, test} from 'vitest'
1414
describe('client.listen', () => {
1515
const client = createClient({})
1616
test('event types', async () => {
17-
// mutation event is default
18-
expectTypeOf(client.listen('*')).toEqualTypeOf<Observable<MutationEvent>>()
17+
// mutation and reset is default
18+
expectTypeOf(client.listen('*')).toEqualTypeOf<Observable<MutationEvent | ResetEvent>>()
1919

2020
// @ts-expect-error - WelcomeEvent should not be emitted
2121
expectTypeOf(client.listen('*')).toEqualTypeOf<Observable<WelcomeEvent>>()
@@ -34,14 +34,17 @@ describe('client.listen', () => {
3434

3535
expectTypeOf(client.listen('*', {}, {events: []})).toEqualTypeOf<Observable<never>>()
3636

37-
//@ts-expect-error – welcomeback and reset requires `enableResume`
38-
expectTypeOf(client.listen('*', {}, {events: ['welcomeback', 'reset']})).toEqualTypeOf<
39-
Observable<ListenEvent>
37+
expectTypeOf(client.listen('*', {}, {events: ['mutation', 'reset']})).toEqualTypeOf<
38+
Observable<MutationEvent | ResetEvent>
4039
>()
4140

4241
expectTypeOf(
43-
client.listen('*', {}, {enableResume: true, events: ['welcome', 'mutation']}),
44-
).toEqualTypeOf<Observable<WelcomeEvent | MutationEvent>>()
42+
client.listen(
43+
'*',
44+
{},
45+
{enableResume: true, events: ['welcome', 'mutation', 'welcomeback', 'reset']},
46+
),
47+
).toEqualTypeOf<Observable<WelcomeEvent | MutationEvent | WelcomeBackEvent | ResetEvent>>()
4548

4649
const observable = client.listen(
4750
'*',

test/listen.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ describe.skipIf(typeof EdgeRuntime === 'string' || typeof document !== 'undefine
5252
query: '*[_type == "beer" && title == $beerName]',
5353
$beerName: '"Headroom Double IPA"',
5454
includeResult: 'true',
55+
enableResume: 'true',
5556
})
5657
expect(request.url, 'url should be correct').toEqual(`/v1/data/listen/prod?${searchParams}`)
5758

0 commit comments

Comments
 (0)