ng-qubee 2.0.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { signal, computed, Injectable, Inject, Optional, NgModule, makeEnvironmentProviders } from '@angular/core';
3
+ import { BehaviorSubject, filter, throwError } from 'rxjs';
3
4
  import * as qs from 'qs';
4
- import { BehaviorSubject, filter } from 'rxjs';
5
5
 
6
6
  class KeyNotFoundError extends Error {
7
7
  constructor(key) {
@@ -66,18 +66,178 @@ class PaginatedCollection {
66
66
  }
67
67
  }
68
68
 
69
- var SortEnum;
70
- (function (SortEnum) {
71
- SortEnum["ASC"] = "asc";
72
- SortEnum["DESC"] = "desc";
73
- })(SortEnum || (SortEnum = {}));
69
+ /**
70
+ * Enum representing the available pagination driver types
71
+ *
72
+ * Each driver encapsulates the full format knowledge for both
73
+ * request building (URI generation) and response parsing.
74
+ */
75
+ var DriverEnum;
76
+ (function (DriverEnum) {
77
+ DriverEnum["LARAVEL"] = "laravel";
78
+ DriverEnum["NESTJS"] = "nestjs";
79
+ DriverEnum["SPATIE"] = "spatie";
80
+ })(DriverEnum || (DriverEnum = {}));
74
81
 
75
- class UnselectableModelError extends Error {
76
- constructor(model) {
77
- super(`Unselectable Model: the selected model (${model}) is not present neither in the "model" property, nor in the includes object.`);
82
+ /**
83
+ * Resolved response field key names with defaults applied
84
+ *
85
+ * Maps logical pagination concepts to the actual key names
86
+ * used in the API response. Unset values fall back to Laravel defaults.
87
+ *
88
+ * For NestJS responses, use dot-notation paths:
89
+ * ```typescript
90
+ * new ResponseOptions({
91
+ * currentPage: 'meta.currentPage',
92
+ * total: 'meta.totalItems'
93
+ * });
94
+ * ```
95
+ */
96
+ class ResponseOptions {
97
+ currentPage;
98
+ data;
99
+ firstPageUrl;
100
+ from;
101
+ lastPage;
102
+ lastPageUrl;
103
+ nextPageUrl;
104
+ path;
105
+ perPage;
106
+ prevPageUrl;
107
+ to;
108
+ total;
109
+ constructor(options) {
110
+ this.currentPage = options.currentPage || 'current_page';
111
+ this.data = options.data || 'data';
112
+ this.firstPageUrl = options.firstPageUrl || 'first_page_url';
113
+ this.from = options.from || 'from';
114
+ this.lastPage = options.lastPage || 'last_page';
115
+ this.lastPageUrl = options.lastPageUrl || 'last_page_url';
116
+ this.nextPageUrl = options.nextPageUrl || 'next_page_url';
117
+ this.path = options.path || 'path';
118
+ this.perPage = options.perPage || 'per_page';
119
+ this.prevPageUrl = options.prevPageUrl || 'prev_page_url';
120
+ this.to = options.to || 'to';
121
+ this.total = options.total || 'total';
122
+ }
123
+ }
124
+ /**
125
+ * Pre-configured ResponseOptions for the NestJS driver
126
+ *
127
+ * Uses dot-notation paths to access nested values in the NestJS response format.
128
+ */
129
+ class NestjsResponseOptions extends ResponseOptions {
130
+ constructor(options) {
131
+ super({
132
+ currentPage: options.currentPage || 'meta.currentPage',
133
+ data: options.data || 'data',
134
+ firstPageUrl: options.firstPageUrl || 'links.first',
135
+ from: options.from || 'meta.from',
136
+ lastPage: options.lastPage || 'meta.totalPages',
137
+ lastPageUrl: options.lastPageUrl || 'links.last',
138
+ nextPageUrl: options.nextPageUrl || 'links.next',
139
+ path: options.path || 'path',
140
+ perPage: options.perPage || 'meta.itemsPerPage',
141
+ prevPageUrl: options.prevPageUrl || 'links.previous',
142
+ to: options.to || 'meta.to',
143
+ total: options.total || 'meta.totalItems'
144
+ });
145
+ }
146
+ }
147
+
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';
78
170
  }
79
171
  }
80
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
+ */
81
241
  class QueryBuilderOptions {
82
242
  appends;
83
243
  fields;
@@ -85,7 +245,10 @@ class QueryBuilderOptions {
85
245
  includes;
86
246
  limit;
87
247
  page;
248
+ search;
249
+ select;
88
250
  sort;
251
+ sortBy;
89
252
  constructor(options) {
90
253
  this.appends = options.appends || 'append';
91
254
  this.fields = options.fields || 'fields';
@@ -93,14 +256,29 @@ class QueryBuilderOptions {
93
256
  this.includes = options.includes || 'include';
94
257
  this.limit = options.limit || 'limit';
95
258
  this.page = options.page || 'page';
259
+ this.search = options.search || 'search';
260
+ this.select = options.select || 'select';
96
261
  this.sort = options.sort || 'sort';
262
+ this.sortBy = options.sortBy || 'sortBy';
97
263
  }
98
264
  }
99
265
 
100
- class InvalidModelNameError extends Error {
101
- constructor(model) {
102
- super(`Invalid model name: Model name must be a non-empty string. Received: ${JSON.stringify(model)}`);
103
- this.name = 'InvalidModelNameError';
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
+ /**
274
+ * Error thrown when an invalid resource name is provided
275
+ *
276
+ * Resource name must be a non-empty string.
277
+ */
278
+ class InvalidResourceNameError extends Error {
279
+ constructor(resource) {
280
+ super(`Invalid resource name: Resource name must be a non-empty string. Received: ${JSON.stringify(resource)}`);
281
+ this.name = 'InvalidResourceNameError';
104
282
  }
105
283
  }
106
284
 
@@ -111,21 +289,17 @@ class InvalidPageNumberError extends Error {
111
289
  }
112
290
  }
113
291
 
114
- class InvalidLimitError extends Error {
115
- constructor(limit) {
116
- super(`Invalid limit value: Limit must be a positive integer greater than 0. Received: ${limit}`);
117
- this.name = 'InvalidLimitError';
118
- }
119
- }
120
-
121
292
  const INITIAL_STATE = {
122
293
  baseUrl: '',
123
294
  fields: {},
124
295
  filters: {},
125
296
  includes: [],
126
297
  limit: 15,
127
- model: '',
298
+ operatorFilters: [],
128
299
  page: 1,
300
+ resource: '',
301
+ search: '',
302
+ select: [],
129
303
  sorts: []
130
304
  };
131
305
  class NestService {
@@ -134,15 +308,15 @@ class NestService {
134
308
  *
135
309
  * @type {IQueryBuilderState}
136
310
  */
137
- _nest = signal(this._clone(INITIAL_STATE));
311
+ _nest = signal(this._clone(INITIAL_STATE), ...(ngDevMode ? [{ debugName: "_nest" }] : []));
138
312
  /**
139
313
  * A computed signal that makes readonly the writable signal _nest
140
314
  *
141
315
  * @type {Signal<IQueryBuilderState>}
142
316
  */
143
- nest = computed(() => this._clone(this._nest()));
317
+ nest = computed(() => this._clone(this._nest()), ...(ngDevMode ? [{ debugName: "nest" }] : []));
144
318
  constructor() {
145
- // Nothing to see here 👮🏻‍♀️
319
+ // Nothing to see here
146
320
  }
147
321
  /**
148
322
  * Set the base URL for the API
@@ -173,22 +347,6 @@ class NestService {
173
347
  limit
174
348
  }));
175
349
  }
176
- /**
177
- * Set the model name for the query
178
- * Must be a non-empty string
179
- *
180
- * @param {string} model - The model/resource name (e.g., 'users', 'posts')
181
- * @throws {InvalidModelNameError} If model is not a non-empty string
182
- * @example
183
- * service.model = 'users';
184
- */
185
- set model(model) {
186
- this._validateModelName(model);
187
- this._nest.update(nest => ({
188
- ...nest,
189
- model
190
- }));
191
- }
192
350
  /**
193
351
  * Set the page number for pagination
194
352
  * Must be a positive integer greater than 0
@@ -205,19 +363,35 @@ class NestService {
205
363
  page
206
364
  }));
207
365
  }
366
+ /**
367
+ * Set the resource name for the query
368
+ * Must be a non-empty string
369
+ *
370
+ * @param {string} resource - The API resource name (e.g., 'users', 'posts')
371
+ * @throws {InvalidResourceNameError} If resource is not a non-empty string
372
+ * @example
373
+ * service.resource = 'users';
374
+ */
375
+ set resource(resource) {
376
+ this._validateResourceName(resource);
377
+ this._nest.update(nest => ({
378
+ ...nest,
379
+ resource
380
+ }));
381
+ }
208
382
  _clone(obj) {
209
383
  return JSON.parse(JSON.stringify(obj));
210
384
  }
211
385
  /**
212
- * Validates that the model name is a non-empty string
386
+ * Validates that the limit is a positive integer
213
387
  *
214
- * @param {string} model - The model name to validate
215
- * @throws {InvalidModelNameError} If model is not a non-empty string
388
+ * @param {number} limit - The limit value to validate
389
+ * @throws {InvalidLimitError} If limit is not a positive integer
216
390
  * @private
217
391
  */
218
- _validateModelName(model) {
219
- if (!model || typeof model !== 'string' || model.trim().length === 0) {
220
- throw new InvalidModelNameError(model);
392
+ _validateLimit(limit) {
393
+ if (!Number.isInteger(limit) || limit < 1) {
394
+ throw new InvalidLimitError(limit);
221
395
  }
222
396
  }
223
397
  /**
@@ -233,15 +407,15 @@ class NestService {
233
407
  }
234
408
  }
235
409
  /**
236
- * Validates that the limit is a positive integer
410
+ * Validates that the resource name is a non-empty string
237
411
  *
238
- * @param {number} limit - The limit value to validate
239
- * @throws {InvalidLimitError} If limit is not a positive integer
412
+ * @param {string} resource - The resource name to validate
413
+ * @throws {InvalidResourceNameError} If resource is not a non-empty string
240
414
  * @private
241
415
  */
242
- _validateLimit(limit) {
243
- if (!Number.isInteger(limit) || limit < 1) {
244
- throw new InvalidLimitError(limit);
416
+ _validateResourceName(resource) {
417
+ if (!resource || typeof resource !== 'string' || resource.trim().length === 0) {
418
+ throw new InvalidResourceNameError(resource);
245
419
  }
246
420
  }
247
421
  /**
@@ -300,7 +474,7 @@ class NestService {
300
474
  * Add resources to include with the request
301
475
  * Automatically prevents duplicate includes
302
476
  *
303
- * @param {string[]} includes - Array of model names to include in the response
477
+ * @param {string[]} includes - Array of resource names to include in the response
304
478
  * @return {void}
305
479
  * @example
306
480
  * service.addIncludes(['profile', 'posts']);
@@ -316,6 +490,56 @@ class NestService {
316
490
  };
317
491
  });
318
492
  }
493
+ /**
494
+ * Add filters with explicit operators (NestJS only)
495
+ * Automatically prevents duplicate operator filters for the same field + operator combination
496
+ *
497
+ * @param {IOperatorFilter[]} filters - Array of operator filter configurations
498
+ * @return {void}
499
+ * @example
500
+ * import { FilterOperatorEnum } from 'ng-qubee';
501
+ * service.addOperatorFilters([{ field: 'age', operator: FilterOperatorEnum.GTE, values: [18] }]);
502
+ */
503
+ addOperatorFilters(filters) {
504
+ this._nest.update(nest => {
505
+ const merged = [...nest.operatorFilters];
506
+ filters.forEach(newFilter => {
507
+ const existingIdx = merged.findIndex(f => f.field === newFilter.field && f.operator === newFilter.operator);
508
+ if (existingIdx > -1) {
509
+ const existingValues = merged[existingIdx].values;
510
+ merged[existingIdx] = {
511
+ ...merged[existingIdx],
512
+ values: Array.from(new Set([...existingValues, ...newFilter.values]))
513
+ };
514
+ }
515
+ else {
516
+ merged.push({ ...newFilter });
517
+ }
518
+ });
519
+ return {
520
+ ...nest,
521
+ operatorFilters: merged
522
+ };
523
+ });
524
+ }
525
+ /**
526
+ * Add flat field selection columns (NestJS only)
527
+ * Automatically prevents duplicate select fields
528
+ *
529
+ * @param {string[]} fields - Array of column names to select
530
+ * @return {void}
531
+ * @example
532
+ * service.addSelect(['id', 'name', 'email']);
533
+ */
534
+ addSelect(fields) {
535
+ this._nest.update(nest => {
536
+ const uniqueSelect = Array.from(new Set([...nest.select, ...fields]));
537
+ return {
538
+ ...nest,
539
+ select: uniqueSelect
540
+ };
541
+ });
542
+ }
319
543
  /**
320
544
  * Add a field that should be used for sorting data
321
545
  *
@@ -390,6 +614,49 @@ class NestService {
390
614
  includes: nest.includes.filter(v => !includes.includes(v))
391
615
  }));
392
616
  }
617
+ /**
618
+ * Remove operator filters by field name (NestJS only)
619
+ *
620
+ * @param {...string[]} fields - Field names of operator filters to remove
621
+ * @return {void}
622
+ * @example
623
+ * service.deleteOperatorFilters('age');
624
+ * service.deleteOperatorFilters('price', 'quantity');
625
+ */
626
+ deleteOperatorFilters(...fields) {
627
+ this._nest.update(nest => ({
628
+ ...nest,
629
+ operatorFilters: nest.operatorFilters.filter(f => !fields.includes(f.field))
630
+ }));
631
+ }
632
+ /**
633
+ * Remove the search term from the state (NestJS only)
634
+ *
635
+ * @return {void}
636
+ * @example
637
+ * service.deleteSearch();
638
+ */
639
+ deleteSearch() {
640
+ this._nest.update(nest => ({
641
+ ...nest,
642
+ search: ''
643
+ }));
644
+ }
645
+ /**
646
+ * Remove flat field selections from the state (NestJS only)
647
+ *
648
+ * @param {...string[]} fields - Field names to remove from selection
649
+ * @return {void}
650
+ * @example
651
+ * service.deleteSelect('email');
652
+ * service.deleteSelect('name', 'email');
653
+ */
654
+ deleteSelect(...fields) {
655
+ this._nest.update(nest => ({
656
+ ...nest,
657
+ select: nest.select.filter(f => !fields.includes(f))
658
+ }));
659
+ }
393
660
  /**
394
661
  * Remove sorts from the request by field name
395
662
  *
@@ -402,7 +669,7 @@ class NestService {
402
669
  deleteSorts(...sorts) {
403
670
  const s = [...this._nest().sorts];
404
671
  sorts.forEach(field => {
405
- const p = this.nest().sorts.findIndex(sort => sort.field === field);
672
+ const p = s.findIndex(sort => sort.field === field);
406
673
  if (p > -1) {
407
674
  s.splice(p, 1);
408
675
  }
@@ -412,6 +679,20 @@ class NestService {
412
679
  sorts: s
413
680
  }));
414
681
  }
682
+ /**
683
+ * Set the full-text search term (NestJS only)
684
+ *
685
+ * @param {string} search - The search term
686
+ * @return {void}
687
+ * @example
688
+ * service.setSearch('john doe');
689
+ */
690
+ setSearch(search) {
691
+ this._nest.update(nest => ({
692
+ ...nest,
693
+ search
694
+ }));
695
+ }
415
696
  /**
416
697
  * Reset the query builder state to initial values
417
698
  * Clears all fields, filters, includes, sorts, and resets pagination
@@ -419,158 +700,121 @@ class NestService {
419
700
  * @return {void}
420
701
  * @example
421
702
  * service.reset();
422
- * // State is now: { baseUrl: '', fields: {}, filters: {}, includes: [], limit: 15, model: '', page: 1, sorts: [] }
423
703
  */
424
704
  reset() {
425
705
  this._nest.update(_ => this._clone(INITIAL_STATE));
426
706
  }
427
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
428
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NestService });
707
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
708
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NestService });
429
709
  }
430
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NestService, decorators: [{
710
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NestService, decorators: [{
431
711
  type: Injectable
432
712
  }], ctorParameters: () => [] });
433
713
 
434
714
  class NgQubeeService {
435
715
  _nestService;
716
+ /**
717
+ * The active pagination driver
718
+ */
719
+ _driver;
720
+ /**
721
+ * Resolved query parameter key name options
722
+ */
436
723
  _options;
437
724
  /**
438
- * This property serves as an accumulator for holding the composed string with each query param
725
+ * The request strategy that builds URIs for the active driver
726
+ */
727
+ _requestStrategy;
728
+ /**
729
+ * Internal BehaviorSubject that holds the latest generated URI
439
730
  */
440
- _uri = '';
441
731
  _uri$ = new BehaviorSubject('');
732
+ /**
733
+ * Observable that emits non-empty generated URIs
734
+ */
442
735
  uri$ = this._uri$.asObservable().pipe(filter(uri => !!uri));
443
- constructor(_nestService, options = {}) {
736
+ constructor(_nestService, requestStrategy, driver, options = {}) {
444
737
  this._nestService = _nestService;
738
+ this._driver = driver;
445
739
  this._options = new QueryBuilderOptions(options);
740
+ this._requestStrategy = requestStrategy;
446
741
  }
447
- _parseFields(s) {
448
- if (!Object.keys(s.fields).length) {
449
- return this._uri;
450
- }
451
- if (!s.model) {
452
- throw new Error('While selecting fields, the -> model <- is required');
453
- }
454
- if (!(s.model in s.fields)) {
455
- throw new Error(`Key ${s.model} is missing in the fields object`);
456
- }
457
- const f = {};
458
- for (const k in s.fields) {
459
- if (s.fields.hasOwnProperty(k)) {
460
- // Check if the key is the model or is declared in "includes".
461
- // If not, it means that has not been selected anywhere and that will cause an error on the API
462
- if (k !== s.model && !s.includes.includes(k)) {
463
- throw new UnselectableModelError(k);
464
- }
465
- Object.assign(f, { [`${this._options.fields}[${k}]`]: s.fields[k].join(',') });
466
- }
467
- }
468
- const param = `${this._prepend(s.model)}${qs.stringify(f, { encode: false })}`;
469
- this._uri += param;
470
- return param;
471
- }
472
- _parseFilters(s) {
473
- const keys = Object.keys(s.filters);
474
- if (!keys.length) {
475
- return this._uri;
742
+ /**
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
748
+ */
749
+ _assertDriver(allowed, error) {
750
+ if (!allowed.includes(this._driver)) {
751
+ throw error;
476
752
  }
477
- const f = {
478
- [`${this._options.filters}`]: keys.reduce((acc, key) => {
479
- return Object.assign(acc, { [key]: s.filters[key].join(',') });
480
- }, {})
481
- };
482
- const param = `${this._prepend(s.model)}${qs.stringify(f, { encode: false })}`;
483
- this._uri += param;
484
- return param;
485
753
  }
486
- _parseIncludes(s) {
487
- if (!s.includes.length) {
488
- return this._uri;
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;
489
766
  }
490
- const param = `${this._prepend(s.model)}${this._options.includes}=${s.includes}`;
491
- this._uri += param;
492
- return param;
493
- }
494
- _parseLimit(s) {
495
- const param = `${this._prepend(s.model)}${this._options.limit}=${s.limit}`;
496
- this._uri += param;
497
- return param;
498
- }
499
- _parsePage(s) {
500
- const param = `${this._prepend(s.model)}${this._options.page}=${s.page}`;
501
- this._uri += param;
502
- return param;
503
- }
504
- _parseSort(s) {
505
- let param = '';
506
- if (!s.sorts.length) {
507
- return param;
508
- }
509
- param = `${this._prepend(s.model)}${this._options.sort}=`;
510
- s.sorts.forEach((sort, idx) => {
511
- param += `${sort.order === SortEnum.DESC ? '-' : ''}${sort.field}`;
512
- if (idx < s.sorts.length - 1) {
513
- param += ',';
514
- }
515
- });
516
- this._uri += param;
517
- return param;
518
- }
519
- _parse(s) {
520
- if (!s.model) {
521
- throw new Error('Set the model property BEFORE adding filters or calling the url() / get() methods');
522
- }
523
- // Cleanup the previously generated URI
524
- this._uri = '';
525
- this._parseIncludes(s);
526
- this._parseFields(s);
527
- this._parseFilters(s);
528
- this._parseLimit(s);
529
- this._parsePage(s);
530
- this._parseSort(s);
531
- return this._uri;
532
- }
533
- _prepend(model) {
534
- return this._uri ? '&' : `/${model}?`;
767
+ this._nestService.addFields({ [model]: fields });
768
+ return this;
535
769
  }
536
770
  /**
537
- * Add fields to the select statement for the given model
771
+ * Add a filter with the given value(s) (Spatie and NestJS only)
538
772
  *
539
- * @param model Model that holds the fields
540
- * @param fields Fields to select
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)
541
777
  * @returns {this}
778
+ * @throws {UnsupportedFilterError} If the active driver does not support filters
542
779
  */
543
- addFields(model, fields) {
544
- if (!fields.length) {
780
+ addFilter(field, ...values) {
781
+ this._assertDriver([DriverEnum.SPATIE, DriverEnum.NESTJS], new UnsupportedFilterError());
782
+ if (!values.length) {
545
783
  return this;
546
784
  }
547
- this._nestService.addFields({ [model]: fields });
785
+ this._nestService.addFilters({
786
+ [field]: values
787
+ });
548
788
  return this;
549
789
  }
550
790
  /**
551
- * Add a filter with the given value(s)
552
- * I.e. filter[field]=1 or filter[field]=1,2,3
791
+ * Add a filter with an explicit operator (NestJS only)
792
+ *
793
+ * Produces: `filter.field=$operator:value`
553
794
  *
554
- * @param {string} field Name of the field to filter
555
- * @param {string[]} value The needle(s)
795
+ * @param {string} field - Name of the field to filter
796
+ * @param {FilterOperatorEnum} operator - The filter operator to apply
797
+ * @param {(string | number | boolean)[]} values - The value(s) for the filter
556
798
  * @returns {this}
799
+ * @throws {UnsupportedFilterOperatorError} If the active driver does not support filter operators
557
800
  */
558
- addFilter(field, ...values) {
801
+ addFilterOperator(field, operator, ...values) {
802
+ this._assertDriver([DriverEnum.NESTJS], new UnsupportedFilterOperatorError());
559
803
  if (!values.length) {
560
804
  return this;
561
805
  }
562
- this._nestService.addFilters({
563
- [field]: values
564
- });
806
+ this._nestService.addOperatorFilters([{ field, operator, values }]);
565
807
  return this;
566
808
  }
567
809
  /**
568
- * Add related entities to include in the request
810
+ * Add related entities to include in the request (Spatie only)
569
811
  *
570
- * @param {string[]} models
571
- * @returns
812
+ * @param {string[]} models - Models to include
813
+ * @returns {this}
814
+ * @throws {UnsupportedIncludesError} If the active driver does not support includes
572
815
  */
573
816
  addIncludes(...models) {
817
+ this._assertDriver([DriverEnum.SPATIE], new UnsupportedIncludesError());
574
818
  if (!models.length) {
575
819
  return this;
576
820
  }
@@ -578,13 +822,32 @@ class NgQubeeService {
578
822
  return this;
579
823
  }
580
824
  /**
581
- * Add a field with a sort criteria
825
+ * Add flat field selection (NestJS only)
826
+ *
827
+ * Produces: `select=col1,col2`
582
828
  *
583
- * @param field Field to use for sorting
584
- * @param {SortEnum} order A value from the SortEnum enumeration
829
+ * @param {string[]} fields - Fields to select
585
830
  * @returns {this}
831
+ * @throws {UnsupportedSelectError} If the active driver does not support flat field selection
832
+ */
833
+ addSelect(...fields) {
834
+ this._assertDriver([DriverEnum.NESTJS], new UnsupportedSelectError());
835
+ if (!fields.length) {
836
+ return this;
837
+ }
838
+ this._nestService.addSelect(fields);
839
+ return this;
840
+ }
841
+ /**
842
+ * Add a field with a sort criteria (Spatie and NestJS only)
843
+ *
844
+ * @param field - Field to use for sorting
845
+ * @param {SortEnum} order - A value from the SortEnum enumeration
846
+ * @returns {this}
847
+ * @throws {UnsupportedSortError} If the active driver does not support sorts
586
848
  */
587
849
  addSort(field, order) {
850
+ this._assertDriver([DriverEnum.SPATIE, DriverEnum.NESTJS], new UnsupportedSortError());
588
851
  this._nestService.addSort({
589
852
  field,
590
853
  order
@@ -592,7 +855,7 @@ class NgQubeeService {
592
855
  return this;
593
856
  }
594
857
  /**
595
- * Delete selected fields for the given models in the current query builder state
858
+ * Delete selected fields for the given models in the current query builder state (Spatie only)
596
859
  *
597
860
  * ```
598
861
  * ngQubeeService.deleteFields({
@@ -601,25 +864,29 @@ class NgQubeeService {
601
864
  * });
602
865
  * ```
603
866
  *
604
- * @param {IFields} fields
605
- * @returns
867
+ * @param {IFields} fields - Object mapping model names to field arrays to remove
868
+ * @returns {this}
869
+ * @throws {UnsupportedFieldSelectionError} If the active driver does not support per-model field selection
606
870
  */
607
871
  deleteFields(fields) {
872
+ this._assertDriver([DriverEnum.SPATIE], new UnsupportedFieldSelectionError());
608
873
  this._nestService.deleteFields(fields);
609
874
  return this;
610
875
  }
611
876
  /**
612
- * Delete selected fields for the given model in the current query builder state
877
+ * Delete selected fields for the given model in the current query builder state (Spatie only)
613
878
  *
614
879
  * ```
615
- * ngQubeeService.deleteFieldsByModel('users', 'email', 'password']);
880
+ * ngQubeeService.deleteFieldsByModel('users', 'email', 'password');
616
881
  * ```
617
882
  *
618
- * @param model Model that holds the fields
619
- * @param {string[]} fields Fields to delete from the state
883
+ * @param model - Model that holds the fields
884
+ * @param {string[]} fields - Fields to delete from the state
620
885
  * @returns {this}
886
+ * @throws {UnsupportedFieldSelectionError} If the active driver does not support per-model field selection
621
887
  */
622
888
  deleteFieldsByModel(model, ...fields) {
889
+ this._assertDriver([DriverEnum.SPATIE], new UnsupportedFieldSelectionError());
623
890
  if (!fields.length) {
624
891
  return this;
625
892
  }
@@ -629,12 +896,14 @@ class NgQubeeService {
629
896
  return this;
630
897
  }
631
898
  /**
632
- * Remove given filters from the query builder state
899
+ * Remove given filters from the query builder state (Spatie and NestJS only)
633
900
  *
634
- * @param {string[]} filters Filters to remove
901
+ * @param {string[]} filters - Filters to remove
635
902
  * @returns {this}
903
+ * @throws {UnsupportedFilterError} If the active driver does not support filters
636
904
  */
637
905
  deleteFilters(...filters) {
906
+ this._assertDriver([DriverEnum.SPATIE, DriverEnum.NESTJS], new UnsupportedFilterError());
638
907
  if (!filters.length) {
639
908
  return this;
640
909
  }
@@ -642,12 +911,14 @@ class NgQubeeService {
642
911
  return this;
643
912
  }
644
913
  /**
645
- * Remove selected related models from the query builder state
914
+ * Remove selected related models from the query builder state (Spatie only)
646
915
  *
647
- * @param {string[]} includes Models to remove
648
- * @returns
916
+ * @param {string[]} includes - Models to remove
917
+ * @returns {this}
918
+ * @throws {UnsupportedIncludesError} If the active driver does not support includes
649
919
  */
650
920
  deleteIncludes(...includes) {
921
+ this._assertDriver([DriverEnum.SPATIE], new UnsupportedIncludesError());
651
922
  if (!includes.length) {
652
923
  return this;
653
924
  }
@@ -655,23 +926,71 @@ class NgQubeeService {
655
926
  return this;
656
927
  }
657
928
  /**
658
- * Remove sorts rules from the query builder state
929
+ * Remove operator filters by field name (NestJS only)
930
+ *
931
+ * @param {string[]} fields - Field names of operator filters to remove
932
+ * @returns {this}
933
+ * @throws {UnsupportedFilterOperatorError} If the active driver does not support filter operators
934
+ */
935
+ deleteOperatorFilters(...fields) {
936
+ this._assertDriver([DriverEnum.NESTJS], new UnsupportedFilterOperatorError());
937
+ if (!fields.length) {
938
+ return this;
939
+ }
940
+ this._nestService.deleteOperatorFilters(...fields);
941
+ return this;
942
+ }
943
+ /**
944
+ * Remove search term from the query builder state (NestJS only)
945
+ *
946
+ * @returns {this}
947
+ * @throws {UnsupportedSearchError} If the active driver does not support search
948
+ */
949
+ deleteSearch() {
950
+ this._assertDriver([DriverEnum.NESTJS], new UnsupportedSearchError());
951
+ this._nestService.deleteSearch();
952
+ return this;
953
+ }
954
+ /**
955
+ * Remove flat field selections from the query builder state (NestJS only)
956
+ *
957
+ * @param {string[]} fields - Fields to remove from selection
958
+ * @returns {this}
959
+ * @throws {UnsupportedSelectError} If the active driver does not support flat field selection
960
+ */
961
+ deleteSelect(...fields) {
962
+ this._assertDriver([DriverEnum.NESTJS], new UnsupportedSelectError());
963
+ if (!fields.length) {
964
+ return this;
965
+ }
966
+ this._nestService.deleteSelect(...fields);
967
+ return this;
968
+ }
969
+ /**
970
+ * Remove sort rules from the query builder state (Spatie and NestJS only)
659
971
  *
660
- * @param sorts Fields used for sorting to remove
972
+ * @param sorts - Fields used for sorting to remove
661
973
  * @returns {this}
974
+ * @throws {UnsupportedSortError} If the active driver does not support sorts
662
975
  */
663
976
  deleteSorts(...sorts) {
977
+ this._assertDriver([DriverEnum.SPATIE, DriverEnum.NESTJS], new UnsupportedSortError());
664
978
  this._nestService.deleteSorts(...sorts);
665
979
  return this;
666
980
  }
667
981
  /**
668
- * Generate an URI accordingly to the given data
982
+ * Generate a URI accordingly to the given data and active driver
669
983
  *
670
- * @returns {Observable<string>} An observable that emits the generated uri
984
+ * @returns {Observable<string>} An observable that emits the generated URI
671
985
  */
672
986
  generateUri() {
673
- this._uri$.next(this._parse(this._nestService.nest()));
674
- return this.uri$;
987
+ try {
988
+ this._uri$.next(this._requestStrategy.buildUri(this._nestService.nest(), this._options));
989
+ return this.uri$;
990
+ }
991
+ catch (error) {
992
+ return throwError(() => error);
993
+ }
675
994
  }
676
995
  /**
677
996
  * Clear the current state and reset the Query Builder to a fresh, clean condition
@@ -683,9 +1002,9 @@ class NgQubeeService {
683
1002
  return this;
684
1003
  }
685
1004
  /**
686
- * Set the base url to use for composing the address
1005
+ * Set the base URL to use for composing the address
687
1006
  *
688
- * @param {string} baseUrl
1007
+ * @param {string} baseUrl - The base URL
689
1008
  * @returns {this}
690
1009
  */
691
1010
  setBaseUrl(baseUrl) {
@@ -695,7 +1014,7 @@ class NgQubeeService {
695
1014
  /**
696
1015
  * Set the items per page number
697
1016
  *
698
- * @param limit
1017
+ * @param limit - Number of items per page
699
1018
  * @returns {this}
700
1019
  */
701
1020
  setLimit(limit) {
@@ -703,91 +1022,698 @@ class NgQubeeService {
703
1022
  return this;
704
1023
  }
705
1024
  /**
706
- * Set the model to use for running the query against
707
- * - I.e. the model "users" will return /users
1025
+ * Set the page that the backend will use to paginate the result set
708
1026
  *
709
- * @param {string} model Model name
1027
+ * @param page - Page number
710
1028
  * @returns {this}
711
1029
  */
712
- setModel(model) {
713
- this._nestService.model = model;
1030
+ setPage(page) {
1031
+ this._nestService.page = page;
714
1032
  return this;
715
1033
  }
716
1034
  /**
717
- * Set the page that the backend will use to paginate the result set
1035
+ * Set the API resource to run the query against
718
1036
  *
719
- * @param page Page param
1037
+ * @param {string} resource - Resource name (e.g. 'users' produces /users)
720
1038
  * @returns {this}
721
1039
  */
722
- setPage(page) {
723
- this._nestService.page = page;
1040
+ setResource(resource) {
1041
+ this._nestService.resource = resource;
724
1042
  return this;
725
1043
  }
726
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgQubeeService, deps: [{ token: NestService }, { token: 'QUERY_PARAMS_CONFIG', optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
727
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgQubeeService });
1044
+ /**
1045
+ * Set the search term for full-text search (NestJS only)
1046
+ *
1047
+ * Produces: `search=term`
1048
+ *
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 });
728
1060
  }
729
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgQubeeService, decorators: [{
1061
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeService, decorators: [{
730
1062
  type: Injectable
731
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: [{
732
1070
  type: Inject,
733
1071
  args: ['QUERY_PARAMS_CONFIG']
734
1072
  }, {
735
1073
  type: Optional
736
1074
  }] }] });
737
1075
 
738
- class ResponseOptions {
739
- currentPage;
740
- data;
741
- firstPageUrl;
742
- from;
743
- lastPage;
744
- lastPageUrl;
745
- nextPageUrl;
746
- path;
747
- perPage;
748
- prevPageUrl;
749
- to;
750
- total;
751
- constructor(options) {
752
- this.currentPage = options.currentPage || 'current_page';
753
- this.data = options.data || 'data';
754
- this.firstPageUrl = options.firstPageUrl || 'first_page_url';
755
- this.from = options.from || 'from';
756
- this.lastPage = options.lastPage || 'last_page';
757
- this.lastPageUrl = options.lastPageUrl || 'last_page_url';
758
- this.nextPageUrl = options.nextPageUrl || 'next_page_url';
759
- this.path = options.path || 'path';
760
- this.perPage = options.perPage || 'per_page';
761
- this.prevPageUrl = options.prevPageUrl || 'prev_page_url';
762
- this.to = options.to || 'to';
763
- this.total = options.total || 'total';
764
- }
765
- }
766
-
767
1076
  class PaginationService {
1077
+ /**
1078
+ * Resolved response key name options
1079
+ */
768
1080
  _options;
769
- constructor(options = {}) {
1081
+ /**
1082
+ * The response strategy that parses responses for the active driver
1083
+ */
1084
+ _responseStrategy;
1085
+ constructor(responseStrategy, options = {}) {
770
1086
  this._options = new ResponseOptions(options);
1087
+ this._responseStrategy = responseStrategy;
771
1088
  }
1089
+ /**
1090
+ * Transform a raw API response into a typed PaginatedCollection
1091
+ *
1092
+ * Delegates to the active driver's response strategy for parsing.
1093
+ *
1094
+ * @param response - The raw API response object
1095
+ * @returns A typed PaginatedCollection instance
1096
+ */
772
1097
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
773
1098
  paginate(response) {
774
- return new PaginatedCollection(response[this._options.data], response[this._options.currentPage], response[this._options.from], response[this._options.to], response[this._options.total], response[this._options.perPage], response[this._options.prevPageUrl], response[this._options.nextPageUrl], response[this._options.lastPage], response[this._options.firstPageUrl], response[this._options.lastPageUrl]);
1099
+ return this._responseStrategy.paginate(response, this._options);
775
1100
  }
776
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: PaginationService, deps: [{ token: 'RESPONSE_OPTIONS', optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
777
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: PaginationService });
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 });
778
1103
  }
779
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: PaginationService, decorators: [{
1104
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: PaginationService, decorators: [{
780
1105
  type: Injectable
781
1106
  }], ctorParameters: () => [{ type: undefined, decorators: [{
1107
+ type: Inject,
1108
+ args: ['RESPONSE_STRATEGY']
1109
+ }] }, { type: undefined, decorators: [{
782
1110
  type: Inject,
783
1111
  args: ['RESPONSE_OPTIONS']
784
1112
  }, {
785
1113
  type: Optional
786
1114
  }] }] });
787
1115
 
1116
+ /**
1117
+ * Request strategy for the Laravel (pagination-only) driver
1118
+ *
1119
+ * Generates simple pagination URIs:
1120
+ * - `/{resource}?limit=N&page=N`
1121
+ *
1122
+ * Filters, sorts, fields, includes, search, and select in state are ignored.
1123
+ */
1124
+ class LaravelRequestStrategy {
1125
+ /**
1126
+ * Build a pagination-only URI from the given state
1127
+ *
1128
+ * @param state - The current query builder state
1129
+ * @param options - The query parameter key name configuration
1130
+ * @returns The composed URI string
1131
+ * @throws Error if resource is not set
1132
+ */
1133
+ buildUri(state, options) {
1134
+ if (!state.resource) {
1135
+ throw new Error('Set the resource property BEFORE calling the url() / get() methods');
1136
+ }
1137
+ const base = state.baseUrl ? `${state.baseUrl}/${state.resource}` : `/${state.resource}`;
1138
+ return `${base}?${options.limit}=${state.limit}&${options.page}=${state.page}`;
1139
+ }
1140
+ }
1141
+
1142
+ /**
1143
+ * Response strategy for the Laravel (pagination-only) driver
1144
+ *
1145
+ * Parses flat Laravel pagination responses:
1146
+ * ```json
1147
+ * {
1148
+ * "data": [...],
1149
+ * "current_page": 1,
1150
+ * "total": 100,
1151
+ * "per_page": 15,
1152
+ * "from": 1,
1153
+ * "to": 15,
1154
+ * ...
1155
+ * }
1156
+ * ```
1157
+ */
1158
+ class LaravelResponseStrategy {
1159
+ /**
1160
+ * Parse a flat Laravel pagination response into a PaginatedCollection
1161
+ *
1162
+ * @param response - The raw API response object
1163
+ * @param options - The response key name configuration
1164
+ * @returns A typed PaginatedCollection instance
1165
+ */
1166
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1167
+ paginate(response, options) {
1168
+ return new PaginatedCollection(response[options.data], response[options.currentPage], response[options.from], response[options.to], response[options.total], response[options.perPage], response[options.prevPageUrl], response[options.nextPageUrl], response[options.lastPage], response[options.firstPageUrl], response[options.lastPageUrl]);
1169
+ }
1170
+ }
1171
+
1172
+ var SortEnum;
1173
+ (function (SortEnum) {
1174
+ SortEnum["ASC"] = "asc";
1175
+ SortEnum["DESC"] = "desc";
1176
+ })(SortEnum || (SortEnum = {}));
1177
+
1178
+ /**
1179
+ * Request strategy for the NestJS (nestjs-paginate) driver
1180
+ *
1181
+ * Generates URIs in the NestJS paginate format:
1182
+ * - Simple filters: `filter.field=value`
1183
+ * - Operator filters: `filter.field=$operator:value`
1184
+ * - Sorts: `sortBy=field1:DESC,field2:ASC`
1185
+ * - Select: `select=col1,col2`
1186
+ * - Search: `search=term`
1187
+ * - Pagination: `limit=N&page=N`
1188
+ *
1189
+ * @see https://github.com/ppetzold/nestjs-paginate
1190
+ */
1191
+ class NestjsRequestStrategy {
1192
+ /**
1193
+ * Accumulator for composing the URI string
1194
+ */
1195
+ _uri = '';
1196
+ /**
1197
+ * Build a URI string from the given state using the NestJS paginate format
1198
+ *
1199
+ * @param state - The current query builder state
1200
+ * @param options - The query parameter key name configuration
1201
+ * @returns The composed URI string
1202
+ * @throws Error if model is not set
1203
+ */
1204
+ buildUri(state, options) {
1205
+ if (!state.resource) {
1206
+ throw new Error('Set the resource property BEFORE adding filters or calling the url() / get() methods');
1207
+ }
1208
+ this._uri = '';
1209
+ this._parseFilters(state, options);
1210
+ this._parseOperatorFilters(state, options);
1211
+ this._parseSort(state, options);
1212
+ this._parseSelect(state, options);
1213
+ this._parseSearch(state, options);
1214
+ this._parseLimit(state, options);
1215
+ this._parsePage(state, options);
1216
+ return this._uri;
1217
+ }
1218
+ /**
1219
+ * Parse and append simple filter parameters
1220
+ *
1221
+ * Generates: `filter.field=value1,value2` for each filter
1222
+ *
1223
+ * @param state - The current query builder state
1224
+ * @param options - The query parameter key name configuration
1225
+ */
1226
+ _parseFilters(state, options) {
1227
+ const keys = Object.keys(state.filters);
1228
+ if (!keys.length) {
1229
+ return;
1230
+ }
1231
+ keys.forEach(key => {
1232
+ const values = state.filters[key].join(',');
1233
+ const param = `${this._prepend(state)}${options.filters}.${key}=${values}`;
1234
+ this._uri += param;
1235
+ });
1236
+ }
1237
+ /**
1238
+ * Parse and append the limit parameter
1239
+ *
1240
+ * @param state - The current query builder state
1241
+ * @param options - The query parameter key name configuration
1242
+ */
1243
+ _parseLimit(state, options) {
1244
+ const param = `${this._prepend(state)}${options.limit}=${state.limit}`;
1245
+ this._uri += param;
1246
+ }
1247
+ /**
1248
+ * Parse and append operator filter parameters
1249
+ *
1250
+ * Groups operator filters by field and generates:
1251
+ * - Single value: `filter.field=$operator:value`
1252
+ * - Multiple values ($in, $btw): `filter.field=$operator:val1,val2`
1253
+ *
1254
+ * @param state - The current query builder state
1255
+ * @param options - The query parameter key name configuration
1256
+ */
1257
+ _parseOperatorFilters(state, options) {
1258
+ if (!state.operatorFilters.length) {
1259
+ return;
1260
+ }
1261
+ state.operatorFilters.forEach((opFilter) => {
1262
+ const values = opFilter.values.join(',');
1263
+ const param = `${this._prepend(state)}${options.filters}.${opFilter.field}=${opFilter.operator}:${values}`;
1264
+ this._uri += param;
1265
+ });
1266
+ }
1267
+ /**
1268
+ * Parse and append the page parameter
1269
+ *
1270
+ * @param state - The current query builder state
1271
+ * @param options - The query parameter key name configuration
1272
+ */
1273
+ _parsePage(state, options) {
1274
+ const param = `${this._prepend(state)}${options.page}=${state.page}`;
1275
+ this._uri += param;
1276
+ }
1277
+ /**
1278
+ * Parse and append the search parameter
1279
+ *
1280
+ * Generates: `search=term`
1281
+ *
1282
+ * @param state - The current query builder state
1283
+ * @param options - The query parameter key name configuration
1284
+ */
1285
+ _parseSearch(state, options) {
1286
+ if (!state.search) {
1287
+ return;
1288
+ }
1289
+ const param = `${this._prepend(state)}${options.search}=${state.search}`;
1290
+ this._uri += param;
1291
+ }
1292
+ /**
1293
+ * Parse and append the select parameter
1294
+ *
1295
+ * Generates: `select=col1,col2`
1296
+ *
1297
+ * @param state - The current query builder state
1298
+ * @param options - The query parameter key name configuration
1299
+ */
1300
+ _parseSelect(state, options) {
1301
+ if (!state.select.length) {
1302
+ return;
1303
+ }
1304
+ const param = `${this._prepend(state)}${options.select}=${state.select.join(',')}`;
1305
+ this._uri += param;
1306
+ }
1307
+ /**
1308
+ * Parse and append sort parameters
1309
+ *
1310
+ * Generates: `sortBy=field1:DESC,field2:ASC`
1311
+ *
1312
+ * @param state - The current query builder state
1313
+ * @param options - The query parameter key name configuration
1314
+ */
1315
+ _parseSort(state, options) {
1316
+ if (!state.sorts.length) {
1317
+ return;
1318
+ }
1319
+ const sortPairs = state.sorts.map(sort => `${sort.field}:${sort.order === SortEnum.DESC ? 'DESC' : 'ASC'}`);
1320
+ const param = `${this._prepend(state)}${options.sortBy}=${sortPairs.join(',')}`;
1321
+ this._uri += param;
1322
+ }
1323
+ /**
1324
+ * Determine the appropriate URI prefix based on the current accumulator state
1325
+ *
1326
+ * Returns the full base path with `?` for the first parameter,
1327
+ * or `&` for subsequent parameters.
1328
+ *
1329
+ * @param state - The current query builder state
1330
+ * @returns The prefix string to prepend to the next parameter
1331
+ */
1332
+ _prepend(state) {
1333
+ if (this._uri) {
1334
+ return '&';
1335
+ }
1336
+ return state.baseUrl ? `${state.baseUrl}/${state.resource}?` : `/${state.resource}?`;
1337
+ }
1338
+ }
1339
+
1340
+ /**
1341
+ * Response strategy for the NestJS (nestjs-paginate) driver
1342
+ *
1343
+ * Parses nested NestJS pagination responses:
1344
+ * ```json
1345
+ * {
1346
+ * "data": [...],
1347
+ * "meta": {
1348
+ * "currentPage": 1,
1349
+ * "totalItems": 100,
1350
+ * "itemsPerPage": 10,
1351
+ * "totalPages": 10
1352
+ * },
1353
+ * "links": {
1354
+ * "first": "url",
1355
+ * "previous": "url",
1356
+ * "next": "url",
1357
+ * "last": "url",
1358
+ * "current": "url"
1359
+ * }
1360
+ * }
1361
+ * ```
1362
+ *
1363
+ * @see https://github.com/ppetzold/nestjs-paginate
1364
+ */
1365
+ class NestjsResponseStrategy {
1366
+ /**
1367
+ * Parse a nested NestJS pagination response into a PaginatedCollection
1368
+ *
1369
+ * Supports dot-notation key paths for accessing nested values.
1370
+ * Computes `from` and `to` from `currentPage` and `itemsPerPage` when
1371
+ * they are not directly available in the response.
1372
+ *
1373
+ * @param response - The raw API response object
1374
+ * @param options - The response key name configuration
1375
+ * @returns A typed PaginatedCollection instance
1376
+ */
1377
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1378
+ paginate(response, options) {
1379
+ const data = this._resolve(response, options.data);
1380
+ const currentPage = this._resolve(response, options.currentPage);
1381
+ const total = this._resolve(response, options.total);
1382
+ const perPage = this._resolve(response, options.perPage);
1383
+ const lastPage = this._resolve(response, options.lastPage);
1384
+ // Compute from/to if not directly available
1385
+ const from = this._resolveFrom(response, options, currentPage, perPage);
1386
+ const to = this._resolveTo(response, options, currentPage, perPage, total);
1387
+ const prevPageUrl = this._resolve(response, options.prevPageUrl);
1388
+ const nextPageUrl = this._resolve(response, options.nextPageUrl);
1389
+ const firstPageUrl = this._resolve(response, options.firstPageUrl);
1390
+ const lastPageUrl = this._resolve(response, options.lastPageUrl);
1391
+ return new PaginatedCollection(data, currentPage, from, to, total, perPage, prevPageUrl, nextPageUrl, lastPage, firstPageUrl, lastPageUrl);
1392
+ }
1393
+ /**
1394
+ * Resolve a value from a response object using a dot-notation path
1395
+ *
1396
+ * Supports both flat keys ('data') and nested paths ('meta.currentPage').
1397
+ *
1398
+ * @param response - The raw response object
1399
+ * @param path - The dot-notation path to resolve
1400
+ * @returns The resolved value, or undefined if not found
1401
+ */
1402
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1403
+ _resolve(response, path) {
1404
+ return path.split('.').reduce((obj, key) => obj?.[key], response);
1405
+ }
1406
+ /**
1407
+ * Resolve the "from" index value
1408
+ *
1409
+ * If the path resolves to a value in the response, use it.
1410
+ * Otherwise, compute it from currentPage and perPage:
1411
+ * `(currentPage - 1) * perPage + 1`
1412
+ *
1413
+ * @param response - The raw response object
1414
+ * @param options - The response key name configuration
1415
+ * @param currentPage - The current page number
1416
+ * @param perPage - The number of items per page
1417
+ * @returns The computed "from" index
1418
+ */
1419
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1420
+ _resolveFrom(response, options, currentPage, perPage) {
1421
+ const direct = this._resolve(response, options.from);
1422
+ if (direct !== undefined) {
1423
+ return direct;
1424
+ }
1425
+ if (currentPage && perPage) {
1426
+ return (currentPage - 1) * perPage + 1;
1427
+ }
1428
+ return undefined;
1429
+ }
1430
+ /**
1431
+ * Resolve the "to" index value
1432
+ *
1433
+ * If the path resolves to a value in the response, use it.
1434
+ * Otherwise, compute it from currentPage, perPage, and total:
1435
+ * `Math.min(currentPage * perPage, total)`
1436
+ *
1437
+ * @param response - The raw response object
1438
+ * @param options - The response key name configuration
1439
+ * @param currentPage - The current page number
1440
+ * @param perPage - The number of items per page
1441
+ * @param total - The total number of items
1442
+ * @returns The computed "to" index
1443
+ */
1444
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1445
+ _resolveTo(response, options, currentPage, perPage, total) {
1446
+ const direct = this._resolve(response, options.to);
1447
+ if (direct !== undefined) {
1448
+ return direct;
1449
+ }
1450
+ if (currentPage && perPage && total) {
1451
+ return Math.min(currentPage * perPage, total);
1452
+ }
1453
+ return undefined;
1454
+ }
1455
+ }
1456
+
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
+ /**
1464
+ * Request strategy for the Spatie Query Builder driver
1465
+ *
1466
+ * Generates URIs in the Spatie format:
1467
+ * - Fields: `fields[model]=col1,col2`
1468
+ * - Filters: `filter[field]=value`
1469
+ * - Includes: `include=model1,model2`
1470
+ * - Sorts: `sort=-field1,field2` (- prefix = DESC)
1471
+ * - Pagination: `limit=N&page=N`
1472
+ *
1473
+ * @see https://spatie.be/docs/laravel-query-builder
1474
+ */
1475
+ class SpatieRequestStrategy {
1476
+ /**
1477
+ * Accumulator for composing the URI string
1478
+ */
1479
+ _uri = '';
1480
+ /**
1481
+ * Build a URI string from the given state using the Spatie format
1482
+ *
1483
+ * @param state - The current query builder state
1484
+ * @param options - The query parameter key name configuration
1485
+ * @returns The composed URI string
1486
+ * @throws Error if resource is not set
1487
+ */
1488
+ buildUri(state, options) {
1489
+ if (!state.resource) {
1490
+ throw new Error('Set the resource property BEFORE adding filters or calling the url() / get() methods');
1491
+ }
1492
+ this._uri = '';
1493
+ this._parseIncludes(state, options);
1494
+ this._parseFields(state, options);
1495
+ this._parseFilters(state, options);
1496
+ this._parseLimit(state, options);
1497
+ this._parsePage(state, options);
1498
+ this._parseSort(state, options);
1499
+ return this._uri;
1500
+ }
1501
+ /**
1502
+ * Parse and append field selection parameters
1503
+ *
1504
+ * Validates that each field model exists either as the main resource
1505
+ * or in the includes list. Fields are grouped by model in bracket notation.
1506
+ *
1507
+ * @param state - The current query builder state
1508
+ * @param options - The query parameter key name configuration
1509
+ * @returns The generated field selection parameter string
1510
+ * @throws Error if resource is required but not set
1511
+ * @throws UnselectableModelError if a field model is not in resource or includes
1512
+ */
1513
+ _parseFields(state, options) {
1514
+ if (!Object.keys(state.fields).length) {
1515
+ return this._uri;
1516
+ }
1517
+ if (!state.resource) {
1518
+ throw new Error('While selecting fields, the -> resource <- is required');
1519
+ }
1520
+ if (!(state.resource in state.fields)) {
1521
+ throw new Error(`Key ${state.resource} is missing in the fields object`);
1522
+ }
1523
+ const f = {};
1524
+ for (const k in state.fields) {
1525
+ if (state.fields.hasOwnProperty(k)) {
1526
+ if (k !== state.resource && !state.includes.includes(k)) {
1527
+ throw new UnselectableModelError(k);
1528
+ }
1529
+ Object.assign(f, { [`${options.fields}[${k}]`]: state.fields[k].join(',') });
1530
+ }
1531
+ }
1532
+ const param = `${this._prepend(state)}${qs.stringify(f, { encode: false })}`;
1533
+ this._uri += param;
1534
+ return param;
1535
+ }
1536
+ /**
1537
+ * Parse and append filter parameters
1538
+ *
1539
+ * Generates filter parameters in bracket notation: `filter[key]=value1,value2`
1540
+ *
1541
+ * @param state - The current query builder state
1542
+ * @param options - The query parameter key name configuration
1543
+ * @returns The generated filter parameter string
1544
+ */
1545
+ _parseFilters(state, options) {
1546
+ const keys = Object.keys(state.filters);
1547
+ if (!keys.length) {
1548
+ return this._uri;
1549
+ }
1550
+ const f = {
1551
+ [`${options.filters}`]: keys.reduce((acc, key) => {
1552
+ return Object.assign(acc, { [key]: state.filters[key].join(',') });
1553
+ }, {})
1554
+ };
1555
+ const param = `${this._prepend(state)}${qs.stringify(f, { encode: false })}`;
1556
+ this._uri += param;
1557
+ return param;
1558
+ }
1559
+ /**
1560
+ * Parse and append include parameters
1561
+ *
1562
+ * Generates: `include=model1,model2`
1563
+ *
1564
+ * @param state - The current query builder state
1565
+ * @param options - The query parameter key name configuration
1566
+ * @returns The generated include parameter string
1567
+ */
1568
+ _parseIncludes(state, options) {
1569
+ if (!state.includes.length) {
1570
+ return this._uri;
1571
+ }
1572
+ const param = `${this._prepend(state)}${options.includes}=${state.includes}`;
1573
+ this._uri += param;
1574
+ return param;
1575
+ }
1576
+ /**
1577
+ * Parse and append the limit parameter
1578
+ *
1579
+ * @param state - The current query builder state
1580
+ * @param options - The query parameter key name configuration
1581
+ * @returns The generated limit parameter string
1582
+ */
1583
+ _parseLimit(state, options) {
1584
+ const param = `${this._prepend(state)}${options.limit}=${state.limit}`;
1585
+ this._uri += param;
1586
+ return param;
1587
+ }
1588
+ /**
1589
+ * Parse and append the page parameter
1590
+ *
1591
+ * @param state - The current query builder state
1592
+ * @param options - The query parameter key name configuration
1593
+ * @returns The generated page parameter string
1594
+ */
1595
+ _parsePage(state, options) {
1596
+ const param = `${this._prepend(state)}${options.page}=${state.page}`;
1597
+ this._uri += param;
1598
+ return param;
1599
+ }
1600
+ /**
1601
+ * Parse and append sort parameters
1602
+ *
1603
+ * Generates: `sort=-field1,field2` where `-` prefix indicates DESC order
1604
+ *
1605
+ * @param state - The current query builder state
1606
+ * @param options - The query parameter key name configuration
1607
+ * @returns The generated sort parameter string
1608
+ */
1609
+ _parseSort(state, options) {
1610
+ let param = '';
1611
+ if (!state.sorts.length) {
1612
+ return param;
1613
+ }
1614
+ param = `${this._prepend(state)}${options.sort}=`;
1615
+ state.sorts.forEach((sort, idx) => {
1616
+ param += `${sort.order === SortEnum.DESC ? '-' : ''}${sort.field}`;
1617
+ if (idx < state.sorts.length - 1) {
1618
+ param += ',';
1619
+ }
1620
+ });
1621
+ this._uri += param;
1622
+ return param;
1623
+ }
1624
+ /**
1625
+ * Determine the appropriate URI prefix based on the current accumulator state
1626
+ *
1627
+ * Returns the full base path with `?` for the first parameter,
1628
+ * or `&` for subsequent parameters.
1629
+ *
1630
+ * @param state - The current query builder state
1631
+ * @returns The prefix string to prepend to the next parameter
1632
+ */
1633
+ _prepend(state) {
1634
+ if (this._uri) {
1635
+ return '&';
1636
+ }
1637
+ return state.baseUrl ? `${state.baseUrl}/${state.resource}?` : `/${state.resource}?`;
1638
+ }
1639
+ }
1640
+
1641
+ /**
1642
+ * Response strategy for the Spatie Query Builder driver
1643
+ *
1644
+ * Parses flat Laravel pagination responses:
1645
+ * ```json
1646
+ * {
1647
+ * "data": [...],
1648
+ * "current_page": 1,
1649
+ * "total": 100,
1650
+ * "per_page": 15,
1651
+ * "from": 1,
1652
+ * "to": 15,
1653
+ * ...
1654
+ * }
1655
+ * ```
1656
+ *
1657
+ * @see https://spatie.be/docs/laravel-query-builder
1658
+ */
1659
+ class SpatieResponseStrategy {
1660
+ /**
1661
+ * Parse a flat Laravel pagination response into a PaginatedCollection
1662
+ *
1663
+ * @param response - The raw API response object
1664
+ * @param options - The response key name configuration
1665
+ * @returns A typed PaginatedCollection instance
1666
+ */
1667
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1668
+ paginate(response, options) {
1669
+ return new PaginatedCollection(response[options.data], response[options.currentPage], response[options.from], response[options.to], response[options.total], response[options.perPage], response[options.prevPageUrl], response[options.nextPageUrl], response[options.lastPage], response[options.firstPageUrl], response[options.lastPageUrl]);
1670
+ }
1671
+ }
1672
+
1673
+ /**
1674
+ * Resolve the request strategy instance for the given driver
1675
+ *
1676
+ * @param driver - The pagination driver
1677
+ * @returns The corresponding request strategy
1678
+ */
1679
+ function resolveRequestStrategy$1(driver) {
1680
+ switch (driver) {
1681
+ case DriverEnum.NESTJS:
1682
+ return new NestjsRequestStrategy();
1683
+ case DriverEnum.SPATIE:
1684
+ return new SpatieRequestStrategy();
1685
+ case DriverEnum.LARAVEL:
1686
+ return new LaravelRequestStrategy();
1687
+ }
1688
+ }
1689
+ /**
1690
+ * Resolve the response strategy instance for the given driver
1691
+ *
1692
+ * @param driver - The pagination driver
1693
+ * @returns The corresponding response strategy
1694
+ */
1695
+ function resolveResponseStrategy$1(driver) {
1696
+ switch (driver) {
1697
+ case DriverEnum.NESTJS:
1698
+ return new NestjsResponseStrategy();
1699
+ case DriverEnum.SPATIE:
1700
+ return new SpatieResponseStrategy();
1701
+ case DriverEnum.LARAVEL:
1702
+ return new LaravelResponseStrategy();
1703
+ }
1704
+ }
788
1705
  // @dynamic
789
1706
  class NgQubeeModule {
790
- static forRoot(config = {}) {
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);
791
1717
  return {
792
1718
  ngModule: NgQubeeModule,
793
1719
  providers: [
@@ -795,52 +1721,99 @@ class NgQubeeModule {
795
1721
  {
796
1722
  deps: [NestService],
797
1723
  provide: NgQubeeService,
798
- useFactory: (nestService) => new NgQubeeService(nestService, Object.assign({}, config.request))
799
- }, {
1724
+ useFactory: (nestService) => new NgQubeeService(nestService, requestStrategy, driver, Object.assign({}, config.request))
1725
+ },
1726
+ {
800
1727
  provide: PaginationService,
801
- useFactory: () => new PaginationService(Object.assign({}, config.response))
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
+ }
802
1734
  }
803
1735
  ]
804
1736
  };
805
1737
  }
806
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgQubeeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
807
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.5", ngImport: i0, type: NgQubeeModule });
808
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgQubeeModule, providers: [{
809
- deps: [NestService],
810
- provide: NgQubeeService,
811
- useFactory: (nestService) => new NgQubeeService(nestService, {})
812
- }] });
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 });
813
1741
  }
814
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.5", ngImport: i0, type: NgQubeeModule, decorators: [{
1742
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule, decorators: [{
815
1743
  type: NgModule,
816
- args: [{
817
- providers: [{
818
- deps: [NestService],
819
- provide: NgQubeeService,
820
- useFactory: (nestService) => new NgQubeeService(nestService, {})
821
- }]
822
- }]
1744
+ args: [{}]
823
1745
  }] });
824
1746
 
1747
+ /**
1748
+ * Resolve the request strategy instance for the given driver
1749
+ *
1750
+ * @param driver - The pagination driver
1751
+ * @returns The corresponding request strategy
1752
+ */
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();
1761
+ }
1762
+ }
1763
+ /**
1764
+ * Resolve the response strategy instance for the given driver
1765
+ *
1766
+ * @param driver - The pagination driver
1767
+ * @returns The corresponding response strategy
1768
+ */
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
+ }
1778
+ }
825
1779
  /**
826
1780
  * Sets up providers necessary to enable `NgQubee` functionality for the application.
827
1781
  *
828
1782
  * @usageNotes
829
1783
  *
830
- * Basic example of how you can add NgQubee to your application:
1784
+ * Basic example with the Laravel driver:
831
1785
  * ```
832
- * const config = {};
1786
+ * bootstrapApplication(AppComponent, {
1787
+ * providers: [provideNgQubee({ driver: DriverEnum.LARAVEL })]
1788
+ * });
1789
+ * ```
1790
+ *
1791
+ * Spatie driver example:
1792
+ * ```
1793
+ * import { DriverEnum } from 'ng-qubee';
833
1794
  *
834
1795
  * bootstrapApplication(AppComponent, {
835
- * providers: [provideNgQubee(config)]
1796
+ * providers: [provideNgQubee({ driver: DriverEnum.SPATIE })]
1797
+ * });
1798
+ * ```
1799
+ *
1800
+ * NestJS driver example:
1801
+ * ```
1802
+ * import { DriverEnum } from 'ng-qubee';
1803
+ *
1804
+ * bootstrapApplication(AppComponent, {
1805
+ * providers: [provideNgQubee({ driver: DriverEnum.NESTJS })]
836
1806
  * });
837
1807
  * ```
838
1808
  *
839
1809
  * @publicApi
840
- * @param config Configuration object compliant to the IConfig interface
1810
+ * @param config - Configuration object compliant to the IConfig interface
841
1811
  * @returns A set of providers to setup NgQubee
842
1812
  */
843
- function provideNgQubee(config = {}) {
1813
+ function provideNgQubee(config) {
1814
+ const driver = config.driver;
1815
+ const requestStrategy = resolveRequestStrategy(driver);
1816
+ const responseStrategy = resolveResponseStrategy(driver);
844
1817
  return makeEnvironmentProviders([
845
1818
  {
846
1819
  provide: NestService,
@@ -849,14 +1822,44 @@ function provideNgQubee(config = {}) {
849
1822
  {
850
1823
  deps: [NestService],
851
1824
  provide: NgQubeeService,
852
- useFactory: (nestService) => new NgQubeeService(nestService, Object.assign({}, config.request))
853
- }, {
1825
+ useFactory: (nestService) => new NgQubeeService(nestService, requestStrategy, driver, Object.assign({}, config.request))
1826
+ },
1827
+ {
854
1828
  provide: PaginationService,
855
- useFactory: () => new PaginationService(Object.assign({}, config.response))
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
+ }
856
1835
  }
857
1836
  ]);
858
1837
  }
859
1838
 
1839
+ /**
1840
+ * Enum representing the available filter operators for the NestJS driver
1841
+ *
1842
+ * These operators map to the nestjs-paginate filter syntax:
1843
+ * `filter.field=$operator:value`
1844
+ *
1845
+ * @see https://github.com/ppetzold/nestjs-paginate
1846
+ */
1847
+ var FilterOperatorEnum;
1848
+ (function (FilterOperatorEnum) {
1849
+ FilterOperatorEnum["BTW"] = "$btw";
1850
+ FilterOperatorEnum["CONTAINS"] = "$contains";
1851
+ FilterOperatorEnum["EQ"] = "$eq";
1852
+ FilterOperatorEnum["GT"] = "$gt";
1853
+ FilterOperatorEnum["GTE"] = "$gte";
1854
+ FilterOperatorEnum["ILIKE"] = "$ilike";
1855
+ FilterOperatorEnum["IN"] = "$in";
1856
+ FilterOperatorEnum["LT"] = "$lt";
1857
+ FilterOperatorEnum["LTE"] = "$lte";
1858
+ FilterOperatorEnum["NOT"] = "$not";
1859
+ FilterOperatorEnum["NULL"] = "$null";
1860
+ FilterOperatorEnum["SW"] = "$sw";
1861
+ })(FilterOperatorEnum || (FilterOperatorEnum = {}));
1862
+
860
1863
  /*
861
1864
  * Public API Surface of angular-query-builder
862
1865
  */
@@ -865,5 +1868,5 @@ function provideNgQubee(config = {}) {
865
1868
  * Generated bundle index. Do not edit.
866
1869
  */
867
1870
 
868
- export { InvalidLimitError, InvalidModelNameError, InvalidPageNumberError, KeyNotFoundError, NgQubeeModule, NgQubeeService, PaginatedCollection, PaginationService, UnselectableModelError, provideNgQubee };
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 };
869
1872
  //# sourceMappingURL=ng-qubee.mjs.map