@klerick/json-api-nestjs-sdk 10.0.0-beta.1 → 10.0.0-beta.10

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 (88) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +811 -0
  3. package/cjs/src/index.js +2 -1
  4. package/cjs/src/index.js.map +1 -1
  5. package/cjs/src/lib/json-api-js.js +21 -2
  6. package/cjs/src/lib/json-api-js.js.map +1 -1
  7. package/cjs/src/lib/service/atomic-operations.service.js.map +1 -1
  8. package/cjs/src/lib/service/fetch-inner-client.js +8 -2
  9. package/cjs/src/lib/service/fetch-inner-client.js.map +1 -1
  10. package/cjs/src/lib/service/json-api-sdk.service.js +42 -14
  11. package/cjs/src/lib/service/json-api-sdk.service.js.map +1 -1
  12. package/cjs/src/lib/service/json-api-utils.service.js +35 -21
  13. package/cjs/src/lib/service/json-api-utils.service.js.map +1 -1
  14. package/cjs/src/lib/types/entity-chain.js +3 -0
  15. package/cjs/src/lib/types/entity-chain.js.map +1 -0
  16. package/cjs/src/lib/types/index.js +1 -0
  17. package/cjs/src/lib/types/index.js.map +1 -1
  18. package/cjs/src/lib/utils/adapter-for-axios.js.map +1 -1
  19. package/cjs/src/lib/utils/generate-atomic-body.js +14 -2
  20. package/cjs/src/lib/utils/generate-atomic-body.js.map +1 -1
  21. package/cjs/src/lib/utils/http-params.js +1 -1
  22. package/cjs/src/lib/utils/http-params.js.map +1 -1
  23. package/cjs/src/lib/utils/utils.js +26 -2
  24. package/cjs/src/lib/utils/utils.js.map +1 -1
  25. package/mjs/src/index.d.ts +2 -2
  26. package/mjs/src/index.js +1 -1
  27. package/mjs/src/index.js.map +1 -1
  28. package/mjs/src/lib/json-api-js.js +20 -0
  29. package/mjs/src/lib/json-api-js.js.map +1 -1
  30. package/mjs/src/lib/service/atomic-operations.service.d.ts +2 -2
  31. package/mjs/src/lib/service/atomic-operations.service.js.map +1 -1
  32. package/mjs/src/lib/service/json-api-sdk.service.d.ts +8 -3
  33. package/mjs/src/lib/service/json-api-sdk.service.js +28 -7
  34. package/mjs/src/lib/service/json-api-sdk.service.js.map +1 -1
  35. package/mjs/src/lib/service/json-api-utils.service.js +5 -2
  36. package/mjs/src/lib/service/json-api-utils.service.js.map +1 -1
  37. package/mjs/src/lib/types/atomic-operation.d.ts +4 -4
  38. package/mjs/src/lib/types/atomic-operation.js +0 -1
  39. package/mjs/src/lib/types/config.js +0 -1
  40. package/mjs/src/lib/types/entity-chain.d.ts +32 -0
  41. package/mjs/src/lib/types/entity-chain.js +1 -0
  42. package/mjs/src/lib/types/entity-chain.js.map +1 -0
  43. package/mjs/src/lib/types/http-inner-client.js +0 -1
  44. package/mjs/src/lib/types/http-request-params.js +0 -1
  45. package/mjs/src/lib/types/index.d.ts +1 -0
  46. package/mjs/src/lib/types/index.js +1 -0
  47. package/mjs/src/lib/types/index.js.map +1 -1
  48. package/mjs/src/lib/types/promise-json-api-sdk.d.ts +14 -3
  49. package/mjs/src/lib/types/promise-json-api-sdk.js +0 -1
  50. package/mjs/src/lib/types/query-params.d.ts +3 -3
  51. package/mjs/src/lib/types/utils.js +0 -1
  52. package/mjs/src/lib/utils/adapter-for-axios.d.ts +2 -2
  53. package/mjs/src/lib/utils/adapter-for-axios.js.map +1 -1
  54. package/mjs/src/lib/utils/generate-atomic-body.js +3 -0
  55. package/mjs/src/lib/utils/generate-atomic-body.js.map +1 -1
  56. package/mjs/src/lib/utils/utils.d.ts +9 -0
  57. package/mjs/src/lib/utils/utils.js +17 -0
  58. package/mjs/src/lib/utils/utils.js.map +1 -1
  59. package/package.json +26 -27
  60. package/cjs/package.json +0 -73
  61. package/cjs/src/index.d.ts +0 -5
  62. package/cjs/src/lib/constants/index.d.ts +0 -1
  63. package/cjs/src/lib/json-api-angular.d.ts +0 -21
  64. package/cjs/src/lib/json-api-js.d.ts +0 -15
  65. package/cjs/src/lib/service/atomic-operations.service.d.ts +0 -22
  66. package/cjs/src/lib/service/fetch-inner-client.d.ts +0 -20
  67. package/cjs/src/lib/service/index.d.ts +0 -3
  68. package/cjs/src/lib/service/json-api-sdk.service.d.ts +0 -21
  69. package/cjs/src/lib/service/json-api-utils.service.d.ts +0 -28
  70. package/cjs/src/lib/token/index.d.ts +0 -4
  71. package/cjs/src/lib/types/atomic-operation.d.ts +0 -33
  72. package/cjs/src/lib/types/atomic-type.d.ts +0 -23
  73. package/cjs/src/lib/types/config.d.ts +0 -14
  74. package/cjs/src/lib/types/filter-operand.d.ts +0 -10
  75. package/cjs/src/lib/types/http-inner-client.d.ts +0 -19
  76. package/cjs/src/lib/types/http-request-params.d.ts +0 -20
  77. package/cjs/src/lib/types/index.d.ts +0 -9
  78. package/cjs/src/lib/types/promise-json-api-sdk.d.ts +0 -16
  79. package/cjs/src/lib/types/query-params.d.ts +0 -47
  80. package/cjs/src/lib/types/utils.d.ts +0 -6
  81. package/cjs/src/lib/utils/adapter-for-axios.d.ts +0 -3
  82. package/cjs/src/lib/utils/entity-array.d.ts +0 -9
  83. package/cjs/src/lib/utils/generate-atomic-body.d.ts +0 -22
  84. package/cjs/src/lib/utils/http-params.d.ts +0 -154
  85. package/cjs/src/lib/utils/index.d.ts +0 -5
  86. package/cjs/src/lib/utils/utils.d.ts +0 -4
  87. package/cjs/src/ngModule.d.ts +0 -4
  88. package/mjs/package.json +0 -75
