swaggie 2.0.0 → 2.1.0-alpha.4
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/README.md +52 -2
- package/dist/cli.js +19 -1
- package/dist/gen/genMocks.js +48 -19
- package/dist/gen/genOperations.js +226 -5
- package/dist/gen/index.js +61 -3
- package/dist/generated/bundledTemplates.js +23 -10
- package/dist/index.js +28 -0
- package/dist/types.d.ts +32 -2
- package/dist/utils/fileUtils.js +20 -0
- package/dist/utils/templateValidator.js +29 -1
- package/package.json +1 -1
- package/templates/axios/barrel.ejs +1 -1
- package/templates/axios/baseClientSetup.ejs +46 -0
- package/templates/fetch/barrel.ejs +1 -1
- package/templates/fetch/baseClientSetup.ejs +38 -0
- package/templates/ky/barrel.ejs +57 -0
- package/templates/ky/baseClient.ejs +5 -0
- package/templates/ky/baseClientSetup.ejs +60 -0
- package/templates/ky/baseClientWithSetup.ejs +34 -0
- package/templates/ky/client.ejs +6 -0
- package/templates/ky/operation.ejs +50 -0
- package/templates/ng2/barrel.ejs +1 -1
- package/templates/swr/client.ejs +9 -17
- package/templates/swr/hooksClient.ejs +62 -0
- package/templates/swr/swrMutationOperation.ejs +2 -1
- package/templates/swr/swrOperation.ejs +2 -1
- package/templates/tsq/client.ejs +9 -20
- package/templates/tsq/hooksClient.ejs +63 -0
- package/templates/tsq/mutationOperation.ejs +2 -1
- package/templates/tsq/queryOperation.ejs +2 -1
- package/templates/xior/barrel.ejs +1 -0
- package/templates/xior/baseClientSetup.ejs +50 -0
|
@@ -3,17 +3,27 @@
|
|
|
3
3
|
|
|
4
4
|
const BUNDLED_TEMPLATES = {
|
|
5
5
|
"axios": {
|
|
6
|
-
"barrel.ejs": "\n/**\n * Serializes a params object into a query string that is compatible with different REST APIs.\n * Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts\n * Kudos to @suhaotian for the original implementation\n */\
|
|
6
|
+
"barrel.ejs": "\n/**\n * Serializes a params object into a query string that is compatible with different REST APIs.\n * Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts\n * Kudos to @suhaotian for the original implementation\n */\nexport function encodeParams<T = any>(\n params: T,\n parentKey: string | null = null,\n options?: {\n allowDots?: boolean;\n serializeDate?: (value: Date) => string;\n arrayFormat?: 'indices' | 'repeat' | 'brackets';\n }\n): string {\n if (params === undefined || params === null) return '';\n const encodedParams: string[] = [];\n const paramsIsArray = Array.isArray(params);\n const { arrayFormat, allowDots, serializeDate } = options || {};\n\n const getKey = (key: string) => {\n if (allowDots && !paramsIsArray) return `.${key}`;\n if (paramsIsArray) {\n if (arrayFormat === 'brackets') {\n return '[]';\n }\n if (arrayFormat === 'repeat') {\n return '';\n }\n }\n return `[${key}]`;\n };\n\n for (const key in params) {\n if (Object.prototype.hasOwnProperty.call(params, key)) {\n let value = (params as any)[key];\n if (value !== undefined) {\n const encodedKey = parentKey ? `${parentKey}${getKey(key)}` : (key as string);\n\n // biome-ignore lint/suspicious/noGlobalIsNan: <explanation>\n if (!isNaN(value) && value instanceof Date) {\n value = serializeDate ? serializeDate(value) : value.toISOString();\n }\n if (typeof value === 'object') {\n // If the value is an object or array, recursively encode its contents\n const result = encodeParams(value, encodedKey, options);\n if (result !== '') encodedParams.push(result);\n } else {\n // Otherwise, encode the key-value pair\n encodedParams.push(`${encodeURIComponent(encodedKey)}=${encodeURIComponent(value)}`);\n }\n }\n }\n }\n\n return encodedParams.join('&');\n}\n\n",
|
|
7
7
|
"baseClient.ejs": "import Axios, { type AxiosPromise, type AxiosRequestConfig } from \"axios\";\n\nexport const axios = Axios.create({\n baseURL: '<%= it.baseUrl || '' %>',\n paramsSerializer: (params: any) =>\n encodeParams(params, null, {\n allowDots: <%= it.allowDots %>,\n arrayFormat: '<%= it.arrayFormat %>',\n }),\n});\n\n",
|
|
8
|
+
"baseClientSetup.ejs": "/**\n * Axios client setup — GENERATED ONCE, will NOT be overwritten on subsequent runs.\n * Re-generate intentionally with: swaggie ... --clientSetup <path> --forceSetup\n *\n * This file is a write-once scaffold for configuring the Axios instance exported\n * from the generated API client. It is NOT imported by api.ts — call\n * `setupApiClient()` once at your application startup.\n *\n * Usage in your app:\n * import { setupApiClient } from '<%= it.relativeSetupImport %>';\n * setupApiClient(); // call once before any API requests\n *\n * The setup function can accept any parameters your app needs (e.g. an auth ref):\n * export function setupApiClient(authRef: AuthRef): () => void { ... }\n */\nimport { axios } from '<%= it.relativeApiImport %>';\n\n/**\n * Configures the shared Axios instance with request/response interceptors.\n * Returns a cleanup function that ejects the interceptors — useful for\n * React effects or other lifecycle-managed contexts.\n */\nexport function setupApiClient(): () => void {\n const requestInterceptor = axios.interceptors.request.use(\n (config) => {\n // TODO: Add request interceptors, e.g. attach an Authorization header:\n // config.headers.Authorization = `Bearer ${getToken()}`;\n return config;\n },\n (error) => Promise.reject(error)\n );\n\n const responseInterceptor = axios.interceptors.response.use(\n (response) => response,\n (error) => {\n // TODO: Add response error handling, e.g. redirect on 401:\n // if (error.response?.status === 401) { loginWithRedirect(); }\n return Promise.reject(error);\n }\n );\n\n return () => {\n axios.interceptors.request.eject(requestInterceptor);\n axios.interceptors.response.eject(responseInterceptor);\n };\n}\n",
|
|
8
9
|
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n};\n\n",
|
|
9
10
|
"operation.ejs": "<%~ it.jsDocs %>\n\n <%= it.name %>(<% it.parameters.forEach((parameter) => { %>\n<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,\n <% }); %>\n$config?: AxiosRequestConfig\n ): AxiosPromise<<%~ it.returnType %>> {\n const url = `<%= it.url %>`;\n\n return axios.request<<%~ it.returnType %>>({\n url: url,\n method: '<%= it.method %>',\n<% if(it.body) { -%>\n<% if(it.body.contentType === 'urlencoded') { -%>\n data: new URLSearchParams(<%= it.body.name %> as any),\n<% } else { -%>\n data: <%= it.body.name %>,\n<% } -%>\n<% } -%>\n<% if(it.query && it.query.length > 0) { -%>\n params: {\n<% it.query.forEach((parameter) => { -%>\n<% if (it.queryParamObject) { -%>\n '<%= parameter.originalName %>': <%= it.queryParamObject.name %>?.<%= parameter.name %>,\n<% } else { -%>\n '<%= parameter.originalName %>': <%= parameter.name %>,\n<% } -%>\n<% }); -%>\n },\n<% } -%>\n<% if(it.headers && it.headers.length > 0) { -%>\n headers: {\n<% it.headers.forEach((parameter) => { -%>\n<% if (parameter.value) { -%>\n '<%= parameter.originalName %>': '<%= parameter.value %>',\n<% } else { -%>\n '<%= parameter.originalName %>': <%= parameter.name %>,\n<% } -%>\n<% }); -%>\n },\n<% } -%>\n ...$config,\n });\n },\n"
|
|
10
11
|
},
|
|
11
12
|
"fetch": {
|
|
12
|
-
"barrel.ejs": "\n/**\n * Serializes a params object into a query string that is compatible with different REST APIs.\n * Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts\n * Kudos to @suhaotian for the original implementation\n */\
|
|
13
|
+
"barrel.ejs": "\n/**\n * Serializes a params object into a query string that is compatible with different REST APIs.\n * Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts\n * Kudos to @suhaotian for the original implementation\n */\nexport function encodeParams<T = any>(\n params: T,\n parentKey: string | null = null,\n options?: {\n allowDots?: boolean;\n serializeDate?: (value: Date) => string;\n arrayFormat?: 'indices' | 'repeat' | 'brackets';\n }\n): string {\n if (params === undefined || params === null) return '';\n const encodedParams: string[] = [];\n const paramsIsArray = Array.isArray(params);\n const { arrayFormat, allowDots, serializeDate } = options || {};\n\n const getKey = (key: string) => {\n if (allowDots && !paramsIsArray) return `.${key}`;\n if (paramsIsArray) {\n if (arrayFormat === 'brackets') {\n return '[]';\n }\n if (arrayFormat === 'repeat') {\n return '';\n }\n }\n return `[${key}]`;\n };\n\n for (const key in params) {\n if (Object.prototype.hasOwnProperty.call(params, key)) {\n let value = (params as any)[key];\n if (value !== undefined) {\n const encodedKey = parentKey ? `${parentKey}${getKey(key)}` : (key as string);\n\n // biome-ignore lint/suspicious/noGlobalIsNan: <explanation>\n if (!isNaN(value) && value instanceof Date) {\n value = serializeDate ? serializeDate(value) : value.toISOString();\n }\n if (typeof value === 'object') {\n // If the value is an object or array, recursively encode its contents\n const result = encodeParams(value, encodedKey, options);\n if (result !== '') encodedParams.push(result);\n } else {\n // Otherwise, encode the key-value pair\n encodedParams.push(`${encodeURIComponent(encodedKey)}=${encodeURIComponent(value)}`);\n }\n }\n }\n }\n\n return encodedParams.join('&');\n}\n\n",
|
|
13
14
|
"baseClient.ejs": "export const defaults = {\n baseUrl: '<%= it.baseUrl || '' %>',\n paramsSerializer: (params: any) =>\n encodeParams(params, null, {\n allowDots: <%= it.allowDots %>,\n arrayFormat: '<%= it.arrayFormat %>',\n }),\n};\n\n",
|
|
15
|
+
"baseClientSetup.ejs": "/**\n * Fetch client setup — GENERATED ONCE, will NOT be overwritten on subsequent runs.\n * Re-generate intentionally with: swaggie ... --clientSetup <path> --forceSetup\n *\n * This file is a write-once scaffold for configuring the fetch-based client\n * exported from the generated API client. It is NOT imported by api.ts — call\n * `setupApiClient()` once at your application startup.\n *\n * Usage in your app:\n * import { setupApiClient } from '<%= it.relativeSetupImport %>';\n * setupApiClient(); // call once before any API requests\n *\n * The setup function can accept any parameters your app needs (e.g. an auth ref):\n * export function setupApiClient(authRef: AuthRef): void { ... }\n */\nimport { defaults } from '<%= it.relativeApiImport %>';\n\n/**\n * Configures the shared fetch client defaults.\n * Override `defaults.fetch` to wrap the native fetch with custom behaviour\n * (auth headers, error handling, logging, etc.).\n */\nexport function setupApiClient(): void {\n const originalFetch = defaults.fetch ?? globalThis.fetch;\n\n defaults.fetch = async (input, init) => {\n // TODO: Add pre-request logic, e.g. attach an Authorization header:\n // const token = await getToken();\n // init = { ...init, headers: { ...init?.headers, Authorization: `Bearer ${token}` } };\n\n const response = await originalFetch(input, init);\n\n // TODO: Add post-response logic, e.g. handle 401 Unauthorized:\n // if (response.status === 401) { loginWithRedirect(); }\n\n return response;\n };\n}\n",
|
|
14
16
|
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n};\n",
|
|
15
17
|
"operation.ejs": "<%~ it.jsDocs %>\n\n <%= it.name %>(<% it.parameters.forEach((parameter) => { %>\n<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,\n <% }); %>\n$config?: RequestInit\n ): Promise<<%~ it.returnType %>> {\n const url = `${defaults.baseUrl}<%= it.url %>?<%\n if(it.query && it.query.length > 0) { %>${defaults.paramsSerializer({<%\n it.query.forEach((parameter) => { %>\n<% if (it.queryParamObject) { %>\n'<%= parameter.originalName %>': <%= it.queryParamObject.name %>?.<%= parameter.name %>,\n<% } else { %>\n'<%= parameter.originalName %>': <%= parameter.name %>,\n<% } %>\n <% }); %>})}<% } %>`;\n\n<% if(it.headers && it.headers.length > 0) { %>\n const { headers: $configHeaders, ...$configRest } = $config ?? {};\n const headers = new Headers({\n<% it.headers.forEach((parameter) => { %>\n<% if (parameter.value) { %>\n '<%= parameter.originalName %>': '<%= parameter.value %>',\n<% } else { %>\n '<%= parameter.originalName %>': <%= parameter.name %> ?? '',\n<% } %>\n<% }); %>\n });\n if ($configHeaders) {\n new Headers($configHeaders).forEach((value, key) => headers.set(key, value));\n }\n\n return fetch(url, {\n method: '<%= it.method %>',\n<% if(it.body) { %>\n<% if(it.body.contentType === 'json') { %>\n body: JSON.stringify(<%= it.body.name %>),\n<% } else if(it.body.contentType === 'urlencoded') { %>\n body: new URLSearchParams(<%= it.body.name %> as any),\n<% } else { %>\n body: <%= it.body.name %>,\n<% } %>\n<% } %>\n headers,\n ...$configRest,\n })\n<% } else { %>\n return fetch(url, {\n method: '<%= it.method %>',\n<% if(it.body) { %>\n<% if(it.body.contentType === 'json') { %>\n body: JSON.stringify(<%= it.body.name %>),\n<% } else if(it.body.contentType === 'urlencoded') { %>\n body: new URLSearchParams(<%= it.body.name %> as any),\n<% } else { %>\n body: <%= it.body.name %>,\n<% } %>\n<% } %>\n ...$config,\n })\n<% } %>\n<% if(it.responseContentType === 'binary') { %>\n .then((response) => response.blob() as Promise<<%~ it.returnType %>>);\n<% } else if(it.responseContentType === 'text') { %>\n .then((response) => response.text() as Promise<<%~ it.returnType %>>);\n<% } else { %>\n .then((response) => response.json() as Promise<<%~ it.returnType %>>);\n<% } %>\n },\n"
|
|
16
18
|
},
|
|
19
|
+
"ky": {
|
|
20
|
+
"barrel.ejs": "\n/**\n * Serializes a params object into a query string that is compatible with different REST APIs.\n * Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts\n * Kudos to @suhaotian for the original implementation\n */\nexport function encodeParams<T = any>(\n params: T,\n parentKey: string | null = null,\n options?: {\n allowDots?: boolean;\n serializeDate?: (value: Date) => string;\n arrayFormat?: 'indices' | 'repeat' | 'brackets';\n }\n): string {\n if (params === undefined || params === null) return '';\n const encodedParams: string[] = [];\n const paramsIsArray = Array.isArray(params);\n const { arrayFormat, allowDots, serializeDate } = options || {};\n\n const getKey = (key: string) => {\n if (allowDots && !paramsIsArray) return `.${key}`;\n if (paramsIsArray) {\n if (arrayFormat === 'brackets') {\n return '[]';\n }\n if (arrayFormat === 'repeat') {\n return '';\n }\n }\n return `[${key}]`;\n };\n\n for (const key in params) {\n if (Object.prototype.hasOwnProperty.call(params, key)) {\n let value = (params as any)[key];\n if (value !== undefined) {\n const encodedKey = parentKey ? `${parentKey}${getKey(key)}` : (key as string);\n\n // biome-ignore lint/suspicious/noGlobalIsNan: <explanation>\n if (!isNaN(value) && value instanceof Date) {\n value = serializeDate ? serializeDate(value) : value.toISOString();\n }\n if (typeof value === 'object') {\n // If the value is an object or array, recursively encode its contents\n const result = encodeParams(value, encodedKey, options);\n if (result !== '') encodedParams.push(result);\n } else {\n // Otherwise, encode the key-value pair\n encodedParams.push(`${encodeURIComponent(encodedKey)}=${encodeURIComponent(value)}`);\n }\n }\n }\n }\n\n return encodedParams.join('&');\n}\n",
|
|
21
|
+
"baseClient.ejs": "import ky, { type Options as KyOptions } from 'ky';\n\nexport const http = ky.create({\n prefix: '<%= it.baseUrl || '' %>',\n});\n",
|
|
22
|
+
"baseClientSetup.ejs": "/**\n * Ky client setup — GENERATED ONCE, will NOT be overwritten on subsequent runs.\n * Re-generate intentionally with: swaggie ... --clientSetup <path> --forceSetup\n *\n * This file configures the ky HTTP client used by the generated API client.\n * Because ky requires hooks to be provided at creation time, this file exports\n * a `createKyConfig()` function that is called by `initKyHttp()` in api.ts.\n *\n * Usage in your app:\n * import { initKyHttp } from '<%= it.relativeApiImport %>';\n * initKyHttp(); // call once at startup, e.g. in a React provider or main.ts\n *\n * If you need to pass runtime values into hooks (e.g. an auth token getter),\n * store them in module-level variables and expose a configuration function:\n *\n * let _getToken: () => Promise<string> = () => Promise.resolve('');\n *\n * export function configureKyClient(opts: { getToken: () => Promise<string> }) {\n * _getToken = opts.getToken;\n * }\n *\n * Then call configureKyClient(...) before initKyHttp() at startup.\n */\nimport type { Options as KyOptions } from 'ky';\n\nexport type KySetupConfig = Pick<KyOptions, 'hooks' | 'prefix' | 'retry' | 'timeout'>;\n\n/**\n * Returns the ky configuration used to create the HTTP client instance.\n * Called once by `initKyHttp()` in the generated api.ts.\n *\n * Add your hooks here — beforeRequest for auth/headers, afterResponse for\n * error handling and monitoring, beforeError for error enrichment.\n */\nexport function createKyConfig(): KySetupConfig {\n return {\n prefix: '<%= it.baseUrl || '' %>',\n hooks: {\n beforeRequest: [\n // TODO: Add request hooks, e.g. attach an Authorization header:\n // async ({ request }) => {\n // const token = await _getToken();\n // if (token) request.headers.set('Authorization', `Bearer ${token}`);\n // },\n ],\n afterResponse: [\n // TODO: Add response hooks, e.g. handle 401 Unauthorized:\n // async ({ response }) => {\n // if (response.status === 401) {\n // await loginWithRedirect({ returnTo: window.location.toString() });\n // }\n // },\n ],\n beforeError: [\n // TODO: Add error hooks, e.g. enrich errors with request context:\n // ({ error }) => error,\n ],\n },\n };\n}\n",
|
|
23
|
+
"baseClientWithSetup.ejs": "import ky, { type KyInstance, type Options as KyOptions } from 'ky';\nimport { createKyConfig } from '<%= it.relativeSetupImport %>';\n\nlet _http: KyInstance | null = null;\n\n/**\n * Initialises the ky HTTP client using the configuration returned by\n * `createKyConfig()` from your setup file.\n *\n * Call this once at application startup — for example inside a React provider\n * or your app's entry point — before any API calls are made.\n *\n * Re-calling this function replaces the existing instance. ky instances are\n * immutable, so hook changes require re-initialisation.\n */\nexport function initKyHttp(): void {\n _http = ky.create(createKyConfig());\n}\n\n/**\n * Returns the initialised ky instance. Throws if called before `initKyHttp()`.\n *\n * This guard prevents accidental use of the client before it has been\n * configured with hooks (auth, error handling, etc.).\n */\nexport function getKyHttp(): KyInstance {\n if (!_http) {\n throw new Error(\n '[Swaggie] ky client is not initialised. ' +\n 'Call initKyHttp() at application startup before making any API requests.'\n );\n }\n return _http;\n}\n",
|
|
24
|
+
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', Object.assign({ httpAccessor: it.httpAccessor }, operation)); %>\n\n<% }); %>\n};\n",
|
|
25
|
+
"operation.ejs": "<%~ it.jsDocs %>\n\n <%= it.name %>(<% it.parameters.forEach((parameter) => { %>\n<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,\n <% }); %>\n$config?: KyOptions\n ): Promise<<%~ it.returnType %>> {\n const url = `<%= it.url.replace(/^\\//, '') %>`;\n\n return <%= it.httpAccessor %>.<%= it.method.toLowerCase() %>(url, {\n<% if(it.body) { -%>\n<% if(it.body.contentType === 'json') { -%>\n json: <%= it.body.name %>,\n<% } else if(it.body.contentType === 'urlencoded') { -%>\n body: new URLSearchParams(<%= it.body.name %> as any),\n<% } else { -%>\n body: <%= it.body.name %>,\n<% } -%>\n<% } -%>\n<% if(it.query && it.query.length > 0) { -%>\n searchParams: {\n<% it.query.forEach((parameter) => { -%>\n<% if (it.queryParamObject) { -%>\n '<%= parameter.originalName %>': <%= it.queryParamObject.name %>?.<%= parameter.name %> as any,\n<% } else { -%>\n '<%= parameter.originalName %>': <%= parameter.name %> as any,\n<% } -%>\n<% }); -%>\n },\n<% } -%>\n<% if(it.headers && it.headers.length > 0) { -%>\n headers: {\n<% it.headers.forEach((parameter) => { -%>\n<% if (parameter.value) { -%>\n '<%= parameter.originalName %>': '<%= parameter.value %>',\n<% } else { -%>\n '<%= parameter.originalName %>': <%= parameter.name %>,\n<% } -%>\n<% }); -%>\n },\n<% } -%>\n ...$config,\n<% if(it.responseContentType === 'binary') { -%>\n }).blob() as Promise<<%~ it.returnType %>>;\n<% } else if(it.responseContentType === 'text') { -%>\n }).text() as Promise<<%~ it.returnType %>>;\n<% } else { -%>\n }).json<<%~ it.returnType %>>();\n<% } -%>\n },\n"
|
|
26
|
+
},
|
|
17
27
|
"ng1": {
|
|
18
28
|
"barrel.ejs": "export class ApiServices {\n public static bootstrap(moduleName: string, baseUrl: string) {\n angular\n .module(moduleName)\n .constant('Api<%= it.servicePrefix -%>BaseUrl', baseUrl)\n<% it.clients.forEach((client) => { %>\n .service('<%= client.fileName %>Service', <%= client.fileName %>Service)\n<% }); %>;\n }\n}\n\nfunction serializeQueryParam(obj: any, property: string): string {\n if (obj === null || obj === undefined || obj === '') {\n return '';\n } else if (obj instanceof Date) {\n return property + '=' + encodeURIComponent(obj.toJSON());\n } else if (Array.isArray(obj)) {\n return Object.values(obj)\n .map(value => `${property}[]=${value}`)\n .join('&');\n } else if (typeof obj !== 'object') {\n return property + '=' + encodeURIComponent(obj);\n } else if (typeof obj === 'object') {\n return Object.keys(obj)\n .filter(key => !!serializeQueryParam(obj[key], property + '.' + key))\n .reduce(\n (a: any, b) =>\n a.push(serializeQueryParam(obj[b], property + '.' + b)) && a,\n []\n )\n .join('&');\n } else {\n return '';\n }\n}\n",
|
|
19
29
|
"baseClient.ejs": "import type { IHttpService, IRequestShortcutConfig, IPromise } from 'angular';\n\nabstract class BaseService {\n constructor(protected readonly $http: IHttpService, public baseUrl: string) { }\n\n protected $get<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<T> {\n return this.$http.get(this.baseUrl + url, config).then((response: any) => {\n return this.processSingle<T>(response);\n });\n }\n\n protected $getAll<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<T[]> {\n return this.$http.get(this.baseUrl + url, config).then((response: any) => {\n return this.processMany<T>(response);\n });\n }\n\n protected $delete<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<any> {\n return this.$http\n .delete(this.baseUrl + url, config)\n .then((response: any) => {\n return this.processSingle<T>(response);\n });\n }\n\n protected $head<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<any> {\n return this.$http.head(this.baseUrl + url, config).then((response: any) => {\n return this.processSingle<T>(response);\n });\n }\n\n protected $jsonp<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<any> {\n return this.$http\n .jsonp(this.baseUrl + url, config)\n .then((response: any) => {\n return this.processSingle<T>(response);\n });\n }\n\n protected $post<T>(\n url: string,\n data: any,\n config?: IRequestShortcutConfig\n ): IPromise<any> {\n return this.$http\n .post(this.baseUrl + url, data, config)\n .then((response: any) => {\n return this.processSingle<T>(response);\n });\n }\n\n protected $put<T>(\n url: string,\n data: any,\n config?: IRequestShortcutConfig\n ): IPromise<any> {\n return this.$http\n .put(this.baseUrl + url, data, config)\n .then((response: any) => {\n return this.processSingle<T>(response);\n });\n }\n\n protected $patch<T>(\n url: string,\n data: any,\n config?: IRequestShortcutConfig\n ): IPromise<any> {\n return this.$http\n .patch(this.baseUrl + url, data, config)\n .then((response: any) => {\n return this.processSingle<T>(response);\n });\n }\n\n protected processSingle<T>(response: any): T {\n var data = response.data;\n var status = response.status;\n\n if (status >= 200 && status <= 299) {\n return data;\n } else {\n throw 'error_no_callback_for_the_received_http_status';\n }\n }\n\n protected processMany<T>(response: any): T[] {\n var data = response.data;\n var status = response.status;\n\n if (status >= 200 && status <= 299) {\n return data;\n } else {\n throw 'error_no_callback_for_the_received_http_status';\n }\n }\n}\n\n",
|
|
@@ -21,26 +31,29 @@
|
|
|
21
31
|
"operation.ejs": "<%~ it.jsDocs %>\n\n <%= it.name %>(<% it.parameters.forEach((parameter) => { %>\n<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,\n <% }); %>\n config?: IRequestShortcutConfig\n ): IPromise<<%~ it.returnType %>> {\n let url = `<%= it.url %>?`;\n<% if(it.query && it.query.length > 0) { %>\n <% it.query.forEach((parameter) => { %>\n if (<%= it.queryParamObject ? `${it.queryParamObject.name}?.${parameter.name}` : parameter.name %> !== undefined) {\n <% if(!!parameter.original && parameter.original.type === 'array') { %>\n <%= it.queryParamObject ? `${it.queryParamObject.name}.${parameter.name}` : parameter.name %>.forEach(item => { url += serializeQueryParam(item, '<%= parameter.originalName %>') + \"&\"; });\n <% } else {%>\n url += serializeQueryParam(<%= it.queryParamObject ? `${it.queryParamObject.name}.${parameter.name}` : parameter.name %>, '<%= parameter.originalName %>') + \"&\";\n <% } %>\n }\n <% }); %>\n<% } %>\n\n<% if(it.headers && it.headers.length > 0) { it.headers.forEach((parameter) => { %>\n<% if (parameter.value) { %>\n config = { ...config, headers: { ...config?.headers, '<%= parameter.originalName %>': '<%= parameter.value %>' } };\n<% } else { %>\n if (<%= parameter.name %>) {\n config = { ...config, headers: { ...config?.headers, '<%= parameter.originalName %>': <%= parameter.name %> } };\n }\n<% } %><% }); } %>\n return this.$<%= it.method.toLowerCase() %>(\n url,\n<% if(['POST', 'PUT', 'PATCH'].includes(it.method)) { %>\n <% if(it.body) { %>\n <%= it.body.contentType === 'urlencoded' ? 'new URLSearchParams(' + it.body.name + ' as any)' : it.body.name %>,\n <% } else { %>\n null,\n <% } %>\n<% } %>\n config\n );\n }\n"
|
|
22
32
|
},
|
|
23
33
|
"ng2": {
|
|
24
|
-
"barrel.ejs": "\n/**\n * Serializes a params object into a query string that is compatible with different REST APIs.\n * Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts\n * Kudos to @suhaotian for the original implementation\n */\
|
|
34
|
+
"barrel.ejs": "\n/**\n * Serializes a params object into a query string that is compatible with different REST APIs.\n * Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts\n * Kudos to @suhaotian for the original implementation\n */\nexport function encodeParams<T = any>(\n params: T,\n parentKey: string | null = null,\n options?: {\n allowDots?: boolean;\n serializeDate?: (value: Date) => string;\n arrayFormat?: 'indices' | 'repeat' | 'brackets';\n }\n): string {\n if (params === undefined || params === null) return '';\n const encodedParams: string[] = [];\n const paramsIsArray = Array.isArray(params);\n const { arrayFormat, allowDots, serializeDate } = options || {};\n\n const getKey = (key: string) => {\n if (allowDots && !paramsIsArray) return `.${key}`;\n if (paramsIsArray) {\n if (arrayFormat === 'brackets') {\n return '[]';\n }\n if (arrayFormat === 'repeat') {\n return '';\n }\n }\n return `[${key}]`;\n };\n\n for (const key in params) {\n if (Object.prototype.hasOwnProperty.call(params, key)) {\n let value = (params as any)[key];\n if (value !== undefined) {\n const encodedKey = parentKey ? `${parentKey}${getKey(key)}` : (key as string);\n\n // biome-ignore lint/suspicious/noGlobalIsNan: <explanation>\n if (!isNaN(value) && value instanceof Date) {\n value = serializeDate ? serializeDate(value) : value.toISOString();\n }\n if (typeof value === 'object') {\n // If the value is an object or array, recursively encode its contents\n const result = encodeParams(value, encodedKey, options);\n if (result !== '') encodedParams.push(result);\n } else {\n // Otherwise, encode the key-value pair\n encodedParams.push(`${encodeURIComponent(encodedKey)}=${encodeURIComponent(value)}`);\n }\n }\n }\n }\n\n return encodedParams.join('&');\n}\n\n",
|
|
25
35
|
"baseClient.ejs": "import type { Observable } from \"rxjs\";\nimport { Injectable, InjectionToken, inject } from \"@angular/core\";\nimport { HttpClient, type HttpContext, type HttpHeaders, type HttpParams } from \"@angular/common/http\";\n\nexport const <%= (it.servicePrefix || 'API').toUpperCase() -%>_BASE_URL = new InjectionToken<string>(\"<%= (it.servicePrefix || 'API').toUpperCase() -%>_BASE_URL\");\n\nexport interface HttpConfig {\n headers?: HttpHeaders | Record<string, string | string[]>;\n context?: HttpContext;\n params?: HttpParams | Record<string, string | number | boolean | (string | number | boolean)[]>;\n reportProgress?: boolean;\n withCredentials?: boolean;\n transferCache?: boolean | { includeHeaders?: string[] };\n}\n\nfunction paramsSerializer(params: any) {\n return encodeParams(params, null, {\n allowDots: <%= it.allowDots %>,\n arrayFormat: '<%= it.arrayFormat %>',\n });\n}\n\n",
|
|
26
36
|
"client.ejs": "@Injectable({\n providedIn: 'root'\n})\nexport class <%= it.clientName.charAt(0).toUpperCase() + it.clientName.slice(1) -%>Service {\n private http = inject(HttpClient);\n private baseUrl = inject(<%= (it.servicePrefix || 'API').toUpperCase() -%>_BASE_URL, { optional: true }) ?? '';\n\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n}\n\n",
|
|
27
37
|
"operation.ejs": "<%~ it.jsDocs %>\n\n <%= it.name %>(\n <% it.parameters.forEach((parameter) => { %>\n<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,\n <% }); %>\nconfig?: HttpConfig\n ): Observable<<%~ it.returnType %>> {\n<% if(it.query && it.query.length > 0) { -%>\n const params = paramsSerializer({\n<% it.query.forEach((parameter) => { -%>\n<% if (it.queryParamObject) { -%>\n '<%= parameter.originalName %>': <%= it.queryParamObject.name %>?.<%= parameter.name %>,\n<% } else { -%>\n '<%= parameter.originalName %>': <%= parameter.name %>,\n<% } -%>\n<% }); -%>\n });\n const url = `<%= it.url %>${params ? '?' + params : ''}`;\n<% } else { -%>\n const url = `<%= it.url %>`;\n<% } -%>\n<% if(it.headers && it.headers.length > 0) { it.headers.forEach((parameter) => { -%>\n<% if (parameter.value) { -%>\n config = { ...config, headers: { ...config?.headers, '<%= parameter.originalName %>': '<%= parameter.value %>' } };\n<% } else { -%>\n if (<%= parameter.name %>) {\n config = { ...config, headers: { ...config?.headers, '<%= parameter.originalName %>': <%= parameter.name %> } };\n }\n<% } -%>\n<% }); } -%>\n<% if(it.method === 'GET') { -%>\n return this.http.get<<%~ it.returnType %>>(this.baseUrl + url, config);\n<% } else if(it.method === 'DELETE') { -%>\n return this.http.delete<<%~ it.returnType %>>(this.baseUrl + url, config);\n<% } else if(['POST', 'PUT', 'PATCH'].includes(it.method)) {\n const bodyArg = it.body\n ? (it.body.contentType === 'urlencoded' ? 'new URLSearchParams(' + it.body.name + ' as any)' : it.body.name)\n : 'null';\n-%>\n return this.http.<%= it.method.toLowerCase() %><%~ '<' + it.returnType + '>' %>(this.baseUrl + url, <%~ bodyArg %>, config);\n<% } %>\n }\n"
|
|
28
38
|
},
|
|
29
39
|
"swr": {
|
|
30
40
|
"baseClient.ejs": "import useSWR, { type SWRConfiguration, type Key } from 'swr';\nimport useSWRMutation, { type SWRMutationConfiguration } from 'swr/mutation';\n\ninterface SwrConfig extends SWRConfiguration {\n /* Custom key for SWR. You don't have to worry about this as by default it's the URL. You can use standard SWR Key here if you need more flexibility. */\n key?: Key;\n}\n",
|
|
31
|
-
"client.ejs": "export const <%= it.camelCaseName -%>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n <% }); %>\n};\n\n<%\nvar getOperations = it.operations.filter((o) => o.method === 'GET');\nvar mutationOperations = it.operations.filter((o) => o.method !== 'GET');\n
|
|
32
|
-
"
|
|
33
|
-
"
|
|
41
|
+
"client.ejs": "export const <%= it.camelCaseName -%>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n <% }); %>\n};\n\n<% if (!it.splitMode) { %>\n<%\nvar getOperations = it.operations.filter((o) => o.method === 'GET');\nvar mutationOperations = it.operations.filter((o) => o.method !== 'GET');\n%>\n\nexport const <%= it.camelCaseName %> = {\n queries: {\n<% getOperations.forEach((operation) => {\n var opName = it.toOpName(operation.name);\n var swrOperation = Object.assign({\n swrOpName: 'use' + opName,\n clientName: it.camelCaseName,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n }, it.safeOperation(operation, it.camelCaseName));\n%>\n<%~ include('swrOperation.ejs', swrOperation); %>\n\n<% }); %>\n },\n\n mutations: {\n<% mutationOperations.forEach((operation) => {\n var opName = operation.name.charAt(0).toUpperCase() + operation.name.slice(1);\n var swrMutationOperation = Object.assign({\n mutOpName: 'use' + opName,\n clientName: it.camelCaseName,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n }, it.safeOperation(operation, it.camelCaseName));\n%>\n<%~ include('swrMutationOperation.ejs', swrMutationOperation); %>\n\n<% }); %>\n },\n\n queryKeys: {\n<% getOperations.forEach((operation) => {\n var opName = it.toOpName(operation.name);\n var keyName = opName.charAt(0).toLowerCase() + opName.slice(1);\n var safeOp = it.safeOperation(operation, it.camelCaseName);\n%>\n <%= keyName %>: (<% safeOp.parameters.forEach((parameter) => { %><%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>, <% }); %>) => `<%= operation.url %><% if(safeOp.query && safeOp.query.length > 0) { %>?${encodeParams({<% safeOp.query.forEach((parameter) => { %>'<%= parameter.originalName %>': <%= safeOp.queryParamObject ? `${safeOp.queryParamObject.name}?.${parameter.name}` : parameter.name %>, <% }); %>})}<% } %>`,\n<% }); %>\n },\n};\n<% } /* end !splitMode */ %>\n",
|
|
42
|
+
"hooksClient.ejs": "<%\nvar getOperations = it.operations.filter((o) => o.method === 'GET');\nvar mutationOperations = it.operations.filter((o) => o.method !== 'GET');\nvar httpClientPrefix = 'API.' + it.camelCaseName;\n%>\n\nexport const <%= it.camelCaseName %> = {\n queries: {\n<% getOperations.forEach((operation) => {\n var opName = it.toOpName(operation.name);\n var safe = it.safeOperation(operation, it.camelCaseName);\n var swrOperation = Object.assign({}, safe, {\n swrOpName: 'use' + opName,\n clientName: it.camelCaseName,\n httpClientName: httpClientPrefix,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n returnType: it.prefixApiType(safe.returnType),\n parameters: safe.parameters.map(function(p) {\n return Object.assign({}, p, { type: it.prefixApiType(p.type) });\n }),\n });\n%>\n<%~ include('swrOperation.ejs', swrOperation); %>\n\n<% }); %>\n },\n\n mutations: {\n<% mutationOperations.forEach((operation) => {\n var opName = operation.name.charAt(0).toUpperCase() + operation.name.slice(1);\n var safe = it.safeOperation(operation, it.camelCaseName);\n var swrMutationOperation = Object.assign({}, safe, {\n mutOpName: 'use' + opName,\n clientName: it.camelCaseName,\n httpClientName: httpClientPrefix,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n returnType: it.prefixApiType(safe.returnType),\n parameters: safe.parameters.map(function(p) {\n return Object.assign({}, p, { type: it.prefixApiType(p.type) });\n }),\n });\n%>\n<%~ include('swrMutationOperation.ejs', swrMutationOperation); %>\n\n<% }); %>\n },\n\n queryKeys: {\n<% getOperations.forEach((operation) => {\n var opName = it.toOpName(operation.name);\n var keyName = opName.charAt(0).toLowerCase() + opName.slice(1);\n var safeOp = it.safeOperation(operation, it.camelCaseName);\n var prefixedParams = safeOp.parameters.map(function(p) {\n return Object.assign({}, p, { type: it.prefixApiType(p.type) });\n });\n%>\n <%= keyName %>: (<% prefixedParams.forEach((parameter) => { %><%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>, <% }); %>) => `<%= operation.url %><% if(safeOp.query && safeOp.query.length > 0) { %>?${API.encodeParams({<% safeOp.query.forEach((parameter) => { %>'<%= parameter.originalName %>': <%= safeOp.queryParamObject ? `${safeOp.queryParamObject.name}?.${parameter.name}` : parameter.name %>, <% }); %>})}<% } %>`,\n<% }); %>\n },\n};\n",
|
|
43
|
+
"swrMutationOperation.ejs": "<%\nvar hasParams = it.parameters.length > 0;\n\nvar variablesType;\nif (!hasParams) {\n variablesType = 'void';\n} else {\n var parts = it.parameters.map(function(p) {\n return p.name + (p.skippable ? '?' : '') + ': ' + p.type + (p.optional ? ' | null' : '');\n });\n variablesType = '{ ' + parts.join('; ') + ' }';\n}\n\nvar callArgs = it.parameters.map(function(p) { return 'arg.' + p.name; });\n\n// Stable SWR key: replace all ${...} path expressions with *\nvar swrKey = it.url.replace(/\\$\\{(?:[^{}]|\\{[^{}]*\\})*\\}/g, '*');\n\nvar docs = it.jsDocs ? it.jsDocs.replace(/^/gm, ' ') + '\\n' : '';\nvar httpClientName = it.httpClientName || it.clientName;\n%>\n<%~ docs %>\n <%= it.mutOpName %>(\n $config?: SWRMutationConfiguration<<%~ it.returnType %>, Error, string, <%~ variablesType %>>,\n $httpConfig?: <%= it.httpConfigType %>\n ) {\n return useSWRMutation<<%~ it.returnType %>, Error, string, <%~ variablesType %>>(\n '<%= swrKey %>',\n (_key: string, { arg }<%= hasParams ? ': { arg: ' + variablesType + ' }' : '' %>) =>\n <%= httpClientName %>Client.<%= it.name %>(<%= callArgs.join(', ') %><%= callArgs.length > 0 ? ', ' : '' %>$httpConfig)<%= it.responseMapper %>,\n $config\n );\n },\n",
|
|
44
|
+
"swrOperation.ejs": "<% var docs = it.jsDocs ? it.jsDocs.replace(/^/gm, ' ') + '\\n' : ''; %>\n<% var httpClientName = it.httpClientName || it.clientName; %>\n<%~ docs %>\n <%= it.swrOpName %>(\n<% it.parameters.forEach((parameter) => { %> <%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>,\n<% }); %> $config?: Omit<SwrConfig, 'key'> & { key?: Key },\n $httpConfig?: <%= it.httpConfigType %>\n ) {\n const { key, ...config } = $config || {};\n const cacheUrl = key ?? <%= it.clientName %>.queryKeys.<%= it.swrOpName.charAt(3).toLowerCase() + it.swrOpName.slice(4) %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>);\n\n const { data, error, isLoading, mutate } = useSWR<<%~ it.returnType %>>(\n cacheUrl,\n () => <%= httpClientName %>Client.<%= it.name %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>$httpConfig)<%= it.responseMapper %>,\n config\n );\n\n return { data, isLoading, error, mutate };\n },\n"
|
|
34
45
|
},
|
|
35
46
|
"tsq": {
|
|
36
47
|
"baseClient.ejs": "import { type UseQueryOptions, type UseMutationOptions, useQuery, useMutation } from '@tanstack/react-query';\n",
|
|
37
|
-
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n};\n\n<%\nvar getOperations = it.operations.filter((o) => o.method === 'GET');\nvar mutationOperations = it.operations.filter((o) => o.method !== 'GET');\n
|
|
38
|
-
"
|
|
39
|
-
"
|
|
48
|
+
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n};\n\n<% if (!it.splitMode) { %>\n<%\nvar getOperations = it.operations.filter((o) => o.method === 'GET');\nvar mutationOperations = it.operations.filter((o) => o.method !== 'GET');\n%>\n\nexport const <%= it.camelCaseName %> = {\n queries: {\n<% getOperations.forEach((operation) => {\n var opName = it.toOpName(operation.name);\n var queryOperation = Object.assign({\n rqOpName: 'use' + opName,\n opKey: it.camelCaseName + opName,\n clientName: it.camelCaseName,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n }, it.safeOperation(operation, it.camelCaseName));\n%>\n<%~ include('queryOperation.ejs', queryOperation); %>\n\n<% }); %>\n },\n\n mutations: {\n<% mutationOperations.forEach((operation) => {\n var opName = operation.name.charAt(0).toUpperCase() + operation.name.slice(1);\n var mutationOperation = Object.assign({\n mutOpName: 'use' + opName,\n clientName: it.camelCaseName,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n }, it.safeOperation(operation, it.camelCaseName));\n%>\n<%~ include('mutationOperation.ejs', mutationOperation); %>\n\n<% }); %>\n },\n\n queryKeys: {\n<% getOperations.forEach((operation) => {\n var opName = it.toOpName(operation.name);\n var keyName = opName.charAt(0).toLowerCase() + opName.slice(1);\n var safeOp = it.safeOperation(operation, it.camelCaseName);\n%>\n <%= keyName %>: (<% safeOp.parameters.forEach((parameter) => { %><%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>, <% }); %>) => ['<%= it.camelCaseName %>', '<%= it.camelCaseName + opName %>'<% safeOp.parameters.forEach((parameter) => { %>, <%= parameter.name %><% }); %>] as const,\n<% }); %>\n },\n};\n<% } /* end !splitMode */ %>\n",
|
|
49
|
+
"hooksClient.ejs": "<%\nvar getOperations = it.operations.filter((o) => o.method === 'GET');\nvar mutationOperations = it.operations.filter((o) => o.method !== 'GET');\nvar httpClientPrefix = 'API.' + it.camelCaseName;\n%>\n\nexport const <%= it.camelCaseName %> = {\n queries: {\n<% getOperations.forEach((operation) => {\n var opName = it.toOpName(operation.name);\n var safe = it.safeOperation(operation, it.camelCaseName);\n var queryOperation = Object.assign({}, safe, {\n rqOpName: 'use' + opName,\n opKey: it.camelCaseName + opName,\n clientName: it.camelCaseName,\n httpClientName: httpClientPrefix,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n returnType: it.prefixApiType(safe.returnType),\n parameters: safe.parameters.map(function(p) {\n return Object.assign({}, p, { type: it.prefixApiType(p.type) });\n }),\n });\n%>\n<%~ include('queryOperation.ejs', queryOperation); %>\n\n<% }); %>\n },\n\n mutations: {\n<% mutationOperations.forEach((operation) => {\n var opName = operation.name.charAt(0).toUpperCase() + operation.name.slice(1);\n var safe = it.safeOperation(operation, it.camelCaseName);\n var mutationOperation = Object.assign({}, safe, {\n mutOpName: 'use' + opName,\n clientName: it.camelCaseName,\n httpClientName: httpClientPrefix,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n returnType: it.prefixApiType(safe.returnType),\n parameters: safe.parameters.map(function(p) {\n return Object.assign({}, p, { type: it.prefixApiType(p.type) });\n }),\n });\n%>\n<%~ include('mutationOperation.ejs', mutationOperation); %>\n\n<% }); %>\n },\n\n queryKeys: {\n<% getOperations.forEach((operation) => {\n var opName = it.toOpName(operation.name);\n var keyName = opName.charAt(0).toLowerCase() + opName.slice(1);\n var safeOp = it.safeOperation(operation, it.camelCaseName);\n var prefixedParams = safeOp.parameters.map(function(p) {\n return Object.assign({}, p, { type: it.prefixApiType(p.type) });\n });\n%>\n <%= keyName %>: (<% prefixedParams.forEach((parameter) => { %><%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>, <% }); %>) => ['<%= it.camelCaseName %>', '<%= it.camelCaseName + opName %>'<% safeOp.parameters.forEach((parameter) => { %>, <%= parameter.name %><% }); %>] as const,\n<% }); %>\n },\n};\n",
|
|
50
|
+
"mutationOperation.ejs": "<%\nvar hasParams = it.parameters.length > 0;\n\nvar variablesType;\nif (!hasParams) {\n variablesType = 'void';\n} else {\n var parts = it.parameters.map(function(p) {\n return p.name + (p.skippable ? '?' : '') + ': ' + p.type + (p.optional ? ' | null' : '');\n });\n variablesType = '{ ' + parts.join('; ') + ' }';\n}\n\nvar callArgs = it.parameters.map(function(p) { return 'vars.' + p.name; });\n\nvar baseAdditionalParams = ' * @param $config (optional) Additional configuration for TanStack Query\\n * @param $httpConfig (optional) Additional HTTP client configuration (passed to the underlying ' + it.httpConfigType + ')';\nvar rawDocs = it.jsDocs\n ? it.jsDocs.replace(/(\\s*)\\*\\/\\s*$/, '\\n' + baseAdditionalParams + '\\n */')\n : '';\nvar docs = rawDocs ? rawDocs.replace(/^/gm, ' ') + '\\n' : '';\nvar httpClientName = it.httpClientName || it.clientName;\n%>\n<%~ docs %>\n <%= it.mutOpName %><TData = <%~ it.returnType %>, TError = Error>(\n $config?: UseMutationOptions<<%~ it.returnType %>, TError, <%~ variablesType %>>,\n $httpConfig?: <%= it.httpConfigType %>\n ) {\n return useMutation<<%~ it.returnType %>, TError, <%~ variablesType %>>({\n mutationFn: (<%= hasParams ? 'vars' : '' %>) => <%= httpClientName %>Client.<%= it.name %>(<%= callArgs.join(', ') %><%= callArgs.length > 0 ? ', ' : '' %>$httpConfig)<%= it.responseMapper %>,\n ...$config\n });\n },\n",
|
|
51
|
+
"queryOperation.ejs": "<%\nvar baseAdditionalParams = ' * @param $config (optional) Additional configuration for TanStack Query\\n * @param $httpConfig (optional) Additional HTTP client configuration (passed to the underlying ' + it.httpConfigType + ')';\nvar rawDocs = it.jsDocs\n ? it.jsDocs.replace(/(\\s*)\\*\\/\\s*$/, '\\n' + baseAdditionalParams + '\\n */')\n : '';\nvar docs = rawDocs ? rawDocs.replace(/^/gm, ' ') + '\\n' : '';\nvar httpClientName = it.httpClientName || it.clientName;\n%>\n<%~ docs %>\n <%= it.rqOpName %><TData = <%~ it.returnType %>, TError = Error>(\n<% it.parameters.forEach((parameter) => { %> <%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>,\n<% }); %> $config?: Omit<UseQueryOptions<<%~ it.returnType %>, TError, TData>, 'queryKey' | 'queryFn'>,\n $httpConfig?: <%= it.httpConfigType %>\n ) {\n return useQuery<<%~ it.returnType %>, TError, TData>({\n queryKey: <%= it.clientName %>.queryKeys.<%= it.rqOpName.charAt(3).toLowerCase() + it.rqOpName.slice(4) %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>),\n queryFn: () => <%= httpClientName %>Client.<%= it.name %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>$httpConfig)<%= it.responseMapper %>,\n ...$config\n });\n },\n"
|
|
40
52
|
},
|
|
41
53
|
"xior": {
|
|
42
|
-
"barrel.ejs": "",
|
|
54
|
+
"barrel.ejs": "export { encodeParams } from 'xior';\n",
|
|
43
55
|
"baseClient.ejs": "import xior, { type XiorResponse, type XiorRequestConfig, encodeParams } from \"xior\";\n\nexport const http = xior.create({\n baseURL: '<%= it.baseUrl || '' %>',\n paramsSerializer: (params: any) =>\n encodeParams(params, true, null, {\n allowDots: <%= it.allowDots %>,\n arrayFormat: '<%= it.arrayFormat %>',\n }),\n});\n\n",
|
|
56
|
+
"baseClientSetup.ejs": "/**\n * Xior client setup — GENERATED ONCE, will NOT be overwritten on subsequent runs.\n * Re-generate intentionally with: swaggie ... --clientSetup <path> --forceSetup\n *\n * This file is a write-once scaffold for configuring the xior instance exported\n * from the generated API client. It is NOT imported by api.ts — call\n * `setupApiClient()` once at your application startup.\n *\n * Usage in your app:\n * import { setupApiClient } from '<%= it.relativeSetupImport %>';\n * const cleanup = setupApiClient(); // call once before any API requests\n *\n * The setup function can accept any parameters your app needs (e.g. an auth ref):\n * export function setupApiClient(authRef: AuthRef): () => void { ... }\n */\nimport { http } from '<%= it.relativeApiImport %>';\n\n/**\n * Registers interceptors on the shared xior instance.\n * Returns a cleanup function that ejects the interceptors — useful for\n * React effects or other lifecycle-managed contexts.\n */\nexport function setupApiClient(): () => void {\n const requestInterceptor = http.interceptors.request.use(\n (config) => {\n // TODO: Add request interceptors, e.g. attach an Authorization header:\n // const token = await authRef.current.getAccessToken();\n // if (token) config.headers.Authorization = `Bearer ${token}`;\n return config;\n },\n (error) => Promise.reject(error)\n );\n\n const responseInterceptor = http.interceptors.response.use(\n (response) => response,\n async (error) => {\n // TODO: Add response error handling, e.g. redirect on 401:\n // if (error.response?.status === 401) {\n // await authRef.current.loginWithRedirect({ returnTo: window.location.toString() });\n // return;\n // }\n return Promise.reject(error);\n }\n );\n\n return () => {\n http.interceptors.request.eject(requestInterceptor);\n http.interceptors.response.eject(responseInterceptor);\n };\n}\n",
|
|
44
57
|
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n};\n\n",
|
|
45
58
|
"operation.ejs": "<%~ it.jsDocs %>\n\n <%= it.name %>(<% it.parameters.forEach((parameter) => { %>\n<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,\n <% }); %>\n$config?: XiorRequestConfig\n ): Promise<XiorResponse<<%~ it.returnType %>>> {\n const url = `<%= it.url %>`;\n\n return http.request<<%~ it.returnType %>>({\n url: url,\n method: '<%= it.method %>',\n<% if(it.body) { -%>\n<% if(it.body.contentType === 'urlencoded') { -%>\n data: new URLSearchParams(<%= it.body.name %> as any),\n<% } else { -%>\n data: <%= it.body.name %>,\n<% } -%>\n<% } -%>\n<% if(it.query && it.query.length > 0) { -%>\n params: {\n<% it.query.forEach((parameter) => { -%>\n<% if (it.queryParamObject) { -%>\n '<%= parameter.originalName %>': <%= it.queryParamObject.name %>?.<%= parameter.name %>,\n<% } else { -%>\n '<%= parameter.originalName %>': <%= parameter.name %>,\n<% } -%>\n<% }); -%>\n },\n<% } -%>\n<% if(it.headers && it.headers.length > 0) { -%>\n headers: {\n<% it.headers.forEach((parameter) => { -%>\n<% if (parameter.value) { -%>\n '<%= parameter.originalName %>': '<%= parameter.value %>',\n<% } else { -%>\n '<%= parameter.originalName %>': <%= parameter.name %>,\n<% } -%>\n<% }); -%>\n },\n<% } -%>\n ...$config,\n });\n },\n"
|
|
46
59
|
}
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,28 @@ function verifyOptions(options) {
|
|
|
40
40
|
'--mocks requires --out to be set, since the mock file needs to import the generated client'
|
|
41
41
|
);
|
|
42
42
|
}
|
|
43
|
+
if (options.hooksOut && !options.out) {
|
|
44
|
+
throw new Error('--hooksOut requires --out to be set');
|
|
45
|
+
}
|
|
46
|
+
if (options.clientSetup && !options.out) {
|
|
47
|
+
throw new Error('--clientSetup requires --out to be set');
|
|
48
|
+
}
|
|
49
|
+
if (options.forceSetup && !options.clientSetup) {
|
|
50
|
+
throw new Error('--forceSetup requires --clientSetup to be set');
|
|
51
|
+
}
|
|
52
|
+
if (options.hooksOut) {
|
|
53
|
+
// Validate after normalization — template may be a bare L2 string or a pair
|
|
54
|
+
const tpl = options.template;
|
|
55
|
+
const isL2 =
|
|
56
|
+
(typeof tpl === 'string' && _templateValidator.isL2Template.call(void 0, tpl)) ||
|
|
57
|
+
(Array.isArray(tpl) && typeof tpl[0] === 'string' && _templateValidator.isL2Template.call(void 0, tpl[0]));
|
|
58
|
+
if (!isL2) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'--hooksOut requires an L2 template (swr, tsq). ' +
|
|
61
|
+
'Reactive hooks are only generated for L2 template pairs.'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
43
65
|
}
|
|
44
66
|
|
|
45
67
|
function gen(spec, options) {
|
|
@@ -103,6 +125,9 @@ function readFile(filePath) {
|
|
|
103
125
|
queryParamsSerialization = {},
|
|
104
126
|
mocks,
|
|
105
127
|
testingFramework,
|
|
128
|
+
hooksOut,
|
|
129
|
+
clientSetup,
|
|
130
|
+
forceSetup,
|
|
106
131
|
...rest
|
|
107
132
|
} = cliOpts;
|
|
108
133
|
const mergedQueryParamsSerialization = {
|
|
@@ -129,6 +154,9 @@ function readFile(filePath) {
|
|
|
129
154
|
queryParamsSerialization: mergedQueryParamsSerialization,
|
|
130
155
|
...(mocks !== undefined ? { mocks } : {}),
|
|
131
156
|
...(testingFramework !== undefined ? { testingFramework } : {}),
|
|
157
|
+
...(hooksOut !== undefined ? { hooksOut } : {}),
|
|
158
|
+
...(clientSetup !== undefined ? { clientSetup } : {}),
|
|
159
|
+
...(forceSetup !== undefined ? { forceSetup } : {}),
|
|
132
160
|
};
|
|
133
161
|
} exports.prepareAppOptions = prepareAppOptions;
|
|
134
162
|
|
package/dist/types.d.ts
CHANGED
|
@@ -50,12 +50,23 @@ export interface ClientOptions {
|
|
|
50
50
|
*/
|
|
51
51
|
enumNamesStyle?: EnumNamesStyle;
|
|
52
52
|
/**
|
|
53
|
-
* Prepends `'use client';` as the very first line of the generated file
|
|
53
|
+
* Prepends `'use client';` as the very first line of the generated hooks file
|
|
54
|
+
* (when `hooksOut` is set) or the main generated file (legacy single-file mode).
|
|
54
55
|
* Required for Next.js App Router when using SWR or TanStack Query hooks,
|
|
55
56
|
* which can only run in Client Components.
|
|
56
57
|
* Has no effect and should not be used with non-RSC environments.
|
|
57
58
|
*/
|
|
58
59
|
useClient?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Output path for the generated reactive hooks file (L2 templates only).
|
|
62
|
+
* When set, the reactive hook namespaces (`pet.queries`, `pet.mutations`,
|
|
63
|
+
* `pet.queryKeys`) are written to this file instead of the main `out` file.
|
|
64
|
+
* The hooks file imports the main file as `import * as API from './api'`.
|
|
65
|
+
* When `useClient` is also set, the `'use client';` directive is prepended
|
|
66
|
+
* only to this file (the main file remains server-safe).
|
|
67
|
+
* Requires `out` to also be set.
|
|
68
|
+
*/
|
|
69
|
+
hooksOut?: string;
|
|
59
70
|
/**
|
|
60
71
|
* Output path for the generated mock/stub file. Requires `testingFramework`
|
|
61
72
|
* and `out` to also be set. When provided, a companion mock file is generated
|
|
@@ -69,6 +80,25 @@ export interface ClientOptions {
|
|
|
69
80
|
* Requires `mocks` and `out` to also be set.
|
|
70
81
|
*/
|
|
71
82
|
testingFramework?: TestingFramework;
|
|
83
|
+
/**
|
|
84
|
+
* Output path for the write-once client setup file. When provided, Swaggie
|
|
85
|
+
* generates a setup scaffold at this path on the first run and never overwrites
|
|
86
|
+
* it on subsequent runs (use `forceSetup` to override this).
|
|
87
|
+
*
|
|
88
|
+
* For the `ky` template, the generated `api.ts` imports from this file and uses
|
|
89
|
+
* its exported `createKyConfig()` to initialise the ky instance with hooks.
|
|
90
|
+
* For other templates (`axios`, `xior`, `fetch`), the setup file is a standalone
|
|
91
|
+
* scaffold showing how to configure the exported HTTP client — `api.ts` does not
|
|
92
|
+
* import it.
|
|
93
|
+
*
|
|
94
|
+
* Requires `out` to also be set.
|
|
95
|
+
*/
|
|
96
|
+
clientSetup?: string;
|
|
97
|
+
/**
|
|
98
|
+
* When `true`, overwrites the setup file even if it already exists.
|
|
99
|
+
* Has no effect unless `clientSetup` is also set.
|
|
100
|
+
*/
|
|
101
|
+
forceSetup?: boolean;
|
|
72
102
|
/** Offers ability to adjust the OpenAPI spec before it is processed */
|
|
73
103
|
modifiers?: {
|
|
74
104
|
/** Global-level modifiers for parameter with a given name */
|
|
@@ -95,7 +125,7 @@ export interface FullAppOptions extends ClientOptions {
|
|
|
95
125
|
config?: string;
|
|
96
126
|
}
|
|
97
127
|
/** HTTP client templates (standalone, no reactive layer) */
|
|
98
|
-
export type L1Template = 'axios' | 'fetch' | 'xior' | 'ng1' | 'ng2';
|
|
128
|
+
export type L1Template = 'axios' | 'fetch' | 'xior' | 'ng1' | 'ng2' | 'ky';
|
|
99
129
|
/** Reactive query layer templates (must be composed with an L1 template) */
|
|
100
130
|
export type L2Template = 'swr' | 'tsq';
|
|
101
131
|
/** Any named built-in template */
|
package/dist/utils/fileUtils.js
CHANGED
|
@@ -35,3 +35,23 @@ var _nodepath = require('node:path');
|
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
} exports.saveFile = saveFile;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Writes `contents` to `filePath` only when the file does not already exist,
|
|
41
|
+
* or when `force` is `true`. Useful for write-once scaffold files that should
|
|
42
|
+
* survive re-generation without losing user edits.
|
|
43
|
+
*
|
|
44
|
+
* @returns `'written'` when the file was created/overwritten, `'skipped'` when
|
|
45
|
+
* it already existed and `force` was not set.
|
|
46
|
+
*/
|
|
47
|
+
async function saveFileIfMissing(
|
|
48
|
+
filePath,
|
|
49
|
+
contents,
|
|
50
|
+
force = false
|
|
51
|
+
) {
|
|
52
|
+
if (!force && _nodefs.existsSync.call(void 0, filePath)) {
|
|
53
|
+
return 'skipped';
|
|
54
|
+
}
|
|
55
|
+
await saveFile(filePath, contents);
|
|
56
|
+
return 'written';
|
|
57
|
+
} exports.saveFileIfMissing = saveFileIfMissing;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
const L1_TEMPLATES = ['axios', 'fetch', 'xior', 'ng1', 'ng2'];
|
|
3
|
+
const L1_TEMPLATES = ['axios', 'fetch', 'xior', 'ng1', 'ng2', 'ky'];
|
|
4
4
|
const L2_TEMPLATES = ['swr', 'tsq'];
|
|
5
5
|
|
|
6
6
|
/** L1 templates that are incompatible with any L2 (framework-specific clients) */
|
|
@@ -120,8 +120,36 @@ const DEFAULT_L1_FOR_L2 = 'fetch';
|
|
|
120
120
|
return 'XiorRequestConfig';
|
|
121
121
|
case 'fetch':
|
|
122
122
|
return 'RequestInit';
|
|
123
|
+
case 'ky':
|
|
124
|
+
return 'KyOptions';
|
|
123
125
|
default:
|
|
124
126
|
// Custom path or unknown — fall back to a permissive type
|
|
125
127
|
return 'Record<string, unknown>';
|
|
126
128
|
}
|
|
127
129
|
} exports.getHttpConfigType = getHttpConfigType;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Returns the response mapper snippet used in L2 operation templates to extract
|
|
133
|
+
* the final data value from a client method call.
|
|
134
|
+
*
|
|
135
|
+
* Axios and xior wrap responses in `{ data: T }`, so the mapper unwraps `.data`.
|
|
136
|
+
* Fetch and ky resolve to `T` directly, so no unwrapping is needed.
|
|
137
|
+
*
|
|
138
|
+
* The returned string is appended after the client call expression:
|
|
139
|
+
* `client.method(...args)` + responseMapper
|
|
140
|
+
*
|
|
141
|
+
* Examples:
|
|
142
|
+
* axios/xior → `.then((resp) => resp.data)`
|
|
143
|
+
* fetch/ky → `` (empty — the promise already resolves to T)
|
|
144
|
+
*/
|
|
145
|
+
function getResponseMapper(template) {
|
|
146
|
+
const l1 = getL1Template(template);
|
|
147
|
+
switch (l1) {
|
|
148
|
+
case 'fetch':
|
|
149
|
+
case 'ky':
|
|
150
|
+
return '';
|
|
151
|
+
default:
|
|
152
|
+
// axios, xior, and unknown custom templates all use { data: T } wrapper
|
|
153
|
+
return '.then((resp) => resp.data)';
|
|
154
|
+
}
|
|
155
|
+
} exports.getResponseMapper = getResponseMapper;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts
|
|
5
5
|
* Kudos to @suhaotian for the original implementation
|
|
6
6
|
*/
|
|
7
|
-
function encodeParams<T = any>(
|
|
7
|
+
export function encodeParams<T = any>(
|
|
8
8
|
params: T,
|
|
9
9
|
parentKey: string | null = null,
|
|
10
10
|
options?: {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Axios client setup — GENERATED ONCE, will NOT be overwritten on subsequent runs.
|
|
3
|
+
* Re-generate intentionally with: swaggie ... --clientSetup <path> --forceSetup
|
|
4
|
+
*
|
|
5
|
+
* This file is a write-once scaffold for configuring the Axios instance exported
|
|
6
|
+
* from the generated API client. It is NOT imported by api.ts — call
|
|
7
|
+
* `setupApiClient()` once at your application startup.
|
|
8
|
+
*
|
|
9
|
+
* Usage in your app:
|
|
10
|
+
* import { setupApiClient } from '<%= it.relativeSetupImport %>';
|
|
11
|
+
* setupApiClient(); // call once before any API requests
|
|
12
|
+
*
|
|
13
|
+
* The setup function can accept any parameters your app needs (e.g. an auth ref):
|
|
14
|
+
* export function setupApiClient(authRef: AuthRef): () => void { ... }
|
|
15
|
+
*/
|
|
16
|
+
import { axios } from '<%= it.relativeApiImport %>';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configures the shared Axios instance with request/response interceptors.
|
|
20
|
+
* Returns a cleanup function that ejects the interceptors — useful for
|
|
21
|
+
* React effects or other lifecycle-managed contexts.
|
|
22
|
+
*/
|
|
23
|
+
export function setupApiClient(): () => void {
|
|
24
|
+
const requestInterceptor = axios.interceptors.request.use(
|
|
25
|
+
(config) => {
|
|
26
|
+
// TODO: Add request interceptors, e.g. attach an Authorization header:
|
|
27
|
+
// config.headers.Authorization = `Bearer ${getToken()}`;
|
|
28
|
+
return config;
|
|
29
|
+
},
|
|
30
|
+
(error) => Promise.reject(error)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const responseInterceptor = axios.interceptors.response.use(
|
|
34
|
+
(response) => response,
|
|
35
|
+
(error) => {
|
|
36
|
+
// TODO: Add response error handling, e.g. redirect on 401:
|
|
37
|
+
// if (error.response?.status === 401) { loginWithRedirect(); }
|
|
38
|
+
return Promise.reject(error);
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
axios.interceptors.request.eject(requestInterceptor);
|
|
44
|
+
axios.interceptors.response.eject(responseInterceptor);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts
|
|
5
5
|
* Kudos to @suhaotian for the original implementation
|
|
6
6
|
*/
|
|
7
|
-
function encodeParams<T = any>(
|
|
7
|
+
export function encodeParams<T = any>(
|
|
8
8
|
params: T,
|
|
9
9
|
parentKey: string | null = null,
|
|
10
10
|
options?: {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch client setup — GENERATED ONCE, will NOT be overwritten on subsequent runs.
|
|
3
|
+
* Re-generate intentionally with: swaggie ... --clientSetup <path> --forceSetup
|
|
4
|
+
*
|
|
5
|
+
* This file is a write-once scaffold for configuring the fetch-based client
|
|
6
|
+
* exported from the generated API client. It is NOT imported by api.ts — call
|
|
7
|
+
* `setupApiClient()` once at your application startup.
|
|
8
|
+
*
|
|
9
|
+
* Usage in your app:
|
|
10
|
+
* import { setupApiClient } from '<%= it.relativeSetupImport %>';
|
|
11
|
+
* setupApiClient(); // call once before any API requests
|
|
12
|
+
*
|
|
13
|
+
* The setup function can accept any parameters your app needs (e.g. an auth ref):
|
|
14
|
+
* export function setupApiClient(authRef: AuthRef): void { ... }
|
|
15
|
+
*/
|
|
16
|
+
import { defaults } from '<%= it.relativeApiImport %>';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configures the shared fetch client defaults.
|
|
20
|
+
* Override `defaults.fetch` to wrap the native fetch with custom behaviour
|
|
21
|
+
* (auth headers, error handling, logging, etc.).
|
|
22
|
+
*/
|
|
23
|
+
export function setupApiClient(): void {
|
|
24
|
+
const originalFetch = defaults.fetch ?? globalThis.fetch;
|
|
25
|
+
|
|
26
|
+
defaults.fetch = async (input, init) => {
|
|
27
|
+
// TODO: Add pre-request logic, e.g. attach an Authorization header:
|
|
28
|
+
// const token = await getToken();
|
|
29
|
+
// init = { ...init, headers: { ...init?.headers, Authorization: `Bearer ${token}` } };
|
|
30
|
+
|
|
31
|
+
const response = await originalFetch(input, init);
|
|
32
|
+
|
|
33
|
+
// TODO: Add post-response logic, e.g. handle 401 Unauthorized:
|
|
34
|
+
// if (response.status === 401) { loginWithRedirect(); }
|
|
35
|
+
|
|
36
|
+
return response;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Serializes a params object into a query string that is compatible with different REST APIs.
|
|
4
|
+
* Implementation from: https://github.com/suhaotian/xior/blob/main/src/utils.ts
|
|
5
|
+
* Kudos to @suhaotian for the original implementation
|
|
6
|
+
*/
|
|
7
|
+
export function encodeParams<T = any>(
|
|
8
|
+
params: T,
|
|
9
|
+
parentKey: string | null = null,
|
|
10
|
+
options?: {
|
|
11
|
+
allowDots?: boolean;
|
|
12
|
+
serializeDate?: (value: Date) => string;
|
|
13
|
+
arrayFormat?: 'indices' | 'repeat' | 'brackets';
|
|
14
|
+
}
|
|
15
|
+
): string {
|
|
16
|
+
if (params === undefined || params === null) return '';
|
|
17
|
+
const encodedParams: string[] = [];
|
|
18
|
+
const paramsIsArray = Array.isArray(params);
|
|
19
|
+
const { arrayFormat, allowDots, serializeDate } = options || {};
|
|
20
|
+
|
|
21
|
+
const getKey = (key: string) => {
|
|
22
|
+
if (allowDots && !paramsIsArray) return `.${key}`;
|
|
23
|
+
if (paramsIsArray) {
|
|
24
|
+
if (arrayFormat === 'brackets') {
|
|
25
|
+
return '[]';
|
|
26
|
+
}
|
|
27
|
+
if (arrayFormat === 'repeat') {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return `[${key}]`;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
for (const key in params) {
|
|
35
|
+
if (Object.prototype.hasOwnProperty.call(params, key)) {
|
|
36
|
+
let value = (params as any)[key];
|
|
37
|
+
if (value !== undefined) {
|
|
38
|
+
const encodedKey = parentKey ? `${parentKey}${getKey(key)}` : (key as string);
|
|
39
|
+
|
|
40
|
+
// biome-ignore lint/suspicious/noGlobalIsNan: <explanation>
|
|
41
|
+
if (!isNaN(value) && value instanceof Date) {
|
|
42
|
+
value = serializeDate ? serializeDate(value) : value.toISOString();
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === 'object') {
|
|
45
|
+
// If the value is an object or array, recursively encode its contents
|
|
46
|
+
const result = encodeParams(value, encodedKey, options);
|
|
47
|
+
if (result !== '') encodedParams.push(result);
|
|
48
|
+
} else {
|
|
49
|
+
// Otherwise, encode the key-value pair
|
|
50
|
+
encodedParams.push(`${encodeURIComponent(encodedKey)}=${encodeURIComponent(value)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return encodedParams.join('&');
|
|
57
|
+
}
|