@klerick/json-api-nestjs-sdk 10.0.0-beta.9 → 10.0.1

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 (79) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +365 -58
  3. package/cjs/src/index.js +9 -8
  4. package/cjs/src/index.js.map +1 -1
  5. package/cjs/src/lib/json-api-angular.js +22 -18
  6. package/cjs/src/lib/json-api-angular.js.map +1 -1
  7. package/cjs/src/lib/json-api-js.js +11 -11
  8. package/cjs/src/lib/json-api-js.js.map +1 -1
  9. package/cjs/src/lib/service/atomic-operations.service.js +22 -16
  10. package/cjs/src/lib/service/atomic-operations.service.js.map +1 -1
  11. package/cjs/src/lib/service/fetch-inner-client.js +3 -3
  12. package/cjs/src/lib/service/fetch-inner-client.js.map +1 -1
  13. package/cjs/src/lib/service/index.js +3 -3
  14. package/cjs/src/lib/service/index.js.map +1 -1
  15. package/cjs/src/lib/service/json-api-sdk.service.js +33 -21
  16. package/cjs/src/lib/service/json-api-sdk.service.js.map +1 -1
  17. package/cjs/src/lib/service/json-api-utils.service.js +135 -59
  18. package/cjs/src/lib/service/json-api-utils.service.js.map +1 -1
  19. package/cjs/src/lib/types/index.js +10 -10
  20. package/cjs/src/lib/types/index.js.map +1 -1
  21. package/cjs/src/lib/utils/generate-atomic-body.js +21 -20
  22. package/cjs/src/lib/utils/generate-atomic-body.js.map +1 -1
  23. package/cjs/src/lib/utils/index.js +5 -5
  24. package/cjs/src/lib/utils/index.js.map +1 -1
  25. package/cjs/src/lib/utils/utils.js +23 -3
  26. package/cjs/src/lib/utils/utils.js.map +1 -1
  27. package/cjs/src/ngModule.js +8 -8
  28. package/cjs/src/ngModule.js.map +1 -1
  29. package/mjs/package.json +1 -0
  30. package/mjs/src/index.d.ts +4 -4
  31. package/mjs/src/index.js +3 -3
  32. package/mjs/src/index.js.map +1 -1
  33. package/mjs/src/lib/json-api-angular.d.ts +10 -12
  34. package/mjs/src/lib/json-api-angular.js +11 -7
  35. package/mjs/src/lib/json-api-angular.js.map +1 -1
  36. package/mjs/src/lib/json-api-js.d.ts +4 -4
  37. package/mjs/src/lib/json-api-js.js +6 -6
  38. package/mjs/src/lib/json-api-js.js.map +1 -1
  39. package/mjs/src/lib/service/atomic-operations.service.d.ts +7 -7
  40. package/mjs/src/lib/service/atomic-operations.service.js +21 -15
  41. package/mjs/src/lib/service/atomic-operations.service.js.map +1 -1
  42. package/mjs/src/lib/service/fetch-inner-client.d.ts +2 -2
  43. package/mjs/src/lib/service/fetch-inner-client.js +1 -1
  44. package/mjs/src/lib/service/fetch-inner-client.js.map +1 -1
  45. package/mjs/src/lib/service/index.d.ts +3 -3
  46. package/mjs/src/lib/service/index.js +3 -3
  47. package/mjs/src/lib/service/index.js.map +1 -1
  48. package/mjs/src/lib/service/json-api-sdk.service.d.ts +14 -14
  49. package/mjs/src/lib/service/json-api-sdk.service.js +29 -17
  50. package/mjs/src/lib/service/json-api-sdk.service.js.map +1 -1
  51. package/mjs/src/lib/service/json-api-utils.service.d.ts +6 -5
  52. package/mjs/src/lib/service/json-api-utils.service.js +128 -52
  53. package/mjs/src/lib/service/json-api-utils.service.js.map +1 -1
  54. package/mjs/src/lib/token/index.d.ts +1 -1
  55. package/mjs/src/lib/types/atomic-operation.d.ts +11 -11
  56. package/mjs/src/lib/types/atomic-type.d.ts +2 -2
  57. package/mjs/src/lib/types/config.d.ts +2 -2
  58. package/mjs/src/lib/types/entity-chain.d.ts +6 -6
  59. package/mjs/src/lib/types/http-inner-client.d.ts +3 -3
  60. package/mjs/src/lib/types/http-request-params.d.ts +3 -0
  61. package/mjs/src/lib/types/index.d.ts +10 -10
  62. package/mjs/src/lib/types/index.js +10 -10
  63. package/mjs/src/lib/types/index.js.map +1 -1
  64. package/mjs/src/lib/types/promise-json-api-sdk.d.ts +16 -16
  65. package/mjs/src/lib/types/query-params.d.ts +1 -1
  66. package/mjs/src/lib/utils/adapter-for-axios.d.ts +1 -1
  67. package/mjs/src/lib/utils/generate-atomic-body.d.ts +7 -7
  68. package/mjs/src/lib/utils/generate-atomic-body.js +19 -18
  69. package/mjs/src/lib/utils/generate-atomic-body.js.map +1 -1
  70. package/mjs/src/lib/utils/index.d.ts +5 -5
  71. package/mjs/src/lib/utils/index.js +5 -5
  72. package/mjs/src/lib/utils/index.js.map +1 -1
  73. package/mjs/src/lib/utils/utils.d.ts +11 -1
  74. package/mjs/src/lib/utils/utils.js +19 -1
  75. package/mjs/src/lib/utils/utils.js.map +1 -1
  76. package/mjs/src/ngModule.d.ts +3 -3
  77. package/mjs/src/ngModule.js +2 -2
  78. package/mjs/src/ngModule.js.map +1 -1
  79. package/package.json +5 -5
