Skip to content

Commit 74862ef

Browse files
committed
remove qs dep
1 parent 312acc2 commit 74862ef

File tree

6 files changed

+106
-26
lines changed

6 files changed

+106
-26
lines changed

package-lock.json

Lines changed: 20 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"test": "jest",
3535
"test:watch": "jest --watch",
3636
"check:runtimes": "tsx scripts/ecosystem-check.ts",
37+
"check:runtimes:full": "tsx scripts/ecosystem-check-enhanced.ts",
3738
"prettier": "prettier \"src/**/*.{js,ts,tsx}\" --check",
3839
"format": "prettier \"src/**/*.{js,ts,tsx}\" --write",
3940
"prepublishOnly": "npm run build",
@@ -42,8 +43,7 @@
4243
"dependencies": {
4344
"iron-session": "^8.0.4",
4445
"jose": "~6.1.0",
45-
"pluralize": "8.0.0",
46-
"qs": "6.14.0"
46+
"pluralize": "8.0.0"
4747
},
4848
"devDependencies": {
4949
"@babel/plugin-transform-modules-commonjs": "^7.26.3",

src/client/sso.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { toQueryString } from './utils';
1+
import { toQueryString } from '../common/utils/query-string';
22
import type { SSOAuthorizationURLOptions as BaseSSOAuthorizationURLOptions } from '../sso/interfaces';
33

44
// Extend the base options to include baseURL for internal use

src/client/user-management.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { toQueryString } from './utils';
1+
import { toQueryString } from '../common/utils/query-string';
22

33
// Re-export necessary interfaces for client use
44
export interface AuthorizationURLOptions {

src/client/utils.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/common/utils/query-string.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Native query string serialization that replaces the qs library.
3+
* Maintains backward compatibility with existing URL formatting.
4+
*
5+
* This implementation:
6+
* - Uses only native Web APIs (works in all runtimes)
7+
* - Handles arrays with 'repeat' format: scope=read&scope=write
8+
* - Sorts keys alphabetically for consistency
9+
* - Uses RFC1738 encoding (space as +)
10+
* - Handles nested objects: provider_query_params[key]=value
11+
* - Filters out undefined values
12+
*/
13+
14+
type QueryValue = string | string[] | Record<string, string | boolean | number> | undefined;
15+
16+
/**
17+
* Converts an options object to a query string.
18+
* Compatible with the qs library's stringify method when used with:
19+
* - arrayFormat: 'repeat'
20+
* - format: 'RFC1738'
21+
* - sort: alphabetical
22+
*/
23+
export function toQueryString(options: Record<string, QueryValue>): string {
24+
const params: Array<[string, string]> = [];
25+
26+
// Get sorted keys for consistent output (matches qs behavior)
27+
const sortedKeys = Object.keys(options).sort((a, b) => a.localeCompare(b));
28+
29+
for (const key of sortedKeys) {
30+
const value = options[key];
31+
32+
// Skip undefined values (matches qs behavior)
33+
if (value === undefined) {
34+
continue;
35+
}
36+
37+
// Handle arrays with 'repeat' format: key=val1&key=val2
38+
if (Array.isArray(value)) {
39+
for (const item of value) {
40+
params.push([key, String(item)]);
41+
}
42+
}
43+
// Handle nested objects: key[subkey]=value
44+
else if (typeof value === 'object' && value !== null) {
45+
const sortedSubKeys = Object.keys(value).sort((a, b) => a.localeCompare(b));
46+
for (const subKey of sortedSubKeys) {
47+
const subValue = value[subKey];
48+
if (subValue !== undefined) {
49+
params.push([`${key}[${subKey}]`, String(subValue)]);
50+
}
51+
}
52+
}
53+
// Handle primitives (string, number, boolean)
54+
else {
55+
params.push([key, String(value)]);
56+
}
57+
}
58+
59+
// Build query string with RFC1738 encoding (space as +)
60+
return params
61+
.map(([key, value]) => {
62+
// Encode using RFC1738 format (matches qs behavior)
63+
const encodedKey = encodeRFC1738(key);
64+
const encodedValue = encodeRFC1738(value);
65+
return `${encodedKey}=${encodedValue}`;
66+
})
67+
.join('&');
68+
}
69+
70+
/**
71+
* Encodes a string using RFC1738 format.
72+
* - Space is encoded as +
73+
* - Additional characters encoded to match qs library behavior
74+
*/
75+
function encodeRFC1738(str: string): string {
76+
return encodeURIComponent(str)
77+
.replace(/%20/g, '+') // Space as + (RFC1738)
78+
.replace(/[!'*]/g, (c) => {
79+
// Encode additional characters to match qs behavior
80+
return '%' + c.charCodeAt(0).toString(16).toUpperCase();
81+
});
82+
}

0 commit comments

Comments
 (0)