@toktokhan-dev/cli-plugin-gen-api-react-query 0.0.1

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.
Files changed (38) hide show
  1. package/dist/index.d.ts +62 -0
  2. package/dist/index.js +196 -0
  3. package/package.json +40 -0
  4. package/templates/custom-axios/api.eta +176 -0
  5. package/templates/custom-axios/data-contracts.eta +76 -0
  6. package/templates/custom-axios/http-client.eta +144 -0
  7. package/templates/custom-axios/procedure-call.eta +133 -0
  8. package/templates/custom-fetch/api.eta +164 -0
  9. package/templates/custom-fetch/data-contracts.eta +76 -0
  10. package/templates/custom-fetch/http-client.eta +224 -0
  11. package/templates/custom-fetch/procedure-call.eta +151 -0
  12. package/templates/default/README.md +17 -0
  13. package/templates/default/base/README.md +8 -0
  14. package/templates/default/base/data-contract-jsdoc.ejs +37 -0
  15. package/templates/default/base/data-contracts.ejs +28 -0
  16. package/templates/default/base/enum-data-contract.ejs +12 -0
  17. package/templates/default/base/http-client.ejs +3 -0
  18. package/templates/default/base/http-clients/axios-http-client.ejs +138 -0
  19. package/templates/default/base/http-clients/fetch-http-client.ejs +224 -0
  20. package/templates/default/base/interface-data-contract.ejs +10 -0
  21. package/templates/default/base/object-field-jsdoc.ejs +28 -0
  22. package/templates/default/base/route-docs.ejs +30 -0
  23. package/templates/default/base/route-name.ejs +43 -0
  24. package/templates/default/base/route-type.ejs +22 -0
  25. package/templates/default/base/type-data-contract.ejs +15 -0
  26. package/templates/default/default/README.md +7 -0
  27. package/templates/default/default/api.ejs +65 -0
  28. package/templates/default/default/procedure-call.ejs +100 -0
  29. package/templates/default/default/route-types.ejs +28 -0
  30. package/templates/default/modular/README.md +7 -0
  31. package/templates/default/modular/api.ejs +28 -0
  32. package/templates/default/modular/procedure-call.ejs +100 -0
  33. package/templates/default/modular/route-types.ejs +18 -0
  34. package/templates/my/param-serializer-by.eta +41 -0
  35. package/templates/my/react-query-hook.eta +171 -0
  36. package/templates/my/react-query-key.eta +46 -0
  37. package/templates/my/react-query-type.eta +61 -0
  38. package/templates/my/util-types.eta +32 -0
