diff --git a/.changeset/calm-drinks-beg.md b/.changeset/calm-drinks-beg.md new file mode 100644 index 000000000000..2d7c3af72411 --- /dev/null +++ b/.changeset/calm-drinks-beg.md @@ -0,0 +1,5 @@ +--- +'@astrojs/db': patch +--- + +Fix options parsing for the libsql client connection to ensure that proper values are being set when adding URLSearchParams to the `ASTRO_DB_REMOTE_URL` diff --git a/packages/db/src/runtime/db-client.ts b/packages/db/src/runtime/db-client.ts index 21f45aa45428..6a4e610e7b88 100644 --- a/packages/db/src/runtime/db-client.ts +++ b/packages/db/src/runtime/db-client.ts @@ -56,8 +56,23 @@ export function createRemoteDatabaseClient(options: RemoteDbClientOptions) { : createRemoteLibSQLClient(options.appToken, remoteUrl, options.remoteUrl.toString()); } +// this function parses the options from a `Record` +// provided from the object conversion of the searchParams and properly +// verifies that the Config object is providing the correct types. +// without this, there is runtime errors due to incorrect values +export function parseOpts(config: Record): Partial { + return { + ... config, + ...(config.syncInterval ? { syncInterval: parseInt(config.syncInterval) } : {}), + ...('readYourWrites' in config ? { readYourWrites: config.readYourWrites !== 'false' } : {}), + ...('offline' in config ? { offline: config.offline !== 'false' } : {}), + ...('tls' in config ? { tls: config.tls !== 'false' } : {}), + ...(config.concurrency ? { concurrency: parseInt(config.concurrency) } : {}), + } +} + function createRemoteLibSQLClient(appToken: string, remoteDbURL: URL, rawUrl: string) { - const options: Partial = Object.fromEntries(remoteDbURL.searchParams.entries()); + const options: Record = Object.fromEntries(remoteDbURL.searchParams.entries()); remoteDbURL.search = ''; let url = remoteDbURL.toString(); @@ -81,11 +96,7 @@ function createRemoteLibSQLClient(appToken: string, remoteDbURL: URL, rawUrl: st url = 'file:' + remoteDbURL.pathname.substring(1); } - const client = createClient({ - ...options, - authToken: appToken, - url, - }); + const client = createClient({ ...parseOpts(options), url, authToken: appToken, }); return drizzleLibsql(client); } diff --git a/packages/db/test/unit/db-client.test.js b/packages/db/test/unit/db-client.test.js new file mode 100644 index 000000000000..ac41aa07f027 --- /dev/null +++ b/packages/db/test/unit/db-client.test.js @@ -0,0 +1,56 @@ +import assert from 'node:assert'; +import test, { describe } from 'node:test'; +import { parseOpts } from '../../dist/runtime/db-client.js'; + +describe('db client config', () => { + test('parse config options from URL (docs example url)', () => { + const remoteURLToParse = new URL('file://local-copy.db?encryptionKey=your-encryption-key&syncInterval=60&syncUrl=libsql%3A%2F%2Fyour.server.io'); + const options = Object.fromEntries(remoteURLToParse.searchParams.entries()); + + const config = parseOpts(options); + + assert.deepEqual(config, { + encryptionKey: "your-encryption-key", + syncInterval: 60, + syncUrl: "libsql://your.server.io", + }) + }); + + test('parse config options from URL (test booleans without value)', () => { + const remoteURLToParse = new URL('file://local-copy.db?readYourWrites&offline&tls'); + const options = Object.fromEntries(remoteURLToParse.searchParams.entries()); + + const config = parseOpts(options); + + assert.deepEqual(config, { + readYourWrites: true, + offline: true, + tls: true + }) + }) + + test('parse config options from URL (test booleans with value)', () => { + const remoteURLToParse = new URL('file://local-copy.db?readYourWrites=true&offline=true&tls=true'); + const options = Object.fromEntries(remoteURLToParse.searchParams.entries()); + + const config = parseOpts(options); + + assert.deepEqual(config, { + readYourWrites: true, + offline: true, + tls: true + }) + }) + + test('parse config options from URL (test numbers)', () => { + const remoteURLToParse = new URL('file://local-copy.db?syncInterval=60&concurrency=2'); + const options = Object.fromEntries(remoteURLToParse.searchParams.entries()); + + const config = parseOpts(options); + + assert.deepEqual(config, { + syncInterval: 60, + concurrency: 2 + }) + }) +}) \ No newline at end of file