package/README.md CHANGED
@@ -16,6 +16,7 @@ Type-safe TypeScript/JavaScript client for consuming [JSON:API](https://jsonapi.
16
16
  - 🔍 **Advanced Filtering** - Rich query builder with operators (eq, ne, in, like, gt, lt, etc.)
17
17
  - 📦 **Relationship Handling** - Easy include, sparse fieldsets, and relationship management
18
18
  - ⚡ **Atomic Operations** - Batch multiple operations in a single request with rollback support
19
+ - 📤 **Meta Support** - Pass additional business logic data with requests (JSON:API spec compliant)
19
20
  - 🌐 **Multiple HTTP Clients** - Works with Axios, Fetch API, and Angular HttpClient
20
21
  - 📄 **Pagination & Sorting** - Built-in support for pagination and multi-field sorting
21
22
  - 🔄 **Observable or Promise** - Choose your async style (RxJS Observable or native Promise)
@@ -38,6 +39,7 @@ Type-safe TypeScript/JavaScript client for consuming [JSON:API](https://jsonapi.
38
39
  - [Using entity() Method](#using-entity-method)
39
40
  - [Using String Type Names](#using-string-type-names)
40
41
  - [Nullifying Relationships](#-nullifying-relationships)
42
+ - [Clearing To-Many Relationships](#-clearing-to-many-relationships)
41
43
  - [Query Options](#-query-options)
42
44
  - [Filtering](#filtering)
43
45
  - [Sorting](#sorting)
@@ -45,6 +47,10 @@ Type-safe TypeScript/JavaScript client for consuming [JSON:API](https://jsonapi.
45
47
  - [Including Relationships](#including-relationships)
46
48
  - [Sparse Fieldsets](#sparse-fieldsets)
47
49
  - [Atomic Operations](#-atomic-operations)
50
+ - [Meta Support](#-meta-support)
51
+ - [Regular Operations with Meta](#regular-operations-with-meta)
52
+ - [Entity Chain with Meta](#entity-chain-with-meta)
53
+ - [Atomic Operations with Meta](#atomic-operations-with-meta)
48
54
  - [Examples](#-examples)
49
55
 
50
56
 
@@ -82,10 +88,10 @@ const jsonSdk = JsonApiJs(
82
88
 
83
89
  // 3. Use SDK
84
90
  // Fetch all users
85
- const users = await jsonSdk.jonApiSdkService.getAll(Users);
91
+ const users = await jsonSdk.jsonApiSdkService.getAll(Users);
86
92
 
87
93
  // Fetch with filtering and relationships
88
- const activeUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
94
+ const activeUsers = await jsonSdk.jsonApiSdkService.getAll(Users, {
89
95
  filter: {
90
96
  target: {
91
97
  isActive: { [FilterOperand.eq]: 'true' }
@@ -95,7 +101,7 @@ const activeUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
95
101
  });
96
102
 
97
103
  // Get one user
98
- const user = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
104
+ const user = await jsonSdk.jsonApiSdkService.getOne(Users, '1', {
99
105
  include: ['addresses', 'comments', 'roles', 'manager']
100
106
  });
101
107
 
@@ -106,14 +112,14 @@ newUser.lastName = 'Doe';
106
112
  newUser.login = 'johndoe';
107
113
  newUser.isActive = true;
108
114
 
109
- const createdUser = await jsonSdk.jonApiSdkService.postOne(newUser);
115
+ const createdUser = await jsonSdk.jsonApiSdkService.postOne(newUser);
110
116
 
111
117
  // Update a user
112
118
  createdUser.firstName = 'Jane';
113
- const updatedUser = await jsonSdk.jonApiSdkService.patchOne(createdUser);
119
+ const updatedUser = await jsonSdk.jsonApiSdkService.patchOne(createdUser);
114
120
 
115
121
  // Delete a user
116
- await jsonSdk.jonApiSdkService.deleteOne(createdUser);
122
+ await jsonSdk.jsonApiSdkService.deleteOne(createdUser);
117
123
  ```
118
124
 
119
125
  ### Angular Setup
@@ -132,6 +138,8 @@ import { Component, inject } from '@angular/core';
132
138
  import { bootstrapApplication } from '@angular/platform-browser';
133
139
 
134
140
  // 1. Configure in your main.ts or app.config.ts
141
+
142
+ // Option A: Direct configuration object
135
143
  const angularConfig = {
136
144
  apiHost: 'http://localhost:3000',
137
145
  idKey: 'id',
@@ -147,6 +155,23 @@ bootstrapApplication(AppComponent, {
147
155
  ],
148
156
  }).catch((err) => console.error(err));
149
157
 
158
+ // Option B: Factory function (useful for dynamic configuration)
159
+ bootstrapApplication(AppComponent, {
160
+ providers: [
161
+ provideHttpClient(withFetch()),
162
+ provideJsonApi(() => {
163
+ const env = inject(EnvironmentService);
164
+ return {
165
+ apiHost: env.apiUrl,
166
+ idKey: 'id',
167
+ apiPrefix: 'api',
168
+ operationUrl: 'operation',
169
+ dateFields: ['createdAt', 'updatedAt']
170
+ };
171
+ })
172
+ ],
173
+ }).catch((err) => console.error(err));
174
+
150
175
  // 2. Use in your components
151
176
  @Component({
152
177
  standalone: true,
@@ -190,6 +215,10 @@ type JsonSdkConfig = {
190
215
  type JsonConfig = JsonSdkConfig & {
191
216
  adapter?: HttpInnerClient; // HTTP client adapter (default: fetch)
192
217
  }
218
+
219
+ // Angular: provideJsonApi accepts config or factory function
220
+ type JsonSdkConfigFactory = () => JsonSdkConfig;
221
+ type JsonSdkConfigOrFactory = JsonSdkConfig | JsonSdkConfigFactory;
193
222
  ```
194
223
 
195
224
  ### HTTP Adapters
@@ -229,10 +258,10 @@ Fetch all resources with optional filtering, sorting, and relationships.
229
258
  import { FilterOperand } from '@klerick/json-api-nestjs-sdk';
230
259
 
231
260
  // Fetch all users
232
- const users = await jsonSdk.jonApiSdkService.getAll(Users);
261
+ const users = await jsonSdk.jsonApiSdkService.getAll(Users);
233
262
 
234
263
  // With filtering
235
- const activeUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
264
+ const activeUsers = await jsonSdk.jsonApiSdkService.getAll(Users, {
236
265
  filter: {
237
266
  target: {
238
267
  isActive: { [FilterOperand.eq]: 'true' },
@@ -243,7 +272,7 @@ const activeUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
243
272
  });
244
273
 
245
274
  // Filter by relationship
246
- const usersWithRoles = await jsonSdk.jonApiSdkService.getAll(Users, {
275
+ const usersWithRoles = await jsonSdk.jsonApiSdkService.getAll(Users, {
247
276
  filter: {
248
277
  target: {
249
278
  id: { [FilterOperand.in]: ['1', '2'] }
@@ -261,7 +290,7 @@ const usersWithRoles = await jsonSdk.jonApiSdkService.getAll(Users, {
261
290
  Fetch resources with pagination (returns paginated results).
262
291
 
263
292
  ```typescript
264
- const firstPage = await jsonSdk.jonApiSdkService.getList(Users, {
293
+ const firstPage = await jsonSdk.jsonApiSdkService.getList(Users, {
265
294
  page: {
266
295
  number: 1,
267
296
  size: 10
@@ -273,7 +302,7 @@ const firstPage = await jsonSdk.jonApiSdkService.getList(Users, {
273
302
  }
274
303
  });
275
304
 
276
- const secondPage = await jsonSdk.jonApiSdkService.getList(Users, {
305
+ const secondPage = await jsonSdk.jsonApiSdkService.getList(Users, {
277
306
  page: {
278
307
  number: 2,
279
308
  size: 10
@@ -292,15 +321,15 @@ Fetch a single resource by ID.
292
321
 
293
322
  ```typescript
294
323
  // Simple fetch
295
- const user = await jsonSdk.jonApiSdkService.getOne(Users, '1');
324
+ const user = await jsonSdk.jsonApiSdkService.getOne(Users, '1');
296
325
 
297
326
  // With relationships
298
- const userWithRelations = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
327
+ const userWithRelations = await jsonSdk.jsonApiSdkService.getOne(Users, '1', {
299
328
  include: ['addresses', 'comments', 'roles', 'manager']
300
329
  });
301
330
 
302
331
  // With sparse fieldsets
303
- const userPartial = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
332
+ const userPartial = await jsonSdk.jsonApiSdkService.getOne(Users, '1', {
304
333
  fields: {
305
334
  users: ['firstName', 'lastName', 'email']
306
335
  }
@@ -321,7 +350,17 @@ newUser.lastName = 'Doe';
321
350
  newUser.login = 'johndoe';
322
351
  newUser.isActive = true;
323
352
 
324
- const createdUser = await jsonSdk.jonApiSdkService.postOne(newUser);
353
+ const createdUser = await jsonSdk.jsonApiSdkService.postOne(newUser);
354
+
355
+ // Create with client-generated ID
356
+ // Note: Server must have `allowSetId: true` option enabled
357
+ const userWithId = new Users();
358
+ userWithId.id = 'my-custom-uuid';
359
+ userWithId.firstName = 'Jane';
360
+ userWithId.lastName = 'Doe';
361
+ userWithId.login = 'janedoe';
362
+
363
+ const createdUserWithId = await jsonSdk.jsonApiSdkService.postOne(userWithId);
325
364
 
326
365
  // Create with relationships
327
366
  const newAddress = new Addresses();
@@ -329,7 +368,7 @@ newAddress.city = 'New York';
329
368
  newAddress.state = 'NY';
330
369
  newAddress.country = 'USA';
331
370
 
332
- const savedAddress = await jsonSdk.jonApiSdkService.postOne(newAddress);
371
+ const savedAddress = await jsonSdk.jsonApiSdkService.postOne(newAddress);
333
372
 
334
373
  const user = new Users();
335
374
  user.firstName = 'Jane';
@@ -337,7 +376,7 @@ user.lastName = 'Doe';
337
376
  user.login = 'janedoe';
338
377
  user.addresses = savedAddress; // Set relationship
339
378
 
340
- const createdUser = await jsonSdk.jonApiSdkService.postOne(user);
379
+ const createdUser = await jsonSdk.jsonApiSdkService.postOne(user);
341
380
  ```
342
381
 
343
382
  ### Updating Resources
@@ -349,13 +388,13 @@ Update an existing resource.
349
388
  ```typescript
350
389
  // Update attributes
351
390
  user.firstName = 'Updated Name';
352
- const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
391
+ const updatedUser = await jsonSdk.jsonApiSdkService.patchOne(user);
353
392
 
354
393
  // Update relationships
355
- const newAddress = await jsonSdk.jonApiSdkService.postOne(addressEntity);
394
+ const newAddress = await jsonSdk.jsonApiSdkService.postOne(addressEntity);
356
395
  user.addresses = newAddress;
357
396
 
358
- const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
397
+ const updatedUser = await jsonSdk.jsonApiSdkService.patchOne(user);
359
398
  ```
360
399
 
361
400
  ### Deleting Resources
@@ -365,7 +404,7 @@ const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
365
404
  Delete a resource.
366
405
 
367
406
  ```typescript
368
- await jsonSdk.jonApiSdkService.deleteOne(user);
407
+ await jsonSdk.jsonApiSdkService.deleteOne(user);
369
408
  ```
370
409
 
371
410
  ### Relationship Operations
@@ -376,13 +415,13 @@ Remove relationships without deleting the related resources.
376
415
 
377
416
  ```typescript
378
417
  // Remove all roles from user
379
- await jsonSdk.jonApiSdkService.deleteRelationships(user, 'roles');
418
+ await jsonSdk.jsonApiSdkService.deleteRelationships(user, 'roles');
380
419
 
381
420
  // Remove manager from user
382
- await jsonSdk.jonApiSdkService.deleteRelationships(user, 'manager');
421
+ await jsonSdk.jsonApiSdkService.deleteRelationships(user, 'manager');
383
422
 
384
423
  // Remove all comments from user
385
- await jsonSdk.jonApiSdkService.deleteRelationships(user, 'comments');
424
+ await jsonSdk.jsonApiSdkService.deleteRelationships(user, 'comments');
386
425
  ```
387
426
 
388
427
  ---
@@ -412,7 +451,7 @@ interface User {
412
451
  const jsonSdk = JsonApiJs({ apiHost: 'http://localhost:3000', apiPrefix: 'api' }, true);
413
452
 
414
453
  // Create entity from plain object - chainable API
415
- const createdUser = await jsonSdk.jonApiSdkService
454
+ const createdUser = await jsonSdk.jsonApiSdkService
416
455
  .entity<User>('Users', {
417
456
  firstName: 'John',
418
457
  lastName: 'Doe',
@@ -421,7 +460,7 @@ const createdUser = await jsonSdk.jonApiSdkService
421
460
  .postOne();
422
461
 
423
462
  // Update entity - chainable API
424
- const updatedUser = await jsonSdk.jonApiSdkService
463
+ const updatedUser = await jsonSdk.jsonApiSdkService
425
464
  .entity<User>('Users', {
426
465
  id: 1,
427
466
  firstName: 'Jane'
@@ -429,12 +468,12 @@ const updatedUser = await jsonSdk.jonApiSdkService
429
468
  .patchOne();
430
469
 
431
470
  // Delete entity - chainable API
432
- await jsonSdk.jonApiSdkService
471
+ await jsonSdk.jsonApiSdkService
433
472
  .entity<User>('Users', { id: 1 })
434
473
  .deleteOne();
435
474
 
436
475
  // Work with relationships
437
- const userRelations = await jsonSdk.jonApiSdkService
476
+ const userRelations = await jsonSdk.jsonApiSdkService
438
477
  .entity<User>('Users', { id: 1 })
439
478
  .getRelationships('manager');
440
479
  ```
@@ -443,14 +482,14 @@ const userRelations = await jsonSdk.jonApiSdkService
443
482
 
444
483
  ```typescript
445
484
  // Get raw entity instance (third argument = true)
446
- const userEntity = jsonSdk.jonApiSdkService.entity<User>('Users', {
485
+ const userEntity = jsonSdk.jsonApiSdkService.entity<User>('Users', {
447
486
  firstName: 'John',
448
487
  lastName: 'Doe',
449
488
  login: 'johndoe'
450
489
  }, true);
451
490
 
452
491
  // Now use it with standard SDK methods
453
- const created = await jsonSdk.jonApiSdkService.postOne(userEntity);
492
+ const created = await jsonSdk.jsonApiSdkService.postOne(userEntity);
454
493
  ```
455
494
 
456
495
  ### Using String Type Names
@@ -459,15 +498,15 @@ GET methods also accept string type names instead of classes:
459
498
 
460
499
  ```typescript
461
500
  // Using string type name
462
- const users = await jsonSdk.jonApiSdkService.getAll<User>('Users', {
501
+ const users = await jsonSdk.jsonApiSdkService.getAll<User>('Users', {
463
502
  include: ['manager']
464
503
  });
465
504
 
466
- const user = await jsonSdk.jonApiSdkService.getOne<User>('Users', '1', {
505
+ const user = await jsonSdk.jsonApiSdkService.getOne<User>('Users', '1', {
467
506
  include: ['manager']
468
507
  });
469
508
 
470
- const userList = await jsonSdk.jonApiSdkService.getList<User>('Users', {
509
+ const userList = await jsonSdk.jsonApiSdkService.getList<User>('Users', {
471
510
  page: { number: 1, size: 10 }
472
511
  });
473
512
  ```
@@ -492,13 +531,13 @@ interface User {
492
531
  const jsonSdk = JsonApiJs({ apiHost: 'http://localhost:3000', apiPrefix: 'api' }, true);
493
532
 
494
533
  // Clear the manager relationship
495
- const user = jsonSdk.jonApiSdkService.entity<User>('Users', {
534
+ const user = jsonSdk.jsonApiSdkService.entity<User>('Users', {
496
535
  id: 1,
497
536
  firstName: 'John',
498
537
  manager: nullRef() // This will send { data: null } for the relationship
499
538
  }, true);
500
539
 
501
- const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
540
+ const updatedUser = await jsonSdk.jsonApiSdkService.patchOne(user);
502
541
  // Result: user.manager is now null
503
542
  ```
504
543
 
@@ -510,7 +549,7 @@ const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
510
549
  **Without nullRef:**
511
550
  ```typescript
512
551
  // This won't clear the relationship - it will be ignored
513
- const user = jsonSdk.jonApiSdkService.entity<User>('Users', {
552
+ const user = jsonSdk.jsonApiSdkService.entity<User>('Users', {
514
553
  id: 1,
515
554
  firstName: 'John',
516
555
  // manager is undefined - not included in request
@@ -520,13 +559,54 @@ const user = jsonSdk.jonApiSdkService.entity<User>('Users', {
520
559
  **With nullRef:**
521
560
  ```typescript
522
561
  // This explicitly clears the relationship
523
- const user = jsonSdk.jonApiSdkService.entity<User>('Users', {
562
+ const user = jsonSdk.jsonApiSdkService.entity<User>('Users', {
524
563
  id: 1,
525
564
  firstName: 'John',
526
565
  manager: nullRef() // Generates: relationships: { manager: { data: null } }
527
566
  }, true);
528
567
  ```
529
568
 
569
+ ## 🔗 Clearing To-Many Relationships
570
+
571
+ To clear all items from a to-many relationship (ManyToMany, OneToMany), use the `emptyArrayRef()` function:
572
+
573
+ ```typescript
574
+ import { JsonApiJs, emptyArrayRef } from '@klerick/json-api-nestjs-sdk';
575
+
576
+ interface User {
577
+ id?: number;
578
+ firstName: string;
579
+ roles?: Role[];
580
+ }
581
+
582
+ const jsonSdk = JsonApiJs({ apiHost: 'http://localhost:3000', apiPrefix: 'api' }, true);
583
+
584
+ // Clear all roles from user
585
+ const user = jsonSdk.jsonApiSdkService.entity<User>('Users', {
586
+ id: 1,
587
+ firstName: 'John',
588
+ roles: emptyArrayRef() // This will send { data: [] } for the relationship
589
+ }, true);
590
+
591
+ const updatedUser = await jsonSdk.jsonApiSdkService.patchOne(user);
592
+ // Result: user.roles is now an empty array
593
+ ```
594
+
595
+ **Why emptyArrayRef is needed:**
596
+ - An empty array `[]` would be treated as an attribute (not a relationship)
597
+ - `emptyArrayRef()` marks it as a relationship that should be cleared
598
+ - At runtime, the SDK generates `{ data: [] }` in the JSON:API request body
599
+
600
+ **Comparison:**
601
+ ```typescript
602
+ // ❌ This won't work - empty array treated as attribute
603
+ const user = { id: 1, roles: [] };
604
+
605
+ // ✅ This works - explicitly clears the relationship
606
+ const user = { id: 1, roles: emptyArrayRef() };
607
+ // Generates: relationships: { roles: { data: [] } }
608
+ ```
609
+
530
610
  ---
531
611
 
532
612
  ## 🔍 Query Options
@@ -554,7 +634,7 @@ enum FilterOperand {
554
634
 
555
635
  ```typescript
556
636
  // Equal
557
- const users = await jsonSdk.jonApiSdkService.getAll(Users, {
637
+ const users = await jsonSdk.jsonApiSdkService.getAll(Users, {
558
638
  filter: {
559
639
  target: {
560
640
  isActive: { [FilterOperand.eq]: 'true' }
@@ -563,7 +643,7 @@ const users = await jsonSdk.jonApiSdkService.getAll(Users, {
563
643
  });
564
644
 
565
645
  // Not equal
566
- const inactiveUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
646
+ const inactiveUsers = await jsonSdk.jsonApiSdkService.getAll(Users, {
567
647
  filter: {
568
648
  target: {
569
649
  isActive: { [FilterOperand.ne]: 'true' }
@@ -572,7 +652,7 @@ const inactiveUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
572
652
  });
573
653
 
574
654
  // In array
575
- const specificUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
655
+ const specificUsers = await jsonSdk.jsonApiSdkService.getAll(Users, {
576
656
  filter: {
577
657
  target: {
578
658
  id: { [FilterOperand.in]: ['1', '2', '3'] }
@@ -581,7 +661,7 @@ const specificUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
581
661
  });
582
662
 
583
663
  // LIKE search
584
- const searchUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
664
+ const searchUsers = await jsonSdk.jsonApiSdkService.getAll(Users, {
585
665
  filter: {
586
666
  target: {
587
667
  login: { [FilterOperand.like]: 'john' }
@@ -590,7 +670,7 @@ const searchUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
590
670
  });
591
671
 
592
672
  // Check null/not null
593
- const usersWithManager = await jsonSdk.jonApiSdkService.getAll(Users, {
673
+ const usersWithManager = await jsonSdk.jsonApiSdkService.getAll(Users, {
594
674
  filter: {
595
675
  target: {
596
676
  manager: { [FilterOperand.ne]: null }
@@ -598,7 +678,7 @@ const usersWithManager = await jsonSdk.jonApiSdkService.getAll(Users, {
598
678
  }
599
679
  });
600
680
 
601
- const usersWithoutManager = await jsonSdk.jonApiSdkService.getAll(Users, {
681
+ const usersWithoutManager = await jsonSdk.jsonApiSdkService.getAll(Users, {
602
682
  filter: {
603
683
  target: {
604
684
  manager: { [FilterOperand.eq]: null }
@@ -611,7 +691,7 @@ const usersWithoutManager = await jsonSdk.jonApiSdkService.getAll(Users, {
611
691
 
612
692
  ```typescript
613
693
  // Sort by single field
614
- const users = await jsonSdk.jonApiSdkService.getList(Users, {
694
+ const users = await jsonSdk.jsonApiSdkService.getList(Users, {
615
695
  sort: {
616
696
  target: {
617
697
  id: 'ASC'
@@ -620,7 +700,7 @@ const users = await jsonSdk.jonApiSdkService.getList(Users, {
620
700
  });
621
701
 
622
702
  // Sort by multiple fields
623
- const sortedUsers = await jsonSdk.jonApiSdkService.getList(Users, {
703
+ const sortedUsers = await jsonSdk.jsonApiSdkService.getList(Users, {
624
704
  sort: {
625
705
  target: {
626
706
  createdAt: 'DESC',
@@ -633,7 +713,7 @@ const sortedUsers = await jsonSdk.jonApiSdkService.getList(Users, {
633
713
  ### Pagination
634
714
 
635
715
  ```typescript
636
- const paginatedUsers = await jsonSdk.jonApiSdkService.getList(Users, {
716
+ const paginatedUsers = await jsonSdk.jsonApiSdkService.getList(Users, {
637
717
  page: {
638
718
  number: 1, // Page number (1-indexed)
639
719
  size: 20 // Items per page
@@ -645,17 +725,17 @@ const paginatedUsers = await jsonSdk.jonApiSdkService.getList(Users, {
645
725
 
646
726
  ```typescript
647
727
  // Include single relationship
648
- const users = await jsonSdk.jonApiSdkService.getAll(Users, {
728
+ const users = await jsonSdk.jsonApiSdkService.getAll(Users, {
649
729
  include: ['addresses']
650
730
  });
651
731
 
652
732
  // Include multiple relationships
653
- const usersWithAll = await jsonSdk.jonApiSdkService.getAll(Users, {
733
+ const usersWithAll = await jsonSdk.jsonApiSdkService.getAll(Users, {
654
734
  include: ['addresses', 'roles', 'comments', 'manager']
655
735
  });
656
736
 
657
737
  // Include nested relationships
658
- const usersWithNested = await jsonSdk.jonApiSdkService.getAll(Users, {
738
+ const usersWithNested = await jsonSdk.jsonApiSdkService.getAll(Users, {
659
739
  include: ['addresses', 'manager.addresses', 'roles']
660
740
  });
661
741
  ```
@@ -665,7 +745,7 @@ const usersWithNested = await jsonSdk.jonApiSdkService.getAll(Users, {
665
745
  Request only specific fields to reduce payload size.
666
746
 
667
747
  ```typescript
668
- const users = await jsonSdk.jonApiSdkService.getAll(Users, {
748
+ const users = await jsonSdk.jsonApiSdkService.getAll(Users, {
669
749
  fields: {
670
750
  users: ['firstName', 'lastName', 'email'],
671
751
  addresses: ['city', 'country']
@@ -760,28 +840,255 @@ const result = await jsonSdk
760
840
  // result[3] - array of all comment IDs after addition
761
841
  ```
762
842
 
763
- ### Using Temporary IDs (lid)
843
+ ### Using Local Identifiers (lid)
844
+
845
+ Create and reference resources within the same atomic request using local identifiers (`lid`). This allows you to establish relationships between resources being created in a single batch operation.
764
846
 
765
- Reference resources created within the same atomic request using temporary IDs.
847
+ **How it works:**
848
+ 1. Assign a temporary ID to the resource (any unique value - number or string)
849
+ 2. Reference this ID in relationships of other resources in the same request
850
+ 3. Server automatically replaces temporary IDs with real database IDs
766
851
 
852
+ **Example - Numeric lid:**
767
853
  ```typescript
768
854
  const address = new Addresses();
769
855
  address.city = 'Boston';
770
- address.id = 10000; // Temporary ID
856
+ address.id = 10000; // Temporary ID (lid)
771
857
 
772
858
  const user = new Users();
773
859
  user.firstName = 'Alice';
774
- user.addresses = address; // Reference by temp ID
860
+ user.addresses = address; // Reference resource by temporary ID
775
861
 
776
862
  const [createdAddress, createdUser] = await jsonSdk
777
863
  .atomicFactory()
778
- .postOne(address)
864
+ .postOne(address) // First operation: create address with lid=10000
865
+ .postOne(user) // Second operation: reference address via lid
866
+ .run();
867
+
868
+ // Server assigns real IDs and maintains relationships
869
+ console.log(createdAddress.id); // Real ID (e.g., 42)
870
+ console.log(createdUser.addresses.id); // Same real ID (42)
871
+ ```
872
+
873
+ **Example - UUID lid (for entities with UUID primary keys):**
874
+ ```typescript
875
+ const book1 = new BookList();
876
+ book1.id = '550e8400-e29b-41d4-a716-446655440000'; // Temporary UUID (lid)
877
+ book1.title = 'TypeScript Handbook';
878
+
879
+ const book2 = new BookList();
880
+ book2.id = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; // Another temporary UUID (lid)
881
+ book2.title = 'Advanced Node.js';
882
+
883
+ const user = new Users();
884
+ user.firstName = 'John';
885
+ user.books = [book1, book2]; // Reference both books by their temporary UUIDs
886
+
887
+ const [createdBook1, createdBook2, createdUser] = await jsonSdk
888
+ .atomicFactory()
889
+ .postOne(book1)
890
+ .postOne(book2)
779
891
  .postOne(user)
780
892
  .run();
781
893
 
782
- // Server assigns real IDs
783
- console.log(createdAddress.id); // Real ID (e.g., 1)
784
- console.log(createdUser.addresses.id); // Same real ID
894
+ // All temporary IDs are used as actual IDs (for UUID fields)
895
+ // or replaced with real IDs (for autoincrement fields)
896
+ console.log(createdBook1.id); // UUID from lid
897
+ console.log(createdBook2.id); // UUID from lid
898
+ console.log(createdUser.books.map(b => b.id)); // Both book UUIDs
899
+ ```
900
+
901
+ **Important notes:**
902
+ - Local identifiers (lid) are only valid within a single atomic request
903
+ - The SDK automatically handles lid assignment in the request body
904
+ - For numeric IDs: lid is replaced with the actual database-generated ID
905
+ - For UUID IDs: lid can be used as the actual ID (if server has `allowSetId: true`)
906
+
907
+ ---
908
+
909
+ ## 📤 Meta Support
910
+
911
+ The SDK supports passing `meta` objects with requests to send additional business logic data that doesn't belong to the entity itself, according to [JSON:API specification](https://jsonapi.org/format/#document-meta).
912
+
913
+ ### Regular Operations with Meta
914
+
915
+ ```typescript
916
+ // POST with meta
917
+ const newUser = new Users();
918
+ newUser.firstName = 'John';
919
+ newUser.lastName = 'Doe';
920
+ newUser.login = 'johndoe';
921
+
922
+ const createdUser = await jsonSdk.jsonApiSdkService.postOne(newUser, {
923
+ source: 'import',
924
+ batchId: '12345'
925
+ });
926
+
927
+ // Request body:
928
+ {
929
+ "data": {
930
+ "type": "users",
931
+ "attributes": { "firstName": "John", "lastName": "Doe", "login": "johndoe" }
932
+ },
933
+ "meta": {
934
+ "source": "import",
935
+ "batchId": "12345"
936
+ }
937
+ }
938
+
939
+ // PATCH with meta
940
+ user.firstName = 'Jane';
941
+ const updatedUser = await jsonSdk.jsonApiSdkService.patchOne(user, {
942
+ updatedBy: 'admin',
943
+ reason: 'name correction'
944
+ });
945
+
946
+ // Relationship operations with meta
947
+ await jsonSdk.jsonApiSdkService.postRelationships(user, 'roles', {
948
+ addedBy: 'admin',
949
+ timestamp: Date.now()
950
+ });
951
+
952
+ await jsonSdk.jsonApiSdkService.patchRelationships(user, 'roles', {
953
+ source: 'sync',
954
+ syncId: 'abc123'
955
+ });
956
+
957
+ await jsonSdk.jsonApiSdkService.deleteRelationships(user, 'roles', {
958
+ removedBy: 'admin',
959
+ reason: 'access revoked'
960
+ });
961
+ ```
962
+
963
+ **Important:** `deleteOne()` does NOT support meta because HTTP DELETE for resources has no body according to JSON:API spec. However, `deleteRelationships()` DOES support meta because relationship deletion endpoints use a request body.
964
+
965
+ ### Entity Chain with Meta
966
+
967
+ ```typescript
968
+ // Chain mode - Observable
969
+ const user = await jsonSdk.jsonApiSdkService
970
+ .entity('Users', {
971
+ firstName: 'John',
972
+ lastName: 'Doe',
973
+ login: 'johndoe'
974
+ })
975
+ .postOne({ source: 'mobile-app', version: '2.0' });
976
+
977
+ // Update with meta
978
+ await jsonSdk.jsonApiSdkService
979
+ .entity('Users', { id: 1, firstName: 'Jane' })
980
+ .patchOne({ updatedBy: 'user-123' });
981
+
982
+ // Relationship operations with meta
983
+ await jsonSdk.jsonApiSdkService
984
+ .entity('Users', user)
985
+ .patchRelationships('roles', { operation: 'bulk-update' });
986
+ ```
987
+
988
+ ### Atomic Operations with Meta
989
+
990
+ Each operation in an atomic request can have its own meta:
991
+
992
+ ```typescript
993
+ const address1 = new Addresses();
994
+ address1.id = 999; // Local identifier (lid)
995
+ address1.city = 'New York';
996
+
997
+ const address2 = new Addresses();
998
+ address2.id = 1000; // Local identifier (lid)
999
+ address2.city = 'Boston';
1000
+
1001
+ const user1 = new Users();
1002
+ user1.firstName = 'Alice';
1003
+ user1.addresses = address1;
1004
+
1005
+ const user2 = new Users();
1006
+ user2.firstName = 'Bob';
1007
+ user2.addresses = address2;
1008
+
1009
+ const [addr1, addr2, createdUser1, createdUser2] = await jsonSdk
1010
+ .atomicFactory()
1011
+ .postOne(address1)
1012
+ .postOne(address2)
1013
+ .postOne(user1, { source: 'import', priority: 'high' }) // User 1 with meta
1014
+ .postOne(user2, { source: 'import', priority: 'normal' }) // User 2 with different meta
1015
+ .run();
1016
+
1017
+ // Request body:
1018
+ {
1019
+ "atomic:operations": [
1020
+ {
1021
+ "op": "add",
1022
+ "ref": { "type": "addresses", "lid": "999" },
1023
+ "data": { "type": "addresses", "attributes": { "city": "New York" } }
1024
+ },
1025
+ {
1026
+ "op": "add",
1027
+ "ref": { "type": "addresses", "lid": "1000" },
1028
+ "data": { "type": "addresses", "attributes": { "city": "Boston" } }
1029
+ },
1030
+ {
1031
+ "op": "add",
1032
+ "ref": { "type": "users" },
1033
+ "data": {
1034
+ "type": "users",
1035
+ "attributes": { "firstName": "Alice" },
1036
+ "relationships": { "addresses": { "data": { "type": "addresses", "id": "999" } } }
1037
+ },
1038
+ "meta": { "source": "import", "priority": "high" } // Meta for operation 3
1039
+ },
1040
+ {
1041
+ "op": "add",
1042
+ "ref": { "type": "users" },
1043
+ "data": {
1044
+ "type": "users",
1045
+ "attributes": { "firstName": "Bob" },
1046
+ "relationships": { "addresses": { "data": { "type": "addresses", "id": "1000" } } }
1047
+ },
1048
+ "meta": { "source": "import", "priority": "normal" } // Meta for operation 4
1049
+ }
1050
+ ]
1051
+ }
1052
+ ```
1053
+
1054
+ ### Meta with Entity Chain in Atomic Operations
1055
+
1056
+ ```typescript
1057
+ const addressEntity = jsonSdk.jsonApiSdkService.entity('Addresses', {
1058
+ id: 999,
1059
+ city: 'New York'
1060
+ }, true);
1061
+
1062
+ const userEntity = jsonSdk.jsonApiSdkService.entity('Users', {
1063
+ firstName: 'Alice',
1064
+ addresses: jsonSdk.jsonApiSdkService.entity('Addresses', { id: 999 }, true)
1065
+ }, true);
1066
+
1067
+ const [createdAddress, createdUser] = await jsonSdk
1068
+ .atomicFactory()
1069
+ .postOne(addressEntity)
1070
+ .postOne(userEntity, { source: 'import', batchId: '12345' })
1071
+ .run();
1072
+ ```
1073
+
1074
+ ### Operations WITHOUT Meta Support
1075
+
1076
+ The following operations do NOT accept meta parameter:
1077
+
1078
+ ```typescript
1079
+ // ❌ deleteOne - HTTP DELETE has no body
1080
+ await jsonSdk.jsonApiSdkService.deleteOne(user);
1081
+ // Cannot pass meta here
1082
+
1083
+ // ❌ deleteOne in atomic operations
1084
+ await jsonSdk.atomicFactory()
1085
+ .deleteOne(user) // No meta parameter
1086
+ .run();
1087
+
1088
+ // ✅ But deleteRelationships DOES support meta (has body)
1089
+ await jsonSdk.jsonApiSdkService.deleteRelationships(user, 'roles', {
1090
+ removedBy: 'admin'
1091
+ });
785
1092
  ```
786
1093
 
787
1094
  ---