ng-qubee 2.1.0 → 3.0.0

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 (32) hide show
  1. package/README.md +301 -54
  2. package/fesm2022/ng-qubee.mjs +1257 -264
  3. package/fesm2022/ng-qubee.mjs.map +1 -1
  4. package/package.json +4 -4
  5. package/types/ng-qubee.d.ts +1302 -0
  6. package/index.d.ts +0 -5
  7. package/lib/enums/sort.enum.d.ts +0 -4
  8. package/lib/errors/invalid-limit.error.d.ts +0 -3
  9. package/lib/errors/invalid-model-name.error.d.ts +0 -3
  10. package/lib/errors/invalid-page-number.error.d.ts +0 -3
  11. package/lib/errors/key-not-found.error.d.ts +0 -3
  12. package/lib/errors/unselectable-model.error.d.ts +0 -3
  13. package/lib/interfaces/config.interface.d.ts +0 -6
  14. package/lib/interfaces/fields.interface.d.ts +0 -3
  15. package/lib/interfaces/filters.interface.d.ts +0 -3
  16. package/lib/interfaces/nest-state.interface.d.ts +0 -4
  17. package/lib/interfaces/normalized.interface.d.ts +0 -3
  18. package/lib/interfaces/page.interface.d.ts +0 -2
  19. package/lib/interfaces/paginated-object.interface.d.ts +0 -3
  20. package/lib/interfaces/pagination-config.interface.d.ts +0 -14
  21. package/lib/interfaces/query-builder-config.interface.d.ts +0 -9
  22. package/lib/interfaces/query-builder-state.interface.d.ts +0 -13
  23. package/lib/interfaces/sort.interface.d.ts +0 -5
  24. package/lib/models/paginated-collection.d.ts +0 -30
  25. package/lib/models/query-builder-options.d.ts +0 -11
  26. package/lib/models/response-options.d.ts +0 -16
  27. package/lib/ng-qubee.module.d.ts +0 -9
  28. package/lib/provide-ngqubee.d.ts +0 -21
  29. package/lib/services/nest.service.d.ts +0 -182
  30. package/lib/services/ng-qubee.service.d.ts +0 -147
  31. package/lib/services/pagination.service.d.ts +0 -13
  32. package/public-api.d.ts +0 -20
package/README.md CHANGED
@@ -15,6 +15,7 @@ NgQubee is a Query Builder for Angular. Easily compose your API requests without
15
15
  - Pagination ready
16
16
  - Reactive, as the results are emitted with a RxJS Observable
17
17
  - Developed with a test-driven approach
18
+ - **Multi-driver support**: Laravel (pagination-only), Spatie Query Builder, and NestJS (nestjs-paginate)
18
19
 
19
20
  ## We love it, we use it ❤️
20
21
  NgQubee uses some open source projects to work properly:
@@ -38,25 +39,58 @@ Install NgQubee via NPM
38
39
  npm i ng-qubee
39
40
  ```
40
41
 
42
+ ## Drivers
43
+
44
+ NgQubee supports three drivers out of the box. A driver **must** be specified in the configuration:
45
+
46
+ | Driver | Backend | Request Format | Response Format |
47
+ |---|---|---|---|
48
+ | **Laravel** | Plain Laravel pagination | `limit=N&page=N` (pagination only) | Flat: `{ data, current_page, total, ... }` |
49
+ | **Spatie** | Spatie Query Builder | `filter[field]=value`, `sort=-field` | Flat: `{ data, current_page, total, ... }` |
50
+ | **NestJS** | nestjs-paginate | `filter.field=$operator:value`, `sortBy=field:DESC` | Nested: `{ data, meta: {...}, links: {...} }` |
51
+
41
52
  ## Usage
42
- Import the module in your Angular app:
53
+
54
+ ### Laravel Driver (pagination-only)
55
+
56
+ The Laravel driver provides basic pagination — limit and page parameters only. No filters, sorts, fields, or includes are supported.
43
57
 
44
58
  ```typescript
