@hkdigital/lib-core 0.4.55 → 0.4.56
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/dist/network/README.md
CHANGED
|
@@ -27,6 +27,85 @@ const result = await jsonRequest('https://api.example.com/users', {
|
|
|
27
27
|
});
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
### HTTP Methods
|
|
31
|
+
|
|
32
|
+
Convenient methods for common HTTP operations:
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import {
|
|
36
|
+
httpGet,
|
|
37
|
+
httpPost,
|
|
38
|
+
httpPut,
|
|
39
|
+
httpPatch,
|
|
40
|
+
httpDelete
|
|
41
|
+
} from '$lib/network/http.js';
|
|
42
|
+
|
|
43
|
+
// GET request
|
|
44
|
+
const response = await httpGet({ url: '/api/users' });
|
|
45
|
+
const data = await response.text();
|
|
46
|
+
|
|
47
|
+
// POST request with form data
|
|
48
|
+
const formResponse = await httpPost({
|
|
49
|
+
url: '/api/users',
|
|
50
|
+
body: new FormData(form)
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// PUT request with custom headers
|
|
54
|
+
const putResponse = await httpPut({
|
|
55
|
+
url: '/api/users/123',
|
|
56
|
+
body: 'raw data',
|
|
57
|
+
headers: { 'Content-Type': 'text/plain' }
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// PATCH request
|
|
61
|
+
const patchResponse = await httpPatch({
|
|
62
|
+
url: '/api/users/123',
|
|
63
|
+
body: JSON.stringify({ status: 'active' }),
|
|
64
|
+
headers: { 'Content-Type': 'application/json' }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// DELETE request
|
|
68
|
+
const deleteResponse = await httpDelete({ url: '/api/users/123' });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### JSON HTTP Methods
|
|
72
|
+
|
|
73
|
+
Convenient methods for common JSON API operations:
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import {
|
|
77
|
+
jsonGet,
|
|
78
|
+
jsonPost,
|
|
79
|
+
jsonPut,
|
|
80
|
+
jsonPatch,
|
|
81
|
+
jsonDelete
|
|
82
|
+
} from '$lib/network/http.js';
|
|
83
|
+
|
|
84
|
+
// GET request for JSON data
|
|
85
|
+
const users = await jsonGet({ url: '/api/users' });
|
|
86
|
+
|
|
87
|
+
// POST request with JSON body
|
|
88
|
+
const newUser = await jsonPost({
|
|
89
|
+
url: '/api/users',
|
|
90
|
+
body: JSON.stringify({ name: 'Jane', email: 'jane@example.com' })
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// PUT request to update resource
|
|
94
|
+
const updatedUser = await jsonPut({
|
|
95
|
+
url: '/api/users/123',
|
|
96
|
+
body: JSON.stringify({ name: 'Jane Smith' })
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// PATCH request for partial updates
|
|
100
|
+
const patchedUser = await jsonPatch({
|
|
101
|
+
url: '/api/users/123',
|
|
102
|
+
body: JSON.stringify({ email: 'newemail@example.com' })
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// DELETE request
|
|
106
|
+
const result = await jsonDelete({ url: '/api/users/123' });
|
|
107
|
+
```
|
|
108
|
+
|
|
30
109
|
### URL Utilities
|
|
31
110
|
|
|
32
111
|
```javascript
|
|
@@ -128,7 +207,9 @@ class CustomLoader extends NetworkLoader {
|
|
|
128
207
|
|
|
129
208
|
### HTTP (`$lib/network/http.js`)
|
|
130
209
|
- `httpRequest()` - Make HTTP requests with configuration
|
|
131
|
-
- `
|
|
210
|
+
- `httpGet()`, `httpPost()`, `httpPut()`, `httpPatch()`, `httpDelete()` - Convenient HTTP methods
|
|
211
|
+
- `jsonRequest()` - Make JSON API requests
|
|
212
|
+
- `jsonGet()`, `jsonPost()`, `jsonPut()`, `jsonPatch()`, `jsonDelete()` - Convenient JSON HTTP methods
|
|
132
213
|
- `toURL()` - Convert strings to URL objects with params
|
|
133
214
|
- `setRequestHeaders()` - Set and merge request headers
|
|
134
215
|
- `waitForAndCheckResponse()` - Handle responses with error checking
|
|
@@ -82,3 +82,121 @@ export function jsonGet(options: import("./typedef").JsonGetOptions): Promise<an
|
|
|
82
82
|
* });
|
|
83
83
|
*/
|
|
84
84
|
export function jsonPost(options: import("./typedef").JsonPostOptions): Promise<any>;
|
|
85
|
+
/**
|
|
86
|
+
* Make a PUT request to fetch JSON encoded data
|
|
87
|
+
*
|
|
88
|
+
* This function performs a PUT request with JSON data and expects a JSON
|
|
89
|
+
* response from the server. It handles common error cases and parses the JSON response.
|
|
90
|
+
*
|
|
91
|
+
* @param {import('./typedef').JsonPutOptions} options
|
|
92
|
+
* Request configuration options
|
|
93
|
+
*
|
|
94
|
+
* @returns {Promise<any>} Parsed JSON data
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Basic JSON PUT request
|
|
98
|
+
* try {
|
|
99
|
+
* const updatedUser = {
|
|
100
|
+
* name: "Jane Smith",
|
|
101
|
+
* email: "jane@example.com"
|
|
102
|
+
* };
|
|
103
|
+
*
|
|
104
|
+
* const data = await jsonPut({
|
|
105
|
+
* url: 'https://api.example.com/users/123',
|
|
106
|
+
* body: JSON.stringify(updatedUser)
|
|
107
|
+
* });
|
|
108
|
+
*
|
|
109
|
+
* console.log('Updated user:', data);
|
|
110
|
+
* } catch (error) {
|
|
111
|
+
* console.error('Failed to update user:', error.message);
|
|
112
|
+
* }
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* // JSON PUT with authentication and timeout
|
|
116
|
+
* const data = await jsonPut({
|
|
117
|
+
* url: 'https://api.example.com/protected/resource/456',
|
|
118
|
+
* body: JSON.stringify({ action: 'replace' }),
|
|
119
|
+
* headers: {
|
|
120
|
+
* 'authorization': 'Bearer ' + token
|
|
121
|
+
* },
|
|
122
|
+
* withCredentials: true,
|
|
123
|
+
* timeoutMs: 5000
|
|
124
|
+
* });
|
|
125
|
+
*/
|
|
126
|
+
export function jsonPut(options: import("./typedef").JsonPutOptions): Promise<any>;
|
|
127
|
+
/**
|
|
128
|
+
* Make a PATCH request to fetch JSON encoded data
|
|
129
|
+
*
|
|
130
|
+
* This function performs a PATCH request with JSON data and expects a JSON
|
|
131
|
+
* response from the server. It handles common error cases and parses the JSON response.
|
|
132
|
+
*
|
|
133
|
+
* @param {import('./typedef').JsonPatchOptions} options
|
|
134
|
+
* Request configuration options
|
|
135
|
+
*
|
|
136
|
+
* @returns {Promise<any>} Parsed JSON data
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* // Basic JSON PATCH request
|
|
140
|
+
* try {
|
|
141
|
+
* const partialUpdate = {
|
|
142
|
+
* email: "newemail@example.com"
|
|
143
|
+
* };
|
|
144
|
+
*
|
|
145
|
+
* const data = await jsonPatch({
|
|
146
|
+
* url: 'https://api.example.com/users/123',
|
|
147
|
+
* body: JSON.stringify(partialUpdate)
|
|
148
|
+
* });
|
|
149
|
+
*
|
|
150
|
+
* console.log('Patched user:', data);
|
|
151
|
+
* } catch (error) {
|
|
152
|
+
* console.error('Failed to patch user:', error.message);
|
|
153
|
+
* }
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* // JSON PATCH with authentication and timeout
|
|
157
|
+
* const data = await jsonPatch({
|
|
158
|
+
* url: 'https://api.example.com/protected/resource/456',
|
|
159
|
+
* body: JSON.stringify({ action: 'partial_update' }),
|
|
160
|
+
* headers: {
|
|
161
|
+
* 'authorization': 'Bearer ' + token
|
|
162
|
+
* },
|
|
163
|
+
* withCredentials: true,
|
|
164
|
+
* timeoutMs: 5000
|
|
165
|
+
* });
|
|
166
|
+
*/
|
|
167
|
+
export function jsonPatch(options: import("./typedef").JsonPatchOptions): Promise<any>;
|
|
168
|
+
/**
|
|
169
|
+
* Make a DELETE request to fetch JSON encoded data
|
|
170
|
+
*
|
|
171
|
+
* This function performs a DELETE request and expects a JSON response from the server.
|
|
172
|
+
* It handles common error cases and parses the JSON response.
|
|
173
|
+
*
|
|
174
|
+
* @param {import('./typedef').JsonDeleteOptions} options
|
|
175
|
+
* Request configuration options
|
|
176
|
+
*
|
|
177
|
+
* @returns {Promise<any>} Parsed JSON data
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* // Basic JSON DELETE request
|
|
181
|
+
* try {
|
|
182
|
+
* const data = await jsonDelete({
|
|
183
|
+
* url: 'https://api.example.com/users/123'
|
|
184
|
+
* });
|
|
185
|
+
* console.log('User deleted:', data);
|
|
186
|
+
* } catch (error) {
|
|
187
|
+
* console.error('Failed to delete user:', error.message);
|
|
188
|
+
* }
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* // JSON DELETE request with parameters and authentication
|
|
192
|
+
* const data = await jsonDelete({
|
|
193
|
+
* url: 'https://api.example.com/posts/456',
|
|
194
|
+
* urlSearchParams: new URLSearchParams({ force: 'true' }),
|
|
195
|
+
* headers: {
|
|
196
|
+
* 'authorization': 'Bearer ' + token
|
|
197
|
+
* },
|
|
198
|
+
* withCredentials: true,
|
|
199
|
+
* timeoutMs: 3000
|
|
200
|
+
* });
|
|
201
|
+
*/
|
|
202
|
+
export function jsonDelete(options: import("./typedef").JsonDeleteOptions): Promise<any>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { METHOD_GET, METHOD_POST } from '../../constants/http/methods.js';
|
|
1
|
+
import { METHOD_GET, METHOD_POST, METHOD_PUT, METHOD_PATCH, METHOD_DELETE } from '../../constants/http/methods.js';
|
|
2
2
|
|
|
3
3
|
import { APPLICATION_JSON } from '../../constants/mime/application.js';
|
|
4
4
|
import { CONTENT_TYPE } from '../../constants/http/headers.js';
|
|
@@ -223,3 +223,323 @@ export async function jsonPost(options) {
|
|
|
223
223
|
|
|
224
224
|
return parsedResponse;
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Make a PUT request to fetch JSON encoded data
|
|
229
|
+
*
|
|
230
|
+
* This function performs a PUT request with JSON data and expects a JSON
|
|
231
|
+
* response from the server. It handles common error cases and parses the JSON response.
|
|
232
|
+
*
|
|
233
|
+
* @param {import('./typedef').JsonPutOptions} options
|
|
234
|
+
* Request configuration options
|
|
235
|
+
*
|
|
236
|
+
* @returns {Promise<any>} Parsed JSON data
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* // Basic JSON PUT request
|
|
240
|
+
* try {
|
|
241
|
+
* const updatedUser = {
|
|
242
|
+
* name: "Jane Smith",
|
|
243
|
+
* email: "jane@example.com"
|
|
244
|
+
* };
|
|
245
|
+
*
|
|
246
|
+
* const data = await jsonPut({
|
|
247
|
+
* url: 'https://api.example.com/users/123',
|
|
248
|
+
* body: JSON.stringify(updatedUser)
|
|
249
|
+
* });
|
|
250
|
+
*
|
|
251
|
+
* console.log('Updated user:', data);
|
|
252
|
+
* } catch (error) {
|
|
253
|
+
* console.error('Failed to update user:', error.message);
|
|
254
|
+
* }
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* // JSON PUT with authentication and timeout
|
|
258
|
+
* const data = await jsonPut({
|
|
259
|
+
* url: 'https://api.example.com/protected/resource/456',
|
|
260
|
+
* body: JSON.stringify({ action: 'replace' }),
|
|
261
|
+
* headers: {
|
|
262
|
+
* 'authorization': 'Bearer ' + token
|
|
263
|
+
* },
|
|
264
|
+
* withCredentials: true,
|
|
265
|
+
* timeoutMs: 5000
|
|
266
|
+
* });
|
|
267
|
+
*/
|
|
268
|
+
export async function jsonPut(options) {
|
|
269
|
+
// Extract specific parameters needed for this function
|
|
270
|
+
const {
|
|
271
|
+
url: rawUrl,
|
|
272
|
+
body,
|
|
273
|
+
urlSearchParams,
|
|
274
|
+
headers,
|
|
275
|
+
withCredentials,
|
|
276
|
+
...otherOptions
|
|
277
|
+
} = options;
|
|
278
|
+
|
|
279
|
+
const url = toURL(rawUrl);
|
|
280
|
+
|
|
281
|
+
// Apply JSON-specific defaults and validation
|
|
282
|
+
expect.defined(body);
|
|
283
|
+
|
|
284
|
+
/** @type {Record<string, string>} */
|
|
285
|
+
const jsonHeaders = headers || {};
|
|
286
|
+
jsonHeaders[ACCEPT] = APPLICATION_JSON;
|
|
287
|
+
jsonHeaders[CONTENT_TYPE] = APPLICATION_JSON;
|
|
288
|
+
|
|
289
|
+
// Check if body is a string when using application/json
|
|
290
|
+
if (
|
|
291
|
+
jsonHeaders[CONTENT_TYPE] === APPLICATION_JSON &&
|
|
292
|
+
typeof body !== 'string'
|
|
293
|
+
) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Trying to send request with [content-type:${APPLICATION_JSON}], ` +
|
|
296
|
+
'but body is not a (JSON encoded) string.'
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Create request with all options
|
|
301
|
+
const responsePromise = httpRequest({
|
|
302
|
+
method: METHOD_PUT,
|
|
303
|
+
url,
|
|
304
|
+
body,
|
|
305
|
+
urlSearchParams,
|
|
306
|
+
headers: jsonHeaders,
|
|
307
|
+
withCredentials,
|
|
308
|
+
...otherOptions // Pass through any other options
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const response = await waitForAndCheckResponse(responsePromise, url);
|
|
312
|
+
|
|
313
|
+
let parsedResponse;
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
//
|
|
317
|
+
// @note when security on the client side fails, an `opaque` response
|
|
318
|
+
// is returned by the browser (empty body) -> parsing fails
|
|
319
|
+
// (use CORS to fix this)
|
|
320
|
+
//
|
|
321
|
+
parsedResponse = await response.json();
|
|
322
|
+
} catch (e) {
|
|
323
|
+
throw new ResponseError(
|
|
324
|
+
`Failed to JSON decode server response from [${decodeURI(url.href)}]`,
|
|
325
|
+
{
|
|
326
|
+
cause: e
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (parsedResponse.error) {
|
|
332
|
+
//
|
|
333
|
+
// @note this is API specific, but it's quite logical
|
|
334
|
+
//
|
|
335
|
+
throw new ResponseError(
|
|
336
|
+
`Server returned response error message [${parsedResponse.error}]`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return parsedResponse;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Make a PATCH request to fetch JSON encoded data
|
|
345
|
+
*
|
|
346
|
+
* This function performs a PATCH request with JSON data and expects a JSON
|
|
347
|
+
* response from the server. It handles common error cases and parses the JSON response.
|
|
348
|
+
*
|
|
349
|
+
* @param {import('./typedef').JsonPatchOptions} options
|
|
350
|
+
* Request configuration options
|
|
351
|
+
*
|
|
352
|
+
* @returns {Promise<any>} Parsed JSON data
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
* // Basic JSON PATCH request
|
|
356
|
+
* try {
|
|
357
|
+
* const partialUpdate = {
|
|
358
|
+
* email: "newemail@example.com"
|
|
359
|
+
* };
|
|
360
|
+
*
|
|
361
|
+
* const data = await jsonPatch({
|
|
362
|
+
* url: 'https://api.example.com/users/123',
|
|
363
|
+
* body: JSON.stringify(partialUpdate)
|
|
364
|
+
* });
|
|
365
|
+
*
|
|
366
|
+
* console.log('Patched user:', data);
|
|
367
|
+
* } catch (error) {
|
|
368
|
+
* console.error('Failed to patch user:', error.message);
|
|
369
|
+
* }
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* // JSON PATCH with authentication and timeout
|
|
373
|
+
* const data = await jsonPatch({
|
|
374
|
+
* url: 'https://api.example.com/protected/resource/456',
|
|
375
|
+
* body: JSON.stringify({ action: 'partial_update' }),
|
|
376
|
+
* headers: {
|
|
377
|
+
* 'authorization': 'Bearer ' + token
|
|
378
|
+
* },
|
|
379
|
+
* withCredentials: true,
|
|
380
|
+
* timeoutMs: 5000
|
|
381
|
+
* });
|
|
382
|
+
*/
|
|
383
|
+
export async function jsonPatch(options) {
|
|
384
|
+
// Extract specific parameters needed for this function
|
|
385
|
+
const {
|
|
386
|
+
url: rawUrl,
|
|
387
|
+
body,
|
|
388
|
+
urlSearchParams,
|
|
389
|
+
headers,
|
|
390
|
+
withCredentials,
|
|
391
|
+
...otherOptions
|
|
392
|
+
} = options;
|
|
393
|
+
|
|
394
|
+
const url = toURL(rawUrl);
|
|
395
|
+
|
|
396
|
+
// Apply JSON-specific defaults and validation
|
|
397
|
+
expect.defined(body);
|
|
398
|
+
|
|
399
|
+
/** @type {Record<string, string>} */
|
|
400
|
+
const jsonHeaders = headers || {};
|
|
401
|
+
jsonHeaders[ACCEPT] = APPLICATION_JSON;
|
|
402
|
+
jsonHeaders[CONTENT_TYPE] = APPLICATION_JSON;
|
|
403
|
+
|
|
404
|
+
// Check if body is a string when using application/json
|
|
405
|
+
if (
|
|
406
|
+
jsonHeaders[CONTENT_TYPE] === APPLICATION_JSON &&
|
|
407
|
+
typeof body !== 'string'
|
|
408
|
+
) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Trying to send request with [content-type:${APPLICATION_JSON}], ` +
|
|
411
|
+
'but body is not a (JSON encoded) string.'
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Create request with all options
|
|
416
|
+
const responsePromise = httpRequest({
|
|
417
|
+
method: METHOD_PATCH,
|
|
418
|
+
url,
|
|
419
|
+
body,
|
|
420
|
+
urlSearchParams,
|
|
421
|
+
headers: jsonHeaders,
|
|
422
|
+
withCredentials,
|
|
423
|
+
...otherOptions // Pass through any other options
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const response = await waitForAndCheckResponse(responsePromise, url);
|
|
427
|
+
|
|
428
|
+
let parsedResponse;
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
//
|
|
432
|
+
// @note when security on the client side fails, an `opaque` response
|
|
433
|
+
// is returned by the browser (empty body) -> parsing fails
|
|
434
|
+
// (use CORS to fix this)
|
|
435
|
+
//
|
|
436
|
+
parsedResponse = await response.json();
|
|
437
|
+
} catch (e) {
|
|
438
|
+
throw new ResponseError(
|
|
439
|
+
`Failed to JSON decode server response from [${decodeURI(url.href)}]`,
|
|
440
|
+
{
|
|
441
|
+
cause: e
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (parsedResponse.error) {
|
|
447
|
+
//
|
|
448
|
+
// @note this is API specific, but it's quite logical
|
|
449
|
+
//
|
|
450
|
+
throw new ResponseError(
|
|
451
|
+
`Server returned response error message [${parsedResponse.error}]`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return parsedResponse;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Make a DELETE request to fetch JSON encoded data
|
|
460
|
+
*
|
|
461
|
+
* This function performs a DELETE request and expects a JSON response from the server.
|
|
462
|
+
* It handles common error cases and parses the JSON response.
|
|
463
|
+
*
|
|
464
|
+
* @param {import('./typedef').JsonDeleteOptions} options
|
|
465
|
+
* Request configuration options
|
|
466
|
+
*
|
|
467
|
+
* @returns {Promise<any>} Parsed JSON data
|
|
468
|
+
*
|
|
469
|
+
* @example
|
|
470
|
+
* // Basic JSON DELETE request
|
|
471
|
+
* try {
|
|
472
|
+
* const data = await jsonDelete({
|
|
473
|
+
* url: 'https://api.example.com/users/123'
|
|
474
|
+
* });
|
|
475
|
+
* console.log('User deleted:', data);
|
|
476
|
+
* } catch (error) {
|
|
477
|
+
* console.error('Failed to delete user:', error.message);
|
|
478
|
+
* }
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* // JSON DELETE request with parameters and authentication
|
|
482
|
+
* const data = await jsonDelete({
|
|
483
|
+
* url: 'https://api.example.com/posts/456',
|
|
484
|
+
* urlSearchParams: new URLSearchParams({ force: 'true' }),
|
|
485
|
+
* headers: {
|
|
486
|
+
* 'authorization': 'Bearer ' + token
|
|
487
|
+
* },
|
|
488
|
+
* withCredentials: true,
|
|
489
|
+
* timeoutMs: 3000
|
|
490
|
+
* });
|
|
491
|
+
*/
|
|
492
|
+
export async function jsonDelete(options) {
|
|
493
|
+
// Extract specific parameters needed for this function
|
|
494
|
+
const {
|
|
495
|
+
url: rawUrl,
|
|
496
|
+
urlSearchParams,
|
|
497
|
+
headers,
|
|
498
|
+
withCredentials,
|
|
499
|
+
...otherOptions
|
|
500
|
+
} = options;
|
|
501
|
+
|
|
502
|
+
const url = toURL(rawUrl);
|
|
503
|
+
|
|
504
|
+
// Apply JSON-specific defaults
|
|
505
|
+
const jsonHeaders = headers || {};
|
|
506
|
+
jsonHeaders[ACCEPT] = APPLICATION_JSON;
|
|
507
|
+
|
|
508
|
+
// Create request with all options
|
|
509
|
+
const responsePromise = httpRequest({
|
|
510
|
+
method: METHOD_DELETE,
|
|
511
|
+
url,
|
|
512
|
+
urlSearchParams,
|
|
513
|
+
headers: jsonHeaders,
|
|
514
|
+
withCredentials,
|
|
515
|
+
...otherOptions // Pass through any other options
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const response = await waitForAndCheckResponse(responsePromise, url);
|
|
519
|
+
|
|
520
|
+
let parsedResponse;
|
|
521
|
+
|
|
522
|
+
try {
|
|
523
|
+
//
|
|
524
|
+
// @note when security on the client side fails, an `opaque` response
|
|
525
|
+
// is returned by the browser (empty body) -> parsing fails
|
|
526
|
+
// (use CORS to fix this)
|
|
527
|
+
//
|
|
528
|
+
parsedResponse = await response.json();
|
|
529
|
+
} catch (e) {
|
|
530
|
+
throw new ResponseError(
|
|
531
|
+
`Failed to JSON decode server response from [${decodeURI(url.href)}]`,
|
|
532
|
+
{
|
|
533
|
+
cause: e
|
|
534
|
+
}
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (parsedResponse.error) {
|
|
539
|
+
throw new ResponseError(
|
|
540
|
+
`Server returned response error message [${parsedResponse.error}]`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return parsedResponse;
|
|
545
|
+
}
|
|
@@ -163,6 +163,152 @@ export type JsonPostOptions = {
|
|
|
163
163
|
*/
|
|
164
164
|
cacheEnabled?: boolean | undefined;
|
|
165
165
|
};
|
|
166
|
+
export type JsonPutOptions = {
|
|
167
|
+
/**
|
|
168
|
+
* URL string or URL object for the request
|
|
169
|
+
*/
|
|
170
|
+
url: string | URL;
|
|
171
|
+
/**
|
|
172
|
+
* Request body (will be sent as JSON)
|
|
173
|
+
*/
|
|
174
|
+
body: any;
|
|
175
|
+
/**
|
|
176
|
+
* Parameters to add to the URL
|
|
177
|
+
*/
|
|
178
|
+
urlSearchParams?: Object | URLSearchParams | undefined;
|
|
179
|
+
/**
|
|
180
|
+
* HTTP headers as name-value pairs
|
|
181
|
+
*/
|
|
182
|
+
headers?: Record<string, string> | undefined;
|
|
183
|
+
/**
|
|
184
|
+
* Whether to include credentials
|
|
185
|
+
*/
|
|
186
|
+
withCredentials?: boolean | undefined;
|
|
187
|
+
/**
|
|
188
|
+
* Request timeout in milliseconds
|
|
189
|
+
*/
|
|
190
|
+
timeoutMs?: number | undefined;
|
|
191
|
+
/**
|
|
192
|
+
* Handler for abort/timeout control
|
|
193
|
+
*/
|
|
194
|
+
requestHandler?: RequestHandler | undefined;
|
|
195
|
+
/**
|
|
196
|
+
* CORS mode ('cors', 'no-cors', 'same-origin')
|
|
197
|
+
*/
|
|
198
|
+
mode?: string | undefined;
|
|
199
|
+
/**
|
|
200
|
+
* Cache mode ('default', 'no-cache', etc.)
|
|
201
|
+
*/
|
|
202
|
+
cache?: string | undefined;
|
|
203
|
+
/**
|
|
204
|
+
* Redirect mode ('follow', 'error', 'manual')
|
|
205
|
+
*/
|
|
206
|
+
redirect?: string | undefined;
|
|
207
|
+
/**
|
|
208
|
+
* Referrer policy
|
|
209
|
+
*/
|
|
210
|
+
referrerPolicy?: string | undefined;
|
|
211
|
+
/**
|
|
212
|
+
* Enable or disabled automatic caching
|
|
213
|
+
*/
|
|
214
|
+
cacheEnabled?: boolean | undefined;
|
|
215
|
+
};
|
|
216
|
+
export type JsonPatchOptions = {
|
|
217
|
+
/**
|
|
218
|
+
* URL string or URL object for the request
|
|
219
|
+
*/
|
|
220
|
+
url: string | URL;
|
|
221
|
+
/**
|
|
222
|
+
* Request body (will be sent as JSON)
|
|
223
|
+
*/
|
|
224
|
+
body: any;
|
|
225
|
+
/**
|
|
226
|
+
* Parameters to add to the URL
|
|
227
|
+
*/
|
|
228
|
+
urlSearchParams?: Object | URLSearchParams | undefined;
|
|
229
|
+
/**
|
|
230
|
+
* HTTP headers as name-value pairs
|
|
231
|
+
*/
|
|
232
|
+
headers?: Record<string, string> | undefined;
|
|
233
|
+
/**
|
|
234
|
+
* Whether to include credentials
|
|
235
|
+
*/
|
|
236
|
+
withCredentials?: boolean | undefined;
|
|
237
|
+
/**
|
|
238
|
+
* Request timeout in milliseconds
|
|
239
|
+
*/
|
|
240
|
+
timeoutMs?: number | undefined;
|
|
241
|
+
/**
|
|
242
|
+
* Handler for abort/timeout control
|
|
243
|
+
*/
|
|
244
|
+
requestHandler?: RequestHandler | undefined;
|
|
245
|
+
/**
|
|
246
|
+
* CORS mode ('cors', 'no-cors', 'same-origin')
|
|
247
|
+
*/
|
|
248
|
+
mode?: string | undefined;
|
|
249
|
+
/**
|
|
250
|
+
* Cache mode ('default', 'no-cache', etc.)
|
|
251
|
+
*/
|
|
252
|
+
cache?: string | undefined;
|
|
253
|
+
/**
|
|
254
|
+
* Redirect mode ('follow', 'error', 'manual')
|
|
255
|
+
*/
|
|
256
|
+
redirect?: string | undefined;
|
|
257
|
+
/**
|
|
258
|
+
* Referrer policy
|
|
259
|
+
*/
|
|
260
|
+
referrerPolicy?: string | undefined;
|
|
261
|
+
/**
|
|
262
|
+
* Enable or disabled automatic caching
|
|
263
|
+
*/
|
|
264
|
+
cacheEnabled?: boolean | undefined;
|
|
265
|
+
};
|
|
266
|
+
export type JsonDeleteOptions = {
|
|
267
|
+
/**
|
|
268
|
+
* URL string or URL object for the request
|
|
269
|
+
*/
|
|
270
|
+
url: string | URL;
|
|
271
|
+
/**
|
|
272
|
+
* Parameters to add to the URL
|
|
273
|
+
*/
|
|
274
|
+
urlSearchParams?: Object | URLSearchParams | undefined;
|
|
275
|
+
/**
|
|
276
|
+
* HTTP headers as name-value pairs
|
|
277
|
+
*/
|
|
278
|
+
headers?: Record<string, string> | undefined;
|
|
279
|
+
/**
|
|
280
|
+
* Whether to include credentials
|
|
281
|
+
*/
|
|
282
|
+
withCredentials?: boolean | undefined;
|
|
283
|
+
/**
|
|
284
|
+
* Request timeout in milliseconds
|
|
285
|
+
*/
|
|
286
|
+
timeoutMs?: number | undefined;
|
|
287
|
+
/**
|
|
288
|
+
* Handler for abort/timeout control
|
|
289
|
+
*/
|
|
290
|
+
requestHandler?: RequestHandler | undefined;
|
|
291
|
+
/**
|
|
292
|
+
* CORS mode ('cors', 'no-cors', 'same-origin')
|
|
293
|
+
*/
|
|
294
|
+
mode?: string | undefined;
|
|
295
|
+
/**
|
|
296
|
+
* Cache mode ('default', 'no-cache', etc.')
|
|
297
|
+
*/
|
|
298
|
+
cache?: string | undefined;
|
|
299
|
+
/**
|
|
300
|
+
* Redirect mode ('follow', 'error', 'manual')
|
|
301
|
+
*/
|
|
302
|
+
redirect?: string | undefined;
|
|
303
|
+
/**
|
|
304
|
+
* Referrer policy
|
|
305
|
+
*/
|
|
306
|
+
referrerPolicy?: string | undefined;
|
|
307
|
+
/**
|
|
308
|
+
* Enable or disabled automatic caching
|
|
309
|
+
*/
|
|
310
|
+
cacheEnabled?: boolean | undefined;
|
|
311
|
+
};
|
|
166
312
|
export type StaleInfo = {
|
|
167
313
|
/**
|
|
168
314
|
* Whether the response contains stale data
|
|
@@ -71,6 +71,62 @@
|
|
|
71
71
|
* @property {boolean} [cacheEnabled] Enable or disabled automatic caching
|
|
72
72
|
*/
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @typedef {Object} JsonPutOptions
|
|
76
|
+
* @property {string|URL} url URL string or URL object for the request
|
|
77
|
+
* @property {*} body Request body (will be sent as JSON)
|
|
78
|
+
*
|
|
79
|
+
* @property {Object|URLSearchParams} [urlSearchParams]
|
|
80
|
+
* Parameters to add to the URL
|
|
81
|
+
*
|
|
82
|
+
* @property {Record<string, string>} [headers] HTTP headers as name-value pairs
|
|
83
|
+
* @property {boolean} [withCredentials] Whether to include credentials
|
|
84
|
+
* @property {number} [timeoutMs] Request timeout in milliseconds
|
|
85
|
+
* @property {RequestHandler} [requestHandler] Handler for abort/timeout control
|
|
86
|
+
* @property {string} [mode] CORS mode ('cors', 'no-cors', 'same-origin')
|
|
87
|
+
* @property {string} [cache] Cache mode ('default', 'no-cache', etc.)
|
|
88
|
+
* @property {string} [redirect] Redirect mode ('follow', 'error', 'manual')
|
|
89
|
+
* @property {string} [referrerPolicy] Referrer policy
|
|
90
|
+
* @property {boolean} [cacheEnabled] Enable or disabled automatic caching
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @typedef {Object} JsonPatchOptions
|
|
95
|
+
* @property {string|URL} url URL string or URL object for the request
|
|
96
|
+
* @property {*} body Request body (will be sent as JSON)
|
|
97
|
+
*
|
|
98
|
+
* @property {Object|URLSearchParams} [urlSearchParams]
|
|
99
|
+
* Parameters to add to the URL
|
|
100
|
+
*
|
|
101
|
+
* @property {Record<string, string>} [headers] HTTP headers as name-value pairs
|
|
102
|
+
* @property {boolean} [withCredentials] Whether to include credentials
|
|
103
|
+
* @property {number} [timeoutMs] Request timeout in milliseconds
|
|
104
|
+
* @property {RequestHandler} [requestHandler] Handler for abort/timeout control
|
|
105
|
+
* @property {string} [mode] CORS mode ('cors', 'no-cors', 'same-origin')
|
|
106
|
+
* @property {string} [cache] Cache mode ('default', 'no-cache', etc.)
|
|
107
|
+
* @property {string} [redirect] Redirect mode ('follow', 'error', 'manual')
|
|
108
|
+
* @property {string} [referrerPolicy] Referrer policy
|
|
109
|
+
* @property {boolean} [cacheEnabled] Enable or disabled automatic caching
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @typedef {Object} JsonDeleteOptions
|
|
114
|
+
* @property {string|URL} url URL string or URL object for the request
|
|
115
|
+
*
|
|
116
|
+
* @property {Object|URLSearchParams} [urlSearchParams]
|
|
117
|
+
* Parameters to add to the URL
|
|
118
|
+
*
|
|
119
|
+
* @property {Record<string, string>} [headers] HTTP headers as name-value pairs
|
|
120
|
+
* @property {boolean} [withCredentials] Whether to include credentials
|
|
121
|
+
* @property {number} [timeoutMs] Request timeout in milliseconds
|
|
122
|
+
* @property {RequestHandler} [requestHandler] Handler for abort/timeout control
|
|
123
|
+
* @property {string} [mode] CORS mode ('cors', 'no-cors', 'same-origin')
|
|
124
|
+
* @property {string} [cache] Cache mode ('default', 'no-cache', etc.')
|
|
125
|
+
* @property {string} [redirect] Redirect mode ('follow', 'error', 'manual')
|
|
126
|
+
* @property {string} [referrerPolicy] Referrer policy
|
|
127
|
+
* @property {boolean} [cacheEnabled] Enable or disabled automatic caching
|
|
128
|
+
*/
|
|
129
|
+
|
|
74
130
|
/**
|
|
75
131
|
* @typedef {Object} StaleInfo
|
|
76
132
|
* @property {boolean} isStale Whether the response contains stale data
|
package/dist/services/README.md
CHANGED
|
@@ -204,22 +204,31 @@ await manager.stopAll();
|
|
|
204
204
|
Services receive helpful utilities in their constructor options for accessing other services:
|
|
205
205
|
|
|
206
206
|
```javascript
|
|
207
|
+
/**
|
|
208
|
+
* Example service that depends on other services
|
|
209
|
+
*/
|
|
207
210
|
class AuthService extends ServiceBase {
|
|
211
|
+
/** @type {(<T>(serviceName: string) => T)} */
|
|
212
|
+
#getService;
|
|
213
|
+
|
|
214
|
+
/** @type {() => import('$hklib-core/services/index.js').ServiceManager} */
|
|
215
|
+
#getManager;
|
|
216
|
+
|
|
208
217
|
constructor(serviceName, options) {
|
|
209
218
|
super(serviceName, options);
|
|
210
219
|
|
|
211
|
-
// Store service access utilities
|
|
212
|
-
this
|
|
213
|
-
this
|
|
220
|
+
// Store service access utilities as private methods
|
|
221
|
+
this.#getService = options.getService; // Bound getService function
|
|
222
|
+
this.#getManager = options.getManager; // Function to get manager (lazy)
|
|
214
223
|
}
|
|
215
224
|
|
|
216
225
|
async authenticateUser(credentials) {
|
|
217
226
|
// Access other services with full type safety and error checking
|
|
218
|
-
const database = this
|
|
227
|
+
const database = this.#getService('database');
|
|
219
228
|
const user = await database.findUser(credentials.username);
|
|
220
229
|
|
|
221
|
-
// Access manager for advanced operations
|
|
222
|
-
const manager = this
|
|
230
|
+
// Access manager for advanced operations when needed
|
|
231
|
+
const manager = this.#getManager();
|
|
223
232
|
const health = await manager.checkHealth();
|
|
224
233
|
|
|
225
234
|
return user;
|
|
@@ -227,6 +236,48 @@ class AuthService extends ServiceBase {
|
|
|
227
236
|
}
|
|
228
237
|
```
|
|
229
238
|
|
|
239
|
+
**Best Practice Pattern:**
|
|
240
|
+
|
|
241
|
+
The recommended approach is to store service access functions as **private methods** using the hash prefix. This pattern:
|
|
242
|
+
|
|
243
|
+
- **Keeps the API clean** - No public getService/getManager methods exposed
|
|
244
|
+
- **Prevents serialization issues** - Private fields don't serialize to JSON
|
|
245
|
+
- **Enforces proper encapsulation** - Service dependencies stay internal
|
|
246
|
+
- **Provides type safety** - Full generic support with `this.#getService<DatabaseService>('database')`
|
|
247
|
+
|
|
248
|
+
```javascript
|
|
249
|
+
/**
|
|
250
|
+
* Unified service for tracking complete player data including progress and
|
|
251
|
+
* profile matches
|
|
252
|
+
*/
|
|
253
|
+
export default class PlayerService extends ServiceBase {
|
|
254
|
+
|
|
255
|
+
/** @type {(<T>(serviceName: string) => T)} */
|
|
256
|
+
#getService;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* @param {string} serviceName
|
|
260
|
+
* @param {import('$hklib-core/services/typedef.js').ServiceOptions} [options]
|
|
261
|
+
*/
|
|
262
|
+
constructor(serviceName, options) {
|
|
263
|
+
super(serviceName, options);
|
|
264
|
+
|
|
265
|
+
this.#getService = options?.getService;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async getPlayerProfile(playerId) {
|
|
269
|
+
// Access dependent services cleanly
|
|
270
|
+
const database = this.#getService('database');
|
|
271
|
+
const analytics = this.#getService('analytics');
|
|
272
|
+
|
|
273
|
+
const profile = await database.getPlayer(playerId);
|
|
274
|
+
const stats = await analytics.getPlayerStats(playerId);
|
|
275
|
+
|
|
276
|
+
return { ...profile, stats };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
230
281
|
**Service Access Methods:**
|
|
231
282
|
|
|
232
283
|
```javascript
|