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.
@@ -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