ng-qubee 3.3.0 → 3.4.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.
@@ -79,6 +79,7 @@ var DriverEnum;
79
79
  DriverEnum["NESTJS"] = "nestjs";
80
80
  DriverEnum["POSTGREST"] = "postgrest";
81
81
  DriverEnum["SPATIE"] = "spatie";
82
+ DriverEnum["STRAPI"] = "strapi";
82
83
  })(DriverEnum || (DriverEnum = {}));
83
84
 
84
85
  /**
@@ -171,6 +172,32 @@ class NestjsResponseOptions extends ResponseOptions {
171
172
  });
172
173
  }
173
174
  }
175
+ /**
176
+ * Pre-configured ResponseOptions for the Strapi driver
177
+ *
178
+ * Uses dot-notation paths to access the nested `meta.pagination.*` envelope
179
+ * Strapi v4/v5 emits. Strapi does not include navigation links by default,
180
+ * so the URL paths point at locations that will resolve to `undefined`
181
+ * unless the consumer overrides them.
182
+ */
183
+ class StrapiResponseOptions extends ResponseOptions {
184
+ constructor(options) {
185
+ super({
186
+ currentPage: options.currentPage || 'meta.pagination.page',
187
+ data: options.data || 'data',
188
+ firstPageUrl: options.firstPageUrl || 'links.first',
189
+ from: options.from || 'meta.pagination.from',
190
+ lastPage: options.lastPage || 'meta.pagination.pageCount',
191
+ lastPageUrl: options.lastPageUrl || 'links.last',
192
+ nextPageUrl: options.nextPageUrl || 'links.next',
193
+ path: options.path || 'path',
194
+ perPage: options.perPage || 'meta.pagination.pageSize',
195
+ prevPageUrl: options.prevPageUrl || 'links.prev',
196
+ to: options.to || 'meta.pagination.to',
197
+ total: options.total || 'meta.pagination.total'
198
+ });
199
+ }
200
+ }
174
201
 
175
202
  var SortEnum;
