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.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable, Inject, Optional, NgModule, makeEnvironmentProviders } from '@angular/core';
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
- * Must be a positive integer greater than 0
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
- class NgQubeeService {
715
- _nestService;
716
- /**
717
- * The active pagination driver
718
- */
719
- _driver;
720
- /**
721
- * Resolved query parameter key name options
722
- */
723
- _options;
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
- * Assert that the active driver is one of the allowed drivers
744
- *
745
- * @param allowed - The allowed drivers
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
- _assertDriver(allowed, error) {
750
- if (!allowed.includes(this._driver)) {
751
- throw error;
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
- * Add fields to the select statement for the given model (Spatie only)
756
- *
757
- * @param model - Model that holds the fields
758
- * @param fields - Fields to select
759
- * @returns {this}
760
- * @throws {UnsupportedFieldSelectionError} If the active driver does not support per-model field selection
761
- */
762
- addFields(model, fields) {
763
- this._assertDriver([DriverEnum.SPATIE], new UnsupportedFieldSelectionError());
764
- if (!fields.length) {
765
- return this;
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
- * Add a filter with the given value(s) (Spatie and NestJS only)
772
- *
773
- * Produces: `filter[field]=value` (Spatie) or `filter.field=value` (NestJS)
774
- *
775
- * @param {string} field - Name of the field to filter
776
- * @param {(string | number | boolean)[]} values - The needle(s)
777
- * @returns {this}
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.SPATIE, DriverEnum.NESTJS], new UnsupportedFilterError());
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 (Spatie and NestJS only)
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.SPATIE, DriverEnum.NESTJS], new UnsupportedSortError());
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
- * Delete selected fields for the given models in the current query builder state (Spatie only)
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 (Spatie and NestJS only)
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.SPATIE, DriverEnum.NESTJS], new UnsupportedFilterError());
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 (Spatie and NestJS only)
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.SPATIE, DriverEnum.NESTJS], new UnsupportedSortError());
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
- * @param limit - Number of items per page
1018
- * @returns {this}
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 page - Page number
1028
- * @returns {this}
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
- setPage(page) {
1031
- this._nestService.page = page;
1032
- return this;
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
- * Set the API resource to run the query against
1645
+ * Resolve a value from a response object using a dot-notation path
1036
1646
  *
1037
- * @param {string} resource - Resource name (e.g. 'users' produces /users)
1038
- * @returns {this}
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
- setResource(resource) {
1041
- this._nestService.resource = resource;
1042
- return this;
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
- * Set the search term for full-text search (NestJS only)
1658
+ * Resolve the "from" index value
1046
1659
  *
1047
- * Produces: `search=term`
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 {string} search - The search term
1050
- * @returns {this}
1051
- * @throws {UnsupportedSearchError} If the active driver does not support search
1052
- */
1053
- setSearch(search) {
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
- _responseStrategy;
1085
- constructor(responseStrategy, options = {}) {
1086
- this._options = new ResponseOptions(options);
1087
- this._responseStrategy = responseStrategy;
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
- * Transform a raw API response into a typed PaginatedCollection
1682
+ * Resolve the "to" index value
1091
1683
  *
1092
- * Delegates to the active driver's response strategy for parsing.
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 API response object
1095
- * @returns A typed PaginatedCollection instance
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
- paginate(response) {
1099
- return this._responseStrategy.paginate(response, this._options);
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$1(driver) {
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$1(driver) {
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 request strategy instance for the given driver
2335
+ * Resolve the driver-specific `ResponseOptions` instance
1749
2336
  *
1750
2337
  * @param driver - The pagination driver
1751
- * @returns The corresponding request strategy
2338
+ * @param responseConfig - User-supplied response key overrides
2339
+ * @returns A pre-built ResponseOptions (or driver-specific subclass)
1752
2340
  */
1753
- function resolveRequestStrategy(driver) {
1754
- switch (driver) {
1755
- case DriverEnum.NESTJS:
1756
- return new NestjsRequestStrategy();
1757
- case DriverEnum.SPATIE:
1758
- return new SpatieRequestStrategy();
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
- * Resolve the response strategy instance for the given driver
2351
+ * Build the core provider list shared by `provideNgQubee()` and
2352
+ * `NgQubeeModule.forRoot()`
1765
2353
  *
1766
- * @param driver - The pagination driver
1767
- * @returns The corresponding response strategy
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 resolveResponseStrategy(driver) {
1770
- switch (driver) {
1771
- case DriverEnum.NESTJS:
1772
- return new NestjsResponseStrategy();
1773
- case DriverEnum.SPATIE:
1774
- return new SpatieResponseStrategy();
1775
- case DriverEnum.LARAVEL:
1776
- return new LaravelResponseStrategy();
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
- const driver = config.driver;
1815
- const requestStrategy = resolveRequestStrategy(driver);
1816
- const responseStrategy = resolveResponseStrategy(driver);
1817
- return makeEnvironmentProviders([
1818
- {
1819
- provide: NestService,
1820
- useClass: NestService
1821
- },
1822
- {
1823
- deps: [NestService],
1824
- provide: NgQubeeService,
1825
- useFactory: (nestService) => new NgQubeeService(nestService, requestStrategy, driver, Object.assign({}, config.request))
1826
- },
1827
- {
1828
- provide: PaginationService,
1829
- useFactory: () => {
1830
- const responseConfig = Object.assign({}, config.response);
1831
- return driver === DriverEnum.NESTJS
1832
- ? new PaginationService(responseStrategy, new NestjsResponseOptions(responseConfig))
1833
- : new PaginationService(responseStrategy, responseConfig);
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