@opra/client 0.25.5 → 0.26.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.
Files changed (64) hide show
  1. package/browser.js +410 -463
  2. package/cjs/client.js +49 -234
  3. package/cjs/constants.js +2 -2
  4. package/cjs/enums/http-observable-type.enum.js +10 -0
  5. package/cjs/enums/index.js +4 -0
  6. package/cjs/impl/collection-node.js +119 -0
  7. package/cjs/impl/http-request-observable.js +246 -0
  8. package/cjs/impl/http-request.js +28 -0
  9. package/cjs/impl/http-service-base.js +16 -0
  10. package/cjs/impl/singleton-node.js +62 -0
  11. package/cjs/index.js +12 -6
  12. package/cjs/interfaces/client-context.interface.js +2 -0
  13. package/cjs/interfaces/http-event.interface.js +35 -0
  14. package/cjs/interfaces/http-request-defaults.interface.js +2 -0
  15. package/cjs/interfaces/index.js +5 -0
  16. package/cjs/types.js +0 -42
  17. package/esm/client.js +51 -236
  18. package/esm/constants.js +1 -1
  19. package/esm/enums/http-observable-type.enum.js +7 -0
  20. package/esm/enums/index.js +1 -0
  21. package/esm/impl/collection-node.js +115 -0
  22. package/esm/impl/http-request-observable.js +242 -0
  23. package/esm/impl/http-request.js +24 -0
  24. package/esm/impl/http-service-base.js +12 -0
  25. package/esm/impl/singleton-node.js +58 -0
  26. package/esm/index.js +9 -6
  27. package/esm/interfaces/client-context.interface.js +1 -0
  28. package/esm/interfaces/http-event.interface.js +32 -0
  29. package/esm/interfaces/http-request-defaults.interface.js +1 -0
  30. package/esm/interfaces/index.js +2 -0
  31. package/esm/types.js +1 -41
  32. package/package.json +2 -2
  33. package/types/client.d.ts +26 -38
  34. package/types/constants.d.ts +1 -1
  35. package/types/enums/http-observable-type.enum.d.ts +6 -0
  36. package/types/enums/index.d.ts +1 -0
  37. package/types/impl/collection-node.d.ts +66 -0
  38. package/types/impl/http-request-observable.d.ts +45 -0
  39. package/types/{http-request.d.ts → impl/http-request.d.ts} +23 -27
  40. package/types/impl/http-service-base.d.ts +7 -0
  41. package/types/impl/singleton-node.d.ts +35 -0
  42. package/types/index.d.ts +9 -6
  43. package/types/interfaces/client-context.interface.d.ts +13 -0
  44. package/types/interfaces/http-event.interface.d.ts +88 -0
  45. package/types/interfaces/http-request-defaults.interface.d.ts +6 -0
  46. package/types/interfaces/index.d.ts +2 -0
  47. package/types/types.d.ts +4 -111
  48. package/cjs/collection-node.js +0 -124
  49. package/cjs/http-request-observable.js +0 -40
  50. package/cjs/http-request.js +0 -86
  51. package/cjs/http-service-base.js +0 -9
  52. package/cjs/singleton-node.js +0 -68
  53. package/esm/collection-node.js +0 -120
  54. package/esm/http-request-observable.js +0 -36
  55. package/esm/http-request.js +0 -82
  56. package/esm/http-service-base.js +0 -5
  57. package/esm/singleton-node.js +0 -64
  58. package/types/collection-node.d.ts +0 -117
  59. package/types/http-request-observable.d.ts +0 -23
  60. package/types/http-service-base.d.ts +0 -5
  61. package/types/singleton-node.d.ts +0 -65
  62. /package/cjs/{http-response.js → impl/http-response.js} +0 -0
  63. /package/esm/{http-response.js → impl/http-response.js} +0 -0
  64. /package/types/{http-response.d.ts → impl/http-response.d.ts} +0 -0
