ng-qubee 3.2.0 → 3.3.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 +116 -1
- package/fesm2022/ng-qubee.mjs +2060 -1717
- package/fesm2022/ng-qubee.mjs.map +1 -1
- package/package.json +4 -2
- package/types/ng-qubee.d.ts +614 -260
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ npm i ng-qubee
|
|
|
41
41
|
|
|
42
42
|
## Drivers
|
|
43
43
|
|
|
44
|
-
NgQubee supports
|
|
44
|
+
NgQubee supports five drivers out of the box. A driver **must** be specified in the configuration:
|
|
45
45
|
|
|
46
46
|
| Driver | Backend | Request Format | Response Format |
|
|
47
47
|
|---|---|---|---|
|
|
@@ -49,6 +49,7 @@ NgQubee supports four drivers out of the box. A driver **must** be specified in
|
|
|
49
49
|
| **Laravel** | Plain Laravel pagination | `limit=N&page=N` (pagination only) | Flat: `{ data, current_page, total, ... }` |
|
|
50
50
|
| **Spatie** | Spatie Query Builder | `filter[field]=value`, `sort=-field` | Flat: `{ data, current_page, total, ... }` |
|
|
51
51
|
| **NestJS** | nestjs-paginate | `filter.field=$operator:value`, `sortBy=field:DESC` | Nested: `{ data, meta: {...}, links: {...} }` |
|
|
52
|
+
| **PostgREST** | PostgREST / Supabase | `col=eq.value`, `order=col.asc`, `limit=N&offset=M` | Bare array body + `Content-Range` header for total |
|
|
52
53
|
|
|
53
54
|
## Usage
|
|
54
55
|
|
|
@@ -176,6 +177,120 @@ The NestJS driver generates URIs compatible with [nestjs-paginate](https://githu
|
|
|
176
177
|
- **Limit** is composed as `limit=15`
|
|
177
178
|
- **Page** is composed as `page=1`
|
|
178
179
|
|
|
180
|
+
### PostgREST / Supabase Driver
|
|
181
|
+
|
|
182
|
+
The PostgREST driver generates URIs compatible with [PostgREST](https://postgrest.org/) and anything built on top of it (notably [Supabase](https://supabase.com/)):
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { DriverEnum } from 'ng-qubee';
|
|
186
|
+
|
|
187
|
+
// Standalone approach
|
|
188
|
+
bootstrapApplication(AppComponent, {
|
|
189
|
+
providers: [provideNgQubee({ driver: DriverEnum.POSTGREST })]
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Module approach
|
|
193
|
+
@NgModule({
|
|
194
|
+
imports: [
|
|
195
|
+
NgQubeeModule.forRoot({ driver: DriverEnum.POSTGREST })
|
|
196
|
+
]
|
|
197
|
+
})
|
|
198
|
+
export class AppModule {}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The PostgREST driver supports:
|
|
202
|
+
|
|
203
|
+
- **Filters** (single value) are composed as `col=eq.value`
|
|
204
|
+
- **Filters** (multi-value) are composed as `col=in.(v1,v2,v3)` — PostgREST's native IN-list syntax
|
|
205
|
+
- **Operator filters** cover the full `FilterOperatorEnum` (eq, gt, gte, lt, lte, ilike, in, not, null, btw, sw, contains) plus PostgREST-native full-text search (`fts`, `plfts`, `phfts`, `wfts`) — see the Operator filters section below
|
|
206
|
+
- **Sorts** are composed as `order=col1.asc,col2.desc`
|
|
207
|
+
- **Select** is composed as `select=col1,col2`
|
|
208
|
+
- **Pagination** defaults to `limit=N&offset=M` on the URL and can optionally move to `Range-Unit` + `Range` HTTP headers — see the RANGE-header pagination section below
|
|
209
|
+
|
|
210
|
+
#### Reading totals from the `Content-Range` header
|
|
211
|
+
|
|
212
|
+
PostgREST returns a bare array body and reports the total row count in the `Content-Range` HTTP response header (e.g. `0-9/50`). Opt in by sending `Prefer: count=exact` on the request, then pass the headers to `paginate()`:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
this._http.get<User[]>(uri, {
|
|
216
|
+
observe: 'response',
|
|
217
|
+
headers: { 'Prefer': 'count=exact' }
|
|
218
|
+
}).subscribe(response => {
|
|
219
|
+
const collection = this._paginationService.paginate(response.body, response.headers);
|
|
220
|
+
// collection.total, collection.page, collection.lastPage are populated
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
The second argument to `paginate()` accepts Angular's `HttpHeaders`, the native `Headers` class, or a plain `Record<string, string>` — whatever shape your HTTP client emits. If you omit the header (or the server returns `Content-Range: 0-9/*`), the collection still populates `data` and `page` but leaves `total` / `lastPage` undefined — the pagination helpers' conservative defaults then kick in (`hasNextPage()` returns `true`, `isLastPage()` returns `false`).
|
|
225
|
+
|
|
226
|
+
#### Operator filters
|
|
227
|
+
|
|
228
|
+
Every `FilterOperatorEnum` value maps to a PostgREST prefix operator. Call `addFilterOperator(column, operator, ...values)` and the strategy emits the correct wire format:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
qb.addFilterOperator('age', FilterOperatorEnum.GTE, 18); // age=gte.18
|
|
232
|
+
qb.addFilterOperator('id', FilterOperatorEnum.IN, 1, 2, 3); // id=in.(1,2,3)
|
|
233
|
+
qb.addFilterOperator('status', FilterOperatorEnum.NOT, 'deleted'); // status=not.eq.deleted
|
|
234
|
+
qb.addFilterOperator('id', FilterOperatorEnum.NOT, 1, 2); // id=not.in.(1,2)
|
|
235
|
+
qb.addFilterOperator('deletedAt', FilterOperatorEnum.NULL, true); // deletedAt=is.null
|
|
236
|
+
qb.addFilterOperator('deletedAt', FilterOperatorEnum.NULL, false); // deletedAt=is.not.null
|
|
237
|
+
qb.addFilterOperator('price', FilterOperatorEnum.BTW, 10, 100); // price=gte.10&price=lte.100
|
|
238
|
+
qb.addFilterOperator('email', FilterOperatorEnum.SW, 'admin'); // email=like.admin*
|
|
239
|
+
qb.addFilterOperator('name', FilterOperatorEnum.CONTAINS, 'john'); // name=ilike.%john%
|
|
240
|
+
qb.addFilterOperator('description', FilterOperatorEnum.FTS, 'rat'); // description=fts.rat
|
|
241
|
+
qb.addFilterOperator('description', FilterOperatorEnum.PLFTS, 'a b'); // description=plfts.a b
|
|
242
|
+
qb.addFilterOperator('description', FilterOperatorEnum.PHFTS, 'a b'); // description=phfts.a b
|
|
243
|
+
qb.addFilterOperator('description', FilterOperatorEnum.WFTS, 'a -b'); // description=wfts.a -b
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Value shape rules enforced at call time (throw `InvalidFilterOperatorValueError`):
|
|
247
|
+
- `BTW` — exactly 2 values (`[min, max]`).
|
|
248
|
+
- `NULL` — exactly 1 boolean value (`true` → `IS NULL`, `false` → `IS NOT NULL`).
|
|
249
|
+
|
|
250
|
+
All other operators let PostgREST validate server-side.
|
|
251
|
+
|
|
252
|
+
The four `*FTS` operators are PostgREST's full-text search variants (`to_tsquery`, `plainto_tsquery`, `phraseto_tsquery`, `websearch_to_tsquery`). They're column-scoped — pick the column to search, pass the term. Language modifiers (`fts(english).term`) are not supported in this release.
|
|
253
|
+
|
|
254
|
+
#### RANGE-header pagination (alternative to limit/offset)
|
|
255
|
+
|
|
256
|
+
PostgREST also accepts pagination via the `Range` HTTP request header instead of URL query params. Opt in via `IConfig.pagination`:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { DriverEnum, PaginationModeEnum } from 'ng-qubee';
|
|
260
|
+
|
|
261
|
+
provideNgQubee({
|
|
262
|
+
driver: DriverEnum.POSTGREST,
|
|
263
|
+
pagination: PaginationModeEnum.RANGE
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
When `RANGE` is active, `generateUri()` omits `limit` and `offset` from the URL and `NgQubeeService.paginationHeaders()` returns the headers the consumer applies to the HTTP request:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
const uri = await firstValueFrom(qb.generateUri());
|
|
271
|
+
const extraHeaders = qb.paginationHeaders(); // { 'Range-Unit': 'items', 'Range': '0-9' }
|
|
272
|
+
|
|
273
|
+
this._http.get<User[]>(uri, {
|
|
274
|
+
observe: 'response',
|
|
275
|
+
headers: { 'Prefer': 'count=exact', ...extraHeaders }
|
|
276
|
+
}).subscribe(resp => this._pagination.paginate(resp.body, resp.headers));
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`paginationHeaders()` returns `null` for any driver that doesn't use header-based pagination — safe to spread into a headers map unconditionally with the nullish-coalescing pattern above.
|
|
280
|
+
|
|
281
|
+
#### Feature matrix
|
|
282
|
+
|
|
283
|
+
| Method | Supported? | Notes |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| `addFilter` / `deleteFilters` | ✓ | Implicit `eq`; multi-value becomes `in.(...)` |
|
|
286
|
+
| `addFilterOperator` / `deleteOperatorFilters` | ✓ | All 16 operators including `FTS`/`PLFTS`/`PHFTS`/`WFTS` |
|
|
287
|
+
| `addSort` / `deleteSorts` | ✓ | Emits `order=col.asc,col.desc` |
|
|
288
|
+
| `addSelect` / `deleteSelect` | ✓ | Flat column selection |
|
|
289
|
+
| `setLimit` / `setPage` | ✓ | `offset` derived from `page` (QUERY mode) or emitted via `Range` header (RANGE mode) |
|
|
290
|
+
| `addFields` / `deleteFields` / `deleteFieldsByModel` | ✗ | Throws `UnsupportedFieldSelectionError`. Per-type field selection is a JSON:API/Spatie concept; use `addSelect` for PostgREST's column pruning. |
|
|
291
|
+
| `addIncludes` / `deleteIncludes` | ✗ | Throws `UnsupportedIncludesError`. PostgREST uses embedded resources via `select=col,rel(*)` — tracked as #66. |
|
|
292
|
+
| `setSearch` / `deleteSearch` | ✗ | Throws `UnsupportedSearchError`. PostgREST's FTS operators are per-column — use `addFilterOperator(col, FilterOperatorEnum.FTS, term)` instead. |
|
|
293
|
+
|
|
179
294
|
### Per-component instances
|
|
180
295
|
|
|
181
296
|
By default, `provideNgQubee()` / `NgQubeeModule.forRoot()` register `NgQubeeService` at the environment injector, so every component that injects it shares the same query-builder state. If you need a dedicated instance — e.g. a feature component whose filters and pagination must not bleed into the app-wide one — spread `provideNgQubeeInstance()` into the component's `providers`:
|