@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.
@@ -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
@@ -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
@@ -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.getManager = options.getManager; // Function to get manager (lazy)
213
- this.getService = options.getService; // Bound getService function
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.getService('database');
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.getManager();
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.4.55",
3
+ "version": "0.4.56",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"