@@ -0,0 +1,30 @@
1
+ <%
2
+ const { config, route, utils } = it;
3
+ const { _, formatDescription, fmtToJSDocLine, pascalCase, require } = utils;
4
+ const { raw, request, routeName } = route;
5
+
6
+ const jsDocDescription = raw.description ?
7
+ ` * @description ${formatDescription(raw.description, true)}` :
8
+ fmtToJSDocLine('No description', { eol: false });
9
+ const jsDocLines = _.compact([
10
+ _.size(raw.tags) && ` * @tags ${raw.tags.join(", ")}`,
11
+ ` * @name ${pascalCase(routeName.usage)}`,
12
+ raw.summary && ` * @summary ${raw.summary}`,
13
+ ` * @request ${_.upperCase(request.method)}:${raw.route}`,
14
+ raw.deprecated && ` * @deprecated`,
15
+ routeName.duplicate && ` * @originalName ${routeName.original}`,
16
+ routeName.duplicate && ` * @duplicate`,
17
+ request.security && ` * @secure`,
18
+ ...(config.generateResponses && raw.responsesTypes.length
19
+ ? raw.responsesTypes.map(
20
+ ({ type, status, description, isSuccess }) =>
21
+ ` * @response \`${status}\` \`${_.replace(_.replace(type, /\/\*/g, "\\*"), /\*\//g, "*\\")}\` ${description}`,
22
+ )
23
+ : []),
24
+ ]).map(str => str.trimEnd()).join("\n");
25
+
26
+ return {
27
+ description: jsDocDescription,
28
+ lines: jsDocLines,
29
+ }
30
+ %>
@@ -0,0 +1,43 @@
1
+ <%
2
+ const { routeInfo, utils } = it;
3
+ const {
4
+ operationId,
5
+ method,
6
+ route,
7
+ moduleName,
8
+ responsesTypes,
9
+ description,
10
+ tags,
11
+ summary,
12
+ pathArgs,
13
+ } = routeInfo;
14
+ const { _, fmtToJSDocLine, require } = utils;
15
+
16
+ const methodAliases = {
17
+ get: (pathName, hasPathInserts) =>
18
+ _.camelCase(`${pathName}_${hasPathInserts ? "detail" : "list"}`),
19
+ post: (pathName, hasPathInserts) => _.camelCase(`${pathName}_create`),
20
+ put: (pathName, hasPathInserts) => _.camelCase(`${pathName}_update`),
21
+ patch: (pathName, hasPathInserts) => _.camelCase(`${pathName}_partial_update`),
22
+ delete: (pathName, hasPathInserts) => _.camelCase(`${pathName}_delete`),
23
+ };
24
+
25
+ const createCustomOperationId = (method, route, moduleName) => {
26
+ const hasPathInserts = /\{(\w){1,}\}/g.test(route);
27
+ const splitedRouteBySlash = _.compact(_.replace(route, /\{(\w){1,}\}/g, "").split("/"));
28
+ const routeParts = (splitedRouteBySlash.length > 1
29
+ ? splitedRouteBySlash.splice(1)
30
+ : splitedRouteBySlash
31
+ ).join("_");
32
+ return routeParts.length > 3 && methodAliases[method]
33
+ ? methodAliases[method](routeParts, hasPathInserts)
34
+ : _.camelCase(_.lowerCase(method) + "_" + [moduleName].join("_")) || "index";
35
+ };
36
+
37
+ if (operationId)
38
+ return _.camelCase(operationId);
39
+ if (route === "/")
40
+ return _.camelCase(`${_.lowerCase(method)}Root`);
41
+
42
+ return createCustomOperationId(method, route, moduleName);
43
+ %>
@@ -0,0 +1,22 @@
1
+ <%
2
+ const { route, utils, config } = it;
3
+ const { _, pascalCase, require } = utils;
4
+ const { query, payload, pathParams, headers } = route.request;
5
+
6
+ const routeDocs = includeFile("@base/route-docs", { config, route, utils });
7
+ const routeNamespace = pascalCase(route.routeName.usage);
8
+
9
+ %>
10
+ /**
11
+ <%~ routeDocs.description %>
12
+
13
+ <%~ routeDocs.lines %>
14
+
15
+ */
16
+ export namespace <%~ routeNamespace %> {
17
+ export type RequestParams = <%~ (pathParams && pathParams.type) || '{}' %>;
18
+ export type RequestQuery = <%~ (query && query.type) || '{}' %>;
19
+ export type RequestBody = <%~ (payload && payload.type) || 'never' %>;
20
+ export type RequestHeaders = <%~ (headers && headers.type) || '{}' %>;
21
+ export type ResponseBody = <%~ route.response.type %>;
22
+ }
@@ -0,0 +1,15 @@
1
+ <%
2
+ const { contract, utils } = it;
3
+ const { formatDescription, require, _ } = utils;
4
+
5
+ %>
6
+ <% if (contract.$content.length) { %>
7
+ export type <%~ contract.name %> = {
8
+ <% _.forEach(contract.$content, (field) => { %>
9
+ <%~ includeFile('@base/object-field-jsdoc.ejs', { ...it, field }) %>
10
+ <%~ field.field %>;
11
+ <% }) %>
12
+ }<%~ utils.isNeedToAddNull(contract) ? ' | null' : ''%>
13
+ <% } else { %>
14
+ export type <%~ contract.name %> = Record<string, any>;
15
+ <% } %>
@@ -0,0 +1,7 @@
1
+ # swagger-typescript-api
2
+
3
+ # templates/default
4
+
5
+ This templates use for single api file (without `--modular` option)
6
+
7
+ path prefix `@default`
@@ -0,0 +1,65 @@
1
+ <%
2
+ const { apiConfig, routes, utils, config } = it;
3
+ const { info, servers, externalDocs } = apiConfig;
4
+ const { _, require, formatDescription } = utils;
5
+
6
+ const server = (servers && servers[0]) || { url: "" };
7
+
8
+ const descriptionLines = _.compact([
9
+ `@title ${info.title || "No title"}`,
10
+ info.version && `@version ${info.version}`,
11
+ info.license && `@license ${_.compact([
12
+ info.license.name,
13
+ info.license.url && `(${info.license.url})`,
14
+ ]).join(" ")}`,
15
+ info.termsOfService && `@termsOfService ${info.termsOfService}`,
16
+ server.url && `@baseUrl ${server.url}`,
17
+ externalDocs.url && `@externalDocs ${externalDocs.url}`,
18
+ info.contact && `@contact ${_.compact([
19
+ info.contact.name,
20
+ info.contact.email && `<${info.contact.email}>`,
21
+ info.contact.url && `(${info.contact.url})`,
22
+ ]).join(" ")}`,
23
+ info.description && " ",
24
+ info.description && _.replace(formatDescription(info.description), /\n/g, "\n * "),
25
+ ]);
26
+
27
+ %>
28
+
29
+ <% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %>
30
+
31
+ <% if (descriptionLines.length) { %>
32
+ /**
33
+ <% descriptionLines.forEach((descriptionLine) => { %>
34
+ * <%~ descriptionLine %>
35
+
36
+ <% }) %>
37
+ */
38
+ <% } %>
39
+ export class <%~ config.apiClassName %><SecurityDataType extends unknown><% if (!config.singleHttpClient) { %> extends HttpClient<SecurityDataType> <% } %> {
40
+
41
+ <% if(config.singleHttpClient) { %>
42
+ http: HttpClient<SecurityDataType>;
43
+
44
+ constructor (http: HttpClient<SecurityDataType>) {
45
+ this.http = http;
46
+ }
47
+ <% } %>
48
+
49
+
50
+ <% routes.outOfModule && routes.outOfModule.forEach((route) => { %>
51
+
52
+ <%~ includeFile('./procedure-call.ejs', { ...it, route }) %>
53
+
54
+ <% }) %>
55
+
56
+ <% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %>
57
+ <%~ moduleName %> = {
58
+ <% routes.forEach((route) => { %>
59
+
60
+ <%~ includeFile('./procedure-call.ejs', { ...it, route }) %>
61
+
62
+ <% }) %>
63
+ }
64
+ <% }) %>
65
+ }
@@ -0,0 +1,100 @@
1
+ <%
2
+ const { utils, route, config } = it;
3
+ const { requestBodyInfo, responseBodyInfo, specificArgNameResolver } = route;
4
+ const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRef, require } = utils;
5
+ const { parameters, path, method, payload, query, formData, security, requestParams } = route.request;
6
+ const { type, errorType, contentTypes } = route.response;
7
+ const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants;
8
+ const routeDocs = includeFile("@base/route-docs", { config, route, utils });
9
+ const queryName = (query && query.name) || "query";
10
+ const pathParams = _.values(parameters);
11
+ const pathParamsNames = _.map(pathParams, "name");
12
+
13
+ const isFetchTemplate = config.httpClientType === HTTP_CLIENT.FETCH;
14
+
15
+ const requestConfigParam = {
16
+ name: specificArgNameResolver.resolve(RESERVED_REQ_PARAMS_ARG_NAMES),
17
+ optional: true,
18
+ type: "RequestParams",
19
+ defaultValue: "{}",
20
+ }
21
+
22
+ const argToTmpl = ({ name, optional, type, defaultValue }) => `${name}${!defaultValue && optional ? '?' : ''}: ${type}${defaultValue ? ` = ${defaultValue}` : ''}`;
23
+
24
+ const rawWrapperArgs = config.extractRequestParams ?
25
+ _.compact([
26
+ requestParams && {
27
+ name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName,
28
+ optional: false,
29
+ type: getInlineParseContent(requestParams),
30
+ },
31
+ ...(!requestParams ? pathParams : []),
32
+ payload,
33
+ requestConfigParam,
34
+ ]) :
35
+ _.compact([
36
+ ...pathParams,
37
+ query,
38
+ payload,
39
+ requestConfigParam,
40
+ ])
41
+
42
+ const wrapperArgs = _
43
+ // Sort by optionality
44
+ .sortBy(rawWrapperArgs, [o => o.optional])
45
+ .map(argToTmpl)
46
+ .join(', ')
47
+
48
+ // RequestParams["type"]
49
+ const requestContentKind = {
50
+ "JSON": "ContentType.Json",
51
+ "URL_ENCODED": "ContentType.UrlEncoded",
52
+ "FORM_DATA": "ContentType.FormData",
53
+ "TEXT": "ContentType.Text",
54
+ }
55
+ // RequestParams["format"]
56
+ const responseContentKind = {
57
+ "JSON": '"json"',
58
+ "IMAGE": '"blob"',
59
+ "FORM_DATA": isFetchTemplate ? '"formData"' : '"document"'
60
+ }
61
+
62
+ const bodyTmpl = _.get(payload, "name") || null;
63
+ const queryTmpl = (query != null && queryName) || null;
64
+ const bodyContentKindTmpl = requestContentKind[requestBodyInfo.contentKind] || null;
65
+ const responseFormatTmpl = responseContentKind[responseBodyInfo.success && responseBodyInfo.success.schema && responseBodyInfo.success.schema.contentKind] || null;
66
+ const securityTmpl = security ? 'true' : null;
67
+
68
+ const describeReturnType = () => {
69
+ if (!config.toJS) return "";
70
+
71
+ switch(config.httpClientType) {
72
+ case HTTP_CLIENT.AXIOS: {
73
+ return `Promise<AxiosResponse<${type}>>`
74
+ }
75
+ default: {
76
+ return `Promise<HttpResponse<${type}, ${errorType}>`
77
+ }
78
+ }
79
+ }
80
+
81
+ %>
82
+ /**
83
+ <%~ routeDocs.description %>
84
+
85
+ *<% /* Here you can add some other JSDoc tags */ %>
86
+
87
+ <%~ routeDocs.lines %>
88
+
89
+ */
90
+ <%~ route.routeName.usage %><%~ route.namespace ? ': ' : ' = ' %>(<%~ wrapperArgs %>)<%~ config.toJS ? `: ${describeReturnType()}` : "" %> =>
91
+ <%~ config.singleHttpClient ? 'this.http.request' : 'this.request' %><<%~ type %>, <%~ errorType %>>({
92
+ path: `<%~ path %>`,
93
+ method: '<%~ _.upperCase(method) %>',
94
+ <%~ queryTmpl ? `query: ${queryTmpl},` : '' %>
95
+ <%~ bodyTmpl ? `body: ${bodyTmpl},` : '' %>
96
+ <%~ securityTmpl ? `secure: ${securityTmpl},` : '' %>
97
+ <%~ bodyContentKindTmpl ? `type: ${bodyContentKindTmpl},` : '' %>
98
+ <%~ responseFormatTmpl ? `format: ${responseFormatTmpl},` : '' %>
99
+ ...<%~ _.get(requestConfigParam, "name") %>,
100
+ })<%~ route.namespace ? ',' : '' %>
@@ -0,0 +1,28 @@
1
+ <%
2
+ const { utils, config, routes, modelTypes } = it;
3
+ const { _, pascalCase } = utils;
4
+ const dataContracts = config.modular ? _.map(modelTypes, "name") : [];
5
+ %>
6
+
7
+ <% if (dataContracts.length) { %>
8
+ import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
9
+ <% } %>
10
+
11
+ <%
12
+ /* TODO: outOfModule, combined should be attributes of route, which will allow to avoid duplication of code */
13
+ %>
14
+
15
+ <% routes.outOfModule && routes.outOfModule.forEach(({ routes = [] }) => { %>
16
+ <% routes.forEach((route) => { %>
17
+ <%~ includeFile('@base/route-type.ejs', { ...it, route }) %>
18
+ <% }) %>
19
+ <% }) %>
20
+
21
+ <% routes.combined && routes.combined.forEach(({ routes = [], moduleName }) => { %>
22
+ export namespace <%~ pascalCase(moduleName) %> {
23
+ <% routes.forEach((route) => { %>
24
+ <%~ includeFile('@base/route-type.ejs', { ...it, route }) %>
25
+ <% }) %>
26
+ }
27
+
28
+ <% }) %>
@@ -0,0 +1,7 @@
1
+ # swagger-typescript-api
2
+
3
+ # templates/modular
4
+
5
+ This templates use for multiple api files (`--modular` option)
6
+
7
+ path prefix `@modular`
@@ -0,0 +1,28 @@
1
+ <%
2
+ const { utils, route, config, modelTypes } = it;
3
+ const { _, pascalCase, require } = utils;
4
+ const apiClassName = pascalCase(route.moduleName);
5
+ const routes = route.routes;
6
+ const dataContracts = _.map(modelTypes, "name");
7
+ %>
8
+
9
+ <% if (config.httpClientType === config.constants.HTTP_CLIENT.AXIOS) { %> import { AxiosRequestConfig, AxiosResponse } from "axios"; <% } %>
10
+
11
+ import { HttpClient, RequestParams, ContentType, HttpResponse } from "./<%~ config.fileNames.httpClient %>";
12
+ <% if (dataContracts.length) { %>
13
+ import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
14
+ <% } %>
15
+
16
+ export class <%= apiClassName %><SecurityDataType = unknown><% if (!config.singleHttpClient) { %> extends HttpClient<SecurityDataType> <% } %> {
17
+ <% if(config.singleHttpClient) { %>
18
+ http: HttpClient<SecurityDataType>;
19
+
20
+ constructor (http: HttpClient<SecurityDataType>) {
21
+ this.http = http;
22
+ }
23
+ <% } %>
24
+
25
+ <% routes.forEach((route) => { %>
26
+ <%~ includeFile('./procedure-call.ejs', { ...it, route }) %>
27
+ <% }) %>
28
+ }
@@ -0,0 +1,100 @@
1
+ <%
2
+ const { utils, route, config } = it;
3
+ const { requestBodyInfo, responseBodyInfo, specificArgNameResolver } = route;
4
+ const { _, getInlineParseContent, getParseContent, parseSchema, getComponentByRef, require } = utils;
5
+ const { parameters, path, method, payload, query, formData, security, requestParams } = route.request;
6
+ const { type, errorType, contentTypes } = route.response;
7
+ const { HTTP_CLIENT, RESERVED_REQ_PARAMS_ARG_NAMES } = config.constants;
8
+ const routeDocs = includeFile("@base/route-docs", { config, route, utils });
9
+ const queryName = (query && query.name) || "query";
10
+ const pathParams = _.values(parameters);
11
+ const pathParamsNames = _.map(pathParams, "name");
12
+
13
+ const isFetchTemplate = config.httpClientType === HTTP_CLIENT.FETCH;
14
+
15
+ const requestConfigParam = {
16
+ name: specificArgNameResolver.resolve(RESERVED_REQ_PARAMS_ARG_NAMES),
17
+ optional: true,
18
+ type: "RequestParams",
19
+ defaultValue: "{}",
20
+ }
21
+
22
+ const argToTmpl = ({ name, optional, type, defaultValue }) => `${name}${!defaultValue && optional ? '?' : ''}: ${type}${defaultValue ? ` = ${defaultValue}` : ''}`;
23
+
24
+ const rawWrapperArgs = config.extractRequestParams ?
25
+ _.compact([
26
+ requestParams && {
27
+ name: pathParams.length ? `{ ${_.join(pathParamsNames, ", ")}, ...${queryName} }` : queryName,
28
+ optional: false,
29
+ type: getInlineParseContent(requestParams),
30
+ },
31
+ ...(!requestParams ? pathParams : []),
32
+ payload,
33
+ requestConfigParam,
34
+ ]) :
35
+ _.compact([
36
+ ...pathParams,
37
+ query,
38
+ payload,
39
+ requestConfigParam,
40
+ ])
41
+
42
+ const wrapperArgs = _
43
+ // Sort by optionality
44
+ .sortBy(rawWrapperArgs, [o => o.optional])
45
+ .map(argToTmpl)
46
+ .join(', ')
47
+
48
+ // RequestParams["type"]
49
+ const requestContentKind = {
50
+ "JSON": "ContentType.Json",
51
+ "URL_ENCODED": "ContentType.UrlEncoded",
52
+ "FORM_DATA": "ContentType.FormData",
53
+ "TEXT": "ContentType.Text",
54
+ }
55
+ // RequestParams["format"]
56
+ const responseContentKind = {
57
+ "JSON": '"json"',
58
+ "IMAGE": '"blob"',
59
+ "FORM_DATA": isFetchTemplate ? '"formData"' : '"document"'
60
+ }
61
+
62
+ const bodyTmpl = _.get(payload, "name") || null;
63
+ const queryTmpl = (query != null && queryName) || null;
64
+ const bodyContentKindTmpl = requestContentKind[requestBodyInfo.contentKind] || null;
65
+ const responseFormatTmpl = responseContentKind[responseBodyInfo.success && responseBodyInfo.success.schema && responseBodyInfo.success.schema.contentKind] || null;
66
+ const securityTmpl = security ? 'true' : null;
67
+
68
+ const describeReturnType = () => {
69
+ if (!config.toJS) return "";
70
+
71
+ switch(config.httpClientType) {
72
+ case HTTP_CLIENT.AXIOS: {
73
+ return `Promise<AxiosResponse<${type}>>`
74
+ }
75
+ default: {
76
+ return `Promise<HttpResponse<${type}, ${errorType}>`
77
+ }
78
+ }
79
+ }
80
+
81
+ %>
82
+ /**
83
+ <%~ routeDocs.description %>
84
+
85
+ *<% /* Here you can add some other JSDoc tags */ %>
86
+
87
+ <%~ routeDocs.lines %>
88
+
89
+ */
90
+ <%~ route.routeName.usage %> = (<%~ wrapperArgs %>)<%~ config.toJS ? `: ${describeReturnType()}` : "" %> =>
91
+ <%~ config.singleHttpClient ? 'this.http.request' : 'this.request' %><<%~ type %>, <%~ errorType %>>({
92
+ path: `<%~ path %>`,
93
+ method: '<%~ _.upperCase(method) %>',
94
+ <%~ queryTmpl ? `query: ${queryTmpl},` : '' %>
95
+ <%~ bodyTmpl ? `body: ${bodyTmpl},` : '' %>
96
+ <%~ securityTmpl ? `secure: ${securityTmpl},` : '' %>
97
+ <%~ bodyContentKindTmpl ? `type: ${bodyContentKindTmpl},` : '' %>
98
+ <%~ responseFormatTmpl ? `format: ${responseFormatTmpl},` : '' %>
99
+ ...<%~ _.get(requestConfigParam, "name") %>,
100
+ })
@@ -0,0 +1,18 @@
1
+ <%
2
+ const { utils, config, route, modelTypes } = it;
3
+ const { _, pascalCase } = utils;
4
+ const { routes, moduleName } = route;
5
+ const dataContracts = config.modular ? _.map(modelTypes, "name") : [];
6
+
7
+ %>
8
+ <% if (dataContracts.length) { %>
9
+ import { <%~ dataContracts.join(", ") %> } from "./<%~ config.fileNames.dataContracts %>"
10
+ <% } %>
11
+
12
+ export namespace <%~ pascalCase(moduleName) %> {
13
+ <% _.forEach(routes, (route) => { %>
14
+
15
+ <%~ includeFile('@base/route-type.ejs', { ...it, route }) %>
16
+
17
+ <% }) %>
18
+ }
@@ -0,0 +1,41 @@
1
+ export const paramsSerializerBy =
2
+ (
3
+ format?: Partial<
4
+ Record<
5
+ (string & {}) | '_default',
6
+ 'brackets' | 'repeat' | 'indices' | 'comma'
7
+ >
8
+ >,
9
+ options?: {
10
+ decoded?: boolean;
11
+ },
12
+ ) =>
13
+ (params: Record<string, any>) => {
14
+ const serialized = Object.entries(params)
15
+ .reduce<string[]>((acc, [key, value]) => {
16
+ const isArray = Array.isArray(value);
17
+ if (!isArray) {
18
+ return acc.concat(`${key}=${params[key]}`);
19
+ }
20
+ const asRepeat = (v: string) => `${key}=${v}`;
21
+ const asBracket = (v: string) => `${key}[]=${v}`;
22
+ const asIndices = (v: string, idx: number) => `${key}[${idx}]=${v}`;
23
+
24
+ switch (format?.[key] || format?._default) {
25
+ case 'repeat':
26
+ return acc.concat(value.map(asRepeat).join('&'));
27
+ case 'brackets':
28
+ return acc.concat(value.map(asBracket).join('&'));
29
+ case 'indices':
30
+ return acc.concat(value.map(asIndices).join('&'));
31
+ default: // comma
32
+ return acc.concat(`${key}=${value.join(',')}`);
33
+ }
34
+ }, [])
35
+ .join('&');
36
+
37
+ if (options?.decoded === true) {
38
+ return decodeURIComponent(serialized);
39
+ }
40
+ return serialized;
41
+ };