176
203
  (function (SortEnum) {
@@ -1444,6 +1471,288 @@ class SpatieResponseStrategy {
1444
1471
  }
1445
1472
  }
1446
1473
 
1474
+ /**
1475
+ * Error thrown when filter operators are attempted with a driver that does not support them
1476
+ *
1477
+ * Filter operators are only supported by the NestJS driver.
1478
+ * Use `addFilter()` for Spatie implicit equality filters.
1479
+ */
1480
+ class UnsupportedFilterOperatorError extends Error {
1481
+ constructor() {
1482
+ super('Filter operators are only supported by the NestJS driver. Use addFilter() for Spatie.');
1483
+ this.name = 'UnsupportedFilterOperatorError';
1484
+ }
1485
+ }
1486
+
1487
+ /**
1488
+ * Request strategy for the Strapi driver
1489
+ *
1490
+ * Generates URIs in [Strapi's filter API format](https://docs.strapi.io/dev-docs/api/rest/filters-locale-publication):
1491
+ * - Filters: `filters[field][$eq]=value` (multi-value collapses to `$in`)
1492
+ * - Operator filters: `filters[field][$op]=value` (translated from
1493
+ * `FilterOperatorEnum` — `BTW`→`$between`, `SW`→`$startsWith`,
1494
+ * `ILIKE`→`$containsi`, `NOT`→`$ne`/`$notIn`,
1495
+ * `NULL`→`$null`/`$notNull`)
1496
+ * - Sorts: `sort[0]=field:asc&sort[1]=field:desc`
1497
+ * - Field selection (flat): `fields[0]=col1&fields[1]=col2`
1498
+ * - Population: `populate[0]=relation`
1499
+ * - Pagination (page-based): `pagination[page]=N&pagination[pageSize]=N`
1500
+ *
1501
+ * Strapi-native full-text search (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) is
1502
+ * PostgREST-only and throws `UnsupportedFilterOperatorError` here.
1503
+ *
1504
+ * @see https://docs.strapi.io/dev-docs/api/rest/filters-locale-publication
1505
+ */
1506
+ class StrapiRequestStrategy extends AbstractRequestStrategy {
1507
+ /**
1508
+ * Filters, operator filters, sorts, populate (`includes`), flat field
1509
+ * selection (`select`) — no per-model fields, no global search (use
1510
+ * `$contains` / `$containsi` operator filters instead)
1511
+ */
1512
+ capabilities = {
1513
+ fields: false,
1514
+ filters: true,
1515
+ includes: true,
1516
+ operatorFilters: true,
1517
+ search: false,
1518
+ select: true,
1519
+ sort: true
1520
+ };
1521
+ /**
1522
+ * Strapi-native names of the four hardcoded query keys
1523
+ *
1524
+ * Strapi's wire format is fixed (the server reads `pagination[page]`,
1525
+ * `populate`, `sort`, `fields`); these keys are intentionally not
1526
+ * configurable through `QueryBuilderOptions` and live as private
1527
+ * statics so they are visible in one place.
1528
+ */
1529
+ static _fieldsKey = 'fields';
1530
+ static _paginationKey = 'pagination';
1531
+ static _populateKey = 'populate';
1532
+ static _sortKey = 'sort';
1533
+ /**
1534
+ * Emit Strapi-format query-string segments in canonical order:
1535
+ * populate → fields → filters (merged) → sort → pagination
1536
+ *
1537
+ * Simple filters and operator filters share a single `filters` wrapper
1538
+ * so qs emits one ordered, deeply-nested bracket structure rather than
1539
+ * two duplicate top-level `filters[...]` blocks.
1540
+ *
1541
+ * @param state - The current query builder state
1542
+ * @param _options - The query parameter key name configuration (unused;
1543
+ * Strapi's wire keys are fixed by the server)
1544
+ * @returns Ordered query-string fragments
1545
+ */
1546
+ parts(state, _options) {
1547
+ const out = [];
1548
+ this._appendPopulate(state, out);
1549
+ this._appendFields(state, out);
1550
+ this._appendFilters(state, out);
1551
+ this._appendSort(state, out);
1552
+ this._appendPagination(state, out);
1553
+ return out;
1554
+ }
1555
+ /**
1556
+ * Append `fields[0]=col1&fields[1]=col2` from the flat select array
1557
+ *
1558
+ * Strapi's `fields` parameter is the column-pruner for the main
1559
+ * resource; per-relation field selection is expressed through the
1560
+ * `populate` deep syntax (out of scope for this driver).
1561
+ *
1562
+ * @param state - The current query builder state
1563
+ * @param out - The accumulator the caller joins into the URI
1564
+ */
1565
+ _appendFields(state, out) {
1566
+ if (!state.select.length) {
1567
+ return;
1568
+ }
1569
+ out.push(qs.stringify({ [StrapiRequestStrategy._fieldsKey]: state.select }, { encode: false }));
1570
+ }
1571
+ /**
1572
+ * Append the unified `filters[...]` wrapper combining simple filters
1573
+ * and operator filters
1574
+ *
1575
+ * Both kinds emit into the same nested object under `filters` so qs
1576
+ * produces a single deeply-bracketed block per request. Simple
1577
+ * single-value filters fold to `$eq`; simple multi-value filters fold
1578
+ * to `$in`. Operator filters then merge into the same per-field map,
1579
+ * potentially co-existing with a simple filter on the same field.
1580
+ *
1581
+ * @param state - The current query builder state
1582
+ * @param out - The accumulator the caller joins into the URI
1583
+ */
1584
+ _appendFilters(state, out) {
1585
+ const simpleKeys = Object.keys(state.filters);
1586
+ if (!simpleKeys.length && !state.operatorFilters.length) {
1587
+ return;
1588
+ }
1589
+ const filters = {};
1590
+ simpleKeys.forEach(key => {
1591
+ const values = state.filters[key];
1592
+ if (!values.length) {
1593
+ return;
1594
+ }
1595
+ filters[key] = values.length === 1
1596
+ ? { $eq: values[0] }
1597
+ : { $in: values };
1598
+ });
1599
+ state.operatorFilters.forEach((filter) => {
1600
+ const payload = this._formatOperatorPayload(filter);
1601
+ filters[filter.field] = {
1602
+ ...(filters[filter.field] ?? {}),
1603
+ ...payload
1604
+ };
1605
+ });
1606
+ if (!Object.keys(filters).length) {
1607
+ return;
1608
+ }
1609
+ out.push(qs.stringify({ filters }, { encode: false }));
1610
+ }
1611
+ /**
1612
+ * Append the `pagination[page]` / `pagination[pageSize]` wrapper
1613
+ *
1614
+ * Page-based mode is the Strapi default; offset-based
1615
+ * (`pagination[start]` / `pagination[limit]`) is out of scope for this
1616
+ * driver until cursor/offset pagination lands library-wide.
1617
+ *
1618
+ * @param state - The current query builder state
1619
+ * @param out - The accumulator the caller joins into the URI
1620
+ */
1621
+ _appendPagination(state, out) {
1622
+ const wrapper = {
1623
+ [StrapiRequestStrategy._paginationKey]: {
1624
+ page: state.page,
1625
+ pageSize: state.limit
1626
+ }
1627
+ };
1628
+ out.push(qs.stringify(wrapper, { encode: false }));
1629
+ }
1630
+ /**
1631
+ * Append the `populate` parameter from the includes array
1632
+ *
1633
+ * Emits `populate[0]=relation1&populate[1]=relation2`; deep-populate
1634
+ * syntax (`populate[author][fields][0]=name`) is not exposed through
1635
+ * the current state shape.
1636
+ *
1637
+ * @param state - The current query builder state
1638
+ * @param out - The accumulator the caller joins into the URI
1639
+ */
1640
+ _appendPopulate(state, out) {
1641
+ if (!state.includes.length) {
1642
+ return;
1643
+ }
1644
+ out.push(qs.stringify({ [StrapiRequestStrategy._populateKey]: state.includes }, { encode: false }));
1645
+ }
1646
+ /**
1647
+ * Append the `sort[N]=field:dir` array
1648
+ *
1649
+ * @param state - The current query builder state
1650
+ * @param out - The accumulator the caller joins into the URI
1651
+ */
1652
+ _appendSort(state, out) {
1653
+ if (!state.sorts.length) {
1654
+ return;
1655
+ }
1656
+ const pairs = state.sorts.map(sort => `${sort.field}:${sort.order === SortEnum.DESC ? 'desc' : 'asc'}`);
1657
+ out.push(qs.stringify({ [StrapiRequestStrategy._sortKey]: pairs }, { encode: false }));
1658
+ }
1659
+ /**
1660
+ * Translate a `FilterOperatorEnum` operator filter into Strapi's
1661
+ * `$operator → value` payload shape
1662
+ *
1663
+ * The mapping is library-canonical → Strapi-native:
1664
+ * - `EQ`/`GT`/`GTE`/`LT`/`LTE`/`CONTAINS` → identity (same key name)
1665
+ * - `ILIKE` → `$containsi` (case-insensitive contains)
1666
+ * - `IN` → `$in` (array)
1667
+ * - `SW` → `$startsWith`
1668
+ * - `BTW` → `$between` with `[min, max]` (arity-checked)
1669
+ * - `NOT` → `$ne` (single value) / `$notIn` (multi-value)
1670
+ * - `NULL` → `$null=true` (when value is `true`) / `$notNull=true`
1671
+ * (when value is `false`); arity- and type-checked
1672
+ *
1673
+ * PostgREST's full-text-search operators (`FTS`, `PHFTS`, `PLFTS`,
1674
+ * `WFTS`) have no Strapi equivalent and throw
1675
+ * `UnsupportedFilterOperatorError`.
1676
+ *
1677
+ * @param filter - The operator filter to translate
1678
+ * @returns A `{ $operator: value }` payload ready to merge under
1679
+ * `filters[field]`
1680
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
1681
+ * exactly two values, or `NULL` does not receive exactly one boolean
1682
+ * @throws {UnsupportedFilterOperatorError} If the operator is a
1683
+ * PostgREST-only FTS variant
1684
+ */
1685
+ _formatOperatorPayload(filter) {
1686
+ const { operator, values } = filter;
1687
+ const first = values[0];
1688
+ switch (operator) {
1689
+ case FilterOperatorEnum.EQ: return { $eq: first };
1690
+ case FilterOperatorEnum.GT: return { $gt: first };
1691
+ case FilterOperatorEnum.GTE: return { $gte: first };
1692
+ case FilterOperatorEnum.LT: return { $lt: first };
1693
+ case FilterOperatorEnum.LTE: return { $lte: first };
1694
+ case FilterOperatorEnum.CONTAINS: return { $contains: first };
1695
+ case FilterOperatorEnum.ILIKE: return { $containsi: first };
1696
+ case FilterOperatorEnum.IN: return { $in: values };
1697
+ case FilterOperatorEnum.SW: return { $startsWith: first };
1698
+ case FilterOperatorEnum.BTW: {
1699
+ if (values.length !== 2) {
1700
+ throw new InvalidFilterOperatorValueError(operator, 'BTW requires exactly 2 values (min, max)');
1701
+ }
1702
+ return { $between: values };
1703
+ }
1704
+ case FilterOperatorEnum.NOT:
1705
+ return values.length === 1
1706
+ ? { $ne: first }
1707
+ : { $notIn: values };
1708
+ case FilterOperatorEnum.NULL: {
1709
+ if (values.length !== 1 || typeof first !== 'boolean') {
1710
+ throw new InvalidFilterOperatorValueError(operator, 'NULL requires exactly 1 boolean value (true → IS NULL, false → IS NOT NULL)');
1711
+ }
1712
+ return first ? { $null: true } : { $notNull: true };
1713
+ }
1714
+ case FilterOperatorEnum.FTS:
1715
+ case FilterOperatorEnum.PHFTS:
1716
+ case FilterOperatorEnum.PLFTS:
1717
+ case FilterOperatorEnum.WFTS:
1718
+ throw new UnsupportedFilterOperatorError();
1719
+ }
1720
+ }
1721
+ }
1722
+
1723
+ /**
1724
+ * Response strategy for the Strapi driver
1725
+ *
1726
+ * Parses Strapi v4/v5 pagination responses:
1727
+ * ```json
1728
+ * {
1729
+ * "data": [{ "id": 1, "documentId": "abc", "title": "Hello" }],
1730
+ * "meta": {
1731
+ * "pagination": {
1732
+ * "page": 1,
1733
+ * "pageSize": 10,
1734
+ * "pageCount": 5,
1735
+ * "total": 48
1736
+ * }
1737
+ * }
1738
+ * }
1739
+ * ```
1740
+ *
1741
+ * Default key paths are configured in `StrapiResponseOptions`. Strapi
1742
+ * does not include navigation links in the envelope, so `firstPageUrl`,
1743
+ * `prevPageUrl`, `nextPageUrl`, and `lastPageUrl` resolve to `undefined`
1744
+ * unless the consumer overrides their paths via `IPaginationConfig`. The
1745
+ * traversal algorithm (dot-notation resolution + computed `from`/`to`)
1746
+ * is inherited from `AbstractDotPathResponseStrategy`; this class exists
1747
+ * so `DriverEnum.STRAPI` resolves to a distinct identity at the DI
1748
+ * layer even though the parsing logic is shared with JSON:API and
1749
+ * NestJS.
1750
+ *
1751
+ * @see https://docs.strapi.io/dev-docs/api/rest/sort-pagination
1752
+ */
1753
+ class StrapiResponseStrategy extends AbstractDotPathResponseStrategy {
1754
+ }
1755
+
1447
1756
  /**
1448
1757
  * Driver registry — single source of truth for what each `DriverEnum`
1449
1758
  * value resolves to
@@ -1479,6 +1788,11 @@ const DRIVERS = {
1479
1788
  createRequestStrategy: () => new SpatieRequestStrategy(),
1480
1789
  createResponseStrategy: () => new SpatieResponseStrategy(),
1481
1790
  createResponseOptions: (config) => new ResponseOptions(config)
1791
+ },
1792
+ [DriverEnum.STRAPI]: {
1793
+ createRequestStrategy: () => new StrapiRequestStrategy(),
1794
+ createResponseStrategy: () => new StrapiResponseStrategy(),
1795
+ createResponseOptions: (config) => new StrapiResponseOptions(config)
1482
1796
  }
1483
1797
  };
1484
1798
 
@@ -2011,19 +2325,6 @@ class UnsupportedFilterError extends Error {
2011
2325
  }
2012
2326
  }
2013
2327
 
2014
- /**
2015
- * Error thrown when filter operators are attempted with a driver that does not support them
2016
- *
2017
- * Filter operators are only supported by the NestJS driver.
2018
- * Use `addFilter()` for Spatie implicit equality filters.
2019
- */
2020
- class UnsupportedFilterOperatorError extends Error {
2021
- constructor() {
2022
- super('Filter operators are only supported by the NestJS driver. Use addFilter() for Spatie.');
2023
- this.name = 'UnsupportedFilterOperatorError';
2024
- }
2025
- }
2026
-
2027
2328
  /**
2028
2329
  * Error thrown when includes are attempted with a driver that does not support them
2029
2330
  *
@@ -2848,5 +3149,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.3", ngImpor
2848
3149
  * Generated bundle index. Do not edit.
2849
3150
  */
2850
3151
 
2851
- 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, UnselectableModelError, UnsupportedFieldSelectionError, UnsupportedFilterError, UnsupportedFilterOperatorError, UnsupportedIncludesError, UnsupportedSearchError, UnsupportedSelectError, UnsupportedSortError, buildNgQubeeProviders, provideNgQubee, provideNgQubeeInstance, readHeader };
3152
+ 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 };
2852
3153
  //# sourceMappingURL=ng-qubee.mjs.map