From 75c4bede289a3c02848923c4cfbd142541177ae4 Mon Sep 17 00:00:00 2001 From: Evan Sosenko Date: Tue, 9 Jan 2024 20:56:29 -0800 Subject: [PATCH] feat: Use @seamapi/url-search-params-serializer --- package-lock.json | 11 ++++ package.json | 1 + src/connect.ts | 1 + src/lib/params-serializer.test.ts | 106 ------------------------------ src/lib/params-serializer.ts | 44 ------------- src/lib/seam/connect/client.ts | 4 +- src/lib/seam/connect/index.ts | 1 - 7 files changed, 15 insertions(+), 153 deletions(-) delete mode 100644 src/lib/params-serializer.test.ts delete mode 100644 src/lib/params-serializer.ts diff --git a/package-lock.json b/package-lock.json index aab5243d..ecd00d12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "devDependencies": { "@seamapi/fake-seam-connect": "^1.44.2", "@seamapi/types": "^1.58.0", + "@seamapi/url-search-params-serializer": "^1.1.0", "@types/eslint": "^8.44.2", "@types/node": "^20.8.10", "ava": "^5.0.1", @@ -988,6 +989,16 @@ } } }, + "node_modules/@seamapi/url-search-params-serializer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@seamapi/url-search-params-serializer/-/url-search-params-serializer-1.1.0.tgz", + "integrity": "sha512-abp2ktK1Maoq+lbxoEg/a4DbY7ZRz7w0S08KYFfDWqITw49yljxBoT84dfCgAGo1l75apDhDzzDX8a59eusC4Q==", + "dev": true, + "engines": { + "node": ">=18.12.0", + "npm": ">= 9.0.0" + } + }, "node_modules/@types/eslint": { "version": "8.44.9", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.9.tgz", diff --git a/package.json b/package.json index 3a1bf592..7f3a9aa6 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "devDependencies": { "@seamapi/fake-seam-connect": "^1.44.2", "@seamapi/types": "^1.58.0", + "@seamapi/url-search-params-serializer": "^1.1.0", "@types/eslint": "^8.44.2", "@types/node": "^20.8.10", "ava": "^5.0.1", diff --git a/src/connect.ts b/src/connect.ts index f6336f3f..d35f711c 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -1 +1,2 @@ export * from './lib/seam/connect/index.js' +export * from '@seamapi/url-search-params-serializer' diff --git a/src/lib/params-serializer.test.ts b/src/lib/params-serializer.test.ts deleted file mode 100644 index 42d1dc80..00000000 --- a/src/lib/params-serializer.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import test from 'ava' - -import { - paramsSerializer, - UnserializableParamError, -} from './params-serializer.js' - -test('serializes empty object', (t) => { - t.is(paramsSerializer({}), '') -}) - -test('serializes string', (t) => { - t.is(paramsSerializer({ foo: 'd' }), 'foo=d') - t.is(paramsSerializer({ foo: 'null' }), 'foo=null') - t.is(paramsSerializer({ foo: 'undefined' }), 'foo=undefined') - t.is(paramsSerializer({ foo: '0' }), 'foo=0') -}) - -test('serializes number', (t) => { - t.is(paramsSerializer({ foo: 1 }), 'foo=1') - t.is(paramsSerializer({ foo: 23.8 }), 'foo=23.8') -}) - -test('serializes boolean', (t) => { - t.is(paramsSerializer({ foo: true }), 'foo=true') - t.is(paramsSerializer({ foo: false }), 'foo=false') -}) - -test('removes undefined params', (t) => { - t.is(paramsSerializer({ bar: undefined }), '') - t.is(paramsSerializer({ foo: 1, bar: undefined }), 'foo=1') -}) - -test('removes null params', (t) => { - t.is(paramsSerializer({ bar: null }), '') - t.is(paramsSerializer({ foo: 1, bar: null }), 'foo=1') -}) - -test('serializes empty array params', (t) => { - t.is(paramsSerializer({ bar: [] }), 'bar=') - t.is(paramsSerializer({ foo: 1, bar: [] }), 'bar=&foo=1') -}) - -test('serializes array params with one value', (t) => { - t.is(paramsSerializer({ bar: ['a'] }), 'bar=a') - t.is(paramsSerializer({ foo: 1, bar: ['a'] }), 'bar=a&foo=1') -}) - -test('serializes array params with many values', (t) => { - t.is(paramsSerializer({ foo: 1, bar: ['a', '2'] }), 'bar=a&bar=2&foo=1') - t.is( - paramsSerializer({ foo: 1, bar: ['null', '2', 'undefined'] }), - 'bar=null&bar=2&bar=undefined&foo=1', - ) - t.is(paramsSerializer({ foo: 1, bar: ['', '', ''] }), 'bar=&bar=&bar=&foo=1') - t.is( - paramsSerializer({ foo: 1, bar: ['', 'a', '2'] }), - 'bar=&bar=a&bar=2&foo=1', - ) - t.is( - paramsSerializer({ foo: 1, bar: ['', 'a', ''] }), - 'bar=&bar=a&bar=&foo=1', - ) -}) - -test('cannot serialize single element array params with empty string', (t) => { - t.throws(() => paramsSerializer({ foo: [''] }), { - instanceOf: UnserializableParamError, - }) -}) - -test('cannot serialize unserializable values', (t) => { - t.throws(() => paramsSerializer({ foo: {} }), { - instanceOf: UnserializableParamError, - }) - t.throws(() => paramsSerializer({ foo: { x: 2 } }), { - instanceOf: UnserializableParamError, - }) - t.throws(() => paramsSerializer({ foo: () => {} }), { - instanceOf: UnserializableParamError, - }) -}) - -test('cannot serialize array params with unserializable values', (t) => { - t.throws(() => paramsSerializer({ bar: ['a', null] }), { - instanceOf: UnserializableParamError, - }) - t.throws(() => paramsSerializer({ bar: ['a', undefined] }), { - instanceOf: UnserializableParamError, - }) - t.throws(() => paramsSerializer({ bar: ['a', ['s']] }), { - instanceOf: UnserializableParamError, - }) - t.throws(() => paramsSerializer({ bar: ['a', []] }), { - instanceOf: UnserializableParamError, - }) - t.throws(() => paramsSerializer({ bar: ['a', {}] }), { - instanceOf: UnserializableParamError, - }) - t.throws(() => paramsSerializer({ bar: ['a', { x: 2 }] }), { - instanceOf: UnserializableParamError, - }) - t.throws(() => paramsSerializer({ bar: ['a', () => {}] }), { - instanceOf: UnserializableParamError, - }) -}) diff --git a/src/lib/params-serializer.ts b/src/lib/params-serializer.ts deleted file mode 100644 index 429b5a66..00000000 --- a/src/lib/params-serializer.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { CustomParamsSerializer } from 'axios' - -export const paramsSerializer: CustomParamsSerializer = (params) => { - const searchParams = new URLSearchParams() - - for (const [name, value] of Object.entries(params)) { - if (value == null) continue - - if (Array.isArray(value)) { - if (value.length === 0) searchParams.set(name, '') - if (value.length === 1 && value[0] === '') { - throw new UnserializableParamError( - name, - `is a single element array containing the empty string which is unsupported because it serializes to the empty array`, - ) - } - for (const v of value) { - searchParams.append(name, serialize(name, v)) - } - continue - } - - searchParams.set(name, serialize(name, value)) - } - - searchParams.sort() - return searchParams.toString() -} - -const serialize = (k: string, v: unknown): string => { - if (typeof v === 'string') return v.toString() - if (typeof v === 'number') return v.toString() - if (typeof v === 'bigint') return v.toString() - if (typeof v === 'boolean') return v.toString() - throw new UnserializableParamError(k, `is a ${typeof v}`) -} - -export class UnserializableParamError extends Error { - constructor(name: string, message: string) { - super(`Could not serialize parameter: '${name}' ${message}`) - this.name = this.constructor.name - Error.captureStackTrace(this, this.constructor) - } -} diff --git a/src/lib/seam/connect/client.ts b/src/lib/seam/connect/client.ts index be845887..09f39fbd 100644 --- a/src/lib/seam/connect/client.ts +++ b/src/lib/seam/connect/client.ts @@ -2,7 +2,7 @@ import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios' import axiosBetterStacktrace from 'axios-better-stacktrace' import axiosRetry, { type AxiosRetry, exponentialDelay } from 'axios-retry' -import { paramsSerializer } from 'lib/params-serializer.js' +import { serializeUrlSearchParams } from '@seamapi/url-search-params-serializer' import { errorInterceptor } from './error-interceptor.js' @@ -17,7 +17,7 @@ type AxiosRetryConfig = Parameters[1] export const createClient = (options: ClientOptions): AxiosInstance => { const client = axios.create({ - paramsSerializer, + paramsSerializer: serializeUrlSearchParams, ...options.axiosOptions, }) diff --git a/src/lib/seam/connect/index.ts b/src/lib/seam/connect/index.ts index 0d3c2c24..ccfd65a0 100644 --- a/src/lib/seam/connect/index.ts +++ b/src/lib/seam/connect/index.ts @@ -21,4 +21,3 @@ export { isPersonalAccessToken, isPublishableKey, } from './token.js' -export * from 'lib/params-serializer.js'