ng-qubee 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -0
- package/fesm2022/ng-qubee.mjs +1017 -759
- package/fesm2022/ng-qubee.mjs.map +1 -1
- package/package.json +1 -2
- package/types/ng-qubee.d.ts +221 -5
package/fesm2022/ng-qubee.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable,
|
|
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
|
|
|
@@ -80,6 +80,37 @@ var DriverEnum;
|
|
|
80
80
|
DriverEnum["SPATIE"] = "spatie";
|
|
81
81
|
})(DriverEnum || (DriverEnum = {}));
|
|
82
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
|
+
|
|
83
114
|
/**
|
|
84
115
|
* Resolved response field key names with defaults applied
|
|
85
116
|
*
|
|
@@ -172,185 +203,666 @@ class NestjsResponseOptions extends ResponseOptions {
|
|
|
172
203
|
}
|
|
173
204
|
|
|
174
205
|
/**
|
|
175
|
-
* Error thrown when
|
|
176
|
-
*
|
|
177
|
-
* Per-model field selection is only supported by the Spatie driver.
|
|
178
|
-
* Use `addSelect()` for NestJS flat field selection.
|
|
179
|
-
*/
|
|
180
|
-
class UnsupportedFieldSelectionError extends Error {
|
|
181
|
-
constructor() {
|
|
182
|
-
super('Per-model field selection is only supported by the Spatie driver. Use addSelect() for NestJS.');
|
|
183
|
-
this.name = 'UnsupportedFieldSelectionError';
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Error thrown when filters are attempted with a driver that does not support them
|
|
189
|
-
*
|
|
190
|
-
* Filters are only supported by the Spatie and NestJS drivers.
|
|
191
|
-
*/
|
|
192
|
-
class UnsupportedFilterError extends Error {
|
|
193
|
-
constructor() {
|
|
194
|
-
super('Filters are only supported by the Spatie and NestJS drivers.');
|
|
195
|
-
this.name = 'UnsupportedFilterError';
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Error thrown when filter operators are attempted with a driver that does not support them
|
|
201
|
-
*
|
|
202
|
-
* Filter operators are only supported by the NestJS driver.
|
|
203
|
-
* Use `addFilter()` for Spatie implicit equality filters.
|
|
204
|
-
*/
|
|
205
|
-
class UnsupportedFilterOperatorError extends Error {
|
|
206
|
-
constructor() {
|
|
207
|
-
super('Filter operators are only supported by the NestJS driver. Use addFilter() for Spatie.');
|
|
208
|
-
this.name = 'UnsupportedFilterOperatorError';
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Error thrown when includes are attempted with a driver that does not support them
|
|
214
|
-
*
|
|
215
|
-
* Includes are only supported by the Spatie driver.
|
|
216
|
-
*/
|
|
217
|
-
class UnsupportedIncludesError extends Error {
|
|
218
|
-
constructor() {
|
|
219
|
-
super('Includes are only supported by the Spatie driver.');
|
|
220
|
-
this.name = 'UnsupportedIncludesError';
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Error thrown when search is attempted with a driver that does not support it
|
|
226
|
-
*
|
|
227
|
-
* Search is only supported by the NestJS driver.
|
|
228
|
-
*/
|
|
229
|
-
class UnsupportedSearchError extends Error {
|
|
230
|
-
constructor() {
|
|
231
|
-
super('Search is only supported by the NestJS driver.');
|
|
232
|
-
this.name = 'UnsupportedSearchError';
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Error thrown when flat field selection is attempted with a driver that does not support it
|
|
238
|
-
*
|
|
239
|
-
* Flat field selection is only supported by the NestJS driver.
|
|
240
|
-
* Use `addFields()` for Spatie per-model field selection.
|
|
241
|
-
*/
|
|
242
|
-
class UnsupportedSelectError extends Error {
|
|
243
|
-
constructor() {
|
|
244
|
-
super('Flat field selection is only supported by the NestJS driver. Use addFields() for Spatie.');
|
|
245
|
-
this.name = 'UnsupportedSelectError';
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Error thrown when sorts are attempted with a driver that does not support them
|
|
206
|
+
* Error thrown when an invalid resource name is provided
|
|
251
207
|
*
|
|
252
|
-
*
|
|
208
|
+
* Resource name must be a non-empty string.
|
|
253
209
|
*/
|
|
254
|
-
class
|
|
255
|
-
constructor() {
|
|
256
|
-
super(
|
|
257
|
-
this.name = '
|
|
210
|
+
class InvalidResourceNameError extends Error {
|
|
211
|
+
constructor(resource) {
|
|
212
|
+
super(`Invalid resource name: Resource name must be a non-empty string. Received: ${JSON.stringify(resource)}`);
|
|
213
|
+
this.name = 'InvalidResourceNameError';
|
|
258
214
|
}
|
|
259
215
|
}
|
|
260
216
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
* used in the generated URI. Unset values fall back to defaults.
|
|
266
|
-
*/
|
|
267
|
-
class QueryBuilderOptions {
|
|
268
|
-
appends;
|
|
269
|
-
fields;
|
|
270
|
-
filters;
|
|
271
|
-
includes;
|
|
272
|
-
limit;
|
|
273
|
-
page;
|
|
274
|
-
search;
|
|
275
|
-
select;
|
|
276
|
-
sort;
|
|
277
|
-
sortBy;
|
|
278
|
-
constructor(options) {
|
|
279
|
-
this.appends = options.appends || 'append';
|
|
280
|
-
this.fields = options.fields || 'fields';
|
|
281
|
-
this.filters = options.filters || 'filter';
|
|
282
|
-
this.includes = options.includes || 'include';
|
|
283
|
-
this.limit = options.limit || 'limit';
|
|
284
|
-
this.page = options.page || 'page';
|
|
285
|
-
this.search = options.search || 'search';
|
|
286
|
-
this.select = options.select || 'select';
|
|
287
|
-
this.sort = options.sort || 'sort';
|
|
288
|
-
this.sortBy = options.sortBy || 'sortBy';
|
|
217
|
+
class InvalidPageNumberError extends Error {
|
|
218
|
+
constructor(page) {
|
|
219
|
+
super(`Invalid page number: Page must be a positive integer greater than 0. Received: ${page}`);
|
|
220
|
+
this.name = 'InvalidPageNumberError';
|
|
289
221
|
}
|
|
290
222
|
}
|
|
291
223
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
224
|
+
const INITIAL_STATE = {
|
|
225
|
+
baseUrl: '',
|
|
226
|
+
fields: {},
|
|
227
|
+
filters: {},
|
|
228
|
+
includes: [],
|
|
229
|
+
isLastPageKnown: false,
|
|
230
|
+
lastPage: 1,
|
|
231
|
+
limit: 15,
|
|
232
|
+
operatorFilters: [],
|
|
233
|
+
page: 1,
|
|
234
|
+
resource: '',
|
|
235
|
+
search: '',
|
|
236
|
+
select: [],
|
|
237
|
+
sorts: []
|
|
238
|
+
};
|
|
239
|
+
class NestService {
|
|
302
240
|
/**
|
|
303
|
-
*
|
|
241
|
+
* Private writable signal that holds the Query Builder state
|
|
242
|
+
*
|
|
243
|
+
* @type {IQueryBuilderState}
|
|
304
244
|
*/
|
|
305
|
-
|
|
245
|
+
_nest = signal(this._clone(INITIAL_STATE), ...(ngDevMode ? [{ debugName: "_nest" }] : []));
|
|
306
246
|
/**
|
|
307
|
-
*
|
|
247
|
+
* A computed signal that makes readonly the writable signal _nest
|
|
248
|
+
*
|
|
249
|
+
* @type {Signal<IQueryBuilderState>}
|
|
308
250
|
*/
|
|
309
|
-
|
|
251
|
+
nest = computed(() => this._clone(this._nest()), ...(ngDevMode ? [{ debugName: "nest" }] : []));
|
|
252
|
+
constructor() {
|
|
253
|
+
// Nothing to see here
|
|
254
|
+
}
|
|
310
255
|
/**
|
|
311
|
-
*
|
|
256
|
+
* Set the base URL for the API
|
|
257
|
+
*
|
|
258
|
+
* @param {string} baseUrl - The base URL to prepend to generated URIs
|
|
259
|
+
* @example
|
|
260
|
+
* service.baseUrl = 'https://api.example.com';
|
|
312
261
|
*/
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
this._requestStrategy = requestStrategy;
|
|
262
|
+
set baseUrl(baseUrl) {
|
|
263
|
+
this._nest.update(nest => ({
|
|
264
|
+
...nest,
|
|
265
|
+
baseUrl
|
|
266
|
+
}));
|
|
319
267
|
}
|
|
320
268
|
/**
|
|
321
|
-
*
|
|
269
|
+
* Set the limit for paginated results
|
|
322
270
|
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
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").
|
|
275
|
+
*
|
|
276
|
+
* @param {number} limit - The number of items per page
|
|
277
|
+
* @example
|
|
278
|
+
* service.limit = 25;
|
|
326
279
|
*/
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
280
|
+
set limit(limit) {
|
|
281
|
+
this._nest.update(nest => ({
|
|
282
|
+
...nest,
|
|
283
|
+
limit
|
|
284
|
+
}));
|
|
331
285
|
}
|
|
332
286
|
/**
|
|
333
|
-
*
|
|
287
|
+
* Set the page number for pagination
|
|
288
|
+
* Must be a positive integer greater than 0
|
|
334
289
|
*
|
|
335
|
-
* @param
|
|
336
|
-
* @
|
|
337
|
-
* @
|
|
338
|
-
*
|
|
290
|
+
* @param {number} page - The page number to fetch
|
|
291
|
+
* @throws {InvalidPageNumberError} If page is not a positive integer
|
|
292
|
+
* @example
|
|
293
|
+
* service.page = 2;
|
|
339
294
|
*/
|
|
340
|
-
|
|
341
|
-
this.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return this;
|
|
295
|
+
set page(page) {
|
|
296
|
+
this._validatePageNumber(page);
|
|
297
|
+
this._nest.update(nest => ({
|
|
298
|
+
...nest,
|
|
299
|
+
page
|
|
300
|
+
}));
|
|
347
301
|
}
|
|
348
302
|
/**
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* Produces: `filter[field]=value` (JSON:API / Spatie) or `filter.field=value` (NestJS)
|
|
303
|
+
* Set the resource name for the query
|
|
304
|
+
* Must be a non-empty string
|
|
352
305
|
*
|
|
353
|
-
* @param {string}
|
|
306
|
+
* @param {string} resource - The API resource name (e.g., 'users', 'posts')
|
|
307
|
+
* @throws {InvalidResourceNameError} If resource is not a non-empty string
|
|
308
|
+
* @example
|
|
309
|
+
* service.resource = 'users';
|
|
310
|
+
*/
|
|
311
|
+
set resource(resource) {
|
|
312
|
+
this._validateResourceName(resource);
|
|
313
|
+
this._nest.update(nest => ({
|
|
314
|
+
...nest,
|
|
315
|
+
resource
|
|
316
|
+
}));
|
|
317
|
+
}
|
|
318
|
+
_clone(obj) {
|
|
319
|
+
return JSON.parse(JSON.stringify(obj));
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Validates that the page number is a positive integer
|
|
323
|
+
*
|
|
324
|
+
* @param {number} page - The page number to validate
|
|
325
|
+
* @throws {InvalidPageNumberError} If page is not a positive integer
|
|
326
|
+
* @private
|
|
327
|
+
*/
|
|
328
|
+
_validatePageNumber(page) {
|
|
329
|
+
if (!Number.isInteger(page) || page < 1) {
|
|
330
|
+
throw new InvalidPageNumberError(page);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Validates that the resource name is a non-empty string
|
|
335
|
+
*
|
|
336
|
+
* @param {string} resource - The resource name to validate
|
|
337
|
+
* @throws {InvalidResourceNameError} If resource is not a non-empty string
|
|
338
|
+
* @private
|
|
339
|
+
*/
|
|
340
|
+
_validateResourceName(resource) {
|
|
341
|
+
if (!resource || typeof resource !== 'string' || resource.trim().length === 0) {
|
|
342
|
+
throw new InvalidResourceNameError(resource);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Add selectable fields for the given model to the request
|
|
347
|
+
* Automatically prevents duplicate fields for each model
|
|
348
|
+
*
|
|
349
|
+
* @param {IFields} fields - Object mapping model names to arrays of field names
|
|
350
|
+
* @return {void}
|
|
351
|
+
* @example
|
|
352
|
+
* service.addFields({ users: ['id', 'email', 'username'] });
|
|
353
|
+
* service.addFields({ posts: ['title', 'content'] });
|
|
354
|
+
*/
|
|
355
|
+
addFields(fields) {
|
|
356
|
+
this._nest.update(nest => {
|
|
357
|
+
const mergedFields = { ...nest.fields };
|
|
358
|
+
Object.keys(fields).forEach(model => {
|
|
359
|
+
const existingFields = mergedFields[model] || [];
|
|
360
|
+
const newFields = fields[model];
|
|
361
|
+
// Use Set to prevent duplicates
|
|
362
|
+
const uniqueFields = Array.from(new Set([...existingFields, ...newFields]));
|
|
363
|
+
mergedFields[model] = uniqueFields;
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
...nest,
|
|
367
|
+
fields: mergedFields
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Add filters to the request
|
|
373
|
+
* Automatically prevents duplicate filter values for each filter key
|
|
374
|
+
*
|
|
375
|
+
* @param {IFilters} filters - Object mapping filter keys to arrays of values
|
|
376
|
+
* @return {void}
|
|
377
|
+
* @example
|
|
378
|
+
* service.addFilters({ id: [1, 2, 3] });
|
|
379
|
+
* service.addFilters({ status: ['active', 'pending'] });
|
|
380
|
+
*/
|
|
381
|
+
addFilters(filters) {
|
|
382
|
+
this._nest.update(nest => {
|
|
383
|
+
const mergedFilters = { ...nest.filters };
|
|
384
|
+
Object.keys(filters).forEach(key => {
|
|
385
|
+
const existingValues = mergedFilters[key] || [];
|
|
386
|
+
const newValues = filters[key];
|
|
387
|
+
// Use Set to prevent duplicates
|
|
388
|
+
const uniqueValues = Array.from(new Set([...existingValues, ...newValues]));
|
|
389
|
+
mergedFilters[key] = uniqueValues;
|
|
390
|
+
});
|
|
391
|
+
return {
|
|
392
|
+
...nest,
|
|
393
|
+
filters: mergedFilters
|
|
394
|
+
};
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Add resources to include with the request
|
|
399
|
+
* Automatically prevents duplicate includes
|
|
400
|
+
*
|
|
401
|
+
* @param {string[]} includes - Array of resource names to include in the response
|
|
402
|
+
* @return {void}
|
|
403
|
+
* @example
|
|
404
|
+
* service.addIncludes(['profile', 'posts']);
|
|
405
|
+
* service.addIncludes(['comments']);
|
|
406
|
+
*/
|
|
407
|
+
addIncludes(includes) {
|
|
408
|
+
this._nest.update(nest => {
|
|
409
|
+
// Use Set to prevent duplicates
|
|
410
|
+
const uniqueIncludes = Array.from(new Set([...nest.includes, ...includes]));
|
|
411
|
+
return {
|
|
412
|
+
...nest,
|
|
413
|
+
includes: uniqueIncludes
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Add filters with explicit operators (NestJS only)
|
|
419
|
+
* Automatically prevents duplicate operator filters for the same field + operator combination
|
|
420
|
+
*
|
|
421
|
+
* @param {IOperatorFilter[]} filters - Array of operator filter configurations
|
|
422
|
+
* @return {void}
|
|
423
|
+
* @example
|
|
424
|
+
* import { FilterOperatorEnum } from 'ng-qubee';
|
|
425
|
+
* service.addOperatorFilters([{ field: 'age', operator: FilterOperatorEnum.GTE, values: [18] }]);
|
|
426
|
+
*/
|
|
427
|
+
addOperatorFilters(filters) {
|
|
428
|
+
this._nest.update(nest => {
|
|
429
|
+
const merged = [...nest.operatorFilters];
|
|
430
|
+
filters.forEach(newFilter => {
|
|
431
|
+
const existingIdx = merged.findIndex(f => f.field === newFilter.field && f.operator === newFilter.operator);
|
|
432
|
+
if (existingIdx > -1) {
|
|
433
|
+
const existingValues = merged[existingIdx].values;
|
|
434
|
+
merged[existingIdx] = {
|
|
435
|
+
...merged[existingIdx],
|
|
436
|
+
values: Array.from(new Set([...existingValues, ...newFilter.values]))
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
merged.push({ ...newFilter });
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
return {
|
|
444
|
+
...nest,
|
|
445
|
+
operatorFilters: merged
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Add flat field selection columns (NestJS only)
|
|
451
|
+
* Automatically prevents duplicate select fields
|
|
452
|
+
*
|
|
453
|
+
* @param {string[]} fields - Array of column names to select
|
|
454
|
+
* @return {void}
|
|
455
|
+
* @example
|
|
456
|
+
* service.addSelect(['id', 'name', 'email']);
|
|
457
|
+
*/
|
|
458
|
+
addSelect(fields) {
|
|
459
|
+
this._nest.update(nest => {
|
|
460
|
+
const uniqueSelect = Array.from(new Set([...nest.select, ...fields]));
|
|
461
|
+
return {
|
|
462
|
+
...nest,
|
|
463
|
+
select: uniqueSelect
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Add a field that should be used for sorting data
|
|
469
|
+
*
|
|
470
|
+
* @param {ISort} sort - Sort configuration with field name and order (ASC/DESC)
|
|
471
|
+
* @return {void}
|
|
472
|
+
* @example
|
|
473
|
+
* import { SortEnum } from 'ng-qubee';
|
|
474
|
+
* service.addSort({ field: 'created_at', order: SortEnum.DESC });
|
|
475
|
+
* service.addSort({ field: 'name', order: SortEnum.ASC });
|
|
476
|
+
*/
|
|
477
|
+
addSort(sort) {
|
|
478
|
+
this._nest.update(nest => ({
|
|
479
|
+
...nest,
|
|
480
|
+
sorts: [...nest.sorts, sort]
|
|
481
|
+
}));
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Remove fields for the given model
|
|
485
|
+
* Uses deep cloning to prevent mutations to the original state
|
|
486
|
+
*
|
|
487
|
+
* @param {IFields} fields - Object mapping model names to arrays of field names to remove
|
|
488
|
+
* @return {void}
|
|
489
|
+
* @example
|
|
490
|
+
* service.deleteFields({ users: ['email'] });
|
|
491
|
+
* service.deleteFields({ posts: ['content', 'body'] });
|
|
492
|
+
*/
|
|
493
|
+
deleteFields(fields) {
|
|
494
|
+
// Deep clone the fields object to prevent mutations
|
|
495
|
+
const f = this._clone(this._nest().fields);
|
|
496
|
+
Object.keys(fields).forEach(k => {
|
|
497
|
+
if (!(k in f)) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
f[k] = f[k].filter(v => !fields[k].includes(v));
|
|
501
|
+
});
|
|
502
|
+
this._nest.update(nest => ({
|
|
503
|
+
...nest,
|
|
504
|
+
fields: f
|
|
505
|
+
}));
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Remove filters from the request
|
|
509
|
+
* Uses deep cloning to prevent mutations to the original state
|
|
510
|
+
*
|
|
511
|
+
* @param {...string[]} filters - Filter keys to remove
|
|
512
|
+
* @return {void}
|
|
513
|
+
* @example
|
|
514
|
+
* service.deleteFilters('id');
|
|
515
|
+
* service.deleteFilters('status', 'type');
|
|
516
|
+
*/
|
|
517
|
+
deleteFilters(...filters) {
|
|
518
|
+
// Deep clone the filters object to prevent mutations
|
|
519
|
+
const f = this._clone(this._nest().filters);
|
|
520
|
+
filters.forEach(k => delete f[k]);
|
|
521
|
+
this._nest.update(nest => ({
|
|
522
|
+
...nest,
|
|
523
|
+
filters: f
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Remove includes from the request
|
|
528
|
+
*
|
|
529
|
+
* @param {...string[]} includes - Include names to remove
|
|
530
|
+
* @return {void}
|
|
531
|
+
* @example
|
|
532
|
+
* service.deleteIncludes('profile');
|
|
533
|
+
* service.deleteIncludes('posts', 'comments');
|
|
534
|
+
*/
|
|
535
|
+
deleteIncludes(...includes) {
|
|
536
|
+
this._nest.update(nest => ({
|
|
537
|
+
...nest,
|
|
538
|
+
includes: nest.includes.filter(v => !includes.includes(v))
|
|
539
|
+
}));
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Remove operator filters by field name (NestJS only)
|
|
543
|
+
*
|
|
544
|
+
* @param {...string[]} fields - Field names of operator filters to remove
|
|
545
|
+
* @return {void}
|
|
546
|
+
* @example
|
|
547
|
+
* service.deleteOperatorFilters('age');
|
|
548
|
+
* service.deleteOperatorFilters('price', 'quantity');
|
|
549
|
+
*/
|
|
550
|
+
deleteOperatorFilters(...fields) {
|
|
551
|
+
this._nest.update(nest => ({
|
|
552
|
+
...nest,
|
|
553
|
+
operatorFilters: nest.operatorFilters.filter(f => !fields.includes(f.field))
|
|
554
|
+
}));
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Remove the search term from the state (NestJS only)
|
|
558
|
+
*
|
|
559
|
+
* @return {void}
|
|
560
|
+
* @example
|
|
561
|
+
* service.deleteSearch();
|
|
562
|
+
*/
|
|
563
|
+
deleteSearch() {
|
|
564
|
+
this._nest.update(nest => ({
|
|
565
|
+
...nest,
|
|
566
|
+
search: ''
|
|
567
|
+
}));
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Remove flat field selections from the state (NestJS only)
|
|
571
|
+
*
|
|
572
|
+
* @param {...string[]} fields - Field names to remove from selection
|
|
573
|
+
* @return {void}
|
|
574
|
+
* @example
|
|
575
|
+
* service.deleteSelect('email');
|
|
576
|
+
* service.deleteSelect('name', 'email');
|
|
577
|
+
*/
|
|
578
|
+
deleteSelect(...fields) {
|
|
579
|
+
this._nest.update(nest => ({
|
|
580
|
+
...nest,
|
|
581
|
+
select: nest.select.filter(f => !fields.includes(f))
|
|
582
|
+
}));
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Remove sorts from the request by field name
|
|
586
|
+
*
|
|
587
|
+
* @param {...string[]} sorts - Field names of sorts to remove
|
|
588
|
+
* @return {void}
|
|
589
|
+
* @example
|
|
590
|
+
* service.deleteSorts('created_at');
|
|
591
|
+
* service.deleteSorts('name', 'created_at');
|
|
592
|
+
*/
|
|
593
|
+
deleteSorts(...sorts) {
|
|
594
|
+
const s = [...this._nest().sorts];
|
|
595
|
+
sorts.forEach(field => {
|
|
596
|
+
const p = s.findIndex(sort => sort.field === field);
|
|
597
|
+
if (p > -1) {
|
|
598
|
+
s.splice(p, 1);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
this._nest.update(nest => ({
|
|
602
|
+
...nest,
|
|
603
|
+
sorts: s
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Set the full-text search term (NestJS only)
|
|
608
|
+
*
|
|
609
|
+
* @param {string} search - The search term
|
|
610
|
+
* @return {void}
|
|
611
|
+
* @example
|
|
612
|
+
* service.setSearch('john doe');
|
|
613
|
+
*/
|
|
614
|
+
setSearch(search) {
|
|
615
|
+
this._nest.update(nest => ({
|
|
616
|
+
...nest,
|
|
617
|
+
search
|
|
618
|
+
}));
|
|
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
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Reset the query builder state to initial values
|
|
641
|
+
* Clears all fields, filters, includes, sorts, and resets pagination
|
|
642
|
+
*
|
|
643
|
+
* @return {void}
|
|
644
|
+
* @example
|
|
645
|
+
* service.reset();
|
|
646
|
+
*/
|
|
647
|
+
reset() {
|
|
648
|
+
this._nest.update(_ => this._clone(INITIAL_STATE));
|
|
649
|
+
}
|
|
650
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NestService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
651
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NestService });
|
|
652
|
+
}
|
|
653
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NestService, decorators: [{
|
|
654
|
+
type: Injectable
|
|
655
|
+
}], ctorParameters: () => [] });
|
|
656
|
+
|
|
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 {
|
|
667
|
+
/**
|
|
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.
|
|
671
|
+
*/
|
|
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';
|
|
675
|
+
}
|
|
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';
|
|
688
|
+
}
|
|
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
|
|
354
866
|
* @param {(string | number | boolean)[]} values - The needle(s)
|
|
355
867
|
* @returns {this}
|
|
356
868
|
* @throws {UnsupportedFilterError} If the active driver does not support filters
|
|
@@ -363,6 +875,7 @@ class NgQubeeService {
|
|
|
363
875
|
this._nestService.addFilters({
|
|
364
876
|
[field]: values
|
|
365
877
|
});
|
|
878
|
+
this._nestService.page = 1;
|
|
366
879
|
return this;
|
|
367
880
|
}
|
|
368
881
|
/**
|
|
@@ -382,6 +895,7 @@ class NgQubeeService {
|
|
|
382
895
|
return this;
|
|
383
896
|
}
|
|
384
897
|
this._nestService.addOperatorFilters([{ field, operator, values }]);
|
|
898
|
+
this._nestService.page = 1;
|
|
385
899
|
return this;
|
|
386
900
|
}
|
|
387
901
|
/**
|
|
@@ -430,8 +944,18 @@ class NgQubeeService {
|
|
|
430
944
|
field,
|
|
431
945
|
order
|
|
432
946
|
});
|
|
947
|
+
this._nestService.page = 1;
|
|
433
948
|
return this;
|
|
434
949
|
}
|
|
950
|
+
/**
|
|
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
|
+
}
|
|
435
959
|
/**
|
|
436
960
|
* Delete selected fields for the given models in the current query builder state (JSON:API and Spatie only)
|
|
437
961
|
*
|
|
@@ -486,594 +1010,314 @@ class NgQubeeService {
|
|
|
486
1010
|
return this;
|
|
487
1011
|
}
|
|
488
1012
|
this._nestService.deleteFilters(...filters);
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Remove selected related models from the query builder state (JSON:API and Spatie only)
|
|
493
|
-
*
|
|
494
|
-
* @param {string[]} includes - Models to remove
|
|
495
|
-
* @returns {this}
|
|
496
|
-
* @throws {UnsupportedIncludesError} If the active driver does not support includes
|
|
497
|
-
*/
|
|
498
|
-
deleteIncludes(...includes) {
|
|
499
|
-
this._assertDriver([DriverEnum.JSON_API, DriverEnum.SPATIE], new UnsupportedIncludesError());
|
|
500
|
-
if (!includes.length) {
|
|
501
|
-
return this;
|
|
502
|
-
}
|
|
503
|
-
this._nestService.deleteIncludes(...includes);
|
|
504
|
-
return this;
|
|
505
|
-
}
|
|
506
|
-
/**
|
|
507
|
-
* Remove operator filters by field name (NestJS only)
|
|
508
|
-
*
|
|
509
|
-
* @param {string[]} fields - Field names of operator filters to remove
|
|
510
|
-
* @returns {this}
|
|
511
|
-
* @throws {UnsupportedFilterOperatorError} If the active driver does not support filter operators
|
|
512
|
-
*/
|
|
513
|
-
deleteOperatorFilters(...fields) {
|
|
514
|
-
this._assertDriver([DriverEnum.NESTJS], new UnsupportedFilterOperatorError());
|
|
515
|
-
if (!fields.length) {
|
|
516
|
-
return this;
|
|
517
|
-
}
|
|
518
|
-
this._nestService.deleteOperatorFilters(...fields);
|
|
519
|
-
return this;
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* Remove search term from the query builder state (NestJS only)
|
|
523
|
-
*
|
|
524
|
-
* @returns {this}
|
|
525
|
-
* @throws {UnsupportedSearchError} If the active driver does not support search
|
|
526
|
-
*/
|
|
527
|
-
deleteSearch() {
|
|
528
|
-
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSearchError());
|
|
529
|
-
this._nestService.deleteSearch();
|
|
530
|
-
return this;
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Remove flat field selections from the query builder state (NestJS only)
|
|
534
|
-
*
|
|
535
|
-
* @param {string[]} fields - Fields to remove from selection
|
|
536
|
-
* @returns {this}
|
|
537
|
-
* @throws {UnsupportedSelectError} If the active driver does not support flat field selection
|
|
538
|
-
*/
|
|
539
|
-
deleteSelect(...fields) {
|
|
540
|
-
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSelectError());
|
|
541
|
-
if (!fields.length) {
|
|
542
|
-
return this;
|
|
543
|
-
}
|
|
544
|
-
this._nestService.deleteSelect(...fields);
|
|
545
|
-
return this;
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* Remove sort rules from the query builder state (JSON:API, NestJS, and Spatie)
|
|
549
|
-
*
|
|
550
|
-
* @param sorts - Fields used for sorting to remove
|
|
551
|
-
* @returns {this}
|
|
552
|
-
* @throws {UnsupportedSortError} If the active driver does not support sorts
|
|
553
|
-
*/
|
|
554
|
-
deleteSorts(...sorts) {
|
|
555
|
-
this._assertDriver([DriverEnum.JSON_API, DriverEnum.NESTJS, DriverEnum.SPATIE], new UnsupportedSortError());
|
|
556
|
-
this._nestService.deleteSorts(...sorts);
|
|
557
|
-
return this;
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Generate a URI accordingly to the given data and active driver
|
|
561
|
-
*
|
|
562
|
-
* @returns {Observable<string>} An observable that emits the generated URI
|
|
563
|
-
*/
|
|
564
|
-
generateUri() {
|
|
565
|
-
try {
|
|
566
|
-
this._uri$.next(this._requestStrategy.buildUri(this._nestService.nest(), this._options));
|
|
567
|
-
return this.uri$;
|
|
568
|
-
}
|
|
569
|
-
catch (error) {
|
|
570
|
-
return throwError(() => error);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* Clear the current state and reset the Query Builder to a fresh, clean condition
|
|
575
|
-
*
|
|
576
|
-
* @returns {this}
|
|
577
|
-
*/
|
|
578
|
-
reset() {
|
|
579
|
-
this._nestService.reset();
|
|
580
|
-
return this;
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* Set the base URL to use for composing the address
|
|
584
|
-
*
|
|
585
|
-
* @param {string} baseUrl - The base URL
|
|
586
|
-
* @returns {this}
|
|
587
|
-
*/
|
|
588
|
-
setBaseUrl(baseUrl) {
|
|
589
|
-
this._nestService.baseUrl = baseUrl;
|
|
590
|
-
return this;
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Set the items per page number
|
|
594
|
-
*
|
|
595
|
-
* Validation is delegated to the active request strategy because the
|
|
596
|
-
* accepted range is driver-specific: nestjs-paginate additionally accepts
|
|
597
|
-
* `-1` as a "fetch all" sentinel, while Laravel, Spatie, and JSON:API
|
|
598
|
-
* require a positive integer.
|
|
599
|
-
*
|
|
600
|
-
* @param limit - Number of items per page (or `-1` to fetch all, NestJS only)
|
|
601
|
-
* @returns {this}
|
|
602
|
-
* @throws {import('../errors/invalid-limit.error').InvalidLimitError} If the value is not accepted by the active driver
|
|
603
|
-
*/
|
|
604
|
-
setLimit(limit) {
|
|
605
|
-
this._requestStrategy.validateLimit(limit);
|
|
606
|
-
this._nestService.limit = limit;
|
|
607
|
-
return this;
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* Set the page that the backend will use to paginate the result set
|
|
611
|
-
*
|
|
612
|
-
* @param page - Page number
|
|
613
|
-
* @returns {this}
|
|
614
|
-
*/
|
|
615
|
-
setPage(page) {
|
|
616
|
-
this._nestService.page = page;
|
|
617
|
-
return this;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Set the API resource to run the query against
|
|
621
|
-
*
|
|
622
|
-
* @param {string} resource - Resource name (e.g. 'users' produces /users)
|
|
623
|
-
* @returns {this}
|
|
624
|
-
*/
|
|
625
|
-
setResource(resource) {
|
|
626
|
-
this._nestService.resource = resource;
|
|
627
|
-
return this;
|
|
628
|
-
}
|
|
629
|
-
/**
|
|
630
|
-
* Set the search term for full-text search (NestJS only)
|
|
631
|
-
*
|
|
632
|
-
* Produces: `search=term`
|
|
633
|
-
*
|
|
634
|
-
* @param {string} search - The search term
|
|
635
|
-
* @returns {this}
|
|
636
|
-
* @throws {UnsupportedSearchError} If the active driver does not support search
|
|
637
|
-
*/
|
|
638
|
-
setSearch(search) {
|
|
639
|
-
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSearchError());
|
|
640
|
-
this._nestService.setSearch(search);
|
|
641
|
-
return this;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* Error thrown when an invalid resource name is provided
|
|
647
|
-
*
|
|
648
|
-
* Resource name must be a non-empty string.
|
|
649
|
-
*/
|
|
650
|
-
class InvalidResourceNameError extends Error {
|
|
651
|
-
constructor(resource) {
|
|
652
|
-
super(`Invalid resource name: Resource name must be a non-empty string. Received: ${JSON.stringify(resource)}`);
|
|
653
|
-
this.name = 'InvalidResourceNameError';
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
class InvalidPageNumberError extends Error {
|
|
658
|
-
constructor(page) {
|
|
659
|
-
super(`Invalid page number: Page must be a positive integer greater than 0. Received: ${page}`);
|
|
660
|
-
this.name = 'InvalidPageNumberError';
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const INITIAL_STATE = {
|
|
665
|
-
baseUrl: '',
|
|
666
|
-
fields: {},
|
|
667
|
-
filters: {},
|
|
668
|
-
includes: [],
|
|
669
|
-
limit: 15,
|
|
670
|
-
operatorFilters: [],
|
|
671
|
-
page: 1,
|
|
672
|
-
resource: '',
|
|
673
|
-
search: '',
|
|
674
|
-
select: [],
|
|
675
|
-
sorts: []
|
|
676
|
-
};
|
|
677
|
-
class NestService {
|
|
678
|
-
/**
|
|
679
|
-
* Private writable signal that holds the Query Builder state
|
|
680
|
-
*
|
|
681
|
-
* @type {IQueryBuilderState}
|
|
682
|
-
*/
|
|
683
|
-
_nest = signal(this._clone(INITIAL_STATE), ...(ngDevMode ? [{ debugName: "_nest" }] : []));
|
|
684
|
-
/**
|
|
685
|
-
* A computed signal that makes readonly the writable signal _nest
|
|
686
|
-
*
|
|
687
|
-
* @type {Signal<IQueryBuilderState>}
|
|
688
|
-
*/
|
|
689
|
-
nest = computed(() => this._clone(this._nest()), ...(ngDevMode ? [{ debugName: "nest" }] : []));
|
|
690
|
-
constructor() {
|
|
691
|
-
// Nothing to see here
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Set the base URL for the API
|
|
695
|
-
*
|
|
696
|
-
* @param {string} baseUrl - The base URL to prepend to generated URIs
|
|
697
|
-
* @example
|
|
698
|
-
* service.baseUrl = 'https://api.example.com';
|
|
699
|
-
*/
|
|
700
|
-
set baseUrl(baseUrl) {
|
|
701
|
-
this._nest.update(nest => ({
|
|
702
|
-
...nest,
|
|
703
|
-
baseUrl
|
|
704
|
-
}));
|
|
1013
|
+
this._nestService.page = 1;
|
|
1014
|
+
return this;
|
|
705
1015
|
}
|
|
706
1016
|
/**
|
|
707
|
-
*
|
|
708
|
-
*
|
|
709
|
-
* This setter performs a raw state write. Validation of the value is the
|
|
710
|
-
* responsibility of the active request strategy and is enforced upstream
|
|
711
|
-
* by `NgQubeeService.setLimit()`, because the accepted range depends on
|
|
712
|
-
* the driver (e.g. nestjs-paginate accepts `-1` for "fetch all").
|
|
1017
|
+
* Remove selected related models from the query builder state (JSON:API and Spatie only)
|
|
713
1018
|
*
|
|
714
|
-
* @param {
|
|
715
|
-
* @
|
|
716
|
-
*
|
|
1019
|
+
* @param {string[]} includes - Models to remove
|
|
1020
|
+
* @returns {this}
|
|
1021
|
+
* @throws {UnsupportedIncludesError} If the active driver does not support includes
|
|
717
1022
|
*/
|
|
718
|
-
|
|
719
|
-
this.
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
1023
|
+
deleteIncludes(...includes) {
|
|
1024
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.SPATIE], new UnsupportedIncludesError());
|
|
1025
|
+
if (!includes.length) {
|
|
1026
|
+
return this;
|
|
1027
|
+
}
|
|
1028
|
+
this._nestService.deleteIncludes(...includes);
|
|
1029
|
+
return this;
|
|
723
1030
|
}
|
|
724
1031
|
/**
|
|
725
|
-
*
|
|
726
|
-
* Must be a positive integer greater than 0
|
|
1032
|
+
* Remove operator filters by field name (NestJS only)
|
|
727
1033
|
*
|
|
728
|
-
* @param {
|
|
729
|
-
* @
|
|
730
|
-
* @
|
|
731
|
-
* service.page = 2;
|
|
1034
|
+
* @param {string[]} fields - Field names of operator filters to remove
|
|
1035
|
+
* @returns {this}
|
|
1036
|
+
* @throws {UnsupportedFilterOperatorError} If the active driver does not support filter operators
|
|
732
1037
|
*/
|
|
733
|
-
|
|
734
|
-
this.
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1038
|
+
deleteOperatorFilters(...fields) {
|
|
1039
|
+
this._assertDriver([DriverEnum.NESTJS], new UnsupportedFilterOperatorError());
|
|
1040
|
+
if (!fields.length) {
|
|
1041
|
+
return this;
|
|
1042
|
+
}
|
|
1043
|
+
this._nestService.deleteOperatorFilters(...fields);
|
|
1044
|
+
this._nestService.page = 1;
|
|
1045
|
+
return this;
|
|
739
1046
|
}
|
|
740
1047
|
/**
|
|
741
|
-
*
|
|
742
|
-
* Must be a non-empty string
|
|
1048
|
+
* Remove search term from the query builder state (NestJS only)
|
|
743
1049
|
*
|
|
744
|
-
* @
|
|
745
|
-
* @throws {
|
|
746
|
-
* @example
|
|
747
|
-
* service.resource = 'users';
|
|
1050
|
+
* @returns {this}
|
|
1051
|
+
* @throws {UnsupportedSearchError} If the active driver does not support search
|
|
748
1052
|
*/
|
|
749
|
-
|
|
750
|
-
this.
|
|
751
|
-
this.
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}));
|
|
755
|
-
}
|
|
756
|
-
_clone(obj) {
|
|
757
|
-
return JSON.parse(JSON.stringify(obj));
|
|
1053
|
+
deleteSearch() {
|
|
1054
|
+
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSearchError());
|
|
1055
|
+
this._nestService.deleteSearch();
|
|
1056
|
+
this._nestService.page = 1;
|
|
1057
|
+
return this;
|
|
758
1058
|
}
|
|
759
1059
|
/**
|
|
760
|
-
*
|
|
1060
|
+
* Remove flat field selections from the query builder state (NestJS only)
|
|
761
1061
|
*
|
|
762
|
-
* @param {
|
|
763
|
-
* @
|
|
764
|
-
* @
|
|
1062
|
+
* @param {string[]} fields - Fields to remove from selection
|
|
1063
|
+
* @returns {this}
|
|
1064
|
+
* @throws {UnsupportedSelectError} If the active driver does not support flat field selection
|
|
765
1065
|
*/
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1066
|
+
deleteSelect(...fields) {
|
|
1067
|
+
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSelectError());
|
|
1068
|
+
if (!fields.length) {
|
|
1069
|
+
return this;
|
|
769
1070
|
}
|
|
1071
|
+
this._nestService.deleteSelect(...fields);
|
|
1072
|
+
return this;
|
|
770
1073
|
}
|
|
771
1074
|
/**
|
|
772
|
-
*
|
|
1075
|
+
* Remove sort rules from the query builder state (JSON:API, NestJS, and Spatie)
|
|
773
1076
|
*
|
|
774
|
-
* @param
|
|
775
|
-
* @
|
|
776
|
-
* @
|
|
1077
|
+
* @param sorts - Fields used for sorting to remove
|
|
1078
|
+
* @returns {this}
|
|
1079
|
+
* @throws {UnsupportedSortError} If the active driver does not support sorts
|
|
777
1080
|
*/
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1081
|
+
deleteSorts(...sorts) {
|
|
1082
|
+
this._assertDriver([DriverEnum.JSON_API, DriverEnum.NESTJS, DriverEnum.SPATIE], new UnsupportedSortError());
|
|
1083
|
+
this._nestService.deleteSorts(...sorts);
|
|
1084
|
+
this._nestService.page = 1;
|
|
1085
|
+
return this;
|
|
782
1086
|
}
|
|
783
1087
|
/**
|
|
784
|
-
*
|
|
785
|
-
* Automatically prevents duplicate fields for each model
|
|
1088
|
+
* Navigate to the first page (page 1)
|
|
786
1089
|
*
|
|
787
|
-
* @
|
|
788
|
-
* @
|
|
789
|
-
* @example
|
|
790
|
-
* service.addFields({ users: ['id', 'email', 'username'] });
|
|
791
|
-
* service.addFields({ posts: ['title', 'content'] });
|
|
1090
|
+
* @remarks Never throws. Idempotent when already on page 1.
|
|
1091
|
+
* @returns {this}
|
|
792
1092
|
*/
|
|
793
|
-
|
|
794
|
-
this.
|
|
795
|
-
|
|
796
|
-
Object.keys(fields).forEach(model => {
|
|
797
|
-
const existingFields = mergedFields[model] || [];
|
|
798
|
-
const newFields = fields[model];
|
|
799
|
-
// Use Set to prevent duplicates
|
|
800
|
-
const uniqueFields = Array.from(new Set([...existingFields, ...newFields]));
|
|
801
|
-
mergedFields[model] = uniqueFields;
|
|
802
|
-
});
|
|
803
|
-
return {
|
|
804
|
-
...nest,
|
|
805
|
-
fields: mergedFields
|
|
806
|
-
};
|
|
807
|
-
});
|
|
1093
|
+
firstPage() {
|
|
1094
|
+
this._nestService.page = 1;
|
|
1095
|
+
return this;
|
|
808
1096
|
}
|
|
809
1097
|
/**
|
|
810
|
-
*
|
|
811
|
-
* Automatically prevents duplicate filter values for each filter key
|
|
1098
|
+
* Generate a URI accordingly to the given data and active driver
|
|
812
1099
|
*
|
|
813
|
-
* @
|
|
814
|
-
* @return {void}
|
|
815
|
-
* @example
|
|
816
|
-
* service.addFilters({ id: [1, 2, 3] });
|
|
817
|
-
* service.addFilters({ status: ['active', 'pending'] });
|
|
1100
|
+
* @returns {Observable<string>} An observable that emits the generated URI
|
|
818
1101
|
*/
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
mergedFilters[key] = uniqueValues;
|
|
828
|
-
});
|
|
829
|
-
return {
|
|
830
|
-
...nest,
|
|
831
|
-
filters: mergedFilters
|
|
832
|
-
};
|
|
833
|
-
});
|
|
1102
|
+
generateUri() {
|
|
1103
|
+
try {
|
|
1104
|
+
this._uri$.next(this._requestStrategy.buildUri(this._nestService.nest(), this._options));
|
|
1105
|
+
return this.uri$;
|
|
1106
|
+
}
|
|
1107
|
+
catch (error) {
|
|
1108
|
+
return throwError(() => error);
|
|
1109
|
+
}
|
|
834
1110
|
}
|
|
835
1111
|
/**
|
|
836
|
-
*
|
|
837
|
-
* Automatically prevents duplicate includes
|
|
1112
|
+
* Navigate directly to the specified page
|
|
838
1113
|
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
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
|
|
844
1121
|
*/
|
|
845
|
-
|
|
846
|
-
this.
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
};
|
|
853
|
-
});
|
|
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;
|
|
854
1129
|
}
|
|
855
1130
|
/**
|
|
856
|
-
*
|
|
857
|
-
* Automatically prevents duplicate operator filters for the same field + operator combination
|
|
1131
|
+
* Check whether a next page exists
|
|
858
1132
|
*
|
|
859
|
-
* @
|
|
860
|
-
* @
|
|
861
|
-
* @example
|
|
862
|
-
* import { FilterOperatorEnum } from 'ng-qubee';
|
|
863
|
-
* service.addOperatorFilters([{ field: 'age', operator: FilterOperatorEnum.GTE, values: [18] }]);
|
|
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
|
|
864
1135
|
*/
|
|
865
|
-
|
|
866
|
-
this.
|
|
867
|
-
|
|
868
|
-
filters.forEach(newFilter => {
|
|
869
|
-
const existingIdx = merged.findIndex(f => f.field === newFilter.field && f.operator === newFilter.operator);
|
|
870
|
-
if (existingIdx > -1) {
|
|
871
|
-
const existingValues = merged[existingIdx].values;
|
|
872
|
-
merged[existingIdx] = {
|
|
873
|
-
...merged[existingIdx],
|
|
874
|
-
values: Array.from(new Set([...existingValues, ...newFilter.values]))
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
else {
|
|
878
|
-
merged.push({ ...newFilter });
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
return {
|
|
882
|
-
...nest,
|
|
883
|
-
operatorFilters: merged
|
|
884
|
-
};
|
|
885
|
-
});
|
|
1136
|
+
hasNextPage() {
|
|
1137
|
+
const state = this._nestService.nest();
|
|
1138
|
+
return !state.isLastPageKnown || state.page < state.lastPage;
|
|
886
1139
|
}
|
|
887
1140
|
/**
|
|
888
|
-
*
|
|
889
|
-
* Automatically prevents duplicate select fields
|
|
1141
|
+
* Check whether a previous page exists
|
|
890
1142
|
*
|
|
891
|
-
* @
|
|
892
|
-
* @
|
|
893
|
-
* @example
|
|
894
|
-
* service.addSelect(['id', 'name', 'email']);
|
|
1143
|
+
* @remarks Always safe. Does not require a synced paginated response.
|
|
1144
|
+
* @returns `true` if `state.page > 1`
|
|
895
1145
|
*/
|
|
896
|
-
|
|
897
|
-
this.
|
|
898
|
-
const uniqueSelect = Array.from(new Set([...nest.select, ...fields]));
|
|
899
|
-
return {
|
|
900
|
-
...nest,
|
|
901
|
-
select: uniqueSelect
|
|
902
|
-
};
|
|
903
|
-
});
|
|
1146
|
+
hasPreviousPage() {
|
|
1147
|
+
return this._nestService.nest().page > 1;
|
|
904
1148
|
}
|
|
905
1149
|
/**
|
|
906
|
-
*
|
|
1150
|
+
* Check whether the current page is the first page
|
|
907
1151
|
*
|
|
908
|
-
* @
|
|
909
|
-
* @
|
|
910
|
-
* @example
|
|
911
|
-
* import { SortEnum } from 'ng-qubee';
|
|
912
|
-
* service.addSort({ field: 'created_at', order: SortEnum.DESC });
|
|
913
|
-
* service.addSort({ field: 'name', order: SortEnum.ASC });
|
|
1152
|
+
* @remarks Always safe. Does not require a synced paginated response.
|
|
1153
|
+
* @returns `true` if `state.page === 1`
|
|
914
1154
|
*/
|
|
915
|
-
|
|
916
|
-
this.
|
|
917
|
-
...nest,
|
|
918
|
-
sorts: [...nest.sorts, sort]
|
|
919
|
-
}));
|
|
1155
|
+
isFirstPage() {
|
|
1156
|
+
return this._nestService.nest().page === 1;
|
|
920
1157
|
}
|
|
921
1158
|
/**
|
|
922
|
-
*
|
|
923
|
-
* Uses deep cloning to prevent mutations to the original state
|
|
1159
|
+
* Check whether the current page is the last page
|
|
924
1160
|
*
|
|
925
|
-
* @
|
|
926
|
-
* @
|
|
927
|
-
* @example
|
|
928
|
-
* service.deleteFields({ users: ['email'] });
|
|
929
|
-
* service.deleteFields({ posts: ['content', 'body'] });
|
|
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`
|
|
930
1163
|
*/
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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;
|
|
944
1182
|
}
|
|
945
1183
|
/**
|
|
946
|
-
*
|
|
947
|
-
* Uses deep cloning to prevent mutations to the original state
|
|
1184
|
+
* Navigate to the next page
|
|
948
1185
|
*
|
|
949
|
-
* @
|
|
950
|
-
* @
|
|
951
|
-
* @example
|
|
952
|
-
* service.deleteFilters('id');
|
|
953
|
-
* service.deleteFilters('status', 'type');
|
|
1186
|
+
* @remarks Never throws. Idempotent at the known last page (no-op). Pair with `hasNextPage()` for a disable-state binding.
|
|
1187
|
+
* @returns {this}
|
|
954
1188
|
*/
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
}));
|
|
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;
|
|
963
1196
|
}
|
|
964
1197
|
/**
|
|
965
|
-
*
|
|
1198
|
+
* Navigate to the previous page
|
|
966
1199
|
*
|
|
967
|
-
* @
|
|
968
|
-
* @
|
|
969
|
-
* @example
|
|
970
|
-
* service.deleteIncludes('profile');
|
|
971
|
-
* service.deleteIncludes('posts', 'comments');
|
|
1200
|
+
* @remarks Never throws. Idempotent at page 1 (floored). Pair with `hasPreviousPage()` for a disable-state binding.
|
|
1201
|
+
* @returns {this}
|
|
972
1202
|
*/
|
|
973
|
-
|
|
974
|
-
this.
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
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;
|
|
978
1210
|
}
|
|
979
1211
|
/**
|
|
980
|
-
*
|
|
1212
|
+
* Clear the current state and reset the Query Builder to a fresh, clean condition
|
|
981
1213
|
*
|
|
982
|
-
* @
|
|
983
|
-
* @return {void}
|
|
984
|
-
* @example
|
|
985
|
-
* service.deleteOperatorFilters('age');
|
|
986
|
-
* service.deleteOperatorFilters('price', 'quantity');
|
|
1214
|
+
* @returns {this}
|
|
987
1215
|
*/
|
|
988
|
-
|
|
989
|
-
this.
|
|
990
|
-
|
|
991
|
-
operatorFilters: nest.operatorFilters.filter(f => !fields.includes(f.field))
|
|
992
|
-
}));
|
|
1216
|
+
reset() {
|
|
1217
|
+
this._nestService.reset();
|
|
1218
|
+
return this;
|
|
993
1219
|
}
|
|
994
1220
|
/**
|
|
995
|
-
*
|
|
1221
|
+
* Set the base URL to use for composing the address
|
|
996
1222
|
*
|
|
997
|
-
* @
|
|
998
|
-
* @
|
|
999
|
-
* service.deleteSearch();
|
|
1223
|
+
* @param {string} baseUrl - The base URL
|
|
1224
|
+
* @returns {this}
|
|
1000
1225
|
*/
|
|
1001
|
-
|
|
1002
|
-
this.
|
|
1003
|
-
|
|
1004
|
-
search: ''
|
|
1005
|
-
}));
|
|
1226
|
+
setBaseUrl(baseUrl) {
|
|
1227
|
+
this._nestService.baseUrl = baseUrl;
|
|
1228
|
+
return this;
|
|
1006
1229
|
}
|
|
1007
1230
|
/**
|
|
1008
|
-
*
|
|
1231
|
+
* Set the items per page number
|
|
1009
1232
|
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
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
|
|
1015
1241
|
*/
|
|
1016
|
-
|
|
1017
|
-
this.
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1242
|
+
setLimit(limit) {
|
|
1243
|
+
this._requestStrategy.validateLimit(limit);
|
|
1244
|
+
this._nestService.limit = limit;
|
|
1245
|
+
this._nestService.page = 1;
|
|
1246
|
+
return this;
|
|
1021
1247
|
}
|
|
1022
1248
|
/**
|
|
1023
|
-
*
|
|
1249
|
+
* Set the page that the backend will use to paginate the result set
|
|
1024
1250
|
*
|
|
1025
|
-
* @param
|
|
1026
|
-
* @
|
|
1027
|
-
* @example
|
|
1028
|
-
* service.deleteSorts('created_at');
|
|
1029
|
-
* service.deleteSorts('name', 'created_at');
|
|
1251
|
+
* @param page - Page number
|
|
1252
|
+
* @returns {this}
|
|
1030
1253
|
*/
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
const p = s.findIndex(sort => sort.field === field);
|
|
1035
|
-
if (p > -1) {
|
|
1036
|
-
s.splice(p, 1);
|
|
1037
|
-
}
|
|
1038
|
-
});
|
|
1039
|
-
this._nest.update(nest => ({
|
|
1040
|
-
...nest,
|
|
1041
|
-
sorts: s
|
|
1042
|
-
}));
|
|
1254
|
+
setPage(page) {
|
|
1255
|
+
this._nestService.page = page;
|
|
1256
|
+
return this;
|
|
1043
1257
|
}
|
|
1044
1258
|
/**
|
|
1045
|
-
* Set the
|
|
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`
|
|
1046
1273
|
*
|
|
1047
1274
|
* @param {string} search - The search term
|
|
1048
|
-
* @
|
|
1049
|
-
* @
|
|
1050
|
-
* service.setSearch('john doe');
|
|
1275
|
+
* @returns {this}
|
|
1276
|
+
* @throws {UnsupportedSearchError} If the active driver does not support search
|
|
1051
1277
|
*/
|
|
1052
1278
|
setSearch(search) {
|
|
1053
|
-
this.
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1279
|
+
this._assertDriver([DriverEnum.NESTJS], new UnsupportedSearchError());
|
|
1280
|
+
this._nestService.setSearch(search);
|
|
1281
|
+
this._nestService.page = 1;
|
|
1282
|
+
return this;
|
|
1057
1283
|
}
|
|
1058
1284
|
/**
|
|
1059
|
-
*
|
|
1060
|
-
* Clears all fields, filters, includes, sorts, and resets pagination
|
|
1285
|
+
* Get the total number of pages reported by the most recent paginated response
|
|
1061
1286
|
*
|
|
1062
|
-
* @
|
|
1063
|
-
* @
|
|
1064
|
-
*
|
|
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)
|
|
1065
1290
|
*/
|
|
1066
|
-
|
|
1067
|
-
|
|
1291
|
+
totalPages() {
|
|
1292
|
+
const state = this._nestService.nest();
|
|
1293
|
+
if (!state.isLastPageKnown) {
|
|
1294
|
+
throw new PaginationNotSyncedError('read totalPages');
|
|
1295
|
+
}
|
|
1296
|
+
return state.lastPage;
|
|
1068
1297
|
}
|
|
1069
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type:
|
|
1070
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type:
|
|
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 });
|
|
1071
1300
|
}
|
|
1072
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type:
|
|
1301
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeService, decorators: [{
|
|
1073
1302
|
type: Injectable
|
|
1074
|
-
}], ctorParameters: () => [
|
|
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
|
+
}] }] });
|
|
1075
1313
|
|
|
1076
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;
|
|
1077
1321
|
/**
|
|
1078
1322
|
* Resolved response key name options
|
|
1079
1323
|
*/
|
|
@@ -1082,23 +1326,48 @@ class PaginationService {
|
|
|
1082
1326
|
* The response strategy that parses responses for the active driver
|
|
1083
1327
|
*/
|
|
1084
1328
|
_responseStrategy;
|
|
1085
|
-
constructor(responseStrategy, options = {}) {
|
|
1086
|
-
this.
|
|
1329
|
+
constructor(nestService, responseStrategy, options = new ResponseOptions({})) {
|
|
1330
|
+
this._nestService = nestService;
|
|
1331
|
+
this._options = options;
|
|
1087
1332
|
this._responseStrategy = responseStrategy;
|
|
1088
1333
|
}
|
|
1089
1334
|
/**
|
|
1090
1335
|
* Transform a raw API response into a typed PaginatedCollection
|
|
1091
1336
|
*
|
|
1092
|
-
* Delegates to the active driver's response strategy for parsing
|
|
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`.
|
|
1093
1346
|
*
|
|
1094
1347
|
* @param response - The raw API response object
|
|
1095
1348
|
* @returns A typed PaginatedCollection instance
|
|
1096
1349
|
*/
|
|
1097
1350
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1098
1351
|
paginate(response) {
|
|
1099
|
-
|
|
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;
|
|
1100
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 });
|
|
1101
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
|
+
}] }] });
|
|
1102
1371
|
|
|
1103
1372
|
var SortEnum;
|
|
1104
1373
|
(function (SortEnum) {
|
|
@@ -2032,7 +2301,7 @@ class SpatieResponseStrategy {
|
|
|
2032
2301
|
* @param driver - The pagination driver
|
|
2033
2302
|
* @returns The corresponding request strategy
|
|
2034
2303
|
*/
|
|
2035
|
-
function resolveRequestStrategy
|
|
2304
|
+
function resolveRequestStrategy(driver) {
|
|
2036
2305
|
switch (driver) {
|
|
2037
2306
|
case DriverEnum.JSON_API:
|
|
2038
2307
|
return new JsonApiRequestStrategy();
|
|
@@ -2050,7 +2319,7 @@ function resolveRequestStrategy$1(driver) {
|
|
|
2050
2319
|
* @param driver - The pagination driver
|
|
2051
2320
|
* @returns The corresponding response strategy
|
|
2052
2321
|
*/
|
|
2053
|
-
function resolveResponseStrategy
|
|
2322
|
+
function resolveResponseStrategy(driver) {
|
|
2054
2323
|
switch (driver) {
|
|
2055
2324
|
case DriverEnum.JSON_API:
|
|
2056
2325
|
return new JsonApiResponseStrategy();
|
|
@@ -2062,86 +2331,47 @@ function resolveResponseStrategy$1(driver) {
|
|
|
2062
2331
|
return new LaravelResponseStrategy();
|
|
2063
2332
|
}
|
|
2064
2333
|
}
|
|
2065
|
-
// @dynamic
|
|
2066
|
-
class NgQubeeModule {
|
|
2067
|
-
/**
|
|
2068
|
-
* Configure NgQubee for the root module
|
|
2069
|
-
*
|
|
2070
|
-
* @param config - Configuration object with driver, and optional request and response settings
|
|
2071
|
-
* @returns Module with providers configured for the specified driver
|
|
2072
|
-
*/
|
|
2073
|
-
static forRoot(config) {
|
|
2074
|
-
const driver = config.driver;
|
|
2075
|
-
const requestStrategy = resolveRequestStrategy$1(driver);
|
|
2076
|
-
const responseStrategy = resolveResponseStrategy$1(driver);
|
|
2077
|
-
return {
|
|
2078
|
-
ngModule: NgQubeeModule,
|
|
2079
|
-
providers: [
|
|
2080
|
-
NestService,
|
|
2081
|
-
{
|
|
2082
|
-
deps: [NestService],
|
|
2083
|
-
provide: NgQubeeService,
|
|
2084
|
-
useFactory: (nestService) => new NgQubeeService(nestService, requestStrategy, driver, Object.assign({}, config.request))
|
|
2085
|
-
},
|
|
2086
|
-
{
|
|
2087
|
-
provide: PaginationService,
|
|
2088
|
-
useFactory: () => {
|
|
2089
|
-
const responseConfig = Object.assign({}, config.response);
|
|
2090
|
-
if (driver === DriverEnum.JSON_API) {
|
|
2091
|
-
return new PaginationService(responseStrategy, new JsonApiResponseOptions(responseConfig));
|
|
2092
|
-
}
|
|
2093
|
-
return driver === DriverEnum.NESTJS
|
|
2094
|
-
? new PaginationService(responseStrategy, new NestjsResponseOptions(responseConfig))
|
|
2095
|
-
: new PaginationService(responseStrategy, responseConfig);
|
|
2096
|
-
}
|
|
2097
|
-
}
|
|
2098
|
-
]
|
|
2099
|
-
};
|
|
2100
|
-
}
|
|
2101
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2102
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule });
|
|
2103
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule });
|
|
2104
|
-
}
|
|
2105
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule, decorators: [{
|
|
2106
|
-
type: NgModule,
|
|
2107
|
-
args: [{}]
|
|
2108
|
-
}] });
|
|
2109
|
-
|
|
2110
2334
|
/**
|
|
2111
|
-
* Resolve the
|
|
2335
|
+
* Resolve the driver-specific `ResponseOptions` instance
|
|
2112
2336
|
*
|
|
2113
2337
|
* @param driver - The pagination driver
|
|
2114
|
-
* @
|
|
2338
|
+
* @param responseConfig - User-supplied response key overrides
|
|
2339
|
+
* @returns A pre-built ResponseOptions (or driver-specific subclass)
|
|
2115
2340
|
*/
|
|
2116
|
-
function
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
case DriverEnum.SPATIE:
|
|
2123
|
-
return new SpatieRequestStrategy();
|
|
2124
|
-
case DriverEnum.LARAVEL:
|
|
2125
|
-
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);
|
|
2126
2347
|
}
|
|
2348
|
+
return new ResponseOptions(responseConfig);
|
|
2127
2349
|
}
|
|
2128
2350
|
/**
|
|
2129
|
-
*
|
|
2351
|
+
* Build the core provider list shared by `provideNgQubee()` and
|
|
2352
|
+
* `NgQubeeModule.forRoot()`
|
|
2130
2353
|
*
|
|
2131
|
-
*
|
|
2132
|
-
*
|
|
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
|
|
2133
2360
|
*/
|
|
2134
|
-
function
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
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
|
+
];
|
|
2145
2375
|
}
|
|
2146
2376
|
/**
|
|
2147
2377
|
* Sets up providers necessary to enable `NgQubee` functionality for the application.
|
|
@@ -2187,33 +2417,61 @@ function resolveResponseStrategy(driver) {
|
|
|
2187
2417
|
* @returns A set of providers to setup NgQubee
|
|
2188
2418
|
*/
|
|
2189
2419
|
function provideNgQubee(config) {
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
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 });
|
|
2216
2470
|
}
|
|
2471
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImport: i0, type: NgQubeeModule, decorators: [{
|
|
2472
|
+
type: NgModule,
|
|
2473
|
+
args: [{}]
|
|
2474
|
+
}] });
|
|
2217
2475
|
|
|
2218
2476
|
/**
|
|
2219
2477
|
* Enum representing the available filter operators for the NestJS driver
|
|
@@ -2247,5 +2505,5 @@ var FilterOperatorEnum;
|
|
|
2247
2505
|
* Generated bundle index. Do not edit.
|
|
2248
2506
|
*/
|
|
2249
2507
|
|
|
2250
|
-
export { DriverEnum, FilterOperatorEnum, InvalidLimitError, InvalidPageNumberError, InvalidResourceNameError, JsonApiRequestStrategy, JsonApiResponseStrategy, 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 };
|
|
2251
2509
|
//# sourceMappingURL=ng-qubee.mjs.map
|