From 59a943db90c72f270a1e8f86debc03bed5c6409b Mon Sep 17 00:00:00 2001
From: slvrtrn <hypnoash@gmail.com>
Date: Thu, 28 Dec 2023 14:34:20 +0100
Subject: [PATCH 1/2] Make keep_alive configurable in the Web version

---
 .../integration/web_connection.test.ts        | 42 +++++++++++++++++++
 packages/client-web/src/client.ts             | 16 ++++++-
 .../src/connection/web_connection.ts          | 10 ++++-
 3 files changed, 64 insertions(+), 4 deletions(-)
 create mode 100644 packages/client-web/__tests__/integration/web_connection.test.ts

diff --git a/packages/client-web/__tests__/integration/web_connection.test.ts b/packages/client-web/__tests__/integration/web_connection.test.ts
new file mode 100644
index 00000000..20aa5bd3
--- /dev/null
+++ b/packages/client-web/__tests__/integration/web_connection.test.ts
@@ -0,0 +1,42 @@
+import { createClient } from '../../src'
+import type { WebClickHouseClient } from '../../src/client'
+
+describe('[Web] Connection', () => {
+  let fetchSpy: jasmine.Spy<typeof window.fetch>
+  beforeEach(() => {
+    fetchSpy = spyOn(window, 'fetch').and.returnValue(
+      Promise.resolve(stubResponse())
+    )
+  })
+
+  describe('KeepAlive setting', () => {
+    it('should be enabled by default', async () => {
+      const client = createClient()
+      const fetchParams = await pingAndGetRequestInit(client)
+      expect(fetchParams.keepalive).toBeTruthy()
+    })
+
+    it('should be possible to disable it', async () => {
+      const client = createClient({ keep_alive: { enabled: false } })
+      const fetchParams = await pingAndGetRequestInit(client)
+      expect(fetchParams!.keepalive).toBeFalsy()
+    })
+
+    it('should be enabled with an explicit setting', async () => {
+      const client = createClient({ keep_alive: { enabled: true } })
+      const fetchParams = await pingAndGetRequestInit(client)
+      expect(fetchParams.keepalive).toBeTruthy()
+    })
+
+    async function pingAndGetRequestInit(client: WebClickHouseClient) {
+      await client.ping()
+      expect(fetchSpy).toHaveBeenCalledTimes(1)
+      const [, fetchParams] = fetchSpy.calls.mostRecent().args
+      return fetchParams!
+    }
+  })
+
+  function stubResponse() {
+    return new Response()
+  }
+})
diff --git a/packages/client-web/src/client.ts b/packages/client-web/src/client.ts
index 49c5bb49..4f0f02ca 100644
--- a/packages/client-web/src/client.ts
+++ b/packages/client-web/src/client.ts
@@ -15,6 +15,14 @@ import { WebConnection } from './connection'
 import { ResultSet } from './result_set'
 import { WebValuesEncoder } from './utils'
 
+export type WebClickHouseClientConfigOptions =
+  BaseClickHouseClientConfigOptions<ReadableStream> & {
+    keep_alive?: {
+      /** Enable or disable HTTP Keep-Alive mechanism. Default: true */
+      enabled: boolean
+    }
+  }
+
 export type WebClickHouseClient = Omit<
   ClickHouseClient<ReadableStream>,
   'insert' | 'query'
@@ -30,11 +38,15 @@ export type WebClickHouseClient = Omit<
 }
 
 export function createClient(
-  config?: BaseClickHouseClientConfigOptions<ReadableStream>
+  config?: WebClickHouseClientConfigOptions
 ): WebClickHouseClient {
+  const keep_alive = {
+    enabled: config?.keep_alive?.enabled ?? true,
+  }
   return new ClickHouseClient<ReadableStream>({
     impl: {
-      make_connection: (params: ConnectionParams) => new WebConnection(params),
+      make_connection: (params: ConnectionParams) =>
+        new WebConnection({ ...params, keep_alive }),
       make_result_set: (
         stream: ReadableStream,
         format: DataFormat,
diff --git a/packages/client-web/src/connection/web_connection.ts b/packages/client-web/src/connection/web_connection.ts
index 847ec65b..710736a7 100644
--- a/packages/client-web/src/connection/web_connection.ts
+++ b/packages/client-web/src/connection/web_connection.ts
@@ -24,9 +24,15 @@ type WebInsertParams<T> = Omit<
   values: string
 }
 
+export type WebConnectionParams = ConnectionParams & {
+  keep_alive: {
+    enabled: boolean
+  }
+}
+
 export class WebConnection implements Connection<ReadableStream> {
   private readonly defaultHeaders: Record<string, string>
-  constructor(private readonly params: ConnectionParams) {
+  constructor(private readonly params: WebConnectionParams) {
     this.defaultHeaders = {
       Authorization: `Basic ${btoa(`${params.username}:${params.password}`)}`,
     }
@@ -175,7 +181,7 @@ export class WebConnection implements Connection<ReadableStream> {
       const response = await fetch(url, {
         body: values,
         headers,
-        keepalive: false,
+        keepalive: this.params.keep_alive.enabled,
         method: method ?? 'POST',
         signal: abortController.signal,
       })

From 95eca7485dbdaa2fa78e0db0766728f655da0ede Mon Sep 17 00:00:00 2001
From: slvrtrn <hypnoash@gmail.com>
Date: Thu, 28 Dec 2023 14:39:06 +0100
Subject: [PATCH 2/2] Simplify the test

---
 .../integration/web_connection.test.ts         | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/packages/client-web/__tests__/integration/web_connection.test.ts b/packages/client-web/__tests__/integration/web_connection.test.ts
index 20aa5bd3..b71121bb 100644
--- a/packages/client-web/__tests__/integration/web_connection.test.ts
+++ b/packages/client-web/__tests__/integration/web_connection.test.ts
@@ -2,14 +2,14 @@ import { createClient } from '../../src'
 import type { WebClickHouseClient } from '../../src/client'
 
 describe('[Web] Connection', () => {
-  let fetchSpy: jasmine.Spy<typeof window.fetch>
-  beforeEach(() => {
-    fetchSpy = spyOn(window, 'fetch').and.returnValue(
-      Promise.resolve(stubResponse())
-    )
-  })
-
   describe('KeepAlive setting', () => {
+    let fetchSpy: jasmine.Spy<typeof window.fetch>
+    beforeEach(() => {
+      fetchSpy = spyOn(window, 'fetch').and.returnValue(
+        Promise.resolve(new Response())
+      )
+    })
+
     it('should be enabled by default', async () => {
       const client = createClient()
       const fetchParams = await pingAndGetRequestInit(client)
@@ -35,8 +35,4 @@ describe('[Web] Connection', () => {
       return fetchParams!
     }
   })
-
-  function stubResponse() {
-    return new Response()
-  }
 })