59
+ import { DriverEnum } from 'ng-qubee';
60
+
61
+ // Standalone approach
62
+ bootstrapApplication(AppComponent, {
63
+ providers: [provideNgQubee({ driver: DriverEnum.LARAVEL })]
64
+ });
65
+
66
+ // Module approach
45
67
  @NgModule({
46
68
  imports: [
47
- NgQubeeModule.forRoot({}) // You can omit the empty object as it is an optional argument
69
+ NgQubeeModule.forRoot({ driver: DriverEnum.LARAVEL })
48
70
  ]
49
71
  })
50
- export class AppModule {}
72
+ export class AppModule {}
51
73
  ```
52
74
 
53
- Or if you are working with Angular 15 or greater, use the provide function:
75
+ ### Spatie Driver
76
+
77
+ The Spatie driver generates URIs compatible with [Spatie Laravel Query Builder](https://spatie.be/docs/laravel-query-builder):
78
+
54
79
  ```typescript
55
- const config = {};
80
+ import { DriverEnum } from 'ng-qubee';
56
81
 
82
+ // Standalone approach
57
83
  bootstrapApplication(AppComponent, {
58
- providers: [provideNgQubee(config)]
84
+ providers: [provideNgQubee({ driver: DriverEnum.SPATIE })]
59
85
  });
86
+
87
+ // Module approach
88
+ @NgModule({
89
+ imports: [
90
+ NgQubeeModule.forRoot({ driver: DriverEnum.SPATIE })
91
+ ]
92
+ })
93
+ export class AppModule {}
60
94
  ```
61
95
 
62
96
  The object given to the _forRoot_ method allows to customize the query param keys. Following, the default behaviour:
@@ -72,6 +106,7 @@ As you can easily imagine, everything that regards the URI composition is placed
72
106
 
73
107
  ```typescript
