ng-qubee 3.5.0 → 3.6.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 +5 -2
- package/fesm2022/ng-qubee.mjs +800 -1
- package/fesm2022/ng-qubee.mjs.map +1 -1
- package/package.json +8 -1
- package/types/ng-qubee.d.ts +746 -1
package/fesm2022/ng-qubee.mjs
CHANGED
|
@@ -78,8 +78,11 @@ var DriverEnum;
|
|
|
78
78
|
DriverEnum["JSON_API"] = "json-api";
|
|
79
79
|
DriverEnum["LARAVEL"] = "laravel";
|
|
80
80
|
DriverEnum["NESTJS"] = "nestjs";
|
|
81
|
+
DriverEnum["NESTJSX_CRUD"] = "nestjsx-crud";
|
|
81
82
|
DriverEnum["POSTGREST"] = "postgrest";
|
|
83
|
+
DriverEnum["SIEVE"] = "sieve";
|
|
82
84
|
DriverEnum["SPATIE"] = "spatie";
|
|
85
|
+
DriverEnum["SPRING"] = "spring";
|
|
83
86
|
DriverEnum["STRAPI"] = "strapi";
|
|
84
87
|
})(DriverEnum || (DriverEnum = {}));
|
|
85
88
|
|
|
@@ -202,6 +205,95 @@ class NestjsResponseOptions extends ResponseOptions {
|
|
|
202
205
|
});
|
|
203
206
|
}
|
|
204
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Pre-configured ResponseOptions for the @nestjsx/crud driver
|
|
210
|
+
*
|
|
211
|
+
* The `getMany` envelope is flat: `{ data, count, total, page,
|
|
212
|
+
* pageCount }`. `perPage` defaults to the `count` field — the number of
|
|
213
|
+
* entities on the **current** page, which equals the requested limit on
|
|
214
|
+
* every page except a partial last one. The envelope carries no
|
|
215
|
+
* `from`/`to` indices and no navigation links, so those paths default to
|
|
216
|
+
* empty strings (the strategy derives `from`/`to` and leaves the URLs
|
|
217
|
+
* `undefined`); consumers can override any path via `IPaginationConfig`.
|
|
218
|
+
*/
|
|
219
|
+
class NestjsxCrudResponseOptions extends ResponseOptions {
|
|
220
|
+
constructor(options) {
|
|
221
|
+
super({
|
|
222
|
+
currentPage: options.currentPage || 'page',
|
|
223
|
+
data: options.data || 'data',
|
|
224
|
+
firstPageUrl: options.firstPageUrl || '',
|
|
225
|
+
from: options.from || '',
|
|
226
|
+
lastPage: options.lastPage || 'pageCount',
|
|
227
|
+
lastPageUrl: options.lastPageUrl || '',
|
|
228
|
+
nextPageUrl: options.nextPageUrl || '',
|
|
229
|
+
path: options.path || '',
|
|
230
|
+
perPage: options.perPage || 'count',
|
|
231
|
+
prevPageUrl: options.prevPageUrl || '',
|
|
232
|
+
to: options.to || '',
|
|
233
|
+
total: options.total || 'total'
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Pre-configured ResponseOptions for the Sieve (.NET) driver
|
|
239
|
+
*
|
|
240
|
+
* Sieve defines no response envelope (it returns an `IQueryable` the
|
|
241
|
+
* developer wraps), so these defaults target the common hand-rolled
|
|
242
|
+
* `PagedResult<T>` shape: `{ data, page, pageSize, total, totalPages }`.
|
|
243
|
+
* Every path is overridable via `IPaginationConfig` — dot notation is
|
|
244
|
+
* supported, so nested wrappers (`meta.page`, `pagination.total`) map
|
|
245
|
+
* without subclassing. `from`/`to` default to empty paths and are
|
|
246
|
+
* derived; the navigation-URL slots resolve to `undefined` unless paths
|
|
247
|
+
* are provided.
|
|
248
|
+
*/
|
|
249
|
+
class SieveResponseOptions extends ResponseOptions {
|
|
250
|
+
constructor(options) {
|
|
251
|
+
super({
|
|
252
|
+
currentPage: options.currentPage || 'page',
|
|
253
|
+
data: options.data || 'data',
|
|
254
|
+
firstPageUrl: options.firstPageUrl || '',
|
|
255
|
+
from: options.from || '',
|
|
256
|
+
lastPage: options.lastPage || 'totalPages',
|
|
257
|
+
lastPageUrl: options.lastPageUrl || '',
|
|
258
|
+
nextPageUrl: options.nextPageUrl || '',
|
|
259
|
+
path: options.path || '',
|
|
260
|
+
perPage: options.perPage || 'pageSize',
|
|
261
|
+
prevPageUrl: options.prevPageUrl || '',
|
|
262
|
+
to: options.to || '',
|
|
263
|
+
total: options.total || 'total'
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Pre-configured ResponseOptions for the Spring Data REST driver
|
|
269
|
+
*
|
|
270
|
+
* Uses dot-notation paths into the HAL envelope: pagination metadata
|
|
271
|
+
* lives under `page.*` and navigation links under `_links.*.href`.
|
|
272
|
+
* `currentPage` points at the **0-indexed** `page.number`; the strategy
|
|
273
|
+
* adds 1 when reading it. `data` defaults to plain `_embedded` because
|
|
274
|
+
* the collection key underneath is the resource rel name (e.g.
|
|
275
|
+
* `_embedded.users`) and cannot be known statically — the strategy picks
|
|
276
|
+
* the first array inside; pin an exact path via `IPaginationConfig` when
|
|
277
|
+
* needed. `from`/`to` default to empty paths and are derived.
|
|
278
|
+
*/
|
|
279
|
+
class SpringResponseOptions extends ResponseOptions {
|
|
280
|
+
constructor(options) {
|
|
281
|
+
super({
|
|
282
|
+
currentPage: options.currentPage || 'page.number',
|
|
283
|
+
data: options.data || '_embedded',
|
|
284
|
+
firstPageUrl: options.firstPageUrl || '_links.first.href',
|
|
285
|
+
from: options.from || '',
|
|
286
|
+
lastPage: options.lastPage || 'page.totalPages',
|
|
287
|
+
lastPageUrl: options.lastPageUrl || '_links.last.href',
|
|
288
|
+
nextPageUrl: options.nextPageUrl || '_links.next.href',
|
|
289
|
+
path: options.path || '',
|
|
290
|
+
perPage: options.perPage || 'page.size',
|
|
291
|
+
prevPageUrl: options.prevPageUrl || '_links.prev.href',
|
|
292
|
+
to: options.to || '',
|
|
293
|
+
total: options.total || 'page.totalElements'
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
205
297
|
/**
|
|
206
298
|
* Pre-configured ResponseOptions for the Strapi driver
|
|
207
299
|
*
|
|
@@ -1400,6 +1492,273 @@ class NestjsRequestStrategy extends AbstractRequestStrategy {
|
|
|
1400
1492
|
class NestjsResponseStrategy extends AbstractDotPathResponseStrategy {
|
|
1401
1493
|
}
|
|
1402
1494
|
|
|
1495
|
+
/**
|
|
1496
|
+
* Request strategy for the @nestjsx/crud driver
|
|
1497
|
+
*
|
|
1498
|
+
* Generates URIs in [@nestjsx/crud's pipe-delimited query format](https://github.com/nestjsx/crud/wiki/Requests):
|
|
1499
|
+
* - Filters: `filter=field||$eq||value` (repeatable; multi-value
|
|
1500
|
+
* collapses to `filter=field||$in||v1,v2`)
|
|
1501
|
+
* - Operator filters: `filter=field||$op||value` (translated from
|
|
1502
|
+
* `FilterOperatorEnum` — `CONTAINS`→`$cont`, `ILIKE`→`$contL`,
|
|
1503
|
+
* `SW`→`$starts`, `BTW`→`$between`, `NOT`→`$ne`/`$notin`,
|
|
1504
|
+
* `NULL`→`$isnull`/`$notnull` with no value segment)
|
|
1505
|
+
* - Joins: `join=relation` (repeatable, from `addIncludes`)
|
|
1506
|
+
* - Field selection (flat): `fields=col1,col2` (from `addSelect`)
|
|
1507
|
+
* - Sorts: `sort=field,ASC` (repeatable, uppercase direction)
|
|
1508
|
+
* - Pagination (page-based): `page=N&limit=N`
|
|
1509
|
+
*
|
|
1510
|
+
* The JSON-shaped `s={...}` search parameter is intentionally out of
|
|
1511
|
+
* scope: it is not a plain search term, so `setSearch` throws
|
|
1512
|
+
* `UnsupportedSearchError` on this driver. PostgREST-native full-text
|
|
1513
|
+
* search operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
|
|
1514
|
+
* `UnsupportedFilterOperatorError`.
|
|
1515
|
+
*
|
|
1516
|
+
* @see https://github.com/nestjsx/crud/wiki/Requests
|
|
1517
|
+
*/
|
|
1518
|
+
class NestjsxCrudRequestStrategy extends AbstractRequestStrategy {
|
|
1519
|
+
/**
|
|
1520
|
+
* Filters, operator filters, joins (`includes`), flat field selection
|
|
1521
|
+
* (`select`), sorts — no per-model fields, no global search (the `s`
|
|
1522
|
+
* parameter is JSON-shaped, not a plain term)
|
|
1523
|
+
*/
|
|
1524
|
+
capabilities = {
|
|
1525
|
+
fields: false,
|
|
1526
|
+
filters: true,
|
|
1527
|
+
includes: true,
|
|
1528
|
+
operatorFilters: true,
|
|
1529
|
+
search: false,
|
|
1530
|
+
select: true,
|
|
1531
|
+
sort: true
|
|
1532
|
+
};
|
|
1533
|
+
/**
|
|
1534
|
+
* @nestjsx/crud-native names of the four hardcoded query keys
|
|
1535
|
+
*
|
|
1536
|
+
* The wire format is fixed (the server's `CrudRequestInterceptor`
|
|
1537
|
+
* reads `fields`, `filter`, `join`, and `sort`); these keys are
|
|
1538
|
+
* intentionally not configurable through `QueryBuilderOptions` and
|
|
1539
|
+
* live as private statics so they are visible in one place.
|
|
1540
|
+
*/
|
|
1541
|
+
static _fieldsKey = 'fields';
|
|
1542
|
+
static _filterKey = 'filter';
|
|
1543
|
+
static _joinKey = 'join';
|
|
1544
|
+
static _sortKey = 'sort';
|
|
1545
|
+
/**
|
|
1546
|
+
* Pipe delimiter separating field, operator, and value segments
|
|
1547
|
+
*/
|
|
1548
|
+
static _separator = '||';
|
|
1549
|
+
/**
|
|
1550
|
+
* Emit @nestjsx/crud-format query-string segments in canonical order:
|
|
1551
|
+
* fields → filters → operator filters → join → sort → limit → page
|
|
1552
|
+
*
|
|
1553
|
+
* @param state - The current query builder state
|
|
1554
|
+
* @param options - The query parameter key name configuration (used
|
|
1555
|
+
* for `page` / `limit`, whose defaults match the wire format; the
|
|
1556
|
+
* `fields` / `filter` / `join` / `sort` keys are fixed by the server)
|
|
1557
|
+
* @returns Ordered query-string fragments
|
|
1558
|
+
*/
|
|
1559
|
+
parts(state, options) {
|
|
1560
|
+
const out = [];
|
|
1561
|
+
this._appendFields(state, out);
|
|
1562
|
+
this._appendFilters(state, out);
|
|
1563
|
+
this._appendOperatorFilters(state, out);
|
|
1564
|
+
this._appendJoin(state, out);
|
|
1565
|
+
this._appendSort(state, out);
|
|
1566
|
+
this._appendLimit(state, options, out);
|
|
1567
|
+
this._appendPage(state, options, out);
|
|
1568
|
+
return out;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Append `fields=col1,col2` from the flat select array
|
|
1572
|
+
*
|
|
1573
|
+
* @param state - The current query builder state
|
|
1574
|
+
* @param out - The accumulator the caller joins into the URI
|
|
1575
|
+
*/
|
|
1576
|
+
_appendFields(state, out) {
|
|
1577
|
+
if (!state.select.length) {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
out.push(`${NestjsxCrudRequestStrategy._fieldsKey}=${state.select.join(',')}`);
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Append simple filters as repeatable `filter=field||$eq||value` params
|
|
1584
|
+
*
|
|
1585
|
+
* Single-value filters fold to `$eq`; multi-value filters fold to
|
|
1586
|
+
* `$in` with comma-joined values.
|
|
1587
|
+
*
|
|
1588
|
+
* @param state - The current query builder state
|
|
1589
|
+
* @param out - The accumulator the caller joins into the URI
|
|
1590
|
+
*/
|
|
1591
|
+
_appendFilters(state, out) {
|
|
1592
|
+
const sep = NestjsxCrudRequestStrategy._separator;
|
|
1593
|
+
Object.keys(state.filters).forEach(field => {
|
|
1594
|
+
const values = state.filters[field];
|
|
1595
|
+
if (!values.length) {
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
const condition = values.length === 1
|
|
1599
|
+
? `$eq${sep}${values[0]}`
|
|
1600
|
+
: `$in${sep}${values.join(',')}`;
|
|
1601
|
+
out.push(`${NestjsxCrudRequestStrategy._filterKey}=${field}${sep}${condition}`);
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* Append `join=relation` params from the includes array
|
|
1606
|
+
*
|
|
1607
|
+
* Per-relation field projection (`join=relation||f1,f2`) is not
|
|
1608
|
+
* expressible through the current state shape and is out of scope.
|
|
1609
|
+
*
|
|
1610
|
+
* @param state - The current query builder state
|
|
1611
|
+
* @param out - The accumulator the caller joins into the URI
|
|
1612
|
+
*/
|
|
1613
|
+
_appendJoin(state, out) {
|
|
1614
|
+
state.includes.forEach(relation => {
|
|
1615
|
+
out.push(`${NestjsxCrudRequestStrategy._joinKey}=${relation}`);
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Append the limit parameter
|
|
1620
|
+
*
|
|
1621
|
+
* @param state - The current query builder state
|
|
1622
|
+
* @param options - The query parameter key name configuration
|
|
1623
|
+
* @param out - The accumulator the caller joins into the URI
|
|
1624
|
+
*/
|
|
1625
|
+
_appendLimit(state, options, out) {
|
|
1626
|
+
out.push(`${options.limit}=${state.limit}`);
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Append operator filters as repeatable `filter=field||$op||value` params
|
|
1630
|
+
*
|
|
1631
|
+
* @param state - The current query builder state
|
|
1632
|
+
* @param out - The accumulator the caller joins into the URI
|
|
1633
|
+
*/
|
|
1634
|
+
_appendOperatorFilters(state, out) {
|
|
1635
|
+
const sep = NestjsxCrudRequestStrategy._separator;
|
|
1636
|
+
state.operatorFilters.forEach((filter) => {
|
|
1637
|
+
const condition = this._formatOperatorCondition(filter);
|
|
1638
|
+
out.push(`${NestjsxCrudRequestStrategy._filterKey}=${filter.field}${sep}${condition}`);
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Append the page parameter
|
|
1643
|
+
*
|
|
1644
|
+
* @param state - The current query builder state
|
|
1645
|
+
* @param options - The query parameter key name configuration
|
|
1646
|
+
* @param out - The accumulator the caller joins into the URI
|
|
1647
|
+
*/
|
|
1648
|
+
_appendPage(state, options, out) {
|
|
1649
|
+
out.push(`${options.page}=${state.page}`);
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Append `sort=field,ASC` params, one per sort rule
|
|
1653
|
+
*
|
|
1654
|
+
* @param state - The current query builder state
|
|
1655
|
+
* @param out - The accumulator the caller joins into the URI
|
|
1656
|
+
*/
|
|
1657
|
+
_appendSort(state, out) {
|
|
1658
|
+
state.sorts.forEach(sort => {
|
|
1659
|
+
const direction = sort.order === SortEnum.DESC ? 'DESC' : 'ASC';
|
|
1660
|
+
out.push(`${NestjsxCrudRequestStrategy._sortKey}=${sort.field},${direction}`);
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Translate a `FilterOperatorEnum` operator filter into @nestjsx/crud's
|
|
1665
|
+
* `$operator||value` condition segment
|
|
1666
|
+
*
|
|
1667
|
+
* The mapping is library-canonical → @nestjsx/crud-native:
|
|
1668
|
+
* - `EQ`/`GT`/`GTE`/`LT`/`LTE`/`IN` → identity (same operator name)
|
|
1669
|
+
* - `CONTAINS` → `$cont`; `ILIKE` → `$contL` (case-insensitive contains)
|
|
1670
|
+
* - `SW` → `$starts`
|
|
1671
|
+
* - `BTW` → `$between` with `min,max` (arity-checked)
|
|
1672
|
+
* - `NOT` → `$ne` (single value) / `$notin` (multi-value)
|
|
1673
|
+
* - `NULL` → `$isnull` (when value is `true`) / `$notnull` (when value
|
|
1674
|
+
* is `false`); both emit **no value segment**; arity- and type-checked
|
|
1675
|
+
*
|
|
1676
|
+
* PostgREST's full-text-search operators (`FTS`, `PHFTS`, `PLFTS`,
|
|
1677
|
+
* `WFTS`) have no @nestjsx/crud equivalent and throw
|
|
1678
|
+
* `UnsupportedFilterOperatorError`.
|
|
1679
|
+
*
|
|
1680
|
+
* @param filter - The operator filter to translate
|
|
1681
|
+
* @returns The `$operator||value` (or bare `$operator`) condition segment
|
|
1682
|
+
* @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
|
|
1683
|
+
* exactly two values, or `NULL` does not receive exactly one boolean
|
|
1684
|
+
* @throws {UnsupportedFilterOperatorError} If the operator is a
|
|
1685
|
+
* PostgREST-only FTS variant
|
|
1686
|
+
*/
|
|
1687
|
+
_formatOperatorCondition(filter) {
|
|
1688
|
+
const sep = NestjsxCrudRequestStrategy._separator;
|
|
1689
|
+
const { operator, values } = filter;
|
|
1690
|
+
const first = values[0];
|
|
1691
|
+
switch (operator) {
|
|
1692
|
+
case FilterOperatorEnum.EQ: return `$eq${sep}${first}`;
|
|
1693
|
+
case FilterOperatorEnum.GT: return `$gt${sep}${first}`;
|
|
1694
|
+
case FilterOperatorEnum.GTE: return `$gte${sep}${first}`;
|
|
1695
|
+
case FilterOperatorEnum.LT: return `$lt${sep}${first}`;
|
|
1696
|
+
case FilterOperatorEnum.LTE: return `$lte${sep}${first}`;
|
|
1697
|
+
case FilterOperatorEnum.CONTAINS: return `$cont${sep}${first}`;
|
|
1698
|
+
case FilterOperatorEnum.ILIKE: return `$contL${sep}${first}`;
|
|
1699
|
+
case FilterOperatorEnum.IN: return `$in${sep}${values.join(',')}`;
|
|
1700
|
+
case FilterOperatorEnum.SW: return `$starts${sep}${first}`;
|
|
1701
|
+
case FilterOperatorEnum.BTW: {
|
|
1702
|
+
if (values.length !== 2) {
|
|
1703
|
+
throw new InvalidFilterOperatorValueError(operator, 'BTW requires exactly 2 values (min, max)');
|
|
1704
|
+
}
|
|
1705
|
+
return `$between${sep}${values.join(',')}`;
|
|
1706
|
+
}
|
|
1707
|
+
case FilterOperatorEnum.NOT:
|
|
1708
|
+
return values.length === 1
|
|
1709
|
+
? `$ne${sep}${first}`
|
|
1710
|
+
: `$notin${sep}${values.join(',')}`;
|
|
1711
|
+
case FilterOperatorEnum.NULL: {
|
|
1712
|
+
if (values.length !== 1 || typeof first !== 'boolean') {
|
|
1713
|
+
throw new InvalidFilterOperatorValueError(operator, 'NULL requires exactly 1 boolean value (true → IS NULL, false → IS NOT NULL)');
|
|
1714
|
+
}
|
|
1715
|
+
return first ? '$isnull' : '$notnull';
|
|
1716
|
+
}
|
|
1717
|
+
case FilterOperatorEnum.FTS:
|
|
1718
|
+
case FilterOperatorEnum.PHFTS:
|
|
1719
|
+
case FilterOperatorEnum.PLFTS:
|
|
1720
|
+
case FilterOperatorEnum.WFTS:
|
|
1721
|
+
throw new UnsupportedFilterOperatorError();
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
/**
|
|
1727
|
+
* Response strategy for the @nestjsx/crud driver
|
|
1728
|
+
*
|
|
1729
|
+
* Parses @nestjsx/crud's `getMany` envelope:
|
|
1730
|
+
* ```json
|
|
1731
|
+
* {
|
|
1732
|
+
* "data": [{ "id": 1, "name": "John" }],
|
|
1733
|
+
* "count": 10,
|
|
1734
|
+
* "total": 48,
|
|
1735
|
+
* "page": 2,
|
|
1736
|
+
* "pageCount": 5
|
|
1737
|
+
* }
|
|
1738
|
+
* ```
|
|
1739
|
+
*
|
|
1740
|
+
* Default key paths are configured in `NestjsxCrudResponseOptions`. The
|
|
1741
|
+
* envelope carries no `from`/`to` indices and no navigation links, so
|
|
1742
|
+
* `from`/`to` are computed from `page` × `count` by the inherited
|
|
1743
|
+
* traversal algorithm and the URL slots resolve to `undefined` unless
|
|
1744
|
+
* the consumer overrides their paths via `IPaginationConfig`.
|
|
1745
|
+
*
|
|
1746
|
+
* Note that `count` is the number of entities **on the current page**,
|
|
1747
|
+
* not the requested page size — on the last page of a result set the
|
|
1748
|
+
* derived `from`/`to` can underestimate. Consumers needing exact
|
|
1749
|
+
* indices should compute them from the requested limit instead.
|
|
1750
|
+
*
|
|
1751
|
+
* The dot-notation traversal is inherited from
|
|
1752
|
+
* `AbstractDotPathResponseStrategy`; this class exists so
|
|
1753
|
+
* `DriverEnum.NESTJSX_CRUD` resolves to a distinct identity at the DI
|
|
1754
|
+
* layer even though the parsing logic is shared with JSON:API, NestJS,
|
|
1755
|
+
* and Strapi.
|
|
1756
|
+
*
|
|
1757
|
+
* @see https://github.com/nestjsx/crud/wiki/Requests
|
|
1758
|
+
*/
|
|
1759
|
+
class NestjsxCrudResponseStrategy extends AbstractDotPathResponseStrategy {
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1403
1762
|
/**
|
|
1404
1763
|
* Enum representing the wire-level pagination mechanism
|
|
1405
1764
|
*
|
|
@@ -1786,6 +2145,228 @@ class PostgrestResponseStrategy {
|
|
|
1786
2145
|
}
|
|
1787
2146
|
}
|
|
1788
2147
|
|
|
2148
|
+
/**
|
|
2149
|
+
* Request strategy for the Sieve (.NET) driver
|
|
2150
|
+
*
|
|
2151
|
+
* Generates URIs in [Sieve's compact expression format](https://github.com/Biarity/Sieve):
|
|
2152
|
+
* - Filters: a single `filters=` parameter holding comma-joined (AND)
|
|
2153
|
+
* `Field{op}Value` terms; multi-value terms use the pipe (OR) on the
|
|
2154
|
+
* value side (`status==active|pending`)
|
|
2155
|
+
* - Operator filters: translated from `FilterOperatorEnum` — see the
|
|
2156
|
+
* mapping on `_formatOperatorTerms`
|
|
2157
|
+
* - Sorts: `sorts=field,-other` (CSV, `-` prefix = DESC)
|
|
2158
|
+
* - Pagination: `page=N&pageSize=N`
|
|
2159
|
+
*
|
|
2160
|
+
* Sieve has no per-model field selection, no relation includes, no flat
|
|
2161
|
+
* column selection, and no global search parameter — the corresponding
|
|
2162
|
+
* fluent methods throw the matching `Unsupported*Error`. PostgREST-native
|
|
2163
|
+
* full-text search operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
|
|
2164
|
+
* `UnsupportedFilterOperatorError`.
|
|
2165
|
+
*
|
|
2166
|
+
* @see https://github.com/Biarity/Sieve
|
|
2167
|
+
*/
|
|
2168
|
+
class SieveRequestStrategy extends AbstractRequestStrategy {
|
|
2169
|
+
/**
|
|
2170
|
+
* Filters, operator filters, sorts — no per-model fields, no includes,
|
|
2171
|
+
* no flat select, no global search (use `CONTAINS` / `ILIKE` operator
|
|
2172
|
+
* filters for partial matches)
|
|
2173
|
+
*/
|
|
2174
|
+
capabilities = {
|
|
2175
|
+
fields: false,
|
|
2176
|
+
filters: true,
|
|
2177
|
+
includes: false,
|
|
2178
|
+
operatorFilters: true,
|
|
2179
|
+
search: false,
|
|
2180
|
+
select: false,
|
|
2181
|
+
sort: true
|
|
2182
|
+
};
|
|
2183
|
+
/**
|
|
2184
|
+
* Sieve-native names of the three hardcoded query keys
|
|
2185
|
+
*
|
|
2186
|
+
* Sieve's model binder reads `filters`, `sorts`, and `pageSize` (the
|
|
2187
|
+
* plural forms differ from the library-wide `filter` / `sort` /
|
|
2188
|
+
* `limit` defaults); these keys are intentionally not configurable
|
|
2189
|
+
* through `QueryBuilderOptions` and live as private statics so they
|
|
2190
|
+
* are visible in one place.
|
|
2191
|
+
*/
|
|
2192
|
+
static _filtersKey = 'filters';
|
|
2193
|
+
static _pageSizeKey = 'pageSize';
|
|
2194
|
+
static _sortsKey = 'sorts';
|
|
2195
|
+
/**
|
|
2196
|
+
* Emit Sieve-format query-string segments in canonical order:
|
|
2197
|
+
* filters → sorts → page → pageSize
|
|
2198
|
+
*
|
|
2199
|
+
* @param state - The current query builder state
|
|
2200
|
+
* @param options - The query parameter key name configuration (used
|
|
2201
|
+
* for `page`, whose default matches the wire format; the `filters` /
|
|
2202
|
+
* `sorts` / `pageSize` keys are fixed by the server)
|
|
2203
|
+
* @returns Ordered query-string fragments
|
|
2204
|
+
*/
|
|
2205
|
+
parts(state, options) {
|
|
2206
|
+
const out = [];
|
|
2207
|
+
this._appendFilters(state, out);
|
|
2208
|
+
this._appendSorts(state, out);
|
|
2209
|
+
this._appendPage(state, options, out);
|
|
2210
|
+
this._appendPageSize(state, out);
|
|
2211
|
+
return out;
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Append the single `filters=` parameter combining simple and operator
|
|
2215
|
+
* filters
|
|
2216
|
+
*
|
|
2217
|
+
* Each term is one `Field{op}Value` expression; terms join with the
|
|
2218
|
+
* comma (Sieve's AND). Simple single-value filters fold to `==`;
|
|
2219
|
+
* simple multi-value filters fold to a value-level pipe OR
|
|
2220
|
+
* (`field==v1|v2`).
|
|
2221
|
+
*
|
|
2222
|
+
* @param state - The current query builder state
|
|
2223
|
+
* @param out - The accumulator the caller joins into the URI
|
|
2224
|
+
*/
|
|
2225
|
+
_appendFilters(state, out) {
|
|
2226
|
+
const terms = [];
|
|
2227
|
+
Object.keys(state.filters).forEach(field => {
|
|
2228
|
+
const values = state.filters[field];
|
|
2229
|
+
if (!values.length) {
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
terms.push(`${field}==${values.join('|')}`);
|
|
2233
|
+
});
|
|
2234
|
+
state.operatorFilters.forEach((filter) => {
|
|
2235
|
+
terms.push(...this._formatOperatorTerms(filter));
|
|
2236
|
+
});
|
|
2237
|
+
if (!terms.length) {
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
out.push(`${SieveRequestStrategy._filtersKey}=${terms.join(',')}`);
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Append the page parameter
|
|
2244
|
+
*
|
|
2245
|
+
* @param state - The current query builder state
|
|
2246
|
+
* @param options - The query parameter key name configuration
|
|
2247
|
+
* @param out - The accumulator the caller joins into the URI
|
|
2248
|
+
*/
|
|
2249
|
+
_appendPage(state, options, out) {
|
|
2250
|
+
out.push(`${options.page}=${state.page}`);
|
|
2251
|
+
}
|
|
2252
|
+
/**
|
|
2253
|
+
* Append the pageSize parameter
|
|
2254
|
+
*
|
|
2255
|
+
* @param state - The current query builder state
|
|
2256
|
+
* @param out - The accumulator the caller joins into the URI
|
|
2257
|
+
*/
|
|
2258
|
+
_appendPageSize(state, out) {
|
|
2259
|
+
out.push(`${SieveRequestStrategy._pageSizeKey}=${state.limit}`);
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Append the `sorts=field,-other` CSV parameter
|
|
2263
|
+
*
|
|
2264
|
+
* @param state - The current query builder state
|
|
2265
|
+
* @param out - The accumulator the caller joins into the URI
|
|
2266
|
+
*/
|
|
2267
|
+
_appendSorts(state, out) {
|
|
2268
|
+
if (!state.sorts.length) {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
const fields = state.sorts.map(sort => sort.order === SortEnum.DESC ? `-${sort.field}` : sort.field);
|
|
2272
|
+
out.push(`${SieveRequestStrategy._sortsKey}=${fields.join(',')}`);
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* Translate a `FilterOperatorEnum` operator filter into one or more
|
|
2276
|
+
* Sieve `Field{op}Value` terms
|
|
2277
|
+
*
|
|
2278
|
+
* The mapping is library-canonical → Sieve-native:
|
|
2279
|
+
* - `EQ` → `==`; `GT`/`GTE`/`LT`/`LTE` → `>` / `>=` / `<` / `<=`
|
|
2280
|
+
* - `CONTAINS` → `@=`; `ILIKE` → `@=*` (case-insensitive contains)
|
|
2281
|
+
* - `SW` → `_=` (starts with)
|
|
2282
|
+
* - `IN` → `==` with a value-level pipe OR (`field==v1|v2`)
|
|
2283
|
+
* - `BTW` → **two** AND-ed terms (`field>=min` and `field<=max`,
|
|
2284
|
+
* arity-checked)
|
|
2285
|
+
* - `NOT` → `!=` — one term per value, AND-ed (`field!=v1,field!=v2`)
|
|
2286
|
+
* - `NULL` → `==null` (when value is `true`) / `!=null` (when value is
|
|
2287
|
+
* `false`); arity- and type-checked
|
|
2288
|
+
*
|
|
2289
|
+
* PostgREST's full-text-search operators (`FTS`, `PHFTS`, `PLFTS`,
|
|
2290
|
+
* `WFTS`) have no Sieve equivalent and throw
|
|
2291
|
+
* `UnsupportedFilterOperatorError`.
|
|
2292
|
+
*
|
|
2293
|
+
* @param filter - The operator filter to translate
|
|
2294
|
+
* @returns One or more `Field{op}Value` terms ready to AND-join
|
|
2295
|
+
* @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
|
|
2296
|
+
* exactly two values, or `NULL` does not receive exactly one boolean
|
|
2297
|
+
* @throws {UnsupportedFilterOperatorError} If the operator is a
|
|
2298
|
+
* PostgREST-only FTS variant
|
|
2299
|
+
*/
|
|
2300
|
+
_formatOperatorTerms(filter) {
|
|
2301
|
+
const { field, operator, values } = filter;
|
|
2302
|
+
const first = values[0];
|
|
2303
|
+
switch (operator) {
|
|
2304
|
+
case FilterOperatorEnum.EQ: return [`${field}==${first}`];
|
|
2305
|
+
case FilterOperatorEnum.GT: return [`${field}>${first}`];
|
|
2306
|
+
case FilterOperatorEnum.GTE: return [`${field}>=${first}`];
|
|
2307
|
+
case FilterOperatorEnum.LT: return [`${field}<${first}`];
|
|
2308
|
+
case FilterOperatorEnum.LTE: return [`${field}<=${first}`];
|
|
2309
|
+
case FilterOperatorEnum.CONTAINS: return [`${field}@=${first}`];
|
|
2310
|
+
case FilterOperatorEnum.ILIKE: return [`${field}@=*${first}`];
|
|
2311
|
+
case FilterOperatorEnum.SW: return [`${field}_=${first}`];
|
|
2312
|
+
case FilterOperatorEnum.IN: return [`${field}==${values.join('|')}`];
|
|
2313
|
+
case FilterOperatorEnum.BTW: {
|
|
2314
|
+
if (values.length !== 2) {
|
|
2315
|
+
throw new InvalidFilterOperatorValueError(operator, 'BTW requires exactly 2 values (min, max)');
|
|
2316
|
+
}
|
|
2317
|
+
return [`${field}>=${values[0]}`, `${field}<=${values[1]}`];
|
|
2318
|
+
}
|
|
2319
|
+
case FilterOperatorEnum.NOT:
|
|
2320
|
+
return values.map(value => `${field}!=${value}`);
|
|
2321
|
+
case FilterOperatorEnum.NULL: {
|
|
2322
|
+
if (values.length !== 1 || typeof first !== 'boolean') {
|
|
2323
|
+
throw new InvalidFilterOperatorValueError(operator, 'NULL requires exactly 1 boolean value (true → IS NULL, false → IS NOT NULL)');
|
|
2324
|
+
}
|
|
2325
|
+
return first ? [`${field}==null`] : [`${field}!=null`];
|
|
2326
|
+
}
|
|
2327
|
+
case FilterOperatorEnum.FTS:
|
|
2328
|
+
case FilterOperatorEnum.PHFTS:
|
|
2329
|
+
case FilterOperatorEnum.PLFTS:
|
|
2330
|
+
case FilterOperatorEnum.WFTS:
|
|
2331
|
+
throw new UnsupportedFilterOperatorError();
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
/**
|
|
2337
|
+
* Response strategy for the Sieve (.NET) driver
|
|
2338
|
+
*
|
|
2339
|
+
* Sieve itself does not define a response envelope — it returns an
|
|
2340
|
+
* `IQueryable` that the ASP.NET developer wraps in a paging DTO of their
|
|
2341
|
+
* choosing. This strategy therefore ships a **sensible default mapping**
|
|
2342
|
+
* for the common hand-rolled `PagedResult<T>` shape:
|
|
2343
|
+
* ```json
|
|
2344
|
+
* {
|
|
2345
|
+
* "data": [{ "id": 1, "title": "Hello" }],
|
|
2346
|
+
* "page": 2,
|
|
2347
|
+
* "pageSize": 10,
|
|
2348
|
+
* "total": 48,
|
|
2349
|
+
* "totalPages": 5
|
|
2350
|
+
* }
|
|
2351
|
+
* ```
|
|
2352
|
+
*
|
|
2353
|
+
* Every key path is configurable through `IConfig.response` (dot
|
|
2354
|
+
* notation supported), so any wrapper shape — `{ items, meta: {...} }`,
|
|
2355
|
+
* `{ results, pagination: {...} }` — can be mapped without subclassing.
|
|
2356
|
+
* Defaults are encoded in `SieveResponseOptions`. `from`/`to` are
|
|
2357
|
+
* computed from `page` × `pageSize` by the inherited traversal
|
|
2358
|
+
* algorithm, and the navigation-URL slots resolve to `undefined` unless
|
|
2359
|
+
* paths are provided.
|
|
2360
|
+
*
|
|
2361
|
+
* The dot-notation traversal is inherited from
|
|
2362
|
+
* `AbstractDotPathResponseStrategy`; this class exists so
|
|
2363
|
+
* `DriverEnum.SIEVE` resolves to a distinct identity at the DI layer.
|
|
2364
|
+
*
|
|
2365
|
+
* @see https://github.com/Biarity/Sieve
|
|
2366
|
+
*/
|
|
2367
|
+
class SieveResponseStrategy extends AbstractDotPathResponseStrategy {
|
|
2368
|
+
}
|
|
2369
|
+
|
|
1789
2370
|
/**
|
|
1790
2371
|
* Request strategy for the Spatie Query Builder driver
|
|
1791
2372
|
*
|
|
@@ -1956,6 +2537,209 @@ class SpatieRequestStrategy extends AbstractRequestStrategy {
|
|
|
1956
2537
|
class SpatieResponseStrategy extends AbstractFlatResponseStrategy {
|
|
1957
2538
|
}
|
|
1958
2539
|
|
|
2540
|
+
/**
|
|
2541
|
+
* Request strategy for the Spring Data REST driver
|
|
2542
|
+
*
|
|
2543
|
+
* Generates URIs in [Spring Data REST's pagination format](https://docs.spring.io/spring-data/rest/reference/paging-and-sorting.html):
|
|
2544
|
+
* - Sorts: `sort=field,asc&sort=other,desc` (repeatable param, one per rule)
|
|
2545
|
+
* - Pagination: `page=N&size=N` — **`page` is 0-indexed on the wire**;
|
|
2546
|
+
* the library state stays 1-indexed and the strategy subtracts 1 at
|
|
2547
|
+
* emission time
|
|
2548
|
+
*
|
|
2549
|
+
* Spring Data REST defines no standard query parameter convention for
|
|
2550
|
+
* filtering, field selection, includes, or global search — those are
|
|
2551
|
+
* implemented server-side via custom query methods or Specifications.
|
|
2552
|
+
* The corresponding fluent methods (`addFilter`, `addFilterOperator`,
|
|
2553
|
+
* `addSelect`, `addFields`, `addIncludes`, `setSearch`) throw the
|
|
2554
|
+
* matching `Unsupported*Error` on this driver.
|
|
2555
|
+
*
|
|
2556
|
+
* @see https://docs.spring.io/spring-data/rest/reference/paging-and-sorting.html
|
|
2557
|
+
*/
|
|
2558
|
+
class SpringRequestStrategy extends AbstractRequestStrategy {
|
|
2559
|
+
/**
|
|
2560
|
+
* Sorts only — Spring Data REST has no standard wire convention for
|
|
2561
|
+
* filters, operator filters, per-model fields, flat select, includes,
|
|
2562
|
+
* or global search
|
|
2563
|
+
*/
|
|
2564
|
+
capabilities = {
|
|
2565
|
+
fields: false,
|
|
2566
|
+
filters: false,
|
|
2567
|
+
includes: false,
|
|
2568
|
+
operatorFilters: false,
|
|
2569
|
+
search: false,
|
|
2570
|
+
select: false,
|
|
2571
|
+
sort: true
|
|
2572
|
+
};
|
|
2573
|
+
/**
|
|
2574
|
+
* Spring-native name of the hardcoded page-size query key
|
|
2575
|
+
*
|
|
2576
|
+
* The wire format is fixed (Spring's `PageableHandlerMethodArgumentResolver`
|
|
2577
|
+
* reads `size` by default); the key is intentionally not configurable
|
|
2578
|
+
* through `QueryBuilderOptions` and lives as a private static so it is
|
|
2579
|
+
* visible in one place.
|
|
2580
|
+
*/
|
|
2581
|
+
static _sizeKey = 'size';
|
|
2582
|
+
/**
|
|
2583
|
+
* Emit Spring-format query-string segments in canonical order:
|
|
2584
|
+
* sort → page → size
|
|
2585
|
+
*
|
|
2586
|
+
* @param state - The current query builder state
|
|
2587
|
+
* @param options - The query parameter key name configuration (used
|
|
2588
|
+
* for `page` and `sort`, whose defaults match the wire format; the
|
|
2589
|
+
* `size` key is fixed by the server)
|
|
2590
|
+
* @returns Ordered query-string fragments
|
|
2591
|
+
*/
|
|
2592
|
+
parts(state, options) {
|
|
2593
|
+
const out = [];
|
|
2594
|
+
this._appendSort(state, options, out);
|
|
2595
|
+
this._appendPage(state, options, out);
|
|
2596
|
+
this._appendSize(state, out);
|
|
2597
|
+
return out;
|
|
2598
|
+
}
|
|
2599
|
+
/**
|
|
2600
|
+
* Append the 0-indexed page parameter
|
|
2601
|
+
*
|
|
2602
|
+
* The library state is 1-indexed (page 1 is the first page); Spring's
|
|
2603
|
+
* `page` request parameter is 0-indexed, so the strategy subtracts 1
|
|
2604
|
+
* at emission time.
|
|
2605
|
+
*
|
|
2606
|
+
* @param state - The current query builder state
|
|
2607
|
+
* @param options - The query parameter key name configuration
|
|
2608
|
+
* @param out - The accumulator the caller joins into the URI
|
|
2609
|
+
*/
|
|
2610
|
+
_appendPage(state, options, out) {
|
|
2611
|
+
out.push(`${options.page}=${state.page - 1}`);
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Append the size parameter
|
|
2615
|
+
*
|
|
2616
|
+
* @param state - The current query builder state
|
|
2617
|
+
* @param out - The accumulator the caller joins into the URI
|
|
2618
|
+
*/
|
|
2619
|
+
_appendSize(state, out) {
|
|
2620
|
+
out.push(`${SpringRequestStrategy._sizeKey}=${state.limit}`);
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* Append `sort=field,asc` params, one per sort rule (repeatable)
|
|
2624
|
+
*
|
|
2625
|
+
* Spring parses each `sort` occurrence independently — multiple rules
|
|
2626
|
+
* are expressed by repeating the parameter, not by comma-joining the
|
|
2627
|
+
* fields.
|
|
2628
|
+
*
|
|
2629
|
+
* @param state - The current query builder state
|
|
2630
|
+
* @param options - The query parameter key name configuration
|
|
2631
|
+
* @param out - The accumulator the caller joins into the URI
|
|
2632
|
+
*/
|
|
2633
|
+
_appendSort(state, options, out) {
|
|
2634
|
+
state.sorts.forEach(sort => {
|
|
2635
|
+
const direction = sort.order === SortEnum.DESC ? 'desc' : 'asc';
|
|
2636
|
+
out.push(`${options.sort}=${sort.field},${direction}`);
|
|
2637
|
+
});
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
/**
|
|
2642
|
+
* Response strategy for the Spring Data REST driver
|
|
2643
|
+
*
|
|
2644
|
+
* Parses Spring Data REST's HAL envelope:
|
|
2645
|
+
* ```json
|
|
2646
|
+
* {
|
|
2647
|
+
* "_embedded": { "users": [{ "id": 1, "name": "John" }] },
|
|
2648
|
+
* "_links": {
|
|
2649
|
+
* "first": { "href": "..." },
|
|
2650
|
+
* "prev": { "href": "..." },
|
|
2651
|
+
* "next": { "href": "..." },
|
|
2652
|
+
* "last": { "href": "..." }
|
|
2653
|
+
* },
|
|
2654
|
+
* "page": { "size": 20, "totalElements": 100, "totalPages": 5, "number": 1 }
|
|
2655
|
+
* }
|
|
2656
|
+
* ```
|
|
2657
|
+
*
|
|
2658
|
+
* Two HAL quirks are absorbed here on top of the inherited dot-path
|
|
2659
|
+
* traversal:
|
|
2660
|
+
*
|
|
2661
|
+
* - **`page.number` is 0-indexed** — the strategy adds 1 so the library
|
|
2662
|
+
* state stays 1-indexed (mirroring `SpringRequestStrategy`, which
|
|
2663
|
+
* subtracts 1 on the way out).
|
|
2664
|
+
* - **The collection key under `_embedded` is the resource rel name**
|
|
2665
|
+
* (e.g. `_embedded.users`), which cannot be known statically. The
|
|
2666
|
+
* default `data` path is plain `_embedded`; when it resolves to an
|
|
2667
|
+
* object rather than an array, the strategy picks the first array
|
|
2668
|
+
* value inside it. Consumers with multiple embedded rels can pin the
|
|
2669
|
+
* exact path via `IConfig.response` (e.g. `data: '_embedded.users'`).
|
|
2670
|
+
*
|
|
2671
|
+
* Default key paths are configured in `SpringResponseOptions`.
|
|
2672
|
+
*
|
|
2673
|
+
* @see https://docs.spring.io/spring-data/rest/reference/paging-and-sorting.html
|
|
2674
|
+
*/
|
|
2675
|
+
class SpringResponseStrategy extends AbstractDotPathResponseStrategy {
|
|
2676
|
+
/**
|
|
2677
|
+
* Parse a Spring Data REST HAL response into a PaginatedCollection
|
|
2678
|
+
*
|
|
2679
|
+
* @param response - The raw API response body
|
|
2680
|
+
* @param options - The response key name configuration
|
|
2681
|
+
* @returns A typed PaginatedCollection instance
|
|
2682
|
+
*/
|
|
2683
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2684
|
+
paginate(response, options) {
|
|
2685
|
+
const data = this._resolveData(response, options);
|
|
2686
|
+
const currentPage = this._resolveCurrentPage(response, options);
|
|
2687
|
+
const total = this.resolve(response, options.total);
|
|
2688
|
+
const perPage = this.resolve(response, options.perPage);
|
|
2689
|
+
const lastPage = this.resolve(response, options.lastPage);
|
|
2690
|
+
const from = this.resolveFrom(response, options, currentPage, perPage);
|
|
2691
|
+
const to = this.resolveTo(response, options, currentPage, perPage, total);
|
|
2692
|
+
const prevPageUrl = this.resolve(response, options.prevPageUrl);
|
|
2693
|
+
const nextPageUrl = this.resolve(response, options.nextPageUrl);
|
|
2694
|
+
const firstPageUrl = this.resolve(response, options.firstPageUrl);
|
|
2695
|
+
const lastPageUrl = this.resolve(response, options.lastPageUrl);
|
|
2696
|
+
return new PaginatedCollection(data, currentPage, from, to, total, perPage, prevPageUrl, nextPageUrl, lastPage, firstPageUrl, lastPageUrl);
|
|
2697
|
+
}
|
|
2698
|
+
/**
|
|
2699
|
+
* Resolve the 1-indexed current page from the 0-indexed `page.number`
|
|
2700
|
+
*
|
|
2701
|
+
* Falls back to page 1 when the path is missing entirely (defensive —
|
|
2702
|
+
* Spring always emits the `page` block on paged endpoints).
|
|
2703
|
+
*
|
|
2704
|
+
* @param response - The raw response object
|
|
2705
|
+
* @param options - The response key name configuration
|
|
2706
|
+
* @returns The 1-indexed current page number
|
|
2707
|
+
*/
|
|
2708
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2709
|
+
_resolveCurrentPage(response, options) {
|
|
2710
|
+
const pageNumber = this.resolve(response, options.currentPage);
|
|
2711
|
+
return (pageNumber ?? 0) + 1;
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Resolve the data array from the HAL `_embedded` wrapper
|
|
2715
|
+
*
|
|
2716
|
+
* When the configured path resolves directly to an array (a consumer
|
|
2717
|
+
* pinned `data: '_embedded.users'`), it is used as-is. When it
|
|
2718
|
+
* resolves to an object (the default `_embedded` path), the first
|
|
2719
|
+
* array value inside it is used — Spring emits exactly one collection
|
|
2720
|
+
* rel per listing endpoint. An empty array is returned when nothing
|
|
2721
|
+
* matches (e.g. Spring omits `_embedded` on empty result sets).
|
|
2722
|
+
*
|
|
2723
|
+
* @param response - The raw response object
|
|
2724
|
+
* @param options - The response key name configuration
|
|
2725
|
+
* @returns The resolved data array (possibly empty)
|
|
2726
|
+
*/
|
|
2727
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2728
|
+
_resolveData(response, options) {
|
|
2729
|
+
const raw = this.resolve(response, options.data);
|
|
2730
|
+
if (Array.isArray(raw)) {
|
|
2731
|
+
return raw;
|
|
2732
|
+
}
|
|
2733
|
+
if (raw && typeof raw === 'object') {
|
|
2734
|
+
const firstArray = Object.values(raw).find(value => Array.isArray(value));
|
|
2735
|
+
if (firstArray) {
|
|
2736
|
+
return firstArray;
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
return [];
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
|
|
1959
2743
|
/**
|
|
1960
2744
|
* Request strategy for the Strapi driver
|
|
1961
2745
|
*
|
|
@@ -2256,16 +3040,31 @@ const DRIVERS = {
|
|
|
2256
3040
|
createResponseStrategy: () => new NestjsResponseStrategy(),
|
|
2257
3041
|
createResponseOptions: (config) => new NestjsResponseOptions(config)
|
|
2258
3042
|
},
|
|
3043
|
+
[DriverEnum.NESTJSX_CRUD]: {
|
|
3044
|
+
createRequestStrategy: () => new NestjsxCrudRequestStrategy(),
|
|
3045
|
+
createResponseStrategy: () => new NestjsxCrudResponseStrategy(),
|
|
3046
|
+
createResponseOptions: (config) => new NestjsxCrudResponseOptions(config)
|
|
3047
|
+
},
|
|
2259
3048
|
[DriverEnum.POSTGREST]: {
|
|
2260
3049
|
createRequestStrategy: (mode) => new PostgrestRequestStrategy(mode),
|
|
2261
3050
|
createResponseStrategy: () => new PostgrestResponseStrategy(),
|
|
2262
3051
|
createResponseOptions: (config) => new ResponseOptions(config)
|
|
2263
3052
|
},
|
|
3053
|
+
[DriverEnum.SIEVE]: {
|
|
3054
|
+
createRequestStrategy: () => new SieveRequestStrategy(),
|
|
3055
|
+
createResponseStrategy: () => new SieveResponseStrategy(),
|
|
3056
|
+
createResponseOptions: (config) => new SieveResponseOptions(config)
|
|
3057
|
+
},
|
|
2264
3058
|
[DriverEnum.SPATIE]: {
|
|
2265
3059
|
createRequestStrategy: () => new SpatieRequestStrategy(),
|
|
2266
3060
|
createResponseStrategy: () => new SpatieResponseStrategy(),
|
|
2267
3061
|
createResponseOptions: (config) => new ResponseOptions(config)
|
|
2268
3062
|
},
|
|
3063
|
+
[DriverEnum.SPRING]: {
|
|
3064
|
+
createRequestStrategy: () => new SpringRequestStrategy(),
|
|
3065
|
+
createResponseStrategy: () => new SpringResponseStrategy(),
|
|
3066
|
+
createResponseOptions: (config) => new SpringResponseOptions(config)
|
|
3067
|
+
},
|
|
2269
3068
|
[DriverEnum.STRAPI]: {
|
|
2270
3069
|
createRequestStrategy: () => new StrapiRequestStrategy(),
|
|
2271
3070
|
createResponseStrategy: () => new StrapiResponseStrategy(),
|
|
@@ -3626,5 +4425,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
|
|
|
3626
4425
|
* Generated bundle index. Do not edit.
|
|
3627
4426
|
*/
|
|
3628
4427
|
|
|
3629
|
-
export { DriverEnum, FilterOperatorEnum, InvalidFilterOperatorValueError, 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, PaginationModeEnum, PaginationNotSyncedError, PaginationService, PostgrestRequestStrategy, PostgrestResponseStrategy, SortEnum, SpatieRequestStrategy, SpatieResponseStrategy, StrapiRequestStrategy, StrapiResponseStrategy, UnselectableModelError, UnsupportedFieldSelectionError, UnsupportedFilterError, UnsupportedFilterOperatorError, UnsupportedIncludesError, UnsupportedSearchError, UnsupportedSelectError, UnsupportedSortError, buildNgQubeeProviders, provideNgQubee, provideNgQubeeInstance, readHeader };
|
|
4428
|
+
export { DrfRequestStrategy, DrfResponseStrategy, DriverEnum, FilterOperatorEnum, InvalidFilterOperatorValueError, 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, NestjsxCrudRequestStrategy, NestjsxCrudResponseStrategy, NgQubeeModule, NgQubeeService, PaginatedCollection, PaginationModeEnum, PaginationNotSyncedError, PaginationService, PostgrestRequestStrategy, PostgrestResponseStrategy, SieveRequestStrategy, SieveResponseStrategy, SortEnum, SpatieRequestStrategy, SpatieResponseStrategy, SpringRequestStrategy, SpringResponseStrategy, StrapiRequestStrategy, StrapiResponseStrategy, UnselectableModelError, UnsupportedFieldSelectionError, UnsupportedFilterError, UnsupportedFilterOperatorError, UnsupportedIncludesError, UnsupportedSearchError, UnsupportedSelectError, UnsupportedSortError, buildNgQubeeProviders, provideNgQubee, provideNgQubeeInstance, readHeader };
|
|
3630
4429
|
//# sourceMappingURL=ng-qubee.mjs.map
|