ng-qubee 3.0.0 → 3.2.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.
- package/README.md +203 -17
- package/fesm2022/ng-qubee.mjs +1035 -398
- package/fesm2022/ng-qubee.mjs.map +1 -1
- package/package.json +1 -2
- package/types/ng-qubee.d.ts +487 -29
package/fesm2022/ng-qubee.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, Inject,
|
|
2
|
+
import { signal, computed, Injectable, InjectionToken, Inject, makeEnvironmentProviders, NgModule } from '@angular/core';
|
|
3
3
|
import { BehaviorSubject, filter, throwError } from 'rxjs';
|
|
4
4
|
import * as qs from 'qs';
|
|
5
5
|
|
|
@@ -74,11 +74,43 @@ class PaginatedCollection {
|
|
|
74
74
|
*/
|
|
75
75
|
var DriverEnum;
|
|
76
76
|
(function (DriverEnum) {
|
|
77
|
+
DriverEnum["JSON_API"] = "json-api";
|
|
77
78
|
DriverEnum["LARAVEL"] = "laravel";
|
|
78
79
|
DriverEnum["NESTJS"] = "nestjs";
|
|
79
80
|
DriverEnum["SPATIE"] = "spatie";
|
|
80
81
|
})(DriverEnum || (DriverEnum = {}));
|
|
81
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Resolved query parameter key names with defaults applied
|
|
85
|
+
*
|
|
86
|
+
* Maps logical query concepts to the actual query parameter names
|
|
87
|
+
* used in the generated URI. Unset values fall back to defaults.
|
|
88
|
+
*/
|
|
89
|
+
class QueryBuilderOptions {
|
|
90
|
+
appends;
|
|
91
|
+
fields;
|
|
92
|
+
filters;
|
|
93
|
+
includes;
|
|
94
|
+
limit;
|
|
95
|
+
page;
|
|
96
|
+
search;
|
|
97
|
+
select;
|
|
98
|
+
sort;
|
|
99
|
+
sortBy;
|
|
100
|
+
constructor(options) {
|
|
101
|
+
this.appends = options.appends || 'append';
|
|
102
|
+
this.fields = options.fields || 'fields';
|
|
103
|
+
this.filters = options.filters || 'filter';
|
|
104
|
+
this.includes = options.includes || 'include';
|
|
105
|
+
this.limit = options.limit || 'limit';
|
|
106
|
+
this.page = options.page || 'page';
|
|
107
|
+
this.search = options.search || 'search';
|
|
108
|
+
this.select = options.select || 'select';
|
|
109
|
+
this.sort = options.sort || 'sort';
|
|
110
|
+
this.sortBy = options.sortBy || 'sortBy';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
82
114
|
/**
|
|
83
115
|
* Resolved response field key names with defaults applied
|
|
84
116
|
*
|
|
@@ -121,6 +153,31 @@ class ResponseOptions {
|
|
|
121
153
|
this.total = options.total || 'total';
|
|
122
154
|
}
|
|
123
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Pre-configured ResponseOptions for the JSON:API driver
|
|
158
|
+
*
|
|
159
|
+
* Uses dot-notation paths to access nested values in the JSON:API response format.
|
|
160
|
+
* JSON:API meta key names vary by implementation; these defaults cover the most
|
|
161
|
+
* common conventions and can be fully customised via `IPaginationConfig`.
|
|
162
|
+
*/
|
|
163
|
+
class JsonApiResponseOptions extends ResponseOptions {
|
|
164
|
+
constructor(options) {
|
|
165
|
+
super({
|
|
166
|
+
currentPage: options.currentPage || 'meta.current-page',
|
|
167
|
+
data: options.data || 'data',
|
|
168
|
+
firstPageUrl: options.firstPageUrl || 'links.first',
|
|
169
|
+
from: options.from || 'meta.from',
|
|
170
|
+
lastPage: options.lastPage || 'meta.page-count',
|
|
171
|
+
lastPageUrl: options.lastPageUrl || 'links.last',
|
|
172
|
+
nextPageUrl: options.nextPageUrl || 'links.next',
|
|
173
|
+
path: options.path || 'path',
|
|
174
|
+
perPage: options.perPage || 'meta.per-page',
|
|
175
|
+
prevPageUrl: options.prevPageUrl || 'links.prev',
|
|
176
|
+
to: options.to || 'meta.to',
|
|
177
|
+
total: options.total || 'meta.total'
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
124
181
|
/**
|
|
125
182
|
* Pre-configured ResponseOptions for the NestJS driver
|
|
126
183
|
*
|
|
@@ -145,131 +202,6 @@ class NestjsResponseOptions extends ResponseOptions {
|
|
|
145
202
|
}
|
|
146
203
|
}
|
|
147
204
|
|
|
148
|
-
/**
|
|
149
|
-
* Error thrown when per-model field selection is attempted with a driver that does not support it
|
|
150
|
-
*
|
|
151
|
-
* Per-model field selection is only supported by the Spatie driver.
|
|
152
|
-
* Use `addSelect()` for NestJS flat field selection.
|
|
153
|
-
*/
|
|
154
|
-
class UnsupportedFieldSelectionError extends Error {
|
|
155
|
-
constructor() {
|
|
156
|
-
super('Per-model field selection is only supported by the Spatie driver. Use addSelect() for NestJS.');
|
|
157
|
-
this.name = 'UnsupportedFieldSelectionError';
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Error thrown when filters are attempted with a driver that does not support them
|
|
163
|
-
*
|
|
164
|
-
* Filters are only supported by the Spatie and NestJS drivers.
|
|
165
|
-
*/
|
|
166
|
-
class UnsupportedFilterError extends Error {
|
|
167
|
-
constructor() {
|
|
168
|
-
super('Filters are only supported by the Spatie and NestJS drivers.');
|
|
169
|
-
this.name = 'UnsupportedFilterError';
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Error thrown when filter operators are attempted with a driver that does not support them
|
|
175
|
-
*
|
|
176
|
-
* Filter operators are only supported by the NestJS driver.
|
|
177
|
-
* Use `addFilter()` for Spatie implicit equality filters.
|
|
178
|
-
*/
|
|
179
|
-
class UnsupportedFilterOperatorError extends Error {
|
|
180
|
-
constructor() {
|
|
181
|
-
super('Filter operators are only supported by the NestJS driver. Use addFilter() for Spatie.');
|
|
182
|
-
this.name = 'UnsupportedFilterOperatorError';
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Error thrown when includes are attempted with a driver that does not support them
|
|
188
|
-
*
|
|
189
|
-
* Includes are only supported by the Spatie driver.
|
|
190
|
-
*/
|
|
191
|
-
class UnsupportedIncludesError extends Error {
|
|
192
|
-
constructor() {
|
|
193
|
-
super('Includes are only supported by the Spatie driver.');
|
|
194
|
-
this.name = 'UnsupportedIncludesError';
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Error thrown when search is attempted with a driver that does not support it
|
|
200
|
-
*
|
|
201
|
-
* Search is only supported by the NestJS driver.
|
|
202
|
-
*/
|
|
203
|
-
class UnsupportedSearchError extends Error {
|
|
204
|
-
constructor() {
|
|
205
|
-
super('Search is only supported by the NestJS driver.');
|
|
206
|
-
this.name = 'UnsupportedSearchError';
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Error thrown when flat field selection is attempted with a driver that does not support it
|
|
212
|
-
*
|
|
213
|
-
* Flat field selection is only supported by the NestJS driver.
|
|
214
|
-
* Use `addFields()` for Spatie per-model field selection.
|
|
215
|
-
*/
|
|
216
|
-
class UnsupportedSelectError extends Error {
|
|
217
|
-
constructor() {
|
|
218
|
-
super('Flat field selection is only supported by the NestJS driver. Use addFields() for Spatie.');
|
|
219
|
-
this.name = 'UnsupportedSelectError';
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Error thrown when sorts are attempted with a driver that does not support them
|
|
225
|
-
*
|
|
226
|
-
* Sorts are only supported by the Spatie and NestJS drivers.
|
|
227
|
-
*/
|
|
228
|
-
class UnsupportedSortError extends Error {
|
|
229
|
-
constructor() {
|
|
230
|
-
super('Sorts are only supported by the Spatie and NestJS drivers.');
|
|
231
|
-
this.name = 'UnsupportedSortError';
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Resolved query parameter key names with defaults applied
|
|
237
|
-
*
|
|
238
|
-
* Maps logical query concepts to the actual query parameter names
|
|
239
|
-
* used in the generated URI. Unset values fall back to defaults.
|
|
240
|
-
*/
|
|
241
|
-
class QueryBuilderOptions {
|
|
242
|
-
appends;
|
|
243
|
-
fields;
|
|
244
|
-
filters;
|
|
245
|
-
includes;
|
|
246
|
-
limit;
|
|
247
|
-
page;
|
|
248
|
-
search;
|
|
249
|
-
select;
|
|
250
|
-
sort;
|
|
251
|
-
sortBy;
|
|
252
|
-
constructor(options) {
|
|
253
|
-
this.appends = options.appends || 'append';
|
|
254
|
-
this.fields = options.fields || 'fields';
|
|
255
|
-
this.filters = options.filters || 'filter';
|
|
256
|
-
this.includes = options.includes || 'include';
|
|
257
|
-
this.limit = options.limit || 'limit';
|
|
258
|
-
this.page = options.page || 'page';
|
|
259
|
-
this.search = options.search || 'search';
|
|
260
|
-
this.select = options.select || 'select';
|
|
261
|
-
this.sort = options.sort || 'sort';
|
|
262
|
-
this.sortBy = options.sortBy || 'sortBy';
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
class InvalidLimitError extends Error {
|
|
267
|
-
constructor(limit) {
|
|
268
|
-
super(`Invalid limit value: Limit must be a positive integer greater than 0. Received: ${limit}`);
|
|
269
|
-
this.name = 'InvalidLimitError';
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
205
|
/**
|
|
274
206
|
* Error thrown when an invalid resource name is provided
|
|
275
207
|
*
|
|
@@ -294,6 +226,8 @@ const INITIAL_STATE = {
|
|
|
294
226
|
fields: {},
|
|
295
227
|
filters: {},
|
|
296
228
|
includes: [],
|
|
229
|
+
isLastPageKnown: false,
|
|
230
|
+
lastPage: 1,
|
|
297
231
|
limit: 15,
|
|
298
232
|
operatorFilters: [],
|
|
299
233
|
page: 1,
|
|
@@ -333,15 +267,17 @@ class NestService {
|
|
|
333
267
|
}
|
|
334
268
|
/**
|
|
335
269
|
* Set the limit for paginated results
|
|
336
|
-
*
|
|
270
|
+
*
|
|
271
|
+
* This setter performs a raw state write. Validation of the value is the
|
|
272
|
+
* responsibility of the active request strategy and is enforced upstream
|
|
273
|
+
* by `NgQubeeService.setLimit()`, because the accepted range depends on
|
|
274
|
+
* the driver (e.g. nestjs-paginate accepts `-1` for "fetch all").
|
|
337
275
|
*
|
|
338
276
|
* @param {number} limit - The number of items per page
|
|
339
|
-
* @throws {InvalidLimitError} If limit is not a positive integer
|
|
340
277
|
* @example
|
|
341
278
|
* service.limit = 25;
|
|
342
279
|
*/
|
|
343
280
|
set limit(limit) {
|
|
344
|
-
this._validateLimit(limit);
|
|
345
281
|
this._nest.update(nest => ({
|
|
346
282
|
...nest,
|
|
347
283
|
limit
|
|
@@ -382,18 +318,6 @@ class NestService {
|
|
|
382
318
|
_clone(obj) {
|
|
383
319
|
return JSON.parse(JSON.stringify(obj));
|
|
384
320
|
}
|
|
385
|
-
/**
|
|
386
|
-
* Validates that the limit is a positive integer
|
|
387
|
-
*
|
|
388
|
-
* @param {number} limit - The limit value to validate
|
|
389
|
-
* @throws {InvalidLimitError} If limit is not a positive integer
|
|
390
|
-
* @private
|
|
391
|
-
*/
|
|
392
|
-
_validateLimit(limit) {
|
|
393
|
-
if (!Number.isInteger(limit) || limit < 1) {
|
|
394
|
-
throw new InvalidLimitError(limit);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
321
|
/**
|
|
398
322
|
* Validates that the page number is a positive integer
|
|
399
323
|
*
|
|
@@ -693,6 +617,25 @@ class NestService {
|
|
|
693
617
|
search
|
|
694
618
|
}));
|
|
695
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* Atomically record the `lastPage` value from a paginated response and
|
|
622
|
+
* flip `isLastPageKnown` to `true`
|
|
623
|
+
*
|
|
624
|
+
* Called exclusively by `PaginationService.paginate()` as part of the
|
|
625
|
+
* auto-sync contract; not intended to be invoked by consumers directly.
|
|
626
|
+
* Keeping the two fields under a single write guarantees they cannot
|
|
627
|
+
* drift out of sync.
|
|
628
|
+
*
|
|
629
|
+
* @param {number} lastPage - The last page number parsed from the most recent paginated response
|
|
630
|
+
* @return {void}
|
|
631
|
+
*/
|
|
632
|
+
syncLastPage(lastPage) {
|
|
633
|
+
this._nest.update(nest => ({
|
|
634
|
+
...nest,
|
|
635
|
+
isLastPageKnown: true,
|
|
636
|
+
lastPage
|
|
637
|
+
}));
|
|
638
|
+
}
|
|
696
639
|
/**
|
|
697
640
|
* Reset the query builder state to initial values
|
|
698
641
|
* Clears all fields, filters, includes, sorts, and resets pagination
|
|
@@ -711,80 +654,228 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
711
654
|
type: Injectable
|
|
712
655
|
}], ctorParameters: () => [] });
|
|
713
656
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
/**
|
|
725
|
-
* The request strategy that builds URIs for the active driver
|
|
726
|
-
*/
|
|
727
|
-
_requestStrategy;
|
|
728
|
-
/**
|
|
729
|
-
* Internal BehaviorSubject that holds the latest generated URI
|
|
730
|
-
*/
|
|
731
|
-
_uri$ = new BehaviorSubject('');
|
|
732
|
-
/**
|
|
733
|
-
* Observable that emits non-empty generated URIs
|
|
734
|
-
*/
|
|
735
|
-
uri$ = this._uri$.asObservable().pipe(filter(uri => !!uri));
|
|
736
|
-
constructor(_nestService, requestStrategy, driver, options = {}) {
|
|
737
|
-
this._nestService = _nestService;
|
|
738
|
-
this._driver = driver;
|
|
739
|
-
this._options = new QueryBuilderOptions(options);
|
|
740
|
-
this._requestStrategy = requestStrategy;
|
|
741
|
-
}
|
|
657
|
+
/**
|
|
658
|
+
* Thrown when a pagination helper that needs `state.lastPage` is called
|
|
659
|
+
* before `PaginationService.paginate()` has ever synced a value.
|
|
660
|
+
*
|
|
661
|
+
* Examples: `NgQubeeService.lastPage()`, `NgQubeeService.totalPages()`.
|
|
662
|
+
*
|
|
663
|
+
* Safe-for-templates predicates (`isLastPage`, `hasNextPage`, etc.) do not
|
|
664
|
+
* throw and return conservative defaults instead.
|
|
665
|
+
*/
|
|
666
|
+
class PaginationNotSyncedError extends Error {
|
|
742
667
|
/**
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
* @param error - The error to throw if the driver is not allowed
|
|
747
|
-
* @throws The provided error if the active driver is not in the allowed list
|
|
668
|
+
* @param action - Short imperative describing what the caller was trying
|
|
669
|
+
* to do (e.g. "navigate to last page", "read totalPages"). Surfaced in
|
|
670
|
+
* the error message so the cause is obvious at the call site.
|
|
748
671
|
*/
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
672
|
+
constructor(action) {
|
|
673
|
+
super(`Cannot ${action}: no paginated response has been synced yet. Call PaginationService.paginate() at least once first.`);
|
|
674
|
+
this.name = 'PaginationNotSyncedError';
|
|
753
675
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
}
|
|
767
|
-
this._nestService.addFields({ [model]: fields });
|
|
768
|
-
return this;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Error thrown when per-model field selection is attempted with a driver that does not support it
|
|
680
|
+
*
|
|
681
|
+
* Per-model field selection is only supported by the Spatie driver.
|
|
682
|
+
* Use `addSelect()` for NestJS flat field selection.
|
|
683
|
+
*/
|
|
684
|
+
class UnsupportedFieldSelectionError extends Error {
|
|
685
|
+
constructor() {
|
|
686
|
+
super('Per-model field selection is only supported by the Spatie driver. Use addSelect() for NestJS.');
|
|
687
|
+
this.name = 'UnsupportedFieldSelectionError';
|
|
769
688
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Error thrown when filters are attempted with a driver that does not support them
|
|
693
|
+
*
|
|
694
|
+
* Filters are only supported by the Spatie and NestJS drivers.
|
|
695
|
+
*/
|
|
696
|
+
class UnsupportedFilterError extends Error {
|
|
697
|
+
constructor() {
|
|
698
|
+
super('Filters are only supported by the Spatie and NestJS drivers.');
|
|
699
|
+
this.name = 'UnsupportedFilterError';
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Error thrown when filter operators are attempted with a driver that does not support them
|
|
705
|
+
*
|
|
706
|
+
* Filter operators are only supported by the NestJS driver.
|
|
707
|
+
* Use `addFilter()` for Spatie implicit equality filters.
|
|
708
|
+
*/
|
|
709
|
+
class UnsupportedFilterOperatorError extends Error {
|
|
710
|
+
constructor() {
|
|
711
|
+
super('Filter operators are only supported by the NestJS driver. Use addFilter() for Spatie.');
|
|
712
|
+
this.name = 'UnsupportedFilterOperatorError';
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Error thrown when includes are attempted with a driver that does not support them
|
|
718
|
+
*
|
|
719
|
+
* Includes are only supported by the Spatie driver.
|
|
720
|
+
*/
|
|
721
|
+
class UnsupportedIncludesError extends Error {
|
|
722
|
+
constructor() {
|
|
723
|
+
super('Includes are only supported by the Spatie driver.');
|
|
724
|
+
this.name = 'UnsupportedIncludesError';
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Error thrown when search is attempted with a driver that does not support it
|
|
730
|
+
*
|
|
731
|
+
* Search is only supported by the NestJS driver.
|
|
732
|
+
*/
|
|
733
|
+
class UnsupportedSearchError extends Error {
|
|
734
|
+
constructor() {
|
|
735
|
+
super('Search is only supported by the NestJS driver.');
|
|
736
|
+
this.name = 'UnsupportedSearchError';
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Error thrown when flat field selection is attempted with a driver that does not support it
|
|
742
|
+
*
|
|
743
|
+
* Flat field selection is only supported by the NestJS driver.
|
|
744
|
+
* Use `addFields()` for Spatie per-model field selection.
|
|
745
|
+
*/
|
|
746
|
+
class UnsupportedSelectError extends Error {
|
|
747
|
+
constructor() {
|
|
748
|
+
super('Flat field selection is only supported by the NestJS driver. Use addFields() for Spatie.');
|
|
749
|
+
this.name = 'UnsupportedSelectError';
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Error thrown when sorts are attempted with a driver that does not support them
|
|
755
|
+
*
|
|
756
|
+
* Sorts are only supported by the Spatie and NestJS drivers.
|
|
757
|
+
*/
|
|
758
|
+
class UnsupportedSortError extends Error {
|
|
759
|
+
constructor() {
|
|
760
|
+
super('Sorts are only supported by the Spatie and NestJS drivers.');
|
|
761
|
+
this.name = 'UnsupportedSortError';
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Injection token for the active pagination driver
|
|
767
|
+
*
|
|
768
|
+
* Provided by `provideNgQubee()` / `NgQubeeModule.forRoot()` from the
|
|
769
|
+
* user-supplied `IConfig.driver`. Services read it to gate driver-specific
|
|
770
|
+
* behavior (e.g. `NgQubeeService._assertDriver`).
|
|
771
|
+
*/
|
|
772
|
+
const NG_QUBEE_DRIVER = new InjectionToken('NG_QUBEE_DRIVER');
|
|
773
|
+
/**
|
|
774
|
+
* Injection token for the resolved request URI strategy
|
|
775
|
+
*
|
|
776
|
+
* Provided by `provideNgQubee()` / `NgQubeeModule.forRoot()` based on the
|
|
777
|
+
* active driver. Used by `NgQubeeService` to build request URIs.
|
|
778
|
+
*/
|
|
779
|
+
const NG_QUBEE_REQUEST_STRATEGY = new InjectionToken('NG_QUBEE_REQUEST_STRATEGY');
|
|
780
|
+
/**
|
|
781
|
+
* Injection token for the resolved request query-parameter key options
|
|
782
|
+
*
|
|
783
|
+
* Provided as a fully-built `QueryBuilderOptions` instance. `provideNgQubee()`
|
|
784
|
+
* constructs it from `IConfig.request`; consumers don't interact with this
|
|
785
|
+
* token directly.
|
|
786
|
+
*/
|
|
787
|
+
const NG_QUBEE_REQUEST_OPTIONS = new InjectionToken('NG_QUBEE_REQUEST_OPTIONS');
|
|
788
|
+
/**
|
|
789
|
+
* Injection token for the resolved response parsing strategy
|
|
790
|
+
*
|
|
791
|
+
* Provided by `provideNgQubee()` / `NgQubeeModule.forRoot()` based on the
|
|
792
|
+
* active driver. Used by `PaginationService` to parse paginated responses.
|
|
793
|
+
*/
|
|
794
|
+
const NG_QUBEE_RESPONSE_STRATEGY = new InjectionToken('NG_QUBEE_RESPONSE_STRATEGY');
|
|
795
|
+
/**
|
|
796
|
+
* Injection token for the resolved response field-key options
|
|
797
|
+
*
|
|
798
|
+
* Provided as a fully-built `ResponseOptions` instance (or a driver-specific
|
|
799
|
+
* subclass like `JsonApiResponseOptions` / `NestjsResponseOptions`).
|
|
800
|
+
* `provideNgQubee()` constructs the correct variant from `IConfig.response`.
|
|
801
|
+
*/
|
|
802
|
+
const NG_QUBEE_RESPONSE_OPTIONS = new InjectionToken('NG_QUBEE_RESPONSE_OPTIONS');
|
|
803
|
+
|
|
804
|
+
class NgQubeeService {
|
|
805
|
+
_nestService;
|
|
806
|
+
/**
|
|
807
|
+
* The active pagination driver
|
|
808
|
+
*/
|
|
809
|
+
_driver;
|
|
810
|
+
/**
|
|
811
|
+
* Resolved query parameter key name options
|
|
812
|
+
*/
|
|
813
|
+
_options;
|
|
814
|
+
/**
|
|
815
|
+
* The request strategy that builds URIs for the active driver
|
|
816
|
+
*/
|
|
817
|
+
_requestStrategy;
|
|
818
|
+
/**
|
|
819
|
+
* Internal BehaviorSubject that holds the latest generated URI
|
|
820
|
+
*/
|
|
821
|
+
_uri$ = new BehaviorSubject('');
|
|
822
|
+
/**
|
|
823
|
+
* Observable that emits non-empty generated URIs
|
|
824
|
+
*/
|
|
825
|
+
uri$ = this._uri$.asObservable().pipe(filter(uri => !!uri));
|
|
826
|
+
constructor(_nestService, requestStrategy, driver, options = new QueryBuilderOptions({})) {
|
|
827
|
+
this._nestService = _nestService;
|
|
828
|
+
this._driver = driver;
|
|
829
|
+
this._options = options;
|
|
830
|
+
this._requestStrategy = requestStrategy;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Assert that the active driver is one of the allowed drivers
|
|
834
|
+
*
|
|
835
|
+
* @param allowed - The allowed drivers
|
|
836
|
+
* @param error - The error to throw if the driver is not allowed
|
|
837
|
+
* @throws The provided error if the active driver is not in the allowed list
|
|
838
|
+
*/
|
|
839
|
+
_assertDriver(allowed, error) {
|
|
840
|
+
if (!allowed.includes(this._driver)) {
|
|
841
|
+
throw error;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Add fields to the select statement for the given model (JSON:API and Spatie only)
|
|
846
|
+
*
|
|
847
|
+
* @param model - Model that holds the fields
|
|
848
|
+
* @param fields - Fields to select
|
|
849
|
+
* @returns {this}
|
|
850
|
+
* @throws {UnsupportedFieldSelectionError} If the active driver does not support per-model field selection
|
|
851
|
+
*/
|
|
852
|
+
addFields(model, fields) {
|
|
853
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.SPATIE], new UnsupportedFieldSelectionError());
|
|
854
|
+
if (!fields.length) {
|
|
855
|
+
return this;
|
|
856
|
+
}
|
|
857
|
+
this._nestService.addFields({ [model]: fields });
|
|
858
|
+
return this;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Add a filter with the given value(s) (JSON:API, NestJS, and Spatie)
|
|
862
|
+
*
|
|
863
|
+
* Produces: `filter[field]=value` (JSON:API / Spatie) or `filter.field=value` (NestJS)
|
|
864
|
+
*
|
|
865
|
+
* @param {string} field - Name of the field to filter
|
|
866
|
+
* @param {(string | number | boolean)[]} values - The needle(s)
|
|
867
|
+
* @returns {this}
|
|
778
868
|
* @throws {UnsupportedFilterError} If the active driver does not support filters
|
|
779
869
|
*/
|
|
780
870
|
addFilter(field, ...values) {
|
|
781
|
-
this._assertDriver([DriverEnum.
|
|
871
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.NESTJS, DriverEnum.SPATIE], new UnsupportedFilterError());
|
|
782
872
|
if (!values.length) {
|
|
783
873
|
return this;
|
|
784
874
|
}
|
|
785
875
|
this._nestService.addFilters({
|
|
786
876
|
[field]: values
|
|
787
877
|
});
|
|
878
|
+
this._nestService.page = 1;
|
|
788
879
|
return this;
|
|
789
880
|
}
|
|
790
881
|
/**
|
|
@@ -804,17 +895,18 @@ class NgQubeeService {
|
|
|
804
895
|
return this;
|
|
805
896
|
}
|
|
806
897
|
this._nestService.addOperatorFilters([{ field, operator, values }]);
|
|
898
|
+
this._nestService.page = 1;
|
|
807
899
|
return this;
|
|
808
900
|
}
|
|
809
901
|
/**
|
|
810
|
-
* Add related entities to include in the request (Spatie only)
|
|
902
|
+
* Add related entities to include in the request (JSON:API and Spatie only)
|
|
811
903
|
*
|
|
812
904
|
* @param {string[]} models - Models to include
|
|
813
905
|
* @returns {this}
|
|
814
906
|
* @throws {UnsupportedIncludesError} If the active driver does not support includes
|
|
815
907
|
*/
|
|
816
908
|
addIncludes(...models) {
|
|
817
|
-
this._assertDriver([DriverEnum.SPATIE], new UnsupportedIncludesError());
|
|
909
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.SPATIE], new UnsupportedIncludesError());
|
|
818
910
|
if (!models.length) {
|
|
819
911
|
return this;
|
|
820
912
|
}
|
|
@@ -839,7 +931,7 @@ class NgQubeeService {
|
|
|
839
931
|
return this;
|
|
840
932
|
}
|
|
841
933
|
/**
|
|
842
|
-
* Add a field with a sort criteria (
|
|
934
|
+
* Add a field with a sort criteria (JSON:API, NestJS, and Spatie)
|
|
843
935
|
*
|
|
844
936
|
* @param field - Field to use for sorting
|
|
845
937
|
* @param {SortEnum} order - A value from the SortEnum enumeration
|
|
@@ -847,15 +939,25 @@ class NgQubeeService {
|
|
|
847
939
|
* @throws {UnsupportedSortError} If the active driver does not support sorts
|
|
848
940
|
*/
|
|
849
941
|
addSort(field, order) {
|
|
850
|
-
this._assertDriver([DriverEnum.
|
|
942
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.NESTJS, DriverEnum.SPATIE], new UnsupportedSortError());
|
|
851
943
|
this._nestService.addSort({
|
|
852
944
|
field,
|
|
853
945
|
order
|
|
854
946
|
});
|
|
947
|
+
this._nestService.page = 1;
|
|
855
948
|
return this;
|
|
856
949
|
}
|
|
857
950
|
/**
|
|
858
|
-
*
|
|
951
|
+
* Get the current page number
|
|
952
|
+
*
|
|
953
|
+
* @remarks Always safe to call. Thin accessor over the internal state's `page` field.
|
|
954
|
+
* @returns The current page number
|
|
955
|
+
*/
|
|
956
|
+
currentPage() {
|
|
957
|
+
return this._nestService.nest().page;
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Delete selected fields for the given models in the current query builder state (JSON:API and Spatie only)
|
|
859
961
|
*
|
|
860
962
|
* ```
|
|
861
963
|
* ngQubeeService.deleteFields({
|
|
@@ -869,12 +971,12 @@ class NgQubeeService {
|
|
|
869
971
|
* @throws {UnsupportedFieldSelectionError} If the active driver does not support per-model field selection
|
|
870
972
|
*/
|
|
871
973
|
deleteFields(fields) {
|
|
872
|
-
this._assertDriver([DriverEnum.SPATIE], new UnsupportedFieldSelectionError());
|
|
974
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.SPATIE], new UnsupportedFieldSelectionError());
|
|
873
975
|
this._nestService.deleteFields(fields);
|
|
874
976
|
return this;
|
|
875
977
|
}
|
|
876
978
|
/**
|
|
877
|
-
* Delete selected fields for the given model in the current query builder state (Spatie only)
|
|
979
|
+
* Delete selected fields for the given model in the current query builder state (JSON:API and Spatie only)
|
|
878
980
|
*
|
|
879
981
|
* ```
|
|
880
982
|
* ngQubeeService.deleteFieldsByModel('users', 'email', 'password');
|
|
@@ -886,7 +988,7 @@ class NgQubeeService {
|
|
|
886
988
|
* @throws {UnsupportedFieldSelectionError} If the active driver does not support per-model field selection
|
|
887
989
|
*/
|
|
888
990
|
deleteFieldsByModel(model, ...fields) {
|
|
889
|
-
this._assertDriver([DriverEnum.SPATIE], new UnsupportedFieldSelectionError());
|
|
991
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.SPATIE], new UnsupportedFieldSelectionError());
|
|
890
992
|
if (!fields.length) {
|
|
891
993
|
return this;
|
|
892
994
|
}
|
|
@@ -896,29 +998,30 @@ class NgQubeeService {
|
|
|
896
998
|
return this;
|
|
897
999
|
}
|
|
898
1000
|
/**
|
|
899
|
-
* Remove given filters from the query builder state (
|
|
1001
|
+
* Remove given filters from the query builder state (JSON:API, NestJS, and Spatie)
|
|
900
1002
|
*
|
|
901
1003
|
* @param {string[]} filters - Filters to remove
|
|
902
1004
|
* @returns {this}
|
|
903
1005
|
* @throws {UnsupportedFilterError} If the active driver does not support filters
|
|
904
1006
|
*/
|
|
905
1007
|
deleteFilters(...filters) {
|
|
906
|
-
this._assertDriver([DriverEnum.
|
|
1008
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.NESTJS, DriverEnum.SPATIE], new UnsupportedFilterError());
|
|
907
1009
|
if (!filters.length) {
|
|
908
1010
|
return this;
|
|
909
1011
|
}
|
|
910
1012
|
this._nestService.deleteFilters(...filters);
|
|
1013
|
+
this._nestService.page = 1;
|
|
911
1014
|
return this;
|
|
912
1015
|
}
|
|
913
1016
|
/**
|
|
914
|
-
* Remove selected related models from the query builder state (Spatie only)
|
|
1017
|
+
* Remove selected related models from the query builder state (JSON:API and Spatie only)
|
|
915
1018
|
*
|
|
916
1019
|
* @param {string[]} includes - Models to remove
|
|
917
1020
|
* @returns {this}
|
|
918
1021
|
* @throws {UnsupportedIncludesError} If the active driver does not support includes
|
|
919
1022
|
*/
|
|
920
1023
|
deleteIncludes(...includes) {
|
|
921
|
-
this._assertDriver([DriverEnum.SPATIE], new UnsupportedIncludesError());
|
|
1024
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.SPATIE], new UnsupportedIncludesError());
|
|
922
1025
|
if (!includes.length) {
|
|
923
1026
|
return this;
|
|
924
1027
|
}
|
|
@@ -938,6 +1041,7 @@ class NgQubeeService {
|
|
|
938
1041
|
return this;
|
|
939
1042
|
}
|
|
940
1043
|
this._nestService.deleteOperatorFilters(...fields);
|
|
1044
|
+
this._nestService.page = 1;
|
|
941
1045
|
return this;
|
|
942
1046
|
}
|
|
943
1047
|
/**
|
|
@@ -949,6 +1053,7 @@ class NgQubeeService {
|
|
|
949
1053
|
deleteSearch() {
|
|
950
1054
|
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSearchError());
|
|
951
1055
|
this._nestService.deleteSearch();
|
|
1056
|
+
this._nestService.page = 1;
|
|
952
1057
|
return this;
|
|
953
1058
|
}
|
|
954
1059
|
/**
|
|
@@ -967,15 +1072,26 @@ class NgQubeeService {
|
|
|
967
1072
|
return this;
|
|
968
1073
|
}
|
|
969
1074
|
/**
|
|
970
|
-
* Remove sort rules from the query builder state (
|
|
1075
|
+
* Remove sort rules from the query builder state (JSON:API, NestJS, and Spatie)
|
|
971
1076
|
*
|
|
972
1077
|
* @param sorts - Fields used for sorting to remove
|
|
973
1078
|
* @returns {this}
|
|
974
1079
|
* @throws {UnsupportedSortError} If the active driver does not support sorts
|
|
975
1080
|
*/
|
|
976
1081
|
deleteSorts(...sorts) {
|
|
977
|
-
this._assertDriver([DriverEnum.
|
|
1082
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.NESTJS, DriverEnum.SPATIE], new UnsupportedSortError());
|
|
978
1083
|
this._nestService.deleteSorts(...sorts);
|
|
1084
|
+
this._nestService.page = 1;
|
|
1085
|
+
return this;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Navigate to the first page (page 1)
|
|
1089
|
+
*
|
|
1090
|
+
* @remarks Never throws. Idempotent when already on page 1.
|
|
1091
|
+
* @returns {this}
|
|
1092
|
+
*/
|
|
1093
|
+
firstPage() {
|
|
1094
|
+
this._nestService.page = 1;
|
|
979
1095
|
return this;
|
|
980
1096
|
}
|
|
981
1097
|
/**
|
|
@@ -992,6 +1108,106 @@ class NgQubeeService {
|
|
|
992
1108
|
return throwError(() => error);
|
|
993
1109
|
}
|
|
994
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Navigate directly to the specified page
|
|
1113
|
+
*
|
|
1114
|
+
* Validates integer/positive via the existing `setPage` path, and
|
|
1115
|
+
* additionally rejects values that exceed `state.lastPage` when
|
|
1116
|
+
* pagination bounds are known.
|
|
1117
|
+
*
|
|
1118
|
+
* @param n - Target page number
|
|
1119
|
+
* @returns {this}
|
|
1120
|
+
* @throws {InvalidPageNumberError} If `n` is not a positive integer, or if `n > state.lastPage` when `state.isLastPageKnown` is true
|
|
1121
|
+
*/
|
|
1122
|
+
goToPage(n) {
|
|
1123
|
+
const state = this._nestService.nest();
|
|
1124
|
+
if (state.isLastPageKnown && n > state.lastPage) {
|
|
1125
|
+
throw new InvalidPageNumberError(n);
|
|
1126
|
+
}
|
|
1127
|
+
this._nestService.page = n;
|
|
1128
|
+
return this;
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Check whether a next page exists
|
|
1132
|
+
*
|
|
1133
|
+
* @remarks Template-safe. Returns `true` when pagination bounds are unknown (conservative default — keeps a "Next" button enabled before the first `paginate()` call).
|
|
1134
|
+
* @returns `true` if `state.page < state.lastPage` when bounds are known, or `true` when bounds are unknown
|
|
1135
|
+
*/
|
|
1136
|
+
hasNextPage() {
|
|
1137
|
+
const state = this._nestService.nest();
|
|
1138
|
+
return !state.isLastPageKnown || state.page < state.lastPage;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Check whether a previous page exists
|
|
1142
|
+
*
|
|
1143
|
+
* @remarks Always safe. Does not require a synced paginated response.
|
|
1144
|
+
* @returns `true` if `state.page > 1`
|
|
1145
|
+
*/
|
|
1146
|
+
hasPreviousPage() {
|
|
1147
|
+
return this._nestService.nest().page > 1;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Check whether the current page is the first page
|
|
1151
|
+
*
|
|
1152
|
+
* @remarks Always safe. Does not require a synced paginated response.
|
|
1153
|
+
* @returns `true` if `state.page === 1`
|
|
1154
|
+
*/
|
|
1155
|
+
isFirstPage() {
|
|
1156
|
+
return this._nestService.nest().page === 1;
|
|
1157
|
+
}
|
|
1158
|
+
/**
|
|
1159
|
+
* Check whether the current page is the last page
|
|
1160
|
+
*
|
|
1161
|
+
* @remarks Template-safe. Returns `false` when pagination bounds are unknown (no paginated response has been synced yet) — keeps "Next" navigation unblocked until the first `paginate()` call syncs.
|
|
1162
|
+
* @returns `true` only when `state.isLastPageKnown` and `state.page === state.lastPage`
|
|
1163
|
+
*/
|
|
1164
|
+
isLastPage() {
|
|
1165
|
+
const state = this._nestService.nest();
|
|
1166
|
+
return state.isLastPageKnown && state.page === state.lastPage;
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Navigate to the last page known from the most recent paginated response
|
|
1170
|
+
*
|
|
1171
|
+
* @remarks Requires at least one `PaginationService.paginate()` call to have synced `state.lastPage`. Before that, the bound is unknown and this method throws.
|
|
1172
|
+
* @returns {this}
|
|
1173
|
+
* @throws {PaginationNotSyncedError} If `state.isLastPageKnown` is false (no paginated response has been synced yet)
|
|
1174
|
+
*/
|
|
1175
|
+
lastPage() {
|
|
1176
|
+
const state = this._nestService.nest();
|
|
1177
|
+
if (!state.isLastPageKnown) {
|
|
1178
|
+
throw new PaginationNotSyncedError('navigate to last page');
|
|
1179
|
+
}
|
|
1180
|
+
this._nestService.page = state.lastPage;
|
|
1181
|
+
return this;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Navigate to the next page
|
|
1185
|
+
*
|
|
1186
|
+
* @remarks Never throws. Idempotent at the known last page (no-op). Pair with `hasNextPage()` for a disable-state binding.
|
|
1187
|
+
* @returns {this}
|
|
1188
|
+
*/
|
|
1189
|
+
nextPage() {
|
|
1190
|
+
const state = this._nestService.nest();
|
|
1191
|
+
if (state.isLastPageKnown && state.page >= state.lastPage) {
|
|
1192
|
+
return this;
|
|
1193
|
+
}
|
|
1194
|
+
this._nestService.page = state.page + 1;
|
|
1195
|
+
return this;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Navigate to the previous page
|
|
1199
|
+
*
|
|
1200
|
+
* @remarks Never throws. Idempotent at page 1 (floored). Pair with `hasPreviousPage()` for a disable-state binding.
|
|
1201
|
+
* @returns {this}
|
|
1202
|
+
*/
|
|
1203
|
+
previousPage() {
|
|
1204
|
+
const state = this._nestService.nest();
|
|
1205
|
+
if (state.page <= 1) {
|
|
1206
|
+
return this;
|
|
1207
|
+
}
|
|
1208
|
+
this._nestService.page = state.page - 1;
|
|
1209
|
+
return this;
|
|
1210
|
+
}
|
|
995
1211
|
/**
|
|
996
1212
|
* Clear the current state and reset the Query Builder to a fresh, clean condition
|
|
997
1213
|
*
|
|
@@ -1012,106 +1228,482 @@ class NgQubeeService {
|
|
|
1012
1228
|
return this;
|
|
1013
1229
|
}
|
|
1014
1230
|
/**
|
|
1015
|
-
* Set the items per page number
|
|
1231
|
+
* Set the items per page number
|
|
1232
|
+
*
|
|
1233
|
+
* Validation is delegated to the active request strategy because the
|
|
1234
|
+
* accepted range is driver-specific: nestjs-paginate additionally accepts
|
|
1235
|
+
* `-1` as a "fetch all" sentinel, while Laravel, Spatie, and JSON:API
|
|
1236
|
+
* require a positive integer.
|
|
1237
|
+
*
|
|
1238
|
+
* @param limit - Number of items per page (or `-1` to fetch all, NestJS only)
|
|
1239
|
+
* @returns {this}
|
|
1240
|
+
* @throws {import('../errors/invalid-limit.error').InvalidLimitError} If the value is not accepted by the active driver
|
|
1241
|
+
*/
|
|
1242
|
+
setLimit(limit) {
|
|
1243
|
+
this._requestStrategy.validateLimit(limit);
|
|
1244
|
+
this._nestService.limit = limit;
|
|
1245
|
+
this._nestService.page = 1;
|
|
1246
|
+
return this;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Set the page that the backend will use to paginate the result set
|
|
1250
|
+
*
|
|
1251
|
+
* @param page - Page number
|
|
1252
|
+
* @returns {this}
|
|
1253
|
+
*/
|
|
1254
|
+
setPage(page) {
|
|
1255
|
+
this._nestService.page = page;
|
|
1256
|
+
return this;
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Set the API resource to run the query against
|
|
1260
|
+
*
|
|
1261
|
+
* @param {string} resource - Resource name (e.g. 'users' produces /users)
|
|
1262
|
+
* @returns {this}
|
|
1263
|
+
*/
|
|
1264
|
+
setResource(resource) {
|
|
1265
|
+
this._nestService.resource = resource;
|
|
1266
|
+
this._nestService.page = 1;
|
|
1267
|
+
return this;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Set the search term for full-text search (NestJS only)
|
|
1271
|
+
*
|
|
1272
|
+
* Produces: `search=term`
|
|
1273
|
+
*
|
|
1274
|
+
* @param {string} search - The search term
|
|
1275
|
+
* @returns {this}
|
|
1276
|
+
* @throws {UnsupportedSearchError} If the active driver does not support search
|
|
1277
|
+
*/
|
|
1278
|
+
setSearch(search) {
|
|
1279
|
+
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSearchError());
|
|
1280
|
+
this._nestService.setSearch(search);
|
|
1281
|
+
this._nestService.page = 1;
|
|
1282
|
+
return this;
|
|
1283
|
+
}
|
|
1284
|
+
/**
|
|
1285
|
+
* Get the total number of pages reported by the most recent paginated response
|
|
1286
|
+
*
|
|
1287
|
+
* @remarks Throws when called before any `paginate()` has synced a value. For a non-throwing read in a template, read `nest().isLastPageKnown` first as a guard.
|
|
1288
|
+
* @returns The last page number
|
|
1289
|
+
* @throws {PaginationNotSyncedError} If `state.isLastPageKnown` is false (no paginated response has been synced yet)
|
|
1290
|
+
*/
|
|
1291
|
+
totalPages() {
|
|
1292
|
+
const state = this._nestService.nest();
|
|
1293
|
+
if (!state.isLastPageKnown) {
|
|
1294
|
+
throw new PaginationNotSyncedError('read totalPages');
|
|
1295
|
+
}
|
|
1296
|
+
return state.lastPage;
|
|
1297
|
+
}
|
|
1298
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeService, deps: [{ token: NestService }, { token: NG_QUBEE_REQUEST_STRATEGY }, { token: NG_QUBEE_DRIVER }, { token: NG_QUBEE_REQUEST_OPTIONS }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1299
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeService });
|
|
1300
|
+
}
|
|
1301
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeService, decorators: [{
|
|
1302
|
+
type: Injectable
|
|
1303
|
+
}], ctorParameters: () => [{ type: NestService }, { type: undefined, decorators: [{
|
|
1304
|
+
type: Inject,
|
|
1305
|
+
args: [NG_QUBEE_REQUEST_STRATEGY]
|
|
1306
|
+
}] }, { type: DriverEnum, decorators: [{
|
|
1307
|
+
type: Inject,
|
|
1308
|
+
args: [NG_QUBEE_DRIVER]
|
|
1309
|
+
}] }, { type: QueryBuilderOptions, decorators: [{
|
|
1310
|
+
type: Inject,
|
|
1311
|
+
args: [NG_QUBEE_REQUEST_OPTIONS]
|
|
1312
|
+
}] }] });
|
|
1313
|
+
|
|
1314
|
+
class PaginationService {
|
|
1315
|
+
/**
|
|
1316
|
+
* The NestService instance that owns the query-builder state for this
|
|
1317
|
+
* PaginationService's scope (environment-level by default, or
|
|
1318
|
+
* component-level when used via `provideNgQubeeInstance()`)
|
|
1319
|
+
*/
|
|
1320
|
+
_nestService;
|
|
1321
|
+
/**
|
|
1322
|
+
* Resolved response key name options
|
|
1323
|
+
*/
|
|
1324
|
+
_options;
|
|
1325
|
+
/**
|
|
1326
|
+
* The response strategy that parses responses for the active driver
|
|
1327
|
+
*/
|
|
1328
|
+
_responseStrategy;
|
|
1329
|
+
constructor(nestService, responseStrategy, options = new ResponseOptions({})) {
|
|
1330
|
+
this._nestService = nestService;
|
|
1331
|
+
this._options = options;
|
|
1332
|
+
this._responseStrategy = responseStrategy;
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Transform a raw API response into a typed PaginatedCollection
|
|
1336
|
+
*
|
|
1337
|
+
* Delegates to the active driver's response strategy for parsing, then
|
|
1338
|
+
* auto-syncs the parsed `page` and `lastPage` back into `NestService`
|
|
1339
|
+
* so pagination navigation helpers on `NgQubeeService` can operate
|
|
1340
|
+
* against the live server-reported bounds without consumer bookkeeping.
|
|
1341
|
+
*
|
|
1342
|
+
* @remarks
|
|
1343
|
+
* `lastPage` is only synced when the response yields a positive integer.
|
|
1344
|
+
* Server-emitted `0` (empty collection edge case) and absent fields are
|
|
1345
|
+
* treated as "no useful info" and leave `isLastPageKnown: false`.
|
|
1346
|
+
*
|
|
1347
|
+
* @param response - The raw API response object
|
|
1348
|
+
* @returns A typed PaginatedCollection instance
|
|
1349
|
+
*/
|
|
1350
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1351
|
+
paginate(response) {
|
|
1352
|
+
const collection = this._responseStrategy.paginate(response, this._options);
|
|
1353
|
+
this._nestService.page = collection.page;
|
|
1354
|
+
if (typeof collection.lastPage === 'number' && Number.isInteger(collection.lastPage) && collection.lastPage > 0) {
|
|
1355
|
+
this._nestService.syncLastPage(collection.lastPage);
|
|
1356
|
+
}
|
|
1357
|
+
return collection;
|
|
1358
|
+
}
|
|
1359
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PaginationService, deps: [{ token: NestService }, { token: NG_QUBEE_RESPONSE_STRATEGY }, { token: NG_QUBEE_RESPONSE_OPTIONS }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1360
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PaginationService });
|
|
1361
|
+
}
|
|
1362
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PaginationService, decorators: [{
|
|
1363
|
+
type: Injectable
|
|
1364
|
+
}], ctorParameters: () => [{ type: NestService }, { type: undefined, decorators: [{
|
|
1365
|
+
type: Inject,
|
|
1366
|
+
args: [NG_QUBEE_RESPONSE_STRATEGY]
|
|
1367
|
+
}] }, { type: ResponseOptions, decorators: [{
|
|
1368
|
+
type: Inject,
|
|
1369
|
+
args: [NG_QUBEE_RESPONSE_OPTIONS]
|
|
1370
|
+
}] }] });
|
|
1371
|
+
|
|
1372
|
+
var SortEnum;
|
|
1373
|
+
(function (SortEnum) {
|
|
1374
|
+
SortEnum["ASC"] = "asc";
|
|
1375
|
+
SortEnum["DESC"] = "desc";
|
|
1376
|
+
})(SortEnum || (SortEnum = {}));
|
|
1377
|
+
|
|
1378
|
+
/**
|
|
1379
|
+
* Thrown when a limit value does not satisfy the active driver's constraints
|
|
1380
|
+
*
|
|
1381
|
+
* Validation is driver-scoped: most drivers require an integer `>= 1`, while
|
|
1382
|
+
* the NestJS driver additionally accepts `-1` as a "fetch all items" sentinel
|
|
1383
|
+
* (as documented by nestjs-paginate). The message is tailored accordingly so
|
|
1384
|
+
* the caller understands which values are permitted.
|
|
1385
|
+
*/
|
|
1386
|
+
class InvalidLimitError extends Error {
|
|
1387
|
+
/**
|
|
1388
|
+
* @param limit - The rejected limit value
|
|
1389
|
+
* @param allowFetchAll - Whether the active driver accepts `-1` (fetch all)
|
|
1390
|
+
*/
|
|
1391
|
+
constructor(limit, allowFetchAll = false) {
|
|
1392
|
+
const allowed = allowFetchAll
|
|
1393
|
+
? 'a positive integer greater than 0, or -1 to fetch all items'
|
|
1394
|
+
: 'a positive integer greater than 0';
|
|
1395
|
+
super(`Invalid limit value: Limit must be ${allowed}. Received: ${limit}`);
|
|
1396
|
+
this.name = 'InvalidLimitError';
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
class UnselectableModelError extends Error {
|
|
1401
|
+
constructor(model) {
|
|
1402
|
+
super(`Unselectable Model: the selected model (${model}) is not present neither in the "model" property, nor in the includes object.`);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* Request strategy for the JSON:API driver
|
|
1408
|
+
*
|
|
1409
|
+
* Generates URIs in the JSON:API format:
|
|
1410
|
+
* - Fields: `fields[articles]=title,body&fields[people]=name`
|
|
1411
|
+
* - Filters: `filter[status]=active`
|
|
1412
|
+
* - Includes: `include=author,comments.author`
|
|
1413
|
+
* - Pagination: `page[number]=1&page[size]=15`
|
|
1414
|
+
* - Sort: `sort=-created_at,name` (- prefix = DESC)
|
|
1415
|
+
*
|
|
1416
|
+
* @see https://jsonapi.org/format/
|
|
1417
|
+
*/
|
|
1418
|
+
class JsonApiRequestStrategy {
|
|
1419
|
+
/**
|
|
1420
|
+
* Accumulator for composing the URI string
|
|
1421
|
+
*/
|
|
1422
|
+
_uri = '';
|
|
1423
|
+
/**
|
|
1424
|
+
* Build a URI string from the given state using the JSON:API format
|
|
1425
|
+
*
|
|
1426
|
+
* @param state - The current query builder state
|
|
1427
|
+
* @param options - The query parameter key name configuration
|
|
1428
|
+
* @returns The composed URI string
|
|
1429
|
+
* @throws Error if resource is not set
|
|
1430
|
+
*/
|
|
1431
|
+
buildUri(state, options) {
|
|
1432
|
+
if (!state.resource) {
|
|
1433
|
+
throw new Error('Set the resource property BEFORE adding filters or calling the url() / get() methods');
|
|
1434
|
+
}
|
|
1435
|
+
this._uri = '';
|
|
1436
|
+
this._parseIncludes(state, options);
|
|
1437
|
+
this._parseFields(state, options);
|
|
1438
|
+
this._parseFilters(state, options);
|
|
1439
|
+
this._parsePagination(state, options);
|
|
1440
|
+
this._parseSort(state, options);
|
|
1441
|
+
return this._uri;
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Validate that the given limit is accepted by the JSON:API driver
|
|
1445
|
+
*
|
|
1446
|
+
* The JSON:API specification leaves pagination semantics to the server and
|
|
1447
|
+
* does not define a "fetch all" sentinel, so only positive integers are
|
|
1448
|
+
* accepted.
|
|
1449
|
+
*
|
|
1450
|
+
* @param limit - The limit value to validate
|
|
1451
|
+
* @throws {InvalidLimitError} If the value is not a positive integer
|
|
1452
|
+
*/
|
|
1453
|
+
validateLimit(limit) {
|
|
1454
|
+
if (Number.isInteger(limit) && limit >= 1) {
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
throw new InvalidLimitError(limit);
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Parse and append field selection parameters
|
|
1461
|
+
*
|
|
1462
|
+
* Validates that each field model exists either as the main resource
|
|
1463
|
+
* or in the includes list. Fields are grouped by type in bracket notation.
|
|
1464
|
+
*
|
|
1465
|
+
* @param state - The current query builder state
|
|
1466
|
+
* @param options - The query parameter key name configuration
|
|
1467
|
+
* @returns The generated field selection parameter string
|
|
1468
|
+
* @throws Error if resource is required but not set
|
|
1469
|
+
* @throws UnselectableModelError if a field model is not in resource or includes
|
|
1470
|
+
*/
|
|
1471
|
+
_parseFields(state, options) {
|
|
1472
|
+
if (!Object.keys(state.fields).length) {
|
|
1473
|
+
return this._uri;
|
|
1474
|
+
}
|
|
1475
|
+
if (!state.resource) {
|
|
1476
|
+
throw new Error('While selecting fields, the -> resource <- is required');
|
|
1477
|
+
}
|
|
1478
|
+
if (!(state.resource in state.fields)) {
|
|
1479
|
+
throw new Error(`Key ${state.resource} is missing in the fields object`);
|
|
1480
|
+
}
|
|
1481
|
+
const f = {};
|
|
1482
|
+
for (const k in state.fields) {
|
|
1483
|
+
if (state.fields.hasOwnProperty(k)) {
|
|
1484
|
+
if (k !== state.resource && !state.includes.includes(k)) {
|
|
1485
|
+
throw new UnselectableModelError(k);
|
|
1486
|
+
}
|
|
1487
|
+
Object.assign(f, { [`${options.fields}[${k}]`]: state.fields[k].join(',') });
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
const param = `${this._prepend(state)}${qs.stringify(f, { encode: false })}`;
|
|
1491
|
+
this._uri += param;
|
|
1492
|
+
return param;
|
|
1493
|
+
}
|
|
1494
|
+
/**
|
|
1495
|
+
* Parse and append filter parameters
|
|
1496
|
+
*
|
|
1497
|
+
* Generates filter parameters in bracket notation: `filter[key]=value1,value2`
|
|
1498
|
+
*
|
|
1499
|
+
* @param state - The current query builder state
|
|
1500
|
+
* @param options - The query parameter key name configuration
|
|
1501
|
+
* @returns The generated filter parameter string
|
|
1502
|
+
*/
|
|
1503
|
+
_parseFilters(state, options) {
|
|
1504
|
+
const keys = Object.keys(state.filters);
|
|
1505
|
+
if (!keys.length) {
|
|
1506
|
+
return this._uri;
|
|
1507
|
+
}
|
|
1508
|
+
const f = {
|
|
1509
|
+
[`${options.filters}`]: keys.reduce((acc, key) => {
|
|
1510
|
+
return Object.assign(acc, { [key]: state.filters[key].join(',') });
|
|
1511
|
+
}, {})
|
|
1512
|
+
};
|
|
1513
|
+
const param = `${this._prepend(state)}${qs.stringify(f, { encode: false })}`;
|
|
1514
|
+
this._uri += param;
|
|
1515
|
+
return param;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Parse and append include parameters
|
|
1519
|
+
*
|
|
1520
|
+
* Generates: `include=author,comments.author`
|
|
1521
|
+
*
|
|
1522
|
+
* @param state - The current query builder state
|
|
1523
|
+
* @param options - The query parameter key name configuration
|
|
1524
|
+
* @returns The generated include parameter string
|
|
1525
|
+
*/
|
|
1526
|
+
_parseIncludes(state, options) {
|
|
1527
|
+
if (!state.includes.length) {
|
|
1528
|
+
return this._uri;
|
|
1529
|
+
}
|
|
1530
|
+
const param = `${this._prepend(state)}${options.includes}=${state.includes}`;
|
|
1531
|
+
this._uri += param;
|
|
1532
|
+
return param;
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Parse and append pagination parameters in JSON:API bracket notation
|
|
1536
|
+
*
|
|
1537
|
+
* Generates: `page[number]=1&page[size]=15`
|
|
1538
|
+
*
|
|
1539
|
+
* @param state - The current query builder state
|
|
1540
|
+
* @param options - The query parameter key name configuration
|
|
1541
|
+
* @returns The generated pagination parameter string
|
|
1542
|
+
*/
|
|
1543
|
+
_parsePagination(state, options) {
|
|
1544
|
+
const pagination = qs.stringify({ [options.page]: { number: state.page, size: state.limit } }, { encode: false });
|
|
1545
|
+
const param = `${this._prepend(state)}${pagination}`;
|
|
1546
|
+
this._uri += param;
|
|
1547
|
+
return param;
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Parse and append sort parameters
|
|
1551
|
+
*
|
|
1552
|
+
* Generates: `sort=-field1,field2` where `-` prefix indicates DESC order
|
|
1553
|
+
*
|
|
1554
|
+
* @param state - The current query builder state
|
|
1555
|
+
* @param options - The query parameter key name configuration
|
|
1556
|
+
* @returns The generated sort parameter string
|
|
1557
|
+
*/
|
|
1558
|
+
_parseSort(state, options) {
|
|
1559
|
+
let param = '';
|
|
1560
|
+
if (!state.sorts.length) {
|
|
1561
|
+
return param;
|
|
1562
|
+
}
|
|
1563
|
+
param = `${this._prepend(state)}${options.sort}=`;
|
|
1564
|
+
state.sorts.forEach((sort, idx) => {
|
|
1565
|
+
param += `${sort.order === SortEnum.DESC ? '-' : ''}${sort.field}`;
|
|
1566
|
+
if (idx < state.sorts.length - 1) {
|
|
1567
|
+
param += ',';
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
this._uri += param;
|
|
1571
|
+
return param;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Determine the appropriate URI prefix based on the current accumulator state
|
|
1575
|
+
*
|
|
1576
|
+
* Returns the full base path with `?` for the first parameter,
|
|
1577
|
+
* or `&` for subsequent parameters.
|
|
1578
|
+
*
|
|
1579
|
+
* @param state - The current query builder state
|
|
1580
|
+
* @returns The prefix string to prepend to the next parameter
|
|
1581
|
+
*/
|
|
1582
|
+
_prepend(state) {
|
|
1583
|
+
if (this._uri) {
|
|
1584
|
+
return '&';
|
|
1585
|
+
}
|
|
1586
|
+
return state.baseUrl ? `${state.baseUrl}/${state.resource}?` : `/${state.resource}?`;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* Response strategy for the JSON:API driver
|
|
1592
|
+
*
|
|
1593
|
+
* Parses JSON:API pagination responses:
|
|
1594
|
+
* ```json
|
|
1595
|
+
* {
|
|
1596
|
+
* "data": [...],
|
|
1597
|
+
* "meta": {
|
|
1598
|
+
* "current-page": 1,
|
|
1599
|
+
* "per-page": 10,
|
|
1600
|
+
* "total": 100,
|
|
1601
|
+
* "page-count": 10,
|
|
1602
|
+
* "from": 1,
|
|
1603
|
+
* "to": 10
|
|
1604
|
+
* },
|
|
1605
|
+
* "links": {
|
|
1606
|
+
* "first": "url",
|
|
1607
|
+
* "prev": "url",
|
|
1608
|
+
* "next": "url",
|
|
1609
|
+
* "last": "url"
|
|
1610
|
+
* }
|
|
1611
|
+
* }
|
|
1612
|
+
* ```
|
|
1613
|
+
*
|
|
1614
|
+
* @see https://jsonapi.org/format/
|
|
1615
|
+
*/
|
|
1616
|
+
class JsonApiResponseStrategy {
|
|
1617
|
+
/**
|
|
1618
|
+
* Parse a JSON:API pagination response into a PaginatedCollection
|
|
1016
1619
|
*
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1019
|
-
|
|
1020
|
-
setLimit(limit) {
|
|
1021
|
-
this._nestService.limit = limit;
|
|
1022
|
-
return this;
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Set the page that the backend will use to paginate the result set
|
|
1620
|
+
* Supports dot-notation key paths for accessing nested values.
|
|
1621
|
+
* Computes `from` and `to` from `currentPage` and `perPage` when
|
|
1622
|
+
* they are not directly available in the response.
|
|
1026
1623
|
*
|
|
1027
|
-
* @param
|
|
1028
|
-
* @
|
|
1624
|
+
* @param response - The raw API response object
|
|
1625
|
+
* @param options - The response key name configuration
|
|
1626
|
+
* @returns A typed PaginatedCollection instance
|
|
1029
1627
|
*/
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1628
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1629
|
+
paginate(response, options) {
|
|
1630
|
+
const data = this._resolve(response, options.data);
|
|
1631
|
+
const currentPage = this._resolve(response, options.currentPage);
|
|
1632
|
+
const total = this._resolve(response, options.total);
|
|
1633
|
+
const perPage = this._resolve(response, options.perPage);
|
|
1634
|
+
const lastPage = this._resolve(response, options.lastPage);
|
|
1635
|
+
// Compute from/to if not directly available
|
|
1636
|
+
const from = this._resolveFrom(response, options, currentPage, perPage);
|
|
1637
|
+
const to = this._resolveTo(response, options, currentPage, perPage, total);
|
|
1638
|
+
const prevPageUrl = this._resolve(response, options.prevPageUrl);
|
|
1639
|
+
const nextPageUrl = this._resolve(response, options.nextPageUrl);
|
|
1640
|
+
const firstPageUrl = this._resolve(response, options.firstPageUrl);
|
|
1641
|
+
const lastPageUrl = this._resolve(response, options.lastPageUrl);
|
|
1642
|
+
return new PaginatedCollection(data, currentPage, from, to, total, perPage, prevPageUrl, nextPageUrl, lastPage, firstPageUrl, lastPageUrl);
|
|
1033
1643
|
}
|
|
1034
1644
|
/**
|
|
1035
|
-
*
|
|
1645
|
+
* Resolve a value from a response object using a dot-notation path
|
|
1036
1646
|
*
|
|
1037
|
-
*
|
|
1038
|
-
*
|
|
1647
|
+
* Supports both flat keys ('data') and nested paths ('meta.current-page').
|
|
1648
|
+
*
|
|
1649
|
+
* @param response - The raw response object
|
|
1650
|
+
* @param path - The dot-notation path to resolve
|
|
1651
|
+
* @returns The resolved value, or undefined if not found
|
|
1039
1652
|
*/
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
return
|
|
1653
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1654
|
+
_resolve(response, path) {
|
|
1655
|
+
return path.split('.').reduce((obj, key) => obj?.[key], response);
|
|
1043
1656
|
}
|
|
1044
1657
|
/**
|
|
1045
|
-
*
|
|
1658
|
+
* Resolve the "from" index value
|
|
1046
1659
|
*
|
|
1047
|
-
*
|
|
1660
|
+
* If the path resolves to a value in the response, use it.
|
|
1661
|
+
* Otherwise, compute it from currentPage and perPage:
|
|
1662
|
+
* `(currentPage - 1) * perPage + 1`
|
|
1048
1663
|
*
|
|
1049
|
-
* @param
|
|
1050
|
-
* @
|
|
1051
|
-
* @
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSearchError());
|
|
1055
|
-
this._nestService.setSearch(search);
|
|
1056
|
-
return this;
|
|
1057
|
-
}
|
|
1058
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeService, deps: [{ token: NestService }, { token: 'REQUEST_STRATEGY' }, { token: 'DRIVER' }, { token: 'QUERY_PARAMS_CONFIG', optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1059
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeService });
|
|
1060
|
-
}
|
|
1061
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeService, decorators: [{
|
|
1062
|
-
type: Injectable
|
|
1063
|
-
}], ctorParameters: () => [{ type: NestService }, { type: undefined, decorators: [{
|
|
1064
|
-
type: Inject,
|
|
1065
|
-
args: ['REQUEST_STRATEGY']
|
|
1066
|
-
}] }, { type: DriverEnum, decorators: [{
|
|
1067
|
-
type: Inject,
|
|
1068
|
-
args: ['DRIVER']
|
|
1069
|
-
}] }, { type: undefined, decorators: [{
|
|
1070
|
-
type: Inject,
|
|
1071
|
-
args: ['QUERY_PARAMS_CONFIG']
|
|
1072
|
-
}, {
|
|
1073
|
-
type: Optional
|
|
1074
|
-
}] }] });
|
|
1075
|
-
|
|
1076
|
-
class PaginationService {
|
|
1077
|
-
/**
|
|
1078
|
-
* Resolved response key name options
|
|
1079
|
-
*/
|
|
1080
|
-
_options;
|
|
1081
|
-
/**
|
|
1082
|
-
* The response strategy that parses responses for the active driver
|
|
1664
|
+
* @param response - The raw response object
|
|
1665
|
+
* @param options - The response key name configuration
|
|
1666
|
+
* @param currentPage - The current page number
|
|
1667
|
+
* @param perPage - The number of items per page
|
|
1668
|
+
* @returns The computed "from" index
|
|
1083
1669
|
*/
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1670
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1671
|
+
_resolveFrom(response, options, currentPage, perPage) {
|
|
1672
|
+
const direct = this._resolve(response, options.from);
|
|
1673
|
+
if (direct !== undefined) {
|
|
1674
|
+
return direct;
|
|
1675
|
+
}
|
|
1676
|
+
if (currentPage && perPage) {
|
|
1677
|
+
return (currentPage - 1) * perPage + 1;
|
|
1678
|
+
}
|
|
1679
|
+
return undefined;
|
|
1088
1680
|
}
|
|
1089
1681
|
/**
|
|
1090
|
-
*
|
|
1682
|
+
* Resolve the "to" index value
|
|
1091
1683
|
*
|
|
1092
|
-
*
|
|
1684
|
+
* If the path resolves to a value in the response, use it.
|
|
1685
|
+
* Otherwise, compute it from currentPage, perPage, and total:
|
|
1686
|
+
* `Math.min(currentPage * perPage, total)`
|
|
1093
1687
|
*
|
|
1094
|
-
* @param response - The raw
|
|
1095
|
-
* @
|
|
1688
|
+
* @param response - The raw response object
|
|
1689
|
+
* @param options - The response key name configuration
|
|
1690
|
+
* @param currentPage - The current page number
|
|
1691
|
+
* @param perPage - The number of items per page
|
|
1692
|
+
* @param total - The total number of items
|
|
1693
|
+
* @returns The computed "to" index
|
|
1096
1694
|
*/
|
|
1097
1695
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1098
|
-
|
|
1099
|
-
|
|
1696
|
+
_resolveTo(response, options, currentPage, perPage, total) {
|
|
1697
|
+
const direct = this._resolve(response, options.to);
|
|
1698
|
+
if (direct !== undefined) {
|
|
1699
|
+
return direct;
|
|
1700
|
+
}
|
|
1701
|
+
if (currentPage && perPage && total) {
|
|
1702
|
+
return Math.min(currentPage * perPage, total);
|
|
1703
|
+
}
|
|
1704
|
+
return undefined;
|
|
1100
1705
|
}
|
|
1101
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PaginationService, deps: [{ token: 'RESPONSE_STRATEGY' }, { token: 'RESPONSE_OPTIONS', optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1102
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PaginationService });
|
|
1103
1706
|
}
|
|
1104
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PaginationService, decorators: [{
|
|
1105
|
-
type: Injectable
|
|
1106
|
-
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1107
|
-
type: Inject,
|
|
1108
|
-
args: ['RESPONSE_STRATEGY']
|
|
1109
|
-
}] }, { type: undefined, decorators: [{
|
|
1110
|
-
type: Inject,
|
|
1111
|
-
args: ['RESPONSE_OPTIONS']
|
|
1112
|
-
}, {
|
|
1113
|
-
type: Optional
|
|
1114
|
-
}] }] });
|
|
1115
1707
|
|
|
1116
1708
|
/**
|
|
1117
1709
|
* Request strategy for the Laravel (pagination-only) driver
|
|
@@ -1137,6 +1729,21 @@ class LaravelRequestStrategy {
|
|
|
1137
1729
|
const base = state.baseUrl ? `${state.baseUrl}/${state.resource}` : `/${state.resource}`;
|
|
1138
1730
|
return `${base}?${options.limit}=${state.limit}&${options.page}=${state.page}`;
|
|
1139
1731
|
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Validate that the given limit is accepted by the Laravel driver
|
|
1734
|
+
*
|
|
1735
|
+
* Laravel pagination does not recognize `-1` as a "fetch all" sentinel,
|
|
1736
|
+
* so only positive integers are accepted.
|
|
1737
|
+
*
|
|
1738
|
+
* @param limit - The limit value to validate
|
|
1739
|
+
* @throws {InvalidLimitError} If the value is not a positive integer
|
|
1740
|
+
*/
|
|
1741
|
+
validateLimit(limit) {
|
|
1742
|
+
if (Number.isInteger(limit) && limit >= 1) {
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
throw new InvalidLimitError(limit);
|
|
1746
|
+
}
|
|
1140
1747
|
}
|
|
1141
1748
|
|
|
1142
1749
|
/**
|
|
@@ -1169,12 +1776,6 @@ class LaravelResponseStrategy {
|
|
|
1169
1776
|
}
|
|
1170
1777
|
}
|
|
1171
1778
|
|
|
1172
|
-
var SortEnum;
|
|
1173
|
-
(function (SortEnum) {
|
|
1174
|
-
SortEnum["ASC"] = "asc";
|
|
1175
|
-
SortEnum["DESC"] = "desc";
|
|
1176
|
-
})(SortEnum || (SortEnum = {}));
|
|
1177
|
-
|
|
1178
1779
|
/**
|
|
1179
1780
|
* Request strategy for the NestJS (nestjs-paginate) driver
|
|
1180
1781
|
*
|
|
@@ -1215,6 +1816,21 @@ class NestjsRequestStrategy {
|
|
|
1215
1816
|
this._parsePage(state, options);
|
|
1216
1817
|
return this._uri;
|
|
1217
1818
|
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Validate that the given limit is accepted by nestjs-paginate
|
|
1821
|
+
*
|
|
1822
|
+
* Accepts any integer `>= 1` as a page size, plus `-1` which nestjs-paginate
|
|
1823
|
+
* interprets as "fetch all items" (server must opt-in via `maxLimit: -1`).
|
|
1824
|
+
*
|
|
1825
|
+
* @param limit - The limit value to validate
|
|
1826
|
+
* @throws {InvalidLimitError} If the value is not an integer, or is 0, or is a negative number other than -1
|
|
1827
|
+
*/
|
|
1828
|
+
validateLimit(limit) {
|
|
1829
|
+
if (Number.isInteger(limit) && (limit === -1 || limit >= 1)) {
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
throw new InvalidLimitError(limit, true);
|
|
1833
|
+
}
|
|
1218
1834
|
/**
|
|
1219
1835
|
* Parse and append simple filter parameters
|
|
1220
1836
|
*
|
|
@@ -1454,12 +2070,6 @@ class NestjsResponseStrategy {
|
|
|
1454
2070
|
}
|
|
1455
2071
|
}
|
|
1456
2072
|
|
|
1457
|
-
class UnselectableModelError extends Error {
|
|
1458
|
-
constructor(model) {
|
|
1459
|
-
super(`Unselectable Model: the selected model (${model}) is not present neither in the "model" property, nor in the includes object.`);
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
2073
|
/**
|
|
1464
2074
|
* Request strategy for the Spatie Query Builder driver
|
|
1465
2075
|
*
|
|
@@ -1498,6 +2108,21 @@ class SpatieRequestStrategy {
|
|
|
1498
2108
|
this._parseSort(state, options);
|
|
1499
2109
|
return this._uri;
|
|
1500
2110
|
}
|
|
2111
|
+
/**
|
|
2112
|
+
* Validate that the given limit is accepted by the Spatie driver
|
|
2113
|
+
*
|
|
2114
|
+
* Spatie query-builder does not recognize `-1` as a "fetch all" sentinel,
|
|
2115
|
+
* so only positive integers are accepted.
|
|
2116
|
+
*
|
|
2117
|
+
* @param limit - The limit value to validate
|
|
2118
|
+
* @throws {InvalidLimitError} If the value is not a positive integer
|
|
2119
|
+
*/
|
|
2120
|
+
validateLimit(limit) {
|
|
2121
|
+
if (Number.isInteger(limit) && limit >= 1) {
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
throw new InvalidLimitError(limit);
|
|
2125
|
+
}
|
|
1501
2126
|
/**
|
|
1502
2127
|
* Parse and append field selection parameters
|
|
1503
2128
|
*
|
|
@@ -1676,8 +2301,10 @@ class SpatieResponseStrategy {
|
|
|
1676
2301
|
* @param driver - The pagination driver
|
|
1677
2302
|
* @returns The corresponding request strategy
|
|
1678
2303
|
*/
|
|
1679
|
-
function resolveRequestStrategy
|
|
2304
|
+
function resolveRequestStrategy(driver) {
|
|
1680
2305
|
switch (driver) {
|
|
2306
|
+
case DriverEnum.JSON_API:
|
|
2307
|
+
return new JsonApiRequestStrategy();
|
|
1681
2308
|
case DriverEnum.NESTJS:
|
|
1682
2309
|
return new NestjsRequestStrategy();
|
|
1683
2310
|
case DriverEnum.SPATIE:
|
|
@@ -1692,8 +2319,10 @@ function resolveRequestStrategy$1(driver) {
|
|
|
1692
2319
|
* @param driver - The pagination driver
|
|
1693
2320
|
* @returns The corresponding response strategy
|
|
1694
2321
|
*/
|
|
1695
|
-
function resolveResponseStrategy
|
|
2322
|
+
function resolveResponseStrategy(driver) {
|
|
1696
2323
|
switch (driver) {
|
|
2324
|
+
case DriverEnum.JSON_API:
|
|
2325
|
+
return new JsonApiResponseStrategy();
|
|
1697
2326
|
case DriverEnum.NESTJS:
|
|
1698
2327
|
return new NestjsResponseStrategy();
|
|
1699
2328
|
case DriverEnum.SPATIE:
|
|
@@ -1702,79 +2331,47 @@ function resolveResponseStrategy$1(driver) {
|
|
|
1702
2331
|
return new LaravelResponseStrategy();
|
|
1703
2332
|
}
|
|
1704
2333
|
}
|
|
1705
|
-
// @dynamic
|
|
1706
|
-
class NgQubeeModule {
|
|
1707
|
-
/**
|
|
1708
|
-
* Configure NgQubee for the root module
|
|
1709
|
-
*
|
|
1710
|
-
* @param config - Configuration object with driver, and optional request and response settings
|
|
1711
|
-
* @returns Module with providers configured for the specified driver
|
|
1712
|
-
*/
|
|
1713
|
-
static forRoot(config) {
|
|
1714
|
-
const driver = config.driver;
|
|
1715
|
-
const requestStrategy = resolveRequestStrategy$1(driver);
|
|
1716
|
-
const responseStrategy = resolveResponseStrategy$1(driver);
|
|
1717
|
-
return {
|
|
1718
|
-
ngModule: NgQubeeModule,
|
|
1719
|
-
providers: [
|
|
1720
|
-
NestService,
|
|
1721
|
-
{
|
|
1722
|
-
deps: [NestService],
|
|
1723
|
-
provide: NgQubeeService,
|
|
1724
|
-
useFactory: (nestService) => new NgQubeeService(nestService, requestStrategy, driver, Object.assign({}, config.request))
|
|
1725
|
-
},
|
|
1726
|
-
{
|
|
1727
|
-
provide: PaginationService,
|
|
1728
|
-
useFactory: () => {
|
|
1729
|
-
const responseConfig = Object.assign({}, config.response);
|
|
1730
|
-
return driver === DriverEnum.NESTJS
|
|
1731
|
-
? new PaginationService(responseStrategy, new NestjsResponseOptions(responseConfig))
|
|
1732
|
-
: new PaginationService(responseStrategy, responseConfig);
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
]
|
|
1736
|
-
};
|
|
1737
|
-
}
|
|
1738
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1739
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule });
|
|
1740
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule });
|
|
1741
|
-
}
|
|
1742
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule, decorators: [{
|
|
1743
|
-
type: NgModule,
|
|
1744
|
-
args: [{}]
|
|
1745
|
-
}] });
|
|
1746
|
-
|
|
1747
2334
|
/**
|
|
1748
|
-
* Resolve the
|
|
2335
|
+
* Resolve the driver-specific `ResponseOptions` instance
|
|
1749
2336
|
*
|
|
1750
2337
|
* @param driver - The pagination driver
|
|
1751
|
-
* @
|
|
2338
|
+
* @param responseConfig - User-supplied response key overrides
|
|
2339
|
+
* @returns A pre-built ResponseOptions (or driver-specific subclass)
|
|
1752
2340
|
*/
|
|
1753
|
-
function
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
case DriverEnum.LARAVEL:
|
|
1760
|
-
return new LaravelRequestStrategy();
|
|
2341
|
+
function resolveResponseOptions(driver, responseConfig) {
|
|
2342
|
+
if (driver === DriverEnum.JSON_API) {
|
|
2343
|
+
return new JsonApiResponseOptions(responseConfig);
|
|
2344
|
+
}
|
|
2345
|
+
if (driver === DriverEnum.NESTJS) {
|
|
2346
|
+
return new NestjsResponseOptions(responseConfig);
|
|
1761
2347
|
}
|
|
2348
|
+
return new ResponseOptions(responseConfig);
|
|
1762
2349
|
}
|
|
1763
2350
|
/**
|
|
1764
|
-
*
|
|
2351
|
+
* Build the core provider list shared by `provideNgQubee()` and
|
|
2352
|
+
* `NgQubeeModule.forRoot()`
|
|
1765
2353
|
*
|
|
1766
|
-
*
|
|
1767
|
-
*
|
|
2354
|
+
* Exposes the driver, strategies, and options via injection tokens so that
|
|
2355
|
+
* consumers can request a component-scoped instance of the services through
|
|
2356
|
+
* `provideNgQubeeInstance()`.
|
|
2357
|
+
*
|
|
2358
|
+
* @param config - Configuration object compliant to the IConfig interface
|
|
2359
|
+
* @returns An array of Providers for the environment injector
|
|
1768
2360
|
*/
|
|
1769
|
-
function
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2361
|
+
function buildNgQubeeProviders(config) {
|
|
2362
|
+
const driver = config.driver;
|
|
2363
|
+
const requestOptions = new QueryBuilderOptions(Object.assign({}, config.request));
|
|
2364
|
+
const responseOptions = resolveResponseOptions(driver, Object.assign({}, config.response));
|
|
2365
|
+
return [
|
|
2366
|
+
{ provide: NG_QUBEE_DRIVER, useValue: driver },
|
|
2367
|
+
{ provide: NG_QUBEE_REQUEST_STRATEGY, useValue: resolveRequestStrategy(driver) },
|
|
2368
|
+
{ provide: NG_QUBEE_REQUEST_OPTIONS, useValue: requestOptions },
|
|
2369
|
+
{ provide: NG_QUBEE_RESPONSE_STRATEGY, useValue: resolveResponseStrategy(driver) },
|
|
2370
|
+
{ provide: NG_QUBEE_RESPONSE_OPTIONS, useValue: responseOptions },
|
|
2371
|
+
NestService,
|
|
2372
|
+
NgQubeeService,
|
|
2373
|
+
PaginationService
|
|
2374
|
+
];
|
|
1778
2375
|
}
|
|
1779
2376
|
/**
|
|
1780
2377
|
* Sets up providers necessary to enable `NgQubee` functionality for the application.
|
|
@@ -1797,6 +2394,15 @@ function resolveResponseStrategy(driver) {
|
|
|
1797
2394
|
* });
|
|
1798
2395
|
* ```
|
|
1799
2396
|
*
|
|
2397
|
+
* JSON:API driver example:
|
|
2398
|
+
* ```
|
|
2399
|
+
* import { DriverEnum } from 'ng-qubee';
|
|
2400
|
+
*
|
|
2401
|
+
* bootstrapApplication(AppComponent, {
|
|
2402
|
+
* providers: [provideNgQubee({ driver: DriverEnum.JSON_API })]
|
|
2403
|
+
* });
|
|
2404
|
+
* ```
|
|
2405
|
+
*
|
|
1800
2406
|
* NestJS driver example:
|
|
1801
2407
|
* ```
|
|
1802
2408
|
* import { DriverEnum } from 'ng-qubee';
|
|
@@ -1811,30 +2417,61 @@ function resolveResponseStrategy(driver) {
|
|
|
1811
2417
|
* @returns A set of providers to setup NgQubee
|
|
1812
2418
|
*/
|
|
1813
2419
|
function provideNgQubee(config) {
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
2420
|
+
return makeEnvironmentProviders(buildNgQubeeProviders(config));
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* Providers for a component-scoped NgQubee instance
|
|
2424
|
+
*
|
|
2425
|
+
* Use this inside a standalone component's `providers: [...]` to get a
|
|
2426
|
+
* dedicated `NgQubeeService` (and its `NestService` / `PaginationService`
|
|
2427
|
+
* collaborators) whose query-builder and pagination state does not bleed
|
|
2428
|
+
* with the app-wide shared instance provided by `provideNgQubee()`.
|
|
2429
|
+
*
|
|
2430
|
+
* @usageNotes
|
|
2431
|
+
*
|
|
2432
|
+
* ```
|
|
2433
|
+
* @Component({
|
|
2434
|
+
* standalone: true,
|
|
2435
|
+
* providers: [...provideNgQubeeInstance()]
|
|
2436
|
+
* })
|
|
2437
|
+
* export class MyFeatureComponent {
|
|
2438
|
+
* constructor(private _qb: NgQubeeService) {}
|
|
2439
|
+
* }
|
|
2440
|
+
* ```
|
|
2441
|
+
*
|
|
2442
|
+
* The driver, strategies, and options are inherited from the environment
|
|
2443
|
+
* injector (`provideNgQubee()` at root), so only the service instances are
|
|
2444
|
+
* re-created at the component level.
|
|
2445
|
+
*
|
|
2446
|
+
* @publicApi
|
|
2447
|
+
* @returns A provider array to spread into a component's `providers`
|
|
2448
|
+
*/
|
|
2449
|
+
function provideNgQubeeInstance() {
|
|
2450
|
+
return [NestService, NgQubeeService, PaginationService];
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
// @dynamic
|
|
2454
|
+
class NgQubeeModule {
|
|
2455
|
+
/**
|
|
2456
|
+
* Configure NgQubee for the root module
|
|
2457
|
+
*
|
|
2458
|
+
* @param config - Configuration object with driver, and optional request and response settings
|
|
2459
|
+
* @returns Module with providers configured for the specified driver
|
|
2460
|
+
*/
|
|
2461
|
+
static forRoot(config) {
|
|
2462
|
+
return {
|
|
2463
|
+
ngModule: NgQubeeModule,
|
|
2464
|
+
providers: buildNgQubeeProviders(config)
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2468
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule });
|
|
2469
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule });
|
|
1837
2470
|
}
|
|
2471
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule, decorators: [{
|
|
2472
|
+
type: NgModule,
|
|
2473
|
+
args: [{}]
|
|
2474
|
+
}] });
|
|
1838
2475
|
|
|
1839
2476
|
/**
|
|
1840
2477
|
* Enum representing the available filter operators for the NestJS driver
|
|
@@ -1868,5 +2505,5 @@ var FilterOperatorEnum;
|
|
|
1868
2505
|
* Generated bundle index. Do not edit.
|
|
1869
2506
|
*/
|
|
1870
2507
|
|
|
1871
|
-
export { DriverEnum, FilterOperatorEnum, InvalidLimitError, InvalidPageNumberError, InvalidResourceNameError, KeyNotFoundError, LaravelRequestStrategy, LaravelResponseStrategy, NestjsRequestStrategy, NestjsResponseStrategy, NgQubeeModule, NgQubeeService, PaginatedCollection, PaginationService, SortEnum, SpatieRequestStrategy, SpatieResponseStrategy, UnselectableModelError, UnsupportedFieldSelectionError, UnsupportedFilterError, UnsupportedFilterOperatorError, UnsupportedIncludesError, UnsupportedSearchError, UnsupportedSelectError, UnsupportedSortError, provideNgQubee };
|
|
2508
|
+
export { DriverEnum, FilterOperatorEnum, InvalidLimitError, InvalidPageNumberError, InvalidResourceNameError, JsonApiRequestStrategy, JsonApiResponseStrategy, KeyNotFoundError, LaravelRequestStrategy, LaravelResponseStrategy, NG_QUBEE_DRIVER, NG_QUBEE_REQUEST_OPTIONS, NG_QUBEE_REQUEST_STRATEGY, NG_QUBEE_RESPONSE_OPTIONS, NG_QUBEE_RESPONSE_STRATEGY, NestjsRequestStrategy, NestjsResponseStrategy, NgQubeeModule, NgQubeeService, PaginatedCollection, PaginationNotSyncedError, PaginationService, SortEnum, SpatieRequestStrategy, SpatieResponseStrategy, UnselectableModelError, UnsupportedFieldSelectionError, UnsupportedFilterError, UnsupportedFilterOperatorError, UnsupportedIncludesError, UnsupportedSearchError, UnsupportedSelectError, UnsupportedSortError, buildNgQubeeProviders, provideNgQubee, provideNgQubeeInstance };
|
|
1872
2509
|
//# sourceMappingURL=ng-qubee.mjs.map
|