@unshared/client 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{0ZzUT3m_.js → B6pUErTM.js} +2 -2
- package/dist/chunks/B6pUErTM.js.map +1 -0
- package/dist/chunks/{CYmaYL5B.cjs → BDxlAULu.cjs} +2 -2
- package/dist/chunks/BDxlAULu.cjs.map +1 -0
- package/dist/chunks/{CO11DuYE.d.ts → BZ5qrH6f.d.ts} +22 -2
- package/dist/chunks/{Biic1J5b.js → B_Gz6Yz8.js} +23 -1
- package/dist/chunks/B_Gz6Yz8.js.map +1 -0
- package/dist/chunks/{VkINJoq7.cjs → BdFNzMcu.cjs} +22 -12
- package/dist/chunks/BdFNzMcu.cjs.map +1 -0
- package/dist/chunks/{DOZHjge0.d.ts → Bs2VarsP.d.ts} +11 -9
- package/dist/chunks/{Du56lBvc.js → Bys4-xE2.js} +23 -12
- package/dist/chunks/Bys4-xE2.js.map +1 -0
- package/dist/chunks/CS5r-m4U.js +191 -0
- package/dist/chunks/CS5r-m4U.js.map +1 -0
- package/dist/chunks/CVzmr2NA.cjs +189 -0
- package/dist/chunks/CVzmr2NA.cjs.map +1 -0
- package/dist/chunks/D1QsGr3A.js +15 -0
- package/dist/chunks/D1QsGr3A.js.map +1 -0
- package/dist/chunks/{CtW2aMuA.cjs → DEyigyGy.cjs} +23 -1
- package/dist/chunks/DEyigyGy.cjs.map +1 -0
- package/dist/chunks/DXrQkl1A.cjs +14 -0
- package/dist/chunks/DXrQkl1A.cjs.map +1 -0
- package/dist/createClient.cjs +5 -3
- package/dist/createClient.cjs.map +1 -1
- package/dist/createClient.d.ts +9 -6
- package/dist/createClient.js +5 -4
- package/dist/createClient.js.map +1 -1
- package/dist/createService.cjs +3 -3
- package/dist/createService.cjs.map +1 -1
- package/dist/createService.d.ts +2 -2
- package/dist/createService.js +4 -4
- package/dist/createService.js.map +1 -1
- package/dist/index.cjs +6 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/openapi.cjs +13 -3
- package/dist/openapi.cjs.map +1 -1
- package/dist/openapi.d.ts +45 -4
- package/dist/openapi.js +11 -1
- package/dist/openapi.js.map +1 -1
- package/dist/utils.cjs +15 -3
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.ts +114 -8
- package/dist/utils.js +21 -8
- package/dist/utils.js.map +1 -1
- package/dist/websocket.cjs +3 -2
- package/dist/websocket.cjs.map +1 -1
- package/dist/websocket.d.ts +13 -18
- package/dist/websocket.js +3 -2
- package/dist/websocket.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunks/0ZzUT3m_.js.map +0 -1
- package/dist/chunks/BUeqbyph.js +0 -114
- package/dist/chunks/BUeqbyph.js.map +0 -1
- package/dist/chunks/Biic1J5b.js.map +0 -1
- package/dist/chunks/CYmaYL5B.cjs.map +0 -1
- package/dist/chunks/Cayg8606.cjs +0 -112
- package/dist/chunks/Cayg8606.cjs.map +0 -1
- package/dist/chunks/CtW2aMuA.cjs.map +0 -1
- package/dist/chunks/DJJsADWD.js +0 -9
- package/dist/chunks/DJJsADWD.js.map +0 -1
- package/dist/chunks/Du56lBvc.js.map +0 -1
- package/dist/chunks/Du_W5H6e.cjs +0 -8
- package/dist/chunks/Du_W5H6e.cjs.map +0 -1
- package/dist/chunks/VkINJoq7.cjs.map +0 -1
@@ -1,7 +1,7 @@
|
|
1
1
|
const EXP_PATH_PARAMETER = /:([\w-]+)|%7B([\w-]+)%7D/g;
|
2
2
|
function parseRequestParameters(context, options) {
|
3
3
|
const { url } = context, { parameters } = options;
|
4
|
-
if (typeof parameters != "object" || parameters === null || url === void 0) return;
|
4
|
+
if (parameters === void 0 || typeof parameters != "object" || parameters === null || url === void 0) return;
|
5
5
|
if (!(url instanceof URL)) throw new Error("The `url` must be an instance of `URL`.");
|
6
6
|
const pathParameters = url.pathname.match(EXP_PATH_PARAMETER);
|
7
7
|
if (pathParameters)
|
@@ -43,4 +43,4 @@ export {
|
|
43
43
|
parseRequestParameters as p,
|
44
44
|
toSearchParams as t
|
45
45
|
};
|
46
|
-
//# sourceMappingURL=
|
46
|
+
//# sourceMappingURL=B6pUErTM.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"B6pUErTM.js","sources":["../../utils/parseRequestParameters.ts","../../utils/toSearchParams.ts","../../utils/parseRequestQuery.ts"],"sourcesContent":["import type { FetchOptions, RequestContext } from './parseRequest'\n\n/** Regular expression to match path parameters in the URL. */\nconst EXP_PATH_PARAMETER = /:([\\w-]+)|%7B([\\w-]+)%7D/g\n\n/**\n * Parse the request parameters from the request data. This function will mutate the\n * `url` object of the context to include the path parameters based on the provided data.\n *\n * @param context The request context to mutate.\n * @param options The options to pass to the request.\n * @example\n *\n * // Using `express` style path parameters.\n * const context = { url: new URL('https://api.example.com/users/:id') }\n * parseRequestParameters(context, { parameters: { id: 1 } })\n * console.log(context.url.pathname) // 'https://api.example.com/users/1'\n *\n * // Using `OpenAPI` style path parameters.\n * const context = { url: new URL('https://api.example.com/users/{id}') }\n * parseRequestParameters(context, { parameters: { id: 1 } })\n * console.log(context.url.pathname) // 'https://api.example.com/users/1'\n */\nexport function parseRequestParameters(context: Partial<RequestContext>, options: FetchOptions): void {\n const { url } = context\n const { parameters } = options\n\n // --- Return early if the parameters is not an object.\n if (parameters === undefined || typeof parameters !== 'object' || parameters === null || url === undefined) return\n if (url instanceof URL === false) throw new Error('The `url` must be an instance of `URL`.')\n\n // --- If the method has a parameter, fill the path with the data.\n const pathParameters = url.pathname.match(EXP_PATH_PARAMETER)\n if (!pathParameters) return\n\n // --- Apply the path parameters to the URL.\n for (const parameter of pathParameters.values()) {\n const key = parameter.replaceAll(EXP_PATH_PARAMETER, '$1$2')\n const value = parameters[key]\n\n // --- If the parameter is provided, replace the path with the value.\n if (\n (typeof value === 'string' && value.length > 0)\n || (typeof value === 'number' && Number.isFinite(value))\n || (typeof value === 'boolean')\n ) {\n url.pathname = url.pathname.replace(parameter, value.toString())\n delete parameters[key]\n }\n }\n}\n","/* eslint-disable unicorn/prevent-abbreviations */\nimport type { MaybeArray } from '@unshared/types'\n\n/** An object that can be converted to a query string. */\nexport type SearchParamsObject = Record<string, MaybeArray<boolean | number | string> | undefined>\n\n/** The search array format options. */\nexport type SearchArrayFormat = 'brackets' | 'comma' | 'flat' | 'indices' | 'path'\n\n/** Options for the query string conversion. */\nexport interface ToSearchParamsOptions {\n\n /**\n * Defines how to handle arrays in the object. There is no standard way to\n * represent arrays in query strings, so this option allows you to choose\n * how to handle them. Additionally, you can provide a custom function to\n * handle it yourself.\n *\n * - `brackets` (default): Convert arrays to `key[]=value&key[]=value` format.\n * - `indices`: Convert arrays to `key[0]=value&key[1]=value` format.\n * - `comma`: Convert arrays to `key=value1,value2` format.\n * - `path`: Convert arrays to `key.0=value&key.1=value` format.\n * - `flat`: Convert arrays to `key=value1&key=value2` format.\n *\n * @default 'flat'\n */\n format?: SearchArrayFormat\n}\n\n/**\n * Convert object to query string parameters. Converting all values to strings\n * and arrays to `key[0]=value&key[1]=value` format.\n *\n * @param object The object to convert to a query string.\n * @param options The query string options.\n * @returns The `URLSearchParams` object.\n */\nexport function toSearchParams(object: SearchParamsObject, options: ToSearchParamsOptions = {}): URLSearchParams {\n const { format = 'flat' } = options\n const search = new URLSearchParams()\n for (const key in object) {\n const value = object[key]\n if (value === undefined) continue\n\n // --- Convert arrays based on the format.\n if (Array.isArray(value)) {\n if (format === 'brackets') for (const v of value) search.append(`${key}[]`, String(v))\n else if (format === 'indices') for (const [i, v] of value.entries()) search.append(`${key}[${i}]`, String(v))\n else if (format === 'comma') search.append(key, value.join(','))\n else if (format === 'path') for (const [i, v] of value.entries()) search.append(`${key}.${i}`, String(v))\n else if (format === 'flat') for (const v of value) search.append(key, String(v))\n }\n\n // --- Convert all values to strings.\n else { search.append(key, value.toString()) }\n }\n\n // --- Return the query string.\n return search\n}\n","/* eslint-disable unicorn/prevent-abbreviations */\nimport type { FetchOptions, RequestContext } from './parseRequest'\nimport type { SearchParamsObject as SearchParametersObject } from './toSearchParams'\nimport { toSearchParams } from './toSearchParams'\n\n/**\n * Parse the query parameters from the request data. This function will append\n * the query parameters to the URL based on the method and the data provided.\n *\n * @param context The request context to modify.\n * @param options The options to pass to the request.\n */\nexport function parseRequestQuery(context: Partial<RequestContext>, options: FetchOptions): void {\n const { url } = context\n const { query, queryArrayFormat } = options\n\n // --- Return early if the query is not an object or the URL is not provided.\n if (url === undefined) return\n if (url instanceof URL === false) throw new Error('The `url` must be an instance of `URL.')\n if (typeof query !== 'object' || query === null) return\n\n // --- Append the `data` to the query parameters if the method does not expect a body.\n const queryObject: SearchParametersObject = {}\n for (const key in query) {\n const value = query[key]\n if (\n (typeof value === 'string' && value.length > 0)\n || (typeof value === 'number' && Number.isFinite(value))\n || (typeof value === 'boolean')\n || Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')\n ) {\n queryObject[key] = value\n delete query[key]\n }\n }\n\n // --- Apply the query parameters to the URL.\n url.search = toSearchParams(queryObject, { format: queryArrayFormat }).toString()\n}\n"],"names":[],"mappings":"AAGA,MAAM,qBAAqB;AAoBX,SAAA,uBAAuB,SAAkC,SAA6B;AACpG,QAAM,EAAE,IAAI,IAAI,SACV,EAAE,WAAe,IAAA;AAGnB,MAAA,eAAe,UAAa,OAAO,cAAe,YAAY,eAAe,QAAQ,QAAQ,OAAW;AAC5G,MAAI,EAAe,eAAA,KAAqB,OAAA,IAAI,MAAM,yCAAyC;AAG3F,QAAM,iBAAiB,IAAI,SAAS,MAAM,kBAAkB;AACvD,MAAA;AAGM,eAAA,aAAa,eAAe,UAAU;AACzC,YAAA,MAAM,UAAU,WAAW,oBAAoB,MAAM,GACrD,QAAQ,WAAW,GAAG;AAG5B,OACG,OAAO,SAAU,YAAY,MAAM,SAAS,KACzC,OAAO,SAAU,YAAY,OAAO,SAAS,KAAK,KAClD,OAAO,SAAU,eAErB,IAAI,WAAW,IAAI,SAAS,QAAQ,WAAW,MAAM,SAAA,CAAU,GAC/D,OAAO,WAAW,GAAG;AAAA,IAAA;AAG3B;ACbO,SAAS,eAAe,QAA4B,UAAiC,IAAqB;AAC/G,QAAM,EAAE,SAAS,OAAA,IAAW,SACtB,SAAS,IAAI,gBAAgB;AACnC,aAAW,OAAO,QAAQ;AAClB,UAAA,QAAQ,OAAO,GAAG;AACxB,QAAI,UAAU;AAGV,UAAA,MAAM,QAAQ,KAAK;AACrB,YAAI,WAAW,WAAuB,YAAA,KAAK,MAAO,QAAO,OAAO,GAAG,GAAG,MAAM,OAAO,CAAC,CAAC;AAAA,iBAC5E,WAAW,UAAW,YAAW,CAAC,GAAG,CAAC,KAAK,MAAM,UAAkB,QAAA,OAAO,GAAG,GAAG,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC;AAAA,iBACnG,WAAW,QAAgB,QAAA,OAAO,KAAK,MAAM,KAAK,GAAG,CAAC;AAAA,iBACtD,WAAW,OAAQ,YAAW,CAAC,GAAG,CAAC,KAAK,MAAM,UAAkB,QAAA,OAAO,GAAG,GAAG,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC;AAAA,iBAC/F,WAAW,OAAQ,YAAW,KAAK,cAAc,OAAO,KAAK,OAAO,CAAC,CAAC;AAAA;AAI1E,eAAO,OAAO,KAAK,MAAM,SAAA,CAAU;AAAA,EAAA;AAIrC,SAAA;AACT;AC/CgB,SAAA,kBAAkB,SAAkC,SAA6B;AAC/F,QAAM,EAAE,IAAI,IAAI,SACV,EAAE,OAAO,qBAAqB;AAGpC,MAAI,QAAQ,OAAW;AACvB,MAAI,EAAe,eAAA,KAAqB,OAAA,IAAI,MAAM,wCAAwC;AAC1F,MAAI,OAAO,SAAU,YAAY,UAAU,KAAM;AAGjD,QAAM,cAAsC,CAAC;AAC7C,aAAW,OAAO,OAAO;AACjB,UAAA,QAAQ,MAAM,GAAG;AACvB,KACG,OAAO,SAAU,YAAY,MAAM,SAAS,KACzC,OAAO,SAAU,YAAY,OAAO,SAAS,KAAK,KAClD,OAAO,SAAU,aAClB,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM,CAAA,MAAK,OAAO,KAAM,YAAY,OAAO,KAAM,YAAY,OAAO,KAAM,SAAS,OAExI,YAAY,GAAG,IAAI,OACnB,OAAO,MAAM,GAAG;AAAA,EAAA;AAKhB,MAAA,SAAS,eAAe,aAAa,EAAE,QAAQ,iBAAiB,CAAC,EAAE,SAAS;AAClF;"}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
const EXP_PATH_PARAMETER = /:([\w-]+)|%7B([\w-]+)%7D/g;
|
3
3
|
function parseRequestParameters(context, options) {
|
4
4
|
const { url } = context, { parameters } = options;
|
5
|
-
if (typeof parameters != "object" || parameters === null || url === void 0) return;
|
5
|
+
if (parameters === void 0 || typeof parameters != "object" || parameters === null || url === void 0) return;
|
6
6
|
if (!(url instanceof URL)) throw new Error("The `url` must be an instance of `URL`.");
|
7
7
|
const pathParameters = url.pathname.match(EXP_PATH_PARAMETER);
|
8
8
|
if (pathParameters)
|
@@ -42,4 +42,4 @@ function parseRequestQuery(context, options) {
|
|
42
42
|
exports.parseRequestParameters = parseRequestParameters;
|
43
43
|
exports.parseRequestQuery = parseRequestQuery;
|
44
44
|
exports.toSearchParams = toSearchParams;
|
45
|
-
//# sourceMappingURL=
|
45
|
+
//# sourceMappingURL=BDxlAULu.cjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"BDxlAULu.cjs","sources":["../../utils/parseRequestParameters.ts","../../utils/toSearchParams.ts","../../utils/parseRequestQuery.ts"],"sourcesContent":["import type { FetchOptions, RequestContext } from './parseRequest'\n\n/** Regular expression to match path parameters in the URL. */\nconst EXP_PATH_PARAMETER = /:([\\w-]+)|%7B([\\w-]+)%7D/g\n\n/**\n * Parse the request parameters from the request data. This function will mutate the\n * `url` object of the context to include the path parameters based on the provided data.\n *\n * @param context The request context to mutate.\n * @param options The options to pass to the request.\n * @example\n *\n * // Using `express` style path parameters.\n * const context = { url: new URL('https://api.example.com/users/:id') }\n * parseRequestParameters(context, { parameters: { id: 1 } })\n * console.log(context.url.pathname) // 'https://api.example.com/users/1'\n *\n * // Using `OpenAPI` style path parameters.\n * const context = { url: new URL('https://api.example.com/users/{id}') }\n * parseRequestParameters(context, { parameters: { id: 1 } })\n * console.log(context.url.pathname) // 'https://api.example.com/users/1'\n */\nexport function parseRequestParameters(context: Partial<RequestContext>, options: FetchOptions): void {\n const { url } = context\n const { parameters } = options\n\n // --- Return early if the parameters is not an object.\n if (parameters === undefined || typeof parameters !== 'object' || parameters === null || url === undefined) return\n if (url instanceof URL === false) throw new Error('The `url` must be an instance of `URL`.')\n\n // --- If the method has a parameter, fill the path with the data.\n const pathParameters = url.pathname.match(EXP_PATH_PARAMETER)\n if (!pathParameters) return\n\n // --- Apply the path parameters to the URL.\n for (const parameter of pathParameters.values()) {\n const key = parameter.replaceAll(EXP_PATH_PARAMETER, '$1$2')\n const value = parameters[key]\n\n // --- If the parameter is provided, replace the path with the value.\n if (\n (typeof value === 'string' && value.length > 0)\n || (typeof value === 'number' && Number.isFinite(value))\n || (typeof value === 'boolean')\n ) {\n url.pathname = url.pathname.replace(parameter, value.toString())\n delete parameters[key]\n }\n }\n}\n","/* eslint-disable unicorn/prevent-abbreviations */\nimport type { MaybeArray } from '@unshared/types'\n\n/** An object that can be converted to a query string. */\nexport type SearchParamsObject = Record<string, MaybeArray<boolean | number | string> | undefined>\n\n/** The search array format options. */\nexport type SearchArrayFormat = 'brackets' | 'comma' | 'flat' | 'indices' | 'path'\n\n/** Options for the query string conversion. */\nexport interface ToSearchParamsOptions {\n\n /**\n * Defines how to handle arrays in the object. There is no standard way to\n * represent arrays in query strings, so this option allows you to choose\n * how to handle them. Additionally, you can provide a custom function to\n * handle it yourself.\n *\n * - `brackets` (default): Convert arrays to `key[]=value&key[]=value` format.\n * - `indices`: Convert arrays to `key[0]=value&key[1]=value` format.\n * - `comma`: Convert arrays to `key=value1,value2` format.\n * - `path`: Convert arrays to `key.0=value&key.1=value` format.\n * - `flat`: Convert arrays to `key=value1&key=value2` format.\n *\n * @default 'flat'\n */\n format?: SearchArrayFormat\n}\n\n/**\n * Convert object to query string parameters. Converting all values to strings\n * and arrays to `key[0]=value&key[1]=value` format.\n *\n * @param object The object to convert to a query string.\n * @param options The query string options.\n * @returns The `URLSearchParams` object.\n */\nexport function toSearchParams(object: SearchParamsObject, options: ToSearchParamsOptions = {}): URLSearchParams {\n const { format = 'flat' } = options\n const search = new URLSearchParams()\n for (const key in object) {\n const value = object[key]\n if (value === undefined) continue\n\n // --- Convert arrays based on the format.\n if (Array.isArray(value)) {\n if (format === 'brackets') for (const v of value) search.append(`${key}[]`, String(v))\n else if (format === 'indices') for (const [i, v] of value.entries()) search.append(`${key}[${i}]`, String(v))\n else if (format === 'comma') search.append(key, value.join(','))\n else if (format === 'path') for (const [i, v] of value.entries()) search.append(`${key}.${i}`, String(v))\n else if (format === 'flat') for (const v of value) search.append(key, String(v))\n }\n\n // --- Convert all values to strings.\n else { search.append(key, value.toString()) }\n }\n\n // --- Return the query string.\n return search\n}\n","/* eslint-disable unicorn/prevent-abbreviations */\nimport type { FetchOptions, RequestContext } from './parseRequest'\nimport type { SearchParamsObject as SearchParametersObject } from './toSearchParams'\nimport { toSearchParams } from './toSearchParams'\n\n/**\n * Parse the query parameters from the request data. This function will append\n * the query parameters to the URL based on the method and the data provided.\n *\n * @param context The request context to modify.\n * @param options The options to pass to the request.\n */\nexport function parseRequestQuery(context: Partial<RequestContext>, options: FetchOptions): void {\n const { url } = context\n const { query, queryArrayFormat } = options\n\n // --- Return early if the query is not an object or the URL is not provided.\n if (url === undefined) return\n if (url instanceof URL === false) throw new Error('The `url` must be an instance of `URL.')\n if (typeof query !== 'object' || query === null) return\n\n // --- Append the `data` to the query parameters if the method does not expect a body.\n const queryObject: SearchParametersObject = {}\n for (const key in query) {\n const value = query[key]\n if (\n (typeof value === 'string' && value.length > 0)\n || (typeof value === 'number' && Number.isFinite(value))\n || (typeof value === 'boolean')\n || Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')\n ) {\n queryObject[key] = value\n delete query[key]\n }\n }\n\n // --- Apply the query parameters to the URL.\n url.search = toSearchParams(queryObject, { format: queryArrayFormat }).toString()\n}\n"],"names":[],"mappings":";AAGA,MAAM,qBAAqB;AAoBX,SAAA,uBAAuB,SAAkC,SAA6B;AACpG,QAAM,EAAE,IAAI,IAAI,SACV,EAAE,WAAe,IAAA;AAGnB,MAAA,eAAe,UAAa,OAAO,cAAe,YAAY,eAAe,QAAQ,QAAQ,OAAW;AAC5G,MAAI,EAAe,eAAA,KAAqB,OAAA,IAAI,MAAM,yCAAyC;AAG3F,QAAM,iBAAiB,IAAI,SAAS,MAAM,kBAAkB;AACvD,MAAA;AAGM,eAAA,aAAa,eAAe,UAAU;AACzC,YAAA,MAAM,UAAU,WAAW,oBAAoB,MAAM,GACrD,QAAQ,WAAW,GAAG;AAG5B,OACG,OAAO,SAAU,YAAY,MAAM,SAAS,KACzC,OAAO,SAAU,YAAY,OAAO,SAAS,KAAK,KAClD,OAAO,SAAU,eAErB,IAAI,WAAW,IAAI,SAAS,QAAQ,WAAW,MAAM,SAAA,CAAU,GAC/D,OAAO,WAAW,GAAG;AAAA,IAAA;AAG3B;ACbO,SAAS,eAAe,QAA4B,UAAiC,IAAqB;AAC/G,QAAM,EAAE,SAAS,OAAA,IAAW,SACtB,SAAS,IAAI,gBAAgB;AACnC,aAAW,OAAO,QAAQ;AAClB,UAAA,QAAQ,OAAO,GAAG;AACxB,QAAI,UAAU;AAGV,UAAA,MAAM,QAAQ,KAAK;AACrB,YAAI,WAAW,WAAuB,YAAA,KAAK,MAAO,QAAO,OAAO,GAAG,GAAG,MAAM,OAAO,CAAC,CAAC;AAAA,iBAC5E,WAAW,UAAW,YAAW,CAAC,GAAG,CAAC,KAAK,MAAM,UAAkB,QAAA,OAAO,GAAG,GAAG,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC;AAAA,iBACnG,WAAW,QAAgB,QAAA,OAAO,KAAK,MAAM,KAAK,GAAG,CAAC;AAAA,iBACtD,WAAW,OAAQ,YAAW,CAAC,GAAG,CAAC,KAAK,MAAM,UAAkB,QAAA,OAAO,GAAG,GAAG,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC;AAAA,iBAC/F,WAAW,OAAQ,YAAW,KAAK,cAAc,OAAO,KAAK,OAAO,CAAC,CAAC;AAAA;AAI1E,eAAO,OAAO,KAAK,MAAM,SAAA,CAAU;AAAA,EAAA;AAIrC,SAAA;AACT;AC/CgB,SAAA,kBAAkB,SAAkC,SAA6B;AAC/F,QAAM,EAAE,IAAI,IAAI,SACV,EAAE,OAAO,qBAAqB;AAGpC,MAAI,QAAQ,OAAW;AACvB,MAAI,EAAe,eAAA,KAAqB,OAAA,IAAI,MAAM,wCAAwC;AAC1F,MAAI,OAAO,SAAU,YAAY,UAAU,KAAM;AAGjD,QAAM,cAAsC,CAAC;AAC7C,aAAW,OAAO,OAAO;AACjB,UAAA,QAAQ,MAAM,GAAG;AACvB,KACG,OAAO,SAAU,YAAY,MAAM,SAAS,KACzC,OAAO,SAAU,YAAY,OAAO,SAAS,KAAK,KAClD,OAAO,SAAU,aAClB,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM,CAAA,MAAK,OAAO,KAAM,YAAY,OAAO,KAAM,YAAY,OAAO,KAAM,SAAS,OAExI,YAAY,GAAG,IAAI,OACnB,OAAO,MAAM,GAAG;AAAA,EAAA;AAKhB,MAAA,SAAS,eAAe,aAAa,EAAE,QAAQ,iBAAiB,CAAC,EAAE,SAAS;AAClF;;;;"}
|
@@ -80,10 +80,30 @@ interface FetchOptions<Method extends FetchMethod = FetchMethod, BaseUrl extends
|
|
80
80
|
* The headers to include in the request.
|
81
81
|
*/
|
82
82
|
headers?: FetchHeaders & Headers;
|
83
|
+
/**
|
84
|
+
* The username for basic authentication.
|
85
|
+
*/
|
86
|
+
username?: string;
|
87
|
+
/**
|
88
|
+
* The password for basic authentication.
|
89
|
+
*/
|
90
|
+
password?: string;
|
91
|
+
/**
|
92
|
+
* The token for API key authentication.
|
93
|
+
*/
|
94
|
+
token?: string;
|
95
|
+
/**
|
96
|
+
* The location where the token should be included in the request.
|
97
|
+
*/
|
98
|
+
tokenLocation?: 'cookie' | 'header' | 'query';
|
99
|
+
/**
|
100
|
+
* The name of the key to use in the request for the token.
|
101
|
+
*/
|
102
|
+
tokenProperty?: string;
|
83
103
|
}
|
84
104
|
interface RequestContext {
|
85
|
-
url
|
86
|
-
init
|
105
|
+
url: URL;
|
106
|
+
init: RequestInit;
|
87
107
|
}
|
88
108
|
/**
|
89
109
|
* Resolves the request body and/or query parameters based on the method type. This function
|
@@ -25,8 +25,30 @@ function resolveOperation(document, operationId) {
|
|
25
25
|
}
|
26
26
|
throw new Error(`Operation "${operationId}" not found in specification.`);
|
27
27
|
}
|
28
|
+
function isOpenAPIV3(value) {
|
29
|
+
return typeof value == "object" && value !== null && "openapi" in value && value.openapi === "3.0.0";
|
30
|
+
}
|
31
|
+
function resolveOperationTokenOptions(document, operation) {
|
32
|
+
if (!isOpenAPIV3(document)) return {};
|
33
|
+
const security = operation.security ?? document.security;
|
34
|
+
if (!security) return {};
|
35
|
+
const securityScheme = document?.components?.securitySchemes;
|
36
|
+
if (!securityScheme) return {};
|
37
|
+
for (const requirement of security)
|
38
|
+
for (const key in requirement) {
|
39
|
+
const scheme = securityScheme[key];
|
40
|
+
if (!(typeof scheme != "object" || scheme === null) && scheme.type === "apiKey")
|
41
|
+
return {
|
42
|
+
tokenLocation: scheme.in,
|
43
|
+
tokenProperty: scheme.name
|
44
|
+
};
|
45
|
+
}
|
46
|
+
return {};
|
47
|
+
}
|
28
48
|
export {
|
49
|
+
resolveOperationTokenOptions as a,
|
29
50
|
getServerUrl as g,
|
51
|
+
isOpenAPIV3 as i,
|
30
52
|
resolveOperation as r
|
31
53
|
};
|
32
|
-
//# sourceMappingURL=
|
54
|
+
//# sourceMappingURL=B_Gz6Yz8.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"B_Gz6Yz8.js","sources":["../../openapi/getServerUrl.ts","../../openapi/resolveOperation.ts","../../openapi/isOpenAPIV3.ts","../../openapi/resolveOperationTokenOptions.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-member-access */\n/** Get the base URL of an OpenAPI specification. */\nexport type ServerUrl<T> =\n\n // --- Handle OpenAPI 2.0 specifications.\n T extends {\n host: infer Host extends string\n basePath?: infer BasePath extends string\n schemes?: Array<infer Scheme extends string>\n }\n ? `${Scheme}://${Host}${BasePath}`\n\n // --- Handle OpenAPI 3.0 specifications.\n : T extends { servers: Array<{ url: infer U extends string }> }\n ? U\n : string\n\n/**\n * Given an OpenAPI specification, get the first base URL.\n *\n * @param specification The OpenAPI specification.\n * @returns The first base URL.\n * @example getBaseUrl(specification) // 'https://api.example.com/v1'\n */\nexport function getServerUrl<T>(specification: T): ServerUrl<T> {\n\n // --- Ensure the specification is an object.\n if (\n !specification\n || typeof specification !== 'object'\n || specification === null)\n throw new Error('Invalid OpenAPI specification.')\n\n // --- Handle OpenAPI 3.0 specifications.\n if (\n 'servers' in specification\n && Array.isArray(specification.servers)\n && specification.servers.length > 0\n && 'url' in specification.servers[0]\n && typeof specification.servers[0].url === 'string'\n && specification.servers[0].url.length > 0)\n return specification.servers[0].url as ServerUrl<T>\n\n // --- Handle OpenAPI 2.0 specifications.\n if (\n 'host' in specification\n && typeof specification.host === 'string') {\n\n const scheme = (\n 'schemes' in specification\n && Array.isArray(specification.schemes)\n && specification.schemes.length > 0\n && typeof specification.schemes[0] === 'string')\n ? specification.schemes[0]\n : 'https'\n\n const basePath = (\n 'basePath' in specification\n && typeof specification.basePath === 'string'\n && specification.basePath.length > 0)\n ? specification.basePath\n : '/'\n\n return `${scheme}://${specification.host}${basePath}` as ServerUrl<T>\n }\n\n throw new Error('No base URL found in the OpenAPI specification.')\n}\n","import type { CollectKey, Pretty } from '@unshared/types'\nimport type { OpenAPI } from 'openapi-types'\nimport type { FetchMethod } from '../utils/parseRequest'\n\n/** The HTTP methods supported by OpenAPI. */\nconst methods = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch'] as const\n\n/** Union of all operation IDs in the specification. */\nexport type OperationId<T> =\nT extends { paths: infer P }\n ? P extends Record<string, infer R>\n ? R extends Record<string, infer O>\n ? O extends { operationId: infer N }\n ? N\n : string\n : string\n : string\n : string\n\n/** A union of possible Operations types in the specification. */\nexport type Operation = OpenAPI.Operation & { method: FetchMethod; path: string }\n\n/** Find an operation by its operationId in an OpenAPI specification. */\nexport type OperationById<T, U extends OperationId<T>> =\n T extends { paths: infer P }\n ? CollectKey<P> extends Record<string, infer R>\n ? CollectKey<R> extends Record<string, infer O>\n ? O extends { $key: [infer P extends string, infer M extends string]; operationId: U }\n ? Pretty<Omit<O, '$key'> & { method: M; path: P }>\n : never\n : never\n : never\n : never\n\n/**\n * Given an OpenAPI specification, find an operation by its operationId.\n *\n * @param document The OpenAPI specification document.\n * @param operationId The operationId of the operation to resolve.\n * @returns The resolved operation.\n * @example resolveOperation(document, 'getUser') // { method: 'get', path: '/users/{username}', ... }\n */\nexport function resolveOperation<T, U extends OperationId<T>>(document: T, operationId: U): OperationById<T, U>\nexport function resolveOperation(document: object, operationId: string): Operation\nexport function resolveOperation(document: object, operationId: string): Operation {\n\n // --- Validate the specification.\n if (!document\n || typeof document !== 'object'\n || document === null\n || 'paths' in document === false\n || typeof document.paths !== 'object'\n || document.paths === null)\n throw new Error('Missing paths object in the OpenAPI specification.')\n\n // --- Search for the operation in the specification's paths.\n const paths = document.paths as OpenAPI.Document['paths']\n for (const path in paths) {\n const route = paths[path]\n if (typeof route !== 'object' || route === null) continue\n\n // --- Search in each method for the operation.\n for (const method of methods) {\n const operation = route[method]\n if (method in route === false\n || typeof operation !== 'object'\n || operation === null\n || 'operationId' in operation === false\n || operation.operationId !== operationId) continue\n\n // --- Route was found, return the operation.\n return { ...route[method], method, path }\n }\n }\n\n // --- Throw an error if the operation was not found.\n throw new Error(`Operation \"${operationId}\" not found in specification.`)\n}\n","/* eslint-disable unicorn/filename-case */\nimport type { OpenAPIV3 } from 'openapi-types'\n\n/**\n * Check if the given document is an OpenAPI v3.0 specification.\n *\n * @param value The document to check.\n * @returns `true` if the document is an OpenAPI v3.0 specification, `false` otherwise.\n * @example isOpenAPIV3({ openapi: '3.0.0', info: { title: 'Test API', version: '1.0.0' } }) // => true\n */\nexport function isOpenAPIV3(value: unknown): value is OpenAPIV3.Document {\n return typeof value === 'object'\n && value !== null\n && 'openapi' in value\n && value.openapi === '3.0.0'\n}\n","import type { OpenAPI, OpenAPIV3 as V3 } from 'openapi-types'\nimport { isOpenAPIV3 } from './isOpenAPIV3'\n\nexport interface TokenOptions {\n tokenLocation?: 'cookie' | 'header' | 'query'\n tokenProperty?: string\n}\n\n/**\n * Resolve the location of the apiKey token based on the OpenAPI specification.\n *\n * @param document The OpenAPI specification document.\n * @param operation The OpenAPI operation object.\n * @returns The location of the apiKey token ('query' | 'cookie' | 'header').\n * @example resolveOperationTokenOptions(document, operation) // => { tokenLocation: 'header', tokenProperty: 'X-API-Key' }\n */\nexport function resolveOperationTokenOptions(document: object, operation: OpenAPI.Operation): TokenOptions {\n if (!isOpenAPIV3(document)) return {}\n\n // --- Find the security scheme in the OpenAPI specification.\n const security = operation.security ?? document.security\n if (!security) return {}\n const securityScheme = document?.components?.securitySchemes\n if (!securityScheme) return {}\n\n // --- Find the first security scheme that is an apiKey.\n for (const requirement of security) {\n for (const key in requirement) {\n const scheme = securityScheme[key] as V3.SecuritySchemeObject\n if (typeof scheme !== 'object' || scheme === null) continue\n if (scheme.type === 'apiKey') {\n return {\n tokenLocation: scheme.in as 'cookie' | 'header' | 'query',\n tokenProperty: scheme.name,\n }\n }\n }\n }\n\n // --- Return an empty object if no apiKey was found.\n return {}\n}\n"],"names":[],"mappings":"AAwBO,SAAS,aAAgB,eAAgC;AAG9D,MACE,CAAC,iBACE,OAAO,iBAAkB,YACzB,kBAAkB;AACf,UAAA,IAAI,MAAM,gCAAgC;AAGlD,MACE,aAAa,iBACV,MAAM,QAAQ,cAAc,OAAO,KACnC,cAAc,QAAQ,SAAS,KAC/B,SAAS,cAAc,QAAQ,CAAC,KAChC,OAAO,cAAc,QAAQ,CAAC,EAAE,OAAQ,YACxC,cAAc,QAAQ,CAAC,EAAE,IAAI,SAAS;AAClC,WAAA,cAAc,QAAQ,CAAC,EAAE;AAGlC,MACE,UAAU,iBACP,OAAO,cAAc,QAAS,UAAU;AAE3C,UAAM,SACJ,aAAa,iBACV,MAAM,QAAQ,cAAc,OAAO,KACnC,cAAc,QAAQ,SAAS,KAC/B,OAAO,cAAc,QAAQ,CAAC,KAAM,WACrC,cAAc,QAAQ,CAAC,IACvB,SAEE,WACJ,cAAc,iBACX,OAAO,cAAc,YAAa,YAClC,cAAc,SAAS,SAAS,IACjC,cAAc,WACd;AAEJ,WAAO,GAAG,MAAM,MAAM,cAAc,IAAI,GAAG,QAAQ;AAAA,EAAA;AAG/C,QAAA,IAAI,MAAM,iDAAiD;AACnE;AC9DA,MAAM,UAAU,CAAC,OAAO,OAAO,QAAQ,UAAU,WAAW,QAAQ,OAAO;AAuC3D,SAAA,iBAAiB,UAAkB,aAAgC;AAGjF,MAAI,CAAC,YACA,OAAO,YAAa,YACpB,aAAa,QACb,EAAW,WAAA,aACX,OAAO,SAAS,SAAU,YAC1B,SAAS,UAAU;AAChB,UAAA,IAAI,MAAM,oDAAoD;AAGtE,QAAM,QAAQ,SAAS;AACvB,aAAW,QAAQ,OAAO;AAClB,UAAA,QAAQ,MAAM,IAAI;AACpB,QAAA,EAAA,OAAO,SAAU,YAAY,UAAU;AAG3C,iBAAW,UAAU,SAAS;AACtB,cAAA,YAAY,MAAM,MAAM;AAC1B,YAAA,EAAA,EAAA,UAAU,UACT,OAAO,aAAc,YACrB,cAAc,QACd,EAAA,iBAAiB,cACjB,UAAU,gBAAgB;AAG/B,iBAAO,EAAE,GAAG,MAAM,MAAM,GAAG,QAAQ,KAAK;AAAA,MAAA;AAAA,EAC1C;AAIF,QAAM,IAAI,MAAM,cAAc,WAAW,+BAA+B;AAC1E;ACnEO,SAAS,YAAY,OAA6C;AAChE,SAAA,OAAO,SAAU,YACnB,UAAU,QACV,aAAa,SACb,MAAM,YAAY;AACzB;ACCgB,SAAA,6BAA6B,UAAkB,WAA4C;AACzG,MAAI,CAAC,YAAY,QAAQ,UAAU,CAAC;AAG9B,QAAA,WAAW,UAAU,YAAY,SAAS;AAC5C,MAAA,CAAC,SAAU,QAAO,CAAC;AACjB,QAAA,iBAAiB,UAAU,YAAY;AACzC,MAAA,CAAC,eAAgB,QAAO,CAAC;AAG7B,aAAW,eAAe;AACxB,eAAW,OAAO,aAAa;AACvB,YAAA,SAAS,eAAe,GAAG;AACjC,UAAI,SAAO,UAAW,YAAY,WAAW,SACzC,OAAO,SAAS;AACX,eAAA;AAAA,UACL,eAAe,OAAO;AAAA,UACtB,eAAe,OAAO;AAAA,QACxB;AAAA,IAAA;AAMN,SAAO,CAAC;AACV;"}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
"use strict";
|
2
|
-
var parseRequestQuery = require("./
|
2
|
+
var functions = require("@unshared/functions"), parseRequestQuery = require("./BDxlAULu.cjs");
|
3
3
|
const EXP_CONNECTION_CHANNEL = /^((?<protocol>[a-z]+) )?(?<url>[^:]+?:\/{2}[^/]+)?(?<path>\/[^\s?]*)/i, PROTOCOLS = /* @__PURE__ */ new Set(["ws", "wss"]);
|
4
4
|
function parseConnectUrl(parameters, channel, options) {
|
5
5
|
const { baseUrl, protocol } = options, match = EXP_CONNECTION_CHANNEL.exec(channel);
|
@@ -11,8 +11,8 @@ function parseConnectUrl(parameters, channel, options) {
|
|
11
11
|
parameters.url = new URL(routeBaseUrl), parameters.url.pathname += parameters.url.pathname.endsWith("/") ? match.groups.path.slice(1) : match.groups.path, parameters.protocol = protocolLower;
|
12
12
|
}
|
13
13
|
function parseConnectOptions(channel, options) {
|
14
|
-
const { baseUrl, protocol, data, parameters = data, query = data } = options,
|
15
|
-
return parseConnectUrl(
|
14
|
+
const { baseUrl, protocol, data, parameters = data, query = data } = options, wsParameters = { url: new URL("about:blank") };
|
15
|
+
return parseConnectUrl(wsParameters, channel, { baseUrl, protocol }), parseRequestQuery.parseRequestParameters(wsParameters, { parameters }), parseRequestQuery.parseRequestQuery(wsParameters, { query }), wsParameters;
|
16
16
|
}
|
17
17
|
class WebSocketChannel {
|
18
18
|
constructor(channel, options) {
|
@@ -24,15 +24,21 @@ class WebSocketChannel {
|
|
24
24
|
* Open a new WebSocket connection to the server. The connection will be opened with the given
|
25
25
|
* URL and protocols. If the connection is already open, the connection will be closed before
|
26
26
|
* opening a new connection. Also add the event listeners that were passed in the options.
|
27
|
+
*
|
28
|
+
* @returns The WebSocket connection.
|
27
29
|
*/
|
28
30
|
async open() {
|
29
31
|
this.webSocket && await this.close();
|
30
32
|
const { url, protocol } = parseConnectOptions(this.channel, this.options);
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
this.webSocket = new WebSocket(url, protocol);
|
34
|
+
const promise = new Promise((resolve, rejects) => {
|
35
|
+
this.webSocket.addEventListener("error", () => rejects(new Error("Failed to open the WebSocket connection")), { once: !0 }), this.webSocket.addEventListener("open", () => {
|
36
|
+
this.options.initialPayload && this.send(this.options.initialPayload), resolve();
|
37
|
+
}, { once: !0 });
|
35
38
|
});
|
39
|
+
return this.options.onOpen && this.on("open", this.options.onOpen, { once: !0 }), this.options.onClose && this.on("close", this.options.onClose, { once: !0 }), this.options.onError && this.on("error", this.options.onError), this.options.onMessage && this.on("message", (message) => this.options.onMessage(message)), this.webSocket.addEventListener("close", (event) => {
|
40
|
+
event.code !== 1e3 && this.options.autoReconnect && (this.options.reconnectLimit && event.wasClean || setTimeout(() => void this.open(), this.options.reconnectDelay ?? 0));
|
41
|
+
}, { once: !0 }), promise.then(() => this);
|
36
42
|
}
|
37
43
|
/**
|
38
44
|
* Send a payload to the server. The payload will be serialized to JSON before sending.
|
@@ -44,17 +50,20 @@ class WebSocketChannel {
|
|
44
50
|
const json = JSON.stringify(payload);
|
45
51
|
this.webSocket.send(json);
|
46
52
|
}
|
47
|
-
on(event, callback) {
|
53
|
+
on(event, callback, options) {
|
48
54
|
if (!this.webSocket) throw new Error("WebSocket connection has not been opened yet");
|
49
|
-
const listener = (event2) => {
|
55
|
+
const listener = async (event2) => {
|
56
|
+
if (event2.type !== "message") return callback(event2);
|
50
57
|
let data = event2.data;
|
58
|
+
data instanceof Blob && (data = await data.text());
|
51
59
|
try {
|
52
60
|
data = JSON.parse(data);
|
53
61
|
} catch {
|
62
|
+
console.error("Failed to parse the message:", data);
|
54
63
|
}
|
55
64
|
callback(data);
|
56
65
|
};
|
57
|
-
return this.webSocket.addEventListener(event, listener), () => this.webSocket.removeEventListener(event, listener);
|
66
|
+
return this.webSocket.addEventListener(event, listener, options), () => this.webSocket.removeEventListener(event, listener);
|
58
67
|
}
|
59
68
|
/**
|
60
69
|
* Close the WebSocket connection to the server. The connection will not be able to send or receive
|
@@ -66,9 +75,10 @@ class WebSocketChannel {
|
|
66
75
|
}
|
67
76
|
}
|
68
77
|
function connect(route, options) {
|
69
|
-
|
78
|
+
const channel = new WebSocketChannel(route, options);
|
79
|
+
return functions.awaitable(channel, () => channel.open());
|
70
80
|
}
|
71
81
|
exports.WebSocketChannel = WebSocketChannel;
|
72
82
|
exports.connect = connect;
|
73
83
|
exports.parseConnectOptions = parseConnectOptions;
|
74
|
-
//# sourceMappingURL=
|
84
|
+
//# sourceMappingURL=BdFNzMcu.cjs.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"BdFNzMcu.cjs","sources":["../../websocket/parseConnectOptions.ts","../../websocket/connect.ts"],"sourcesContent":["import type { Loose, ObjectLike, UnionMerge } from '@unshared/types'\nimport { parseRequestParameters } from '../utils/parseRequestParameters'\nimport { parseRequestQuery } from '../utils/parseRequestQuery'\n\n/** Regular expression to match the request method and URL. */\nconst EXP_CONNECTION_CHANNEL = /^((?<protocol>[a-z]+) )?(?<url>[^:]+?:\\/{2}[^/]+)?(?<path>\\/[^\\s?]*)/i\n\n/** Valid WebSocket protocols. */\nconst PROTOCOLS = new Set(['ws', 'wss'])\n\n/** The protocols to use for the connection. */\nexport type ConnectProtocol = 'WS' | 'WSS'\n\n/** Options to pass to the `createChannel` function. */\nexport interface ConnectOptions<\n BaseUrl extends string = string,\n Query extends ObjectLike = ObjectLike,\n Parameters extends ObjectLike = ObjectLike,\n ClientData extends ObjectLike = any,\n ServerData extends ObjectLike = any,\n> {\n\n /** The protocol to use when connecting to the server. */\n protocol?: Lowercase<ConnectProtocol> | Uppercase<ConnectProtocol>\n\n /** The base URL to connect to. */\n baseUrl?: BaseUrl\n\n /**\n * The path parameters to use when connecting to the server. These parameters will be used to\n * fill in the path parameters of the connection URL.\n *\n * @example { id: 1 }\n */\n parameters?: Parameters\n\n /**\n * The query parameters to use when connecting to the server. These parameters will be used to\n * fill in the query parameters of the connection URL.\n *\n * @example { limit: 10, offset: 0 }\n */\n query?: Loose<Query>\n\n /**\n * The data to send when creating the connection. Namely, the path parameters\n * to use when connecting to the server.\n *\n * @example\n *\n * // Create a new connection to `http://localhost:8080/users/1`.\n * connect('GET /users/:id', {\n * data: { id: 1 },\n * baseUrl: 'http://localhost:8080'\n * })\n */\n data?: UnionMerge<Loose<Query> | Parameters>\n\n /**\n * The payload to send when creating the connection. Namely, the initial message\n * to send to the server when the connection is established.\n */\n initialPayload?: Loose<ClientData>\n\n /**\n * Weather to reconnect the connection when it is closed unexpectedly. If `true`,\n * the connection will automatically reconnect when it is closed. If `false`, the\n * connection will not reconnect when it is closed.\n *\n * @default false\n */\n autoReconnect?: boolean\n\n /**\n * The delay in milliseconds to wait before reconnecting the connection. This delay\n * will be used to wait before reconnecting the connection after it is closed.\n *\n * @default 0\n */\n reconnectDelay?: number\n\n /**\n * The maximum number of times to reconnect the connection before giving up. This\n * number will be used to determine when to stop trying to reconnect the connection.\n *\n * @default 3\n */\n reconnectLimit?: number\n\n /**\n * The function to call when the connection is opened. This function will be called\n * when the connection is successfully opened or reconnected.\n */\n onOpen?: (event: Event) => void\n\n /**\n * The function to call when the connection is closed with an error. This function will\n * be called when the connection is closed unexpectedly with an error.\n */\n onError?: (event: Event) => void\n\n /**\n * The function to call when the connection is closed. This function will be called\n * when the connection is closed unexpectedly or when the connection is closed manually.\n */\n onClose?: (event: CloseEvent) => void\n\n /**\n * The function to call when a message is received from the server. This function will\n * be called when a message is received from the server.\n */\n onMessage?: (data: ServerData) => void\n}\n\nexport interface WebSocketParameters {\n url: URL\n protocol?: 'ws' | 'wss'\n}\n\nfunction parseConnectUrl(parameters: WebSocketParameters, channel: string, options: ConnectOptions): void {\n const { baseUrl, protocol } = options\n\n // --- Extract the path, method, and base URL from the route name.\n const match = EXP_CONNECTION_CHANNEL.exec(channel)\n if (!match?.groups) throw new Error('Could not resolve the `RequestInit` object: Invalid route name.')\n const routeProtocol = protocol ?? match.groups.protocol ?? 'ws'\n const routeBaseUrl = baseUrl ?? match.groups.url\n\n // --- Assert the base URL is provided, either in the options or the route name.\n if (!routeBaseUrl) throw new Error('Could not resolve the `RequestInit` object: the `baseUrl` is missing.')\n\n // --- Assert the method is valid.\n const protocolLower = routeProtocol.toLowerCase()\n const protocolIsValid = PROTOCOLS.has(protocolLower)\n if (!protocolIsValid) throw new Error(`Could not resolve the \\`RequestInit\\` object:, the method \\`${routeProtocol}\\` is invalid.`)\n\n // --- Create the url and apply the method.\n parameters.url = new URL(routeBaseUrl)\n parameters.url.pathname += parameters.url.pathname.endsWith('/') ? match.groups.path.slice(1) : match.groups.path\n parameters.protocol = protocolLower as 'ws' | 'wss'\n}\n\nexport function parseConnectOptions(channel: string, options: ConnectOptions): WebSocketParameters {\n const { baseUrl, protocol, data, parameters = data, query = data } = options\n const wsParameters: WebSocketParameters = { url: new URL('about:blank') }\n parseConnectUrl(wsParameters, channel, { baseUrl, protocol })\n parseRequestParameters(wsParameters, { parameters })\n parseRequestQuery(wsParameters, { query })\n return wsParameters\n}\n","import type { Awaitable } from '@unshared/functions'\nimport type { ConnectOptions } from './parseConnectOptions'\nimport { awaitable } from '@unshared/functions'\nimport { parseConnectOptions } from './parseConnectOptions'\n\ntype RemoveListener = () => void\n\ntype ClientData<T extends ConnectOptions> =\n T extends ConnectOptions<any, any, any, infer R, any> ? R : any\n\ntype ServerData<T extends ConnectOptions> =\n T extends ConnectOptions<any, any, any, any, infer R> ? R : any\n\nexport class WebSocketChannel<T extends ConnectOptions = ConnectOptions> {\n constructor(public channel: string, public options: T) {}\n\n /** The WebSocket connection to the server. */\n public webSocket: undefined | WebSocket\n\n /**\n * Open a new WebSocket connection to the server. The connection will be opened with the given\n * URL and protocols. If the connection is already open, the connection will be closed before\n * opening a new connection. Also add the event listeners that were passed in the options.\n *\n * @returns The WebSocket connection.\n */\n async open(): Promise<this> {\n if (this.webSocket) await this.close()\n const { url, protocol } = parseConnectOptions(this.channel, this.options)\n this.webSocket = new WebSocket(url, protocol)\n\n // --- Return a promise that resolves when the connection is opened.\n const promise = new Promise<void>((resolve, rejects) => {\n this.webSocket!.addEventListener('error', () => rejects(new Error('Failed to open the WebSocket connection')), { once: true })\n this.webSocket!.addEventListener('open', () => {\n if (this.options.initialPayload) this.send(this.options.initialPayload as ClientData<T>)\n resolve()\n }, { once: true })\n })\n\n // --- Add the options' hooks to the WebSocket connection.\n if (this.options.onOpen) this.on('open', this.options.onOpen, { once: true })\n if (this.options.onClose) this.on('close', this.options.onClose, { once: true })\n if (this.options.onError) this.on('error', this.options.onError)\n if (this.options.onMessage) this.on('message', message => this.options.onMessage!(message))\n\n // --- Handle reconnection when the connection is closed unexpectedly.\n this.webSocket.addEventListener('close', (event) => {\n if (event.code === 1000) return\n if (!this.options.autoReconnect) return\n if (this.options.reconnectLimit && event.wasClean) return\n setTimeout(() => void this.open(), this.options.reconnectDelay ?? 0)\n }, { once: true })\n\n return promise.then(() => this)\n }\n\n /**\n * Send a payload to the server. The payload will be serialized to JSON before sending.\n *\n * @param payload The data to send to the server.\n */\n send(payload: ClientData<T>) {\n if (!this.webSocket) throw new Error('WebSocket connection is not open')\n const json = JSON.stringify(payload)\n this.webSocket.send(json)\n }\n\n /**\n * Listen for events from the server. The event will be deserialized from JSON before calling the callback.\n *\n * @param event The event to listen for.\n * @param callback The callback to call when the event is received.\n * @returns A function to remove the event listener.\n */\n on(event: 'message', callback: (data: ServerData<T>) => void, options?: AddEventListenerOptions): RemoveListener\n on(event: 'close', callback: (event: CloseEvent) => void, options?: AddEventListenerOptions): RemoveListener\n on(event: 'error', callback: (event: Event) => void, options?: AddEventListenerOptions): RemoveListener\n on(event: 'open', callback: (event: Event) => void, options?: AddEventListenerOptions): RemoveListener\n on(event: string, callback: (data: any) => void, options?: AddEventListenerOptions) {\n if (!this.webSocket) throw new Error('WebSocket connection has not been opened yet')\n\n const listener = async(event: CloseEvent | Event | MessageEvent<Blob>): Promise<void> => {\n if (event.type !== 'message') return callback(event)\n // @ts-expect-error: `data` exists on the event.\n let data = event.data as unknown\n if (data instanceof Blob) data = await data.text()\n try { data = JSON.parse(data as string) }\n catch { console.error('Failed to parse the message:', data) }\n callback(data)\n }\n\n /* eslint-disable @typescript-eslint/no-misused-promises */\n this.webSocket.addEventListener(event, listener, options)\n return () => this.webSocket!.removeEventListener(event, listener)\n /* eslint-enable @typescript-eslint/no-misused-promises */\n }\n\n /**\n * Close the WebSocket connection to the server. The connection will not be able to send or receive\n * messages after it is closed.\n */\n async close() {\n if (!this.webSocket) throw new Error('WebSocket connection has not been opened yet')\n if (this.webSocket.readyState === WebSocket.CLOSED) return\n if (this.webSocket.readyState === WebSocket.CLOSING) return\n this.webSocket.close(1000, 'Client closed the connection')\n await new Promise<void>(resolve => this.webSocket!.addEventListener('close', () => resolve()))\n }\n}\n\n/**\n * Create a new WebSocket connection to the server with the given path. The connection will\n * automatically reconnect if the connection is closed unexpectedly.\n *\n * @param route The name of the route to connect to.\n * @param options The options to pass to the connection.\n * @returns The WebSocket connection.\n */\nexport function connect(route: string, options: ConnectOptions): Awaitable<WebSocketChannel, WebSocketChannel> {\n const channel = new WebSocketChannel(route, options)\n return awaitable(channel, () => channel.open())\n}\n"],"names":["parseRequestParameters","parseRequestQuery","event","awaitable"],"mappings":"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"}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Loose, UnionMerge, Override, Pretty, CollectKey, MaybeLiteral, StringSplit } from '@unshared/types';
|
2
|
-
import { F as FetchMethod, R as RequestOptions } from './
|
2
|
+
import { F as FetchMethod, R as RequestOptions } from './BZ5qrH6f.js';
|
3
3
|
import { OpenAPI } from 'openapi-types';
|
4
4
|
|
5
5
|
/** Get the base URL of an OpenAPI specification. */
|
@@ -40,10 +40,11 @@ declare namespace OpenAPIV2 {
|
|
40
40
|
properties: infer P extends Record<string, any>;
|
41
41
|
} ? {
|
42
42
|
[K in keyof P]?: InferSchema<P[K]>;
|
43
|
-
} :
|
43
|
+
} : T extends {
|
44
|
+
additionalProperties: infer U extends Record<string, any>;
|
45
|
+
} ? Record<string, InferSchema<U>> : Record<string, unknown>;
|
44
46
|
type InferSchemaArray<T> = T extends {
|
45
47
|
items?: infer U;
|
46
|
-
additionalItems?: infer U;
|
47
48
|
} ? Array<InferSchema<U>> : unknown[];
|
48
49
|
type InferSchema<T> = Loose<(T extends {
|
49
50
|
anyOf: Array<infer U>;
|
@@ -166,20 +167,20 @@ type OperationId<T> = T extends {
|
|
166
167
|
operationId: infer N;
|
167
168
|
} ? N : string : string : string : string;
|
168
169
|
/** A union of possible Operations types in the specification. */
|
169
|
-
type Operation = {
|
170
|
+
type Operation = OpenAPI.Operation & {
|
170
171
|
method: FetchMethod;
|
171
172
|
path: string;
|
172
|
-
}
|
173
|
+
};
|
173
174
|
/** Find an operation by its operationId in an OpenAPI specification. */
|
174
175
|
type OperationById<T, U extends OperationId<T>> = T extends {
|
175
176
|
paths: infer P;
|
176
177
|
} ? CollectKey<P> extends Record<string, infer R> ? CollectKey<R> extends Record<string, infer O> ? O extends {
|
177
178
|
$key: [infer P extends string, infer M extends string];
|
178
179
|
operationId: U;
|
179
|
-
} ? Pretty<{
|
180
|
+
} ? Pretty<Omit<O, '$key'> & {
|
180
181
|
method: M;
|
181
182
|
path: P;
|
182
|
-
}
|
183
|
+
}> : never : never : never : never;
|
183
184
|
/**
|
184
185
|
* Given an OpenAPI specification, find an operation by its operationId.
|
185
186
|
*
|
@@ -189,6 +190,7 @@ type OperationById<T, U extends OperationId<T>> = T extends {
|
|
189
190
|
* @example resolveOperation(document, 'getUser') // { method: 'get', path: '/users/{username}', ... }
|
190
191
|
*/
|
191
192
|
declare function resolveOperation<T, U extends OperationId<T>>(document: T, operationId: U): OperationById<T, U>;
|
193
|
+
declare function resolveOperation(document: object, operationId: string): Operation;
|
192
194
|
|
193
195
|
interface OpenAPIV2Like {
|
194
196
|
swagger: string;
|
@@ -212,10 +214,10 @@ type OperationRoute<T> = T extends {
|
|
212
214
|
/** Find an operation by its route name in an OpenAPI specification. */
|
213
215
|
type OperationByRoute<T, U extends OperationRoute<T>> = StringSplit<U, ' '> extends [infer M extends string, infer P extends string] ? T extends {
|
214
216
|
paths: infer U;
|
215
|
-
} ? U extends Record<P, infer R> ? R extends Record<Lowercase<M>, infer O> ? Pretty<{
|
217
|
+
} ? U extends Record<P, infer R> ? R extends Record<Lowercase<M>, infer O> ? Pretty<O & {
|
216
218
|
method: Lowercase<M>;
|
217
219
|
path: P;
|
218
|
-
}
|
220
|
+
}> : never : never : never : never;
|
219
221
|
/** The `ClientRoutes` inferred from the OpenAPI specification. */
|
220
222
|
type OpenAPIOptionsMap<T> = {
|
221
223
|
[K in OperationRoute<T>]: OperationOptions<T, OperationByRoute<T, K>>;
|
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import { awaitable } from "@unshared/functions";
|
2
|
+
import { p as parseRequestParameters, a as parseRequestQuery } from "./B6pUErTM.js";
|
2
3
|
const EXP_CONNECTION_CHANNEL = /^((?<protocol>[a-z]+) )?(?<url>[^:]+?:\/{2}[^/]+)?(?<path>\/[^\s?]*)/i, PROTOCOLS = /* @__PURE__ */ new Set(["ws", "wss"]);
|
3
4
|
function parseConnectUrl(parameters, channel, options) {
|
4
5
|
const { baseUrl, protocol } = options, match = EXP_CONNECTION_CHANNEL.exec(channel);
|
@@ -10,8 +11,8 @@ function parseConnectUrl(parameters, channel, options) {
|
|
10
11
|
parameters.url = new URL(routeBaseUrl), parameters.url.pathname += parameters.url.pathname.endsWith("/") ? match.groups.path.slice(1) : match.groups.path, parameters.protocol = protocolLower;
|
11
12
|
}
|
12
13
|
function parseConnectOptions(channel, options) {
|
13
|
-
const { baseUrl, protocol, data, parameters = data, query = data } = options,
|
14
|
-
return parseConnectUrl(
|
14
|
+
const { baseUrl, protocol, data, parameters = data, query = data } = options, wsParameters = { url: new URL("about:blank") };
|
15
|
+
return parseConnectUrl(wsParameters, channel, { baseUrl, protocol }), parseRequestParameters(wsParameters, { parameters }), parseRequestQuery(wsParameters, { query }), wsParameters;
|
15
16
|
}
|
16
17
|
class WebSocketChannel {
|
17
18
|
constructor(channel, options) {
|
@@ -23,15 +24,21 @@ class WebSocketChannel {
|
|
23
24
|
* Open a new WebSocket connection to the server. The connection will be opened with the given
|
24
25
|
* URL and protocols. If the connection is already open, the connection will be closed before
|
25
26
|
* opening a new connection. Also add the event listeners that were passed in the options.
|
27
|
+
*
|
28
|
+
* @returns The WebSocket connection.
|
26
29
|
*/
|
27
30
|
async open() {
|
28
31
|
this.webSocket && await this.close();
|
29
32
|
const { url, protocol } = parseConnectOptions(this.channel, this.options);
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
this.webSocket = new WebSocket(url, protocol);
|
34
|
+
const promise = new Promise((resolve, rejects) => {
|
35
|
+
this.webSocket.addEventListener("error", () => rejects(new Error("Failed to open the WebSocket connection")), { once: !0 }), this.webSocket.addEventListener("open", () => {
|
36
|
+
this.options.initialPayload && this.send(this.options.initialPayload), resolve();
|
37
|
+
}, { once: !0 });
|
34
38
|
});
|
39
|
+
return this.options.onOpen && this.on("open", this.options.onOpen, { once: !0 }), this.options.onClose && this.on("close", this.options.onClose, { once: !0 }), this.options.onError && this.on("error", this.options.onError), this.options.onMessage && this.on("message", (message) => this.options.onMessage(message)), this.webSocket.addEventListener("close", (event) => {
|
40
|
+
event.code !== 1e3 && this.options.autoReconnect && (this.options.reconnectLimit && event.wasClean || setTimeout(() => void this.open(), this.options.reconnectDelay ?? 0));
|
41
|
+
}, { once: !0 }), promise.then(() => this);
|
35
42
|
}
|
36
43
|
/**
|
37
44
|
* Send a payload to the server. The payload will be serialized to JSON before sending.
|
@@ -43,17 +50,20 @@ class WebSocketChannel {
|
|
43
50
|
const json = JSON.stringify(payload);
|
44
51
|
this.webSocket.send(json);
|
45
52
|
}
|
46
|
-
on(event, callback) {
|
53
|
+
on(event, callback, options) {
|
47
54
|
if (!this.webSocket) throw new Error("WebSocket connection has not been opened yet");
|
48
|
-
const listener = (event2) => {
|
55
|
+
const listener = async (event2) => {
|
56
|
+
if (event2.type !== "message") return callback(event2);
|
49
57
|
let data = event2.data;
|
58
|
+
data instanceof Blob && (data = await data.text());
|
50
59
|
try {
|
51
60
|
data = JSON.parse(data);
|
52
61
|
} catch {
|
62
|
+
console.error("Failed to parse the message:", data);
|
53
63
|
}
|
54
64
|
callback(data);
|
55
65
|
};
|
56
|
-
return this.webSocket.addEventListener(event, listener), () => this.webSocket.removeEventListener(event, listener);
|
66
|
+
return this.webSocket.addEventListener(event, listener, options), () => this.webSocket.removeEventListener(event, listener);
|
57
67
|
}
|
58
68
|
/**
|
59
69
|
* Close the WebSocket connection to the server. The connection will not be able to send or receive
|
@@ -65,11 +75,12 @@ class WebSocketChannel {
|
|
65
75
|
}
|
66
76
|
}
|
67
77
|
function connect(route, options) {
|
68
|
-
|
78
|
+
const channel = new WebSocketChannel(route, options);
|
79
|
+
return awaitable(channel, () => channel.open());
|
69
80
|
}
|
70
81
|
export {
|
71
82
|
WebSocketChannel as W,
|
72
83
|
connect as c,
|
73
84
|
parseConnectOptions as p
|
74
85
|
};
|
75
|
-
//# sourceMappingURL=
|
86
|
+
//# sourceMappingURL=Bys4-xE2.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"Bys4-xE2.js","sources":["../../websocket/parseConnectOptions.ts","../../websocket/connect.ts"],"sourcesContent":["import type { Loose, ObjectLike, UnionMerge } from '@unshared/types'\nimport { parseRequestParameters } from '../utils/parseRequestParameters'\nimport { parseRequestQuery } from '../utils/parseRequestQuery'\n\n/** Regular expression to match the request method and URL. */\nconst EXP_CONNECTION_CHANNEL = /^((?<protocol>[a-z]+) )?(?<url>[^:]+?:\\/{2}[^/]+)?(?<path>\\/[^\\s?]*)/i\n\n/** Valid WebSocket protocols. */\nconst PROTOCOLS = new Set(['ws', 'wss'])\n\n/** The protocols to use for the connection. */\nexport type ConnectProtocol = 'WS' | 'WSS'\n\n/** Options to pass to the `createChannel` function. */\nexport interface ConnectOptions<\n BaseUrl extends string = string,\n Query extends ObjectLike = ObjectLike,\n Parameters extends ObjectLike = ObjectLike,\n ClientData extends ObjectLike = any,\n ServerData extends ObjectLike = any,\n> {\n\n /** The protocol to use when connecting to the server. */\n protocol?: Lowercase<ConnectProtocol> | Uppercase<ConnectProtocol>\n\n /** The base URL to connect to. */\n baseUrl?: BaseUrl\n\n /**\n * The path parameters to use when connecting to the server. These parameters will be used to\n * fill in the path parameters of the connection URL.\n *\n * @example { id: 1 }\n */\n parameters?: Parameters\n\n /**\n * The query parameters to use when connecting to the server. These parameters will be used to\n * fill in the query parameters of the connection URL.\n *\n * @example { limit: 10, offset: 0 }\n */\n query?: Loose<Query>\n\n /**\n * The data to send when creating the connection. Namely, the path parameters\n * to use when connecting to the server.\n *\n * @example\n *\n * // Create a new connection to `http://localhost:8080/users/1`.\n * connect('GET /users/:id', {\n * data: { id: 1 },\n * baseUrl: 'http://localhost:8080'\n * })\n */\n data?: UnionMerge<Loose<Query> | Parameters>\n\n /**\n * The payload to send when creating the connection. Namely, the initial message\n * to send to the server when the connection is established.\n */\n initialPayload?: Loose<ClientData>\n\n /**\n * Weather to reconnect the connection when it is closed unexpectedly. If `true`,\n * the connection will automatically reconnect when it is closed. If `false`, the\n * connection will not reconnect when it is closed.\n *\n * @default false\n */\n autoReconnect?: boolean\n\n /**\n * The delay in milliseconds to wait before reconnecting the connection. This delay\n * will be used to wait before reconnecting the connection after it is closed.\n *\n * @default 0\n */\n reconnectDelay?: number\n\n /**\n * The maximum number of times to reconnect the connection before giving up. This\n * number will be used to determine when to stop trying to reconnect the connection.\n *\n * @default 3\n */\n reconnectLimit?: number\n\n /**\n * The function to call when the connection is opened. This function will be called\n * when the connection is successfully opened or reconnected.\n */\n onOpen?: (event: Event) => void\n\n /**\n * The function to call when the connection is closed with an error. This function will\n * be called when the connection is closed unexpectedly with an error.\n */\n onError?: (event: Event) => void\n\n /**\n * The function to call when the connection is closed. This function will be called\n * when the connection is closed unexpectedly or when the connection is closed manually.\n */\n onClose?: (event: CloseEvent) => void\n\n /**\n * The function to call when a message is received from the server. This function will\n * be called when a message is received from the server.\n */\n onMessage?: (data: ServerData) => void\n}\n\nexport interface WebSocketParameters {\n url: URL\n protocol?: 'ws' | 'wss'\n}\n\nfunction parseConnectUrl(parameters: WebSocketParameters, channel: string, options: ConnectOptions): void {\n const { baseUrl, protocol } = options\n\n // --- Extract the path, method, and base URL from the route name.\n const match = EXP_CONNECTION_CHANNEL.exec(channel)\n if (!match?.groups) throw new Error('Could not resolve the `RequestInit` object: Invalid route name.')\n const routeProtocol = protocol ?? match.groups.protocol ?? 'ws'\n const routeBaseUrl = baseUrl ?? match.groups.url\n\n // --- Assert the base URL is provided, either in the options or the route name.\n if (!routeBaseUrl) throw new Error('Could not resolve the `RequestInit` object: the `baseUrl` is missing.')\n\n // --- Assert the method is valid.\n const protocolLower = routeProtocol.toLowerCase()\n const protocolIsValid = PROTOCOLS.has(protocolLower)\n if (!protocolIsValid) throw new Error(`Could not resolve the \\`RequestInit\\` object:, the method \\`${routeProtocol}\\` is invalid.`)\n\n // --- Create the url and apply the method.\n parameters.url = new URL(routeBaseUrl)\n parameters.url.pathname += parameters.url.pathname.endsWith('/') ? match.groups.path.slice(1) : match.groups.path\n parameters.protocol = protocolLower as 'ws' | 'wss'\n}\n\nexport function parseConnectOptions(channel: string, options: ConnectOptions): WebSocketParameters {\n const { baseUrl, protocol, data, parameters = data, query = data } = options\n const wsParameters: WebSocketParameters = { url: new URL('about:blank') }\n parseConnectUrl(wsParameters, channel, { baseUrl, protocol })\n parseRequestParameters(wsParameters, { parameters })\n parseRequestQuery(wsParameters, { query })\n return wsParameters\n}\n","import type { Awaitable } from '@unshared/functions'\nimport type { ConnectOptions } from './parseConnectOptions'\nimport { awaitable } from '@unshared/functions'\nimport { parseConnectOptions } from './parseConnectOptions'\n\ntype RemoveListener = () => void\n\ntype ClientData<T extends ConnectOptions> =\n T extends ConnectOptions<any, any, any, infer R, any> ? R : any\n\ntype ServerData<T extends ConnectOptions> =\n T extends ConnectOptions<any, any, any, any, infer R> ? R : any\n\nexport class WebSocketChannel<T extends ConnectOptions = ConnectOptions> {\n constructor(public channel: string, public options: T) {}\n\n /** The WebSocket connection to the server. */\n public webSocket: undefined | WebSocket\n\n /**\n * Open a new WebSocket connection to the server. The connection will be opened with the given\n * URL and protocols. If the connection is already open, the connection will be closed before\n * opening a new connection. Also add the event listeners that were passed in the options.\n *\n * @returns The WebSocket connection.\n */\n async open(): Promise<this> {\n if (this.webSocket) await this.close()\n const { url, protocol } = parseConnectOptions(this.channel, this.options)\n this.webSocket = new WebSocket(url, protocol)\n\n // --- Return a promise that resolves when the connection is opened.\n const promise = new Promise<void>((resolve, rejects) => {\n this.webSocket!.addEventListener('error', () => rejects(new Error('Failed to open the WebSocket connection')), { once: true })\n this.webSocket!.addEventListener('open', () => {\n if (this.options.initialPayload) this.send(this.options.initialPayload as ClientData<T>)\n resolve()\n }, { once: true })\n })\n\n // --- Add the options' hooks to the WebSocket connection.\n if (this.options.onOpen) this.on('open', this.options.onOpen, { once: true })\n if (this.options.onClose) this.on('close', this.options.onClose, { once: true })\n if (this.options.onError) this.on('error', this.options.onError)\n if (this.options.onMessage) this.on('message', message => this.options.onMessage!(message))\n\n // --- Handle reconnection when the connection is closed unexpectedly.\n this.webSocket.addEventListener('close', (event) => {\n if (event.code === 1000) return\n if (!this.options.autoReconnect) return\n if (this.options.reconnectLimit && event.wasClean) return\n setTimeout(() => void this.open(), this.options.reconnectDelay ?? 0)\n }, { once: true })\n\n return promise.then(() => this)\n }\n\n /**\n * Send a payload to the server. The payload will be serialized to JSON before sending.\n *\n * @param payload The data to send to the server.\n */\n send(payload: ClientData<T>) {\n if (!this.webSocket) throw new Error('WebSocket connection is not open')\n const json = JSON.stringify(payload)\n this.webSocket.send(json)\n }\n\n /**\n * Listen for events from the server. The event will be deserialized from JSON before calling the callback.\n *\n * @param event The event to listen for.\n * @param callback The callback to call when the event is received.\n * @returns A function to remove the event listener.\n */\n on(event: 'message', callback: (data: ServerData<T>) => void, options?: AddEventListenerOptions): RemoveListener\n on(event: 'close', callback: (event: CloseEvent) => void, options?: AddEventListenerOptions): RemoveListener\n on(event: 'error', callback: (event: Event) => void, options?: AddEventListenerOptions): RemoveListener\n on(event: 'open', callback: (event: Event) => void, options?: AddEventListenerOptions): RemoveListener\n on(event: string, callback: (data: any) => void, options?: AddEventListenerOptions) {\n if (!this.webSocket) throw new Error('WebSocket connection has not been opened yet')\n\n const listener = async(event: CloseEvent | Event | MessageEvent<Blob>): Promise<void> => {\n if (event.type !== 'message') return callback(event)\n // @ts-expect-error: `data` exists on the event.\n let data = event.data as unknown\n if (data instanceof Blob) data = await data.text()\n try { data = JSON.parse(data as string) }\n catch { console.error('Failed to parse the message:', data) }\n callback(data)\n }\n\n /* eslint-disable @typescript-eslint/no-misused-promises */\n this.webSocket.addEventListener(event, listener, options)\n return () => this.webSocket!.removeEventListener(event, listener)\n /* eslint-enable @typescript-eslint/no-misused-promises */\n }\n\n /**\n * Close the WebSocket connection to the server. The connection will not be able to send or receive\n * messages after it is closed.\n */\n async close() {\n if (!this.webSocket) throw new Error('WebSocket connection has not been opened yet')\n if (this.webSocket.readyState === WebSocket.CLOSED) return\n if (this.webSocket.readyState === WebSocket.CLOSING) return\n this.webSocket.close(1000, 'Client closed the connection')\n await new Promise<void>(resolve => this.webSocket!.addEventListener('close', () => resolve()))\n }\n}\n\n/**\n * Create a new WebSocket connection to the server with the given path. The connection will\n * automatically reconnect if the connection is closed unexpectedly.\n *\n * @param route The name of the route to connect to.\n * @param options The options to pass to the connection.\n * @returns The WebSocket connection.\n */\nexport function connect(route: string, options: ConnectOptions): Awaitable<WebSocketChannel, WebSocketChannel> {\n const channel = new WebSocketChannel(route, options)\n return awaitable(channel, () => channel.open())\n}\n"],"names":["event"],"mappings":"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"}
|