74
108
  NgQubeeModule.forRoot({
109
+ driver: DriverEnum.SPATIE,
75
110
  request: {
76
111
  filters: 'custom-filter-key',
77
112
  fields: 'custom-fields-key',
@@ -80,6 +115,39 @@ NgQubeeModule.forRoot({
80
115
  })
81
116
  ```
82
117
 
118
+ ### NestJS Driver
119
+
120
+ To use the NestJS driver, specify the driver in your configuration:
121
+
122
+ ```typescript
123
+ import { DriverEnum } from 'ng-qubee';
124
+
125
+ // Standalone approach
126
+ bootstrapApplication(AppComponent, {
127
+ providers: [provideNgQubee({ driver: DriverEnum.NESTJS })]
128
+ });
129
+
130
+ // Module approach
131
+ @NgModule({
132
+ imports: [
133
+ NgQubeeModule.forRoot({ driver: DriverEnum.NESTJS })
134
+ ]
135
+ })
136
+ export class AppModule {}
137
+ ```
138
+
139
+ The NestJS driver generates URIs compatible with [nestjs-paginate](https://github.com/ppetzold/nestjs-paginate):
140
+
141
+ - **Filters** are composed as `filter.field=value`
142
+ - **Filter operators** are composed as `filter.field=$operator:value`
143
+ - **Sorts** are composed as `sortBy=field:ASC,field2:DESC`
144
+ - **Select** is composed as `select=col1,col2`
145
+ - **Search** is composed as `search=term`
146
+ - **Limit** is composed as `limit=15`
147
+ - **Page** is composed as `page=1`
148
+
149
+ ## Query Builder API
150
+
83
151
  For composing queries, the first step is to inject the proper NgQubeeService:
84
152
 
85
153
  ```typescript
@@ -89,16 +157,16 @@ export class YourService {
89
157
  }
90
158
  ```
91
159
 
92
- Set the **model** to run the query against:
160
+ Set the **resource** to run the query against:
93
161
 
94
162
  ```typescript
95
- this._ngQubeeService.setModel('users');
163
+ this._ngQubeeService.setResource('users');
96
164
  ```
97
165
 
98
166
  This is necessary to build the prefix of the URI (/users)
99
167
 
100
168
 
101
- ### Fields
169
+ ### Fields (Spatie only)
102
170
  Fields can be selected as following:
103
171
 
104
172
  ```typescript
@@ -107,14 +175,25 @@ this._ngQubeeService.addFields('users', ['id', 'email']);
107
175
 
108
176
  Will output _/users?fields[users]=id,email_
109
177
 
110
- ### Filters
178
+ ### Select (NestJS only)
179
+ Flat field selection for the NestJS driver:
180
+
181
+ ```typescript
182
+ this._ngQubeeService.addSelect('id', 'name', 'email');
183
+ ```
184
+
185
+ Will output _/users?select=id,name,email_
186
+
187
+ ### Filters (Spatie + NestJS)
111
188
  Filters are applied as following:
112
189
 
113
190
  ```typescript
114
191
  this._ngQubeeService.addFilter('id', 5);
115
192
  ```
116
193
 
117
- Will output _/users?filter[id]=5_
194
+ Will output:
195
+ - Spatie: _/users?filter[id]=5_
196
+ - NestJS: _/users?filter.id=5_
118
197
 
119
198
  Multiple values are allowed too:
120
199
 
@@ -122,11 +201,40 @@ Multiple values are allowed too:
122
201
  this._ngQubeeService.addFilter('id', 5, 7, 10);
123
202
  ```
124
203
 
125
- Will output _/users?filter[id]=5,7,10_
204
+ Will output:
205
+ - Spatie: _/users?filter[id]=5,7,10_
206
+ - NestJS: _/users?filter.id=5,7,10_
207
+
208
+ ### Filter Operators (NestJS only)
209
+ The NestJS driver supports explicit filter operators:
210
+
211
+ ```typescript
212
+ import { FilterOperatorEnum } from 'ng-qubee';
213
+
214
+ // Equality
215
+ this._ngQubeeService.addFilterOperator('status', FilterOperatorEnum.EQ, 'active');
216
+ // Output: filter.status=$eq:active
217
+
218
+ // Greater than or equal
219
+ this._ngQubeeService.addFilterOperator('age', FilterOperatorEnum.GTE, 18);
220
+ // Output: filter.age=$gte:18
126
221
 
127
-
222
+ // In (multiple values)
223
+ this._ngQubeeService.addFilterOperator('id', FilterOperatorEnum.IN, 1, 2, 3);
224
+ // Output: filter.id=$in:1,2,3
128
225
 
129
- ### Includes
226
+ // Between
227
+ this._ngQubeeService.addFilterOperator('price', FilterOperatorEnum.BTW, 10, 100);
228
+ // Output: filter.price=$btw:10,100
229
+
230
+ // Case-insensitive like
231
+ this._ngQubeeService.addFilterOperator('name', FilterOperatorEnum.ILIKE, 'john');
232
+ // Output: filter.name=$ilike:john
233
+ ```
234
+
235
+ **Available operators:** `$eq`, `$not`, `$null`, `$in`, `$gt`, `$gte`, `$lt`, `$lte`, `$btw`, `$ilike`, `$sw`, `$contains`
236
+
237
+ ### Includes (Spatie only)
130
238
  Ask to include related models with:
131
239
 
132
240
  ```typescript
@@ -135,7 +243,16 @@ this._ngQubeeService.addIncludes('profile', 'settings');
135
243
 
136
244
  Will output _/users?include=profile,settings_
137
245
 
138
- ### Sort
246
+ ### Search (NestJS only)
247
+ Full-text search for the NestJS driver:
248
+
249
+ ```typescript
250
+ this._ngQubeeService.setSearch('john doe');
251
+ ```
252
+
253
+ Will output _/users?search=john doe_
254
+
255
+ ### Sort (Spatie + NestJS)
139
256
  Sort elements as following:
140
257
 
141
258
  ```typescript
@@ -144,7 +261,9 @@ import { SortEnum } from 'ng-qubee';
144
261
  this._ngQubeeService.addSort('fieldName', SortEnum.ASC);
145
262
  ```
146
263
 
147
- Will output _/users?sort=fieldName_ (or _/users?sort=-fieldName_ if DESC)
264
+ Will output:
265
+ - Spatie: _/users?sort=fieldName_ (or _/users?sort=-fieldName_ if DESC)
266
+ - NestJS: _/users?sortBy=fieldName:ASC_ (or _/users?sortBy=fieldName:DESC_ if DESC)
148
267
 
149
268
  The `SortEnum` provides two ordering options:
150
269
  - `SortEnum.ASC` - Ascending order
@@ -173,13 +292,47 @@ URI is generated invoking the _generateUri_ method of the NgQubeeService. An obs
173
292
  this._ngQubeeService.generateUri().subscribe(uri => console.log(uri));
174
293
  ```
175
294
 
295
+ ### Deleting State
296
+
297
+ All query features have corresponding delete methods:
298
+
299
+ ```typescript
300
+ // Spatie + NestJS
301
+ this._ngQubeeService.deleteFilters('status', 'role');
302
+ this._ngQubeeService.deleteSorts('created_at');
303
+
304
+ // Spatie only
305
+ this._ngQubeeService.deleteFields({ users: ['email'] });
306
+ this._ngQubeeService.deleteFieldsByModel('users', 'email');
307
+ this._ngQubeeService.deleteIncludes('profile');
308
+
309
+ // NestJS only
310
+ this._ngQubeeService.deleteOperatorFilters('age');
311
+ this._ngQubeeService.deleteSelect('email');
312
+ this._ngQubeeService.deleteSearch();
313
+ ```
314
+
176
315
  ### Reset state
177
- Query Builder state can be cleaned with the reset method. This will clean up everything set up previously, including the current model, filters, includes and so on...
316
+ Query Builder state can be cleaned with the reset method. This will clean up everything set up previously, including the current resource, filters, includes and so on...
178
317
 
179
318
  ```typescript
180
319
  this._ngQubeeService.reset();
181
320
  ```
182
321
 
322
+ ### Driver Validation
323
+
324
+ Calling a method that is not supported by the active driver throws a descriptive error immediately:
325
+
326
+ | Method | Laravel | Spatie | NestJS |
327
+ |---|---|---|---|
328
+ | `addFilter()` / `deleteFilters()` | throws `UnsupportedFilterError` | supported | supported |
329
+ | `addSort()` / `deleteSorts()` | throws `UnsupportedSortError` | supported | supported |
330
+ | `addFields()` / `deleteFields()` / `deleteFieldsByModel()` | throws `UnsupportedFieldSelectionError` | supported | throws `UnsupportedFieldSelectionError` |
331
+ | `addIncludes()` / `deleteIncludes()` | throws `UnsupportedIncludesError` | supported | throws `UnsupportedIncludesError` |
332
+ | `addFilterOperator()` / `deleteOperatorFilters()` | throws `UnsupportedFilterOperatorError` | throws `UnsupportedFilterOperatorError` | supported |
333
+ | `addSelect()` / `deleteSelect()` | throws `UnsupportedSelectError` | throws `UnsupportedSelectError` | supported |
334
+ | `setSearch()` / `deleteSearch()` | throws `UnsupportedSearchError` | throws `UnsupportedSearchError` | supported |
335
+
183
336
  ## Pagination
184
337
  If you are working with an API that supports pagination, we have got you covered 😉 NgQubee provides:
185
338
  - A PaginatedCollection class that holds paginated data
@@ -199,31 +352,69 @@ this._pg.paginate<Model>({ ...response, data: response.data.map(e => new Mo
199
352
 
200
353
  The "paginate" method returns a PaginatedCollection that helps handling paginated data. Additionally, if you are dealing with a state library in your application, you can use the "normalize" method of the collection to normalize the data.
201
354
 
202
- As you can see from the example, the paginate method requires a generic type: put there your model and you'll be provided with a PaginatedCollection<Model>. By default, the paginated collection will check for the following keys in the response:
355
+ ### Laravel / Spatie Response Format
356
+
357
+ When using the Laravel or Spatie driver, the paginated collection will check for the following keys in the response:
203
358
 
204
359
  - data - the key that holds the response data
205
- - currentPage - requested page for the pagination
206
- - from - Showing items from n (where n is a number)
207
- - to - Showing items from n (where n is a number)
208
- - total - Count of the items available in thw whole pagination
209
- - perPage - Items per page
210
- - prevPageUrl - Url to the previous page
211
- - nextPageUrl - Url to the next page
212
- - lastPage - Last page number
213
- - firstPageUrl - Url to the first page
214
- - lastPageUrl - Url to the last page
215
-
216
- Just like the query builder, the pagination service supports customizable keys. While invoking the forRoot method of the module, use the response key to look for different keys in the API response. Let's assume that the "currentPage" key is named "pg" in your API responseL your forRoot configuration will look as following:
360
+ - current_page - requested page for the pagination
361
+ - from - Showing items from n
362
+ - to - Showing items to n
363
+ - total - Count of the items available in the whole pagination
364
+ - per_page - Items per page
365
+ - prev_page_url - URL to the previous page
366
+ - next_page_url - URL to the next page
367
+ - last_page - Last page number
368
+ - first_page_url - URL to the first page
369
+ - last_page_url - URL to the last page
370
+
371
+ ### NestJS Response Format
372
+
373
+ When using the NestJS driver, the PaginationService automatically parses nested responses:
374
+
375
+ ```json
376
+ {
377
+ "data": [...],
378
+ "meta": {
379
+ "currentPage": 1,
380
+ "totalItems": 100,
381
+ "itemsPerPage": 10,
382
+ "totalPages": 10
383
+ },
384
+ "links": {
385
+ "first": "http://api.com/users?page=1",
386
+ "previous": null,
387
+ "next": "http://api.com/users?page=2",
388
+ "last": "http://api.com/users?page=10",
389
+ "current": "http://api.com/users?page=1"
390
+ }
391
+ }
392
+ ```
393
+
394
+ The `from` and `to` values are computed automatically from `currentPage` and `itemsPerPage` when not present in the response.
395
+
396
+ ### Customizing Response Keys
397
+
398
+ Just like the query builder, the pagination service supports customizable keys. While invoking the forRoot method of the module, use the response key to look for different keys in the API response:
217
399
 
218
400
  ```typescript
401
+ // Spatie
219
402
  NgQubeeModule.forRoot({
403
+ driver: DriverEnum.SPATIE,
220
404
  response: {
221
405
  currentPage: 'pg'
222
406
  }
223
407
  })
224
- ```
225
408
 
226
- Feel free to customize your PaginationService as you need, using the keys shown in the upper list.
409
+ // NestJS (use dot-notation for nested paths)
410
+ NgQubeeModule.forRoot({
411
+ driver: DriverEnum.NESTJS,
412
+ response: {
413
+ currentPage: 'pagination.page',
414
+ total: 'pagination.total'
415
+ }
416
+ })
417
+ ```
227
418
 
228
419
  ## TypeScript Support
229
420
 
@@ -232,11 +423,30 @@ NgQubee is fully typed and exports all public interfaces, enums, and types for T
232
423
  ### Available Enums
233
424
 
234
425
  ```typescript
235
- import { SortEnum } from 'ng-qubee';
426
+ import { DriverEnum, FilterOperatorEnum, SortEnum } from 'ng-qubee';
427
+
428
+ // Driver options
429
+ DriverEnum.LARAVEL // 'laravel' (pagination only)
430
+ DriverEnum.SPATIE // 'spatie'
431
+ DriverEnum.NESTJS // 'nestjs'
236
432
 
237
433
  // Sorting options
238
434
  SortEnum.ASC // 'asc'
239
435
  SortEnum.DESC // 'desc'
436
+
437
+ // Filter operators (NestJS only)
438
+ FilterOperatorEnum.EQ // '$eq'
439
+ FilterOperatorEnum.NOT // '$not'
440
+ FilterOperatorEnum.NULL // '$null'
441
+ FilterOperatorEnum.IN // '$in'
442
+ FilterOperatorEnum.GT // '$gt'
443
+ FilterOperatorEnum.GTE // '$gte'
444
+ FilterOperatorEnum.LT // '$lt'
445
+ FilterOperatorEnum.LTE // '$lte'
446
+ FilterOperatorEnum.BTW // '$btw'
447
+ FilterOperatorEnum.ILIKE // '$ilike'
448
+ FilterOperatorEnum.SW // '$sw'
449
+ FilterOperatorEnum.CONTAINS // '$contains'
240
450
  ```
241
451
 
242
452
  ### Available Interfaces
@@ -252,8 +462,9 @@ import {
252
462
  IPaginationConfig
253
463
  } from 'ng-qubee';
254
464
 
255
- // Main configuration interface
465
+ // Main configuration interface (driver is required)
256
466
  const config: IConfig = {
467
+ driver: DriverEnum.NESTJS,
257
468
  request: {
258
469
  filters: 'custom-filter-key',
259
470
  fields: 'custom-fields-key',
@@ -277,7 +488,8 @@ const config: IConfig = {
277
488
  import {
278
489
  IFilters,
279
490
  IFields,
280
- ISort
491
+ ISort,
492
+ IOperatorFilter
281
493
  } from 'ng-qubee';
282
494
 
283
495
  // Filters interface - key-value pairs with array values
@@ -297,30 +509,25 @@ const sort: ISort = {
297
509
  field: 'created_at',
298
510
  order: SortEnum.DESC
299
511
  };
512
+
513
+ // Operator filter interface (NestJS only)
514
+ const operatorFilter: IOperatorFilter = {
515
+ field: 'age',
516
+ operator: FilterOperatorEnum.GTE,
517
+ values: [18]
518
+ };
300
519
  ```
301
520
 
302
- #### Pagination Interfaces
521
+ #### Strategy Interfaces
303
522
 
304
523
  ```typescript
305
- import { IPaginatedObject } from 'ng-qubee';
306
-
307
- // Use with your model type
308
- interface User {
309
- id: number;
310
- email: string;
311
- username: string;
312
- }
313
-
314
- // The paginated object can contain any additional keys
315
- const paginatedData: IPaginatedObject = {
316
- data: [/* users */],
317
- currentPage: 1,
318
- total: 100,
319
- perPage: 15
320
- };
524
+ import {
525
+ IRequestStrategy,
526
+ IResponseStrategy
527
+ } from 'ng-qubee';
321
528
  ```
322
529
 
323
- ### Usage Example with Full Types
530
+ ### Spatie Usage Example
324
531
 
325
532
  ```typescript
326
533
  import { Component, OnInit } from '@angular/core';
@@ -340,7 +547,7 @@ export class UsersComponent implements OnInit {
340
547
 
341
548
  ngOnInit(): void {
342
549
  // Set up the query with type safety
343
- this.ngQubee.setModel('users');
550
+ this.ngQubee.setResource('users');
344
551
 
345
552
  // Define fields with type checking
346
553
  const userFields: IFields = {
@@ -368,6 +575,46 @@ export class UsersComponent implements OnInit {
368
575
  }
369
576
  ```
370
577
 
578
+ ### NestJS Usage Example
579
+
580
+ ```typescript
581
+ import { Component, OnInit } from '@angular/core';
582
+ import {
583
+ NgQubeeService,
584
+ PaginationService,
585
+ FilterOperatorEnum,
586
+ SortEnum
587
+ } from 'ng-qubee';
588
+
589
+ @Component({
590
+ selector: 'app-users',
591
+ template: '...'
592
+ })
593
+ export class UsersComponent implements OnInit {
594
+ constructor(
595
+ private ngQubee: NgQubeeService,
596
+ private pagination: PaginationService
597
+ ) {}
598
+
599
+ ngOnInit(): void {
600
+ this.ngQubee
601
+ .setResource('users')
602
+ .addFilterOperator('age', FilterOperatorEnum.GTE, 18)
603
+ .addFilter('status', 'active')
604
+ .addSelect('id', 'name', 'email')
605
+ .addSort('name', SortEnum.ASC)
606
+ .setSearch('john')
607
+ .setLimit(10)
608
+ .setPage(1);
609
+
610
+ this.ngQubee.generateUri().subscribe(uri => {
611
+ console.log(uri);
612
+ // Output: /users?filter.status=active&filter.age=$gte:18&sortBy=name:ASC&select=id,name,email&search=john&limit=10&page=1
613
+ });
614
+ }
615
+ }
616
+ ```
617
+
371
618
  [ng-qubee]: <https://github.com/AndreaAlhena/ng-qubee>
372
619
  [rxjs]: <https://reactivex.io>
373
620
  [qs]: <https://github.com/ljharb/qs>