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

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