swaggie 2.2.2 → 2.4.0
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 +15 -0
- package/dist/cli.js +7 -0
- package/dist/gen/genMocks.js +1 -6
- package/dist/gen/genOperations.js +54 -7
- package/dist/generated/bundledTemplates.js +22 -22
- package/dist/index.js +39 -0
- package/dist/types.d.ts +33 -0
- package/dist/utils/templateValidator.js +21 -2
- package/package.json +1 -1
- package/templates/axios/baseClient.ejs +8 -1
- package/templates/axios/operation.ejs +14 -2
- package/templates/fetch/baseClient.ejs +7 -0
- package/templates/fetch/operation.ejs +14 -6
- package/templates/ky/baseClient.ejs +7 -0
- package/templates/ky/baseClientWithSetup.ejs +7 -0
- package/templates/ky/operation.ejs +14 -2
- package/templates/ng1/baseClient.ejs +65 -1
- package/templates/ng1/operation.ejs +2 -2
- package/templates/ng2/baseClient.ejs +9 -1
- package/templates/ng2/operation.ejs +19 -7
- package/templates/swr/baseClient.ejs +2 -2
- package/templates/swr/client.ejs +4 -0
- package/templates/swr/hooksClient.ejs +4 -0
- package/templates/swr/swrMutationOperation.ejs +3 -2
- package/templates/swr/swrOperation.ejs +3 -2
- package/templates/tsq/client.ejs +4 -0
- package/templates/tsq/hooksClient.ejs +4 -0
- package/templates/tsq/mutationOperation.ejs +4 -3
- package/templates/tsq/queryOperation.ejs +4 -3
- package/templates/xior/baseClient.ejs +7 -0
- package/templates/xior/operation.ejs +14 -2
package/README.md
CHANGED
|
@@ -396,6 +396,21 @@ Sometimes an API spec marks a parameter as required, but your client handles it
|
|
|
396
396
|
- `"optional"` — the parameter becomes optional regardless of what the spec says
|
|
397
397
|
- `"required"` — the parameter is always required (generally better to just fix the spec)
|
|
398
398
|
|
|
399
|
+
### Excluding Operations
|
|
400
|
+
|
|
401
|
+
Use `exclude` to drop entire operations from the generated output by tag or `operationId` — without modifying the spec. Types used exclusively by excluded operations are pruned automatically. Both fields support `*` and `?` wildcards.
|
|
402
|
+
|
|
403
|
+
```json
|
|
404
|
+
{
|
|
405
|
+
"exclude": {
|
|
406
|
+
"tags": ["admin", "internal"],
|
|
407
|
+
"operationIds": ["deleteAccount", "admin*"]
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
See the [full documentation](https://yhnavein.github.io/swaggie/guide/advanced#excluding-operations) for details and examples.
|
|
413
|
+
|
|
399
414
|
### Code Quality
|
|
400
415
|
|
|
401
416
|
Swaggie's output is functional but not always perfectly formatted, since it uses a templating engine internally. It is strongly recommended to run the output through a formatter to ensure consistent style across regenerations.
|
package/dist/cli.js
CHANGED
|
@@ -45,6 +45,12 @@ const queryParamsAsObjectOption = new (0, _commander.Option)(
|
|
|
45
45
|
'--queryParamsAsObject [threshold]',
|
|
46
46
|
'Group query params into a single object; pass a number to group only when query params count is greater than threshold'
|
|
47
47
|
).argParser(parseQueryParamsAsObjectArg);
|
|
48
|
+
const responseShapeOption = new (0, _commander.Option)(
|
|
49
|
+
'--responseShape <shape>',
|
|
50
|
+
"Standardize operation return shape across templates: 'body' returns the body (T); " +
|
|
51
|
+
"'full' returns an APIResponse<T> wrapper with data, headers and statusCode. " +
|
|
52
|
+
'When omitted, each template keeps its current default return shape.'
|
|
53
|
+
).choices(['body', 'full']);
|
|
48
54
|
|
|
49
55
|
const program = new (0, _commander.Command)();
|
|
50
56
|
program
|
|
@@ -101,6 +107,7 @@ program
|
|
|
101
107
|
.addOption(dateFormatOption)
|
|
102
108
|
.addOption(nullableStrategyOption)
|
|
103
109
|
.addOption(queryParamsAsObjectOption)
|
|
110
|
+
.addOption(responseShapeOption)
|
|
104
111
|
.option(
|
|
105
112
|
'--hooksOut <filePath>',
|
|
106
113
|
'Output path for the generated hooks file (L2 templates only). ' +
|
package/dist/gen/genMocks.js
CHANGED
|
@@ -287,14 +287,9 @@ function withMockSWR<Fn extends (...args: never[]) => unknown>(spy: ${ref}.Spied
|
|
|
287
287
|
/** Augments a spy with a \`mockSWRMutation\` shorthand for useSWRMutation hooks. */
|
|
288
288
|
function withMockSWRMutation<Fn extends (...args: never[]) => unknown>(spy: ${ref}.SpiedFunction<Fn>) {
|
|
289
289
|
return Object.assign(spy, {
|
|
290
|
-
mockSWRMutation({
|
|
291
|
-
data,
|
|
292
|
-
...rest
|
|
293
|
-
}: MockSWRMutationReturn) {
|
|
290
|
+
mockSWRMutation({ ...rest }: MockSWRMutationReturn) {
|
|
294
291
|
spy.mockReturnValue({
|
|
295
292
|
...defaultSWRMutationReturn,
|
|
296
|
-
trigger: ${ref}.fn(() => Promise.resolve(undefined)),
|
|
297
|
-
data,
|
|
298
293
|
...rest,
|
|
299
294
|
} as ReturnType<Fn>);
|
|
300
295
|
},
|
|
@@ -23,6 +23,16 @@ var _header = require('./header');
|
|
|
23
23
|
|
|
24
24
|
var _jsDocs = require('./jsDocs');
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Name of the generated wrapper interface emitted when `responseShape: 'full'`.
|
|
28
|
+
* Exposed to templates as `it.apiResponseType` so the name lives in one place.
|
|
29
|
+
*
|
|
30
|
+
* NOTE: like the 3rd-party type imports (e.g. `AxiosRequestConfig`), this name is
|
|
31
|
+
* not currently guarded against collisions with a user-defined schema of the same
|
|
32
|
+
* name. Keep it stable and reasonably unique.
|
|
33
|
+
*/
|
|
34
|
+
const API_RESPONSE_TYPE = 'APIResponse';
|
|
35
|
+
|
|
26
36
|
/**
|
|
27
37
|
* Function that will analyze paths in the spec and generate the code for all the operations.
|
|
28
38
|
*
|
|
@@ -45,6 +55,8 @@ var _jsDocs = require('./jsDocs');
|
|
|
45
55
|
servicePrefix,
|
|
46
56
|
baseUrl: options.baseUrl,
|
|
47
57
|
...options.queryParamsSerialization,
|
|
58
|
+
responseShape: options.responseShape,
|
|
59
|
+
apiResponseType: API_RESPONSE_TYPE,
|
|
48
60
|
// For the ky+setup variant, embed the relative import to the setup file
|
|
49
61
|
...(isKyWithSetup && relativeSetupImport ? { relativeSetupImport } : {}),
|
|
50
62
|
};
|
|
@@ -81,7 +93,9 @@ var _jsDocs = require('./jsDocs');
|
|
|
81
93
|
...clientData,
|
|
82
94
|
servicePrefix,
|
|
83
95
|
httpConfigType: _templateValidator.getHttpConfigType.call(void 0, options.template),
|
|
84
|
-
responseMapper: _templateValidator.getResponseMapper.call(void 0, options.template),
|
|
96
|
+
responseMapper: _templateValidator.getResponseMapper.call(void 0, options.template, options.responseShape),
|
|
97
|
+
responseShape: options.responseShape,
|
|
98
|
+
apiResponseType: API_RESPONSE_TYPE,
|
|
85
99
|
// For the ky+setup variant, operations call getKyHttp() instead of the
|
|
86
100
|
// module-level `http` singleton.
|
|
87
101
|
httpAccessor: isKyWithSetup ? 'getKyHttp()' : 'http',
|
|
@@ -157,6 +171,8 @@ var _jsDocs = require('./jsDocs');
|
|
|
157
171
|
servicePrefix,
|
|
158
172
|
baseUrl: options.baseUrl,
|
|
159
173
|
...options.queryParamsSerialization,
|
|
174
|
+
responseShape: options.responseShape,
|
|
175
|
+
apiResponseType: API_RESPONSE_TYPE,
|
|
160
176
|
};
|
|
161
177
|
|
|
162
178
|
// L2 base client contains the reactive library imports (useSWR, useQuery, etc.)
|
|
@@ -191,7 +207,9 @@ var _jsDocs = require('./jsDocs');
|
|
|
191
207
|
...clientData,
|
|
192
208
|
servicePrefix,
|
|
193
209
|
httpConfigType: _templateValidator.getHttpConfigType.call(void 0, options.template),
|
|
194
|
-
responseMapper: _templateValidator.getResponseMapper.call(void 0, options.template),
|
|
210
|
+
responseMapper: _templateValidator.getResponseMapper.call(void 0, options.template, options.responseShape),
|
|
211
|
+
responseShape: options.responseShape,
|
|
212
|
+
apiResponseType: API_RESPONSE_TYPE,
|
|
195
213
|
// Template helper functions — defined once here, used in all L2 templates.
|
|
196
214
|
toOpName,
|
|
197
215
|
safeOperation,
|
|
@@ -255,6 +273,18 @@ function prepareClient(
|
|
|
255
273
|
ops = ops.filter((op) => !op.deprecated);
|
|
256
274
|
}
|
|
257
275
|
|
|
276
|
+
if (_optionalChain([options, 'access', _2 => _2.exclude, 'optionalAccess', _3 => _3.tags, 'optionalAccess', _4 => _4.length])) {
|
|
277
|
+
ops = ops.filter(
|
|
278
|
+
(op) => !_optionalChain([op, 'access', _5 => _5.tags, 'optionalAccess', _6 => _6.some, 'call', _7 => _7((t) => options.exclude.tags.some((p) => matchesPattern(t, p)))])
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (_optionalChain([options, 'access', _8 => _8.exclude, 'optionalAccess', _9 => _9.operationIds, 'optionalAccess', _10 => _10.length])) {
|
|
283
|
+
ops = ops.filter(
|
|
284
|
+
(op) => !options.exclude.operationIds.some((p) => matchesPattern(_nullishCoalesce(op.operationId, () => ( '')), p))
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
258
288
|
return ops.map((op) => {
|
|
259
289
|
const operationContext = `${op.method.toUpperCase()} ${op.path} (${op.operationId || 'unknown operationId'})`;
|
|
260
290
|
|
|
@@ -295,9 +325,9 @@ function prepareClient(
|
|
|
295
325
|
|
|
296
326
|
const headers = getParams(op.parameters , options, ['header']);
|
|
297
327
|
// Some libraries need explicit Content-Type for request bodies.
|
|
298
|
-
if (_optionalChain([body, 'optionalAccess',
|
|
328
|
+
if (_optionalChain([body, 'optionalAccess', _11 => _11.contentType]) === 'urlencoded') {
|
|
299
329
|
upsertFixedHeader(headers, 'Content-Type', 'application/x-www-form-urlencoded');
|
|
300
|
-
} else if (_optionalChain([body, 'optionalAccess',
|
|
330
|
+
} else if (_optionalChain([body, 'optionalAccess', _12 => _12.contentType]) === 'json' && _templateValidator.getL1Template.call(void 0, options.template) === 'fetch') {
|
|
301
331
|
upsertFixedHeader(headers, 'Content-Type', 'application/json');
|
|
302
332
|
}
|
|
303
333
|
|
|
@@ -485,10 +515,10 @@ function prepareUrl(path) {
|
|
|
485
515
|
type: _swagger.getParameterType.call(void 0, p, options),
|
|
486
516
|
optional: p.required === undefined || p.required === null ? true : !p.required,
|
|
487
517
|
original: p,
|
|
488
|
-
jsDoc: _optionalChain([p, 'access',
|
|
518
|
+
jsDoc: _optionalChain([p, 'access', _13 => _13.description, 'optionalAccess', _14 => _14.trim, 'call', _15 => _15()]),
|
|
489
519
|
}));
|
|
490
520
|
|
|
491
|
-
if (_optionalChain([options, 'access',
|
|
521
|
+
if (_optionalChain([options, 'access', _16 => _16.modifiers, 'optionalAccess', _17 => _17.parameters])) {
|
|
492
522
|
for (const [name, modifier] of Object.entries(options.modifiers.parameters)) {
|
|
493
523
|
const paramIndex = result.findIndex(
|
|
494
524
|
(p) => p.original.in !== 'path' && (p.originalName === name || p.name === name)
|
|
@@ -539,7 +569,7 @@ function getRequestBody(
|
|
|
539
569
|
let reqBody;
|
|
540
570
|
if ('$ref' in rawReqBody) {
|
|
541
571
|
const refName = rawReqBody.$ref.replace('#/components/requestBodies/', '');
|
|
542
|
-
const resolved = _optionalChain([components, 'optionalAccess',
|
|
572
|
+
const resolved = _optionalChain([components, 'optionalAccess', _18 => _18.requestBodies, 'optionalAccess', _19 => _19[refName]]);
|
|
543
573
|
if (!resolved || '$ref' in resolved) {
|
|
544
574
|
console.error(`RequestBody $ref '${rawReqBody.$ref}' not found in components/requestBodies`);
|
|
545
575
|
return null;
|
|
@@ -673,6 +703,23 @@ function getL1HttpTypeImport(template) {
|
|
|
673
703
|
}
|
|
674
704
|
}
|
|
675
705
|
|
|
706
|
+
/**
|
|
707
|
+
* Matches a value against a pattern that may contain `*` (any sequence of
|
|
708
|
+
* characters) or `?` (any single character). Falls back to exact equality for
|
|
709
|
+
* patterns that contain neither wildcard (fast path).
|
|
710
|
+
*/
|
|
711
|
+
function matchesPattern(value, pattern) {
|
|
712
|
+
if (!pattern.includes('*') && !pattern.includes('?')) {
|
|
713
|
+
return value === pattern;
|
|
714
|
+
}
|
|
715
|
+
const regex = new RegExp(
|
|
716
|
+
'^' +
|
|
717
|
+
pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') +
|
|
718
|
+
'$'
|
|
719
|
+
);
|
|
720
|
+
return regex.test(value);
|
|
721
|
+
} exports.matchesPattern = matchesPattern;
|
|
722
|
+
|
|
676
723
|
function upsertFixedHeader(headers, headerName, value) {
|
|
677
724
|
const headerIndex = headers.findIndex(
|
|
678
725
|
(header) => header.originalName.toLowerCase() === headerName.toLowerCase()
|
|
@@ -4,58 +4,58 @@
|
|
|
4
4
|
const BUNDLED_TEMPLATES = {
|
|
5
5
|
"axios": {
|
|
6
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
|
-
"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",
|
|
7
|
+
"baseClient.ejs": "import Axios, { type AxiosPromise, type AxiosRequestConfig<% if (it.responseShape === 'full') { %>, type AxiosResponse<% } %> } 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<% if (it.responseShape === 'full') { %>\nexport interface <%= it.apiResponseType %><T> {\n data: T;\n headers: AxiosResponse<T>['headers'];\n status: number;\n}\n<% } %>\n\n",
|
|
8
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",
|
|
9
9
|
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n};\n\n",
|
|
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 ):
|
|
10
|
+
"operation.ejs": "<%\nvar returnTypeWrapped = 'AxiosPromise<' + it.returnType + '>';\nvar responseMapper = '';\nif (it.responseShape === 'full') {\n // AxiosResponse<T> already exposes { data, headers, status } and is therefore\n // structurally assignable to APIResponse<T> — no remap needed.\n returnTypeWrapped = 'Promise<' + it.apiResponseType + '<' + it.returnType + '>>';\n} else if (it.responseShape === 'body') {\n returnTypeWrapped = 'Promise<' + it.returnType + '>';\n responseMapper = '.then((resp) => resp.data)';\n}\n%>\n<%~ 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 ): <%~ returnTypeWrapped %> {\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 })<%~ responseMapper %>;\n },\n"
|
|
11
11
|
},
|
|
12
12
|
"fetch": {
|
|
13
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",
|
|
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",
|
|
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<% if (it.responseShape === 'full') { %>\nexport interface <%= it.apiResponseType %><T> {\n data: T;\n headers: Headers;\n status: number;\n}\n<% } %>\n\n",
|
|
15
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",
|
|
16
16
|
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n};\n",
|
|
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 ):
|
|
17
|
+
"operation.ejs": "<%\nvar parseMethod = it.responseContentType === 'binary' ? 'blob' : (it.responseContentType === 'text' ? 'text' : 'json');\nvar returnTypeWrapped = it.responseShape === 'full'\n ? 'Promise<' + it.apiResponseType + '<' + it.returnType + '>>'\n : 'Promise<' + it.returnType + '>';\n%>\n<%~ 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 ): <%~ returnTypeWrapped %> {\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.responseShape === 'full') { %>\n .then(async (response) => ({\n data: (await response.<%= parseMethod %>()) as <%~ it.returnType %>,\n headers: response.headers,\n status: response.status,\n }));\n<% } else { %>\n .then((response) => response.<%= parseMethod %>() as Promise<<%~ it.returnType %>>);\n<% } %>\n },\n"
|
|
18
18
|
},
|
|
19
19
|
"ky": {
|
|
20
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 */\nfunction _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/** Serializes a params object into a query string. Options override the generated defaults (allowDots, arrayFormat). */\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 return _encodeParams(params, parentKey, {\n allowDots: <%= it.allowDots %>,\n arrayFormat: '<%= it.arrayFormat %>',\n ...options,\n });\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",
|
|
21
|
+
"baseClient.ejs": "import ky, { type Options as KyOptions } from 'ky';\n\nexport const http = ky.create({\n prefix: '<%= it.baseUrl || '' %>',\n});\n<% if (it.responseShape === 'full') { %>\nexport interface <%= it.apiResponseType %><T> {\n data: T;\n headers: Headers;\n status: number;\n}\n<% } %>\n",
|
|
22
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",
|
|
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<% if (it.responseShape === 'full') { %>\nexport interface <%= it.apiResponseType %><T> {\n data: T;\n headers: Headers;\n status: number;\n}\n<% } %>\n",
|
|
24
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 ):
|
|
25
|
+
"operation.ejs": "<%\nvar parseMethod = it.responseContentType === 'binary' ? 'blob' : (it.responseContentType === 'text' ? 'text' : 'json');\nvar returnTypeWrapped = it.responseShape === 'full'\n ? 'Promise<' + it.apiResponseType + '<' + it.returnType + '>>'\n : 'Promise<' + it.returnType + '>';\n%>\n<%~ 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 ): <%~ returnTypeWrapped %> {\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: encodeParams({\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<% if (it.responseShape === 'full') { -%>\n }).then(async (response) => ({\n data: (await response.<%= parseMethod %>()) as <%~ it.returnType %>,\n headers: response.headers,\n status: response.status,\n }));\n<% } else 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
26
|
},
|
|
27
27
|
"ng1": {
|
|
28
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",
|
|
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",
|
|
29
|
+
"baseClient.ejs": "import type { IHttpService, IRequestShortcutConfig, IPromise<% if (it.responseShape === 'full') { %>, IHttpHeadersGetter<% } %> } 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<% if (it.responseShape === 'full') { %>\n protected $getFull<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<<%= it.apiResponseType %><T>> {\n return this.$http.get(this.baseUrl + url, config).then((response: any) => this.processFull<T>(response));\n }\n\n protected $deleteFull<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<<%= it.apiResponseType %><T>> {\n return this.$http.delete(this.baseUrl + url, config).then((response: any) => this.processFull<T>(response));\n }\n\n protected $headFull<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<<%= it.apiResponseType %><T>> {\n return this.$http.head(this.baseUrl + url, config).then((response: any) => this.processFull<T>(response));\n }\n\n protected $jsonpFull<T>(\n url: string,\n config?: IRequestShortcutConfig\n ): IPromise<<%= it.apiResponseType %><T>> {\n return this.$http.jsonp(this.baseUrl + url, config).then((response: any) => this.processFull<T>(response));\n }\n\n protected $postFull<T>(\n url: string,\n data: any,\n config?: IRequestShortcutConfig\n ): IPromise<<%= it.apiResponseType %><T>> {\n return this.$http.post(this.baseUrl + url, data, config).then((response: any) => this.processFull<T>(response));\n }\n\n protected $putFull<T>(\n url: string,\n data: any,\n config?: IRequestShortcutConfig\n ): IPromise<<%= it.apiResponseType %><T>> {\n return this.$http.put(this.baseUrl + url, data, config).then((response: any) => this.processFull<T>(response));\n }\n\n protected $patchFull<T>(\n url: string,\n data: any,\n config?: IRequestShortcutConfig\n ): IPromise<<%= it.apiResponseType %><T>> {\n return this.$http.patch(this.baseUrl + url, data, config).then((response: any) => this.processFull<T>(response));\n }\n\n protected processFull<T>(response: any): <%= it.apiResponseType %><T> {\n return { data: response.data, headers: response.headers, status: response.status };\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<% if (it.responseShape === 'full') { %>\nexport interface <%= it.apiResponseType %><T> {\n data: T;\n headers: IHttpHeadersGetter;\n status: number;\n}\n<% } %>\n\n",
|
|
30
30
|
"client.ejs": "export class <%= it.clientName -%>Service extends BaseService {\n /* @ngInject */\n constructor($http: IHttpService, Api<%= it.servicePrefix -%>BaseUrl: string) {\n super($http, Api<%= it.servicePrefix -%>BaseUrl);\n }\n\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n}\n\n",
|
|
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
|
|
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 ): <%~ it.responseShape === 'full' ? 'IPromise<' + it.apiResponseType + '<' + it.returnType + '>>' : '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() %><%= it.responseShape === 'full' ? 'Full' : '' %>(\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"
|
|
32
32
|
},
|
|
33
33
|
"ng2": {
|
|
34
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",
|
|
35
|
-
"baseClient.ejs": "import type { Observable } from \"rxjs\";\
|
|
35
|
+
"baseClient.ejs": "import type { Observable } from \"rxjs\";\n<% if (it.responseShape === 'full') { %>import { map } from \"rxjs/operators\";\n<% } %>import { 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<% if (it.responseShape === 'full') { %>\nexport interface <%= it.apiResponseType %><T> {\n data: T;\n headers: HttpHeaders;\n status: number;\n}\n<% } %>\n\n",
|
|
36
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",
|
|
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
|
|
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 ): <%~ it.responseShape === 'full' ? 'Observable<' + it.apiResponseType + '<' + it.returnType + '>>' : '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<%\nvar fullMap = '.pipe(\\n map((response) => ({ data: response.body as ' + it.returnType + ', headers: response.headers, status: response.status }))\\n )';\nvar bodyArg = it.body\n ? (it.body.contentType === 'urlencoded' ? 'new URLSearchParams(' + it.body.name + ' as any)' : it.body.name)\n : 'null';\n-%>\n<% if (it.responseShape === 'full') { -%>\n<% if(it.method === 'GET') { -%>\n return this.http.get<<%~ it.returnType %>>(this.baseUrl + url, { ...config, observe: 'response' as const })<%~ fullMap %>;\n<% } else if(it.method === 'DELETE') { -%>\n return this.http.delete<<%~ it.returnType %>>(this.baseUrl + url, { ...config, observe: 'response' as const })<%~ fullMap %>;\n<% } else if(['POST', 'PUT', 'PATCH'].includes(it.method)) { -%>\n return this.http.<%= it.method.toLowerCase() %><%~ '<' + it.returnType + '>' %>(this.baseUrl + url, <%~ bodyArg %>, { ...config, observe: 'response' as const })<%~ fullMap %>;\n<% } -%>\n<% } else { -%>\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 return this.http.<%= it.method.toLowerCase() %><%~ '<' + it.returnType + '>' %>(this.baseUrl + url, <%~ bodyArg %>, config);\n<% } -%>\n<% } -%>\n }\n"
|
|
38
38
|
},
|
|
39
39
|
"swr": {
|
|
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?:
|
|
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.hooksCamelCaseName %> = {\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.hooksCamelCaseName,\n httpClientName: 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.hooksCamelCaseName,\n httpClientName: 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.hooksCamelCaseName %> = {\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.hooksCamelCaseName,\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.hooksCamelCaseName,\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<<%~
|
|
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?:
|
|
40
|
+
"baseClient.ejs": "import useSWR, { type SWRConfiguration, type Key as SWRKey } 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?: SWRKey;\n}\n",
|
|
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.hooksCamelCaseName %> = {\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.hooksCamelCaseName,\n httpClientName: it.camelCaseName,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n responseShape: it.responseShape,\n apiResponseType: it.apiResponseType,\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.hooksCamelCaseName,\n httpClientName: it.camelCaseName,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n responseShape: it.responseShape,\n apiResponseType: it.apiResponseType,\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.hooksCamelCaseName %> = {\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.hooksCamelCaseName,\n httpClientName: httpClientPrefix,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n responseShape: it.responseShape,\n apiResponseType: it.prefixApiType(it.apiResponseType),\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.hooksCamelCaseName,\n httpClientName: httpClientPrefix,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n responseShape: it.responseShape,\n apiResponseType: it.prefixApiType(it.apiResponseType),\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;\nvar dataType = it.responseShape === 'full' ? it.apiResponseType + '<' + it.returnType + '>' : it.returnType;\n%>\n<%~ docs %>\n <%= it.mutOpName %>(\n $config?: SWRMutationConfiguration<<%~ dataType %>, Error, string, <%~ variablesType %>>,\n $httpConfig?: <%= it.httpConfigType %>\n ) {\n return useSWRMutation<<%~ dataType %>, 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<% var dataType = it.responseShape === 'full' ? it.apiResponseType + '<' + it.returnType + '>' : it.returnType; %>\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?: SWRKey },\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, isValidating, mutate } = useSWR<<%~ dataType %>>(\n cacheUrl,\n () => <%= httpClientName %>Client.<%= it.name %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>$httpConfig)<%~ it.responseMapper %>,\n config\n );\n\n return { data, isLoading, isValidating, error, mutate };\n },\n"
|
|
45
45
|
},
|
|
46
46
|
"tsq": {
|
|
47
47
|
"baseClient.ejs": "import { type UseQueryOptions, type UseMutationOptions, useQuery, useMutation } from '@tanstack/react-query';\n",
|
|
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.hooksCamelCaseName %> = {\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.hooksCamelCaseName + opName,\n clientName: it.hooksCamelCaseName,\n httpClientName: 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.hooksCamelCaseName,\n httpClientName: 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.hooksCamelCaseName %>', '<%= it.hooksCamelCaseName + 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.hooksCamelCaseName %> = {\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.hooksCamelCaseName + opName,\n clientName: it.hooksCamelCaseName,\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.hooksCamelCaseName,\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.hooksCamelCaseName %>', '<%= it.hooksCamelCaseName + 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 = <%~
|
|
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 = <%~
|
|
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.hooksCamelCaseName %> = {\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.hooksCamelCaseName + opName,\n clientName: it.hooksCamelCaseName,\n httpClientName: it.camelCaseName,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n responseShape: it.responseShape,\n apiResponseType: it.apiResponseType,\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.hooksCamelCaseName,\n httpClientName: it.camelCaseName,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n responseShape: it.responseShape,\n apiResponseType: it.apiResponseType,\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.hooksCamelCaseName %>', '<%= it.hooksCamelCaseName + 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.hooksCamelCaseName %> = {\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.hooksCamelCaseName + opName,\n clientName: it.hooksCamelCaseName,\n httpClientName: httpClientPrefix,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n responseShape: it.responseShape,\n apiResponseType: it.prefixApiType(it.apiResponseType),\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.hooksCamelCaseName,\n httpClientName: httpClientPrefix,\n httpConfigType: it.httpConfigType,\n responseMapper: it.responseMapper,\n responseShape: it.responseShape,\n apiResponseType: it.prefixApiType(it.apiResponseType),\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.hooksCamelCaseName %>', '<%= it.hooksCamelCaseName + 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;\nvar dataType = it.responseShape === 'full' ? it.apiResponseType + '<' + it.returnType + '>' : it.returnType;\n%>\n<%~ docs %>\n <%= it.mutOpName %><TData = <%~ dataType %>, TError = Error>(\n $config?: UseMutationOptions<<%~ dataType %>, TError, <%~ variablesType %>>,\n $httpConfig?: <%= it.httpConfigType %>\n ) {\n return useMutation<<%~ dataType %>, 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;\nvar dataType = it.responseShape === 'full' ? it.apiResponseType + '<' + it.returnType + '>' : it.returnType;\n%>\n<%~ docs %>\n <%= it.rqOpName %><TData = <%~ dataType %>, TError = Error>(\n<% it.parameters.forEach((parameter) => { %> <%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>,\n<% }); %> $config?: Omit<UseQueryOptions<<%~ dataType %>, TError, TData>, 'queryKey' | 'queryFn'>,\n $httpConfig?: <%= it.httpConfigType %>\n ) {\n return useQuery<<%~ dataType %>, 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"
|
|
52
52
|
},
|
|
53
53
|
"xior": {
|
|
54
54
|
"barrel.ejs": "export { encodeParams } from 'xior';\n",
|
|
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",
|
|
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<% if (it.responseShape === 'full') { %>\nexport interface <%= it.apiResponseType %><T> {\n data: T;\n headers: XiorResponse<T>['headers'];\n status: number;\n}\n<% } %>\n\n",
|
|
56
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",
|
|
57
57
|
"client.ejs": "export const <%= it.camelCaseName %>Client = {\n <% it.operations.forEach((operation) => { %>\n<%~ include('operation.ejs', operation); %>\n\n<% }); %>\n};\n\n",
|
|
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 ):
|
|
58
|
+
"operation.ejs": "<%\nvar returnTypeWrapped = 'Promise<XiorResponse<' + it.returnType + '>>';\nvar responseMapper = '';\nif (it.responseShape === 'full') {\n // XiorResponse<T> already exposes { data, headers, status } and is therefore\n // structurally assignable to APIResponse<T> — no remap needed.\n returnTypeWrapped = 'Promise<' + it.apiResponseType + '<' + it.returnType + '>>';\n} else if (it.responseShape === 'body') {\n returnTypeWrapped = 'Promise<' + it.returnType + '>';\n responseMapper = '.then((resp) => resp.data)';\n}\n%>\n<%~ 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 ): <%~ returnTypeWrapped %> {\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 })<%~ responseMapper %>;\n },\n"
|
|
59
59
|
}
|
|
60
60
|
} ; exports.BUNDLED_TEMPLATES = BUNDLED_TEMPLATES;
|
|
61
61
|
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ var _gen = require('./gen'); var _gen2 = _interopRequireDefault(_gen);
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
|
|
13
14
|
var _utils = require('./utils');
|
|
14
15
|
var _templateValidator = require('./utils/templateValidator');
|
|
15
16
|
var _swagger = require('./swagger');
|
|
@@ -86,6 +87,7 @@ function verifyOptions(options) {
|
|
|
86
87
|
);
|
|
87
88
|
}
|
|
88
89
|
}
|
|
90
|
+
verifyExcludeOptions(options.exclude);
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
/**
|
|
@@ -126,6 +128,43 @@ function verifyEntryOptions(opts) {
|
|
|
126
128
|
);
|
|
127
129
|
}
|
|
128
130
|
}
|
|
131
|
+
verifyExcludeOptions(opts.exclude);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Returns true when a pattern looks like a regex (starts with `/` or contains
|
|
136
|
+
* regex-specific metacharacters that are not our supported wildcards).
|
|
137
|
+
* Our supported wildcards are `*` and `?` only.
|
|
138
|
+
*/
|
|
139
|
+
function isRegexPattern(pattern) {
|
|
140
|
+
if (pattern.startsWith('/')) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
// Characters that are regex metacharacters but are NOT our supported wildcards
|
|
144
|
+
const regexOnlyChars = /[\\^$.|+()[\]{}]/;
|
|
145
|
+
return regexOnlyChars.test(pattern);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Throws if any pattern in `exclude.tags` or `exclude.operationIds` looks like
|
|
150
|
+
* a regex. Only plain strings and * / ? wildcards are supported.
|
|
151
|
+
*/
|
|
152
|
+
function verifyExcludeOptions(exclude) {
|
|
153
|
+
if (!exclude) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const allPatterns = [
|
|
157
|
+
...(_nullishCoalesce(exclude.tags, () => ( []))).map((p) => ['exclude.tags', p]),
|
|
158
|
+
...(_nullishCoalesce(exclude.operationIds, () => ( []))).map((p) => ['exclude.operationIds', p]),
|
|
159
|
+
];
|
|
160
|
+
for (const [field, pattern] of allPatterns) {
|
|
161
|
+
if (isRegexPattern(pattern)) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Invalid pattern "${pattern}" in ${field}: regex patterns are not supported. ` +
|
|
164
|
+
'Use plain strings or wildcard patterns with * and ? only.'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
129
168
|
}
|
|
130
169
|
|
|
131
170
|
/**
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,18 @@ interface QueryParamsSerializationOptions {
|
|
|
4
4
|
arrayFormat?: ArrayFormat;
|
|
5
5
|
queryParamsAsObject?: boolean | number;
|
|
6
6
|
}
|
|
7
|
+
export interface ExcludeOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Exclude operations whose first tag matches any of these values.
|
|
10
|
+
* Supports * (any sequence of characters) and ? (any single character) wildcards.
|
|
11
|
+
*/
|
|
12
|
+
tags?: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Exclude operations whose operationId matches any of these values.
|
|
15
|
+
* Supports * (any sequence of characters) and ? (any single character) wildcards.
|
|
16
|
+
*/
|
|
17
|
+
operationIds?: string[];
|
|
18
|
+
}
|
|
7
19
|
export interface ClientOptions {
|
|
8
20
|
/**
|
|
9
21
|
* Path or URL to the Swagger specification file (JSON or YAML).
|
|
@@ -106,6 +118,21 @@ export interface ClientOptions {
|
|
|
106
118
|
[key: string]: 'optional' | 'required' | 'ignore';
|
|
107
119
|
};
|
|
108
120
|
};
|
|
121
|
+
/** Excludes specific operations from code generation by tag or operationId */
|
|
122
|
+
exclude?: ExcludeOptions;
|
|
123
|
+
/**
|
|
124
|
+
* Controls the shape of the value each generated operation returns.
|
|
125
|
+
* - `undefined` (default) — leave each template's current return shape untouched
|
|
126
|
+
* (non-breaking). axios/xior return their full native response, while
|
|
127
|
+
* fetch/ky/ng return the response body only.
|
|
128
|
+
* - `'body'` — standardize every template to return just the response body (`T`).
|
|
129
|
+
* - `'full'` — standardize every template to return a unified `APIResponse<T>`
|
|
130
|
+
* wrapper exposing `data`, `headers` (template-native type) and `statusCode`.
|
|
131
|
+
*
|
|
132
|
+
* Any explicit value (`'body'` or `'full'`) is a deliberate, potentially
|
|
133
|
+
* breaking change relative to the template's default return shape.
|
|
134
|
+
*/
|
|
135
|
+
responseShape?: ResponseShape;
|
|
109
136
|
}
|
|
110
137
|
export interface CliOptions extends Omit<FullAppOptions, 'enumNamesStyle'> {
|
|
111
138
|
allowDots?: boolean;
|
|
@@ -150,6 +177,12 @@ export type SchemaDeclarationStyle = 'interface' | 'type';
|
|
|
150
177
|
export type EnumDeclarationStyle = 'union' | 'enum';
|
|
151
178
|
export type EnumNamesStyle = 'original' | 'PascalCase';
|
|
152
179
|
export type TestingFramework = 'vitest' | 'jest';
|
|
180
|
+
/**
|
|
181
|
+
* Controls the shape of the value each generated operation returns.
|
|
182
|
+
* `'body'` returns the response body (`T`); `'full'` returns an `APIResponse<T>`
|
|
183
|
+
* wrapper. When unset, each template keeps its current default return shape.
|
|
184
|
+
*/
|
|
185
|
+
export type ResponseShape = 'body' | 'full';
|
|
153
186
|
/**
|
|
154
187
|
* Internal options type used throughout the app after `prepareAppOptions` has run.
|
|
155
188
|
* All fields that have defaults are required here so the rest of the codebase never
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
3
9
|
const L1_TEMPLATES = ['axios', 'fetch', 'xior', 'ng1', 'ng2', 'ky'];
|
|
4
10
|
const L2_TEMPLATES = ['swr', 'tsq'];
|
|
5
11
|
|
|
@@ -138,11 +144,24 @@ const DEFAULT_L1_FOR_L2 = 'fetch';
|
|
|
138
144
|
* The returned string is appended after the client call expression:
|
|
139
145
|
* `client.method(...args)` + responseMapper
|
|
140
146
|
*
|
|
141
|
-
*
|
|
147
|
+
* When `responseShape` is set (`'body'` or `'full'`), every L1 operation already
|
|
148
|
+
* resolves to the final shape (`T` or `APIResponse<T>`), so no extra mapping is
|
|
149
|
+
* needed and this returns an empty string regardless of the L1 template.
|
|
150
|
+
*
|
|
151
|
+
* Examples (responseShape unset):
|
|
142
152
|
* axios/xior → `.then((resp) => resp.data)`
|
|
143
153
|
* fetch/ky → `` (empty — the promise already resolves to T)
|
|
144
154
|
*/
|
|
145
|
-
function getResponseMapper(
|
|
155
|
+
function getResponseMapper(
|
|
156
|
+
template,
|
|
157
|
+
responseShape
|
|
158
|
+
) {
|
|
159
|
+
// When the response shape is standardized, the L1 operation already returns
|
|
160
|
+
// the final value (body or APIResponse wrapper) — no unwrapping needed.
|
|
161
|
+
if (responseShape) {
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
|
|
146
165
|
const l1 = getL1Template(template);
|
|
147
166
|
switch (l1) {
|
|
148
167
|
case 'fetch':
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Axios, { type AxiosPromise, type AxiosRequestConfig } from "axios";
|
|
1
|
+
import Axios, { type AxiosPromise, type AxiosRequestConfig<% if (it.responseShape === 'full') { %>, type AxiosResponse<% } %> } from "axios";
|
|
2
2
|
|
|
3
3
|
export const axios = Axios.create({
|
|
4
4
|
baseURL: '<%= it.baseUrl || '' %>',
|
|
@@ -8,4 +8,11 @@ export const axios = Axios.create({
|
|
|
8
8
|
arrayFormat: '<%= it.arrayFormat %>',
|
|
9
9
|
}),
|
|
10
10
|
});
|
|
11
|
+
<% if (it.responseShape === 'full') { %>
|
|
12
|
+
export interface <%= it.apiResponseType %><T> {
|
|
13
|
+
data: T;
|
|
14
|
+
headers: AxiosResponse<T>['headers'];
|
|
15
|
+
status: number;
|
|
16
|
+
}
|
|
17
|
+
<% } %>
|
|
11
18
|
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
<%
|
|
2
|
+
var returnTypeWrapped = 'AxiosPromise<' + it.returnType + '>';
|
|
3
|
+
var responseMapper = '';
|
|
4
|
+
if (it.responseShape === 'full') {
|
|
5
|
+
// AxiosResponse<T> already exposes { data, headers, status } and is therefore
|
|
6
|
+
// structurally assignable to APIResponse<T> — no remap needed.
|
|
7
|
+
returnTypeWrapped = 'Promise<' + it.apiResponseType + '<' + it.returnType + '>>';
|
|
8
|
+
} else if (it.responseShape === 'body') {
|
|
9
|
+
returnTypeWrapped = 'Promise<' + it.returnType + '>';
|
|
10
|
+
responseMapper = '.then((resp) => resp.data)';
|
|
11
|
+
}
|
|
12
|
+
%>
|
|
1
13
|
<%~ it.jsDocs %>
|
|
2
14
|
|
|
3
15
|
<%= it.name %>(<% it.parameters.forEach((parameter) => { %>
|
|
4
16
|
<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,
|
|
5
17
|
<% }); %>
|
|
6
18
|
$config?: AxiosRequestConfig
|
|
7
|
-
):
|
|
19
|
+
): <%~ returnTypeWrapped %> {
|
|
8
20
|
const url = `<%= it.url %>`;
|
|
9
21
|
|
|
10
22
|
return axios.request<<%~ it.returnType %>>({
|
|
@@ -40,5 +52,5 @@ $config?: AxiosRequestConfig
|
|
|
40
52
|
},
|
|
41
53
|
<% } -%>
|
|
42
54
|
...$config,
|
|
43
|
-
})
|
|
55
|
+
})<%~ responseMapper %>;
|
|
44
56
|
},
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
<%
|
|
2
|
+
var parseMethod = it.responseContentType === 'binary' ? 'blob' : (it.responseContentType === 'text' ? 'text' : 'json');
|
|
3
|
+
var returnTypeWrapped = it.responseShape === 'full'
|
|
4
|
+
? 'Promise<' + it.apiResponseType + '<' + it.returnType + '>>'
|
|
5
|
+
: 'Promise<' + it.returnType + '>';
|
|
6
|
+
%>
|
|
1
7
|
<%~ it.jsDocs %>
|
|
2
8
|
|
|
3
9
|
<%= it.name %>(<% it.parameters.forEach((parameter) => { %>
|
|
4
10
|
<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,
|
|
5
11
|
<% }); %>
|
|
6
12
|
$config?: RequestInit
|
|
7
|
-
):
|
|
13
|
+
): <%~ returnTypeWrapped %> {
|
|
8
14
|
const url = `${defaults.baseUrl}<%= it.url %>?<%
|
|
9
15
|
if(it.query && it.query.length > 0) { %>${defaults.paramsSerializer({<%
|
|
10
16
|
it.query.forEach((parameter) => { %>
|
|
@@ -59,11 +65,13 @@ $config?: RequestInit
|
|
|
59
65
|
...$config,
|
|
60
66
|
})
|
|
61
67
|
<% } %>
|
|
62
|
-
<% if(it.
|
|
63
|
-
.then((response) =>
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
<% if (it.responseShape === 'full') { %>
|
|
69
|
+
.then(async (response) => ({
|
|
70
|
+
data: (await response.<%= parseMethod %>()) as <%~ it.returnType %>,
|
|
71
|
+
headers: response.headers,
|
|
72
|
+
status: response.status,
|
|
73
|
+
}));
|
|
66
74
|
<% } else { %>
|
|
67
|
-
.then((response) => response
|
|
75
|
+
.then((response) => response.<%= parseMethod %>() as Promise<<%~ it.returnType %>>);
|
|
68
76
|
<% } %>
|
|
69
77
|
},
|
|
@@ -3,3 +3,10 @@ import ky, { type Options as KyOptions } from 'ky';
|
|
|
3
3
|
export const http = ky.create({
|
|
4
4
|
prefix: '<%= it.baseUrl || '' %>',
|
|
5
5
|
});
|
|
6
|
+
<% if (it.responseShape === 'full') { %>
|
|
7
|
+
export interface <%= it.apiResponseType %><T> {
|
|
8
|
+
data: T;
|
|
9
|
+
headers: Headers;
|
|
10
|
+
status: number;
|
|
11
|
+
}
|
|
12
|
+
<% } %>
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
<%
|
|
2
|
+
var parseMethod = it.responseContentType === 'binary' ? 'blob' : (it.responseContentType === 'text' ? 'text' : 'json');
|
|
3
|
+
var returnTypeWrapped = it.responseShape === 'full'
|
|
4
|
+
? 'Promise<' + it.apiResponseType + '<' + it.returnType + '>>'
|
|
5
|
+
: 'Promise<' + it.returnType + '>';
|
|
6
|
+
%>
|
|
1
7
|
<%~ it.jsDocs %>
|
|
2
8
|
|
|
3
9
|
<%= it.name %>(<% it.parameters.forEach((parameter) => { %>
|
|
4
10
|
<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,
|
|
5
11
|
<% }); %>
|
|
6
12
|
$config?: KyOptions
|
|
7
|
-
):
|
|
13
|
+
): <%~ returnTypeWrapped %> {
|
|
8
14
|
const url = `<%= it.url.replace(/^\//, '') %>`;
|
|
9
15
|
|
|
10
16
|
return <%= it.httpAccessor %>.<%= it.method.toLowerCase() %>(url, {
|
|
@@ -40,7 +46,13 @@ $config?: KyOptions
|
|
|
40
46
|
},
|
|
41
47
|
<% } -%>
|
|
42
48
|
...$config,
|
|
43
|
-
<% if(it.
|
|
49
|
+
<% if (it.responseShape === 'full') { -%>
|
|
50
|
+
}).then(async (response) => ({
|
|
51
|
+
data: (await response.<%= parseMethod %>()) as <%~ it.returnType %>,
|
|
52
|
+
headers: response.headers,
|
|
53
|
+
status: response.status,
|
|
54
|
+
}));
|
|
55
|
+
<% } else if(it.responseContentType === 'binary') { -%>
|
|
44
56
|
}).blob() as Promise<<%~ it.returnType %>>;
|
|
45
57
|
<% } else if(it.responseContentType === 'text') { -%>
|
|
46
58
|
}).text() as Promise<<%~ it.returnType %>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IHttpService, IRequestShortcutConfig, IPromise } from 'angular';
|
|
1
|
+
import type { IHttpService, IRequestShortcutConfig, IPromise<% if (it.responseShape === 'full') { %>, IHttpHeadersGetter<% } %> } from 'angular';
|
|
2
2
|
|
|
3
3
|
abstract class BaseService {
|
|
4
4
|
constructor(protected readonly $http: IHttpService, public baseUrl: string) { }
|
|
@@ -88,6 +88,63 @@ abstract class BaseService {
|
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
<% if (it.responseShape === 'full') { %>
|
|
92
|
+
protected $getFull<T>(
|
|
93
|
+
url: string,
|
|
94
|
+
config?: IRequestShortcutConfig
|
|
95
|
+
): IPromise<<%= it.apiResponseType %><T>> {
|
|
96
|
+
return this.$http.get(this.baseUrl + url, config).then((response: any) => this.processFull<T>(response));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected $deleteFull<T>(
|
|
100
|
+
url: string,
|
|
101
|
+
config?: IRequestShortcutConfig
|
|
102
|
+
): IPromise<<%= it.apiResponseType %><T>> {
|
|
103
|
+
return this.$http.delete(this.baseUrl + url, config).then((response: any) => this.processFull<T>(response));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected $headFull<T>(
|
|
107
|
+
url: string,
|
|
108
|
+
config?: IRequestShortcutConfig
|
|
109
|
+
): IPromise<<%= it.apiResponseType %><T>> {
|
|
110
|
+
return this.$http.head(this.baseUrl + url, config).then((response: any) => this.processFull<T>(response));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
protected $jsonpFull<T>(
|
|
114
|
+
url: string,
|
|
115
|
+
config?: IRequestShortcutConfig
|
|
116
|
+
): IPromise<<%= it.apiResponseType %><T>> {
|
|
117
|
+
return this.$http.jsonp(this.baseUrl + url, config).then((response: any) => this.processFull<T>(response));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected $postFull<T>(
|
|
121
|
+
url: string,
|
|
122
|
+
data: any,
|
|
123
|
+
config?: IRequestShortcutConfig
|
|
124
|
+
): IPromise<<%= it.apiResponseType %><T>> {
|
|
125
|
+
return this.$http.post(this.baseUrl + url, data, config).then((response: any) => this.processFull<T>(response));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
protected $putFull<T>(
|
|
129
|
+
url: string,
|
|
130
|
+
data: any,
|
|
131
|
+
config?: IRequestShortcutConfig
|
|
132
|
+
): IPromise<<%= it.apiResponseType %><T>> {
|
|
133
|
+
return this.$http.put(this.baseUrl + url, data, config).then((response: any) => this.processFull<T>(response));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected $patchFull<T>(
|
|
137
|
+
url: string,
|
|
138
|
+
data: any,
|
|
139
|
+
config?: IRequestShortcutConfig
|
|
140
|
+
): IPromise<<%= it.apiResponseType %><T>> {
|
|
141
|
+
return this.$http.patch(this.baseUrl + url, data, config).then((response: any) => this.processFull<T>(response));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
protected processFull<T>(response: any): <%= it.apiResponseType %><T> {
|
|
145
|
+
return { data: response.data, headers: response.headers, status: response.status };
|
|
146
|
+
}
|
|
147
|
+
<% } %>
|
|
91
148
|
protected processSingle<T>(response: any): T {
|
|
92
149
|
var data = response.data;
|
|
93
150
|
var status = response.status;
|
|
@@ -110,4 +167,11 @@ abstract class BaseService {
|
|
|
110
167
|
}
|
|
111
168
|
}
|
|
112
169
|
}
|
|
170
|
+
<% if (it.responseShape === 'full') { %>
|
|
171
|
+
export interface <%= it.apiResponseType %><T> {
|
|
172
|
+
data: T;
|
|
173
|
+
headers: IHttpHeadersGetter;
|
|
174
|
+
status: number;
|
|
175
|
+
}
|
|
176
|
+
<% } %>
|
|
113
177
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,
|
|
5
5
|
<% }); %>
|
|
6
6
|
config?: IRequestShortcutConfig
|
|
7
|
-
): IPromise
|
|
7
|
+
): <%~ it.responseShape === 'full' ? 'IPromise<' + it.apiResponseType + '<' + it.returnType + '>>' : 'IPromise<' + it.returnType + '>' %> {
|
|
8
8
|
let url = `<%= it.url %>?`;
|
|
9
9
|
<% if(it.query && it.query.length > 0) { %>
|
|
10
10
|
<% it.query.forEach((parameter) => { %>
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
config = { ...config, headers: { ...config?.headers, '<%= parameter.originalName %>': <%= parameter.name %> } };
|
|
27
27
|
}
|
|
28
28
|
<% } %><% }); } %>
|
|
29
|
-
return this.$<%= it.method.toLowerCase() %>(
|
|
29
|
+
return this.$<%= it.method.toLowerCase() %><%= it.responseShape === 'full' ? 'Full' : '' %>(
|
|
30
30
|
url,
|
|
31
31
|
<% if(['POST', 'PUT', 'PATCH'].includes(it.method)) { %>
|
|
32
32
|
<% if(it.body) { %>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Observable } from "rxjs";
|
|
2
|
-
|
|
2
|
+
<% if (it.responseShape === 'full') { %>import { map } from "rxjs/operators";
|
|
3
|
+
<% } %>import { Injectable, InjectionToken, inject } from "@angular/core";
|
|
3
4
|
import { HttpClient, type HttpContext, type HttpHeaders, type HttpParams } from "@angular/common/http";
|
|
4
5
|
|
|
5
6
|
export const <%= (it.servicePrefix || 'API').toUpperCase() -%>_BASE_URL = new InjectionToken<string>("<%= (it.servicePrefix || 'API').toUpperCase() -%>_BASE_URL");
|
|
@@ -19,4 +20,11 @@ function paramsSerializer(params: any) {
|
|
|
19
20
|
arrayFormat: '<%= it.arrayFormat %>',
|
|
20
21
|
});
|
|
21
22
|
}
|
|
23
|
+
<% if (it.responseShape === 'full') { %>
|
|
24
|
+
export interface <%= it.apiResponseType %><T> {
|
|
25
|
+
data: T;
|
|
26
|
+
headers: HttpHeaders;
|
|
27
|
+
status: number;
|
|
28
|
+
}
|
|
29
|
+
<% } %>
|
|
22
30
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,
|
|
6
6
|
<% }); %>
|
|
7
7
|
config?: HttpConfig
|
|
8
|
-
): Observable
|
|
8
|
+
): <%~ it.responseShape === 'full' ? 'Observable<' + it.apiResponseType + '<' + it.returnType + '>>' : 'Observable<' + it.returnType + '>' %> {
|
|
9
9
|
<% if(it.query && it.query.length > 0) { -%>
|
|
10
10
|
const params = paramsSerializer({
|
|
11
11
|
<% it.query.forEach((parameter) => { -%>
|
|
@@ -29,15 +29,27 @@ config?: HttpConfig
|
|
|
29
29
|
}
|
|
30
30
|
<% } -%>
|
|
31
31
|
<% }); } -%>
|
|
32
|
+
<%
|
|
33
|
+
var fullMap = '.pipe(\n map((response) => ({ data: response.body as ' + it.returnType + ', headers: response.headers, status: response.status }))\n )';
|
|
34
|
+
var bodyArg = it.body
|
|
35
|
+
? (it.body.contentType === 'urlencoded' ? 'new URLSearchParams(' + it.body.name + ' as any)' : it.body.name)
|
|
36
|
+
: 'null';
|
|
37
|
+
-%>
|
|
38
|
+
<% if (it.responseShape === 'full') { -%>
|
|
39
|
+
<% if(it.method === 'GET') { -%>
|
|
40
|
+
return this.http.get<<%~ it.returnType %>>(this.baseUrl + url, { ...config, observe: 'response' as const })<%~ fullMap %>;
|
|
41
|
+
<% } else if(it.method === 'DELETE') { -%>
|
|
42
|
+
return this.http.delete<<%~ it.returnType %>>(this.baseUrl + url, { ...config, observe: 'response' as const })<%~ fullMap %>;
|
|
43
|
+
<% } else if(['POST', 'PUT', 'PATCH'].includes(it.method)) { -%>
|
|
44
|
+
return this.http.<%= it.method.toLowerCase() %><%~ '<' + it.returnType + '>' %>(this.baseUrl + url, <%~ bodyArg %>, { ...config, observe: 'response' as const })<%~ fullMap %>;
|
|
45
|
+
<% } -%>
|
|
46
|
+
<% } else { -%>
|
|
32
47
|
<% if(it.method === 'GET') { -%>
|
|
33
48
|
return this.http.get<<%~ it.returnType %>>(this.baseUrl + url, config);
|
|
34
49
|
<% } else if(it.method === 'DELETE') { -%>
|
|
35
50
|
return this.http.delete<<%~ it.returnType %>>(this.baseUrl + url, config);
|
|
36
|
-
<% } else if(['POST', 'PUT', 'PATCH'].includes(it.method)) {
|
|
37
|
-
const bodyArg = it.body
|
|
38
|
-
? (it.body.contentType === 'urlencoded' ? 'new URLSearchParams(' + it.body.name + ' as any)' : it.body.name)
|
|
39
|
-
: 'null';
|
|
40
|
-
-%>
|
|
51
|
+
<% } else if(['POST', 'PUT', 'PATCH'].includes(it.method)) { -%>
|
|
41
52
|
return this.http.<%= it.method.toLowerCase() %><%~ '<' + it.returnType + '>' %>(this.baseUrl + url, <%~ bodyArg %>, config);
|
|
42
|
-
<% }
|
|
53
|
+
<% } -%>
|
|
54
|
+
<% } -%>
|
|
43
55
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import useSWR, { type SWRConfiguration, type Key } from 'swr';
|
|
1
|
+
import useSWR, { type SWRConfiguration, type Key as SWRKey } from 'swr';
|
|
2
2
|
import useSWRMutation, { type SWRMutationConfiguration } from 'swr/mutation';
|
|
3
3
|
|
|
4
4
|
interface SwrConfig extends SWRConfiguration {
|
|
5
5
|
/* 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. */
|
|
6
|
-
key?:
|
|
6
|
+
key?: SWRKey;
|
|
7
7
|
}
|
package/templates/swr/client.ejs
CHANGED
|
@@ -21,6 +21,8 @@ export const <%= it.hooksCamelCaseName %> = {
|
|
|
21
21
|
httpClientName: it.camelCaseName,
|
|
22
22
|
httpConfigType: it.httpConfigType,
|
|
23
23
|
responseMapper: it.responseMapper,
|
|
24
|
+
responseShape: it.responseShape,
|
|
25
|
+
apiResponseType: it.apiResponseType,
|
|
24
26
|
}, it.safeOperation(operation, it.camelCaseName));
|
|
25
27
|
%>
|
|
26
28
|
<%~ include('swrOperation.ejs', swrOperation); %>
|
|
@@ -37,6 +39,8 @@ export const <%= it.hooksCamelCaseName %> = {
|
|
|
37
39
|
httpClientName: it.camelCaseName,
|
|
38
40
|
httpConfigType: it.httpConfigType,
|
|
39
41
|
responseMapper: it.responseMapper,
|
|
42
|
+
responseShape: it.responseShape,
|
|
43
|
+
apiResponseType: it.apiResponseType,
|
|
40
44
|
}, it.safeOperation(operation, it.camelCaseName));
|
|
41
45
|
%>
|
|
42
46
|
<%~ include('swrMutationOperation.ejs', swrMutationOperation); %>
|
|
@@ -15,6 +15,8 @@ export const <%= it.hooksCamelCaseName %> = {
|
|
|
15
15
|
httpClientName: httpClientPrefix,
|
|
16
16
|
httpConfigType: it.httpConfigType,
|
|
17
17
|
responseMapper: it.responseMapper,
|
|
18
|
+
responseShape: it.responseShape,
|
|
19
|
+
apiResponseType: it.prefixApiType(it.apiResponseType),
|
|
18
20
|
returnType: it.prefixApiType(safe.returnType),
|
|
19
21
|
parameters: safe.parameters.map(function(p) {
|
|
20
22
|
return Object.assign({}, p, { type: it.prefixApiType(p.type) });
|
|
@@ -36,6 +38,8 @@ export const <%= it.hooksCamelCaseName %> = {
|
|
|
36
38
|
httpClientName: httpClientPrefix,
|
|
37
39
|
httpConfigType: it.httpConfigType,
|
|
38
40
|
responseMapper: it.responseMapper,
|
|
41
|
+
responseShape: it.responseShape,
|
|
42
|
+
apiResponseType: it.prefixApiType(it.apiResponseType),
|
|
39
43
|
returnType: it.prefixApiType(safe.returnType),
|
|
40
44
|
parameters: safe.parameters.map(function(p) {
|
|
41
45
|
return Object.assign({}, p, { type: it.prefixApiType(p.type) });
|
|
@@ -18,13 +18,14 @@ var swrKey = it.url.replace(/\$\{(?:[^{}]|\{[^{}]*\})*\}/g, '*');
|
|
|
18
18
|
|
|
19
19
|
var docs = it.jsDocs ? it.jsDocs.replace(/^/gm, ' ') + '\n' : '';
|
|
20
20
|
var httpClientName = it.httpClientName || it.clientName;
|
|
21
|
+
var dataType = it.responseShape === 'full' ? it.apiResponseType + '<' + it.returnType + '>' : it.returnType;
|
|
21
22
|
%>
|
|
22
23
|
<%~ docs %>
|
|
23
24
|
<%= it.mutOpName %>(
|
|
24
|
-
$config?: SWRMutationConfiguration<<%~
|
|
25
|
+
$config?: SWRMutationConfiguration<<%~ dataType %>, Error, string, <%~ variablesType %>>,
|
|
25
26
|
$httpConfig?: <%= it.httpConfigType %>
|
|
26
27
|
) {
|
|
27
|
-
return useSWRMutation<<%~
|
|
28
|
+
return useSWRMutation<<%~ dataType %>, Error, string, <%~ variablesType %>>(
|
|
28
29
|
'<%= swrKey %>',
|
|
29
30
|
(_key: string, { arg }<%~ hasParams ? ': { arg: ' + variablesType + ' }' : '' %>) =>
|
|
30
31
|
<%= httpClientName %>Client.<%= it.name %>(<%= callArgs.join(', ') %><%= callArgs.length > 0 ? ', ' : '' %>$httpConfig)<%~ it.responseMapper %>,
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
<% var docs = it.jsDocs ? it.jsDocs.replace(/^/gm, ' ') + '\n' : ''; %>
|
|
2
2
|
<% var httpClientName = it.httpClientName || it.clientName; %>
|
|
3
|
+
<% var dataType = it.responseShape === 'full' ? it.apiResponseType + '<' + it.returnType + '>' : it.returnType; %>
|
|
3
4
|
<%~ docs %>
|
|
4
5
|
<%= it.swrOpName %>(
|
|
5
6
|
<% it.parameters.forEach((parameter) => { %> <%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>,
|
|
6
|
-
<% }); %> $config?: Omit<SwrConfig, 'key'> & { key?:
|
|
7
|
+
<% }); %> $config?: Omit<SwrConfig, 'key'> & { key?: SWRKey },
|
|
7
8
|
$httpConfig?: <%= it.httpConfigType %>
|
|
8
9
|
) {
|
|
9
10
|
const { key, ...config } = $config || {};
|
|
10
11
|
const cacheUrl = key ?? <%= it.clientName %>.queryKeys.<%= it.swrOpName.charAt(3).toLowerCase() + it.swrOpName.slice(4) %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>);
|
|
11
12
|
|
|
12
|
-
const { data, error, isLoading, isValidating, mutate } = useSWR<<%~
|
|
13
|
+
const { data, error, isLoading, isValidating, mutate } = useSWR<<%~ dataType %>>(
|
|
13
14
|
cacheUrl,
|
|
14
15
|
() => <%= httpClientName %>Client.<%= it.name %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>$httpConfig)<%~ it.responseMapper %>,
|
|
15
16
|
config
|
package/templates/tsq/client.ejs
CHANGED
|
@@ -22,6 +22,8 @@ export const <%= it.hooksCamelCaseName %> = {
|
|
|
22
22
|
httpClientName: it.camelCaseName,
|
|
23
23
|
httpConfigType: it.httpConfigType,
|
|
24
24
|
responseMapper: it.responseMapper,
|
|
25
|
+
responseShape: it.responseShape,
|
|
26
|
+
apiResponseType: it.apiResponseType,
|
|
25
27
|
}, it.safeOperation(operation, it.camelCaseName));
|
|
26
28
|
%>
|
|
27
29
|
<%~ include('queryOperation.ejs', queryOperation); %>
|
|
@@ -38,6 +40,8 @@ export const <%= it.hooksCamelCaseName %> = {
|
|
|
38
40
|
httpClientName: it.camelCaseName,
|
|
39
41
|
httpConfigType: it.httpConfigType,
|
|
40
42
|
responseMapper: it.responseMapper,
|
|
43
|
+
responseShape: it.responseShape,
|
|
44
|
+
apiResponseType: it.apiResponseType,
|
|
41
45
|
}, it.safeOperation(operation, it.camelCaseName));
|
|
42
46
|
%>
|
|
43
47
|
<%~ include('mutationOperation.ejs', mutationOperation); %>
|
|
@@ -16,6 +16,8 @@ export const <%= it.hooksCamelCaseName %> = {
|
|
|
16
16
|
httpClientName: httpClientPrefix,
|
|
17
17
|
httpConfigType: it.httpConfigType,
|
|
18
18
|
responseMapper: it.responseMapper,
|
|
19
|
+
responseShape: it.responseShape,
|
|
20
|
+
apiResponseType: it.prefixApiType(it.apiResponseType),
|
|
19
21
|
returnType: it.prefixApiType(safe.returnType),
|
|
20
22
|
parameters: safe.parameters.map(function(p) {
|
|
21
23
|
return Object.assign({}, p, { type: it.prefixApiType(p.type) });
|
|
@@ -37,6 +39,8 @@ export const <%= it.hooksCamelCaseName %> = {
|
|
|
37
39
|
httpClientName: httpClientPrefix,
|
|
38
40
|
httpConfigType: it.httpConfigType,
|
|
39
41
|
responseMapper: it.responseMapper,
|
|
42
|
+
responseShape: it.responseShape,
|
|
43
|
+
apiResponseType: it.prefixApiType(it.apiResponseType),
|
|
40
44
|
returnType: it.prefixApiType(safe.returnType),
|
|
41
45
|
parameters: safe.parameters.map(function(p) {
|
|
42
46
|
return Object.assign({}, p, { type: it.prefixApiType(p.type) });
|
|
@@ -19,13 +19,14 @@ var rawDocs = it.jsDocs
|
|
|
19
19
|
: '';
|
|
20
20
|
var docs = rawDocs ? rawDocs.replace(/^/gm, ' ') + '\n' : '';
|
|
21
21
|
var httpClientName = it.httpClientName || it.clientName;
|
|
22
|
+
var dataType = it.responseShape === 'full' ? it.apiResponseType + '<' + it.returnType + '>' : it.returnType;
|
|
22
23
|
%>
|
|
23
24
|
<%~ docs %>
|
|
24
|
-
<%= it.mutOpName %><TData = <%~
|
|
25
|
-
$config?: UseMutationOptions<<%~
|
|
25
|
+
<%= it.mutOpName %><TData = <%~ dataType %>, TError = Error>(
|
|
26
|
+
$config?: UseMutationOptions<<%~ dataType %>, TError, <%~ variablesType %>>,
|
|
26
27
|
$httpConfig?: <%= it.httpConfigType %>
|
|
27
28
|
) {
|
|
28
|
-
return useMutation<<%~
|
|
29
|
+
return useMutation<<%~ dataType %>, TError, <%~ variablesType %>>({
|
|
29
30
|
mutationFn: (<%= hasParams ? 'vars' : '' %>) => <%= httpClientName %>Client.<%= it.name %>(<%= callArgs.join(', ') %><%= callArgs.length > 0 ? ', ' : '' %>$httpConfig)<%~ it.responseMapper %>,
|
|
30
31
|
...$config
|
|
31
32
|
});
|
|
@@ -5,14 +5,15 @@ var rawDocs = it.jsDocs
|
|
|
5
5
|
: '';
|
|
6
6
|
var docs = rawDocs ? rawDocs.replace(/^/gm, ' ') + '\n' : '';
|
|
7
7
|
var httpClientName = it.httpClientName || it.clientName;
|
|
8
|
+
var dataType = it.responseShape === 'full' ? it.apiResponseType + '<' + it.returnType + '>' : it.returnType;
|
|
8
9
|
%>
|
|
9
10
|
<%~ docs %>
|
|
10
|
-
<%= it.rqOpName %><TData = <%~
|
|
11
|
+
<%= it.rqOpName %><TData = <%~ dataType %>, TError = Error>(
|
|
11
12
|
<% it.parameters.forEach((parameter) => { %> <%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %><%= parameter.optional ? (parameter.skippable ? ' | null' : ' | null | undefined') : '' %>,
|
|
12
|
-
<% }); %> $config?: Omit<UseQueryOptions<<%~
|
|
13
|
+
<% }); %> $config?: Omit<UseQueryOptions<<%~ dataType %>, TError, TData>, 'queryKey' | 'queryFn'>,
|
|
13
14
|
$httpConfig?: <%= it.httpConfigType %>
|
|
14
15
|
) {
|
|
15
|
-
return useQuery<<%~
|
|
16
|
+
return useQuery<<%~ dataType %>, TError, TData>({
|
|
16
17
|
queryKey: <%= it.clientName %>.queryKeys.<%= it.rqOpName.charAt(3).toLowerCase() + it.rqOpName.slice(4) %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>),
|
|
17
18
|
queryFn: () => <%= httpClientName %>Client.<%= it.name %>(<% it.parameters.forEach((parameter) => { %><%= parameter.name %>, <% }); %>$httpConfig)<%~ it.responseMapper %>,
|
|
18
19
|
...$config
|
|
@@ -8,4 +8,11 @@ export const http = xior.create({
|
|
|
8
8
|
arrayFormat: '<%= it.arrayFormat %>',
|
|
9
9
|
}),
|
|
10
10
|
});
|
|
11
|
+
<% if (it.responseShape === 'full') { %>
|
|
12
|
+
export interface <%= it.apiResponseType %><T> {
|
|
13
|
+
data: T;
|
|
14
|
+
headers: XiorResponse<T>['headers'];
|
|
15
|
+
status: number;
|
|
16
|
+
}
|
|
17
|
+
<% } %>
|
|
11
18
|
|
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
<%
|
|
2
|
+
var returnTypeWrapped = 'Promise<XiorResponse<' + it.returnType + '>>';
|
|
3
|
+
var responseMapper = '';
|
|
4
|
+
if (it.responseShape === 'full') {
|
|
5
|
+
// XiorResponse<T> already exposes { data, headers, status } and is therefore
|
|
6
|
+
// structurally assignable to APIResponse<T> — no remap needed.
|
|
7
|
+
returnTypeWrapped = 'Promise<' + it.apiResponseType + '<' + it.returnType + '>>';
|
|
8
|
+
} else if (it.responseShape === 'body') {
|
|
9
|
+
returnTypeWrapped = 'Promise<' + it.returnType + '>';
|
|
10
|
+
responseMapper = '.then((resp) => resp.data)';
|
|
11
|
+
}
|
|
12
|
+
%>
|
|
1
13
|
<%~ it.jsDocs %>
|
|
2
14
|
|
|
3
15
|
<%= it.name %>(<% it.parameters.forEach((parameter) => { %>
|
|
4
16
|
<%= parameter.name %><%= parameter.skippable ? '?' : '' %>: <%~ parameter.type %> <%= parameter.optional ? (parameter.skippable ? '| null' : '| null | undefined') : '' %>,
|
|
5
17
|
<% }); %>
|
|
6
18
|
$config?: XiorRequestConfig
|
|
7
|
-
):
|
|
19
|
+
): <%~ returnTypeWrapped %> {
|
|
8
20
|
const url = `<%= it.url %>`;
|
|
9
21
|
|
|
10
22
|
return http.request<<%~ it.returnType %>>({
|
|
@@ -40,5 +52,5 @@ $config?: XiorRequestConfig
|
|
|
40
52
|
},
|
|
41
53
|
<% } -%>
|
|
42
54
|
...$config,
|
|
43
|
-
})
|
|
55
|
+
})<%~ responseMapper %>;
|
|
44
56
|
},
|