add duckdb-ui-client & other ts pkgs (#10)

* add duckdb-ui-client & other ts pkgs

* workflow fixes

* fix working dir

* no sparse checkout; specify package.json path

* path to pnpm-lock.yaml

* add check & build test

* workflow step descriptions

* use comments & names

* one more naming tweak
This commit is contained in:
Jeff Raymakers
2025-06-13 09:06:55 -07:00
parent d6cc9eeea4
commit 0edb52054a
133 changed files with 11112 additions and 4 deletions

View File

@@ -0,0 +1,138 @@
import { http, HttpResponse } from 'msw';
import { expect, suite, test } from 'vitest';
import { DuckDBUIHttpRequestQueue } from '../../../src/http/classes/DuckDBUIHttpRequestQueue';
import { makeBuffer } from '../../helpers/makeBuffer';
import { mockRequests } from '../../helpers/mockRequests';
suite('DuckDBUIHttpRequestQueue', () => {
test('single request', () => {
return mockRequests(
[
http.post('http://localhost/example/path', () => {
return HttpResponse.arrayBuffer(makeBuffer([17, 42]));
}),
],
async () => {
const queue = new DuckDBUIHttpRequestQueue();
const id = queue.enqueue(
'http://localhost/example/path',
'example body',
);
expect(queue.length).toBe(1);
expect(queue.isCurrent(id)).toBe(true);
const result = await queue.enqueuedResult(id);
expect(result.buffer).toEqual(makeBuffer([17, 42]));
},
);
});
test('multiple requests', () => {
return mockRequests(
[
http.post('http://localhost/example/path', async ({ request }) => {
const body = await request.text();
const value = parseInt(body.split(' ')[0], 10);
return HttpResponse.arrayBuffer(makeBuffer([value]));
}),
],
async () => {
const queue = new DuckDBUIHttpRequestQueue();
const id1 = queue.enqueue(
'http://localhost/example/path',
'11 example body',
);
const id2 = queue.enqueue(
'http://localhost/example/path',
'22 example body',
);
expect(queue.length).toBe(2);
expect(queue.isCurrent(id1)).toBe(true);
const result1 = await queue.enqueuedResult(id1);
expect(result1.buffer).toEqual(makeBuffer([11]));
expect(queue.length).toBe(1);
expect(queue.isCurrent(id2)).toBe(true);
const result2 = await queue.enqueuedResult(id2);
expect(result2.buffer).toEqual(makeBuffer([22]));
},
);
});
test('cancel (first request)', () => {
return mockRequests(
[
http.post('http://localhost/example/path', async ({ request }) => {
const body = await request.text();
const value = parseInt(body.split(' ')[0], 10);
return HttpResponse.arrayBuffer(makeBuffer([value]));
}),
],
async () => {
const queue = new DuckDBUIHttpRequestQueue();
const id1 = queue.enqueue(
'http://localhost/example/path',
'11 example body',
);
const id2 = queue.enqueue(
'http://localhost/example/path',
'22 example body',
);
expect(queue.length).toBe(2);
expect(queue.isCurrent(id1)).toBe(true);
queue.cancel(id1);
await expect(queue.enqueuedResult(id1)).rejects.toEqual(
new Error('query was canceled'),
);
const result2 = await queue.enqueuedResult(id2);
expect(result2.buffer).toEqual(makeBuffer([22]));
},
);
});
test('cancel (second request)', () => {
return mockRequests(
[
http.post('http://localhost/example/path', async ({ request }) => {
const body = await request.text();
const value = parseInt(body.split(' ')[0], 10);
return HttpResponse.arrayBuffer(makeBuffer([value]));
}),
],
async () => {
const queue = new DuckDBUIHttpRequestQueue();
const id1 = queue.enqueue(
'http://localhost/example/path',
'11 example body',
);
const id2 = queue.enqueue(
'http://localhost/example/path',
'22 example body',
);
const id3 = queue.enqueue(
'http://localhost/example/path',
'33 example body',
);
expect(queue.length).toBe(3);
expect(queue.isCurrent(id1)).toBe(true);
const promise2 = queue.enqueuedResult(id2);
queue.cancel(id2, 'example error message');
const result1 = await queue.enqueuedResult(id1);
expect(result1.buffer).toEqual(makeBuffer([11]));
expect(queue.length).toBe(1);
expect(queue.isCurrent(id3)).toBe(true);
await expect(promise2).rejects.toEqual(
new Error('example error message'),
);
const result3 = await queue.enqueuedResult(id3);
expect(result3.buffer).toEqual(makeBuffer([33]));
},
);
});
});

View File

@@ -0,0 +1,39 @@
import { expect, suite, test } from 'vitest';
import { makeDuckDBUIHttpRequestHeaders } from '../../../src/http/functions/makeDuckDBUIHttpRequestHeaders';
suite('makeDuckDBUIHttpRequestHeaders', () => {
test('description', () => {
expect([
...makeDuckDBUIHttpRequestHeaders({
description: 'example description',
}).entries(),
]).toEqual([['x-duckdb-ui-request-description', 'example description']]);
});
test('connection name', () => {
expect([
...makeDuckDBUIHttpRequestHeaders({
connectionName: 'example connection name',
}).entries(),
]).toEqual([['x-duckdb-ui-connection-name', 'example connection name']]);
});
test('database name', () => {
// should be base64 encoded
expect([
...makeDuckDBUIHttpRequestHeaders({
databaseName: 'example database name',
}).entries(),
]).toEqual([['x-duckdb-ui-database-name', 'ZXhhbXBsZSBkYXRhYmFzZSBuYW1l']]);
});
test('parameters', () => {
// values should be base64 encoded
expect([
...makeDuckDBUIHttpRequestHeaders({
parameters: ['first', 'second'],
}).entries(),
]).toEqual([
['x-duckdb-ui-parameter-count', '2'],
['x-duckdb-ui-parameter-value-0', 'Zmlyc3Q='],
['x-duckdb-ui-parameter-value-1', 'c2Vjb25k'],
]);
});
});

View File

@@ -0,0 +1,54 @@
import { http, HttpResponse } from 'msw';
import { expect, suite, test } from 'vitest';
import { sendDuckDBUIHttpRequest } from '../../../src/http/functions/sendDuckDBUIHttpRequest';
import { makeBuffer } from '../../helpers/makeBuffer';
import { mockRequests } from '../../helpers/mockRequests';
suite('sendDuckDBUIHttpRequest', () => {
test('basic', async () => {
return mockRequests(
[
http.post('http://localhost/example/path', () => {
return HttpResponse.arrayBuffer(makeBuffer([17, 42]));
}),
],
async () => {
await expect(
sendDuckDBUIHttpRequest(
'http://localhost/example/path',
'example body',
),
).resolves.toEqual(makeBuffer([17, 42]));
},
);
});
test('headers', async () => {
return mockRequests(
[
http.post('http://localhost/example/path', ({ request }) => {
if (
request.headers.get('X-Example-Header-1') !==
'example-header-1-value' ||
request.headers.get('X-Example-Header-2') !==
'example-header-2-value'
) {
return HttpResponse.error();
}
return HttpResponse.arrayBuffer(makeBuffer([17, 42]));
}),
],
async () => {
const headers = new Headers();
headers.append('X-Example-Header-1', 'example-header-1-value');
headers.append('X-Example-Header-2', 'example-header-2-value');
await expect(
sendDuckDBUIHttpRequest(
'http://localhost/example/path',
'example body',
headers,
),
).resolves.toEqual(makeBuffer([17, 42]));
},
);
});
});