package/README.md ADDED
@@ -0,0 +1,811 @@
1
+ <p align='center'>
2
+ <a href="https://www.npmjs.com/package/@klerick/json-api-nestjs-sdk" target="_blank"><img src="https://img.shields.io/npm/v/@klerick/json-api-nestjs-sdk.svg" alt="NPM Version" /></a>
3
+ <a href="https://www.npmjs.com/package/@klerick/json-api-nestjs-sdk" target="_blank"><img src="https://img.shields.io/npm/l/@klerick/json-api-nestjs-sdk.svg" alt="Package License" /></a>
4
+ <a href="https://www.npmjs.com/package/@klerick/json-api-nestjs-sdk" target="_blank"><img src="https://img.shields.io/npm/dm/json-api-nestjs-sdk.svg" alt="NPM Downloads" /></a>
5
+ <a href="http://commitizen.github.io/cz-cli/" target="_blank"><img src="https://img.shields.io/badge/commitizen-friendly-brightgreen.svg" alt="Commitizen friendly" /></a>
6
+ <img src="https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/klerick/02a4c98cf7008fea2af70dc2d50f4cb7/raw/json-api-nestjs-sdk.json" alt="Coverage Badge" />
7
+ </p>
8
+
9
+ # JSON:API Client SDK
10
+
11
+ Type-safe TypeScript/JavaScript client for consuming [JSON:API](https://jsonapi.org/) endpoints built with [@klerick/json-api-nestjs](https://www.npmjs.com/package/@klerick/json-api-nestjs).
12
+
13
+ ## ✨ Features
14
+
15
+ - 🎯 **Full Type Safety** - Complete TypeScript support with type inference from your entities
16
+ - 🔍 **Advanced Filtering** - Rich query builder with operators (eq, ne, in, like, gt, lt, etc.)
17
+ - 📦 **Relationship Handling** - Easy include, sparse fieldsets, and relationship management
18
+ - ⚡ **Atomic Operations** - Batch multiple operations in a single request with rollback support
19
+ - 🌐 **Multiple HTTP Clients** - Works with Axios, Fetch API, and Angular HttpClient
20
+ - 📄 **Pagination & Sorting** - Built-in support for pagination and multi-field sorting
21
+ - 🔄 **Observable or Promise** - Choose your async style (RxJS Observable or native Promise)
22
+ - 🔗 **Relationship Operations** - Post, patch, and delete relationships independently
23
+
24
+ ## 📚 Table of Contents
25
+
26
+ - [Installation](#installation)
27
+ - [Quick Start](#-quick-start)
28
+ - [Basic Setup (Axios)](#basic-setup-axios)
29
+ - [Angular Setup](#angular-setup)
30
+ - [Configuration](#-configuration)
31
+ - [API Methods](#-api-methods)
32
+ - [Fetching Resources](#fetching-resources)
33
+ - [Creating Resources](#creating-resources)
34
+ - [Updating Resources](#updating-resources)
35
+ - [Deleting Resources](#deleting-resources)
36
+ - [Relationship Operations](#relationship-operations)
37
+ - [Working with Plain Objects](#-working-with-plain-objects)
38
+ - [Using entity() Method](#using-entity-method)
39
+ - [Using String Type Names](#using-string-type-names)
40
+ - [Nullifying Relationships](#-nullifying-relationships)
41
+ - [Query Options](#-query-options)
42
+ - [Filtering](#filtering)
43
+ - [Sorting](#sorting)
44
+ - [Pagination](#pagination)
45
+ - [Including Relationships](#including-relationships)
46
+ - [Sparse Fieldsets](#sparse-fieldsets)
47
+ - [Atomic Operations](#-atomic-operations)
48
+ - [Examples](#-examples)
49
+
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ npm install @klerick/json-api-nestjs-sdk
55
+ ```
56
+
57
+ ---
58
+
59
+ ## 🚀 Quick Start
60
+
61
+ ### Basic Setup (Axios)
62
+
63
+ ```typescript
64
+ import { JsonApiJs, adapterForAxios, FilterOperand } from '@klerick/json-api-nestjs-sdk';
65
+ import axios from 'axios';
66
+ import { Users } from './entities'; // Your entity classes
67
+
68
+ // 1. Create adapter
69
+ const axiosAdapter = adapterForAxios(axios);
70
+
71
+ // 2. Configure SDK
72
+ const jsonSdk = JsonApiJs(
73
+ {
74
+ adapter: axiosAdapter,
75
+ apiHost: 'http://localhost:3000',
76
+ apiPrefix: 'api',
77
+ dateFields: ['createdAt', 'updatedAt'],
78
+ operationUrl: 'operation',
79
+ },
80
+ true // true = return Promises, false = return Observables
81
+ );
82
+
83
+ // 3. Use SDK
84
+ // Fetch all users
85
+ const users = await jsonSdk.jonApiSdkService.getAll(Users);
86
+
87
+ // Fetch with filtering and relationships
88
+ const activeUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
89
+ filter: {
90
+ target: {
91
+ isActive: { [FilterOperand.eq]: 'true' }
92
+ }
93
+ },
94
+ include: ['addresses', 'roles']
95
+ });
96
+
97
+ // Get one user
98
+ const user = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
99
+ include: ['addresses', 'comments', 'roles', 'manager']
100
+ });
101
+
102
+ // Create a user
103
+ const newUser = new Users();
104
+ newUser.firstName = 'John';
105
+ newUser.lastName = 'Doe';
106
+ newUser.login = 'johndoe';
107
+ newUser.isActive = true;
108
+
109
+ const createdUser = await jsonSdk.jonApiSdkService.postOne(newUser);
110
+
111
+ // Update a user
112
+ createdUser.firstName = 'Jane';
113
+ const updatedUser = await jsonSdk.jonApiSdkService.patchOne(createdUser);
114
+
115
+ // Delete a user
116
+ await jsonSdk.jonApiSdkService.deleteOne(createdUser);
117
+ ```
118
+
119
+ ### Angular Setup
120
+
121
+ ```typescript
122
+ import {
123
+ provideJsonApi,
124
+ AtomicFactory,
125
+ JsonApiSdkService
126
+ } from '@klerick/json-api-nestjs-sdk/ngModule';
127
+ import {
128
+ provideHttpClient,
129
+ withFetch,
130
+ } from '@angular/common/http';
131
+ import { Component, inject } from '@angular/core';
132
+ import { bootstrapApplication } from '@angular/platform-browser';
133
+
134
+ // 1. Configure in your main.ts or app.config.ts
135
+ const angularConfig = {
136
+ apiHost: 'http://localhost:3000',
137
+ idKey: 'id',
138
+ apiPrefix: 'api',
139
+ operationUrl: 'operation',
140
+ dateFields: ['createdAt', 'updatedAt']
141
+ };
142
+
143
+ bootstrapApplication(AppComponent, {
144
+ providers: [
145
+ provideHttpClient(withFetch()),
146
+ provideJsonApi(angularConfig)
147
+ ],
148
+ }).catch((err) => console.error(err));
149
+
150
+ // 2. Use in your components
151
+ @Component({
152
+ standalone: true,
153
+ selector: 'app-users',
154
+ templateUrl: './users.component.html',
155
+ })
156
+ export class UsersComponent {
157
+ private jsonApiService = inject(JsonApiSdkService);
158
+ private atomicFactory = inject(AtomicFactory);
159
+
160
+ async loadUsers() {
161
+ const users = await this.jsonApiService.getAll(Users, {
162
+ include: ['addresses']
163
+ });
164
+ return users;
165
+ }
166
+
167
+ async createMultipleResources() {
168
+ const result = await this.atomicFactory()
169
+ .postOne(newUser)
170
+ .postOne(newAddress)
171
+ .run();
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## ⚙️ Configuration
177
+
178
+ ### JsonConfig Type
179
+
180
+ ```typescript
181
+ type JsonSdkConfig = {
182
+ apiHost: string; // Base URL of your API (e.g., 'http://localhost:3000')
183
+ apiPrefix?: string; // API prefix (e.g., 'api' -> '/api/users')
184
+ idKey?: string; // Name of ID field (default: 'id')
185
+ idIsNumber?: boolean; // Parse IDs as numbers (default: false)
186
+ operationUrl?: string; // URL path for atomic operations (default: 'operation')
187
+ dateFields?: string[]; // Fields to convert to Date objects (e.g., ['createdAt', 'updatedAt'])
188
+ }
189
+
190
+ type JsonConfig = JsonSdkConfig & {
191
+ adapter?: HttpInnerClient; // HTTP client adapter (default: fetch)
192
+ }
193
+ ```
194
+
195
+ ### HTTP Adapters
196
+
197
+ **Axios Adapter:**
198
+ ```typescript
199
+ import { adapterForAxios } from '@klerick/json-api-nestjs-sdk';
200
+ import axios from 'axios';
201
+
202
+ const adapter = adapterForAxios(axios);
203
+ ```
204
+
205
+ **Fetch API (default):**
206
+ ```typescript
207
+ // No adapter needed, fetch is used by default
208
+ const jsonSdk = JsonApiJs({
209
+ apiHost: 'http://localhost:3000',
210
+ apiPrefix: 'api',
211
+ }, true);
212
+ ```
213
+
214
+ **Custom Adapter:**
215
+
216
+ See [HttpInnerClient interface](https://github.com/klerick/nestjs-json-api/blob/master/libs/json-api/json-api-nestjs-sdk/src/lib/types/http-inner-client.ts) for implementation details.
217
+
218
+ ---
219
+
220
+ ## 📖 API Methods
221
+
222
+ ### Fetching Resources
223
+
224
+ #### `getAll(Entity, options?)`
225
+
226
+ Fetch all resources with optional filtering, sorting, and relationships.
227
+
228
+ ```typescript
229
+ import { FilterOperand } from '@klerick/json-api-nestjs-sdk';
230
+
231
+ // Fetch all users
232
+ const users = await jsonSdk.jonApiSdkService.getAll(Users);
233
+
234
+ // With filtering
235
+ const activeUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
236
+ filter: {
237
+ target: {
238
+ isActive: { [FilterOperand.eq]: 'true' },
239
+ id: { [FilterOperand.in]: ['1', '2', '3'] }
240
+ }
241
+ },
242
+ include: ['addresses', 'roles']
243
+ });
244
+
245
+ // Filter by relationship
246
+ const usersWithRoles = await jsonSdk.jonApiSdkService.getAll(Users, {
247
+ filter: {
248
+ target: {
249
+ id: { [FilterOperand.in]: ['1', '2'] }
250
+ },
251
+ roles: {
252
+ name: { [FilterOperand.eq]: 'admin' }
253
+ }
254
+ },
255
+ include: ['roles']
256
+ });
257
+ ```
258
+
259
+ #### `getList(Entity, options)`
260
+
261
+ Fetch resources with pagination (returns paginated results).
262
+
263
+ ```typescript
264
+ const firstPage = await jsonSdk.jonApiSdkService.getList(Users, {
265
+ page: {
266
+ number: 1,
267
+ size: 10
268
+ },
269
+ sort: {
270
+ target: {
271
+ id: 'ASC'
272
+ }
273
+ }
274
+ });
275
+
276
+ const secondPage = await jsonSdk.jonApiSdkService.getList(Users, {
277
+ page: {
278
+ number: 2,
279
+ size: 10
280
+ },
281
+ sort: {
282
+ target: {
283
+ createdAt: 'DESC'
284
+ }
285
+ }
286
+ });
287
+ ```
288
+
289
+ #### `getOne(Entity, id, options?)`
290
+
291
+ Fetch a single resource by ID.
292
+
293
+ ```typescript
294
+ // Simple fetch
295
+ const user = await jsonSdk.jonApiSdkService.getOne(Users, '1');
296
+
297
+ // With relationships
298
+ const userWithRelations = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
299
+ include: ['addresses', 'comments', 'roles', 'manager']
300
+ });
301
+
302
+ // With sparse fieldsets
303
+ const userPartial = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
304
+ fields: {
305
+ users: ['firstName', 'lastName', 'email']
306
+ }
307
+ });
308
+ ```
309
+
310
+ ### Creating Resources
311
+
312
+ #### `postOne(entity, options?)`
313
+
314
+ Create a new resource.
315
+
316
+ ```typescript
317
+ // Simple create
318
+ const newUser = new Users();
319
+ newUser.firstName = 'John';
320
+ newUser.lastName = 'Doe';
321
+ newUser.login = 'johndoe';
322
+ newUser.isActive = true;
323
+
324
+ const createdUser = await jsonSdk.jonApiSdkService.postOne(newUser);
325
+
326
+ // Create with relationships
327
+ const newAddress = new Addresses();
328
+ newAddress.city = 'New York';
329
+ newAddress.state = 'NY';
330
+ newAddress.country = 'USA';
331
+
332
+ const savedAddress = await jsonSdk.jonApiSdkService.postOne(newAddress);
333
+
334
+ const user = new Users();
335
+ user.firstName = 'Jane';
336
+ user.lastName = 'Doe';
337
+ user.login = 'janedoe';
338
+ user.addresses = savedAddress; // Set relationship
339
+
340
+ const createdUser = await jsonSdk.jonApiSdkService.postOne(user);
341
+ ```
342
+
343
+ ### Updating Resources
344
+
345
+ #### `patchOne(entity, options?)`
346
+
347
+ Update an existing resource.
348
+
349
+ ```typescript
350
+ // Update attributes
351
+ user.firstName = 'Updated Name';
352
+ const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
353
+
354
+ // Update relationships
355
+ const newAddress = await jsonSdk.jonApiSdkService.postOne(addressEntity);
356
+ user.addresses = newAddress;
357
+
358
+ const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
359
+ ```
360
+
361
+ ### Deleting Resources
362
+
363
+ #### `deleteOne(entity)`
364
+
365
+ Delete a resource.
366
+
367
+ ```typescript
368
+ await jsonSdk.jonApiSdkService.deleteOne(user);
369
+ ```
370
+
371
+ ### Relationship Operations
372
+
373
+ #### `deleteRelationships(entity, relationshipName)`
374
+
375
+ Remove relationships without deleting the related resources.
376
+
377
+ ```typescript
378
+ // Remove all roles from user
379
+ await jsonSdk.jonApiSdkService.deleteRelationships(user, 'roles');
380
+
381
+ // Remove manager from user
382
+ await jsonSdk.jonApiSdkService.deleteRelationships(user, 'manager');
383
+
384
+ // Remove all comments from user
385
+ await jsonSdk.jonApiSdkService.deleteRelationships(user, 'comments');
386
+ ```
387
+
388
+ ---
389
+
390
+ ## 🏗️ Working with Plain Objects
391
+
392
+ In monorepo environments or when sharing types between frontend and backend, you may want to use plain TypeScript types/interfaces instead of classes. The SDK provides tools to work with plain objects while maintaining full type safety.
393
+
394
+ ### Using entity() Method
395
+
396
+ The `entity()` method creates a properly typed entity instance from a plain object. This is essential when:
397
+ - You share types (not classes) between frontend and backend
398
+ - The SDK needs to identify the resource type at runtime (via `constructor.name`)
399
+
400
+ ```typescript
401
+ import { JsonApiJs } from '@klerick/json-api-nestjs-sdk';
402
+
403
+ // Shared type (not a class)
404
+ interface User {
405
+ id?: number;
406
+ firstName: string;
407
+ lastName: string;
408
+ login: string;
409
+ manager?: User | null;
410
+ }
411
+
412
+ const jsonSdk = JsonApiJs({ apiHost: 'http://localhost:3000', apiPrefix: 'api' }, true);
413
+
414
+ // Create entity from plain object - chainable API
415
+ const createdUser = await jsonSdk.jonApiSdkService
416
+ .entity<User>('Users', {
417
+ firstName: 'John',
418
+ lastName: 'Doe',
419
+ login: 'johndoe'
420
+ })
421
+ .postOne();
422
+
423
+ // Update entity - chainable API
424
+ const updatedUser = await jsonSdk.jonApiSdkService
425
+ .entity<User>('Users', {
426
+ id: 1,
427
+ firstName: 'Jane'
428
+ })
429
+ .patchOne();
430
+
431
+ // Delete entity - chainable API
432
+ await jsonSdk.jonApiSdkService
433
+ .entity<User>('Users', { id: 1 })
434
+ .deleteOne();
435
+
436
+ // Work with relationships
437
+ const userRelations = await jsonSdk.jonApiSdkService
438
+ .entity<User>('Users', { id: 1 })
439
+ .getRelationships('manager');
440
+ ```
441
+
442
+ **Raw mode** - get the entity instance without chaining:
443
+
444
+ ```typescript
445
+ // Get raw entity instance (third argument = true)
446
+ const userEntity = jsonSdk.jonApiSdkService.entity<User>('Users', {
447
+ firstName: 'John',
448
+ lastName: 'Doe',
449
+ login: 'johndoe'
450
+ }, true);
451
+
452
+ // Now use it with standard SDK methods
453
+ const created = await jsonSdk.jonApiSdkService.postOne(userEntity);
454
+ ```
455
+
456
+ ### Using String Type Names
457
+
458
+ GET methods also accept string type names instead of classes:
459
+
460
+ ```typescript
461
+ // Using string type name
462
+ const users = await jsonSdk.jonApiSdkService.getAll<User>('Users', {
463
+ include: ['manager']
464
+ });
465
+
466
+ const user = await jsonSdk.jonApiSdkService.getOne<User>('Users', '1', {
467
+ include: ['manager']
468
+ });
469
+
470
+ const userList = await jsonSdk.jonApiSdkService.getList<User>('Users', {
471
+ page: { number: 1, size: 10 }
472
+ });
473
+ ```
474
+
475
+ ---
476
+
477
+ ## 🔗 Nullifying Relationships
478
+
479
+ To clear a relationship (set it to `null`), use the `nullRef()` function. This is necessary because the SDK distinguishes between:
480
+ - **Missing relationship** - not included in the request (no change)
481
+ - **Null relationship** - explicitly set to `null` (clear the relationship)
482
+
483
+ ```typescript
484
+ import { JsonApiJs, nullRef } from '@klerick/json-api-nestjs-sdk';
485
+
486
+ interface User {
487
+ id?: number;
488
+ firstName: string;
489
+ manager?: User | null;
490
+ }
491
+
492
+ const jsonSdk = JsonApiJs({ apiHost: 'http://localhost:3000', apiPrefix: 'api' }, true);
493
+
494
+ // Clear the manager relationship
495
+ const user = jsonSdk.jonApiSdkService.entity<User>('Users', {
496
+ id: 1,
497
+ firstName: 'John',
498
+ manager: nullRef() // This will send { data: null } for the relationship
499
+ }, true);
500
+
501
+ const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
502
+ // Result: user.manager is now null
503
+ ```
504
+
505
+ **How it works:**
506
+ - `nullRef()` returns a special marker object that TypeScript sees as `null`
507
+ - At runtime, the SDK detects this marker and generates `{ data: null }` in the JSON:API request body
508
+ - The server then clears the relationship
509
+
510
+ **Without nullRef:**
511
+ ```typescript
512
+ // This won't clear the relationship - it will be ignored
513
+ const user = jsonSdk.jonApiSdkService.entity<User>('Users', {
514
+ id: 1,
515
+ firstName: 'John',
516
+ // manager is undefined - not included in request
517
+ }, true);
518
+ ```
519
+
520
+ **With nullRef:**
521
+ ```typescript
522
+ // This explicitly clears the relationship
523
+ const user = jsonSdk.jonApiSdkService.entity<User>('Users', {
524
+ id: 1,
525
+ firstName: 'John',
526
+ manager: nullRef() // Generates: relationships: { manager: { data: null } }
527
+ }, true);
528
+ ```
529
+
530
+ ---
531
+
532
+ ## 🔍 Query Options
533
+
534
+ ### Filtering
535
+
536
+ Available operators:
537
+
538
+ ```typescript
539
+ enum FilterOperand {
540
+ eq = 'eq', // Equal
541
+ ne = 'ne', // Not equal
542
+ in = 'in', // In array
543
+ nin = 'nin', // Not in array
544
+ lt = 'lt', // Less than
545
+ lte = 'lte', // Less than or equal
546
+ gt = 'gt', // Greater than
547
+ gte = 'gte', // Greater than or equal
548
+ like = 'like', // SQL LIKE
549
+ re = 'regexp', // Regular expression
550
+ }
551
+ ```
552
+
553
+ **Examples:**
554
+
555
+ ```typescript
556
+ // Equal
557
+ const users = await jsonSdk.jonApiSdkService.getAll(Users, {
558
+ filter: {
559
+ target: {
560
+ isActive: { [FilterOperand.eq]: 'true' }
561
+ }
562
+ }
563
+ });
564
+
565
+ // Not equal
566
+ const inactiveUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
567
+ filter: {
568
+ target: {
569
+ isActive: { [FilterOperand.ne]: 'true' }
570
+ }
571
+ }
572
+ });
573
+
574
+ // In array
575
+ const specificUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
576
+ filter: {
577
+ target: {
578
+ id: { [FilterOperand.in]: ['1', '2', '3'] }
579
+ }
580
+ }
581
+ });
582
+
583
+ // LIKE search
584
+ const searchUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
585
+ filter: {
586
+ target: {
587
+ login: { [FilterOperand.like]: 'john' }
588
+ }
589
+ }
590
+ });
591
+
592
+ // Check null/not null
593
+ const usersWithManager = await jsonSdk.jonApiSdkService.getAll(Users, {
594
+ filter: {
595
+ target: {
596
+ manager: { [FilterOperand.ne]: null }
597
+ }
598
+ }
599
+ });
600
+
601
+ const usersWithoutManager = await jsonSdk.jonApiSdkService.getAll(Users, {
602
+ filter: {
603
+ target: {
604
+ manager: { [FilterOperand.eq]: null }
605
+ }
606
+ }
607
+ });
608
+ ```
609
+
610
+ ### Sorting
611
+
612
+ ```typescript
613
+ // Sort by single field
614
+ const users = await jsonSdk.jonApiSdkService.getList(Users, {
615
+ sort: {
616
+ target: {
617
+ id: 'ASC'
618
+ }
619
+ }
620
+ });
621
+
622
+ // Sort by multiple fields
623
+ const sortedUsers = await jsonSdk.jonApiSdkService.getList(Users, {
624
+ sort: {
625
+ target: {
626
+ createdAt: 'DESC',
627
+ lastName: 'ASC'
628
+ }
629
+ }
630
+ });
631
+ ```
632
+
633
+ ### Pagination
634
+
635
+ ```typescript
636
+ const paginatedUsers = await jsonSdk.jonApiSdkService.getList(Users, {
637
+ page: {
638
+ number: 1, // Page number (1-indexed)
639
+ size: 20 // Items per page
640
+ }
641
+ });
642
+ ```
643
+
644
+ ### Including Relationships
645
+
646
+ ```typescript
647
+ // Include single relationship
648
+ const users = await jsonSdk.jonApiSdkService.getAll(Users, {
649
+ include: ['addresses']
650
+ });
651
+
652
+ // Include multiple relationships
653
+ const usersWithAll = await jsonSdk.jonApiSdkService.getAll(Users, {
654
+ include: ['addresses', 'roles', 'comments', 'manager']
655
+ });
656
+
657
+ // Include nested relationships
658
+ const usersWithNested = await jsonSdk.jonApiSdkService.getAll(Users, {
659
+ include: ['addresses', 'manager.addresses', 'roles']
660
+ });
661
+ ```
662
+
663
+ ### Sparse Fieldsets
664
+
665
+ Request only specific fields to reduce payload size.
666
+
667
+ ```typescript
668
+ const users = await jsonSdk.jonApiSdkService.getAll(Users, {
669
+ fields: {
670
+ users: ['firstName', 'lastName', 'email'],
671
+ addresses: ['city', 'country']
672
+ },
673
+ include: ['addresses']
674
+ });
675
+ ```
676
+
677
+ ---
678
+
679
+ ## ⚡ Atomic Operations
680
+
681
+ Execute multiple operations in a single HTTP request. All operations succeed or fail together.
682
+
683
+ ### Basic Atomic Operation
684
+
685
+ ```typescript
686
+ const newUser = new Users();
687
+ newUser.firstName = 'John';
688
+ newUser.lastName = 'Doe';
689
+ newUser.login = 'johndoe';
690
+
691
+ const result = await jsonSdk.atomicFactory()
692
+ .postOne(newUser)
693
+ .run();
694
+
695
+ console.log(result[0]); // Created user
696
+ ```
697
+
698
+ ### Multiple Operations
699
+
700
+ ```typescript
701
+ // Create multiple related resources
702
+ const address = new Addresses();
703
+ address.city = 'New York';
704
+ address.state = 'NY';
705
+ address.country = 'USA';
706
+
707
+ const role = new Roles();
708
+ role.name = 'Admin';
709
+ role.key = 'admin';
710
+
711
+ const user = new Users();
712
+ user.firstName = 'Jane';
713
+ user.lastName = 'Doe';
714
+ user.login = 'janedoe';
715
+ user.addresses = address;
716
+ user.roles = [role];
717
+
718
+ const [createdAddress, createdRole, createdUser] = await jsonSdk
719
+ .atomicFactory()
720
+ .postOne(address)
721
+ .postOne(role)
722
+ .postOne(user)
723
+ .run();
724
+ ```
725
+
726
+ ### Mixed Operations (POST, PATCH, Relationships)
727
+
728
+ ```typescript
729
+ // Create user first
730
+ const newUser = new Users();
731
+ newUser.firstName = 'John';
732
+ newUser.login = 'john';
733
+
734
+ const [createdUser] = await jsonSdk.atomicFactory()
735
+ .postOne(newUser)
736
+ .run();
737
+
738
+ // Then update and manage relationships atomically
739
+ const patchUser = Object.assign(new Users(), createdUser);
740
+ patchUser.firstName = 'John Updated';
741
+ patchUser.roles = [role1];
742
+
743
+ const patchUser2 = Object.assign(new Users(), createdUser);
744
+ patchUser2.comments = [comment1];
745
+
746
+ const patchUser3 = Object.assign(new Users(), createdUser);
747
+ patchUser3.comments = [comment2];
748
+
749
+ const result = await jsonSdk
750
+ .atomicFactory()
751
+ .patchOne(patchUser) // Update user attributes and set roles
752
+ .patchOne(patchUser2) // Set comments
753
+ .patchRelationships(patchUser2, 'comments') // Replace comments (keep only comment1)
754
+ .postRelationships(patchUser3, 'comments') // Add comment2 to existing comments
755
+ .run();
756
+
757
+ // result[0] - updated user
758
+ // result[1] - updated user with comments
759
+ // result[2] - array of comment IDs after replacement
760
+ // result[3] - array of all comment IDs after addition
761
+ ```
762
+
763
+ ### Using Temporary IDs (lid)
764
+
765
+ Reference resources created within the same atomic request using temporary IDs.
766
+
767
+ ```typescript
768
+ const address = new Addresses();
769
+ address.city = 'Boston';
770
+ address.id = 10000; // Temporary ID
771
+
772
+ const user = new Users();
773
+ user.firstName = 'Alice';
774
+ user.addresses = address; // Reference by temp ID
775
+
776
+ const [createdAddress, createdUser] = await jsonSdk
777
+ .atomicFactory()
778
+ .postOne(address)
779
+ .postOne(user)
780
+ .run();
781
+
782
+ // Server assigns real IDs
783
+ console.log(createdAddress.id); // Real ID (e.g., 1)
784
+ console.log(createdUser.addresses.id); // Same real ID
785
+ ```
786
+
787
+ ---
788
+
789
+ ## 💡 Examples
790
+
791
+ For comprehensive real-world examples, see the [E2E test suite](https://github.com/klerick/nestjs-json-api/tree/master/apps/json-api-server-e2e/src/json-api/json-api-sdk):
792
+
793
+ - **[GET Operations](https://github.com/klerick/nestjs-json-api/blob/master/apps/json-api-server-e2e/src/json-api/json-api-sdk/get-method.spec.ts)** - Fetching, filtering, pagination, sparse fieldsets
794
+ - **[POST Operations](https://github.com/klerick/nestjs-json-api/blob/master/apps/json-api-server-e2e/src/json-api/json-api-sdk/post-method.spec.ts)** - Creating resources with relationships
795
+ - **[PATCH Operations](https://github.com/klerick/nestjs-json-api/blob/master/apps/json-api-server-e2e/src/json-api/json-api-sdk/patch-methode.spec.ts)** - Updating resources and relationships
796
+ - **[Atomic Operations](https://github.com/klerick/nestjs-json-api/blob/master/apps/json-api-server-e2e/src/json-api/json-api-sdk/atomic-sdk.spec.ts)** - Batch requests with rollback
797
+ - **[Common Decorators](https://github.com/klerick/nestjs-json-api/blob/master/apps/json-api-server-e2e/src/json-api/json-api-sdk/check-common-decorator.spec.ts)** - Guards, interceptors, custom behavior
798
+
799
+ ---
800
+
801
+ ## 📝 License
802
+
803
+ MIT
804
+
805
+ ---
806
+
807
+ ## 🔗 Related Packages
808
+
809
+ - [@klerick/json-api-nestjs](https://www.npmjs.com/package/@klerick/json-api-nestjs) - JSON:API server implementation for NestJS
810
+ - [@klerick/json-api-nestjs-typeorm](https://www.npmjs.com/package/@klerick/json-api-nestjs-typeorm) - TypeORM adapter
811
+ - [@klerick/json-api-nestjs-microorm](https://www.npmjs.com/package/@klerick/json-api-nestjs-microorm) - MikroORM adapter