1
1
import type { ClientOptions , RequestPolicy } from "@urql/core" ;
2
2
import { Client , cacheExchange , fetchExchange , subscriptionExchange } from "@urql/core" ;
3
- import fetchPolyfill from "cross-fetch" ;
3
+
4
4
import type { ExecutionResult } from "graphql" ;
5
5
import type { Sink , Client as SubscriptionClient , ClientOptions as SubscriptionClientOptions } from "graphql-ws" ;
6
6
import { CloseCode , createClient as createSubscriptionClient } from "graphql-ws" ;
@@ -11,16 +11,8 @@ import { GadgetTransaction, TransactionRolledBack } from "./GadgetTransaction.js
11
11
import type { BrowserStorage } from "./InMemoryStorage.js" ;
12
12
import { InMemoryStorage } from "./InMemoryStorage.js" ;
13
13
import { operationNameExchange } from "./exchanges/operationNameExchange.js" ;
14
- import { otelExchange } from "./exchanges/otelExchange.js" ;
15
14
import { urlParamExchange } from "./exchanges/urlParamExchange.js" ;
16
- import {
17
- GadgetUnexpectedCloseError ,
18
- GadgetWebsocketConnectionTimeoutError ,
19
- getCurrentSpan ,
20
- isCloseEvent ,
21
- storageAvailable ,
22
- traceFunction ,
23
- } from "./support.js" ;
15
+ import { GadgetUnexpectedCloseError , GadgetWebsocketConnectionTimeoutError , isCloseEvent , storageAvailable } from "./support.js" ;
24
16
25
17
export type TransactionRun < T > = ( transaction : GadgetTransaction ) => Promise < T > ;
26
18
export interface GadgetSubscriptionClientOptions extends Partial < SubscriptionClientOptions > {
@@ -47,7 +39,7 @@ export interface GadgetConnectionOptions {
47
39
websocketsEndpoint ?: string ;
48
40
subscriptionClientOptions ?: GadgetSubscriptionClientOptions ;
49
41
websocketImplementation ?: any ;
50
- fetchImplementation ?: typeof fetchPolyfill ;
42
+ fetchImplementation ?: typeof globalThis . fetch ;
51
43
environment ?: "Development" | "Production" ;
52
44
requestPolicy ?: ClientOptions [ "requestPolicy" ] ;
53
45
applicationId ?: string ;
@@ -74,12 +66,14 @@ export enum AuthenticationMode {
74
66
* Manages transactions and the connection to a Gadget API
75
67
*/
76
68
export class GadgetConnection {
69
+ version = "<prerelease>" as const ;
70
+
77
71
// Options used when generating new GraphQL clients for the base connection and for for transactions
78
72
private endpoint : string ;
79
73
private subscriptionClientOptions ?: SubscriptionClientOptions ;
80
74
private websocketsEndpoint : string ;
81
75
private websocketImplementation : any ;
82
- private _fetchImplementation : typeof fetchPolyfill ;
76
+ private _fetchImplementation : typeof globalThis . fetch ;
83
77
private environment : "Development" | "Production" ;
84
78
private exchanges : Required < Exchanges > ;
85
79
@@ -103,7 +97,10 @@ export class GadgetConnection {
103
97
} else if ( typeof window != "undefined" && window . fetch ) {
104
98
this . _fetchImplementation = window . fetch . bind ( window ) ;
105
99
} else {
106
- this . _fetchImplementation = fetchPolyfill ;
100
+ this . _fetchImplementation = async ( ...args : [ any ] ) => {
101
+ const { fetch } = await import ( "cross-fetch" ) ;
102
+ return await fetch ( ...args ) ;
103
+ } ;
107
104
}
108
105
this . websocketImplementation = options . websocketImplementation ?? globalThis ?. WebSocket ?? WebSocket ;
109
106
this . websocketsEndpoint = options . websocketsEndpoint ?? options . endpoint + "/batch" ;
@@ -132,7 +129,7 @@ export class GadgetConnection {
132
129
return this . currentTransaction ?. client || this . baseClient ;
133
130
}
134
131
135
- set fetchImplementation ( implementation : typeof fetchPolyfill ) {
132
+ set fetchImplementation ( implementation : typeof globalThis . fetch ) {
136
133
this . _fetchImplementation = implementation ;
137
134
this . resetClients ( ) ;
138
135
}
@@ -182,96 +179,90 @@ export class GadgetConnection {
182
179
transaction : {
183
180
< T > ( options : GadgetSubscriptionClientOptions , run : TransactionRun < T > ) : Promise < T > ;
184
181
< T > ( run : TransactionRun < T > ) : Promise < T > ;
185
- } = traceFunction (
186
- "api-client.transaction" ,
187
- async < T > ( optionsOrRun : GadgetSubscriptionClientOptions | TransactionRun < T > , maybeRun ?: TransactionRun < T > ) : Promise < T > => {
188
- let run : TransactionRun < T > ;
189
- let options : GadgetSubscriptionClientOptions ;
190
-
191
- if ( maybeRun ) {
192
- run = maybeRun ;
193
- options = optionsOrRun as GadgetSubscriptionClientOptions ;
194
- } else {
195
- run = optionsOrRun as TransactionRun < T > ;
196
- options = { } ;
197
- }
182
+ } = async < T > ( optionsOrRun : GadgetSubscriptionClientOptions | TransactionRun < T > , maybeRun ?: TransactionRun < T > ) : Promise < T > => {
183
+ let run : TransactionRun < T > ;
184
+ let options : GadgetSubscriptionClientOptions ;
198
185
199
- if ( this . currentTransaction ) {
200
- return await run ( this . currentTransaction ) ;
201
- }
186
+ if ( maybeRun ) {
187
+ run = maybeRun ;
188
+ options = optionsOrRun as GadgetSubscriptionClientOptions ;
189
+ } else {
190
+ run = optionsOrRun as TransactionRun < T > ;
191
+ options = { } ;
192
+ }
202
193
203
- getCurrentSpan ( ) ?. setAttributes ( { applicationId : this . options . applicationId , environmentName : this . environment } ) ;
194
+ if ( this . currentTransaction ) {
195
+ return await run ( this . currentTransaction ) ;
196
+ }
204
197
205
- let subscriptionClient : SubscriptionClient | null = null ;
206
- let transaction ;
198
+ let subscriptionClient : SubscriptionClient | null = null ;
199
+ let transaction ;
200
+ try {
201
+ // The server will error if it receives any operations before the auth dance has been completed, so we block on that happening before sending our first operation. It's important that this happens synchronously after instantiating the client so we don't miss any messages
202
+ subscriptionClient = await this . waitForOpenedConnection ( {
203
+ isFatalConnectionProblem ( errorOrCloseEvent ) {
204
+ // any interruption of the transaction is fatal to the transaction
205
+ console . warn ( "Transport error encountered during transaction processing" , errorOrCloseEvent ) ;
206
+ return true ;
207
+ } ,
208
+ connectionAckWaitTimeout : DEFAULT_CONN_ACK_TIMEOUT ,
209
+ ...options ,
210
+ lazy : false ,
211
+ // super ultra critical option that ensures graphql-ws doesn't automatically close the websocket connection when there are no outstanding operations. this is key so we can start a transaction then make mutations within it
212
+ lazyCloseTimeout : 100000 ,
213
+ retryAttempts : 0 ,
214
+ } ) ;
215
+
216
+ const client = new Client ( {
217
+ url : "/-" , // not used because there's no fetch exchange, set for clarity
218
+ requestPolicy : "network-only" , // skip any cached data during transactions
219
+ exchanges : [
220
+ ...this . exchanges . beforeAll ,
221
+ operationNameExchange ,
222
+ ...this . exchanges . beforeAsync ,
223
+ subscriptionExchange ( {
224
+ forwardSubscription ( request ) {
225
+ const input = { ...request , query : request . query || "" } ;
226
+ return {
227
+ subscribe : ( sink ) => {
228
+ const dispose = subscriptionClient ! . subscribe ( input , sink as Sink < ExecutionResult > ) ;
229
+ return {
230
+ unsubscribe : dispose ,
231
+ } ;
232
+ } ,
233
+ } ;
234
+ } ,
235
+ enableAllOperations : true ,
236
+ } ) ,
237
+ ...this . exchanges . afterAll ,
238
+ ] ,
239
+ } ) ;
240
+ ( client as any ) [ $gadgetConnection ] = this ;
241
+
242
+ transaction = new GadgetTransaction ( client , subscriptionClient ) ;
243
+ this . currentTransaction = transaction ;
244
+ await transaction . start ( ) ;
245
+ const result = await run ( transaction ) ;
246
+ await transaction . commit ( ) ;
247
+ return result ;
248
+ } catch ( error ) {
207
249
try {
208
- // The server will error if it receives any operations before the auth dance has been completed, so we block on that happening before sending our first operation. It's important that this happens synchronously after instantiating the client so we don't miss any messages
209
- subscriptionClient = await this . waitForOpenedConnection ( {
210
- isFatalConnectionProblem ( errorOrCloseEvent ) {
211
- // any interruption of the transaction is fatal to the transaction
212
- console . warn ( "Transport error encountered during transaction processing" , errorOrCloseEvent ) ;
213
- return true ;
214
- } ,
215
- connectionAckWaitTimeout : DEFAULT_CONN_ACK_TIMEOUT ,
216
- ...options ,
217
- lazy : false ,
218
- // super ultra critical option that ensures graphql-ws doesn't automatically close the websocket connection when there are no outstanding operations. this is key so we can start a transaction then make mutations within it
219
- lazyCloseTimeout : 100000 ,
220
- retryAttempts : 0 ,
221
- } ) ;
222
-
223
- const client = new Client ( {
224
- url : "/-" , // not used because there's no fetch exchange, set for clarity
225
- requestPolicy : "network-only" , // skip any cached data during transactions
226
- exchanges : [
227
- ...this . exchanges . beforeAll ,
228
- operationNameExchange ,
229
- otelExchange ,
230
- ...this . exchanges . beforeAsync ,
231
- subscriptionExchange ( {
232
- forwardSubscription ( request ) {
233
- const input = { ...request , query : request . query || "" } ;
234
- return {
235
- subscribe : ( sink ) => {
236
- const dispose = subscriptionClient ! . subscribe ( input , sink as Sink < ExecutionResult > ) ;
237
- return {
238
- unsubscribe : dispose ,
239
- } ;
240
- } ,
241
- } ;
242
- } ,
243
- enableAllOperations : true ,
244
- } ) ,
245
- ...this . exchanges . afterAll ,
246
- ] ,
247
- } ) ;
248
- ( client as any ) [ $gadgetConnection ] = this ;
249
-
250
- transaction = new GadgetTransaction ( client , subscriptionClient ) ;
251
- this . currentTransaction = transaction ;
252
- await transaction . start ( ) ;
253
- const result = await run ( transaction ) ;
254
- await transaction . commit ( ) ;
255
- return result ;
256
- } catch ( error ) {
257
- try {
258
- if ( transaction ?. open ) await transaction . rollback ( ) ;
259
- } catch ( rollbackError ) {
260
- if ( ! ( rollbackError instanceof TransactionRolledBack ) ) {
261
- console . warn ( "Encountered another error while rolling back a Gadget transaction that errored. The other error:" , rollbackError ) ;
262
- }
263
- }
264
- if ( isCloseEvent ( error ) ) {
265
- throw new GadgetUnexpectedCloseError ( error ) ;
266
- } else {
267
- throw error ;
250
+ if ( transaction ?. open ) await transaction . rollback ( ) ;
251
+ } catch ( rollbackError ) {
252
+ if ( ! ( rollbackError instanceof TransactionRolledBack ) ) {
253
+ console . warn ( "Encountered another error while rolling back a Gadget transaction that errored. The other error:" , rollbackError ) ;
268
254
}
269
- } finally {
270
- await subscriptionClient ?. dispose ( ) ;
271
- this . currentTransaction = null ;
272
255
}
256
+ if ( isCloseEvent ( error ) ) {
257
+ throw new GadgetUnexpectedCloseError ( error ) ;
258
+ } else {
259
+ throw error ;
260
+ }
261
+ } finally {
262
+ await subscriptionClient ?. dispose ( ) ;
263
+ this . currentTransaction = null ;
273
264
}
274
- ) ;
265
+ } ;
275
266
276
267
close ( ) {
277
268
this . disposeClient ( this . baseSubscriptionClient ) ;
@@ -290,7 +281,7 @@ export class GadgetConnection {
290
281
* // fetch a relative URL from the endpoint this API client is configured to fetch from
291
282
* await api.connection.fetch("/foo/bar");
292
283
**/
293
- fetch = traceFunction ( "api-client.fetch" , async ( input : RequestInfo | URL , init : RequestInit = { } ) => {
284
+ fetch = async ( input : RequestInfo | URL , init : RequestInit = { } ) => {
294
285
input = processMaybeRelativeInput ( input , this . options . baseRouteURL ?? this . options . endpoint ) ;
295
286
296
287
if ( this . isGadgetRequest ( input ) ) {
@@ -311,7 +302,7 @@ export class GadgetConnection {
311
302
}
312
303
313
304
return response ;
314
- } ) ;
305
+ } ;
315
306
316
307
private isGadgetRequest ( input : RequestInfo | URL ) {
317
308
let requestUrl ;
@@ -345,7 +336,7 @@ export class GadgetConnection {
345
336
}
346
337
347
338
private newBaseClient ( ) {
348
- const exchanges = [ ...this . exchanges . beforeAll , operationNameExchange , otelExchange , urlParamExchange ] ;
339
+ const exchanges = [ ...this . exchanges . beforeAll , operationNameExchange , urlParamExchange ] ;
349
340
350
341
// apply urql's default caching behaviour when client side (but skip it server side)
351
342
if ( typeof window != "undefined" ) {
0 commit comments