ng2-rest 21.0.49 → 21.0.52
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/VERIFIED-BUILD-DATA.jsonc +3 -3
- package/browser/package.json +1 -1
- package/browser-prod/fesm2022/ng2-rest-browser-prod.mjs.map +1 -1
- package/browser-prod/package.json +1 -1
- package/lib/build-info._auto-generated_.d.ts +1 -1
- package/lib/build-info._auto-generated_.js +1 -1
- package/lib/package.json +1 -1
- package/lib-esm/app.d.ts +0 -0
- package/lib-esm/app.electron.d.ts +0 -0
- package/lib-esm/app.electron.js +82 -0
- package/lib-esm/app.electron.js.map +1 -0
- package/lib-esm/app.hosts.d.ts +3 -0
- package/lib-esm/app.hosts.js +48 -0
- package/lib-esm/app.hosts.js.map +1 -0
- package/lib-esm/app.js +318 -0
- package/lib-esm/app.js.map +1 -0
- package/lib-esm/app.vscode.d.ts +0 -0
- package/lib-esm/app.vscode.js +47 -0
- package/lib-esm/app.vscode.js.map +1 -0
- package/lib-esm/cli.d.ts +1 -0
- package/lib-esm/cli.js +4 -0
- package/lib-esm/cli.js.map +1 -0
- package/lib-esm/index.d.ts +1 -0
- package/lib-esm/index.js +2 -0
- package/lib-esm/index.js.map +1 -0
- package/lib-esm/lib/build-info._auto-generated_.d.ts +24 -0
- package/lib-esm/lib/build-info._auto-generated_.js +27 -0
- package/lib-esm/lib/build-info._auto-generated_.js.map +1 -0
- package/lib-esm/lib/env/env.angular-node-app.d.ts +66 -0
- package/lib-esm/lib/env/env.angular-node-app.js +135 -0
- package/lib-esm/lib/env/env.angular-node-app.js.map +1 -0
- package/lib-esm/lib/env/env.docs-webapp.d.ts +66 -0
- package/lib-esm/lib/env/env.docs-webapp.js +135 -0
- package/lib-esm/lib/env/env.docs-webapp.js.map +1 -0
- package/lib-esm/lib/env/env.electron-app.d.ts +66 -0
- package/lib-esm/lib/env/env.electron-app.js +135 -0
- package/lib-esm/lib/env/env.electron-app.js.map +1 -0
- package/lib-esm/lib/env/env.mobile-app.d.ts +66 -0
- package/lib-esm/lib/env/env.mobile-app.js +135 -0
- package/lib-esm/lib/env/env.mobile-app.js.map +1 -0
- package/lib-esm/lib/env/env.npm-lib-and-cli-tool.d.ts +66 -0
- package/lib-esm/lib/env/env.npm-lib-and-cli-tool.js +135 -0
- package/lib-esm/lib/env/env.npm-lib-and-cli-tool.js.map +1 -0
- package/lib-esm/lib/env/env.vscode-plugin.d.ts +66 -0
- package/lib-esm/lib/env/env.vscode-plugin.js +135 -0
- package/lib-esm/lib/env/env.vscode-plugin.js.map +1 -0
- package/lib-esm/lib/env/index.d.ts +6 -0
- package/lib-esm/lib/env/index.js +7 -0
- package/lib-esm/lib/env/index.js.map +1 -0
- package/lib-esm/lib/index._auto-generated_.d.ts +0 -0
- package/lib-esm/lib/index._auto-generated_.js +6 -0
- package/lib-esm/lib/index._auto-generated_.js.map +1 -0
- package/lib-esm/lib/index.d.ts +3 -0
- package/lib-esm/lib/index.js +4 -0
- package/lib-esm/lib/index.js.map +1 -0
- package/lib-esm/lib/new-mapping-decode.test.d.ts +1 -0
- package/lib-esm/lib/new-mapping-decode.test.js +119 -0
- package/lib-esm/lib/new-mapping-decode.test.js.map +1 -0
- package/lib-esm/lib/new-mapping-encode.test.d.ts +1 -0
- package/lib-esm/lib/new-mapping-encode.test.js +229 -0
- package/lib-esm/lib/new-mapping-encode.test.js.map +1 -0
- package/lib-esm/lib/new-mapping.d.ts +55 -0
- package/lib-esm/lib/new-mapping.js +314 -0
- package/lib-esm/lib/new-mapping.js.map +1 -0
- package/lib-esm/lib/new-mapping.test.d.ts +1 -0
- package/lib-esm/lib/new-mapping.test.js +7 -0
- package/lib-esm/lib/new-mapping.test.js.map +1 -0
- package/lib-esm/lib/ng2-rest.d.ts +301 -0
- package/lib-esm/lib/ng2-rest.js +998 -0
- package/lib-esm/lib/ng2-rest.js.map +1 -0
- package/lib-esm/lib/start-cli.d.ts +1 -0
- package/lib-esm/lib/start-cli.js +31 -0
- package/lib-esm/lib/start-cli.js.map +1 -0
- package/lib-prod/build-info._auto-generated_.d.ts +1 -1
- package/lib-prod/build-info._auto-generated_.js +1 -1
- package/lib-prod/new-mapping.js.map +1 -1
- package/lib-prod/ng2-rest.js.map +1 -1
- package/lib-prod/package.json +1 -1
- package/package.json +1 -1
- package/src.js +4 -20
- package/websql/package.json +1 -1
- package/websql-prod/fesm2022/ng2-rest-websql-prod.mjs.map +1 -1
- package/websql-prod/package.json +1 -1
|
@@ -0,0 +1,998 @@
|
|
|
1
|
+
//#region imports
|
|
2
|
+
import { URL } from 'url'; // @backend
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { JSON10 } from 'json10/lib';
|
|
5
|
+
import { Level, Log } from 'ng2-logger/lib';
|
|
6
|
+
import { firstValueFrom, from, Observable, shareReplay, Subject, } from 'rxjs';
|
|
7
|
+
import { CoreModels, Helpers, _ } from 'tnp-core/lib';
|
|
8
|
+
import { CLASS } from 'typescript-class-helpers/lib';
|
|
9
|
+
import { encodeMapping } from './new-mapping';
|
|
10
|
+
// import { Mapping } from './mapping';
|
|
11
|
+
//#endregion
|
|
12
|
+
const log = Log.create('ng2-rest', Level.WARN, Level.ERROR);
|
|
13
|
+
const listenErrorsSrc = new Subject();
|
|
14
|
+
//#region cookie
|
|
15
|
+
// TODO do it for nodejs
|
|
16
|
+
// import { CookieJar } from 'tough-cookie';
|
|
17
|
+
// const jar = new CookieJar();
|
|
18
|
+
// const rest = Resource.create('http://my-website.pl', 'api/v3/user/:userId', {
|
|
19
|
+
// cookieJar: jar,
|
|
20
|
+
// });
|
|
21
|
+
// await rest.model({ userId: 1 }).get(); // cookies persist across requests
|
|
22
|
+
// import type { CookieJar } from 'tough-cookie';
|
|
23
|
+
// interface ResourceOptions {
|
|
24
|
+
// // ...
|
|
25
|
+
// cookieJar?: CookieJar;
|
|
26
|
+
// }
|
|
27
|
+
// export class CookieJarInterceptor implements TaonAxiosClientInterceptor<any> {
|
|
28
|
+
// constructor(private readonly jar: CookieJar) {}
|
|
29
|
+
// intercept({ req, next }: TaonClientMiddlewareInterceptOptions<any>) {
|
|
30
|
+
// return new Observable<AxiosResponse<any>>(subscriber => {
|
|
31
|
+
// const url = req.url || '';
|
|
32
|
+
// // 1) attach Cookie header from jar
|
|
33
|
+
// this.jar.getCookieString(url, (err, cookieString) => {
|
|
34
|
+
// if (err) {
|
|
35
|
+
// subscriber.error(err);
|
|
36
|
+
// return;
|
|
37
|
+
// }
|
|
38
|
+
// if (cookieString) {
|
|
39
|
+
// req.headers = req.headers || {};
|
|
40
|
+
// // axios headers can be plain object or AxiosHeaders
|
|
41
|
+
// if (req.headers instanceof AxiosHeaders) {
|
|
42
|
+
// req.headers.set('Cookie', cookieString);
|
|
43
|
+
// } else {
|
|
44
|
+
// (req.headers as any)['Cookie'] = cookieString;
|
|
45
|
+
// }
|
|
46
|
+
// }
|
|
47
|
+
// // 2) proceed
|
|
48
|
+
// const sub = next.handle(req).subscribe({
|
|
49
|
+
// next: res => {
|
|
50
|
+
// // 3) store Set-Cookie back into jar
|
|
51
|
+
// const setCookie = (res.headers as any)?.['set-cookie'];
|
|
52
|
+
// const cookies: string[] =
|
|
53
|
+
// typeof setCookie === 'string'
|
|
54
|
+
// ? [setCookie]
|
|
55
|
+
// : Array.isArray(setCookie)
|
|
56
|
+
// ? setCookie
|
|
57
|
+
// : [];
|
|
58
|
+
// if (!cookies.length) {
|
|
59
|
+
// subscriber.next(res);
|
|
60
|
+
// return;
|
|
61
|
+
// }
|
|
62
|
+
// let pending = cookies.length;
|
|
63
|
+
// for (const c of cookies) {
|
|
64
|
+
// this.jar.setCookie(c, url, () => {
|
|
65
|
+
// pending--;
|
|
66
|
+
// if (pending === 0) {
|
|
67
|
+
// subscriber.next(res);
|
|
68
|
+
// }
|
|
69
|
+
// });
|
|
70
|
+
// }
|
|
71
|
+
// },
|
|
72
|
+
// error: e => subscriber.error(e),
|
|
73
|
+
// complete: () => subscriber.complete(),
|
|
74
|
+
// });
|
|
75
|
+
// return () => sub.unsubscribe();
|
|
76
|
+
// });
|
|
77
|
+
// });
|
|
78
|
+
// }
|
|
79
|
+
// }
|
|
80
|
+
export class Cookie {
|
|
81
|
+
static get Instance() {
|
|
82
|
+
if (!Cookie.__instance) {
|
|
83
|
+
Cookie.__instance = new Cookie();
|
|
84
|
+
}
|
|
85
|
+
return Cookie.__instance;
|
|
86
|
+
}
|
|
87
|
+
static __instance;
|
|
88
|
+
constructor() { }
|
|
89
|
+
read(name) {
|
|
90
|
+
if (typeof document === 'undefined')
|
|
91
|
+
return null;
|
|
92
|
+
var result = new RegExp('(?:^|; )' + encodeURIComponent(name) + '=([^;]*)').exec(document.cookie);
|
|
93
|
+
return result ? result[1] : null;
|
|
94
|
+
}
|
|
95
|
+
write(name, value, days) {
|
|
96
|
+
if (typeof document === 'undefined')
|
|
97
|
+
return null;
|
|
98
|
+
if (!days) {
|
|
99
|
+
days = 365 * 20;
|
|
100
|
+
}
|
|
101
|
+
var date = new Date();
|
|
102
|
+
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
103
|
+
var expires = '; expires=' + date.toUTCString();
|
|
104
|
+
document.cookie = name + '=' + value + expires + '; path=/';
|
|
105
|
+
}
|
|
106
|
+
remove(name) {
|
|
107
|
+
if (typeof document === 'undefined')
|
|
108
|
+
return null;
|
|
109
|
+
this.write(name, '', -1);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region get params url
|
|
114
|
+
/**
|
|
115
|
+
* Create query params string for url
|
|
116
|
+
*
|
|
117
|
+
* @export
|
|
118
|
+
* @param {UrlParams[]} params
|
|
119
|
+
* @returns {string}
|
|
120
|
+
*/
|
|
121
|
+
export function getParamsUrl(params, doNotSerialize = false) {
|
|
122
|
+
params = _.cloneDeep(params); // TODO refactor it
|
|
123
|
+
let urlparts = [];
|
|
124
|
+
if (!params)
|
|
125
|
+
return '';
|
|
126
|
+
if (!(params instanceof Array))
|
|
127
|
+
return '';
|
|
128
|
+
if (params.length === 0)
|
|
129
|
+
return '';
|
|
130
|
+
params.forEach(urlparam => {
|
|
131
|
+
if (JSON.stringify(urlparam) !== '{}') {
|
|
132
|
+
let parameters = [];
|
|
133
|
+
let paramObject = urlparam;
|
|
134
|
+
for (let p in paramObject) {
|
|
135
|
+
if (paramObject[p] === void 0)
|
|
136
|
+
delete paramObject[p];
|
|
137
|
+
if (paramObject.hasOwnProperty(p) &&
|
|
138
|
+
typeof p === 'string' &&
|
|
139
|
+
p !== 'regex' &&
|
|
140
|
+
!(paramObject[p] instanceof RegExp)) {
|
|
141
|
+
if (p.length > 0 && p[0] === '/') {
|
|
142
|
+
let newName = p.slice(1, p.length - 1);
|
|
143
|
+
urlparam[newName] = urlparam[p];
|
|
144
|
+
urlparam[p] = void 0;
|
|
145
|
+
p = newName;
|
|
146
|
+
}
|
|
147
|
+
if (p.length > 0 && p[p.length - 1] === '/') {
|
|
148
|
+
let newName = p.slice(0, p.length - 2);
|
|
149
|
+
urlparam[newName] = urlparam[p];
|
|
150
|
+
urlparam[p] = void 0;
|
|
151
|
+
p = newName;
|
|
152
|
+
}
|
|
153
|
+
let v = urlparam[p];
|
|
154
|
+
if (v instanceof Object) {
|
|
155
|
+
urlparam[p] = JSON.stringify(urlparam[p]);
|
|
156
|
+
}
|
|
157
|
+
urlparam[p] = doNotSerialize
|
|
158
|
+
? urlparam[p]
|
|
159
|
+
: encodeURIComponent(urlparam[p]);
|
|
160
|
+
if (urlparam.regex !== void 0 && urlparam.regex instanceof RegExp) {
|
|
161
|
+
if (!urlparam.regex.test(urlparam[p])) {
|
|
162
|
+
console.warn(`Data: ${urlparam[p]} incostistent with regex ${urlparam.regex.source}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
parameters.push(`${p}=${urlparam[p]}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
urlparts.push(parameters.join('&'));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
let join = urlparts.join().trim();
|
|
172
|
+
if (join.trim() === '')
|
|
173
|
+
return '';
|
|
174
|
+
return `?${urlparts.join('&')}`;
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region interpolate utils
|
|
178
|
+
export const regexisPath = /[^\..]+(\.[^\..]+)+/g;
|
|
179
|
+
/**
|
|
180
|
+
* Models like books/:id
|
|
181
|
+
*/
|
|
182
|
+
const cutUrlModel = (params, models, output) => {
|
|
183
|
+
if (models.length === 0)
|
|
184
|
+
return output.join('\/');
|
|
185
|
+
let m = models.pop();
|
|
186
|
+
let param = m.match(/:[a-zA-Z0-9\.]+/)[0].replace(':', '');
|
|
187
|
+
const paramIsPath = regexisPath.test(param);
|
|
188
|
+
// log.i('cut param', param)
|
|
189
|
+
let model = m.match(/[a-zA-Z0-9]+\//)[0].replace('\/', '');
|
|
190
|
+
if (params === void 0 ||
|
|
191
|
+
(paramIsPath
|
|
192
|
+
? _.get(params, param) === void 0
|
|
193
|
+
: params[param] === void 0) ||
|
|
194
|
+
param === 'undefined') {
|
|
195
|
+
output.length = 0;
|
|
196
|
+
output.unshift(model);
|
|
197
|
+
return cutUrlModel(params, models, output);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
if (paramIsPath) {
|
|
201
|
+
// log.i('param is path', param)
|
|
202
|
+
let mrep = m.replace(new RegExp(`:${param}`, 'g'), `${_.get(params, param)}`);
|
|
203
|
+
output.unshift(mrep);
|
|
204
|
+
return cutUrlModel(params, models, output);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
// log.i('param is normal', param)
|
|
208
|
+
let mrep = m.replace(new RegExp(`:${param}`, 'g'), `${params[param]}`);
|
|
209
|
+
output.unshift(mrep);
|
|
210
|
+
return cutUrlModel(params, models, output);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* let pattern = '/books/:bookid';
|
|
216
|
+
* let url = `/books/34`;
|
|
217
|
+
*/
|
|
218
|
+
export function interpolateParamsToUrl(params, url) {
|
|
219
|
+
const regexInt = /\[\[([^\..]+\.[^\..]+)+\]\]/g;
|
|
220
|
+
url = url
|
|
221
|
+
.split('/')
|
|
222
|
+
.map(p => {
|
|
223
|
+
// log.d('url parts', p)
|
|
224
|
+
let isParam = p.startsWith(':');
|
|
225
|
+
if (isParam) {
|
|
226
|
+
let part = p.slice(1);
|
|
227
|
+
// log.d('url param part', p)
|
|
228
|
+
if (regexInt.test(part)) {
|
|
229
|
+
// let level = (url.split('.').length - 1)
|
|
230
|
+
part = part.replace('[[', '');
|
|
231
|
+
part = part.replace(']]', '');
|
|
232
|
+
}
|
|
233
|
+
return `:${part}`;
|
|
234
|
+
}
|
|
235
|
+
return p;
|
|
236
|
+
})
|
|
237
|
+
.join('/');
|
|
238
|
+
// log.i('URL TO EXPOSE', url)
|
|
239
|
+
// log.i('params', params)
|
|
240
|
+
let slash = {
|
|
241
|
+
start: url.charAt(0) === '\/',
|
|
242
|
+
end: url.charAt(url.length - 1) === '\/',
|
|
243
|
+
};
|
|
244
|
+
let morePramsOnEnd = url.match(/(\/:[a-zA-Z0-9\.]+){2,10}/g);
|
|
245
|
+
if (morePramsOnEnd &&
|
|
246
|
+
Array.isArray(morePramsOnEnd) &&
|
|
247
|
+
morePramsOnEnd.length === 1) {
|
|
248
|
+
// log.i('morePramsOnEnd', morePramsOnEnd)
|
|
249
|
+
let m = morePramsOnEnd[0];
|
|
250
|
+
let match = m.match(/\/:[a-zA-Z0-9\.]+/g);
|
|
251
|
+
// log.i('match', match)
|
|
252
|
+
match.forEach(e => {
|
|
253
|
+
let c = e.replace('\/:', '');
|
|
254
|
+
// log.i('c', c)
|
|
255
|
+
if (regexisPath.test(c)) {
|
|
256
|
+
url = url.replace(e, `/${_.get(params, c)}`);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
url = url.replace(e, `/${params[c]}`);
|
|
260
|
+
}
|
|
261
|
+
// log.i('prog url', url)
|
|
262
|
+
});
|
|
263
|
+
return url;
|
|
264
|
+
}
|
|
265
|
+
let nestedParams = url.match(/[a-zA-Z0-9]+\/:[a-zA-Z0-9\.]+/g);
|
|
266
|
+
if (!nestedParams ||
|
|
267
|
+
(Array.isArray(nestedParams) && nestedParams.length === 0))
|
|
268
|
+
return url;
|
|
269
|
+
// check alone params
|
|
270
|
+
if (!slash.end)
|
|
271
|
+
url = `${url}/`;
|
|
272
|
+
let addUndefinedForAlone = !/:[a-zA-Z0-9\.]+\/$/g.test(url) && /[a-zA-Z0-9]+\/$/g.test(url);
|
|
273
|
+
let replace = (nestedParams.length > 1 ? nestedParams.join('\/') : nestedParams[0]) +
|
|
274
|
+
(addUndefinedForAlone ? '\/' + url.match(/[a-zA-Z0-9]+\/$/g)[0] : '\/');
|
|
275
|
+
let beginHref = url.replace(replace, '');
|
|
276
|
+
if (addUndefinedForAlone) {
|
|
277
|
+
url = url.replace(/\/$/g, '/:undefined');
|
|
278
|
+
nestedParams = url.match(/[a-zA-Z0-9]+\/:[a-zA-Z0-9\.]+/g);
|
|
279
|
+
url = cutUrlModel(params, nestedParams, []);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
url = cutUrlModel(params, nestedParams, []);
|
|
283
|
+
}
|
|
284
|
+
url = beginHref + url;
|
|
285
|
+
if (url.charAt(url.length - 1) !== '/' && slash.end)
|
|
286
|
+
url = `${url}/`;
|
|
287
|
+
if (url.charAt(0) !== '\/' && slash.start)
|
|
288
|
+
url = `/${url}`;
|
|
289
|
+
return url;
|
|
290
|
+
}
|
|
291
|
+
// Optional helper for passing around context (browser/client)
|
|
292
|
+
// === Backend handler (last in chain) ===
|
|
293
|
+
export class AxiosBackendHandler {
|
|
294
|
+
handle(req) {
|
|
295
|
+
// axios returns a Promise; wrap as Observable
|
|
296
|
+
return from(axios.request(req));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// === Chain builder (request: forward order, response: reverse order) ===
|
|
300
|
+
export const buildInterceptorChain = (globalInterceptors, backend) => {
|
|
301
|
+
return globalInterceptors.reduceRight((next, interceptor) => ({
|
|
302
|
+
handle: req => interceptor.intercept({ req, next }),
|
|
303
|
+
}), backend);
|
|
304
|
+
};
|
|
305
|
+
export class RestHeaders {
|
|
306
|
+
/** @internal header names are lower case */
|
|
307
|
+
_headers = new Map();
|
|
308
|
+
/** @internal map lower case names to actual names */
|
|
309
|
+
_normalizedNames = new Map();
|
|
310
|
+
static from(headers) {
|
|
311
|
+
return new RestHeaders(headers || {});
|
|
312
|
+
}
|
|
313
|
+
apply(headers) {
|
|
314
|
+
if (headers instanceof RestHeaders) {
|
|
315
|
+
headers.forEach((values, name) => {
|
|
316
|
+
values.forEach(value => this.set(name, value));
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
Object.keys(headers).forEach((name) => {
|
|
321
|
+
const values = (Array.isArray(headers[name]) ? headers[name] : [headers[name]]);
|
|
322
|
+
this.delete(name);
|
|
323
|
+
values.forEach(value => this.set(name, value));
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return this;
|
|
327
|
+
}
|
|
328
|
+
constructor(headers) {
|
|
329
|
+
this.apply(headers);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Returns a new RestHeaders instance from the given DOMString of Response RestHeaders
|
|
333
|
+
*/
|
|
334
|
+
static fromResponseHeaderString(headersString) {
|
|
335
|
+
const headers = new RestHeaders();
|
|
336
|
+
// console.log({
|
|
337
|
+
// headersString
|
|
338
|
+
// })
|
|
339
|
+
headersString.split('\n').forEach(line => {
|
|
340
|
+
const index = line.indexOf(':');
|
|
341
|
+
if (index > 0) {
|
|
342
|
+
const name = line.slice(0, index);
|
|
343
|
+
const value = line.slice(index + 1).trim();
|
|
344
|
+
headers.set(name, value);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
return headers;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Appends a header to existing list of header values for a given header name.
|
|
351
|
+
*/
|
|
352
|
+
append(name, value) {
|
|
353
|
+
const values = this.getAll(name);
|
|
354
|
+
if (values === null) {
|
|
355
|
+
this.set(name, value);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
values.push(value);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Deletes all header values for the given name.
|
|
363
|
+
*/
|
|
364
|
+
delete(name) {
|
|
365
|
+
const lcName = name.toLowerCase();
|
|
366
|
+
this._normalizedNames.delete(lcName);
|
|
367
|
+
this._headers.delete(lcName);
|
|
368
|
+
}
|
|
369
|
+
forEach(fn) {
|
|
370
|
+
this._headers.forEach((values, lcName) => fn(values, this._normalizedNames.get(lcName), this._headers));
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Returns first header that matches given name.
|
|
374
|
+
*/
|
|
375
|
+
get(name) {
|
|
376
|
+
const values = this.getAll(name);
|
|
377
|
+
if (values === null) {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
return values.length > 0 ? values[0] : null;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Checks for existence of header by given name.
|
|
384
|
+
*/
|
|
385
|
+
has(name) {
|
|
386
|
+
return this._headers.has(name.toLowerCase());
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Returns the names of the headers
|
|
390
|
+
*/
|
|
391
|
+
keys() {
|
|
392
|
+
return Array.from(this._normalizedNames.values());
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Sets or overrides header value for given name.
|
|
396
|
+
*/
|
|
397
|
+
set(name, value) {
|
|
398
|
+
if (Array.isArray(value)) {
|
|
399
|
+
if (value.length) {
|
|
400
|
+
this._headers.set(name.toLowerCase(), [value.join(',')]);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
this._headers.set(name.toLowerCase(), [value]);
|
|
405
|
+
}
|
|
406
|
+
this.mayBeSetNormalizedName(name);
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Returns values of all headers.
|
|
410
|
+
*/
|
|
411
|
+
values() {
|
|
412
|
+
return Array.from(this._headers.values());
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Returns string of all headers.
|
|
416
|
+
*/
|
|
417
|
+
// TODO(vicb): returns {[name: string]: string[]}
|
|
418
|
+
toJSON() {
|
|
419
|
+
const serialized = {};
|
|
420
|
+
if (!this._headers) {
|
|
421
|
+
// debugger
|
|
422
|
+
}
|
|
423
|
+
// console.log('serializing headers',this._headers)
|
|
424
|
+
this._headers.forEach((values, name) => {
|
|
425
|
+
const split = [];
|
|
426
|
+
values.forEach(v => split.push(...v.split(',')));
|
|
427
|
+
// console.log({
|
|
428
|
+
// values
|
|
429
|
+
// })
|
|
430
|
+
// values.forEach(v => split.push(...(v ? v : '').split(',')));
|
|
431
|
+
serialized[this._normalizedNames.get(name)] = split;
|
|
432
|
+
});
|
|
433
|
+
return serialized;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Returns list of header values for a given name.
|
|
437
|
+
*/
|
|
438
|
+
getAll(name) {
|
|
439
|
+
return this.has(name) ? this._headers.get(name.toLowerCase()) : null;
|
|
440
|
+
}
|
|
441
|
+
mayBeSetNormalizedName(name) {
|
|
442
|
+
const lcName = name.toLowerCase();
|
|
443
|
+
if (!this._normalizedNames.has(lcName)) {
|
|
444
|
+
this._normalizedNames.set(lcName, name);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region handle result source request options
|
|
450
|
+
class RestCommonHttpResponseWrapper {
|
|
451
|
+
}
|
|
452
|
+
export class RestResponseWrapper extends RestCommonHttpResponseWrapper {
|
|
453
|
+
}
|
|
454
|
+
export class RestErrorResponseWrapper extends RestCommonHttpResponseWrapper {
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region base body
|
|
458
|
+
export class BaseBody {
|
|
459
|
+
toJSON(data, opt) {
|
|
460
|
+
opt = opt || { isJSONArray: false };
|
|
461
|
+
let r = opt.isJSONArray ? [] : {};
|
|
462
|
+
if (typeof data === 'string') {
|
|
463
|
+
try {
|
|
464
|
+
let parsed = JSON.parse(data);
|
|
465
|
+
if (typeof parsed === 'string' && parsed.trim().startsWith('{')) {
|
|
466
|
+
parsed = JSON.parse(parsed);
|
|
467
|
+
}
|
|
468
|
+
if (opt.parsingError && parsed[CoreModels.TaonHttpErrorCustomProp]) {
|
|
469
|
+
return _.merge(new RestErrorResponseWrapper(), parsed);
|
|
470
|
+
}
|
|
471
|
+
return parsed;
|
|
472
|
+
}
|
|
473
|
+
catch (e) { }
|
|
474
|
+
}
|
|
475
|
+
else if (typeof data === 'object') {
|
|
476
|
+
return data;
|
|
477
|
+
}
|
|
478
|
+
return r;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
export class HttpBody extends BaseBody {
|
|
482
|
+
url;
|
|
483
|
+
method;
|
|
484
|
+
headers;
|
|
485
|
+
responseText;
|
|
486
|
+
options;
|
|
487
|
+
isArray;
|
|
488
|
+
constructor(url, method, headers, responseText, options, isArray) {
|
|
489
|
+
super();
|
|
490
|
+
this.url = url;
|
|
491
|
+
this.method = method;
|
|
492
|
+
this.headers = headers;
|
|
493
|
+
this.responseText = responseText;
|
|
494
|
+
this.options = options;
|
|
495
|
+
this.isArray = isArray;
|
|
496
|
+
}
|
|
497
|
+
get entity() {
|
|
498
|
+
if (typeof this.options.responseMapping?.entity === 'string') {
|
|
499
|
+
// const headerWithMapping = headers.get(entity);
|
|
500
|
+
// console.log('header key ',this.options.responseMapping?.entity);
|
|
501
|
+
// console.log(this.headers)
|
|
502
|
+
let entityJSON = this.headers?.getAll(this.options.responseMapping?.entity);
|
|
503
|
+
if (!!entityJSON) {
|
|
504
|
+
return JSON.parse(entityJSON.join());
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const entityAsResolvableFn = this.options?.responseMapping?.entity;
|
|
508
|
+
if (typeof entityAsResolvableFn === 'function') {
|
|
509
|
+
const mappingFromFunction = entityAsResolvableFn();
|
|
510
|
+
// console.log({ mappingFromFunction });
|
|
511
|
+
return mappingFromFunction;
|
|
512
|
+
}
|
|
513
|
+
return this.options.responseMapping?.entity;
|
|
514
|
+
}
|
|
515
|
+
get circular() {
|
|
516
|
+
if (typeof this.options.responseMapping?.circular === 'string') {
|
|
517
|
+
// const headerWithMapping = headers.get(circular);
|
|
518
|
+
let circuralJSON = this.headers?.getAll(this.options.responseMapping.circular);
|
|
519
|
+
if (!!circuralJSON) {
|
|
520
|
+
return JSON.parse(circuralJSON.join());
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return (this.options.responseMapping?.circular || []);
|
|
524
|
+
}
|
|
525
|
+
get blob() {
|
|
526
|
+
return this.responseText;
|
|
527
|
+
}
|
|
528
|
+
get booleanValue() {
|
|
529
|
+
if (!Helpers.isBlob(this.responseText)) {
|
|
530
|
+
return ['ok', 'true'].includes(this.responseText?.trim());
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
get numericValue() {
|
|
534
|
+
if (!Helpers.isBlob(this.responseText)) {
|
|
535
|
+
return Number(this.responseText?.trim());
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
get rawJson() {
|
|
539
|
+
if (!Helpers.isBlob(this.responseText)) {
|
|
540
|
+
let res = this.toJSON(this.responseText, { isJSONArray: this.isArray });
|
|
541
|
+
if (this.circular && Array.isArray(this.circular)) {
|
|
542
|
+
res = JSON10.parse(JSON.stringify(res), this.circular);
|
|
543
|
+
}
|
|
544
|
+
return res;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
get json() {
|
|
548
|
+
const isBlob = Helpers.isBlob(this.responseText);
|
|
549
|
+
if (isBlob) {
|
|
550
|
+
return void 0;
|
|
551
|
+
}
|
|
552
|
+
if (this.entity && typeof this.entity === 'object') {
|
|
553
|
+
const json = this.toJSON(this.responseText, {
|
|
554
|
+
isJSONArray: this.isArray,
|
|
555
|
+
});
|
|
556
|
+
// console.log({ entityMapping: this.entity })
|
|
557
|
+
const resEntityMapping = encodeMapping(json, this.entity, this.circular);
|
|
558
|
+
// console.log({ resEntityMapping })
|
|
559
|
+
this.displayWarningWhenNotUsingProperAPI(resEntityMapping);
|
|
560
|
+
return resEntityMapping;
|
|
561
|
+
}
|
|
562
|
+
let res = this.toJSON(this.responseText, { isJSONArray: this.isArray });
|
|
563
|
+
if (this.circular && Array.isArray(this.circular)) {
|
|
564
|
+
res = JSON10.parse(JSON.stringify(res), this.circular);
|
|
565
|
+
}
|
|
566
|
+
this.displayWarningWhenNotUsingProperAPI(res);
|
|
567
|
+
return res;
|
|
568
|
+
}
|
|
569
|
+
displayWarningWhenNotUsingProperAPI(res) {
|
|
570
|
+
if (!this.options.useArrayApiWarning) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (this.isArray) {
|
|
574
|
+
Helpers.warn(`[${this.method}: ${this.url}]
|
|
575
|
+
Your api response is object, but you are using .array api`);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
if (Array.isArray(res)) {
|
|
579
|
+
Helpers.warn(`[${this.method}: ${this.url}]
|
|
580
|
+
Your api response is array, but you are using object api instread .arrray.`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* undefined when blob
|
|
586
|
+
*/
|
|
587
|
+
get text() {
|
|
588
|
+
if (!Helpers.isBlob(this.responseText)) {
|
|
589
|
+
return this.responseText
|
|
590
|
+
.replace(/^\"/, '')
|
|
591
|
+
.replace(/\"$/, '');
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
export class ErrorBody extends BaseBody {
|
|
596
|
+
url;
|
|
597
|
+
data;
|
|
598
|
+
constructor(url, data) {
|
|
599
|
+
super();
|
|
600
|
+
this.url = url;
|
|
601
|
+
this.data = data;
|
|
602
|
+
}
|
|
603
|
+
get json() {
|
|
604
|
+
return this.toJSON(this.data, { parsingError: true });
|
|
605
|
+
}
|
|
606
|
+
get text() {
|
|
607
|
+
return this.data;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
export class BaseResponse {
|
|
611
|
+
responseText;
|
|
612
|
+
options;
|
|
613
|
+
statusCode;
|
|
614
|
+
headers;
|
|
615
|
+
isArray;
|
|
616
|
+
constructor(responseText, options, statusCode, headers, isArray) {
|
|
617
|
+
this.responseText = responseText;
|
|
618
|
+
this.options = options;
|
|
619
|
+
this.statusCode = statusCode;
|
|
620
|
+
this.headers = headers;
|
|
621
|
+
this.isArray = isArray;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
//#endregion
|
|
625
|
+
//#region http response
|
|
626
|
+
export class HttpResponse extends BaseResponse {
|
|
627
|
+
url;
|
|
628
|
+
method;
|
|
629
|
+
responseText;
|
|
630
|
+
headers;
|
|
631
|
+
statusCode;
|
|
632
|
+
options;
|
|
633
|
+
isArray;
|
|
634
|
+
body;
|
|
635
|
+
constructor(url, method, responseText, headers, statusCode, options, isArray) {
|
|
636
|
+
super(responseText, options, statusCode, headers, isArray);
|
|
637
|
+
this.url = url;
|
|
638
|
+
this.method = method;
|
|
639
|
+
this.responseText = responseText;
|
|
640
|
+
this.headers = headers;
|
|
641
|
+
this.statusCode = statusCode;
|
|
642
|
+
this.options = options;
|
|
643
|
+
this.isArray = isArray;
|
|
644
|
+
this.body = new HttpBody(url, method, headers, responseText, options, isArray);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
export class HttpResponseError extends BaseResponse {
|
|
648
|
+
url;
|
|
649
|
+
method;
|
|
650
|
+
responseText;
|
|
651
|
+
options;
|
|
652
|
+
headers;
|
|
653
|
+
statusCode;
|
|
654
|
+
isArray;
|
|
655
|
+
body;
|
|
656
|
+
constructor(url, method, responseText, options, headers, statusCode, isArray) {
|
|
657
|
+
super(responseText, options, statusCode, headers, isArray);
|
|
658
|
+
this.url = url;
|
|
659
|
+
this.method = method;
|
|
660
|
+
this.responseText = responseText;
|
|
661
|
+
this.options = options;
|
|
662
|
+
this.headers = headers;
|
|
663
|
+
this.statusCode = statusCode;
|
|
664
|
+
this.isArray = isArray;
|
|
665
|
+
this.body = new ErrorBody(url, responseText);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
//#region default headers
|
|
669
|
+
export const HeaderKeyContentType = 'Content-Type';
|
|
670
|
+
export const HeaderKeyAccept = 'Accept';
|
|
671
|
+
export const DEFAULT_HEADERS = {
|
|
672
|
+
// JSON (most APIs)
|
|
673
|
+
APPLICATION_JSON: RestHeaders.from({
|
|
674
|
+
[HeaderKeyContentType]: 'application/json',
|
|
675
|
+
[HeaderKeyAccept]: 'application/json',
|
|
676
|
+
}),
|
|
677
|
+
// JSON:API (you already have)
|
|
678
|
+
APPLICATION_VND_API_JSON: RestHeaders.from({
|
|
679
|
+
[HeaderKeyContentType]: 'application/vnd.api+json',
|
|
680
|
+
[HeaderKeyAccept]: 'application/vnd.api+json',
|
|
681
|
+
}),
|
|
682
|
+
// Form URL encoded (old APIs, OAuth token endpoints)
|
|
683
|
+
APPLICATION_X_WWW_FORM_URLENCODED: RestHeaders.from({
|
|
684
|
+
[HeaderKeyContentType]: 'application/x-www-form-urlencoded',
|
|
685
|
+
[HeaderKeyAccept]: 'application/json',
|
|
686
|
+
}),
|
|
687
|
+
// Multipart form-data (file uploads) — note: boundary will be set by FormData in Node
|
|
688
|
+
MULTIPART_FORM_DATA: RestHeaders.from({
|
|
689
|
+
[HeaderKeyContentType]: 'multipart/form-data',
|
|
690
|
+
[HeaderKeyAccept]: 'application/json',
|
|
691
|
+
}),
|
|
692
|
+
// Plain text request/response (health checks, simple endpoints)
|
|
693
|
+
TEXT_PLAIN: RestHeaders.from({
|
|
694
|
+
[HeaderKeyContentType]: 'text/plain; charset=utf-8',
|
|
695
|
+
[HeaderKeyAccept]: 'text/plain',
|
|
696
|
+
}),
|
|
697
|
+
// Accept anything (downloads, weird backends)
|
|
698
|
+
ACCEPT_ANY: RestHeaders.from({
|
|
699
|
+
[HeaderKeyAccept]: '*/*',
|
|
700
|
+
}),
|
|
701
|
+
// Binary download (still just headers; axios responseType controls actual handling)
|
|
702
|
+
OCTET_STREAM: RestHeaders.from({
|
|
703
|
+
[HeaderKeyAccept]: 'application/octet-stream',
|
|
704
|
+
}),
|
|
705
|
+
};
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region abstract resource reponse class
|
|
708
|
+
export class ResourceResponse {
|
|
709
|
+
httpMethodName;
|
|
710
|
+
urlOrigin;
|
|
711
|
+
urlPathname;
|
|
712
|
+
options;
|
|
713
|
+
body;
|
|
714
|
+
urlParams;
|
|
715
|
+
axiosOptions;
|
|
716
|
+
isArray;
|
|
717
|
+
headers;
|
|
718
|
+
globalInterceptors;
|
|
719
|
+
methodsInterceptors;
|
|
720
|
+
[Symbol.toStringTag] = 'Promise';
|
|
721
|
+
_promise;
|
|
722
|
+
_promiseAbort;
|
|
723
|
+
_observable;
|
|
724
|
+
//#region constructor
|
|
725
|
+
constructor(httpMethodName, urlOrigin, urlPathname, options, body, urlParams, axiosOptions, isArray, headers, globalInterceptors, methodsInterceptors) {
|
|
726
|
+
this.httpMethodName = httpMethodName;
|
|
727
|
+
this.urlOrigin = urlOrigin;
|
|
728
|
+
this.urlPathname = urlPathname;
|
|
729
|
+
this.options = options;
|
|
730
|
+
this.body = body;
|
|
731
|
+
this.urlParams = urlParams;
|
|
732
|
+
this.axiosOptions = axiosOptions;
|
|
733
|
+
this.isArray = isArray;
|
|
734
|
+
this.headers = headers;
|
|
735
|
+
this.globalInterceptors = globalInterceptors;
|
|
736
|
+
this.methodsInterceptors = methodsInterceptors;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* ✅ Explicit cancel (useful for "promise style")
|
|
740
|
+
*/
|
|
741
|
+
cancel(reason) {
|
|
742
|
+
this._promiseAbort?.abort(reason);
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Promise API (cannot be auto-cancelled by consumer, so we expose cancel())
|
|
746
|
+
*/
|
|
747
|
+
get promise() {
|
|
748
|
+
if (!this._promise) {
|
|
749
|
+
this._promiseAbort = new AbortController();
|
|
750
|
+
this._promise = this.makeRequest(this._promiseAbort.signal);
|
|
751
|
+
}
|
|
752
|
+
return this._promise;
|
|
753
|
+
}
|
|
754
|
+
then(onfulfilled, onrejected) {
|
|
755
|
+
return this.promise.then(onfulfilled, onrejected);
|
|
756
|
+
}
|
|
757
|
+
catch(onrejected) {
|
|
758
|
+
return this.promise.catch(onrejected);
|
|
759
|
+
}
|
|
760
|
+
finally(onfinally) {
|
|
761
|
+
return this.promise.finally(onfinally);
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* ✅ Observable owns AbortController:
|
|
765
|
+
* - subscribe starts request
|
|
766
|
+
* - unsubscribe aborts request
|
|
767
|
+
* - shareReplay shares the same in-flight request among subscribers
|
|
768
|
+
*/
|
|
769
|
+
get observable() {
|
|
770
|
+
if (!this._observable) {
|
|
771
|
+
this._observable = new Observable(subscriber => {
|
|
772
|
+
const ac = new AbortController();
|
|
773
|
+
this.makeRequest(ac.signal)
|
|
774
|
+
.then(res => {
|
|
775
|
+
if (res instanceof HttpResponseError) {
|
|
776
|
+
subscriber.error(res);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
subscriber.next(res);
|
|
780
|
+
subscriber.complete();
|
|
781
|
+
})
|
|
782
|
+
.catch(err => subscriber.error(err));
|
|
783
|
+
return () => ac.abort('rxjs-unsubscribe');
|
|
784
|
+
}).pipe(shareReplay({ bufferSize: 1, refCount: true }));
|
|
785
|
+
}
|
|
786
|
+
return this._observable;
|
|
787
|
+
}
|
|
788
|
+
// -------------------------
|
|
789
|
+
// Internals
|
|
790
|
+
// -------------------------
|
|
791
|
+
creatUrl(params, doNotSerializeParams = false) {
|
|
792
|
+
const origin = (this.urlOrigin || '').replace(/\/+$/, '');
|
|
793
|
+
const path = (this.urlPathname || '').replace(/^\/+/, '');
|
|
794
|
+
const endpoint = `${origin}/${path}`;
|
|
795
|
+
return `${endpoint}${getParamsUrl(params, doNotSerializeParams)}`;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
//#endregion
|
|
799
|
+
//#region resource reponse http strategy
|
|
800
|
+
class ResourceResponseHttp extends ResourceResponse {
|
|
801
|
+
async makeRequest(abortSignal) {
|
|
802
|
+
const url = this.creatUrl(this.urlParams, !!this.axiosOptions?.doNotSerializeParams);
|
|
803
|
+
const method = this.httpMethodName;
|
|
804
|
+
log.d(`Requesting ${method} ${url}`);
|
|
805
|
+
const isFormData = CLASS.getNameFromObject(this.body) === 'FormData';
|
|
806
|
+
const formData = isFormData ? this.body : void 0;
|
|
807
|
+
//#region @backend
|
|
808
|
+
if (formData) {
|
|
809
|
+
const headersForm = formData.getHeaders();
|
|
810
|
+
headersForm['Content-Length'] = formData.getLengthSync();
|
|
811
|
+
for (const [key, value] of Object.entries(headersForm)) {
|
|
812
|
+
this.headers.set(key, value?.toString());
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
//#endregion
|
|
816
|
+
const responseType = this.headers.get('responsetypeaxios')?.toString() || 'text';
|
|
817
|
+
const headersObj = Object.fromEntries(Object.entries(this.headers.toJSON()).map(([k, v]) => [
|
|
818
|
+
k,
|
|
819
|
+
Array.isArray(v) ? v.join(',') : v,
|
|
820
|
+
]));
|
|
821
|
+
const axiosConfig = {
|
|
822
|
+
url,
|
|
823
|
+
method,
|
|
824
|
+
data: this.body,
|
|
825
|
+
responseType,
|
|
826
|
+
headers: headersObj,
|
|
827
|
+
signal: abortSignal, // ✅ this is the key
|
|
828
|
+
...this.axiosOptions,
|
|
829
|
+
};
|
|
830
|
+
if (isFormData) {
|
|
831
|
+
axiosConfig.maxBodyLength = Infinity;
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
const uri = new URL(url);
|
|
835
|
+
const backend = new AxiosBackendHandler();
|
|
836
|
+
const globalInterceptors = Array.from(this.globalInterceptors.values());
|
|
837
|
+
const methodInterceptors = Array.from(this.methodsInterceptors.entries())
|
|
838
|
+
.filter(([key]) => key.endsWith(`-${method?.toUpperCase()}-${uri.pathname}`))
|
|
839
|
+
.map(([_, interceptor]) => interceptor);
|
|
840
|
+
const handler = buildInterceptorChain([...globalInterceptors, ...methodInterceptors], backend);
|
|
841
|
+
const response = await firstValueFrom(handler.handle(axiosConfig));
|
|
842
|
+
return new HttpResponse(url, method, response.data, RestHeaders.from(response.headers), response.status, this.options, this.isArray);
|
|
843
|
+
}
|
|
844
|
+
catch (catchedError) {
|
|
845
|
+
// ✅ treat cancellation separately (nice UX)
|
|
846
|
+
if (catchedError?.code === 'ERR_CANCELED' ||
|
|
847
|
+
catchedError?.name === 'CanceledError') {
|
|
848
|
+
throw new HttpResponseError(url, method, JSON.stringify({ message: 'Request canceled' }), this.options, RestHeaders.from(), 0, this.isArray);
|
|
849
|
+
}
|
|
850
|
+
//#region handle global error listener for notificaitons
|
|
851
|
+
if (typeof catchedError === 'object' &&
|
|
852
|
+
catchedError.response &&
|
|
853
|
+
catchedError.response.data) {
|
|
854
|
+
const err = catchedError.response.data;
|
|
855
|
+
const msg = catchedError.response.data.message || '';
|
|
856
|
+
// console.log({
|
|
857
|
+
// 'err.stack': err?.stack
|
|
858
|
+
// })
|
|
859
|
+
let stack = (err.stack || '').split('\n');
|
|
860
|
+
listenErrorsSrc.next({
|
|
861
|
+
msg,
|
|
862
|
+
stack,
|
|
863
|
+
data: catchedError.response.data,
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
//#endregion
|
|
867
|
+
const status = catchedError?.response?.status ?? 0; // ✅ FIX: you used "status" before defining it
|
|
868
|
+
const data = catchedError?.response?.data ?? catchedError?.message ?? catchedError;
|
|
869
|
+
const responseText = typeof data === 'string' ? data : JSON.stringify(data);
|
|
870
|
+
throw new HttpResponseError(url, method, responseText, this.options, RestHeaders.from(catchedError?.response?.headers), status, this.isArray);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
[];
|
|
875
|
+
//#endregion
|
|
876
|
+
//#region resource namespace
|
|
877
|
+
export var Resource;
|
|
878
|
+
(function (Resource) {
|
|
879
|
+
Resource.globalInterceptors = new Map();
|
|
880
|
+
Resource.methodsInterceptors = new Map();
|
|
881
|
+
Resource.listenErrors = listenErrorsSrc.asObservable();
|
|
882
|
+
Resource.Cookies = Cookie.Instance;
|
|
883
|
+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
|
884
|
+
function create(originUrl, pathnameModel, resourceOptions) {
|
|
885
|
+
return {
|
|
886
|
+
model: (interpolateParams, overrideOptions) => {
|
|
887
|
+
const methods = (isArray = false) => {
|
|
888
|
+
const methodsObj = {};
|
|
889
|
+
for (const methodName of CoreModels.HttpMethodArr) {
|
|
890
|
+
methodsObj[methodName] = (body, urlParams, axiosOptions) => {
|
|
891
|
+
let localPathname = pathnameModel;
|
|
892
|
+
if (!localPathname.startsWith('/')) {
|
|
893
|
+
localPathname = `/${localPathname}`;
|
|
894
|
+
}
|
|
895
|
+
let localOriginUrl = originUrl;
|
|
896
|
+
if (localOriginUrl.endsWith('/')) {
|
|
897
|
+
localOriginUrl = localOriginUrl.replace(/\/$/, '');
|
|
898
|
+
}
|
|
899
|
+
let localUrl = new URL(`${localOriginUrl}${localPathname}`);
|
|
900
|
+
//#region validate pathname model
|
|
901
|
+
const badRestRegEX = new RegExp('((\/:)[a-z]+)+', 'g');
|
|
902
|
+
const matchArr = localPathname.match(badRestRegEX) || [];
|
|
903
|
+
const badModelsNextToEachOther = matchArr.join();
|
|
904
|
+
const atleas2DoubleDots = (badModelsNextToEachOther.match(new RegExp(':', 'g')) || [])
|
|
905
|
+
.length >= 2;
|
|
906
|
+
if (atleas2DoubleDots &&
|
|
907
|
+
localPathname.search(badModelsNextToEachOther) !== -1) {
|
|
908
|
+
throw new Error(`
|
|
909
|
+
|
|
910
|
+
Bad rest model: ${localPathname}
|
|
911
|
+
|
|
912
|
+
Do not create rest models like this: /book/author/:bookid/:authorid
|
|
913
|
+
Instead use nested approach: /book/:bookid/author/:authorid
|
|
914
|
+
`);
|
|
915
|
+
}
|
|
916
|
+
//#endregion
|
|
917
|
+
let options = resourceOptions;
|
|
918
|
+
options = options || {};
|
|
919
|
+
options.responseMapping = options.responseMapping || {};
|
|
920
|
+
options = {
|
|
921
|
+
...options,
|
|
922
|
+
...(_.isFunction(overrideOptions)
|
|
923
|
+
? overrideOptions(options)
|
|
924
|
+
: overrideOptions || {}),
|
|
925
|
+
};
|
|
926
|
+
if (interpolateParams) {
|
|
927
|
+
// console.log({ interpolateParams });
|
|
928
|
+
// interpolate args
|
|
929
|
+
let pathNameInterpolated = interpolateParamsToUrl(interpolateParams, localUrl.pathname);
|
|
930
|
+
// console.log(
|
|
931
|
+
// `interpolated ${pathNameInterpolated}, url ${url.toString()}`,
|
|
932
|
+
// );
|
|
933
|
+
localUrl = new URL(`${localUrl.origin}/${pathNameInterpolated}`);
|
|
934
|
+
}
|
|
935
|
+
const headers = RestHeaders.from(options.headers);
|
|
936
|
+
options.strategy = options.strategy || 'http';
|
|
937
|
+
options.defaultHeadersProfile =
|
|
938
|
+
options.defaultHeadersProfile || 'APPLICATION_JSON';
|
|
939
|
+
if (options.defaultHeadersProfile) {
|
|
940
|
+
DEFAULT_HEADERS[options.defaultHeadersProfile].forEach((values, name) => {
|
|
941
|
+
values.forEach(headerValue => headers.set(name, headerValue));
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
if (options.strategy === 'http') {
|
|
945
|
+
return new ResourceResponseHttp(methodName, localUrl.origin, localUrl.pathname, options, body, urlParams, axiosOptions, isArray, headers, Resource.globalInterceptors, Resource.methodsInterceptors);
|
|
946
|
+
}
|
|
947
|
+
else if (options.strategy === 'ipc-electron') {
|
|
948
|
+
// TODO later
|
|
949
|
+
}
|
|
950
|
+
else if (options.strategy === 'js-mock') {
|
|
951
|
+
// TODO later
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
return methodsObj;
|
|
956
|
+
};
|
|
957
|
+
const methodsRes = methods();
|
|
958
|
+
const methodsArrayRes = methods(true);
|
|
959
|
+
const res = {
|
|
960
|
+
get array() {
|
|
961
|
+
return methodsArrayRes;
|
|
962
|
+
},
|
|
963
|
+
...methodsRes,
|
|
964
|
+
};
|
|
965
|
+
return res;
|
|
966
|
+
},
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
Resource.create = create;
|
|
970
|
+
})(Resource || (Resource = {}));
|
|
971
|
+
//#endregion
|
|
972
|
+
//#region exmaple usage
|
|
973
|
+
/**
|
|
974
|
+
* EXample useage
|
|
975
|
+
*/
|
|
976
|
+
class ExampleBook {
|
|
977
|
+
title;
|
|
978
|
+
}
|
|
979
|
+
async function example() {
|
|
980
|
+
const rest = Resource.create('http://my-website.pl', 'api/v3/user/:userId', {
|
|
981
|
+
responseMapping: {
|
|
982
|
+
entity: () => ({ '': ExampleBook }),
|
|
983
|
+
},
|
|
984
|
+
});
|
|
985
|
+
const response = await rest.model({ userId: 1 }).get();
|
|
986
|
+
response; // type of response should be HttpResponse
|
|
987
|
+
const responseObservable = rest
|
|
988
|
+
.model({ userId: 1 })
|
|
989
|
+
.array.post([new ExampleBook()], [{ 'location-id': 123 }]).observable;
|
|
990
|
+
const responseObservableOnlyONe = rest
|
|
991
|
+
.model({ userId: 1 })
|
|
992
|
+
.post(new ExampleBook(), [{ 'location-id': 123 }]).observable;
|
|
993
|
+
responseObservable.subscribe(data => {
|
|
994
|
+
data; // HttpResponse<ExampleBook>
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
//#endregion
|
|
998
|
+
//# sourceMappingURL=ng2-rest.js.map
|