package/esm/client.js CHANGED
@@ -1,264 +1,79 @@
1
- import { lastValueFrom, Observable } from 'rxjs';
2
- import { isReadableStreamLike } from 'rxjs/internal/util/isReadableStreamLike';
3
- import { DocumentFactory, isBlob, OpraURL } from '@opra/common';
4
- import { ClientError } from './client-error.js';
5
- import { HttpCollectionNode } from './collection-node.js';
6
- import { FORMDATA_CONTENT_TYPE_PATTERN, JSON_CONTENT_TYPE_PATTERN, OPRA_JSON_CONTENT_TYPE_PATTERN, TEXT_CONTENT_TYPE_PATTERN } from './constants.js';
7
- import { HttpRequest } from './http-request.js';
8
- import { HttpResponse } from './http-response.js';
9
- import { HttpSingletonNode } from './singleton-node.js';
10
- import { HttpEventType, HttpObserveType, } from './types.js';
11
- const kAssets = Symbol('kAssets');
1
+ import { ApiDocumentFactory } from '@opra/common';
2
+ import { kContext } from './constants.js';
3
+ import { HttpCollectionNode } from './impl/collection-node.js';
4
+ import { HttpRequestObservable } from './impl/http-request-observable.js';
5
+ import { HttpResponse } from './impl/http-response.js';
6
+ import { HttpSingletonNode } from './impl/singleton-node.js';
12
7
  export class OpraHttpClient {
13
8
  constructor(serviceUrl, options) {
14
- Object.defineProperty(this, kAssets, {
9
+ const context = {
10
+ serviceUrl,
11
+ requestInterceptors: [...(options?.requestInterceptors || [])],
12
+ responseInterceptors: [...(options?.responseInterceptors || [])],
13
+ api: options?.api,
14
+ defaults: {
15
+ ...options?.defaults,
16
+ headers: options?.defaults?.headers instanceof Headers
17
+ ? options?.defaults?.headers : new Headers(options?.defaults?.headers),
18
+ params: options?.defaults?.params instanceof URLSearchParams
19
+ ? options?.defaults?.params : new URLSearchParams(options?.defaults?.params)
20
+ },
21
+ fetch,
22
+ createResponse: (init) => new HttpResponse(init)
23
+ };
24
+ Object.defineProperty(this, kContext, {
15
25
  enumerable: false,
16
- value: {
17
- serviceUrl,
18
- api: options?.api,
19
- requestInterceptors: options?.requestInterceptors || [],
20
- responseInterceptors: options?.responseInterceptors || []
21
- }
26
+ value: context
22
27
  });
23
- this.defaults = {
24
- ...options?.defaults,
25
- headers: options?.defaults?.headers instanceof Headers
26
- ? options?.defaults?.headers : new Headers(options?.defaults?.headers),
27
- params: options?.defaults?.params instanceof URLSearchParams
28
- ? options?.defaults?.params : new URLSearchParams(options?.defaults?.params)
29
- };
30
28
  }
31
29
  get serviceUrl() {
32
- return this[kAssets].serviceUrl;
30
+ return this[kContext].serviceUrl;
31
+ }
32
+ get api() {
33
+ return this[kContext].api;
34
+ }
35
+ get defaults() {
36
+ return this[kContext].defaults;
33
37
  }
34
38
  async getMetadata() {
35
- if (this[kAssets].api)
36
- return this[kAssets].api;
37
- let promise = this[kAssets].metadataPromise;
38
- if (promise) {
39
+ let promise = this._metadataPromise;
40
+ if (promise)
39
41
  return promise;
40
- }
41
- this[kAssets].metadataPromise = promise = lastValueFrom(this._sendRequest(HttpObserveType.Body, new HttpRequest({
42
+ const controller = new HttpRequestObservable(this, {
42
43
  method: 'GET',
43
44
  url: '',
44
- headers: new Headers({ 'accept': 'application/json' })
45
- })));
45
+ headers: { 'accept': 'application/json' }
46
+ });
47
+ this._metadataPromise = promise = controller.getData();
46
48
  return await promise
47
49
  .then(async (body) => {
48
50
  if (!body)
49
51
  throw new Error(`No response returned.`);
50
- const api = await DocumentFactory.createDocument(body);
51
- this[kAssets].api = api;
52
+ const api = await ApiDocumentFactory.createDocument(body);
53
+ this[kContext].api = api;
52
54
  return api;
53
55
  })
54
56
  .catch((e) => {
55
57
  e.message = 'Unable to fetch metadata from service url (' + this.serviceUrl + '). ' + e.message;
56
58
  throw e;
57
59
  })
58
- .finally(() => delete this[kAssets].metadataPromise);
60
+ .finally(() => delete this._metadataPromise);
59
61
  }
60
- // batch(requests: HttpRequestHost<any>[]): BatchRequest {
61
- // this._assertMetadata();
62
- // return new BatchRequest(request => this._sendRequest('response', request, requests);
63
- // }
64
- collection(resourceName) {
65
- // If name argument is a class, we extract name from the class
66
- if (typeof resourceName === 'function')
67
- resourceName = resourceName.name;
68
- const ctx = {
69
- client: this,
70
- sourceKind: 'Collection',
71
- endpoint: '',
72
- resource: resourceName,
73
- send: (observe, request) => this._sendRequest(observe, request, 'Collection', ctx.endpoint, ctx),
74
- // requestInterceptors: [
75
- // // Validate resource exists and is a collection resource
76
- // async () => {
77
- // const metadata = await this.getMetadata();
78
- // metadata.getCollection(ctx.sourceName);
79
- // }
80
- // ],
81
- responseInterceptors: []
82
- };
83
- return new HttpCollectionNode(ctx);
62
+ collection(path) {
63
+ return new HttpCollectionNode(this, path);
84
64
  }
85
- singleton(sourceName) {
86
- // If name argument is a class, we extract name from the class
87
- if (typeof sourceName === 'function')
88
- sourceName = sourceName.name;
89
- const ctx = {
90
- client: this,
91
- sourceKind: 'Singleton',
92
- endpoint: '',
93
- resource: sourceName,
94
- send: (observe, request) => this._sendRequest(observe, request, 'Singleton', ctx.endpoint, ctx),
95
- // requestInterceptors: [
96
- // // Validate resource exists and is a singleton resource
97
- // async () => {
98
- // const metadata = await this.getMetadata();
99
- // metadata.getSingleton(ctx.sourceName);
100
- // }
101
- // ],
102
- responseInterceptors: []
103
- };
104
- return new HttpSingletonNode(ctx);
65
+ singleton(path) {
66
+ return new HttpSingletonNode(this, path);
105
67
  }
106
- _sendRequest(observe, request, sourceKind, endpoint, ctx) {
107
- return new Observable(subscriber => {
108
- (async () => {
109
- request.inset(this.defaults);
110
- const url = new OpraURL(request.url, this.serviceUrl);
111
- let body;
112
- if (request.body) {
113
- let contentType;
114
- if (typeof request.body === 'string' || typeof request.body === 'number' || typeof request.body === 'boolean') {
115
- contentType = 'text/plain;charset=UTF-8"';
116
- body = String(request.body);
117
- request.headers.delete('Content-Size');
118
- delete request.duplex;
119
- }
120
- else if (isReadableStreamLike(request.body)) {
121
- contentType = 'application/octet-stream';
122
- body = request.body;
123
- request.duplex = 'half';
124
- }
125
- else if (Buffer.isBuffer(request.body)) {
126
- contentType = 'application/octet-stream';
127
- body = request.body;
128
- request.headers.set('Content-Size', String(request.body.length));
129
- delete request.duplex;
130
- }
131
- else if (isBlob(request.body)) {
132
- contentType = request.body.type || 'application/octet-stream';
133
- body = request.body;
134
- request.headers.set('Content-Size', String(request.body.length));
135
- delete request.duplex;
136
- }
137
- else {
138
- contentType = 'application/json';
139
- body = JSON.stringify(request.body);
140
- request.headers.delete('Content-Size');
141
- delete request.duplex;
142
- }
143
- if (!request.headers.has('Content-Type') && contentType)
144
- request.headers.set('Content-Type', contentType);
145
- request.body = body;
146
- }
147
- if (ctx) {
148
- const requestInterceptors = [
149
- ...this[kAssets].requestInterceptors,
150
- ...(ctx.requestInterceptors || [])
151
- ];
152
- for (const interceptor of requestInterceptors) {
153
- await interceptor(ctx, request);
154
- }
155
- }
156
- if (observe === HttpObserveType.Events)
157
- subscriber.next({
158
- observe,
159
- request,
160
- event: HttpEventType.Sent,
161
- });
162
- const response = await this._fetch(url.toString(), request);
163
- await this._handleResponse(observe, subscriber, request, response, sourceKind, endpoint, ctx);
164
- })().catch(error => subscriber.error(error));
68
+ action(path, params) {
69
+ const observable = new HttpRequestObservable(this, {
70
+ method: 'GET',
71
+ url: path
165
72
  });
166
- }
167
- _fetch(url, init = {}) {
168
- return fetch(url, init);
169
- }
170
- _createResponse(init) {
171
- return new HttpResponse(init);
172
- }
173
- async _handleResponse(observe, subscriber, request, fetchResponse, sourceKind, endpoint, ctx) {
174
- const headers = fetchResponse.headers;
175
- if (observe === HttpObserveType.Events) {
176
- const response = this._createResponse({
177
- url: fetchResponse.url,
178
- headers,
179
- status: fetchResponse.status,
180
- statusText: fetchResponse.statusText,
181
- hasBody: !!fetchResponse.body
182
- });
183
- subscriber.next({
184
- observe,
185
- request,
186
- event: HttpEventType.ResponseHeader,
187
- response
188
- });
189
- }
190
- let body;
191
- let totalCount;
192
- let affected;
193
- const contentType = headers.get('Content-Type') || '';
194
- if (fetchResponse.body) {
195
- if (JSON_CONTENT_TYPE_PATTERN.test(contentType)) {
196
- body = await fetchResponse.json();
197
- if (typeof body === 'string')
198
- body = JSON.parse(body);
199
- if (OPRA_JSON_CONTENT_TYPE_PATTERN.test(contentType)) {
200
- totalCount = body.totalCount;
201
- affected = body.affected;
202
- }
203
- }
204
- else if (TEXT_CONTENT_TYPE_PATTERN.test(headers.get('Content-Type') || ''))
205
- body = await fetchResponse.text();
206
- else if (FORMDATA_CONTENT_TYPE_PATTERN.test(headers.get('Content-Type') || ''))
207
- body = await fetchResponse.formData();
208
- else {
209
- const buf = await fetchResponse.arrayBuffer();
210
- if (buf.byteLength)
211
- body = buf;
212
- }
213
- }
214
- if (observe === HttpObserveType.Body && fetchResponse.status >= 400 && fetchResponse.status < 600) {
215
- subscriber.error(new ClientError({
216
- message: fetchResponse.status + ' ' + fetchResponse.statusText,
217
- status: fetchResponse.status,
218
- issues: body?.errors
219
- }));
220
- subscriber.complete();
221
- return;
222
- }
223
- const responseInit = {
224
- url: fetchResponse.url,
225
- headers,
226
- status: fetchResponse.status,
227
- statusText: fetchResponse.statusText,
228
- body
229
- };
230
- if (totalCount != null)
231
- responseInit.totalCount = totalCount;
232
- if (affected != null)
233
- responseInit.affected = affected;
234
- const response = this._createResponse(responseInit);
235
- if (ctx) {
236
- const responseInterceptors = [
237
- ...this[kAssets].responseInterceptors,
238
- ...(ctx.responseInterceptors || [])
239
- ];
240
- for (const interceptor of responseInterceptors) {
241
- await interceptor(ctx, observe, request);
242
- }
243
- }
244
- if (observe === HttpObserveType.Body) {
245
- if (OPRA_JSON_CONTENT_TYPE_PATTERN.test(contentType))
246
- subscriber.next(body.data);
247
- else
248
- subscriber.next(body);
249
- }
250
- else {
251
- if (observe === HttpObserveType.Events)
252
- subscriber.next({
253
- observe,
254
- request,
255
- event: HttpEventType.Response,
256
- response
257
- });
258
- else
259
- subscriber.next(response);
73
+ if (params) {
74
+ Object.keys(params).forEach(k => params[k] = String(params[k]));
75
+ observable.param(params);
260
76
  }
261
- subscriber.complete();
77
+ return observable;
262
78
  }
263
79
  }
264
- OpraHttpClient.kAssets = kAssets;
package/esm/constants.js CHANGED
@@ -2,5 +2,5 @@ export const OPRA_JSON_CONTENT_TYPE_PATTERN = /^application\/\bopra\+json\b/i;
2
2
  export const JSON_CONTENT_TYPE_PATTERN = /^application\/([\w-]+\+)?\bjson\b/i;
3
3
  export const TEXT_CONTENT_TYPE_PATTERN = /^text\/.*$/i;
4
4
  export const FORMDATA_CONTENT_TYPE_PATTERN = /^multipart\/\bform-data\b/i;
5
- export const kRequest = Symbol.for('kRequest');
5
+ export const kClient = Symbol.for('kClient');
6
6
  export const kContext = Symbol.for('kContext');
@@ -0,0 +1,7 @@
1
+ export var HttpObserveType;
2
+ (function (HttpObserveType) {
3
+ HttpObserveType["ResponseHeader"] = "response-header";
4
+ HttpObserveType["Response"] = "response";
5
+ HttpObserveType["Body"] = "body";
6
+ HttpObserveType["Events"] = "events";
7
+ })(HttpObserveType || (HttpObserveType = {}));
@@ -0,0 +1 @@
1
+ export * from './http-observable-type.enum.js';
@@ -0,0 +1,115 @@
1
+ import { toArrayDef } from 'putil-varhelpers';
2
+ import { OpraURL } from '@opra/common';
3
+ import { HttpRequestObservable } from './http-request-observable.js';
4
+ /**
5
+ * @class HttpCollectionNode
6
+ */
7
+ export class HttpCollectionNode {
8
+ constructor(client, path) {
9
+ this._client = client;
10
+ this._path = path;
11
+ }
12
+ create(data, options) {
13
+ const observable = new HttpRequestObservable(this._client, {
14
+ method: 'POST',
15
+ url: this._path,
16
+ body: data
17
+ });
18
+ if (options?.include)
19
+ observable.param('include', toArrayDef(options.include, []).join(','));
20
+ if (options?.pick)
21
+ observable.param('pick', toArrayDef(options.pick, []).join(','));
22
+ if (options?.omit)
23
+ observable.param('omit', toArrayDef(options.omit, []).join(','));
24
+ return observable;
25
+ }
26
+ delete(id) {
27
+ if (id == null || id === '')
28
+ throw new TypeError(`'id' argument must have a value`);
29
+ const url = new OpraURL();
30
+ url.join({ resource: this._path, key: id });
31
+ return new HttpRequestObservable(this._client, {
32
+ method: 'DELETE',
33
+ url
34
+ });
35
+ }
36
+ deleteMany(options) {
37
+ const observable = new HttpRequestObservable(this._client, {
38
+ method: 'DELETE',
39
+ url: this._path
40
+ });
41
+ if (options?.filter)
42
+ observable.param('filter', String(options.filter));
43
+ return observable;
44
+ }
45
+ get(id, options) {
46
+ if (id == null || id === '')
47
+ throw new TypeError(`'id' argument must have a value`);
48
+ const url = new OpraURL();
49
+ url.join({ resource: this._path, key: id });
50
+ const observable = new HttpRequestObservable(this._client, {
51
+ method: 'GET',
52
+ url
53
+ });
54
+ if (options?.include)
55
+ observable.param('include', toArrayDef(options.include, []).join(','));
56
+ if (options?.pick)
57
+ observable.param('pick', toArrayDef(options.pick, []).join(','));
58
+ if (options?.omit)
59
+ observable.param('omit', toArrayDef(options.omit, []).join(','));
60
+ return observable;
61
+ }
62
+ findMany(options) {
63
+ const observable = new HttpRequestObservable(this._client, {
64
+ method: 'GET',
65
+ url: this._path
66
+ });
67
+ if (options?.include)
68
+ observable.param('include', toArrayDef(options.include, []).join(','));
69
+ if (options?.pick)
70
+ observable.param('pick', toArrayDef(options.pick, []).join(','));
71
+ if (options?.omit)
72
+ observable.param('omit', toArrayDef(options.omit, []).join(','));
73
+ if (options?.sort)
74
+ observable.param('sort', toArrayDef(options.sort, []).join(','));
75
+ if (options?.filter)
76
+ observable.param('filter', String(options.filter));
77
+ if (options?.limit != null)
78
+ observable.param('limit', String(options.limit));
79
+ if (options?.skip != null)
80
+ observable.param('skip', String(options.skip));
81
+ if (options?.count != null)
82
+ observable.param('count', String(options.count));
83
+ if (options?.distinct != null)
84
+ observable.param('distinct', String(options.distinct));
85
+ return observable;
86
+ }
87
+ update(id, data, options) {
88
+ if (id == null)
89
+ throw new TypeError(`'id' argument must have a value`);
90
+ const url = new OpraURL();
91
+ url.join({ resource: this._path, key: id });
92
+ const observable = new HttpRequestObservable(this._client, {
93
+ method: 'PATCH',
94
+ url,
95
+ body: data
96
+ });
97
+ if (options?.include)
98
+ observable.param('include', toArrayDef(options.include, []).join(','));
99
+ if (options?.pick)
100
+ observable.param('pick', toArrayDef(options.pick, []).join(','));
101
+ if (options?.omit)
102
+ observable.param('omit', toArrayDef(options.omit, []).join(','));
103
+ return observable;
104
+ }
105
+ updateMany(data, options) {
106
+ const observable = new HttpRequestObservable(this._client, {
107
+ method: 'PATCH',
108
+ url: this._path,
109
+ body: data
110
+ });
111
+ if (options?.filter)
112
+ observable.param('filter', String(options.filter));
113
+ return observable;
114
+ }
115
+ }