@hkdigital/lib-core 0.4.55 → 0.4.57

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.
@@ -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
- - `jsonRequest()` - Make JSON API requests
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