@klerick/json-api-nestjs-sdk 10.0.0-beta.4 → 10.0.0-beta.5
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.
- package/CHANGELOG.md +10 -0
- package/README.md +608 -97
- package/cjs/src/index.js +4 -13
- package/cjs/src/index.js.map +1 -1
- package/cjs/src/lib/constants/index.js +1 -4
- package/cjs/src/lib/constants/index.js.map +1 -1
- package/cjs/src/lib/json-api-angular.js +19 -26
- package/cjs/src/lib/json-api-angular.js.map +1 -1
- package/cjs/src/lib/json-api-js.js +13 -16
- package/cjs/src/lib/json-api-js.js.map +1 -1
- package/cjs/src/lib/service/atomic-operations.service.js +8 -12
- package/cjs/src/lib/service/atomic-operations.service.js.map +1 -1
- package/cjs/src/lib/service/fetch-inner-client.js +6 -10
- package/cjs/src/lib/service/fetch-inner-client.js.map +1 -1
- package/cjs/src/lib/service/index.js +3 -6
- package/cjs/src/lib/service/index.js.map +1 -1
- package/cjs/src/lib/service/json-api-sdk.service.js +29 -33
- package/cjs/src/lib/service/json-api-sdk.service.js.map +1 -1
- package/cjs/src/lib/service/json-api-utils.service.js +27 -31
- package/cjs/src/lib/service/json-api-utils.service.js.map +1 -1
- package/cjs/src/lib/token/index.js +3 -6
- package/cjs/src/lib/token/index.js.map +1 -1
- package/cjs/src/lib/types/atomic-operation.js +0 -2
- package/cjs/src/lib/types/atomic-type.js +1 -3
- package/cjs/src/lib/types/atomic-type.js.map +1 -1
- package/cjs/src/lib/types/config.js +0 -2
- package/cjs/src/lib/types/filter-operand.js +1 -3
- package/cjs/src/lib/types/filter-operand.js.map +1 -1
- package/cjs/src/lib/types/http-inner-client.js +0 -2
- package/cjs/src/lib/types/http-request-params.js +0 -2
- package/cjs/src/lib/types/index.js +9 -12
- package/cjs/src/lib/types/index.js.map +1 -1
- package/cjs/src/lib/types/promise-json-api-sdk.js +0 -2
- package/cjs/src/lib/types/query-params.js +1 -3
- package/cjs/src/lib/types/query-params.js.map +1 -1
- package/cjs/src/lib/types/utils.js +0 -2
- package/cjs/src/lib/utils/adapter-for-axios.js +5 -8
- package/cjs/src/lib/utils/adapter-for-axios.js.map +1 -1
- package/cjs/src/lib/utils/entity-array.js +1 -5
- package/cjs/src/lib/utils/entity-array.js.map +1 -1
- package/cjs/src/lib/utils/generate-atomic-body.js +11 -15
- package/cjs/src/lib/utils/generate-atomic-body.js.map +1 -1
- package/cjs/src/lib/utils/http-params.js +2 -7
- package/cjs/src/lib/utils/http-params.js.map +1 -1
- package/cjs/src/lib/utils/index.js +5 -8
- package/cjs/src/lib/utils/index.js.map +1 -1
- package/cjs/src/lib/utils/utils.js +8 -13
- package/cjs/src/lib/utils/utils.js.map +1 -1
- package/cjs/src/ngModule.js +3 -13
- package/cjs/src/ngModule.js.map +1 -1
- package/mjs/src/lib/types/atomic-operation.js +0 -1
- package/mjs/src/lib/types/config.js +0 -1
- package/mjs/src/lib/types/http-inner-client.js +0 -1
- package/mjs/src/lib/types/http-request-params.js +0 -1
- package/mjs/src/lib/types/promise-json-api-sdk.js +0 -1
- package/mjs/src/lib/types/utils.js +0 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,154 +1,665 @@
|
|
|
1
1
|
<p align='center'>
|
|
2
|
-
<a href="https://www.npmjs.com/package/json-api-nestjs-sdk" target="_blank"><img src="https://img.shields.io/npm/v/json-api-nestjs-sdk.svg" alt="NPM Version" /></a>
|
|
3
|
-
<a href="https://www.npmjs.com/package/json-api-nestjs-sdk" target="_blank"><img src="https://img.shields.io/npm/l/json-api-nestjs-sdk.svg" alt="Package License" /></a>
|
|
4
|
-
<a href="https://www.npmjs.com/package/json-api-nestjs-sdk" target="_blank"><img src="https://img.shields.io/npm/dm/json-api-nestjs-sdk.svg" alt="NPM Downloads" /></a>
|
|
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
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
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
7
|
</p>
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
- [Query Options](#-query-options)
|
|
38
|
+
- [Filtering](#filtering)
|
|
39
|
+
- [Sorting](#sorting)
|
|
40
|
+
- [Pagination](#pagination)
|
|
41
|
+
- [Including Relationships](#including-relationships)
|
|
42
|
+
- [Sparse Fieldsets](#sparse-fieldsets)
|
|
43
|
+
- [Atomic Operations](#-atomic-operations)
|
|
44
|
+
- [Examples](#-examples)
|
|
12
45
|
|
|
13
46
|
|
|
14
47
|
## Installation
|
|
15
48
|
|
|
16
|
-
```bash
|
|
17
|
-
npm install @klerick/json-api-nestjs-sdk
|
|
49
|
+
```bash
|
|
50
|
+
npm install @klerick/json-api-nestjs-sdk
|
|
18
51
|
```
|
|
19
52
|
|
|
20
|
-
|
|
53
|
+
---
|
|
21
54
|
|
|
22
|
-
|
|
55
|
+
## 🚀 Quick Start
|
|
23
56
|
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
adapterForAxios,
|
|
27
|
-
FilterOperand,
|
|
28
|
-
JsonApiJs,
|
|
29
|
-
JsonSdkPromise,
|
|
30
|
-
} from '@klerick/json-api-nestjs-sdk';
|
|
31
|
-
import { faker } from '@faker-js/faker';
|
|
32
|
-
import axios from 'axios';
|
|
57
|
+
### Basic Setup (Axios)
|
|
33
58
|
|
|
34
|
-
|
|
59
|
+
```typescript
|
|
60
|
+
import { JsonApiJs, adapterForAxios, FilterOperand } from '@klerick/json-api-nestjs-sdk';
|
|
61
|
+
import axios from 'axios';
|
|
62
|
+
import { Users } from './entities'; // Your entity classes
|
|
35
63
|
|
|
64
|
+
// 1. Create adapter
|
|
36
65
|
const axiosAdapter = adapterForAxios(axios);
|
|
37
66
|
|
|
38
|
-
|
|
39
|
-
adapter: axiosAdapter,
|
|
40
|
-
apiHost: 'http://localhost:3000',
|
|
41
|
-
apiPrefix: 'api',
|
|
42
|
-
dateFields: ['createdAt', 'updatedAt'],
|
|
43
|
-
operationUrl: 'operation',
|
|
44
|
-
}
|
|
45
|
-
|
|
67
|
+
// 2. Configure SDK
|
|
46
68
|
const jsonSdk = JsonApiJs(
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
{
|
|
70
|
+
adapter: axiosAdapter,
|
|
71
|
+
apiHost: 'http://localhost:3000',
|
|
72
|
+
apiPrefix: 'api',
|
|
73
|
+
dateFields: ['createdAt', 'updatedAt'],
|
|
74
|
+
operationUrl: 'operation',
|
|
75
|
+
},
|
|
76
|
+
true // true = return Promises, false = return Observables
|
|
49
77
|
);
|
|
50
78
|
|
|
51
|
-
|
|
79
|
+
// 3. Use SDK
|
|
80
|
+
// Fetch all users
|
|
81
|
+
const users = await jsonSdk.jonApiSdkService.getAll(Users);
|
|
82
|
+
|
|
83
|
+
// Fetch with filtering and relationships
|
|
84
|
+
const activeUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
52
85
|
filter: {
|
|
53
86
|
target: {
|
|
54
|
-
|
|
55
|
-
}
|
|
87
|
+
isActive: { [FilterOperand.eq]: 'true' }
|
|
88
|
+
}
|
|
56
89
|
},
|
|
57
|
-
include: ['addresses', '
|
|
90
|
+
include: ['addresses', 'roles']
|
|
58
91
|
});
|
|
59
92
|
|
|
93
|
+
// Get one user
|
|
94
|
+
const user = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
|
|
95
|
+
include: ['addresses', 'comments', 'roles', 'manager']
|
|
96
|
+
});
|
|
60
97
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
address.country = faker.string.alpha(50);
|
|
68
|
-
address.id = 10000;
|
|
69
|
-
|
|
70
|
-
const manager = getUser();
|
|
71
|
-
manager.id = 10001;
|
|
72
|
-
manager.addresses = address;
|
|
98
|
+
// Create a user
|
|
99
|
+
const newUser = new Users();
|
|
100
|
+
newUser.firstName = 'John';
|
|
101
|
+
newUser.lastName = 'Doe';
|
|
102
|
+
newUser.login = 'johndoe';
|
|
103
|
+
newUser.isActive = true;
|
|
73
104
|
|
|
74
|
-
const
|
|
75
|
-
roles.id = 10002;
|
|
76
|
-
roles.name = faker.string.alpha(50);
|
|
77
|
-
roles.key = faker.string.alpha(50);
|
|
105
|
+
const createdUser = await jsonSdk.jonApiSdkService.postOne(newUser);
|
|
78
106
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
user.roles = [roles];
|
|
107
|
+
// Update a user
|
|
108
|
+
createdUser.firstName = 'Jane';
|
|
109
|
+
const updatedUser = await jsonSdk.jonApiSdkService.patchOne(createdUser);
|
|
83
110
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.postOne(manager)
|
|
88
|
-
.postOne(roles)
|
|
89
|
-
.postOne(user)
|
|
90
|
-
.run();
|
|
111
|
+
// Delete a user
|
|
112
|
+
await jsonSdk.jonApiSdkService.deleteOne(createdUser);
|
|
113
|
+
```
|
|
91
114
|
|
|
115
|
+
### Angular Setup
|
|
92
116
|
|
|
93
|
-
```
|
|
94
|
-
or you can use Angular module:
|
|
95
117
|
```typescript
|
|
96
|
-
import {
|
|
97
|
-
provideJsonApi,
|
|
98
|
-
AtomicFactory,
|
|
99
|
-
JsonApiSdkService
|
|
118
|
+
import {
|
|
119
|
+
provideJsonApi,
|
|
120
|
+
AtomicFactory,
|
|
121
|
+
JsonApiSdkService
|
|
100
122
|
} from '@klerick/json-api-nestjs-sdk/ngModule';
|
|
101
123
|
import {
|
|
102
124
|
provideHttpClient,
|
|
103
125
|
withFetch,
|
|
104
126
|
} from '@angular/common/http';
|
|
127
|
+
import { Component, inject } from '@angular/core';
|
|
128
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
105
129
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
templateUrl: './app.component.html',
|
|
110
|
-
styleUrl: './app.component.css',
|
|
111
|
-
})
|
|
112
|
-
export class AppComponent {
|
|
113
|
-
private JsonApiSdkService = inject(JsonApiSdkService);
|
|
114
|
-
private atomicFactory = inject(AtomicFactory);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const angularConfig: JsonSdkConfig = {
|
|
118
|
-
apiHost: 'http://localhost:4200',
|
|
130
|
+
// 1. Configure in your main.ts or app.config.ts
|
|
131
|
+
const angularConfig = {
|
|
132
|
+
apiHost: 'http://localhost:3000',
|
|
119
133
|
idKey: 'id',
|
|
120
134
|
apiPrefix: 'api',
|
|
121
135
|
operationUrl: 'operation',
|
|
122
|
-
|
|
136
|
+
dateFields: ['createdAt', 'updatedAt']
|
|
137
|
+
};
|
|
123
138
|
|
|
124
139
|
bootstrapApplication(AppComponent, {
|
|
125
140
|
providers: [
|
|
126
141
|
provideHttpClient(withFetch()),
|
|
127
142
|
provideJsonApi(angularConfig)
|
|
128
143
|
],
|
|
129
|
-
}).catch((err) =>
|
|
130
|
-
console.error(err)
|
|
131
|
-
);
|
|
132
|
-
|
|
144
|
+
}).catch((err) => console.error(err));
|
|
133
145
|
|
|
146
|
+
// 2. Use in your components
|
|
147
|
+
@Component({
|
|
148
|
+
standalone: true,
|
|
149
|
+
selector: 'app-users',
|
|
150
|
+
templateUrl: './users.component.html',
|
|
151
|
+
})
|
|
152
|
+
export class UsersComponent {
|
|
153
|
+
private jsonApiService = inject(JsonApiSdkService);
|
|
154
|
+
private atomicFactory = inject(AtomicFactory);
|
|
134
155
|
|
|
156
|
+
async loadUsers() {
|
|
157
|
+
const users = await this.jsonApiService.getAll(Users, {
|
|
158
|
+
include: ['addresses']
|
|
159
|
+
});
|
|
160
|
+
return users;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async createMultipleResources() {
|
|
164
|
+
const result = await this.atomicFactory()
|
|
165
|
+
.postOne(newUser)
|
|
166
|
+
.postOne(newAddress)
|
|
167
|
+
.run();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
135
170
|
```
|
|
136
171
|
|
|
137
|
-
## Configuration
|
|
172
|
+
## ⚙️ Configuration
|
|
138
173
|
|
|
139
|
-
|
|
174
|
+
### JsonConfig Type
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
140
177
|
type JsonSdkConfig = {
|
|
141
|
-
apiHost: string;
|
|
142
|
-
apiPrefix?: string;
|
|
143
|
-
idKey?: string;
|
|
144
|
-
idIsNumber?: boolean;
|
|
145
|
-
operationUrl?: string;
|
|
146
|
-
dateFields
|
|
147
|
-
}
|
|
178
|
+
apiHost: string; // Base URL of your API (e.g., 'http://localhost:3000')
|
|
179
|
+
apiPrefix?: string; // API prefix (e.g., 'api' -> '/api/users')
|
|
180
|
+
idKey?: string; // Name of ID field (default: 'id')
|
|
181
|
+
idIsNumber?: boolean; // Parse IDs as numbers (default: false)
|
|
182
|
+
operationUrl?: string; // URL path for atomic operations (default: 'operation')
|
|
183
|
+
dateFields?: string[]; // Fields to convert to Date objects (e.g., ['createdAt', 'updatedAt'])
|
|
184
|
+
}
|
|
148
185
|
|
|
149
186
|
type JsonConfig = JsonSdkConfig & {
|
|
150
|
-
adapter?: HttpInnerClient; //
|
|
187
|
+
adapter?: HttpInnerClient; // HTTP client adapter (default: fetch)
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### HTTP Adapters
|
|
192
|
+
|
|
193
|
+
**Axios Adapter:**
|
|
194
|
+
```typescript
|
|
195
|
+
import { adapterForAxios } from '@klerick/json-api-nestjs-sdk';
|
|
196
|
+
import axios from 'axios';
|
|
197
|
+
|
|
198
|
+
const adapter = adapterForAxios(axios);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Fetch API (default):**
|
|
202
|
+
```typescript
|
|
203
|
+
// No adapter needed, fetch is used by default
|
|
204
|
+
const jsonSdk = JsonApiJs({
|
|
205
|
+
apiHost: 'http://localhost:3000',
|
|
206
|
+
apiPrefix: 'api',
|
|
207
|
+
}, true);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Custom Adapter:**
|
|
211
|
+
|
|
212
|
+
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.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 📖 API Methods
|
|
217
|
+
|
|
218
|
+
### Fetching Resources
|
|
219
|
+
|
|
220
|
+
#### `getAll(Entity, options?)`
|
|
221
|
+
|
|
222
|
+
Fetch all resources with optional filtering, sorting, and relationships.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { FilterOperand } from '@klerick/json-api-nestjs-sdk';
|
|
226
|
+
|
|
227
|
+
// Fetch all users
|
|
228
|
+
const users = await jsonSdk.jonApiSdkService.getAll(Users);
|
|
229
|
+
|
|
230
|
+
// With filtering
|
|
231
|
+
const activeUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
232
|
+
filter: {
|
|
233
|
+
target: {
|
|
234
|
+
isActive: { [FilterOperand.eq]: 'true' },
|
|
235
|
+
id: { [FilterOperand.in]: ['1', '2', '3'] }
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
include: ['addresses', 'roles']
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Filter by relationship
|
|
242
|
+
const usersWithRoles = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
243
|
+
filter: {
|
|
244
|
+
target: {
|
|
245
|
+
id: { [FilterOperand.in]: ['1', '2'] }
|
|
246
|
+
},
|
|
247
|
+
roles: {
|
|
248
|
+
name: { [FilterOperand.eq]: 'admin' }
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
include: ['roles']
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### `getList(Entity, options)`
|
|
256
|
+
|
|
257
|
+
Fetch resources with pagination (returns paginated results).
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
const firstPage = await jsonSdk.jonApiSdkService.getList(Users, {
|
|
261
|
+
page: {
|
|
262
|
+
number: 1,
|
|
263
|
+
size: 10
|
|
264
|
+
},
|
|
265
|
+
sort: {
|
|
266
|
+
target: {
|
|
267
|
+
id: 'ASC'
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const secondPage = await jsonSdk.jonApiSdkService.getList(Users, {
|
|
273
|
+
page: {
|
|
274
|
+
number: 2,
|
|
275
|
+
size: 10
|
|
276
|
+
},
|
|
277
|
+
sort: {
|
|
278
|
+
target: {
|
|
279
|
+
createdAt: 'DESC'
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### `getOne(Entity, id, options?)`
|
|
286
|
+
|
|
287
|
+
Fetch a single resource by ID.
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// Simple fetch
|
|
291
|
+
const user = await jsonSdk.jonApiSdkService.getOne(Users, '1');
|
|
292
|
+
|
|
293
|
+
// With relationships
|
|
294
|
+
const userWithRelations = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
|
|
295
|
+
include: ['addresses', 'comments', 'roles', 'manager']
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// With sparse fieldsets
|
|
299
|
+
const userPartial = await jsonSdk.jonApiSdkService.getOne(Users, '1', {
|
|
300
|
+
fields: {
|
|
301
|
+
users: ['firstName', 'lastName', 'email']
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Creating Resources
|
|
307
|
+
|
|
308
|
+
#### `postOne(entity, options?)`
|
|
309
|
+
|
|
310
|
+
Create a new resource.
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Simple create
|
|
314
|
+
const newUser = new Users();
|
|
315
|
+
newUser.firstName = 'John';
|
|
316
|
+
newUser.lastName = 'Doe';
|
|
317
|
+
newUser.login = 'johndoe';
|
|
318
|
+
newUser.isActive = true;
|
|
319
|
+
|
|
320
|
+
const createdUser = await jsonSdk.jonApiSdkService.postOne(newUser);
|
|
321
|
+
|
|
322
|
+
// Create with relationships
|
|
323
|
+
const newAddress = new Addresses();
|
|
324
|
+
newAddress.city = 'New York';
|
|
325
|
+
newAddress.state = 'NY';
|
|
326
|
+
newAddress.country = 'USA';
|
|
327
|
+
|
|
328
|
+
const savedAddress = await jsonSdk.jonApiSdkService.postOne(newAddress);
|
|
329
|
+
|
|
330
|
+
const user = new Users();
|
|
331
|
+
user.firstName = 'Jane';
|
|
332
|
+
user.lastName = 'Doe';
|
|
333
|
+
user.login = 'janedoe';
|
|
334
|
+
user.addresses = savedAddress; // Set relationship
|
|
335
|
+
|
|
336
|
+
const createdUser = await jsonSdk.jonApiSdkService.postOne(user);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Updating Resources
|
|
340
|
+
|
|
341
|
+
#### `patchOne(entity, options?)`
|
|
342
|
+
|
|
343
|
+
Update an existing resource.
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// Update attributes
|
|
347
|
+
user.firstName = 'Updated Name';
|
|
348
|
+
const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
|
|
349
|
+
|
|
350
|
+
// Update relationships
|
|
351
|
+
const newAddress = await jsonSdk.jonApiSdkService.postOne(addressEntity);
|
|
352
|
+
user.addresses = newAddress;
|
|
353
|
+
|
|
354
|
+
const updatedUser = await jsonSdk.jonApiSdkService.patchOne(user);
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Deleting Resources
|
|
358
|
+
|
|
359
|
+
#### `deleteOne(entity)`
|
|
360
|
+
|
|
361
|
+
Delete a resource.
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
await jsonSdk.jonApiSdkService.deleteOne(user);
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Relationship Operations
|
|
368
|
+
|
|
369
|
+
#### `deleteRelationships(entity, relationshipName)`
|
|
370
|
+
|
|
371
|
+
Remove relationships without deleting the related resources.
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// Remove all roles from user
|
|
375
|
+
await jsonSdk.jonApiSdkService.deleteRelationships(user, 'roles');
|
|
376
|
+
|
|
377
|
+
// Remove manager from user
|
|
378
|
+
await jsonSdk.jonApiSdkService.deleteRelationships(user, 'manager');
|
|
379
|
+
|
|
380
|
+
// Remove all comments from user
|
|
381
|
+
await jsonSdk.jonApiSdkService.deleteRelationships(user, 'comments');
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 🔍 Query Options
|
|
387
|
+
|
|
388
|
+
### Filtering
|
|
389
|
+
|
|
390
|
+
Available operators:
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
enum FilterOperand {
|
|
394
|
+
eq = 'eq', // Equal
|
|
395
|
+
ne = 'ne', // Not equal
|
|
396
|
+
in = 'in', // In array
|
|
397
|
+
nin = 'nin', // Not in array
|
|
398
|
+
lt = 'lt', // Less than
|
|
399
|
+
lte = 'lte', // Less than or equal
|
|
400
|
+
gt = 'gt', // Greater than
|
|
401
|
+
gte = 'gte', // Greater than or equal
|
|
402
|
+
like = 'like', // SQL LIKE
|
|
403
|
+
re = 'regexp', // Regular expression
|
|
151
404
|
}
|
|
152
405
|
```
|
|
153
|
-
|
|
154
|
-
|
|
406
|
+
|
|
407
|
+
**Examples:**
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
// Equal
|
|
411
|
+
const users = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
412
|
+
filter: {
|
|
413
|
+
target: {
|
|
414
|
+
isActive: { [FilterOperand.eq]: 'true' }
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Not equal
|
|
420
|
+
const inactiveUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
421
|
+
filter: {
|
|
422
|
+
target: {
|
|
423
|
+
isActive: { [FilterOperand.ne]: 'true' }
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// In array
|
|
429
|
+
const specificUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
430
|
+
filter: {
|
|
431
|
+
target: {
|
|
432
|
+
id: { [FilterOperand.in]: ['1', '2', '3'] }
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// LIKE search
|
|
438
|
+
const searchUsers = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
439
|
+
filter: {
|
|
440
|
+
target: {
|
|
441
|
+
login: { [FilterOperand.like]: 'john' }
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Check null/not null
|
|
447
|
+
const usersWithManager = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
448
|
+
filter: {
|
|
449
|
+
target: {
|
|
450
|
+
manager: { [FilterOperand.ne]: null }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const usersWithoutManager = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
456
|
+
filter: {
|
|
457
|
+
target: {
|
|
458
|
+
manager: { [FilterOperand.eq]: null }
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Sorting
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
// Sort by single field
|
|
468
|
+
const users = await jsonSdk.jonApiSdkService.getList(Users, {
|
|
469
|
+
sort: {
|
|
470
|
+
target: {
|
|
471
|
+
id: 'ASC'
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Sort by multiple fields
|
|
477
|
+
const sortedUsers = await jsonSdk.jonApiSdkService.getList(Users, {
|
|
478
|
+
sort: {
|
|
479
|
+
target: {
|
|
480
|
+
createdAt: 'DESC',
|
|
481
|
+
lastName: 'ASC'
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Pagination
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
const paginatedUsers = await jsonSdk.jonApiSdkService.getList(Users, {
|
|
491
|
+
page: {
|
|
492
|
+
number: 1, // Page number (1-indexed)
|
|
493
|
+
size: 20 // Items per page
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Including Relationships
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// Include single relationship
|
|
502
|
+
const users = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
503
|
+
include: ['addresses']
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Include multiple relationships
|
|
507
|
+
const usersWithAll = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
508
|
+
include: ['addresses', 'roles', 'comments', 'manager']
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// Include nested relationships
|
|
512
|
+
const usersWithNested = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
513
|
+
include: ['addresses', 'manager.addresses', 'roles']
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Sparse Fieldsets
|
|
518
|
+
|
|
519
|
+
Request only specific fields to reduce payload size.
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
const users = await jsonSdk.jonApiSdkService.getAll(Users, {
|
|
523
|
+
fields: {
|
|
524
|
+
users: ['firstName', 'lastName', 'email'],
|
|
525
|
+
addresses: ['city', 'country']
|
|
526
|
+
},
|
|
527
|
+
include: ['addresses']
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## ⚡ Atomic Operations
|
|
534
|
+
|
|
535
|
+
Execute multiple operations in a single HTTP request. All operations succeed or fail together.
|
|
536
|
+
|
|
537
|
+
### Basic Atomic Operation
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
const newUser = new Users();
|
|
541
|
+
newUser.firstName = 'John';
|
|
542
|
+
newUser.lastName = 'Doe';
|
|
543
|
+
newUser.login = 'johndoe';
|
|
544
|
+
|
|
545
|
+
const result = await jsonSdk.atomicFactory()
|
|
546
|
+
.postOne(newUser)
|
|
547
|
+
.run();
|
|
548
|
+
|
|
549
|
+
console.log(result[0]); // Created user
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Multiple Operations
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// Create multiple related resources
|
|
556
|
+
const address = new Addresses();
|
|
557
|
+
address.city = 'New York';
|
|
558
|
+
address.state = 'NY';
|
|
559
|
+
address.country = 'USA';
|
|
560
|
+
|
|
561
|
+
const role = new Roles();
|
|
562
|
+
role.name = 'Admin';
|
|
563
|
+
role.key = 'admin';
|
|
564
|
+
|
|
565
|
+
const user = new Users();
|
|
566
|
+
user.firstName = 'Jane';
|
|
567
|
+
user.lastName = 'Doe';
|
|
568
|
+
user.login = 'janedoe';
|
|
569
|
+
user.addresses = address;
|
|
570
|
+
user.roles = [role];
|
|
571
|
+
|
|
572
|
+
const [createdAddress, createdRole, createdUser] = await jsonSdk
|
|
573
|
+
.atomicFactory()
|
|
574
|
+
.postOne(address)
|
|
575
|
+
.postOne(role)
|
|
576
|
+
.postOne(user)
|
|
577
|
+
.run();
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### Mixed Operations (POST, PATCH, Relationships)
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
// Create user first
|
|
584
|
+
const newUser = new Users();
|
|
585
|
+
newUser.firstName = 'John';
|
|
586
|
+
newUser.login = 'john';
|
|
587
|
+
|
|
588
|
+
const [createdUser] = await jsonSdk.atomicFactory()
|
|
589
|
+
.postOne(newUser)
|
|
590
|
+
.run();
|
|
591
|
+
|
|
592
|
+
// Then update and manage relationships atomically
|
|
593
|
+
const patchUser = Object.assign(new Users(), createdUser);
|
|
594
|
+
patchUser.firstName = 'John Updated';
|
|
595
|
+
patchUser.roles = [role1];
|
|
596
|
+
|
|
597
|
+
const patchUser2 = Object.assign(new Users(), createdUser);
|
|
598
|
+
patchUser2.comments = [comment1];
|
|
599
|
+
|
|
600
|
+
const patchUser3 = Object.assign(new Users(), createdUser);
|
|
601
|
+
patchUser3.comments = [comment2];
|
|
602
|
+
|
|
603
|
+
const result = await jsonSdk
|
|
604
|
+
.atomicFactory()
|
|
605
|
+
.patchOne(patchUser) // Update user attributes and set roles
|
|
606
|
+
.patchOne(patchUser2) // Set comments
|
|
607
|
+
.patchRelationships(patchUser2, 'comments') // Replace comments (keep only comment1)
|
|
608
|
+
.postRelationships(patchUser3, 'comments') // Add comment2 to existing comments
|
|
609
|
+
.run();
|
|
610
|
+
|
|
611
|
+
// result[0] - updated user
|
|
612
|
+
// result[1] - updated user with comments
|
|
613
|
+
// result[2] - array of comment IDs after replacement
|
|
614
|
+
// result[3] - array of all comment IDs after addition
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### Using Temporary IDs (lid)
|
|
618
|
+
|
|
619
|
+
Reference resources created within the same atomic request using temporary IDs.
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
const address = new Addresses();
|
|
623
|
+
address.city = 'Boston';
|
|
624
|
+
address.id = 10000; // Temporary ID
|
|
625
|
+
|
|
626
|
+
const user = new Users();
|
|
627
|
+
user.firstName = 'Alice';
|
|
628
|
+
user.addresses = address; // Reference by temp ID
|
|
629
|
+
|
|
630
|
+
const [createdAddress, createdUser] = await jsonSdk
|
|
631
|
+
.atomicFactory()
|
|
632
|
+
.postOne(address)
|
|
633
|
+
.postOne(user)
|
|
634
|
+
.run();
|
|
635
|
+
|
|
636
|
+
// Server assigns real IDs
|
|
637
|
+
console.log(createdAddress.id); // Real ID (e.g., 1)
|
|
638
|
+
console.log(createdUser.addresses.id); // Same real ID
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
## 💡 Examples
|
|
644
|
+
|
|
645
|
+
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):
|
|
646
|
+
|
|
647
|
+
- **[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
|
|
648
|
+
- **[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
|
|
649
|
+
- **[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
|
|
650
|
+
- **[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
|
|
651
|
+
- **[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
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
## 📝 License
|
|
656
|
+
|
|
657
|
+
MIT
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## 🔗 Related Packages
|
|
662
|
+
|
|
663
|
+
- [@klerick/json-api-nestjs](https://www.npmjs.com/package/@klerick/json-api-nestjs) - JSON:API server implementation for NestJS
|
|
664
|
+
- [@klerick/json-api-nestjs-typeorm](https://www.npmjs.com/package/@klerick/json-api-nestjs-typeorm) - TypeORM adapter
|
|
665
|
+
- [@klerick/json-api-nestjs-microorm](https://www.npmjs.com/package/@klerick/json-api-nestjs-microorm) - MikroORM adapter
|