ng-qubee 3.6.0 → 3.7.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.
@@ -40,39 +40,52 @@ declare class PaginatedCollection<T extends IPaginatedObject> {
40
40
  }
41
41
 
42
42
  /**
43
- * Enum representing the available pagination driver types
43
+ * Configuration interface for customizing request query parameter key names
44
44
  *
45
- * Each driver encapsulates the full format knowledge for both
46
- * request building (URI generation) and response parsing.
45
+ * Each property maps a logical query concept to the actual query parameter name
46
+ * used in the generated URI. The defaults depend on the selected driver.
47
47
  */
48
- declare enum DriverEnum {
49
- DRF = "drf",
50
- JSON_API = "json-api",
51
- LARAVEL = "laravel",
52
- NESTJS = "nestjs",
53
- NESTJSX_CRUD = "nestjsx-crud",
54
- POSTGREST = "postgrest",
55
- SIEVE = "sieve",
56
- SPATIE = "spatie",
57
- SPRING = "spring",
58
- STRAPI = "strapi"
48
+ interface IQueryBuilderConfig {
49
+ /** Key for the appends parameter (Laravel only, default: 'append') */
50
+ appends?: string;
51
+ /** Key for the fields parameter (Laravel: 'fields', NestJS: 'select') */
52
+ fields?: string;
53
+ /** Key for the filters parameter (default: 'filter') */
54
+ filters?: string;
55
+ /** Key for the includes parameter (Laravel only, default: 'include') */
56
+ includes?: string;
57
+ /** Key for the limit parameter (default: 'limit') */
58
+ limit?: string;
59
+ /** Key for the page parameter (default: 'page') */
60
+ page?: string;
61
+ /** Key for the search parameter (NestJS only, default: 'search') */
62
+ search?: string;
63
+ /** Key for the select parameter (NestJS only, default: 'select') */
64
+ select?: string;
65
+ /** Key for the sort parameter (Laravel: 'sort', NestJS: 'sortBy') */
66
+ sort?: string;
67
+ /** Key for the sortBy parameter (NestJS only, default: 'sortBy') */
68
+ sortBy?: string;
59
69
  }
60
70
 
61
71
  /**
62
- * Enum representing the wire-level pagination mechanism
63
- *
64
- * `QUERY` (default) — the request strategy emits `limit` and `offset` (or
65
- * equivalent) query parameters on the URL.
72
+ * Resolved query parameter key names with defaults applied
66
73
  *
67
- * `RANGE` the request strategy omits URL-based pagination and the
68
- * consumer instead applies HTTP request headers returned by
69
- * `NgQubeeService.paginationHeaders()`. Currently honoured only by the
70
- * PostgREST driver, which maps it to `Range-Unit: items` + `Range: 0-9`.
71
- * Other drivers ignore the setting.
74
+ * Maps logical query concepts to the actual query parameter names
75
+ * used in the generated URI. Unset values fall back to defaults.
72
76
  */
73
- declare enum PaginationModeEnum {
74
- QUERY = "query",
75
- RANGE = "range"
77
+ declare class QueryBuilderOptions {
78
+ readonly appends: string;
79
+ readonly fields: string;
80
+ readonly filters: string;
81
+ readonly includes: string;
82
+ readonly limit: string;
83
+ readonly page: string;
84
+ readonly search: string;
85
+ readonly select: string;
86
+ readonly sort: string;
87
+ readonly sortBy: string;
88
+ constructor(options: IQueryBuilderConfig);
76
89
  }
77
90
 
78
91
  /**
@@ -112,32 +125,264 @@ interface IPaginationConfig {
112
125
  }
113
126
 
114
127
  /**
115
- * Configuration interface for customizing request query parameter key names
128
+ * Resolved response field key names with defaults applied
116
129
  *
117
- * Each property maps a logical query concept to the actual query parameter name
118
- * used in the generated URI. The defaults depend on the selected driver.
130
+ * Maps logical pagination concepts to the actual key names
131
+ * used in the API response. Unset values fall back to Laravel defaults.
132
+ *
133
+ * For NestJS responses, use dot-notation paths:
134
+ * ```typescript
135
+ * new ResponseOptions({
136
+ * currentPage: 'meta.currentPage',
137
+ * total: 'meta.totalItems'
138
+ * });
139
+ * ```
119
140
  */
120
- interface IQueryBuilderConfig {
121
- /** Key for the appends parameter (Laravel only, default: 'append') */
122
- appends?: string;
123
- /** Key for the fields parameter (Laravel: 'fields', NestJS: 'select') */
124
- fields?: string;
125
- /** Key for the filters parameter (default: 'filter') */
126
- filters?: string;
127
- /** Key for the includes parameter (Laravel only, default: 'include') */
128
- includes?: string;
129
- /** Key for the limit parameter (default: 'limit') */
130
- limit?: string;
131
- /** Key for the page parameter (default: 'page') */
132
- page?: string;
133
- /** Key for the search parameter (NestJS only, default: 'search') */
134
- search?: string;
135
- /** Key for the select parameter (NestJS only, default: 'select') */
136
- select?: string;
137
- /** Key for the sort parameter (Laravel: 'sort', NestJS: 'sortBy') */
138
- sort?: string;
139
- /** Key for the sortBy parameter (NestJS only, default: 'sortBy') */
140
- sortBy?: string;
141
+ declare class ResponseOptions {
142
+ readonly currentPage: string;
143
+ readonly data: string;
144
+ readonly firstPageUrl: string;
145
+ readonly from: string;
146
+ readonly lastPage: string;
147
+ readonly lastPageUrl: string;
148
+ readonly nextPageUrl: string;
149
+ readonly path: string;
150
+ readonly perPage: string;
151
+ readonly prevPageUrl: string;
152
+ readonly to: string;
153
+ readonly total: string;
154
+ constructor(options: IPaginationConfig);
155
+ }
156
+ /**
157
+ * Pre-configured ResponseOptions for the API Platform (Symfony) driver
158
+ *
159
+ * Uses dot-notation paths into the Hydra/JSON-LD envelope — the Hydra
160
+ * keys contain colons but no dots, so `hydra:view.hydra:next` traverses
161
+ * `response['hydra:view']['hydra:next']`. The `path` slot points at the
162
+ * view's `@id`, which the strategy parses for the current page and page
163
+ * size; `currentPage` / `perPage` / `lastPage` have no body field and
164
+ * default to empty paths (derived from the view URLs instead).
165
+ */
166
+ declare class ApiPlatformResponseOptions extends ResponseOptions {
167
+ constructor(options: IPaginationConfig);
168
+ }
169
+ /**
170
+ * Pre-configured ResponseOptions for the Directus driver
171
+ *
172
+ * The Directus envelope is `{ data, meta: { total_count, filter_count } }`
173
+ * (with `meta=total_count,filter_count` requested — the request strategy
174
+ * always emits it). `total` defaults to `meta.filter_count`, the count of
175
+ * items matching the current filter; point it at `meta.total_count` via
176
+ * `IPaginationConfig` for the unfiltered collection size. The envelope
177
+ * names no current page, page size, or navigation URLs, so those paths
178
+ * default to empty strings — the strategy falls back to page 1 and
179
+ * derives `lastPage`/`from`/`to` only when the response provably holds
180
+ * the whole filtered set. All paths are overridable (dot notation
181
+ * supported) for custom wrappers that do include paging fields.
182
+ */
183
+ declare class DirectusResponseOptions extends ResponseOptions {
184
+ constructor(options: IPaginationConfig);
185
+ }
186
+ /**
187
+ * Pre-configured ResponseOptions for the Django REST Framework (DRF) driver
188
+ *
189
+ * DRF's `PageNumberPagination` envelope is `{ count, next, previous,
190
+ * results }`, with no body field naming the current page, per-page, or
191
+ * last-page. The strategy parses those from the `next` / `previous`
192
+ * URLs, so the corresponding key paths default to empty strings; the
193
+ * strategy ignores `options.currentPage`, `options.perPage`,
194
+ * `options.lastPage`, `options.from`, `options.to`, `options.path`,
195
+ * `options.firstPageUrl`, and `options.lastPageUrl`.
196
+ */
197
+ declare class DrfResponseOptions extends ResponseOptions {
198
+ constructor(options: IPaginationConfig);
199
+ }
200
+ /**
201
+ * Pre-configured ResponseOptions for the FeathersJS driver
202
+ *
203
+ * The Feathers adapter envelope is `{ total, limit, skip, data }` —
204
+ * offset-based, with no page number and no navigation URLs. `perPage`
205
+ * maps to the envelope's `limit` key and `total` to `total`; the
206
+ * strategy derives `currentPage` / `lastPage` / `from` / `to` from
207
+ * `skip` and `limit`, so the corresponding key paths default to empty
208
+ * strings (the `skip` key itself is fixed by the envelope and read
209
+ * directly by the strategy).
210
+ */
211
+ declare class FeathersResponseOptions extends ResponseOptions {
212
+ constructor(options: IPaginationConfig);
213
+ }
214
+ /**
215
+ * Pre-configured ResponseOptions for the JSON:API driver
216
+ *
217
+ * Uses dot-notation paths to access nested values in the JSON:API response format.
218
+ * JSON:API meta key names vary by implementation; these defaults cover the most
219
+ * common conventions and can be fully customised via `IPaginationConfig`.
220
+ */
221
+ declare class JsonApiResponseOptions extends ResponseOptions {
222
+ constructor(options: IPaginationConfig);
223
+ }
224
+ /**
225
+ * Pre-configured ResponseOptions for the json-server driver
226
+ *
227
+ * The json-server v1 envelope is `{ first, prev, next, last, pages,
228
+ * items, data }`, where `first`/`prev`/`next`/`last` are **page
229
+ * numbers**, not URLs — the strategy reads `prev`/`next` directly for
230
+ * position derivation and leaves the URL slots `undefined`, so the
231
+ * navigation-URL paths default to empty strings. `total` maps to
232
+ * `items` and `lastPage` to `pages`; `currentPage` and `perPage` have
233
+ * no body field and are derived.
234
+ */
235
+ declare class JsonServerResponseOptions extends ResponseOptions {
236
+ constructor(options: IPaginationConfig);
237
+ }
238
+ /**
239
+ * Pre-configured ResponseOptions for the NestJS driver
240
+ *
241
+ * Uses dot-notation paths to access nested values in the NestJS response format.
242
+ */
243
+ declare class NestjsResponseOptions extends ResponseOptions {
244
+ constructor(options: IPaginationConfig);
245
+ }
246
+ /**
247
+ * Pre-configured ResponseOptions for the @nestjsx/crud driver
248
+ *
249
+ * The `getMany` envelope is flat: `{ data, count, total, page,
250
+ * pageCount }`. `perPage` defaults to the `count` field — the number of
251
+ * entities on the **current** page, which equals the requested limit on
252
+ * every page except a partial last one. The envelope carries no
253
+ * `from`/`to` indices and no navigation links, so those paths default to
254
+ * empty strings (the strategy derives `from`/`to` and leaves the URLs
255
+ * `undefined`); consumers can override any path via `IPaginationConfig`.
256
+ */
257
+ declare class NestjsxCrudResponseOptions extends ResponseOptions {
258
+ constructor(options: IPaginationConfig);
259
+ }
260
+ /**
261
+ * Pre-configured ResponseOptions for the OData v4 driver
262
+ *
263
+ * The OData collection envelope is `{ "@odata.count", "@odata.nextLink",
264
+ * "value" }` — flat keys that contain **literal dots**, so the strategy
265
+ * reads them with flat bracket access (never dot-path traversal). No body
266
+ * field names the current page, per-page, or last-page; the strategy
267
+ * derives those from the `@odata.nextLink` URL's `$skip` / `$top`
268
+ * params, so the corresponding key paths default to empty strings and
269
+ * are ignored.
270
+ */
271
+ declare class OdataResponseOptions extends ResponseOptions {
272
+ constructor(options: IPaginationConfig);
273
+ }
274
+ /**
275
+ * Pre-configured ResponseOptions for the Payload CMS driver
276
+ *
277
+ * The envelope is the flat `mongoose-paginate-v2` shape: `{ docs,
278
+ * totalDocs, limit, totalPages, page, pagingCounter, hasPrevPage,
279
+ * hasNextPage, prevPage, nextPage }`. `pagingCounter` is the 1-indexed
280
+ * offset of the first doc on the page and maps onto `from`; `to` has no
281
+ * body field and is derived. `prevPage`/`nextPage` are page numbers,
282
+ * not URLs, so the navigation-URL paths default to empty strings. All
283
+ * paths are overridable via `IPaginationConfig` (dot notation
284
+ * supported) for custom wrappers.
285
+ */
286
+ declare class PayloadResponseOptions extends ResponseOptions {
287
+ constructor(options: IPaginationConfig);
288
+ }
289
+ /**
290
+ * Pre-configured ResponseOptions for the PocketBase driver
291
+ *
292
+ * The records-list envelope is flat: `{ page, perPage, totalItems,
293
+ * totalPages, items }`. The envelope carries no `from`/`to` indices and
294
+ * no navigation links, so those paths default to empty strings (the
295
+ * strategy derives `from`/`to` from `page` × `perPage` and leaves the
296
+ * URLs `undefined`); all paths are overridable via `IPaginationConfig`
297
+ * (dot notation supported) for custom wrappers.
298
+ */
299
+ declare class PocketbaseResponseOptions extends ResponseOptions {
300
+ constructor(options: IPaginationConfig);
301
+ }
302
+ /**
303
+ * Pre-configured ResponseOptions for the Sieve (.NET) driver
304
+ *
305
+ * Sieve defines no response envelope (it returns an `IQueryable` the
306
+ * developer wraps), so these defaults target the common hand-rolled
307
+ * `PagedResult<T>` shape: `{ data, page, pageSize, total, totalPages }`.
308
+ * Every path is overridable via `IPaginationConfig` — dot notation is
309
+ * supported, so nested wrappers (`meta.page`, `pagination.total`) map
310
+ * without subclassing. `from`/`to` default to empty paths and are
311
+ * derived; the navigation-URL slots resolve to `undefined` unless paths
312
+ * are provided.
313
+ */
314
+ declare class SieveResponseOptions extends ResponseOptions {
315
+ constructor(options: IPaginationConfig);
316
+ }
317
+ /**
318
+ * Pre-configured ResponseOptions for the Spring Data REST driver
319
+ *
320
+ * Uses dot-notation paths into the HAL envelope: pagination metadata
321
+ * lives under `page.*` and navigation links under `_links.*.href`.
322
+ * `currentPage` points at the **0-indexed** `page.number`; the strategy
323
+ * adds 1 when reading it. `data` defaults to plain `_embedded` because
324
+ * the collection key underneath is the resource rel name (e.g.
325
+ * `_embedded.users`) and cannot be known statically — the strategy picks
326
+ * the first array inside; pin an exact path via `IPaginationConfig` when
327
+ * needed. `from`/`to` default to empty paths and are derived.
328
+ */
329
+ declare class SpringResponseOptions extends ResponseOptions {
330
+ constructor(options: IPaginationConfig);
331
+ }
332
+ /**
333
+ * Pre-configured ResponseOptions for the Strapi driver
334
+ *
335
+ * Uses dot-notation paths to access the nested `meta.pagination.*` envelope
336
+ * Strapi v4/v5 emits. Strapi does not include navigation links by default,
337
+ * so the URL paths point at locations that will resolve to `undefined`
338
+ * unless the consumer overrides them.
339
+ */
340
+ declare class StrapiResponseOptions extends ResponseOptions {
341
+ constructor(options: IPaginationConfig);
342
+ }
343
+
344
+ /**
345
+ * Enum representing the available pagination driver types
346
+ *
347
+ * Each driver encapsulates the full format knowledge for both
348
+ * request building (URI generation) and response parsing.
349
+ */
350
+ declare enum DriverEnum {
351
+ API_PLATFORM = "api-platform",
352
+ DIRECTUS = "directus",
353
+ DRF = "drf",
354
+ FEATHERS = "feathers",
355
+ JSON_API = "json-api",
356
+ JSON_SERVER = "json-server",
357
+ LARAVEL = "laravel",
358
+ NESTJS = "nestjs",
359
+ NESTJSX_CRUD = "nestjsx-crud",
360
+ ODATA = "odata",
361
+ PAYLOAD = "payload",
362
+ POCKETBASE = "pocketbase",
363
+ POSTGREST = "postgrest",
364
+ SIEVE = "sieve",
365
+ SPATIE = "spatie",
366
+ SPRING = "spring",
367
+ STRAPI = "strapi",
368
+ WORDPRESS = "wordpress"
369
+ }
370
+
371
+ /**
372
+ * Enum representing the wire-level pagination mechanism
373
+ *
374
+ * `QUERY` (default) — the request strategy emits `limit` and `offset` (or
375
+ * equivalent) query parameters on the URL.
376
+ *
377
+ * `RANGE` — the request strategy omits URL-based pagination and the
378
+ * consumer instead applies HTTP request headers returned by
379
+ * `NgQubeeService.paginationHeaders()`. Currently honoured only by the
380
+ * PostgREST driver, which maps it to `Range-Unit: items` + `Range: 0-9`.
381
+ * Other drivers ignore the setting.
382
+ */
383
+ declare enum PaginationModeEnum {
384
+ QUERY = "query",
385
+ RANGE = "range"
141
386
  }
142
387
 
143
388
  /**
@@ -352,6 +597,19 @@ interface ISort {
352
597
  order: SortEnum;
353
598
  }
354
599
 
600
+ /**
601
+ * Embedded-resource selection map (PostgREST only)
602
+ *
603
+ * Maps a related table / foreign-key name (as PostgREST sees it) to the
604
+ * columns to project from it. An empty array means "all columns" and
605
+ * emits `relation(*)`; a non-empty array emits `relation(col1,col2)`.
606
+ * The PostgREST request strategy splices these fragments into the single
607
+ * `select=` query parameter alongside flat columns from `addSelect`.
608
+ */
609
+ type Embedded = {
610
+ [relation: string]: string[];
611
+ };
612
+
355
613
  /**
356
614
  * Represents the complete query builder state
357
615
  *
@@ -361,6 +619,8 @@ interface ISort {
361
619
  interface IQueryBuilderState {
362
620
  /** The base URL to prepend to generated URIs */
363
621
  baseUrl: string;
622
+ /** Embedded-resource selection (PostgREST only) */
623
+ embedded: Embedded;
364
624
  /** Per-model field selection (Spatie only) */
365
625
  fields: IFields;
366
626
  /** Simple key-value filters (Spatie and NestJS) */
@@ -398,6 +658,8 @@ interface IQueryBuilderState {
398
658
  * strategy class — `NgQubeeService` does not need to be touched.
399
659
  */
400
660
  interface IStrategyCapabilities {
661
+ /** Embedded-resource selection inside `select` (PostgREST `select=col,rel(col1)`) */
662
+ readonly embedded: boolean;
401
663
  /** Per-model field selection (e.g. JSON:API `fields[type]=col1,col2`) */
402
664
  readonly fields: boolean;
403
665
  /** Simple key-value filters (e.g. `filter.status=active`) */
@@ -414,26 +676,6 @@ interface IStrategyCapabilities {
414
676
  readonly sort: boolean;
415
677
  }
416
678
 
417
- /**
418
- * Resolved query parameter key names with defaults applied
419
- *
420
- * Maps logical query concepts to the actual query parameter names
421
- * used in the generated URI. Unset values fall back to defaults.
422
- */
423
- declare class QueryBuilderOptions {
424
- readonly appends: string;
425
- readonly fields: string;
426
- readonly filters: string;
427
- readonly includes: string;
428
- readonly limit: string;
429
- readonly page: string;
430
- readonly search: string;
431
- readonly select: string;
432
- readonly sort: string;
433
- readonly sortBy: string;
434
- constructor(options: IQueryBuilderConfig);
435
- }
436
-
437
679
  /**
438
680
  * Strategy interface for building request URIs
439
681
  *
@@ -559,6 +801,21 @@ declare class NestService {
559
801
  * @private
560
802
  */
561
803
  private _validateResourceName;
804
+ /**
805
+ * Add embedded-resource selections to the request (PostgREST only)
806
+ * Automatically prevents duplicate columns for each relation
807
+ *
808
+ * An empty column array means "all columns" (`relation(*)`); merging an
809
+ * empty array into a relation that already has explicit columns keeps
810
+ * the explicit columns.
811
+ *
812
+ * @param {Embedded} embedded - Object mapping relation names to arrays of columns to project
813
+ * @return {void}
814
+ * @example
815
+ * service.addEmbedded({ author: ['id', 'name'] });
816
+ * service.addEmbedded({ comments: [] });
817
+ */
818
+ addEmbedded(embedded: Embedded): void;
562
819
  /**
563
820
  * Add selectable fields for the given model to the request
564
821
  * Automatically prevents duplicate fields for each model
@@ -624,6 +881,18 @@ declare class NestService {
624
881
  * service.addSort({ field: 'name', order: SortEnum.ASC });
625
882
  */
626
883
  addSort(sort: ISort): void;
884
+ /**
885
+ * Remove embedded-resource relations from the state (PostgREST only)
886
+ *
887
+ * Removes the whole relation entry, columns included.
888
+ *
889
+ * @param {...string[]} relations - Relation names to remove
890
+ * @return {void}
891
+ * @example
892
+ * service.deleteEmbedded('author');
893
+ * service.deleteEmbedded('comments', 'tags');
894
+ */
895
+ deleteEmbedded(...relations: string[]): void;
627
896
  /**
628
897
  * Remove fields for the given model
629
898
  * Uses deep cloning to prevent mutations to the original state
@@ -764,6 +1033,29 @@ declare class NgQubeeService {
764
1033
  * @throws The provided error if the active strategy lacks the capability
765
1034
  */
766
1035
  private _assertCapability;
1036
+ /**
1037
+ * Add an embedded resource to the select statement (PostgREST only)
1038
+ *
1039
+ * Splices `relation(col1,col2)` into the single `select=` query
1040
+ * parameter alongside flat columns from `addSelect`. Omit the columns
1041
+ * to project all of them (`relation(*)`):
1042
+ *
1043
+ * ```
1044
+ * qb.addEmbedded('author', 'id', 'name')
1045
+ * .addEmbedded('comments')
1046
+ * .addSelect('title');
1047
+ * // → select=title,author(id,name),comments(*)
1048
+ * ```
1049
+ *
1050
+ * Calling repeatedly with the same relation merge-dedups the columns.
1051
+ * Does not reset the page (column shape change, not record-set change).
1052
+ *
1053
+ * @param {string} relation - The related table / foreign-key name as PostgREST sees it
1054
+ * @param {string[]} columns - Optional column projection; omit for `relation(*)`
1055
+ * @returns {this}
1056
+ * @throws {UnsupportedEmbeddedError} If the active driver does not support embedded resources
1057
+ */
1058
+ addEmbedded(relation: string, ...columns: string[]): this;
767
1059
  /**
768
1060
  * Add fields to the select statement for the given model (JSON:API and Spatie only)
769
1061
  *
@@ -831,11 +1123,21 @@ declare class NgQubeeService {
831
1123
  */
832
1124
  currentPage(): number;
833
1125
  /**
834
- * Delete selected fields for the given models in the current query builder state (JSON:API and Spatie only)
1126
+ * Remove embedded resources from the current query builder state (PostgREST only)
835
1127
  *
836
- * ```
837
- * ngQubeeService.deleteFields({
838
- * users: ['email', 'password'],
1128
+ * Removes the whole relation entry, columns included.
1129
+ *
1130
+ * @param {string[]} relations - Relation names to remove
1131
+ * @returns {this}
1132
+ * @throws {UnsupportedEmbeddedError} If the active driver does not support embedded resources
1133
+ */
1134
+ deleteEmbedded(...relations: string[]): this;
1135
+ /**
1136
+ * Delete selected fields for the given models in the current query builder state (JSON:API and Spatie only)
1137
+ *
1138
+ * ```
1139
+ * ngQubeeService.deleteFields({
1140
+ * users: ['email', 'password'],
839
1141
  * address: ['zipcode']
840
1142
  * });
841
1143
  * ```
@@ -1076,36 +1378,6 @@ type HeaderBag = {
1076
1378
  */
1077
1379
  declare function readHeader(bag: HeaderBag | null | undefined, name: string): string | null;
1078
1380
 
1079
- /**
1080
- * Resolved response field key names with defaults applied
1081
- *
1082
- * Maps logical pagination concepts to the actual key names
1083
- * used in the API response. Unset values fall back to Laravel defaults.
1084
- *
1085
- * For NestJS responses, use dot-notation paths:
1086
- * ```typescript
1087
- * new ResponseOptions({
1088
- * currentPage: 'meta.currentPage',
1089
- * total: 'meta.totalItems'
1090
- * });
1091
- * ```
1092
- */
1093
- declare class ResponseOptions {
1094
- readonly currentPage: string;
1095
- readonly data: string;
1096
- readonly firstPageUrl: string;
1097
- readonly from: string;
1098
- readonly lastPage: string;
1099
- readonly lastPageUrl: string;
1100
- readonly nextPageUrl: string;
1101
- readonly path: string;
1102
- readonly perPage: string;
1103
- readonly prevPageUrl: string;
1104
- readonly to: string;
1105
- readonly total: string;
1106
- constructor(options: IPaginationConfig);
1107
- }
1108
-
1109
1381
  /**
1110
1382
  * Strategy interface for parsing paginated API responses
1111
1383
  *
@@ -1250,6 +1522,19 @@ declare class UnselectableModelError extends Error {
1250
1522
  constructor(model: string);
1251
1523
  }
1252
1524
 
1525
+ /**
1526
+ * Error thrown when embedded resources are attempted with a driver that
1527
+ * does not support them
1528
+ *
1529
+ * Embedded-resource fetching (`select=col,relation(col1,col2)`) is a
1530
+ * PostgREST-native join mechanism and is only supported by the PostgREST
1531
+ * driver. Drivers with a standalone relation parameter expose it through
1532
+ * `addIncludes()` instead (JSON:API, Spatie, Strapi, @nestjsx/crud).
1533
+ */
1534
+ declare class UnsupportedEmbeddedError extends Error {
1535
+ constructor();
1536
+ }
1537
+
1253
1538
  /**
1254
1539
  * Error thrown when per-model field selection is attempted with a driver that does not support it
1255
1540
  *
@@ -1449,294 +1734,382 @@ declare abstract class AbstractRequestStrategy implements IRequestStrategy {
1449
1734
  }
1450
1735
 
1451
1736
  /**
1452
- * Request strategy for the Django REST Framework (DRF) driver
1453
- *
1454
- * Generates URIs in DRF's flat query-parameter format, augmented by
1455
- * django-filter's double-underscore lookup convention:
1456
- *
1457
- * - Simple filters: `field=value` (multi-value collapses to `field__in=v1,v2`)
1458
- * - Operator filters: `field__lookup=value` (translated from
1459
- * `FilterOperatorEnum` `GTE`→`__gte`, `ILIKE`→`__icontains`,
1460
- * `BTW`→`__range`, `NULL`→`__isnull`, etc.)
1461
- * - Ordering: `ordering=-field1,field2` (`-` prefix = DESC)
1462
- * - Search: `search=term` (DRF's SearchFilter)
1463
- * - Pagination: `page=N&page_size=M` (PageNumberPagination)
1464
- *
1465
- * `ordering` and `page_size` are DRF-idiomatic param names and are
1466
- * intentionally not configurable via `QueryBuilderOptions` same
1467
- * precedent as PostgREST's `order` and `offset`. PostgREST's full-text
1468
- * search operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) and the generic
1469
- * `NOT` have no django-filter equivalent and throw
1470
- * `UnsupportedFilterOperatorError`.
1471
- *
1472
- * @see https://www.django-rest-framework.org/api-guide/filtering/
1473
- * @see https://django-filter.readthedocs.io/
1737
+ * Request strategy for the API Platform (Symfony) driver
1738
+ *
1739
+ * Generates URIs in [API Platform's filter format](https://api-platform.com/docs/core/filters/):
1740
+ * - Filters: `field=value` (exact); multi-value uses the array syntax
1741
+ * (`field[]=v1&field[]=v2`, OR semantics)
1742
+ * - Operator filters: bracket syntax `field[op]=value` RangeFilter
1743
+ * (`gt`/`gte`/`lt`/`lte`/`between`), SearchFilter strategies
1744
+ * (`partial`/`ipartial`/`start`), ExistsFilter (`exists`) — see the
1745
+ * mapping on `_formatOperatorSegments`
1746
+ * - Relation filtering: dot paths pass through (`author.name=John` via
1747
+ * `addFilter('author.name', 'John')`)
1748
+ * - Sorts: `order[field]=asc` / `order[field]=desc` (one param per rule)
1749
+ * - Pagination: `page=N&itemsPerPage=M`
1750
+ *
1751
+ * The `order` and `itemsPerPage` keys are API Platform conventions and
1752
+ * intentionally not configurable through `QueryBuilderOptions`; `page`
1753
+ * honours the existing option key (its default matches the wire format).
1754
+ *
1755
+ * Date fields use API Platform's DateFilter (`field[after]=…`,
1756
+ * `field[before]=…`) — there is no `FilterOperatorEnum` counterpart, but
1757
+ * the bracket key passes through `addFilter` directly:
1758
+ * `addFilter('createdAt[after]', '2023-01-01')`.
1759
+ *
1760
+ * `NOT` (no negation filter in API Platform core) and the
1761
+ * PostgREST-native full-text operators (`FTS`, `PHFTS`, `PLFTS`,
1762
+ * `WFTS`) throw `UnsupportedFilterOperatorError`.
1763
+ *
1764
+ * @see https://api-platform.com/docs/core/filters/
1474
1765
  */
1475
- declare class DrfRequestStrategy extends AbstractRequestStrategy {
1766
+ declare class ApiPlatformRequestStrategy extends AbstractRequestStrategy {
1476
1767
  /**
1477
- * Simple filters, operator filters (django-filter lookups), sorts, and
1478
- * global search no per-model fields, no relation includes, no flat
1479
- * select (django-restql adds it but is not core DRF)
1768
+ * Filters, operator filters, sorts — no per-model fields, no
1769
+ * includes (relations embed via serialization groups server-side),
1770
+ * no flat select, no global search parameter
1480
1771
  */
1481
1772
  readonly capabilities: IStrategyCapabilities;
1482
1773
  /**
1483
- * DRF-native names of the three hardcoded query keys
1774
+ * API Platform-native names of the two hardcoded query keys
1484
1775
  *
1485
- * `ordering` and `page_size` are DRF/django-filter conventions and are
1486
- * intentionally not configurable through `QueryBuilderOptions`. `page`
1487
- * matches the default `QueryBuilderOptions.page`, and `search` matches
1488
- * the default `QueryBuilderOptions.search`, so those flow through the
1489
- * shared options object.
1776
+ * `order[...]` and `itemsPerPage` are fixed conventions of API
1777
+ * Platform's OrderFilter and pagination; they are intentionally not
1778
+ * configurable through `QueryBuilderOptions` and live as private
1779
+ * statics so they are visible in one place.
1490
1780
  */
1491
- private static readonly _orderingKey;
1492
- private static readonly _pageSizeKey;
1781
+ private static readonly _itemsPerPageKey;
1782
+ private static readonly _orderKey;
1493
1783
  /**
1494
- * Emit DRF-format query-string segments in canonical order:
1495
- * filters → operator filters → orderingsearchpagination
1784
+ * Emit API Platform-format query-string segments in canonical order:
1785
+ * filters → operator filters → orderpageitemsPerPage
1496
1786
  *
1497
1787
  * @param state - The current query builder state
1498
- * @param options - The query parameter key name configuration
1788
+ * @param options - The query parameter key name configuration (used
1789
+ * for `page`, whose default matches the wire format)
1499
1790
  * @returns Ordered query-string fragments
1500
1791
  */
1501
1792
  protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
1502
1793
  /**
1503
1794
  * Append simple filter parameters
1504
1795
  *
1505
- * Single-value filters emit `field=value` (django-filter's default
1506
- * exact match). Multi-value filters collapse to django-filter's
1507
- * `field__in=v1,v2,v3` form, which is the idiomatic way to express
1508
- * "value in list" in DRF.
1796
+ * A single value emits the bare exact-match form (`field=value`);
1797
+ * multiple values use API Platform's array syntax with OR semantics
1798
+ * (`field[]=v1&field[]=v2`).
1509
1799
  *
1510
1800
  * @param state - The current query builder state
1511
1801
  * @param out - The accumulator the caller joins into the URI
1512
1802
  */
1513
1803
  private _appendFilters;
1514
1804
  /**
1515
- * Append operator-filter parameters as `field__lookup=value`
1516
- *
1517
- * Maps each `FilterOperatorEnum` value to a django-filter lookup
1518
- * suffix. `BTW` expands to `field__range=min,max`; `NULL` emits
1519
- * `field__isnull=true|false`; the generic `NOT` and PostgREST-only
1520
- * FTS operators are unsupported.
1805
+ * Append the itemsPerPage parameter
1521
1806
  *
1522
1807
  * @param state - The current query builder state
1523
1808
  * @param out - The accumulator the caller joins into the URI
1524
- * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
1525
- * exactly two values, or `NULL` does not receive exactly one boolean
1526
- * @throws {UnsupportedFilterOperatorError} If the operator has no
1527
- * django-filter equivalent
1528
1809
  */
1529
- private _appendOperatorFilters;
1810
+ private _appendItemsPerPage;
1530
1811
  /**
1531
- * Append `ordering=-field1,field2` (django's `-` prefix = DESC)
1812
+ * Append explicit operator filters in the bracket syntax
1532
1813
  *
1533
1814
  * @param state - The current query builder state
1534
1815
  * @param out - The accumulator the caller joins into the URI
1535
1816
  */
1536
- private _appendOrdering;
1817
+ private _appendOperatorFilters;
1537
1818
  /**
1538
- * Append `page=N&page_size=M`
1539
- *
1540
- * `page` follows `options.page` (default `page`, matching DRF); the
1541
- * size key is hardcoded to DRF's idiomatic `page_size`.
1819
+ * Append `order[field]=asc` / `order[field]=desc` params, one per rule
1542
1820
  *
1543
1821
  * @param state - The current query builder state
1544
- * @param options - The query parameter key name configuration
1545
1822
  * @param out - The accumulator the caller joins into the URI
1546
1823
  */
1547
- private _appendPagination;
1824
+ private _appendOrder;
1548
1825
  /**
1549
- * Append `search=term` when a search term is set
1826
+ * Append the page parameter
1550
1827
  *
1551
1828
  * @param state - The current query builder state
1552
1829
  * @param options - The query parameter key name configuration
1553
1830
  * @param out - The accumulator the caller joins into the URI
1554
1831
  */
1555
- private _appendSearch;
1832
+ private _appendPage;
1556
1833
  /**
1557
- * Translate a `FilterOperatorEnum` operator filter into a
1558
- * `[suffix, serializedValue]` pair
1559
- *
1560
- * The suffix is appended to the field name with a `__` separator on the
1561
- * wire side (`field__gte=18`). The empty string means "no suffix"
1562
- * django-filter's implicit `exact` lookup.
1563
- *
1564
- * Mapping:
1565
- * - `EQ` → `''` (no suffix; default exact match)
1566
- * - `GT`/`GTE`/`LT`/`LTE`/`CONTAINS` → identity (lowercased name)
1567
- * - `ILIKE` → `icontains` (closest case-insensitive analog)
1568
- * - `IN` → `in` with comma-joined values
1569
- * - `SW` → `startswith`
1570
- * - `BTW` `range` with comma-joined `[min, max]` (arity-checked)
1571
- * - `NULL` → `isnull` with boolean value (arity- and type-checked)
1572
- * - `NOT` → `UnsupportedFilterOperatorError` (no generic negation in
1573
- * django-filter; use `__exclude` on the queryset instead)
1574
- * - `FTS`/`PLFTS`/`PHFTS`/`WFTS` `UnsupportedFilterOperatorError`
1834
+ * Translate a `FilterOperatorEnum` operator filter into one or more
1835
+ * API Platform bracket segments
1836
+ *
1837
+ * The mapping is library-canonical API Platform-native:
1838
+ * - `EQ` bare exact match (`field=value`, SearchFilter exact)
1839
+ * - `GT`/`GTE`/`LT`/`LTE` → RangeFilter (`field[gt]=v`, …)
1840
+ * - `BTW` → RangeFilter `field[between]=min..max` (arity-checked)
1841
+ * - `CONTAINS` → SearchFilter partial (`field[partial]=v`)
1842
+ * - `ILIKE` → SearchFilter ipartial (`field[ipartial]=v`,
1843
+ * case-insensitive)
1844
+ * - `SW` → SearchFilter start (`field[start]=v`)
1845
+ * - `IN` → array syntax (`field[]=v1&field[]=v2`)
1846
+ * - `NULL` → ExistsFilter — **inverted**: `true` (IS NULL) emits
1847
+ * `field[exists]=false`, `false` (IS NOT NULL) emits
1848
+ * `field[exists]=true`; arity- and type-checked
1849
+ *
1850
+ * `NOT` (API Platform core ships no negation filter) and PostgREST's
1851
+ * full-text-search operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
1852
+ * `UnsupportedFilterOperatorError`.
1575
1853
  *
1576
1854
  * @param filter - The operator filter to translate
1577
- * @returns A `[lookupSuffix, serializedValue]` tuple
1855
+ * @returns One or more bracket-syntax query-string segments
1578
1856
  * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
1579
1857
  * exactly two values, or `NULL` does not receive exactly one boolean
1580
- * @throws {UnsupportedFilterOperatorError} If the operator has no
1581
- * django-filter equivalent
1858
+ * @throws {UnsupportedFilterOperatorError} If the operator has no API
1859
+ * Platform equivalent
1582
1860
  */
1583
- private _formatOperatorPayload;
1861
+ private _formatOperatorSegments;
1584
1862
  }
1585
1863
 
1586
1864
  /**
1587
- * Response strategy for the Django REST Framework (DRF) driver
1865
+ * Base class for response strategies whose pagination metadata lives at
1866
+ * dot-notation paths inside the response body
1588
1867
  *
1589
- * Parses DRF `PageNumberPagination` responses:
1868
+ * JSON:API and NestJS share an identical body-traversal algorithm: the
1869
+ * total / current-page / etc. live at nested keys like `meta.total`, and
1870
+ * `from`/`to` are either present directly or must be derived from
1871
+ * `currentPage` × `perPage`. Both strategies were duplicating this
1872
+ * verbatim before this base existed; concrete classes now extend and
1873
+ * provide only the docstring describing their driver's specific path
1874
+ * conventions (see `JsonApiResponseStrategy`, `NestjsResponseStrategy`).
1875
+ *
1876
+ * Drivers whose pagination metadata travels via HTTP headers (PostgREST)
1877
+ * or whose body has a flat shape with no dot paths (Laravel, Spatie) do
1878
+ * not extend this class — they implement `IResponseStrategy` directly.
1879
+ */
1880
+ declare abstract class AbstractDotPathResponseStrategy implements IResponseStrategy {
1881
+ /**
1882
+ * Parse a nested-envelope pagination response into a PaginatedCollection
1883
+ *
1884
+ * @param response - The raw API response object
1885
+ * @param options - The response key name configuration (dot-notation paths supported)
1886
+ * @returns A typed PaginatedCollection instance
1887
+ */
1888
+ paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
1889
+ /**
1890
+ * Resolve a value from a response object using a dot-notation path
1891
+ *
1892
+ * Supports both flat keys (`'data'`) and nested paths (`'meta.totalItems'`).
1893
+ *
1894
+ * @param response - The raw response object
1895
+ * @param path - The dot-notation path to resolve
1896
+ * @returns The resolved value, or undefined if any segment is missing
1897
+ */
1898
+ protected resolve(response: Record<string, any>, path: string): unknown;
1899
+ /**
1900
+ * Resolve the "from" index value
1901
+ *
1902
+ * If `options.from` resolves to a value in the response, use it.
1903
+ * Otherwise compute `(currentPage - 1) * perPage + 1` when both are known.
1904
+ *
1905
+ * @param response - The raw response object
1906
+ * @param options - The response key name configuration
1907
+ * @param currentPage - The current page number
1908
+ * @param perPage - The number of items per page
1909
+ * @returns The "from" index, or `undefined` when neither path nor inputs suffice
1910
+ */
1911
+ protected resolveFrom(response: Record<string, any>, options: ResponseOptions, currentPage: number, perPage?: number): number | undefined;
1912
+ /**
1913
+ * Resolve the "to" index value
1914
+ *
1915
+ * If `options.to` resolves to a value in the response, use it.
1916
+ * Otherwise compute `Math.min(currentPage * perPage, total)` when all
1917
+ * three are known.
1918
+ *
1919
+ * @param response - The raw response object
1920
+ * @param options - The response key name configuration
1921
+ * @param currentPage - The current page number
1922
+ * @param perPage - The number of items per page
1923
+ * @param total - The total number of items
1924
+ * @returns The "to" index, or `undefined` when neither path nor inputs suffice
1925
+ */
1926
+ protected resolveTo(response: Record<string, any>, options: ResponseOptions, currentPage: number, perPage?: number, total?: number): number | undefined;
1927
+ }
1928
+
1929
+ /**
1930
+ * Response strategy for the API Platform (Symfony) driver
1931
+ *
1932
+ * Parses API Platform's default Hydra/JSON-LD collection envelope:
1590
1933
  *
1591
1934
  * ```json
1592
1935
  * {
1593
- * "count": 100,
1594
- * "next": "http://api.example.com/items/?page=3",
1595
- * "previous": "http://api.example.com/items/?page=1",
1596
- * "results": [...]
1936
+ * "@context": "/contexts/Book",
1937
+ * "@type": "hydra:Collection",
1938
+ * "hydra:totalItems": 48,
1939
+ * "hydra:member": [...],
1940
+ * "hydra:view": {
1941
+ * "@id": "/books?page=3&itemsPerPage=10",
1942
+ * "hydra:first": "/books?page=1",
1943
+ * "hydra:previous": "/books?page=2",
1944
+ * "hydra:next": "/books?page=4",
1945
+ * "hydra:last": "/books?page=5"
1946
+ * }
1597
1947
  * }
1598
1948
  * ```
1599
1949
  *
1600
- * DRF emits no `current_page` field in the body, so this strategy
1601
- * **derives** the current page (and the page size) by inspecting the
1602
- * `next` / `previous` URLs:
1603
- *
1604
- * - `previous === null` → current page is **1**.
1605
- * - `previous` set but has no `?page=N` param → DRF omits `page=1` from
1606
- * URLs when the previous page is the first, so we infer **2**.
1607
- * - `previous` has `?page=N` current page is **N + 1**.
1608
- *
1609
- * Similarly, `perPage` is parsed from any `?page_size=N` query param on
1610
- * `next` or `previous`, and `lastPage` is computed as
1611
- * `ceil(count / perPage)`. When `perPage` cannot be discovered (e.g. on a
1612
- * single-page response that emits both URLs as `null`), `perPage` and
1613
- * `lastPage` are left undefined.
1614
- *
1615
- * Key paths are resolved through `DrfResponseOptions`, which defaults
1616
- * `data 'results'`, `total 'count'`, `nextPageUrl → 'next'`,
1617
- * `prevPageUrl 'previous'`. The current-page / per-page / last-page
1618
- * paths default to empty strings the strategy ignores `options` for
1619
- * those slots and uses URL inspection instead.
1620
- *
1621
- * @see https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination
1950
+ * The Hydra keys contain colons but no dots, so the inherited
1951
+ * dot-notation resolver traverses them cleanly (`hydra:view.hydra:next`
1952
+ * `response['hydra:view']['hydra:next']`). The body names no
1953
+ * current-page or page-size field, so both are **derived from the
1954
+ * `hydra:view` URLs**:
1955
+ *
1956
+ * - `currentPage` from the `page` param of the view's `@id` (the
1957
+ * `path` option slot points there); missing view page **1**.
1958
+ * - `perPage` from the `itemsPerPage` param of the view's `@id`
1959
+ * (echoed whenever the request set it this driver's request
1960
+ * strategy always does), falling back to the item count of a page
1961
+ * that has a `hydra:next` successor.
1962
+ * - `lastPage` from the `page` param of `hydra:last`, falling back to
1963
+ * `ceil(total ÷ perPage)`; a view-less response holding the whole
1964
+ * collection resolves to 1.
1965
+ *
1966
+ * URLs are typically **relative** (`/books?page=4`) parsing retries
1967
+ * against a placeholder base, and the links are surfaced as-is on the
1968
+ * collection. JSON:API and HAL serialization formats are out of scope
1969
+ * (use the JSON:API driver for the former).
1970
+ *
1971
+ * @see https://api-platform.com/docs/core/pagination/
1622
1972
  */
1623
- declare class DrfResponseStrategy implements IResponseStrategy {
1973
+ declare class ApiPlatformResponseStrategy extends AbstractDotPathResponseStrategy {
1624
1974
  /**
1625
- * Parse a DRF pagination response into a PaginatedCollection
1975
+ * Parse a Hydra collection response into a PaginatedCollection
1626
1976
  *
1627
1977
  * @param response - The raw API response body
1628
- * @param options - The response key name configuration
1978
+ * @param options - The response key name configuration (dot-notation paths supported)
1629
1979
  * @returns A typed PaginatedCollection instance
1630
1980
  */
1631
1981
  paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
1632
1982
  /**
1633
- * Derive the current page number from the `previous` URL
1983
+ * Derive the current page number from the `hydra:view` `@id` URL
1634
1984
  *
1635
- * - `null` page 1
1636
- * - URL without `?page=N` page 2 (DRF omits `page=1` from URLs)
1637
- * - URL with `?page=N` N + 1
1985
+ * Reads the `page` query param; a missing view (pagination disabled
1986
+ * or a single-page collection without a partial view) or a link
1987
+ * without the param resolves to page 1.
1638
1988
  *
1639
- * @param prevPageUrl - The `previous` link from the DRF response, or null
1989
+ * @param viewUrl - The `hydra:view.@id` URL, or null
1640
1990
  * @returns The current page number
1641
1991
  */
1642
1992
  private _deriveCurrentPage;
1643
1993
  /**
1644
- * Derive `from` as the 1-indexed offset of the first item on this page
1645
- *
1646
- * @param currentPage - The current page number
1647
- * @param perPage - The page size (may be undefined)
1648
- * @returns The 1-indexed `from` index, or undefined when perPage is unknown
1649
- */
1650
- private _deriveFrom;
1651
- /**
1652
- * Derive the last page number as `ceil(total / perPage)`
1994
+ * Derive the last page number
1653
1995
  *
1654
- * Both inputs must be defined; an empty result set (`total === 0`)
1655
- * yields `lastPage = 0` which the caller treats as "no useful info"
1656
- * and skips the sync to `NestService.lastPage`.
1996
+ * Resolution order: the `page` param of `hydra:last`, then
1997
+ * `ceil(total ÷ perPage)`, then 1 for a view-less response that
1998
+ * provably holds the entire non-empty collection.
1657
1999
  *
2000
+ * @param lastPageUrl - The `hydra:view.hydra:last` URL (may be undefined)
2001
+ * @param viewUrl - The `hydra:view.@id` URL, or null
2002
+ * @param data - The items on the current page
1658
2003
  * @param total - The total item count
1659
2004
  * @param perPage - The page size
1660
- * @returns The last page number, or undefined when either input is missing
2005
+ * @returns The last page number, or undefined when inputs insufficient
1661
2006
  */
1662
2007
  private _deriveLastPage;
1663
2008
  /**
1664
- * Derive `perPage` by parsing `?page_size=N` from any available URL
2009
+ * Derive `perPage` from the `hydra:view` `@id` URL
1665
2010
  *
1666
- * Tries `next` first (page 1 always has a `next` URL with `page_size`
1667
- * if any non-default size was requested), then falls back to
1668
- * `previous`. Returns undefined when neither URL contains the param
1669
- * the consumer is then on a single-page result with the server's
1670
- * default page size, which is not introspectable from the body alone.
2011
+ * Reads the `itemsPerPage` query param (echoed whenever the request
2012
+ * set it). When absent, a page with a `hydra:next` successor is
2013
+ * necessarily full, so its item count equals the page size.
1671
2014
  *
1672
- * @param nextPageUrl - The `next` link from the response, or null
1673
- * @param prevPageUrl - The `previous` link from the response, or null
2015
+ * @param viewUrl - The `hydra:view.@id` URL, or null
2016
+ * @param nextPageUrl - The `hydra:view.hydra:next` URL (may be undefined)
2017
+ * @param data - The items on the current page
1674
2018
  * @returns The page size, or undefined
1675
2019
  */
1676
2020
  private _derivePerPage;
1677
2021
  /**
1678
- * Derive `to` as the 1-indexed offset of the last item on this page
2022
+ * Extract an integer query parameter from a Hydra URL
1679
2023
  *
1680
- * Clamped to `total` so the last page does not report past the end.
1681
- *
1682
- * @param currentPage - The current page number
1683
- * @param perPage - The page size (may be undefined)
1684
- * @param total - The total item count (may be undefined)
1685
- * @returns The 1-indexed `to` index, or undefined when inputs insufficient
2024
+ * @param url - The URL to parse
2025
+ * @param name - The query-parameter name to look up (e.g. `page`)
2026
+ * @returns The integer value, or undefined
1686
2027
  */
1687
- private _deriveTo;
2028
+ private _extractNumberParam;
1688
2029
  /**
1689
- * Extract the `page` query parameter from a DRF pagination URL
2030
+ * Extract a single query parameter from a URL via the WHATWG URL parser
1690
2031
  *
1691
- * Returns the integer value of `?page=N`, or undefined when the URL is
1692
- * malformed or has no `page` param (which, by DRF convention, means
1693
- * page 1 the caller infers the semantics).
2032
+ * Hydra links are typically **relative** (`/books?page=4`), so parsing
2033
+ * retries against a placeholder base before giving up. Returns
2034
+ * undefined when the URL is unparseable or the parameter is absent.
1694
2035
  *
1695
2036
  * @param url - The URL to parse
1696
- * @returns The integer page value, or undefined
2037
+ * @param name - The query-parameter name to look up
2038
+ * @returns The raw string value of the parameter, or undefined
1697
2039
  */
1698
- private _extractPageParam;
2040
+ private _extractQueryParam;
1699
2041
  /**
1700
- * Extract the `page_size` query parameter from a DRF pagination URL
2042
+ * Derive `from` for a view-less response holding the whole collection
1701
2043
  *
1702
- * @param url - The URL to parse (or null)
1703
- * @returns The integer page-size value, or undefined
2044
+ * @param viewUrl - The `hydra:view.@id` URL, or null
2045
+ * @param data - The items on the current page
2046
+ * @param total - The total item count
2047
+ * @returns 1 when the response provably holds all items, undefined otherwise
1704
2048
  */
1705
- private _extractPageSizeParam;
2049
+ private _wholeSetFrom;
1706
2050
  /**
1707
- * Extract a single query parameter from a URL via the WHATWG URL parser
2051
+ * Derive `to` for a view-less response holding the whole collection
1708
2052
  *
1709
- * Returns undefined when the URL is unparseable (relative URL without a
1710
- * base, or malformed) or when the parameter is absent.
1711
- *
1712
- * @param url - The URL to parse
1713
- * @param name - The query-parameter name to look up
1714
- * @returns The raw string value of the parameter, or undefined
2053
+ * @param viewUrl - The `hydra:view.@id` URL, or null
2054
+ * @param data - The items on the current page
2055
+ * @param total - The total item count
2056
+ * @returns The item count when the response provably holds all items, undefined otherwise
1715
2057
  */
1716
- private _extractQueryParam;
2058
+ private _wholeSetTo;
1717
2059
  }
1718
2060
 
1719
2061
  /**
1720
- * Request strategy for the JSON:API driver
1721
- *
1722
- * Generates URIs in the JSON:API format:
1723
- * - Fields: `fields[articles]=title,body&fields[people]=name`
1724
- * - Filters: `filter[status]=active`
1725
- * - Includes: `include=author,comments.author`
1726
- * - Pagination: `page[number]=1&page[size]=15`
1727
- * - Sort: `sort=-created_at,name` (- prefix = DESC)
1728
- *
1729
- * @see https://jsonapi.org/format/
2062
+ * Request strategy for the Directus driver
2063
+ *
2064
+ * Generates URIs in [Directus' query format](https://docs.directus.io/reference/query.html):
2065
+ * - Filters: `filter[field][_eq]=value` (multi-value collapses to `_in`)
2066
+ * - Operator filters: `filter[field][_op]=value` (translated from
2067
+ * `FilterOperatorEnum` `BTW`→`_between`, `SW`→`_starts_with`,
2068
+ * `ILIKE`→`_icontains`, `NOT`→`_neq`/`_nin`, `NULL`→`_null`/`_nnull`)
2069
+ * - Sorts: `sort=-created_at,name` (CSV, `-` prefix = DESC)
2070
+ * - Field selection / relations: a single `fields=` CSV — flat columns
2071
+ * from `addSelect`, whole relations from `addIncludes` (`rel.*`), and
2072
+ * column-projected relations from `addEmbedded` (`rel.col1,rel.col2`)
2073
+ * - Search: `search=term` (global full-text search)
2074
+ * - Metadata: a constant `meta=total_count,filter_count` so responses
2075
+ * carry the totals the response strategy needs
2076
+ * - Pagination (page-based): `limit=N&page=N`
2077
+ *
2078
+ * The `filter` / `sort` / `fields` / `search` / `limit` / `page` keys
2079
+ * honour the existing `QueryBuilderOptions` names (their defaults match
2080
+ * the Directus wire format); `meta` is fixed by the server and lives as
2081
+ * a private static. Directus' `deep[...]` relational query options and
2082
+ * nested relation filtering are out of scope.
2083
+ *
2084
+ * PostgREST-native full-text search operators (`FTS`, `PHFTS`, `PLFTS`,
2085
+ * `WFTS`) throw `UnsupportedFilterOperatorError` — use `search` or the
2086
+ * `CONTAINS` / `ILIKE` operator filters instead.
2087
+ *
2088
+ * @see https://docs.directus.io/reference/query.html
2089
+ * @see https://docs.directus.io/reference/filter-rules.html
1730
2090
  */
1731
- declare class JsonApiRequestStrategy extends AbstractRequestStrategy {
2091
+ declare class DirectusRequestStrategy extends AbstractRequestStrategy {
1732
2092
  /**
1733
- * Filters, sorts, includes, per-model fields same shape as Spatie
1734
- * but with bracket-style pagination
2093
+ * Filters, operator filters, sorts, flat select, includes and embedded
2094
+ * (both folding into `fields=`), global search — no per-model fields
2095
+ * (Directus scopes relational projections with dot paths, not a
2096
+ * `fields[model]` map)
1735
2097
  */
1736
2098
  readonly capabilities: IStrategyCapabilities;
1737
2099
  /**
1738
- * Emit JSON:API-format query-string segments in canonical order:
1739
- * include → fields → filters → pagination → sort
2100
+ * Directus-native name of the metadata query key
2101
+ *
2102
+ * `meta` has no `QueryBuilderOptions` slot and is fixed by the server,
2103
+ * so it lives as a private static to be visible in one place.
2104
+ */
2105
+ private static readonly _metaKey;
2106
+ /**
2107
+ * Emit Directus-format query-string segments in canonical order:
2108
+ * filter (merged) → sort → fields → search → meta → limit → page
2109
+ *
2110
+ * Simple filters and operator filters share a single `filter` wrapper
2111
+ * so qs emits one ordered, deeply-nested bracket structure rather than
2112
+ * two duplicate top-level `filter[...]` blocks.
1740
2113
  *
1741
2114
  * @param state - The current query builder state
1742
2115
  * @param options - The query parameter key name configuration
@@ -1744,17 +2117,31 @@ declare class JsonApiRequestStrategy extends AbstractRequestStrategy {
1744
2117
  */
1745
2118
  protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
1746
2119
  /**
1747
- * Append per-type field selection in bracket notation
2120
+ * Append the single `fields=` CSV combining flat columns, whole
2121
+ * relations, and column-projected relations
2122
+ *
2123
+ * Flat columns come from `addSelect`; relations from `addIncludes`
2124
+ * emit as `rel.*`; embedded relations from `addEmbedded` emit one
2125
+ * `rel.col` entry per column (or `rel.*` when no columns were given).
2126
+ * A relation present in both folds into the embedded fragment, which
2127
+ * carries the column information. When relations are present but no
2128
+ * flat columns were selected, the flat part defaults to `*` so the
2129
+ * base item's columns are not silently dropped from the projection.
1748
2130
  *
1749
2131
  * @param state - The current query builder state
1750
2132
  * @param options - The query parameter key name configuration
1751
2133
  * @param out - The accumulator the caller joins into the URI
1752
- * @throws Error if the resource is missing from the fields object
1753
- * @throws UnselectableModelError if a field type is not the resource or in includes
1754
2134
  */
1755
2135
  private _appendFields;
1756
2136
  /**
1757
- * Append filter parameters in bracket notation: `filter[key]=value`
2137
+ * Append the unified `filter[...]` wrapper combining simple filters
2138
+ * and operator filters
2139
+ *
2140
+ * Both kinds emit into the same nested object under the filter key so
2141
+ * qs produces a single deeply-bracketed block per request. Simple
2142
+ * single-value filters fold to `_eq`; simple multi-value filters fold
2143
+ * to `_in` (CSV). Operator filters then merge into the same per-field
2144
+ * map, potentially co-existing with a simple filter on the same field.
1758
2145
  *
1759
2146
  * @param state - The current query builder state
1760
2147
  * @param options - The query parameter key name configuration
@@ -1762,523 +2149,1989 @@ declare class JsonApiRequestStrategy extends AbstractRequestStrategy {
1762
2149
  */
1763
2150
  private _appendFilters;
1764
2151
  /**
1765
- * Append include parameter as `include=author,comments.author`
2152
+ * Append the limit parameter
1766
2153
  *
1767
2154
  * @param state - The current query builder state
1768
2155
  * @param options - The query parameter key name configuration
1769
2156
  * @param out - The accumulator the caller joins into the URI
1770
2157
  */
1771
- private _appendIncludes;
2158
+ private _appendLimit;
1772
2159
  /**
1773
- * Append JSON:API bracket pagination as `page[number]=1&page[size]=15`
2160
+ * Append the constant `meta=total_count,filter_count` parameter
1774
2161
  *
1775
- * `qs.stringify` already returns the two segments joined with `&`, so we
1776
- * push the whole string as one accumulator entry `_join` will glue
1777
- * it onto the rest with the same separator.
2162
+ * Always emitted: the Directus response strategy reads the totals from
2163
+ * `meta`, which the server only includes when the request asks for it.
2164
+ *
2165
+ * @param out - The accumulator the caller joins into the URI
2166
+ */
2167
+ private _appendMeta;
2168
+ /**
2169
+ * Append the page parameter
2170
+ *
2171
+ * Directus pages are 1-indexed, matching the library state directly.
1778
2172
  *
1779
2173
  * @param state - The current query builder state
1780
2174
  * @param options - The query parameter key name configuration
1781
2175
  * @param out - The accumulator the caller joins into the URI
1782
2176
  */
1783
- private _appendPagination;
2177
+ private _appendPage;
1784
2178
  /**
1785
- * Append sort parameter as `sort=-field1,field2` (`-` prefix = DESC)
2179
+ * Append the `search=` global full-text search parameter
2180
+ *
2181
+ * @param state - The current query builder state
2182
+ * @param options - The query parameter key name configuration
2183
+ * @param out - The accumulator the caller joins into the URI
2184
+ */
2185
+ private _appendSearch;
2186
+ /**
2187
+ * Append the `sort=-created_at,name` CSV parameter
1786
2188
  *
1787
2189
  * @param state - The current query builder state
1788
2190
  * @param options - The query parameter key name configuration
1789
2191
  * @param out - The accumulator the caller joins into the URI
1790
2192
  */
1791
2193
  private _appendSort;
2194
+ /**
2195
+ * Translate a `FilterOperatorEnum` operator filter into Directus'
2196
+ * `_operator → value` payload shape
2197
+ *
2198
+ * The mapping is library-canonical → Directus-native:
2199
+ * - `EQ`/`GT`/`GTE`/`LT`/`LTE` → `_eq`/`_gt`/`_gte`/`_lt`/`_lte`
2200
+ * - `CONTAINS` → `_contains`; `ILIKE` → `_icontains` (case-insensitive)
2201
+ * - `SW` → `_starts_with`
2202
+ * - `IN` → `_in` (CSV)
2203
+ * - `BTW` → `_between` with `min,max` (arity-checked)
2204
+ * - `NOT` → `_neq` (single value) / `_nin` (multi-value, CSV)
2205
+ * - `NULL` → `_null=true` (when value is `true`) / `_nnull=true` (when
2206
+ * value is `false`); arity- and type-checked
2207
+ *
2208
+ * PostgREST's full-text-search operators (`FTS`, `PHFTS`, `PLFTS`,
2209
+ * `WFTS`) have no Directus equivalent and throw
2210
+ * `UnsupportedFilterOperatorError`.
2211
+ *
2212
+ * @param filter - The operator filter to translate
2213
+ * @returns A `{ _operator: value }` payload ready to merge under
2214
+ * `filter[field]`
2215
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
2216
+ * exactly two values, or `NULL` does not receive exactly one boolean
2217
+ * @throws {UnsupportedFilterOperatorError} If the operator is a
2218
+ * PostgREST-only FTS variant
2219
+ */
2220
+ private _formatOperatorPayload;
1792
2221
  }
1793
2222
 
1794
2223
  /**
1795
- * Base class for response strategies whose pagination metadata lives at
1796
- * dot-notation paths inside the response body
2224
+ * Response strategy for the Directus driver
1797
2225
  *
1798
- * JSON:API and NestJS share an identical body-traversal algorithm: the
1799
- * total / current-page / etc. live at nested keys like `meta.total`, and
1800
- * `from`/`to` are either present directly or must be derived from
1801
- * `currentPage` × `perPage`. Both strategies were duplicating this
1802
- * verbatim before this base existed; concrete classes now extend and
1803
- * provide only the docstring describing their driver's specific path
1804
- * conventions (see `JsonApiResponseStrategy`, `NestjsResponseStrategy`).
2226
+ * Parses Directus collection responses (with `meta=total_count,filter_count`
2227
+ * requested, which the request strategy always does):
1805
2228
  *
1806
- * Drivers whose pagination metadata travels via HTTP headers (PostgREST)
1807
- * or whose body has a flat shape with no dot paths (Laravel, Spatie) do
1808
- * not extend this class they implement `IResponseStrategy` directly.
2229
+ * ```json
2230
+ * {
2231
+ * "data": [{ "id": 1, "title": "Hello" }],
2232
+ * "meta": { "total_count": 48, "filter_count": 12 }
2233
+ * }
2234
+ * ```
2235
+ *
2236
+ * The default `total` path is `meta.filter_count` — the number of items
2237
+ * matching the current filter, which is the relevant total for paging a
2238
+ * filtered collection (`meta.total_count` ignores filters; point the
2239
+ * `total` path at it via `IPaginationConfig` if that is what you want).
2240
+ *
2241
+ * The envelope carries **no current-page or page-size field**, so:
2242
+ *
2243
+ * - `currentPage` falls back to **1** unless a `currentPage` path is
2244
+ * configured and resolves (only guaranteed correct for single-page
2245
+ * results — track the requested page in your own state for multi-page
2246
+ * UIs).
2247
+ * - `perPage` resolves only when a `perPage` path is configured.
2248
+ * - `lastPage` is `ceil(total ÷ perPage)` when both are known; on a
2249
+ * response that provably holds the whole filtered set it resolves
2250
+ * to 1.
2251
+ *
2252
+ * Every key path is overridable via `IPaginationConfig` (dot notation
2253
+ * supported), so custom wrappers that do include paging fields map
2254
+ * without subclassing.
2255
+ *
2256
+ * @see https://docs.directus.io/reference/query.html#meta
1809
2257
  */
1810
- declare abstract class AbstractDotPathResponseStrategy implements IResponseStrategy {
2258
+ declare class DirectusResponseStrategy extends AbstractDotPathResponseStrategy {
1811
2259
  /**
1812
- * Parse a nested-envelope pagination response into a PaginatedCollection
2260
+ * Parse a Directus collection response into a PaginatedCollection
1813
2261
  *
1814
- * @param response - The raw API response object
2262
+ * @param response - The raw API response body
1815
2263
  * @param options - The response key name configuration (dot-notation paths supported)
1816
2264
  * @returns A typed PaginatedCollection instance
1817
2265
  */
1818
2266
  paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
1819
2267
  /**
1820
- * Resolve a value from a response object using a dot-notation path
2268
+ * Derive the last page number
1821
2269
  *
1822
- * Supports both flat keys (`'data'`) and nested paths (`'meta.totalItems'`).
2270
+ * Resolution order: the configured `lastPage` path, then
2271
+ * `ceil(total ÷ perPage)` when both are known, then 1 when the
2272
+ * response provably holds the entire non-empty filtered set.
1823
2273
  *
1824
2274
  * @param response - The raw response object
1825
- * @param path - The dot-notation path to resolve
1826
- * @returns The resolved value, or undefined if any segment is missing
2275
+ * @param options - The response key name configuration
2276
+ * @param data - The items on the current page
2277
+ * @param total - The total item count
2278
+ * @param perPage - The page size
2279
+ * @returns The last page number, or undefined when inputs insufficient
1827
2280
  */
1828
- protected resolve(response: Record<string, any>, path: string): unknown;
2281
+ private _deriveLastPage;
1829
2282
  /**
1830
- * Resolve the "from" index value
2283
+ * Derive `from` for a response that holds the whole filtered set
1831
2284
  *
1832
- * If `options.from` resolves to a value in the response, use it.
1833
- * Otherwise compute `(currentPage - 1) * perPage + 1` when both are known.
1834
- *
1835
- * @param response - The raw response object
1836
- * @param options - The response key name configuration
1837
- * @param currentPage - The current page number
1838
- * @param perPage - The number of items per page
1839
- * @returns The "from" index, or `undefined` when neither path nor inputs suffice
2285
+ * @param data - The items on the current page
2286
+ * @param total - The total item count
2287
+ * @returns 1 when the page provably holds all items, undefined otherwise
1840
2288
  */
1841
- protected resolveFrom(response: Record<string, any>, options: ResponseOptions, currentPage: number, perPage?: number): number | undefined;
2289
+ private _singlePageFrom;
1842
2290
  /**
1843
- * Resolve the "to" index value
1844
- *
1845
- * If `options.to` resolves to a value in the response, use it.
1846
- * Otherwise compute `Math.min(currentPage * perPage, total)` when all
1847
- * three are known.
2291
+ * Derive `to` for a response that holds the whole filtered set
1848
2292
  *
1849
- * @param response - The raw response object
1850
- * @param options - The response key name configuration
1851
- * @param currentPage - The current page number
1852
- * @param perPage - The number of items per page
1853
- * @param total - The total number of items
1854
- * @returns The "to" index, or `undefined` when neither path nor inputs suffice
2293
+ * @param data - The items on the current page
2294
+ * @param total - The total item count
2295
+ * @returns The item count when the page provably holds all items, undefined otherwise
1855
2296
  */
1856
- protected resolveTo(response: Record<string, any>, options: ResponseOptions, currentPage: number, perPage?: number, total?: number): number | undefined;
2297
+ private _singlePageTo;
1857
2298
  }
1858
2299
 
1859
2300
  /**
1860
- * Response strategy for the JSON:API driver
1861
- *
1862
- * Parses JSON:API pagination responses:
1863
- * ```json
1864
- * {
1865
- * "data": [...],
1866
- * "meta": {
1867
- * "current-page": 1,
1868
- * "per-page": 10,
1869
- * "total": 100,
1870
- * "page-count": 10,
1871
- * "from": 1,
1872
- * "to": 10
1873
- * },
1874
- * "links": {
1875
- * "first": "url",
1876
- * "prev": "url",
1877
- * "next": "url",
1878
- * "last": "url"
1879
- * }
1880
- * }
1881
- * ```
2301
+ * Request strategy for the Django REST Framework (DRF) driver
1882
2302
  *
1883
- * Default key paths are configured in `JsonApiResponseOptions`. The
1884
- * traversal algorithm (dot-notation resolution + computed `from`/`to`) is
1885
- * inherited from `AbstractDotPathResponseStrategy`; this class exists so
1886
- * `DriverEnum.JSON_API` resolves to a distinct identity at the DI layer
1887
- * even though the parsing logic is shared with NestJS.
2303
+ * Generates URIs in DRF's flat query-parameter format, augmented by
2304
+ * django-filter's double-underscore lookup convention:
1888
2305
  *
1889
- * @see https://jsonapi.org/format/
1890
- */
1891
- declare class JsonApiResponseStrategy extends AbstractDotPathResponseStrategy {
1892
- }
1893
-
1894
- /**
1895
- * Request strategy for the Laravel (pagination-only) driver
2306
+ * - Simple filters: `field=value` (multi-value collapses to `field__in=v1,v2`)
2307
+ * - Operator filters: `field__lookup=value` (translated from
2308
+ * `FilterOperatorEnum` `GTE`→`__gte`, `ILIKE`→`__icontains`,
2309
+ * `BTW`→`__range`, `NULL`→`__isnull`, etc.)
2310
+ * - Ordering: `ordering=-field1,field2` (`-` prefix = DESC)
2311
+ * - Search: `search=term` (DRF's SearchFilter)
2312
+ * - Pagination: `page=N&page_size=M` (PageNumberPagination)
1896
2313
  *
1897
- * Generates simple pagination URIs:
1898
- * - `/{resource}?limit=N&page=N`
2314
+ * `ordering` and `page_size` are DRF-idiomatic param names and are
2315
+ * intentionally not configurable via `QueryBuilderOptions` — same
2316
+ * precedent as PostgREST's `order` and `offset`. PostgREST's full-text
2317
+ * search operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) and the generic
2318
+ * `NOT` have no django-filter equivalent and throw
2319
+ * `UnsupportedFilterOperatorError`.
1899
2320
  *
1900
- * Filters, sorts, fields, includes, search, and select in state are ignored.
2321
+ * @see https://www.django-rest-framework.org/api-guide/filtering/
2322
+ * @see https://django-filter.readthedocs.io/
1901
2323
  */
1902
- declare class LaravelRequestStrategy extends AbstractRequestStrategy {
2324
+ declare class DrfRequestStrategy extends AbstractRequestStrategy {
1903
2325
  /**
1904
- * Pagination-only driver no filtering, sorting, or column selection
2326
+ * Simple filters, operator filters (django-filter lookups), sorts, and
2327
+ * global search — no per-model fields, no relation includes, no flat
2328
+ * select (django-restql adds it but is not core DRF)
1905
2329
  */
1906
2330
  readonly capabilities: IStrategyCapabilities;
1907
2331
  /**
1908
- * Emit only the pagination params; filters/sorts/etc. are ignored
2332
+ * DRF-native names of the three hardcoded query keys
2333
+ *
2334
+ * `ordering` and `page_size` are DRF/django-filter conventions and are
2335
+ * intentionally not configurable through `QueryBuilderOptions`. `page`
2336
+ * matches the default `QueryBuilderOptions.page`, and `search` matches
2337
+ * the default `QueryBuilderOptions.search`, so those flow through the
2338
+ * shared options object.
2339
+ */
2340
+ private static readonly _orderingKey;
2341
+ private static readonly _pageSizeKey;
2342
+ /**
2343
+ * Emit DRF-format query-string segments in canonical order:
2344
+ * filters → operator filters → ordering → search → pagination
1909
2345
  *
1910
2346
  * @param state - The current query builder state
1911
2347
  * @param options - The query parameter key name configuration
1912
- * @returns The two pagination query-string fragments
2348
+ * @returns Ordered query-string fragments
1913
2349
  */
1914
2350
  protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
1915
- }
1916
-
1917
- /**
1918
- * Base class for response strategies whose pagination metadata is a flat
1919
- * key-value envelope on the response body
1920
- *
1921
- * Laravel's stock pagination and Spatie's `QueryBuilder` both emit the
1922
- * same flat shape — `{ data, current_page, total, per_page, from, to,
1923
- * next_page_url, prev_page_url, first_page_url, last_page, last_page_url
1924
- * }` and both response strategies were duplicating the byte-identical
1925
- * `new PaginatedCollection(response[options.X], ...)` body before this
1926
- * base existed. Concrete classes now extend and provide only the
1927
- * docstring describing their driver's specific shape (see
1928
- * `LaravelResponseStrategy`, `SpatieResponseStrategy`).
1929
- *
1930
- * Drivers whose pagination metadata is a nested envelope (JSON:API,
2351
+ /**
2352
+ * Append simple filter parameters
2353
+ *
2354
+ * Single-value filters emit `field=value` (django-filter's default
2355
+ * exact match). Multi-value filters collapse to django-filter's
2356
+ * `field__in=v1,v2,v3` form, which is the idiomatic way to express
2357
+ * "value in list" in DRF.
2358
+ *
2359
+ * @param state - The current query builder state
2360
+ * @param out - The accumulator the caller joins into the URI
2361
+ */
2362
+ private _appendFilters;
2363
+ /**
2364
+ * Append operator-filter parameters as `field__lookup=value`
2365
+ *
2366
+ * Maps each `FilterOperatorEnum` value to a django-filter lookup
2367
+ * suffix. `BTW` expands to `field__range=min,max`; `NULL` emits
2368
+ * `field__isnull=true|false`; the generic `NOT` and PostgREST-only
2369
+ * FTS operators are unsupported.
2370
+ *
2371
+ * @param state - The current query builder state
2372
+ * @param out - The accumulator the caller joins into the URI
2373
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
2374
+ * exactly two values, or `NULL` does not receive exactly one boolean
2375
+ * @throws {UnsupportedFilterOperatorError} If the operator has no
2376
+ * django-filter equivalent
2377
+ */
2378
+ private _appendOperatorFilters;
2379
+ /**
2380
+ * Append `ordering=-field1,field2` (django's `-` prefix = DESC)
2381
+ *
2382
+ * @param state - The current query builder state
2383
+ * @param out - The accumulator the caller joins into the URI
2384
+ */
2385
+ private _appendOrdering;
2386
+ /**
2387
+ * Append `page=N&page_size=M`
2388
+ *
2389
+ * `page` follows `options.page` (default `page`, matching DRF); the
2390
+ * size key is hardcoded to DRF's idiomatic `page_size`.
2391
+ *
2392
+ * @param state - The current query builder state
2393
+ * @param options - The query parameter key name configuration
2394
+ * @param out - The accumulator the caller joins into the URI
2395
+ */
2396
+ private _appendPagination;
2397
+ /**
2398
+ * Append `search=term` when a search term is set
2399
+ *
2400
+ * @param state - The current query builder state
2401
+ * @param options - The query parameter key name configuration
2402
+ * @param out - The accumulator the caller joins into the URI
2403
+ */
2404
+ private _appendSearch;
2405
+ /**
2406
+ * Translate a `FilterOperatorEnum` operator filter into a
2407
+ * `[suffix, serializedValue]` pair
2408
+ *
2409
+ * The suffix is appended to the field name with a `__` separator on the
2410
+ * wire side (`field__gte=18`). The empty string means "no suffix" —
2411
+ * django-filter's implicit `exact` lookup.
2412
+ *
2413
+ * Mapping:
2414
+ * - `EQ` → `''` (no suffix; default exact match)
2415
+ * - `GT`/`GTE`/`LT`/`LTE`/`CONTAINS` → identity (lowercased name)
2416
+ * - `ILIKE` → `icontains` (closest case-insensitive analog)
2417
+ * - `IN` → `in` with comma-joined values
2418
+ * - `SW` → `startswith`
2419
+ * - `BTW` → `range` with comma-joined `[min, max]` (arity-checked)
2420
+ * - `NULL` → `isnull` with boolean value (arity- and type-checked)
2421
+ * - `NOT` → `UnsupportedFilterOperatorError` (no generic negation in
2422
+ * django-filter; use `__exclude` on the queryset instead)
2423
+ * - `FTS`/`PLFTS`/`PHFTS`/`WFTS` → `UnsupportedFilterOperatorError`
2424
+ *
2425
+ * @param filter - The operator filter to translate
2426
+ * @returns A `[lookupSuffix, serializedValue]` tuple
2427
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
2428
+ * exactly two values, or `NULL` does not receive exactly one boolean
2429
+ * @throws {UnsupportedFilterOperatorError} If the operator has no
2430
+ * django-filter equivalent
2431
+ */
2432
+ private _formatOperatorPayload;
2433
+ }
2434
+
2435
+ /**
2436
+ * Response strategy for the Django REST Framework (DRF) driver
2437
+ *
2438
+ * Parses DRF `PageNumberPagination` responses:
2439
+ *
2440
+ * ```json
2441
+ * {
2442
+ * "count": 100,
2443
+ * "next": "http://api.example.com/items/?page=3",
2444
+ * "previous": "http://api.example.com/items/?page=1",
2445
+ * "results": [...]
2446
+ * }
2447
+ * ```
2448
+ *
2449
+ * DRF emits no `current_page` field in the body, so this strategy
2450
+ * **derives** the current page (and the page size) by inspecting the
2451
+ * `next` / `previous` URLs:
2452
+ *
2453
+ * - `previous === null` → current page is **1**.
2454
+ * - `previous` set but has no `?page=N` param → DRF omits `page=1` from
2455
+ * URLs when the previous page is the first, so we infer **2**.
2456
+ * - `previous` has `?page=N` → current page is **N + 1**.
2457
+ *
2458
+ * Similarly, `perPage` is parsed from any `?page_size=N` query param on
2459
+ * `next` or `previous`, and `lastPage` is computed as
2460
+ * `ceil(count / perPage)`. When `perPage` cannot be discovered (e.g. on a
2461
+ * single-page response that emits both URLs as `null`), `perPage` and
2462
+ * `lastPage` are left undefined.
2463
+ *
2464
+ * Key paths are resolved through `DrfResponseOptions`, which defaults
2465
+ * `data → 'results'`, `total → 'count'`, `nextPageUrl → 'next'`,
2466
+ * `prevPageUrl → 'previous'`. The current-page / per-page / last-page
2467
+ * paths default to empty strings — the strategy ignores `options` for
2468
+ * those slots and uses URL inspection instead.
2469
+ *
2470
+ * @see https://www.django-rest-framework.org/api-guide/pagination/#pagenumberpagination
2471
+ */
2472
+ declare class DrfResponseStrategy implements IResponseStrategy {
2473
+ /**
2474
+ * Parse a DRF pagination response into a PaginatedCollection
2475
+ *
2476
+ * @param response - The raw API response body
2477
+ * @param options - The response key name configuration
2478
+ * @returns A typed PaginatedCollection instance
2479
+ */
2480
+ paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
2481
+ /**
2482
+ * Derive the current page number from the `previous` URL
2483
+ *
2484
+ * - `null` → page 1
2485
+ * - URL without `?page=N` → page 2 (DRF omits `page=1` from URLs)
2486
+ * - URL with `?page=N` → N + 1
2487
+ *
2488
+ * @param prevPageUrl - The `previous` link from the DRF response, or null
2489
+ * @returns The current page number
2490
+ */
2491
+ private _deriveCurrentPage;
2492
+ /**
2493
+ * Derive `from` as the 1-indexed offset of the first item on this page
2494
+ *
2495
+ * @param currentPage - The current page number
2496
+ * @param perPage - The page size (may be undefined)
2497
+ * @returns The 1-indexed `from` index, or undefined when perPage is unknown
2498
+ */
2499
+ private _deriveFrom;
2500
+ /**
2501
+ * Derive the last page number as `ceil(total / perPage)`
2502
+ *
2503
+ * Both inputs must be defined; an empty result set (`total === 0`)
2504
+ * yields `lastPage = 0` which the caller treats as "no useful info"
2505
+ * and skips the sync to `NestService.lastPage`.
2506
+ *
2507
+ * @param total - The total item count
2508
+ * @param perPage - The page size
2509
+ * @returns The last page number, or undefined when either input is missing
2510
+ */
2511
+ private _deriveLastPage;
2512
+ /**
2513
+ * Derive `perPage` by parsing `?page_size=N` from any available URL
2514
+ *
2515
+ * Tries `next` first (page 1 always has a `next` URL with `page_size`
2516
+ * if any non-default size was requested), then falls back to
2517
+ * `previous`. Returns undefined when neither URL contains the param —
2518
+ * the consumer is then on a single-page result with the server's
2519
+ * default page size, which is not introspectable from the body alone.
2520
+ *
2521
+ * @param nextPageUrl - The `next` link from the response, or null
2522
+ * @param prevPageUrl - The `previous` link from the response, or null
2523
+ * @returns The page size, or undefined
2524
+ */
2525
+ private _derivePerPage;
2526
+ /**
2527
+ * Derive `to` as the 1-indexed offset of the last item on this page
2528
+ *
2529
+ * Clamped to `total` so the last page does not report past the end.
2530
+ *
2531
+ * @param currentPage - The current page number
2532
+ * @param perPage - The page size (may be undefined)
2533
+ * @param total - The total item count (may be undefined)
2534
+ * @returns The 1-indexed `to` index, or undefined when inputs insufficient
2535
+ */
2536
+ private _deriveTo;
2537
+ /**
2538
+ * Extract the `page` query parameter from a DRF pagination URL
2539
+ *
2540
+ * Returns the integer value of `?page=N`, or undefined when the URL is
2541
+ * malformed or has no `page` param (which, by DRF convention, means
2542
+ * page 1 — the caller infers the semantics).
2543
+ *
2544
+ * @param url - The URL to parse
2545
+ * @returns The integer page value, or undefined
2546
+ */
2547
+ private _extractPageParam;
2548
+ /**
2549
+ * Extract the `page_size` query parameter from a DRF pagination URL
2550
+ *
2551
+ * @param url - The URL to parse (or null)
2552
+ * @returns The integer page-size value, or undefined
2553
+ */
2554
+ private _extractPageSizeParam;
2555
+ /**
2556
+ * Extract a single query parameter from a URL via the WHATWG URL parser
2557
+ *
2558
+ * Returns undefined when the URL is unparseable (relative URL without a
2559
+ * base, or malformed) or when the parameter is absent.
2560
+ *
2561
+ * @param url - The URL to parse
2562
+ * @param name - The query-parameter name to look up
2563
+ * @returns The raw string value of the parameter, or undefined
2564
+ */
2565
+ private _extractQueryParam;
2566
+ }
2567
+
2568
+ /**
2569
+ * Request strategy for the FeathersJS driver
2570
+ *
2571
+ * Generates URIs in [Feathers' common database adapter query syntax](https://feathersjs.com/api/databases/querying):
2572
+ * - Filters: `field=value` (exact match); multi-value folds to `$in`
2573
+ * (`field[$in][0]=v1&field[$in][1]=v2`)
2574
+ * - Operator filters: `field[$op]=value` (translated from
2575
+ * `FilterOperatorEnum` — `BTW`→`$gte`+`$lte` pair, `NOT`→`$ne`/`$nin`)
2576
+ * - Sorts: `$sort[field]=1` (ASC) / `$sort[field]=-1` (DESC)
2577
+ * - Flat field selection: `$select[0]=col1&$select[1]=col2`
2578
+ * - Pagination (offset-based): `$limit=N&$skip=M` with
2579
+ * `skip = (page - 1) × limit`
2580
+ *
2581
+ * The dollar-prefixed query keys (`$limit`, `$skip`, `$sort`, `$select`)
2582
+ * are fixed by the Feathers adapter-commons parser and intentionally not
2583
+ * configurable through `QueryBuilderOptions`; they live as private
2584
+ * statics so they are visible in one place.
2585
+ *
2586
+ * Feathers' core adapter syntax has no relation includes, no per-model
2587
+ * field selection, and no global search — the corresponding fluent
2588
+ * methods throw the matching `Unsupported*Error`. `CONTAINS` / `ILIKE` /
2589
+ * `SW` (LIKE-style matching is adapter-specific, not core), `NULL` (no
2590
+ * null-check operator on the wire), and the PostgREST-native full-text
2591
+ * operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
2592
+ * `UnsupportedFilterOperatorError`.
2593
+ *
2594
+ * @see https://feathersjs.com/api/databases/querying
2595
+ */
2596
+ declare class FeathersRequestStrategy extends AbstractRequestStrategy {
2597
+ /**
2598
+ * Filters, operator filters, sorts, flat field selection (`select`) —
2599
+ * no per-model fields, no includes, no global search, no embedded
2600
+ * resources
2601
+ */
2602
+ readonly capabilities: IStrategyCapabilities;
2603
+ /**
2604
+ * Feathers-native names of the four hardcoded query keys
2605
+ *
2606
+ * The dollar prefix marks adapter-commons system params apart from
2607
+ * filter fields; these keys are fixed by the server and intentionally
2608
+ * not configurable through `QueryBuilderOptions`.
2609
+ */
2610
+ private static readonly _limitKey;
2611
+ private static readonly _selectKey;
2612
+ private static readonly _skipKey;
2613
+ private static readonly _sortKey;
2614
+ /**
2615
+ * Emit Feathers-format query-string segments in canonical order:
2616
+ * filters → operator filters → $sort → $select → $limit → $skip
2617
+ *
2618
+ * @param state - The current query builder state
2619
+ * @param _options - The query parameter key name configuration (unused;
2620
+ * Feathers' system keys are fixed by the server)
2621
+ * @returns Ordered query-string fragments
2622
+ */
2623
+ protected parts(state: IQueryBuilderState, _options: QueryBuilderOptions): string[];
2624
+ /**
2625
+ * Append simple filter parameters
2626
+ *
2627
+ * A single value emits the bare exact-match form (`field=value`);
2628
+ * multiple values fold to the `$in` list
2629
+ * (`field[$in][0]=v1&field[$in][1]=v2`).
2630
+ *
2631
+ * @param state - The current query builder state
2632
+ * @param out - The accumulator the caller joins into the URI
2633
+ */
2634
+ private _appendFilters;
2635
+ /**
2636
+ * Append explicit operator filters in the `field[$op]=value` syntax
2637
+ *
2638
+ * @param state - The current query builder state
2639
+ * @param out - The accumulator the caller joins into the URI
2640
+ */
2641
+ private _appendOperatorFilters;
2642
+ /**
2643
+ * Append the `$limit` / `$skip` pagination pair
2644
+ *
2645
+ * Feathers paginates by offset, so the 1-based page number from state
2646
+ * converts to `skip = (page - 1) × limit`.
2647
+ *
2648
+ * @param state - The current query builder state
2649
+ * @param out - The accumulator the caller joins into the URI
2650
+ */
2651
+ private _appendPagination;
2652
+ /**
2653
+ * Append the `$select[0]=col1&$select[1]=col2` array from the flat
2654
+ * select state
2655
+ *
2656
+ * @param state - The current query builder state
2657
+ * @param out - The accumulator the caller joins into the URI
2658
+ */
2659
+ private _appendSelect;
2660
+ /**
2661
+ * Append the `$sort[field]=1|-1` map
2662
+ *
2663
+ * @param state - The current query builder state
2664
+ * @param out - The accumulator the caller joins into the URI
2665
+ */
2666
+ private _appendSort;
2667
+ /**
2668
+ * Translate a `FilterOperatorEnum` operator filter into Feathers'
2669
+ * `$operator → value` payload shape (or a bare primitive for `EQ`)
2670
+ *
2671
+ * The mapping is library-canonical → Feathers-native:
2672
+ * - `EQ` → bare `field=value` (no operator wrapper)
2673
+ * - `GT`/`GTE`/`LT`/`LTE` → `$gt` / `$gte` / `$lt` / `$lte`
2674
+ * - `IN` → `$in` (array)
2675
+ * - `BTW` → `$gte` + `$lte` pair in one payload (arity-checked)
2676
+ * - `NOT` → `$ne` (single value) / `$nin` (multi-value)
2677
+ *
2678
+ * `CONTAINS`, `ILIKE`, and `SW` (LIKE-style matching is
2679
+ * adapter-specific in Feathers, not part of the common syntax),
2680
+ * `NULL` (no null-check operator), and PostgREST's full-text-search
2681
+ * operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
2682
+ * `UnsupportedFilterOperatorError`.
2683
+ *
2684
+ * @param filter - The operator filter to translate
2685
+ * @returns A `{ $operator: value }` payload ready to nest under
2686
+ * `field[...]`, or a bare primitive for `EQ`
2687
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
2688
+ * exactly two values
2689
+ * @throws {UnsupportedFilterOperatorError} If the operator has no
2690
+ * Feathers equivalent
2691
+ */
2692
+ private _formatOperatorPayload;
2693
+ }
2694
+
2695
+ /**
2696
+ * Response strategy for the FeathersJS driver
2697
+ *
2698
+ * Parses the paginated envelope emitted by Feathers database adapters:
2699
+ *
2700
+ * ```json
2701
+ * {
2702
+ * "total": 48,
2703
+ * "limit": 10,
2704
+ * "skip": 10,
2705
+ * "data": [...]
2706
+ * }
2707
+ * ```
2708
+ *
2709
+ * The envelope is offset-based — no page number, no navigation URLs —
2710
+ * so position derives arithmetically:
2711
+ *
2712
+ * - `currentPage` is `skip / limit + 1` (integer division; a zero or
2713
+ * missing `limit` falls back to page **1**).
2714
+ * - `perPage` comes straight from `limit`; `lastPage` is
2715
+ * `ceil(total / limit)`.
2716
+ * - `from`/`to` are 1-indexed offsets derived from `skip` and the item
2717
+ * count of the current page (`from = skip + 1`,
2718
+ * `to = skip + data.length`); both stay `undefined` on an empty page.
2719
+ *
2720
+ * The `data` / `total` / `limit` key names are configurable through
2721
+ * `FeathersResponseOptions`; the `skip` key is fixed by the Feathers
2722
+ * envelope and lives as a private static.
2723
+ *
2724
+ * @see https://feathersjs.com/api/databases/common#pagination
2725
+ */
2726
+ declare class FeathersResponseStrategy implements IResponseStrategy {
2727
+ /**
2728
+ * Feathers-native name of the offset key
2729
+ *
2730
+ * Fixed by the adapter envelope; read for page derivation only.
2731
+ */
2732
+ private static readonly _skipKey;
2733
+ /**
2734
+ * Parse a Feathers pagination response into a PaginatedCollection
2735
+ *
2736
+ * @param response - The raw API response body
2737
+ * @param options - The response key name configuration
2738
+ * @returns A typed PaginatedCollection instance
2739
+ */
2740
+ paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
2741
+ }
2742
+
2743
+ /**
2744
+ * Request strategy for the JSON:API driver
2745
+ *
2746
+ * Generates URIs in the JSON:API format:
2747
+ * - Fields: `fields[articles]=title,body&fields[people]=name`
2748
+ * - Filters: `filter[status]=active`
2749
+ * - Includes: `include=author,comments.author`
2750
+ * - Pagination: `page[number]=1&page[size]=15`
2751
+ * - Sort: `sort=-created_at,name` (- prefix = DESC)
2752
+ *
2753
+ * @see https://jsonapi.org/format/
2754
+ */
2755
+ declare class JsonApiRequestStrategy extends AbstractRequestStrategy {
2756
+ /**
2757
+ * Filters, sorts, includes, per-model fields — same shape as Spatie
2758
+ * but with bracket-style pagination
2759
+ */
2760
+ readonly capabilities: IStrategyCapabilities;
2761
+ /**
2762
+ * Emit JSON:API-format query-string segments in canonical order:
2763
+ * include → fields → filters → pagination → sort
2764
+ *
2765
+ * @param state - The current query builder state
2766
+ * @param options - The query parameter key name configuration
2767
+ * @returns Ordered query-string fragments
2768
+ */
2769
+ protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
2770
+ /**
2771
+ * Append per-type field selection in bracket notation
2772
+ *
2773
+ * @param state - The current query builder state
2774
+ * @param options - The query parameter key name configuration
2775
+ * @param out - The accumulator the caller joins into the URI
2776
+ * @throws Error if the resource is missing from the fields object
2777
+ * @throws UnselectableModelError if a field type is not the resource or in includes
2778
+ */
2779
+ private _appendFields;
2780
+ /**
2781
+ * Append filter parameters in bracket notation: `filter[key]=value`
2782
+ *
2783
+ * @param state - The current query builder state
2784
+ * @param options - The query parameter key name configuration
2785
+ * @param out - The accumulator the caller joins into the URI
2786
+ */
2787
+ private _appendFilters;
2788
+ /**
2789
+ * Append include parameter as `include=author,comments.author`
2790
+ *
2791
+ * @param state - The current query builder state
2792
+ * @param options - The query parameter key name configuration
2793
+ * @param out - The accumulator the caller joins into the URI
2794
+ */
2795
+ private _appendIncludes;
2796
+ /**
2797
+ * Append JSON:API bracket pagination as `page[number]=1&page[size]=15`
2798
+ *
2799
+ * `qs.stringify` already returns the two segments joined with `&`, so we
2800
+ * push the whole string as one accumulator entry — `_join` will glue
2801
+ * it onto the rest with the same separator.
2802
+ *
2803
+ * @param state - The current query builder state
2804
+ * @param options - The query parameter key name configuration
2805
+ * @param out - The accumulator the caller joins into the URI
2806
+ */
2807
+ private _appendPagination;
2808
+ /**
2809
+ * Append sort parameter as `sort=-field1,field2` (`-` prefix = DESC)
2810
+ *
2811
+ * @param state - The current query builder state
2812
+ * @param options - The query parameter key name configuration
2813
+ * @param out - The accumulator the caller joins into the URI
2814
+ */
2815
+ private _appendSort;
2816
+ }
2817
+
2818
+ /**
2819
+ * Response strategy for the JSON:API driver
2820
+ *
2821
+ * Parses JSON:API pagination responses:
2822
+ * ```json
2823
+ * {
2824
+ * "data": [...],
2825
+ * "meta": {
2826
+ * "current-page": 1,
2827
+ * "per-page": 10,
2828
+ * "total": 100,
2829
+ * "page-count": 10,
2830
+ * "from": 1,
2831
+ * "to": 10
2832
+ * },
2833
+ * "links": {
2834
+ * "first": "url",
2835
+ * "prev": "url",
2836
+ * "next": "url",
2837
+ * "last": "url"
2838
+ * }
2839
+ * }
2840
+ * ```
2841
+ *
2842
+ * Default key paths are configured in `JsonApiResponseOptions`. The
2843
+ * traversal algorithm (dot-notation resolution + computed `from`/`to`) is
2844
+ * inherited from `AbstractDotPathResponseStrategy`; this class exists so
2845
+ * `DriverEnum.JSON_API` resolves to a distinct identity at the DI layer
2846
+ * even though the parsing logic is shared with NestJS.
2847
+ *
2848
+ * @see https://jsonapi.org/format/
2849
+ */
2850
+ declare class JsonApiResponseStrategy extends AbstractDotPathResponseStrategy {
2851
+ }
2852
+
2853
+ /**
2854
+ * Request strategy for the json-server driver
2855
+ *
2856
+ * Generates URIs in [json-server's](https://github.com/typicode/json-server)
2857
+ * query format — the de-facto standard mock REST API for prototyping:
2858
+ * - Filters: `field=value` (exact, no operator); multi-value folds to the
2859
+ * `in` list (`field:in=v1,v2`)
2860
+ * - Operator filters: colon syntax `field:op=value` — see the mapping on
2861
+ * `_formatOperatorSegments`
2862
+ * - Sorts: `_sort=-views,title` (CSV, `-` prefix = DESC)
2863
+ * - Search: `q=term` (full-text search)
2864
+ * - Pagination: `_page=N&_per_page=M`
2865
+ *
2866
+ * The underscore-prefixed system params (`_page`, `_per_page`, `_sort`)
2867
+ * and the `q` search key are fixed by the server and intentionally not
2868
+ * configurable through `QueryBuilderOptions`; they live as private
2869
+ * statics so they are visible in one place.
2870
+ *
2871
+ * json-server has no per-model field selection, no relation includes,
2872
+ * no column projection — the corresponding fluent methods throw the
2873
+ * matching `Unsupported*Error`. `ILIKE` (no case-insensitive variant),
2874
+ * `NULL` (no null check), and the PostgREST-native full-text operators
2875
+ * (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
2876
+ * `UnsupportedFilterOperatorError`.
2877
+ *
2878
+ * @see https://github.com/typicode/json-server
2879
+ */
2880
+ declare class JsonServerRequestStrategy extends AbstractRequestStrategy {
2881
+ /**
2882
+ * Filters, operator filters, sorts, global search — no per-model
2883
+ * fields, no includes, no flat select, no embedded resources
2884
+ */
2885
+ readonly capabilities: IStrategyCapabilities;
2886
+ /**
2887
+ * json-server-native names of the four hardcoded query keys
2888
+ *
2889
+ * The underscore prefix marks system params apart from filter fields;
2890
+ * these keys are fixed by the server and intentionally not
2891
+ * configurable through `QueryBuilderOptions`.
2892
+ */
2893
+ private static readonly _pageKey;
2894
+ private static readonly _perPageKey;
2895
+ private static readonly _qKey;
2896
+ private static readonly _sortKey;
2897
+ /**
2898
+ * Emit json-server-format query-string segments in canonical order:
2899
+ * filters → operator filters → _sort → q → _page → _per_page
2900
+ *
2901
+ * @param state - The current query builder state
2902
+ * @param _options - The query parameter key name configuration (unused;
2903
+ * json-server's system keys are fixed by the server)
2904
+ * @returns Ordered query-string fragments
2905
+ */
2906
+ protected parts(state: IQueryBuilderState, _options: QueryBuilderOptions): string[];
2907
+ /**
2908
+ * Append simple filter parameters
2909
+ *
2910
+ * A single value emits the bare exact-match form (`field=value`);
2911
+ * multiple values fold to the `in` list (`field:in=v1,v2`).
2912
+ *
2913
+ * @param state - The current query builder state
2914
+ * @param out - The accumulator the caller joins into the URI
2915
+ */
2916
+ private _appendFilters;
2917
+ /**
2918
+ * Append explicit operator filters in the colon syntax
2919
+ *
2920
+ * @param state - The current query builder state
2921
+ * @param out - The accumulator the caller joins into the URI
2922
+ */
2923
+ private _appendOperatorFilters;
2924
+ /**
2925
+ * Append the _page parameter
2926
+ *
2927
+ * @param state - The current query builder state
2928
+ * @param out - The accumulator the caller joins into the URI
2929
+ */
2930
+ private _appendPage;
2931
+ /**
2932
+ * Append the _per_page parameter
2933
+ *
2934
+ * @param state - The current query builder state
2935
+ * @param out - The accumulator the caller joins into the URI
2936
+ */
2937
+ private _appendPerPage;
2938
+ /**
2939
+ * Append the `q=` full-text search parameter
2940
+ *
2941
+ * @param state - The current query builder state
2942
+ * @param out - The accumulator the caller joins into the URI
2943
+ */
2944
+ private _appendSearch;
2945
+ /**
2946
+ * Append the `_sort=-views,title` CSV parameter
2947
+ *
2948
+ * @param state - The current query builder state
2949
+ * @param out - The accumulator the caller joins into the URI
2950
+ */
2951
+ private _appendSort;
2952
+ /**
2953
+ * Translate a `FilterOperatorEnum` operator filter into one or more
2954
+ * json-server `field:op=value` segments
2955
+ *
2956
+ * The mapping is library-canonical → json-server-native:
2957
+ * - `EQ` → `:eq`; `GT`/`GTE`/`LT`/`LTE` → `:gt` / `:gte` / `:lt` / `:lte`
2958
+ * - `CONTAINS` → `:contains`
2959
+ * - `SW` → `:startsWith`
2960
+ * - `IN` → `:in` (CSV)
2961
+ * - `BTW` → **two** AND-ed segments (`field:gte=min` and
2962
+ * `field:lte=max`, arity-checked)
2963
+ * - `NOT` → `:ne` — one segment per value, AND-ed
2964
+ *
2965
+ * `ILIKE` (json-server has no case-insensitive variant), `NULL` (no
2966
+ * null-check operator), and PostgREST's full-text-search operators
2967
+ * (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
2968
+ * `UnsupportedFilterOperatorError`.
2969
+ *
2970
+ * @param filter - The operator filter to translate
2971
+ * @returns One or more `field:op=value` query-string segments
2972
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
2973
+ * exactly two values
2974
+ * @throws {UnsupportedFilterOperatorError} If the operator has no
2975
+ * json-server equivalent
2976
+ */
2977
+ private _formatOperatorSegments;
2978
+ }
2979
+
2980
+ /**
2981
+ * Response strategy for the json-server driver
2982
+ *
2983
+ * Parses json-server v1 paginated responses:
2984
+ *
2985
+ * ```json
2986
+ * {
2987
+ * "first": 1,
2988
+ * "prev": null,
2989
+ * "next": 2,
2990
+ * "last": 5,
2991
+ * "pages": 5,
2992
+ * "items": 48,
2993
+ * "data": [...]
2994
+ * }
2995
+ * ```
2996
+ *
2997
+ * Uniquely among the drivers, `first` / `prev` / `next` / `last` are
2998
+ * **page numbers**, not URLs — so the navigation-URL slots on
2999
+ * `PaginatedCollection` stay `undefined` and the strategy instead uses
3000
+ * the numbers to derive position:
3001
+ *
3002
+ * - `currentPage` is `prev + 1` (`prev === null` → page **1**).
3003
+ * - `perPage` is the item count of the current page whenever `next` is
3004
+ * set (a page with a successor is necessarily full); on the last page
3005
+ * of a multi-page set it is not introspectable and stays `undefined`.
3006
+ * - `lastPage` comes straight from `pages`; `from`/`to` derive from
3007
+ * `currentPage` × `perPage` on full pages, or count back from the
3008
+ * total on the last page (`from = total - items + 1`, `to = total`).
3009
+ *
3010
+ * The `data` / `items` / `pages` key paths are configurable through
3011
+ * `JsonServerResponseOptions`; the `prev` / `next` keys are fixed by the
3012
+ * json-server envelope and live as private statics.
3013
+ *
3014
+ * @see https://github.com/typicode/json-server
3015
+ */
3016
+ declare class JsonServerResponseStrategy implements IResponseStrategy {
3017
+ /**
3018
+ * json-server-native names of the page-number navigation keys
3019
+ *
3020
+ * Fixed by the v1 envelope; read for derivation only and never
3021
+ * surfaced as URLs.
3022
+ */
3023
+ private static readonly _nextKey;
3024
+ private static readonly _prevKey;
3025
+ /**
3026
+ * Parse a json-server pagination response into a PaginatedCollection
3027
+ *
3028
+ * @param response - The raw API response body
3029
+ * @param options - The response key name configuration
3030
+ * @returns A typed PaginatedCollection instance
3031
+ */
3032
+ paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
3033
+ /**
3034
+ * Derive `from` as the 1-indexed offset of the first item on this page
3035
+ *
3036
+ * Computed from `currentPage` × `perPage` on full pages; on the last
3037
+ * page (no `next`) it counts back from the total
3038
+ * (`total - items + 1`), which also covers single-page responses.
3039
+ *
3040
+ * @param data - The items on the current page
3041
+ * @param currentPage - The current page number
3042
+ * @param next - The next page number, or null
3043
+ * @param perPage - The page size (may be undefined)
3044
+ * @param total - The total item count (may be undefined)
3045
+ * @returns The 1-indexed `from` index, or undefined when inputs insufficient
3046
+ */
3047
+ private _deriveFrom;
3048
+ /**
3049
+ * Derive `perPage` from the current page's item count
3050
+ *
3051
+ * A page with a `next` successor is necessarily full, so its item
3052
+ * count equals the page size. Without a successor the page may be
3053
+ * partial and the size is not introspectable from the body alone.
3054
+ *
3055
+ * @param next - The next page number, or null
3056
+ * @param data - The items on the current page
3057
+ * @returns The page size, or undefined
3058
+ */
3059
+ private _derivePerPage;
3060
+ /**
3061
+ * Derive `to` as the 1-indexed offset of the last item on this page
3062
+ *
3063
+ * Clamped to `total` so the last page does not report past the end;
3064
+ * on the last page (no `next`) it is simply the total.
3065
+ *
3066
+ * @param data - The items on the current page
3067
+ * @param currentPage - The current page number
3068
+ * @param next - The next page number, or null
3069
+ * @param perPage - The page size (may be undefined)
3070
+ * @param total - The total item count (may be undefined)
3071
+ * @returns The 1-indexed `to` index, or undefined when inputs insufficient
3072
+ */
3073
+ private _deriveTo;
3074
+ }
3075
+
3076
+ /**
3077
+ * Request strategy for the Laravel (pagination-only) driver
3078
+ *
3079
+ * Generates simple pagination URIs:
3080
+ * - `/{resource}?limit=N&page=N`
3081
+ *
3082
+ * Filters, sorts, fields, includes, search, and select in state are ignored.
3083
+ */
3084
+ declare class LaravelRequestStrategy extends AbstractRequestStrategy {
3085
+ /**
3086
+ * Pagination-only driver — no filtering, sorting, or column selection
3087
+ */
3088
+ readonly capabilities: IStrategyCapabilities;
3089
+ /**
3090
+ * Emit only the pagination params; filters/sorts/etc. are ignored
3091
+ *
3092
+ * @param state - The current query builder state
3093
+ * @param options - The query parameter key name configuration
3094
+ * @returns The two pagination query-string fragments
3095
+ */
3096
+ protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
3097
+ }
3098
+
3099
+ /**
3100
+ * Base class for response strategies whose pagination metadata is a flat
3101
+ * key-value envelope on the response body
3102
+ *
3103
+ * Laravel's stock pagination and Spatie's `QueryBuilder` both emit the
3104
+ * same flat shape — `{ data, current_page, total, per_page, from, to,
3105
+ * next_page_url, prev_page_url, first_page_url, last_page, last_page_url
3106
+ * }` — and both response strategies were duplicating the byte-identical
3107
+ * `new PaginatedCollection(response[options.X], ...)` body before this
3108
+ * base existed. Concrete classes now extend and provide only the
3109
+ * docstring describing their driver's specific shape (see
3110
+ * `LaravelResponseStrategy`, `SpatieResponseStrategy`).
3111
+ *
3112
+ * Drivers whose pagination metadata is a nested envelope (JSON:API,
1931
3113
  * NestJS, Strapi) extend `AbstractDotPathResponseStrategy` instead.
1932
3114
  * Drivers whose metadata comes from HTTP headers (PostgREST) or is
1933
3115
  * derived from response URLs (DRF) implement `IResponseStrategy`
1934
3116
  * directly.
1935
3117
  */
1936
- declare abstract class AbstractFlatResponseStrategy implements IResponseStrategy {
3118
+ declare abstract class AbstractFlatResponseStrategy implements IResponseStrategy {
3119
+ /**
3120
+ * Parse a flat-envelope pagination response into a PaginatedCollection
3121
+ *
3122
+ * @param response - The raw API response object
3123
+ * @param options - The response key name configuration
3124
+ * @returns A typed PaginatedCollection instance
3125
+ */
3126
+ paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
3127
+ }
3128
+
3129
+ /**
3130
+ * Response strategy for the Laravel (pagination-only) driver
3131
+ *
3132
+ * Parses flat Laravel pagination responses:
3133
+ * ```json
3134
+ * {
3135
+ * "data": [...],
3136
+ * "current_page": 1,
3137
+ * "total": 100,
3138
+ * "per_page": 15,
3139
+ * "from": 1,
3140
+ * "to": 15,
3141
+ * "next_page_url": "...",
3142
+ * "prev_page_url": "...",
3143
+ * "first_page_url": "...",
3144
+ * "last_page": 7,
3145
+ * "last_page_url": "..."
3146
+ * }
3147
+ * ```
3148
+ *
3149
+ * The traversal algorithm (flat `response[options.X]` lookups) is
3150
+ * inherited from `AbstractFlatResponseStrategy`; this class exists so
3151
+ * `DriverEnum.LARAVEL` resolves to a distinct identity at the DI layer
3152
+ * even though the parsing logic is shared with Spatie.
3153
+ */
3154
+ declare class LaravelResponseStrategy extends AbstractFlatResponseStrategy {
3155
+ }
3156
+
3157
+ /**
3158
+ * Request strategy for the NestJS (nestjs-paginate) driver
3159
+ *
3160
+ * Generates URIs in the NestJS paginate format:
3161
+ * - Simple filters: `filter.field=value`
3162
+ * - Operator filters: `filter.field=$operator:value`
3163
+ * - Sorts: `sortBy=field1:DESC,field2:ASC`
3164
+ * - Select: `select=col1,col2`
3165
+ * - Search: `search=term`
3166
+ * - Pagination: `limit=N&page=N`
3167
+ *
3168
+ * @see https://github.com/ppetzold/nestjs-paginate
3169
+ */
3170
+ declare class NestjsRequestStrategy extends AbstractRequestStrategy {
1937
3171
  /**
1938
- * Parse a flat-envelope pagination response into a PaginatedCollection
3172
+ * Filters, operator filters, sorts, flat select, global search no
3173
+ * per-model fields, no includes
3174
+ */
3175
+ readonly capabilities: IStrategyCapabilities;
3176
+ /**
3177
+ * Validate that the given limit is accepted by nestjs-paginate
3178
+ *
3179
+ * Accepts any integer `>= 1` as a page size, plus `-1` which nestjs-paginate
3180
+ * interprets as "fetch all items" (server must opt-in via `maxLimit: -1`).
3181
+ *
3182
+ * @param limit - The limit value to validate
3183
+ * @throws {InvalidLimitError} If the value is not an integer, or is 0, or is a negative number other than -1
3184
+ */
3185
+ validateLimit(limit: number): void;
3186
+ /**
3187
+ * Emit NestJS-format query-string segments in canonical order:
3188
+ * filters → operator filters → sortBy → select → search → limit → page
3189
+ *
3190
+ * @param state - The current query builder state
3191
+ * @param options - The query parameter key name configuration
3192
+ * @returns Ordered query-string fragments
3193
+ */
3194
+ protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
3195
+ /**
3196
+ * Append simple filter parameters as `filter.field=value1,value2`
3197
+ *
3198
+ * @param state - The current query builder state
3199
+ * @param options - The query parameter key name configuration
3200
+ * @param out - The accumulator the caller joins into the URI
3201
+ */
3202
+ private _appendFilters;
3203
+ /**
3204
+ * Append the limit parameter
3205
+ *
3206
+ * @param state - The current query builder state
3207
+ * @param options - The query parameter key name configuration
3208
+ * @param out - The accumulator the caller joins into the URI
3209
+ */
3210
+ private _appendLimit;
3211
+ /**
3212
+ * Append operator-filter parameters as `filter.field=$op:value`
3213
+ *
3214
+ * Groups by field; multi-value operators ($in, $btw) join values with commas.
3215
+ *
3216
+ * @param state - The current query builder state
3217
+ * @param options - The query parameter key name configuration
3218
+ * @param out - The accumulator the caller joins into the URI
3219
+ */
3220
+ private _appendOperatorFilters;
3221
+ /**
3222
+ * Append the page parameter
3223
+ *
3224
+ * @param state - The current query builder state
3225
+ * @param options - The query parameter key name configuration
3226
+ * @param out - The accumulator the caller joins into the URI
3227
+ */
3228
+ private _appendPage;
3229
+ /**
3230
+ * Append the search parameter as `search=term`
3231
+ *
3232
+ * @param state - The current query builder state
3233
+ * @param options - The query parameter key name configuration
3234
+ * @param out - The accumulator the caller joins into the URI
3235
+ */
3236
+ private _appendSearch;
3237
+ /**
3238
+ * Append the select parameter as `select=col1,col2`
3239
+ *
3240
+ * @param state - The current query builder state
3241
+ * @param options - The query parameter key name configuration
3242
+ * @param out - The accumulator the caller joins into the URI
3243
+ */
3244
+ private _appendSelect;
3245
+ /**
3246
+ * Append sort parameter as `sortBy=field1:DESC,field2:ASC`
3247
+ *
3248
+ * @param state - The current query builder state
3249
+ * @param options - The query parameter key name configuration
3250
+ * @param out - The accumulator the caller joins into the URI
3251
+ */
3252
+ private _appendSort;
3253
+ }
3254
+
3255
+ /**
3256
+ * Response strategy for the NestJS (nestjs-paginate) driver
3257
+ *
3258
+ * Parses nested NestJS pagination responses:
3259
+ * ```json
3260
+ * {
3261
+ * "data": [...],
3262
+ * "meta": {
3263
+ * "currentPage": 1,
3264
+ * "totalItems": 100,
3265
+ * "itemsPerPage": 10,
3266
+ * "totalPages": 10
3267
+ * },
3268
+ * "links": {
3269
+ * "first": "url",
3270
+ * "previous": "url",
3271
+ * "next": "url",
3272
+ * "last": "url",
3273
+ * "current": "url"
3274
+ * }
3275
+ * }
3276
+ * ```
3277
+ *
3278
+ * Default key paths are configured in `NestjsResponseOptions`. The
3279
+ * traversal algorithm (dot-notation resolution + computed `from`/`to`) is
3280
+ * inherited from `AbstractDotPathResponseStrategy`; this class exists so
3281
+ * `DriverEnum.NESTJS` resolves to a distinct identity at the DI layer
3282
+ * even though the parsing logic is shared with JSON:API.
3283
+ *
3284
+ * @see https://github.com/ppetzold/nestjs-paginate
3285
+ */
3286
+ declare class NestjsResponseStrategy extends AbstractDotPathResponseStrategy {
3287
+ }
3288
+
3289
+ /**
3290
+ * Request strategy for the @nestjsx/crud driver
3291
+ *
3292
+ * Generates URIs in [@nestjsx/crud's pipe-delimited query format](https://github.com/nestjsx/crud/wiki/Requests):
3293
+ * - Filters: `filter=field||$eq||value` (repeatable; multi-value
3294
+ * collapses to `filter=field||$in||v1,v2`)
3295
+ * - Operator filters: `filter=field||$op||value` (translated from
3296
+ * `FilterOperatorEnum` — `CONTAINS`→`$cont`, `ILIKE`→`$contL`,
3297
+ * `SW`→`$starts`, `BTW`→`$between`, `NOT`→`$ne`/`$notin`,
3298
+ * `NULL`→`$isnull`/`$notnull` with no value segment)
3299
+ * - Joins: `join=relation` (repeatable, from `addIncludes`)
3300
+ * - Field selection (flat): `fields=col1,col2` (from `addSelect`)
3301
+ * - Sorts: `sort=field,ASC` (repeatable, uppercase direction)
3302
+ * - Pagination (page-based): `page=N&limit=N`
3303
+ *
3304
+ * The JSON-shaped `s={...}` search parameter is intentionally out of
3305
+ * scope: it is not a plain search term, so `setSearch` throws
3306
+ * `UnsupportedSearchError` on this driver. PostgREST-native full-text
3307
+ * search operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
3308
+ * `UnsupportedFilterOperatorError`.
3309
+ *
3310
+ * @see https://github.com/nestjsx/crud/wiki/Requests
3311
+ */
3312
+ declare class NestjsxCrudRequestStrategy extends AbstractRequestStrategy {
3313
+ /**
3314
+ * Filters, operator filters, joins (`includes`), flat field selection
3315
+ * (`select`), sorts — no per-model fields, no global search (the `s`
3316
+ * parameter is JSON-shaped, not a plain term)
3317
+ */
3318
+ readonly capabilities: IStrategyCapabilities;
3319
+ /**
3320
+ * @nestjsx/crud-native names of the four hardcoded query keys
3321
+ *
3322
+ * The wire format is fixed (the server's `CrudRequestInterceptor`
3323
+ * reads `fields`, `filter`, `join`, and `sort`); these keys are
3324
+ * intentionally not configurable through `QueryBuilderOptions` and
3325
+ * live as private statics so they are visible in one place.
3326
+ */
3327
+ private static readonly _fieldsKey;
3328
+ private static readonly _filterKey;
3329
+ private static readonly _joinKey;
3330
+ private static readonly _sortKey;
3331
+ /**
3332
+ * Pipe delimiter separating field, operator, and value segments
3333
+ */
3334
+ private static readonly _separator;
3335
+ /**
3336
+ * Emit @nestjsx/crud-format query-string segments in canonical order:
3337
+ * fields → filters → operator filters → join → sort → limit → page
3338
+ *
3339
+ * @param state - The current query builder state
3340
+ * @param options - The query parameter key name configuration (used
3341
+ * for `page` / `limit`, whose defaults match the wire format; the
3342
+ * `fields` / `filter` / `join` / `sort` keys are fixed by the server)
3343
+ * @returns Ordered query-string fragments
3344
+ */
3345
+ protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
3346
+ /**
3347
+ * Append `fields=col1,col2` from the flat select array
3348
+ *
3349
+ * @param state - The current query builder state
3350
+ * @param out - The accumulator the caller joins into the URI
3351
+ */
3352
+ private _appendFields;
3353
+ /**
3354
+ * Append simple filters as repeatable `filter=field||$eq||value` params
3355
+ *
3356
+ * Single-value filters fold to `$eq`; multi-value filters fold to
3357
+ * `$in` with comma-joined values.
3358
+ *
3359
+ * @param state - The current query builder state
3360
+ * @param out - The accumulator the caller joins into the URI
3361
+ */
3362
+ private _appendFilters;
3363
+ /**
3364
+ * Append `join=relation` params from the includes array
3365
+ *
3366
+ * Per-relation field projection (`join=relation||f1,f2`) is not
3367
+ * expressible through the current state shape and is out of scope.
3368
+ *
3369
+ * @param state - The current query builder state
3370
+ * @param out - The accumulator the caller joins into the URI
3371
+ */
3372
+ private _appendJoin;
3373
+ /**
3374
+ * Append the limit parameter
3375
+ *
3376
+ * @param state - The current query builder state
3377
+ * @param options - The query parameter key name configuration
3378
+ * @param out - The accumulator the caller joins into the URI
3379
+ */
3380
+ private _appendLimit;
3381
+ /**
3382
+ * Append operator filters as repeatable `filter=field||$op||value` params
3383
+ *
3384
+ * @param state - The current query builder state
3385
+ * @param out - The accumulator the caller joins into the URI
3386
+ */
3387
+ private _appendOperatorFilters;
3388
+ /**
3389
+ * Append the page parameter
3390
+ *
3391
+ * @param state - The current query builder state
3392
+ * @param options - The query parameter key name configuration
3393
+ * @param out - The accumulator the caller joins into the URI
3394
+ */
3395
+ private _appendPage;
3396
+ /**
3397
+ * Append `sort=field,ASC` params, one per sort rule
3398
+ *
3399
+ * @param state - The current query builder state
3400
+ * @param out - The accumulator the caller joins into the URI
3401
+ */
3402
+ private _appendSort;
3403
+ /**
3404
+ * Translate a `FilterOperatorEnum` operator filter into @nestjsx/crud's
3405
+ * `$operator||value` condition segment
3406
+ *
3407
+ * The mapping is library-canonical → @nestjsx/crud-native:
3408
+ * - `EQ`/`GT`/`GTE`/`LT`/`LTE`/`IN` → identity (same operator name)
3409
+ * - `CONTAINS` → `$cont`; `ILIKE` → `$contL` (case-insensitive contains)
3410
+ * - `SW` → `$starts`
3411
+ * - `BTW` → `$between` with `min,max` (arity-checked)
3412
+ * - `NOT` → `$ne` (single value) / `$notin` (multi-value)
3413
+ * - `NULL` → `$isnull` (when value is `true`) / `$notnull` (when value
3414
+ * is `false`); both emit **no value segment**; arity- and type-checked
3415
+ *
3416
+ * PostgREST's full-text-search operators (`FTS`, `PHFTS`, `PLFTS`,
3417
+ * `WFTS`) have no @nestjsx/crud equivalent and throw
3418
+ * `UnsupportedFilterOperatorError`.
1939
3419
  *
1940
- * @param response - The raw API response object
1941
- * @param options - The response key name configuration
1942
- * @returns A typed PaginatedCollection instance
3420
+ * @param filter - The operator filter to translate
3421
+ * @returns The `$operator||value` (or bare `$operator`) condition segment
3422
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
3423
+ * exactly two values, or `NULL` does not receive exactly one boolean
3424
+ * @throws {UnsupportedFilterOperatorError} If the operator is a
3425
+ * PostgREST-only FTS variant
1943
3426
  */
1944
- paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
3427
+ private _formatOperatorCondition;
1945
3428
  }
1946
3429
 
1947
3430
  /**
1948
- * Response strategy for the Laravel (pagination-only) driver
3431
+ * Response strategy for the @nestjsx/crud driver
1949
3432
  *
1950
- * Parses flat Laravel pagination responses:
3433
+ * Parses @nestjsx/crud's `getMany` envelope:
1951
3434
  * ```json
1952
3435
  * {
1953
- * "data": [...],
1954
- * "current_page": 1,
1955
- * "total": 100,
1956
- * "per_page": 15,
1957
- * "from": 1,
1958
- * "to": 15,
1959
- * "next_page_url": "...",
1960
- * "prev_page_url": "...",
1961
- * "first_page_url": "...",
1962
- * "last_page": 7,
1963
- * "last_page_url": "..."
3436
+ * "data": [{ "id": 1, "name": "John" }],
3437
+ * "count": 10,
3438
+ * "total": 48,
3439
+ * "page": 2,
3440
+ * "pageCount": 5
1964
3441
  * }
1965
3442
  * ```
1966
3443
  *
1967
- * The traversal algorithm (flat `response[options.X]` lookups) is
1968
- * inherited from `AbstractFlatResponseStrategy`; this class exists so
1969
- * `DriverEnum.LARAVEL` resolves to a distinct identity at the DI layer
1970
- * even though the parsing logic is shared with Spatie.
3444
+ * Default key paths are configured in `NestjsxCrudResponseOptions`. The
3445
+ * envelope carries no `from`/`to` indices and no navigation links, so
3446
+ * `from`/`to` are computed from `page` × `count` by the inherited
3447
+ * traversal algorithm and the URL slots resolve to `undefined` unless
3448
+ * the consumer overrides their paths via `IPaginationConfig`.
3449
+ *
3450
+ * Note that `count` is the number of entities **on the current page**,
3451
+ * not the requested page size — on the last page of a result set the
3452
+ * derived `from`/`to` can underestimate. Consumers needing exact
3453
+ * indices should compute them from the requested limit instead.
3454
+ *
3455
+ * The dot-notation traversal is inherited from
3456
+ * `AbstractDotPathResponseStrategy`; this class exists so
3457
+ * `DriverEnum.NESTJSX_CRUD` resolves to a distinct identity at the DI
3458
+ * layer even though the parsing logic is shared with JSON:API, NestJS,
3459
+ * and Strapi.
3460
+ *
3461
+ * @see https://github.com/nestjsx/crud/wiki/Requests
1971
3462
  */
1972
- declare class LaravelResponseStrategy extends AbstractFlatResponseStrategy {
3463
+ declare class NestjsxCrudResponseStrategy extends AbstractDotPathResponseStrategy {
1973
3464
  }
1974
3465
 
1975
3466
  /**
1976
- * Request strategy for the NestJS (nestjs-paginate) driver
1977
- *
1978
- * Generates URIs in the NestJS paginate format:
1979
- * - Simple filters: `filter.field=value`
1980
- * - Operator filters: `filter.field=$operator:value`
1981
- * - Sorts: `sortBy=field1:DESC,field2:ASC`
1982
- * - Select: `select=col1,col2`
1983
- * - Search: `search=term`
1984
- * - Pagination: `limit=N&page=N`
3467
+ * Request strategy for the OData v4 driver
1985
3468
  *
1986
- * @see https://github.com/ppetzold/nestjs-paginate
3469
+ * Generates URIs using [OData's system query options](https://www.odata.org/getting-started/basic-tutorial/#queryData):
3470
+ * - Filters: a single `$filter=` parameter holding ` and `-joined terms in
3471
+ * OData's expression language (`Price gt 20 and Category eq 'Electronics'`)
3472
+ * - Operator filters: translated from `FilterOperatorEnum` — see the
3473
+ * mapping on `_formatOperatorTerms`
3474
+ * - Sorts: `$orderby=field asc,other desc` (CSV with explicit direction)
3475
+ * - Select: `$select=col1,col2`
3476
+ * - Expand: `$expand=rel,other($select=col1,col2)` — plain relations come
3477
+ * from `addIncludes`, column-projected ones from `addEmbedded`
3478
+ * - Search: `$search=term` (OData v4 free-text search)
3479
+ * - Pagination: `$top=N&$skip=M` (skip derived from state.page) plus a
3480
+ * constant `$count=true` so responses carry the `@odata.count` total
3481
+ * the response strategy needs
3482
+ *
3483
+ * The `$`-prefixed parameter names are fixed by the OData specification
3484
+ * and intentionally not configurable through `QueryBuilderOptions`; they
3485
+ * live as private statics so they are visible in one place. String
3486
+ * literals are single-quoted with embedded quotes doubled (`'O''Brien'`)
3487
+ * per the OData ABNF; numbers and booleans are emitted bare.
3488
+ *
3489
+ * PostgREST-native full-text search operators (`FTS`, `PHFTS`, `PLFTS`,
3490
+ * `WFTS`) throw `UnsupportedFilterOperatorError` — use `$search` or the
3491
+ * `CONTAINS` / `ILIKE` operator filters instead.
3492
+ *
3493
+ * @see https://www.odata.org/
3494
+ * @see https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html
1987
3495
  */
1988
- declare class NestjsRequestStrategy extends AbstractRequestStrategy {
3496
+ declare class OdataRequestStrategy extends AbstractRequestStrategy {
1989
3497
  /**
1990
- * Filters, operator filters, sorts, flat select, global search — no
1991
- * per-model fields, no includes
3498
+ * Filters, operator filters, sorts, flat select, includes and embedded
3499
+ * (both folding into `$expand`), global search — no per-model fields
3500
+ * (OData has no JSON:API-style `fields[type]` projection)
1992
3501
  */
1993
3502
  readonly capabilities: IStrategyCapabilities;
1994
3503
  /**
1995
- * Validate that the given limit is accepted by nestjs-paginate
3504
+ * OData-native names of the system query options
1996
3505
  *
1997
- * Accepts any integer `>= 1` as a page size, plus `-1` which nestjs-paginate
1998
- * interprets as "fetch all items" (server must opt-in via `maxLimit: -1`).
1999
- *
2000
- * @param limit - The limit value to validate
2001
- * @throws {InvalidLimitError} If the value is not an integer, or is 0, or is a negative number other than -1
3506
+ * The `$` prefix is mandated by the OData URL conventions; these keys
3507
+ * are intentionally not configurable through `QueryBuilderOptions` and
3508
+ * live as private statics so they are visible in one place.
2002
3509
  */
2003
- validateLimit(limit: number): void;
3510
+ private static readonly _countKey;
3511
+ private static readonly _expandKey;
3512
+ private static readonly _filterKey;
3513
+ private static readonly _orderbyKey;
3514
+ private static readonly _searchKey;
3515
+ private static readonly _selectKey;
3516
+ private static readonly _skipKey;
3517
+ private static readonly _topKey;
2004
3518
  /**
2005
- * Emit NestJS-format query-string segments in canonical order:
2006
- * filtersoperator filters sortByselect → search → limitpage
3519
+ * Emit OData-format query-string segments in canonical order:
3520
+ * $filter$orderby$select$expand$search → $count$top → $skip
2007
3521
  *
2008
3522
  * @param state - The current query builder state
2009
- * @param options - The query parameter key name configuration
3523
+ * @param _options - The query parameter key name configuration (unused —
3524
+ * every OData system query option name is fixed by the specification)
2010
3525
  * @returns Ordered query-string fragments
2011
3526
  */
2012
- protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
3527
+ protected parts(state: IQueryBuilderState, _options: QueryBuilderOptions): string[];
2013
3528
  /**
2014
- * Append simple filter parameters as `filter.field=value1,value2`
3529
+ * Append the constant `$count=true` parameter
3530
+ *
3531
+ * Always emitted: the OData response strategy reads the total from
3532
+ * `@odata.count`, which servers only include when the request asks
3533
+ * for it.
2015
3534
  *
2016
- * @param state - The current query builder state
2017
- * @param options - The query parameter key name configuration
2018
3535
  * @param out - The accumulator the caller joins into the URI
2019
3536
  */
2020
- private _appendFilters;
3537
+ private _appendCount;
2021
3538
  /**
2022
- * Append the limit parameter
3539
+ * Append the `$expand=` parameter combining includes and embedded
3540
+ * relations
3541
+ *
3542
+ * Plain relations from `addIncludes` emit bare (`rel`); embedded
3543
+ * relations from `addEmbedded` emit with an inline projection
3544
+ * (`rel($select=col1,col2)`) or bare when no columns were given. A
3545
+ * relation present in both folds into the embedded fragment, which
3546
+ * carries the column information.
2023
3547
  *
2024
3548
  * @param state - The current query builder state
2025
- * @param options - The query parameter key name configuration
2026
3549
  * @param out - The accumulator the caller joins into the URI
2027
3550
  */
2028
- private _appendLimit;
3551
+ private _appendExpand;
2029
3552
  /**
2030
- * Append operator-filter parameters as `filter.field=$op:value`
3553
+ * Append the single `$filter=` parameter combining simple and operator
3554
+ * filters
2031
3555
  *
2032
- * Groups by field; multi-value operators ($in, $btw) join values with commas.
3556
+ * Each term is one OData boolean expression; terms join with ` and `.
3557
+ * Simple single-value filters fold to `eq`; simple multi-value filters
3558
+ * fold to the `in` list operator (`field in ('v1','v2')`).
2033
3559
  *
2034
3560
  * @param state - The current query builder state
2035
- * @param options - The query parameter key name configuration
2036
3561
  * @param out - The accumulator the caller joins into the URI
2037
3562
  */
2038
- private _appendOperatorFilters;
3563
+ private _appendFilter;
2039
3564
  /**
2040
- * Append the page parameter
3565
+ * Append the `$orderby=field asc,other desc` CSV parameter
2041
3566
  *
2042
3567
  * @param state - The current query builder state
2043
- * @param options - The query parameter key name configuration
2044
3568
  * @param out - The accumulator the caller joins into the URI
2045
3569
  */
2046
- private _appendPage;
3570
+ private _appendOrderby;
2047
3571
  /**
2048
- * Append the search parameter as `search=term`
3572
+ * Append the `$search=` free-text search parameter
2049
3573
  *
2050
3574
  * @param state - The current query builder state
2051
- * @param options - The query parameter key name configuration
2052
3575
  * @param out - The accumulator the caller joins into the URI
2053
3576
  */
2054
3577
  private _appendSearch;
2055
3578
  /**
2056
- * Append the select parameter as `select=col1,col2`
3579
+ * Append the `$select=col1,col2` projection parameter
2057
3580
  *
2058
3581
  * @param state - The current query builder state
2059
- * @param options - The query parameter key name configuration
2060
3582
  * @param out - The accumulator the caller joins into the URI
2061
3583
  */
2062
3584
  private _appendSelect;
2063
3585
  /**
2064
- * Append sort parameter as `sortBy=field1:DESC,field2:ASC`
3586
+ * Append the `$skip=` parameter, derived from state.page
3587
+ *
3588
+ * OData uses offset-based pagination: the skip is computed as
3589
+ * `(page - 1) * limit`. Omitted when the skip would be 0 (i.e. page 1)
3590
+ * since OData defaults to no skip and dropping it keeps the URI shorter.
3591
+ *
3592
+ * @param state - The current query builder state
3593
+ * @param out - The accumulator the caller joins into the URI
3594
+ */
3595
+ private _appendSkip;
3596
+ /**
3597
+ * Append the `$top=` page-size parameter
3598
+ *
3599
+ * @param state - The current query builder state
3600
+ * @param out - The accumulator the caller joins into the URI
3601
+ */
3602
+ private _appendTop;
3603
+ /**
3604
+ * Format a filter value as an OData primitive literal
3605
+ *
3606
+ * Strings are single-quoted with embedded single quotes doubled
3607
+ * (`'O''Brien'`) per the OData ABNF; numbers and booleans are emitted
3608
+ * bare.
3609
+ *
3610
+ * @param value - The raw filter value
3611
+ * @returns The OData-formatted literal
3612
+ */
3613
+ private _formatLiteral;
3614
+ /**
3615
+ * Translate a `FilterOperatorEnum` operator filter into one or more
3616
+ * OData boolean expressions
3617
+ *
3618
+ * The mapping is library-canonical → OData-native:
3619
+ * - `EQ` → `eq`; `GT`/`GTE`/`LT`/`LTE` → `gt` / `ge` / `lt` / `le`
3620
+ * - `CONTAINS` → `contains(field,'val')`
3621
+ * - `ILIKE` → `contains(tolower(field),tolower('val'))` (case-insensitive
3622
+ * contains)
3623
+ * - `SW` → `startswith(field,'val')`
3624
+ * - `IN` → `field in ('v1','v2')` (OData v4.01 list operator)
3625
+ * - `BTW` → **two** AND-ed terms (`field ge min` and `field le max`,
3626
+ * arity-checked)
3627
+ * - `NOT` → `ne` — one term per value, AND-ed (`field ne v1 and field ne v2`)
3628
+ * - `NULL` → `field eq null` (when value is `true`) / `field ne null`
3629
+ * (when value is `false`); arity- and type-checked
3630
+ *
3631
+ * PostgREST's full-text-search operators (`FTS`, `PHFTS`, `PLFTS`,
3632
+ * `WFTS`) have no OData equivalent and throw
3633
+ * `UnsupportedFilterOperatorError`.
3634
+ *
3635
+ * @param filter - The operator filter to translate
3636
+ * @returns One or more OData boolean expressions ready to ` and `-join
3637
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
3638
+ * exactly two values, or `NULL` does not receive exactly one boolean
3639
+ * @throws {UnsupportedFilterOperatorError} If the operator is a
3640
+ * PostgREST-only FTS variant
3641
+ */
3642
+ private _formatOperatorTerms;
3643
+ }
3644
+
3645
+ /**
3646
+ * Response strategy for the OData v4 driver
3647
+ *
3648
+ * Parses OData collection responses:
3649
+ *
3650
+ * ```json
3651
+ * {
3652
+ * "@odata.context": "https://api.example.com/$metadata#Products",
3653
+ * "@odata.count": 100,
3654
+ * "@odata.nextLink": "https://api.example.com/Products?$count=true&$top=10&$skip=30",
3655
+ * "value": [...]
3656
+ * }
3657
+ * ```
3658
+ *
3659
+ * OData emits no current-page or page-size field in the body, so this
3660
+ * strategy **derives** them by inspecting the `@odata.nextLink` URL:
3661
+ *
3662
+ * - `perPage` comes from the link's `$top` param, falling back to the
3663
+ * item count of the current (necessarily full) page when the link
3664
+ * carries no `$top`.
3665
+ * - `currentPage` is `$skip ÷ perPage` — the next page starts where the
3666
+ * current one ends. Without a usable link (`$skiptoken`-based
3667
+ * server-driven paging, or the last page) the strategy falls back to
3668
+ * page **1**, which is only guaranteed correct for single-page results.
3669
+ * - `lastPage` is `ceil(total ÷ perPage)`; on a link-less response it
3670
+ * resolves to 1 when the page provably holds the whole result set.
3671
+ *
3672
+ * The total requires `$count=true` on the request — the request strategy
3673
+ * always emits it. Envelope keys contain **literal dots** (`@odata.count`),
3674
+ * so key paths from `OdataResponseOptions` are read with flat bracket
3675
+ * access, never dot-path traversal.
3676
+ *
3677
+ * @see https://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html
3678
+ */
3679
+ declare class OdataResponseStrategy implements IResponseStrategy {
3680
+ /**
3681
+ * Parse an OData collection response into a PaginatedCollection
3682
+ *
3683
+ * @param response - The raw API response body
3684
+ * @param options - The response key name configuration
3685
+ * @returns A typed PaginatedCollection instance
3686
+ */
3687
+ paginate<T extends IPaginatedObject>(response: Record<string, any>, options: ResponseOptions): PaginatedCollection<T>;
3688
+ /**
3689
+ * Derive the current page number from the `@odata.nextLink` URL
3690
+ *
3691
+ * The next page starts where the current one ends, so
3692
+ * `currentPage = $skip ÷ perPage` (e.g. `$skip=30` with `$top=10` →
3693
+ * page 3). Falls back to **1** when the link is absent (last or only
3694
+ * page), carries no `$skip` (`$skiptoken`-based paging), or the page
3695
+ * size is unknown.
3696
+ *
3697
+ * @param nextPageUrl - The `@odata.nextLink` from the response, or null
3698
+ * @param perPage - The derived page size (may be undefined)
3699
+ * @returns The current page number
3700
+ */
3701
+ private _deriveCurrentPage;
3702
+ /**
3703
+ * Derive `from` as the 1-indexed offset of the first item on this page
3704
+ *
3705
+ * Computed from `currentPage` × `perPage` when the page size is known;
3706
+ * on a link-less single page it is 1 whenever items are present.
3707
+ *
3708
+ * @param nextPageUrl - The `@odata.nextLink` from the response, or null
3709
+ * @param data - The items on the current page
3710
+ * @param currentPage - The current page number
3711
+ * @param perPage - The page size (may be undefined)
3712
+ * @returns The 1-indexed `from` index, or undefined when inputs insufficient
3713
+ */
3714
+ private _deriveFrom;
3715
+ /**
3716
+ * Derive the last page number
3717
+ *
3718
+ * `ceil(total ÷ perPage)` when both are known; a link-less response
3719
+ * that provably holds the entire non-empty result set resolves to 1.
3720
+ *
3721
+ * @param nextPageUrl - The `@odata.nextLink` from the response, or null
3722
+ * @param data - The items on the current page
3723
+ * @param total - The total item count
3724
+ * @param perPage - The page size
3725
+ * @returns The last page number, or undefined when inputs insufficient
3726
+ */
3727
+ private _deriveLastPage;
3728
+ /**
3729
+ * Derive `perPage` from the `@odata.nextLink` URL
3730
+ *
3731
+ * Reads the link's `$top` param. When the link exists but carries no
3732
+ * `$top` (server-driven page size), the current page is necessarily
3733
+ * full, so its item count equals the page size. Without a link the
3734
+ * page may be partial and the size is not introspectable.
3735
+ *
3736
+ * @param nextPageUrl - The `@odata.nextLink` from the response, or null
3737
+ * @param data - The items on the current page
3738
+ * @returns The page size, or undefined
3739
+ */
3740
+ private _derivePerPage;
3741
+ /**
3742
+ * Derive `to` as the 1-indexed offset of the last item on this page
3743
+ *
3744
+ * Clamped to `total` so the last page does not report past the end;
3745
+ * on a link-less single page it is the item count.
3746
+ *
3747
+ * @param nextPageUrl - The `@odata.nextLink` from the response, or null
3748
+ * @param data - The items on the current page
3749
+ * @param currentPage - The current page number
3750
+ * @param perPage - The page size (may be undefined)
3751
+ * @param total - The total item count (may be undefined)
3752
+ * @returns The 1-indexed `to` index, or undefined when inputs insufficient
3753
+ */
3754
+ private _deriveTo;
3755
+ /**
3756
+ * Extract an integer query parameter from an OData pagination URL
3757
+ *
3758
+ * @param url - The URL to parse
3759
+ * @param name - The query-parameter name to look up (e.g. `$skip`)
3760
+ * @returns The integer value, or undefined
3761
+ */
3762
+ private _extractNumberParam;
3763
+ /**
3764
+ * Extract a single query parameter from a URL via the WHATWG URL parser
3765
+ *
3766
+ * OData permits **relative** `@odata.nextLink` values, so parsing
3767
+ * retries against a placeholder base before giving up. Returns
3768
+ * undefined when the URL is unparseable or the parameter is absent.
3769
+ *
3770
+ * @param url - The URL to parse
3771
+ * @param name - The query-parameter name to look up
3772
+ * @returns The raw string value of the parameter, or undefined
3773
+ */
3774
+ private _extractQueryParam;
3775
+ }
3776
+
3777
+ /**
3778
+ * Request strategy for the Payload CMS driver
3779
+ *
3780
+ * Generates URIs in [Payload's REST query format](https://payloadcms.com/docs/queries/overview):
3781
+ * - Filters: `where[field][equals]=value` (multi-value collapses to
3782
+ * `where[field][in]=v1,v2` CSV)
3783
+ * - Operator filters: `where[field][op]=value` (translated from
3784
+ * `FilterOperatorEnum` — `BTW`→`greater_than_equal`+`less_than_equal`
3785
+ * pair, `NOT`→`not_equals`/`not_in`, `NULL`→`exists` with inverted
3786
+ * boolean)
3787
+ * - Sorts: `sort=-createdAt,title` (CSV, `-` prefix = DESC)
3788
+ * - Field selection (flat): `select[col1]=true&select[col2]=true`
3789
+ * - Pagination (page-based): `page=N&limit=M`
3790
+ *
3791
+ * The `where` / `sort` / `select` / `page` / `limit` keys are fixed by
3792
+ * Payload's REST endpoints and intentionally not configurable through
3793
+ * `QueryBuilderOptions`; they live as private statics so they are
3794
+ * visible in one place.
3795
+ *
3796
+ * Relationship population is controlled by Payload's numeric `depth`
3797
+ * param, not a named-relation list — `addIncludes` therefore throws
3798
+ * `UnsupportedIncludesError` (pass `depth` through your HTTP layer if
3799
+ * needed). There is no per-model field selection and no global search
3800
+ * param. `SW` (no starts-with operator) and the PostgREST-native
3801
+ * full-text operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
3802
+ * `UnsupportedFilterOperatorError`.
3803
+ *
3804
+ * @see https://payloadcms.com/docs/queries/overview
3805
+ */
3806
+ declare class PayloadRequestStrategy extends AbstractRequestStrategy {
3807
+ /**
3808
+ * Filters, operator filters, sorts, flat field selection (`select`)
3809
+ * — no per-model fields, no includes (numeric `depth` instead), no
3810
+ * global search, no embedded resources
3811
+ */
3812
+ readonly capabilities: IStrategyCapabilities;
3813
+ /**
3814
+ * Payload-native names of the five hardcoded query keys
3815
+ *
3816
+ * Fixed by the REST endpoints and intentionally not configurable
3817
+ * through `QueryBuilderOptions`.
3818
+ */
3819
+ private static readonly _limitKey;
3820
+ private static readonly _pageKey;
3821
+ private static readonly _selectKey;
3822
+ private static readonly _sortKey;
3823
+ private static readonly _whereKey;
3824
+ /**
3825
+ * Emit Payload-format query-string segments in canonical order:
3826
+ * where (merged) → sort → select → page → limit
3827
+ *
3828
+ * Simple filters and operator filters share a single `where` wrapper
3829
+ * so qs emits one ordered bracket structure rather than two duplicate
3830
+ * top-level `where[...]` blocks.
3831
+ *
3832
+ * @param state - The current query builder state
3833
+ * @param _options - The query parameter key name configuration (unused;
3834
+ * Payload's wire keys are fixed by the server)
3835
+ * @returns Ordered query-string fragments
3836
+ */
3837
+ protected parts(state: IQueryBuilderState, _options: QueryBuilderOptions): string[];
3838
+ /**
3839
+ * Append the `page=` / `limit=` pagination pair
3840
+ *
3841
+ * @param state - The current query builder state
3842
+ * @param out - The accumulator the caller joins into the URI
3843
+ */
3844
+ private _appendPagination;
3845
+ /**
3846
+ * Append `select[col]=true` flags from the flat select array
3847
+ *
3848
+ * Payload's select API is an object of `true` flags rather than a
3849
+ * CSV; nested selection (`select[group.field]=true`) passes through
3850
+ * when given as a dotted column name.
3851
+ *
3852
+ * @param state - The current query builder state
3853
+ * @param out - The accumulator the caller joins into the URI
3854
+ */
3855
+ private _appendSelect;
3856
+ /**
3857
+ * Append the `sort=-createdAt,title` CSV parameter
2065
3858
  *
2066
3859
  * @param state - The current query builder state
2067
- * @param options - The query parameter key name configuration
2068
3860
  * @param out - The accumulator the caller joins into the URI
2069
3861
  */
2070
- private _appendSort;
3862
+ private _appendSort;
3863
+ /**
3864
+ * Append the unified `where[...]` wrapper combining simple filters
3865
+ * and operator filters
3866
+ *
3867
+ * Both kinds emit into the same nested object under `where` so qs
3868
+ * produces a single bracketed block per request. Simple single-value
3869
+ * filters fold to `equals`; simple multi-value filters fold to the
3870
+ * `in` CSV. Operator filters then merge into the same per-field map,
3871
+ * potentially co-existing with a simple filter on the same field.
3872
+ *
3873
+ * @param state - The current query builder state
3874
+ * @param out - The accumulator the caller joins into the URI
3875
+ */
3876
+ private _appendWhere;
3877
+ /**
3878
+ * Translate a `FilterOperatorEnum` operator filter into Payload's
3879
+ * `operator → value` payload shape
3880
+ *
3881
+ * The mapping is library-canonical → Payload-native:
3882
+ * - `EQ` → `equals`
3883
+ * - `GT`/`GTE`/`LT`/`LTE` → `greater_than` / `greater_than_equal` /
3884
+ * `less_than` / `less_than_equal`
3885
+ * - `CONTAINS` → `contains` (case-insensitive substring)
3886
+ * - `ILIKE` → `like` (case-insensitive, word-based)
3887
+ * - `IN` → `in` (CSV)
3888
+ * - `BTW` → `greater_than_equal` + `less_than_equal` pair in one
3889
+ * payload (arity-checked)
3890
+ * - `NOT` → `not_equals` (single value) / `not_in` (multi-value, CSV)
3891
+ * - `NULL` → `exists` with **inverted** boolean (`true` →
3892
+ * `exists=false` ⇔ IS NULL); arity- and type-checked
3893
+ *
3894
+ * `SW` (Payload has no starts-with operator) and PostgREST's
3895
+ * full-text-search operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
3896
+ * `UnsupportedFilterOperatorError`.
3897
+ *
3898
+ * @param filter - The operator filter to translate
3899
+ * @returns An `{ operator: value }` payload ready to merge under
3900
+ * `where[field]`
3901
+ * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
3902
+ * exactly two values, or `NULL` does not receive exactly one boolean
3903
+ * @throws {UnsupportedFilterOperatorError} If the operator has no
3904
+ * Payload equivalent
3905
+ */
3906
+ private _formatOperatorPayload;
2071
3907
  }
2072
3908
 
2073
3909
  /**
2074
- * Response strategy for the NestJS (nestjs-paginate) driver
3910
+ * Response strategy for the Payload CMS driver
2075
3911
  *
2076
- * Parses nested NestJS pagination responses:
3912
+ * Parses Payload's paginated collection responses — the
3913
+ * `mongoose-paginate-v2` envelope, shared by many Express/Mongoose
3914
+ * backends:
2077
3915
  * ```json
2078
3916
  * {
2079
- * "data": [...],
2080
- * "meta": {
2081
- * "currentPage": 1,
2082
- * "totalItems": 100,
2083
- * "itemsPerPage": 10,
2084
- * "totalPages": 10
2085
- * },
2086
- * "links": {
2087
- * "first": "url",
2088
- * "previous": "url",
2089
- * "next": "url",
2090
- * "last": "url",
2091
- * "current": "url"
2092
- * }
3917
+ * "docs": [{ "id": "abc123", "title": "Hello" }],
3918
+ * "totalDocs": 48,
3919
+ * "limit": 10,
3920
+ * "totalPages": 5,
3921
+ * "page": 2,
3922
+ * "pagingCounter": 11,
3923
+ * "hasPrevPage": true,
3924
+ * "hasNextPage": true,
3925
+ * "prevPage": 1,
3926
+ * "nextPage": 3
2093
3927
  * }
2094
3928
  * ```
2095
3929
  *
2096
- * Default key paths are configured in `NestjsResponseOptions`. The
2097
- * traversal algorithm (dot-notation resolution + computed `from`/`to`) is
2098
- * inherited from `AbstractDotPathResponseStrategy`; this class exists so
2099
- * `DriverEnum.NESTJS` resolves to a distinct identity at the DI layer
2100
- * even though the parsing logic is shared with JSON:API.
3930
+ * Default key paths are configured in `PayloadResponseOptions`. The
3931
+ * envelope's `pagingCounter` is the 1-indexed offset of the first doc
3932
+ * on the page, so it maps straight onto `from`; `to` is computed from
3933
+ * `page` × `limit` (clamped to the total). `prevPage` / `nextPage` are
3934
+ * **page numbers**, not URLs, so the navigation-URL slots on
3935
+ * `PaginatedCollection` stay `undefined` unless the consumer overrides
3936
+ * their paths via `IPaginationConfig`. The traversal algorithm is
3937
+ * inherited from `AbstractDotPathResponseStrategy`; this class exists
3938
+ * so `DriverEnum.PAYLOAD` resolves to a distinct identity at the DI
3939
+ * layer even though the parsing logic is shared with JSON:API, NestJS,
3940
+ * Strapi, and PocketBase.
2101
3941
  *
2102
- * @see https://github.com/ppetzold/nestjs-paginate
3942
+ * @see https://payloadcms.com/docs/rest-api/overview
2103
3943
  */
2104
- declare class NestjsResponseStrategy extends AbstractDotPathResponseStrategy {
3944
+ declare class PayloadResponseStrategy extends AbstractDotPathResponseStrategy {
2105
3945
  }
2106
3946
 
2107
3947
  /**
2108
- * Request strategy for the @nestjsx/crud driver
2109
- *
2110
- * Generates URIs in [@nestjsx/crud's pipe-delimited query format](https://github.com/nestjsx/crud/wiki/Requests):
2111
- * - Filters: `filter=field||$eq||value` (repeatable; multi-value
2112
- * collapses to `filter=field||$in||v1,v2`)
2113
- * - Operator filters: `filter=field||$op||value` (translated from
2114
- * `FilterOperatorEnum` `CONTAINS`→`$cont`, `ILIKE`→`$contL`,
2115
- * `SW`→`$starts`, `BTW`→`$between`, `NOT`→`$ne`/`$notin`,
2116
- * `NULL`→`$isnull`/`$notnull` with no value segment)
2117
- * - Joins: `join=relation` (repeatable, from `addIncludes`)
2118
- * - Field selection (flat): `fields=col1,col2` (from `addSelect`)
2119
- * - Sorts: `sort=field,ASC` (repeatable, uppercase direction)
2120
- * - Pagination (page-based): `page=N&limit=N`
2121
- *
2122
- * The JSON-shaped `s={...}` search parameter is intentionally out of
2123
- * scope: it is not a plain search term, so `setSearch` throws
2124
- * `UnsupportedSearchError` on this driver. PostgREST-native full-text
2125
- * search operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
3948
+ * Request strategy for the PocketBase driver
3949
+ *
3950
+ * Generates URIs in [PocketBase's records-list format](https://pocketbase.io/docs/api-records/):
3951
+ * - Filters: a single `filter=(...)` expression-language parameter —
3952
+ * simple single-value filters fold to `field='value'`, multi-value
3953
+ * filters fold to an OR group (`(field='v1' || field='v2')`), and all
3954
+ * clauses join with ` && `
3955
+ * - Operator filters: expression terms (`field>=10`, `field~'val'`)
3956
+ * see the mapping on `_formatOperatorClause`
3957
+ * - Sorts: `sort=-created,title` (CSV, `-` prefix = DESC)
3958
+ * - Field selection (flat): `fields=id,title` (CSV)
3959
+ * - Relation expansion: `expand=author,comments` (CSV)
3960
+ * - Pagination (page-based): `page=N&perPage=M`
3961
+ *
3962
+ * The `page` / `perPage` / `sort` / `filter` / `expand` / `fields` keys
3963
+ * are fixed by the PocketBase records API and intentionally not
3964
+ * configurable through `QueryBuilderOptions`; they live as private
3965
+ * statics so they are visible in one place.
3966
+ *
3967
+ * String literals are single-quoted with embedded quotes
3968
+ * backslash-escaped (`name='O\'Brien'`); numbers and booleans emit
3969
+ * bare. PocketBase has no global search param (use `~` terms instead)
3970
+ * and no per-model field selection — the corresponding fluent methods
3971
+ * throw the matching `Unsupported*Error`. The PostgREST-native
3972
+ * full-text operators (`FTS`, `PHFTS`, `PLFTS`, `WFTS`) throw
2126
3973
  * `UnsupportedFilterOperatorError`.
2127
3974
  *
2128
- * @see https://github.com/nestjsx/crud/wiki/Requests
3975
+ * @see https://pocketbase.io/docs/api-records/
2129
3976
  */
2130
- declare class NestjsxCrudRequestStrategy extends AbstractRequestStrategy {
3977
+ declare class PocketbaseRequestStrategy extends AbstractRequestStrategy {
2131
3978
  /**
2132
- * Filters, operator filters, joins (`includes`), flat field selection
2133
- * (`select`), sorts — no per-model fields, no global search (the `s`
2134
- * parameter is JSON-shaped, not a plain term)
3979
+ * Filters, operator filters, sorts, expand (`includes`), flat field
3980
+ * selection (`select`) — no per-model fields, no global search, no
3981
+ * embedded resources
2135
3982
  */
2136
3983
  readonly capabilities: IStrategyCapabilities;
2137
3984
  /**
2138
- * @nestjsx/crud-native names of the four hardcoded query keys
3985
+ * PocketBase-native names of the six hardcoded query keys
2139
3986
  *
2140
- * The wire format is fixed (the server's `CrudRequestInterceptor`
2141
- * reads `fields`, `filter`, `join`, and `sort`); these keys are
2142
- * intentionally not configurable through `QueryBuilderOptions` and
2143
- * live as private statics so they are visible in one place.
3987
+ * The records API reads exactly these names; they are intentionally
3988
+ * not configurable through `QueryBuilderOptions` and live as private
3989
+ * statics so they are visible in one place.
2144
3990
  */
3991
+ private static readonly _expandKey;
2145
3992
  private static readonly _fieldsKey;
2146
3993
  private static readonly _filterKey;
2147
- private static readonly _joinKey;
3994
+ private static readonly _pageKey;
3995
+ private static readonly _perPageKey;
2148
3996
  private static readonly _sortKey;
2149
3997
  /**
2150
- * Pipe delimiter separating field, operator, and value segments
2151
- */
2152
- private static readonly _separator;
2153
- /**
2154
- * Emit @nestjsx/crud-format query-string segments in canonical order:
2155
- * fields → filters → operator filters → join → sort → limit → page
3998
+ * Emit PocketBase-format query-string segments in canonical order:
3999
+ * expand → fields → filter (merged) → sort → page → perPage
2156
4000
  *
2157
- * @param state - The current query builder state
2158
- * @param options - The query parameter key name configuration (used
2159
- * for `page` / `limit`, whose defaults match the wire format; the
2160
- * `fields` / `filter` / `join` / `sort` keys are fixed by the server)
2161
- * @returns Ordered query-string fragments
2162
- */
2163
- protected parts(state: IQueryBuilderState, options: QueryBuilderOptions): string[];
2164
- /**
2165
- * Append `fields=col1,col2` from the flat select array
4001
+ * Simple filters and operator filters share the single `filter=(...)`
4002
+ * parameter so the server receives one combined expression rather
4003
+ * than two conflicting `filter` params.
2166
4004
  *
2167
4005
  * @param state - The current query builder state
2168
- * @param out - The accumulator the caller joins into the URI
4006
+ * @param _options - The query parameter key name configuration (unused;
4007
+ * PocketBase's wire keys are fixed by the server)
4008
+ * @returns Ordered query-string fragments
2169
4009
  */
2170
- private _appendFields;
4010
+ protected parts(state: IQueryBuilderState, _options: QueryBuilderOptions): string[];
2171
4011
  /**
2172
- * Append simple filters as repeatable `filter=field||$eq||value` params
4012
+ * Append the `expand=` CSV from the includes array
2173
4013
  *
2174
- * Single-value filters fold to `$eq`; multi-value filters fold to
2175
- * `$in` with comma-joined values.
4014
+ * Nested expansion (`author.address`) passes through verbatim when
4015
+ * given as a dotted include name.
2176
4016
  *
2177
4017
  * @param state - The current query builder state
2178
4018
  * @param out - The accumulator the caller joins into the URI
2179
4019
  */
2180
- private _appendFilters;
4020
+ private _appendExpand;
2181
4021
  /**
2182
- * Append `join=relation` params from the includes array
2183
- *
2184
- * Per-relation field projection (`join=relation||f1,f2`) is not
2185
- * expressible through the current state shape and is out of scope.
4022
+ * Append the `fields=` CSV from the flat select array
2186
4023
  *
2187
4024
  * @param state - The current query builder state
2188
4025
  * @param out - The accumulator the caller joins into the URI
2189
4026
  */
2190
- private _appendJoin;
4027
+ private _appendFields;
2191
4028
  /**
2192
- * Append the limit parameter
4029
+ * Append the single `filter=(...)` expression combining simple
4030
+ * filters and operator filters
2193
4031
  *
2194
- * @param state - The current query builder state
2195
- * @param options - The query parameter key name configuration
2196
- * @param out - The accumulator the caller joins into the URI
2197
- */
2198
- private _appendLimit;
2199
- /**
2200
- * Append operator filters as repeatable `filter=field||$op||value` params
4032
+ * Clauses join with ` && `; multi-value clauses keep their own
4033
+ * parentheses so OR groups stay correctly scoped inside the AND
4034
+ * chain.
2201
4035
  *
2202
4036
  * @param state - The current query builder state
2203
4037
  * @param out - The accumulator the caller joins into the URI
2204
4038
  */
2205
- private _appendOperatorFilters;
4039
+ private _appendFilter;
2206
4040
  /**
2207
- * Append the page parameter
4041
+ * Append the `page=` / `perPage=` pagination pair
2208
4042
  *
2209
4043
  * @param state - The current query builder state
2210
- * @param options - The query parameter key name configuration
2211
4044
  * @param out - The accumulator the caller joins into the URI
2212
4045
  */
2213
- private _appendPage;
4046
+ private _appendPagination;
2214
4047
  /**
2215
- * Append `sort=field,ASC` params, one per sort rule
4048
+ * Append the `sort=-created,title` CSV parameter
2216
4049
  *
2217
4050
  * @param state - The current query builder state
2218
4051
  * @param out - The accumulator the caller joins into the URI
2219
4052
  */
2220
4053
  private _appendSort;
2221
4054
  /**
2222
- * Translate a `FilterOperatorEnum` operator filter into @nestjsx/crud's
2223
- * `$operator||value` condition segment
2224
- *
2225
- * The mapping is library-canonical → @nestjsx/crud-native:
2226
- * - `EQ`/`GT`/`GTE`/`LT`/`LTE`/`IN` → identity (same operator name)
2227
- * - `CONTAINS` → `$cont`; `ILIKE` `$contL` (case-insensitive contains)
2228
- * - `SW` `$starts`
2229
- * - `BTW` → `$between` with `min,max` (arity-checked)
2230
- * - `NOT` → `$ne` (single value) / `$notin` (multi-value)
2231
- * - `NULL` → `$isnull` (when value is `true`) / `$notnull` (when value
2232
- * is `false`); both emit **no value segment**; arity- and type-checked
4055
+ * Translate a `FilterOperatorEnum` operator filter into one
4056
+ * PocketBase expression clause
4057
+ *
4058
+ * The mapping is library-canonical → PocketBase-native:
4059
+ * - `EQ` → `field='v'`; `GT`/`GTE`/`LT`/`LTE` → `>` / `>=` / `<` / `<=`
4060
+ * - `CONTAINS`/`ILIKE` → `field~'v'` (PocketBase's `~` is a
4061
+ * case-insensitive LIKE that auto-wraps the operand in `%...%`)
4062
+ * - `SW` → `field~'v%'` (explicit trailing wildcard disables the
4063
+ * auto-wrap)
4064
+ * - `IN` → OR group `(field='v1' || field='v2')`
4065
+ * - `BTW` AND group `(field>=min && field<=max)` (arity-checked)
4066
+ * - `NOT` → `field!='v'` (single) / AND group of `!=` terms (multi)
4067
+ * - `NULL` → `field=null` (when value is `true`) / `field!=null`
4068
+ * (when value is `false`); arity- and type-checked
2233
4069
  *
2234
4070
  * PostgREST's full-text-search operators (`FTS`, `PHFTS`, `PLFTS`,
2235
- * `WFTS`) have no @nestjsx/crud equivalent and throw
4071
+ * `WFTS`) have no PocketBase equivalent and throw
2236
4072
  * `UnsupportedFilterOperatorError`.
2237
4073
  *
2238
4074
  * @param filter - The operator filter to translate
2239
- * @returns The `$operator||value` (or bare `$operator`) condition segment
4075
+ * @returns One expression clause ready to join into `filter=(...)`
2240
4076
  * @throws {InvalidFilterOperatorValueError} If `BTW` does not receive
2241
4077
  * exactly two values, or `NULL` does not receive exactly one boolean
2242
4078
  * @throws {UnsupportedFilterOperatorError} If the operator is a
2243
4079
  * PostgREST-only FTS variant
2244
4080
  */
2245
- private _formatOperatorCondition;
4081
+ private _formatOperatorClause;
4082
+ /**
4083
+ * Backslash-escape single quotes inside a string literal
4084
+ *
4085
+ * @param value - The raw string value
4086
+ * @returns The escaped string, ready to wrap in single quotes
4087
+ */
4088
+ private _escape;
4089
+ /**
4090
+ * Format a filter value as a PocketBase expression literal
4091
+ *
4092
+ * Strings are single-quoted (embedded quotes backslash-escaped);
4093
+ * numbers and booleans emit bare, matching the expression language's
4094
+ * literal rules.
4095
+ *
4096
+ * @param value - The raw filter value
4097
+ * @returns The formatted literal
4098
+ */
4099
+ private _formatValue;
2246
4100
  }
2247
4101
 
2248
4102
  /**
2249
- * Response strategy for the @nestjsx/crud driver
4103
+ * Response strategy for the PocketBase driver
2250
4104
  *
2251
- * Parses @nestjsx/crud's `getMany` envelope:
4105
+ * Parses PocketBase records-list responses:
2252
4106
  * ```json
2253
4107
  * {
2254
- * "data": [{ "id": 1, "name": "John" }],
2255
- * "count": 10,
2256
- * "total": 48,
2257
- * "page": 2,
2258
- * "pageCount": 5
4108
+ * "page": 1,
4109
+ * "perPage": 30,
4110
+ * "totalItems": 48,
4111
+ * "totalPages": 2,
4112
+ * "items": [{ "id": "abc123", "title": "Hello" }]
2259
4113
  * }
2260
4114
  * ```
2261
4115
  *
2262
- * Default key paths are configured in `NestjsxCrudResponseOptions`. The
2263
- * envelope carries no `from`/`to` indices and no navigation links, so
2264
- * `from`/`to` are computed from `page` × `count` by the inherited
2265
- * traversal algorithm and the URL slots resolve to `undefined` unless
2266
- * the consumer overrides their paths via `IPaginationConfig`.
2267
- *
2268
- * Note that `count` is the number of entities **on the current page**,
2269
- * not the requested page size on the last page of a result set the
2270
- * derived `from`/`to` can underestimate. Consumers needing exact
2271
- * indices should compute them from the requested limit instead.
2272
- *
2273
- * The dot-notation traversal is inherited from
2274
- * `AbstractDotPathResponseStrategy`; this class exists so
2275
- * `DriverEnum.NESTJSX_CRUD` resolves to a distinct identity at the DI
4116
+ * Default key paths are configured in `PocketbaseResponseOptions`
4117
+ * all flat keys, resolved by the inherited dot-path traversal (a flat
4118
+ * key is just a one-segment path). PocketBase does not include
4119
+ * navigation links in the envelope, so `firstPageUrl`, `prevPageUrl`,
4120
+ * `nextPageUrl`, and `lastPageUrl` resolve to `undefined` unless the
4121
+ * consumer overrides their paths via `IPaginationConfig`; `from`/`to`
4122
+ * are computed from `page` × `perPage`. This class exists so
4123
+ * `DriverEnum.POCKETBASE` resolves to a distinct identity at the DI
2276
4124
  * layer even though the parsing logic is shared with JSON:API, NestJS,
2277
4125
  * and Strapi.
2278
4126
  *
2279
- * @see https://github.com/nestjsx/crud/wiki/Requests
4127
+ * When the request was sent with `skipTotal=1` (not emitted by this
4128
+ * driver), PocketBase reports `totalItems`/`totalPages` as `-1`;
4129
+ * override the paths or treat the values accordingly if you opt into
4130
+ * that server-side optimisation.
4131
+ *
4132
+ * @see https://pocketbase.io/docs/api-records/
2280
4133
  */
2281
- declare class NestjsxCrudResponseStrategy extends AbstractDotPathResponseStrategy {
4134
+ declare class PocketbaseResponseStrategy extends AbstractDotPathResponseStrategy {
2282
4135
  }
2283
4136
 
2284
4137
  /**
@@ -2423,10 +4276,17 @@ declare class PostgrestRequestStrategy extends AbstractRequestStrategy {
2423
4276
  */
2424
4277
  private _appendOrder;
2425
4278
  /**
2426
- * Append the select parameter as `select=col1,col2`
4279
+ * Append the select parameter as `select=col1,col2,rel(col1,col2)`
2427
4280
  *
2428
- * PostgREST uses a `select` query param for column pruning, matching
2429
- * NestJS semantics.
4281
+ * PostgREST uses a single `select` query param for both column pruning
4282
+ * (matching NestJS semantics) and embedded-resource fetching — the
4283
+ * embedded fragments from `addEmbedded` are spliced into the same
4284
+ * param value, never emitted as a second `select`.
4285
+ *
4286
+ * Fragment shape per relation: `rel(col1,col2)` with explicit columns,
4287
+ * `rel(*)` without. When embedded relations are present but no flat
4288
+ * columns were selected, the flat part defaults to `*` so the base
4289
+ * row's columns are not silently dropped from the projection.
2430
4290
  *
2431
4291
  * @param state - The current query builder state
2432
4292
  * @param options - The query parameter key name configuration
@@ -3054,5 +4914,241 @@ declare class StrapiRequestStrategy extends AbstractRequestStrategy {
3054
4914
  declare class StrapiResponseStrategy extends AbstractDotPathResponseStrategy {
3055
4915
  }
3056
4916
 
3057
- 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 };
3058
- export type { HeaderBag, IConfig, IFields, IFilters, INestState, IOperatorFilter, IPage, IPaginatedObject, IPaginationConfig, IQueryBuilderConfig, IQueryBuilderState, IRequestStrategy, IResponseStrategy, ISort };
4917
+ /**
4918
+ * Request strategy for the WordPress REST API driver
4919
+ *
4920
+ * Generates URIs in the [WordPress REST API collection format](https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/):
4921
+ * - Filters: `field=value` (collection parameters like `status`,
4922
+ * `author`, `categories`); multi-value folds to a CSV (`field=v1,v2`,
4923
+ * the list convention WordPress uses for ID params)
4924
+ * - Sorts: `orderby=field&order=asc|desc` — WordPress core supports a
4925
+ * **single** orderby, so only the first sort rule is emitted
4926
+ * - Field selection (flat): `_fields=id,title` (CSV)
4927
+ * - Relation embedding: `_embed=author,wp:term` (CSV)
4928
+ * - Search: `search=term`
4929
+ * - Pagination (page-based): `page=N&per_page=M`
4930
+ *
4931
+ * The `page` / `per_page` / `orderby` / `order` / `search` and the
4932
+ * underscore-prefixed global params (`_fields`, `_embed`) are fixed by
4933
+ * the server and intentionally not configurable through
4934
+ * `QueryBuilderOptions`; they live as private statics so they are
4935
+ * visible in one place.
4936
+ *
4937
+ * WordPress caps `per_page` at **100** server-side (a 400
4938
+ * `rest_invalid_param` response beyond that); the cap is endpoint
4939
+ * policy rather than a syntax rule, so `validateLimit` keeps the
4940
+ * default positive-integer check and the server stays authoritative.
4941
+ * There is no generic comparison-operator syntax (only
4942
+ * parameter-specific helpers like `before`/`after` for dates, which
4943
+ * pass through `addFilter`), so `addFilterOperator` throws
4944
+ * `UnsupportedFilterOperatorError` via the capability gate.
4945
+ *
4946
+ * @see https://developer.wordpress.org/rest-api/reference/posts/#list-posts
4947
+ */
4948
+ declare class WordpressRequestStrategy extends AbstractRequestStrategy {
4949
+ /**
4950
+ * Filters, sorts, global search, flat field selection (`select`),
4951
+ * embedding (`includes`) — no operator filters, no per-model fields,
4952
+ * no embedded-column projection
4953
+ */
4954
+ readonly capabilities: IStrategyCapabilities;
4955
+ /**
4956
+ * WordPress-native names of the seven hardcoded query keys
4957
+ *
4958
+ * The underscore prefix marks the REST API's global params apart
4959
+ * from collection filters; all keys are fixed by the server and
4960
+ * intentionally not configurable through `QueryBuilderOptions`.
4961
+ */
4962
+ private static readonly _embedKey;
4963
+ private static readonly _fieldsKey;
4964
+ private static readonly _orderbyKey;
4965
+ private static readonly _orderKey;
4966
+ private static readonly _pageKey;
4967
+ private static readonly _perPageKey;
4968
+ private static readonly _searchKey;
4969
+ /**
4970
+ * Emit WordPress-format query-string segments in canonical order:
4971
+ * filters → orderby/order → _fields → _embed → search → page → per_page
4972
+ *
4973
+ * @param state - The current query builder state
4974
+ * @param _options - The query parameter key name configuration (unused;
4975
+ * WordPress' wire keys are fixed by the server)
4976
+ * @returns Ordered query-string fragments
4977
+ */
4978
+ protected parts(state: IQueryBuilderState, _options: QueryBuilderOptions): string[];
4979
+ /**
4980
+ * Append the `_embed=` CSV from the includes array
4981
+ *
4982
+ * A bare `_embed` (no value) embeds everything; this driver always
4983
+ * emits the named form so the response stays lean.
4984
+ *
4985
+ * @param state - The current query builder state
4986
+ * @param out - The accumulator the caller joins into the URI
4987
+ */
4988
+ private _appendEmbed;
4989
+ /**
4990
+ * Append the `_fields=` CSV from the flat select array
4991
+ *
4992
+ * @param state - The current query builder state
4993
+ * @param out - The accumulator the caller joins into the URI
4994
+ */
4995
+ private _appendFields;
4996
+ /**
4997
+ * Append simple filter parameters
4998
+ *
4999
+ * A single value emits the bare form (`status=publish`); multiple
5000
+ * values fold to the CSV list convention (`categories=2,3`).
5001
+ *
5002
+ * @param state - The current query builder state
5003
+ * @param out - The accumulator the caller joins into the URI
5004
+ */
5005
+ private _appendFilters;
5006
+ /**
5007
+ * Append the `page=` / `per_page=` pagination pair
5008
+ *
5009
+ * @param state - The current query builder state
5010
+ * @param out - The accumulator the caller joins into the URI
5011
+ */
5012
+ private _appendPagination;
5013
+ /**
5014
+ * Append the `search=` parameter
5015
+ *
5016
+ * @param state - The current query builder state
5017
+ * @param out - The accumulator the caller joins into the URI
5018
+ */
5019
+ private _appendSearch;
5020
+ /**
5021
+ * Append the `orderby=` / `order=` pair from the first sort rule
5022
+ *
5023
+ * WordPress core accepts a single `orderby` value — additional sort
5024
+ * rules in state are ignored by design (the server would reject a
5025
+ * CSV here).
5026
+ *
5027
+ * @param state - The current query builder state
5028
+ * @param out - The accumulator the caller joins into the URI
5029
+ */
5030
+ private _appendSort;
5031
+ }
5032
+
5033
+ /**
5034
+ * Response strategy for the WordPress REST API driver
5035
+ *
5036
+ * WordPress returns a bare array body for collection endpoints.
5037
+ * Pagination metadata travels in HTTP response headers:
5038
+ *
5039
+ * - `X-WP-Total` — total number of records in the collection
5040
+ * - `X-WP-TotalPages` — total number of pages at the requested
5041
+ * `per_page`
5042
+ * - `Link` — RFC 5988 navigation links (`rel="next"` / `rel="prev"`)
5043
+ *
5044
+ * The strategy surfaces the `Link` URLs as `nextPageUrl` /
5045
+ * `prevPageUrl` and derives position from them:
5046
+ *
5047
+ * - `currentPage` is the `prev` link's `page` param + 1 (no `prev` →
5048
+ * page **1**), falling back to the `next` link's `page` param − 1.
5049
+ * - `perPage` is the item count of the current page whenever a `next`
5050
+ * link exists (a page with a successor is necessarily full); on the
5051
+ * last page of a multi-page set it is not introspectable and stays
5052
+ * `undefined`.
5053
+ * - `from`/`to` derive from `currentPage` × `perPage` on full pages,
5054
+ * or count back from the total on the last page
5055
+ * (`from = total - data.length + 1`, `to = total`).
5056
+ *
5057
+ * This strategy expects the consumer to pass the array body as
5058
+ * `response` (or a plain object with `response[options.data]` pointing
5059
+ * at the array) and the response headers via the optional `headers`
5060
+ * bag — the same call-site shape as the PostgREST driver. Omitted
5061
+ * headers are tolerated and yield a collection with `undefined`
5062
+ * bounds.
5063
+ *
5064
+ * @see https://developer.wordpress.org/rest-api/using-the-rest-api/pagination/
5065
+ */
5066
+ declare class WordpressResponseStrategy implements IResponseStrategy {
5067
+ private static readonly _linkHeader;
5068
+ private static readonly _linkRegex;
5069
+ private static readonly _pageParamRegex;
5070
+ private static readonly _totalHeader;
5071
+ private static readonly _totalPagesHeader;
5072
+ /**
5073
+ * Parse a WordPress REST response into a typed PaginatedCollection
5074
+ *
5075
+ * @param response - The raw response. Either the array body directly, or
5076
+ * an object with the array at `response[options.data]`.
5077
+ * @param options - The response key configuration (only `options.data` is
5078
+ * consulted; all pagination metadata comes from headers).
5079
+ * @param headers - Optional HTTP response headers. `X-WP-Total` /
5080
+ * `X-WP-TotalPages` drive the totals and the `Link` header drives
5081
+ * navigation and page derivation; omission is tolerated.
5082
+ * @returns A typed PaginatedCollection instance
5083
+ */
5084
+ paginate<T extends IPaginatedObject>(response: Record<string, unknown>, options: ResponseOptions, headers?: HeaderBag): PaginatedCollection<T>;
5085
+ /**
5086
+ * Derive the current page from the Link relations
5087
+ *
5088
+ * The `prev` URL's `page` param + 1 is authoritative; without a
5089
+ * `prev` the page is 1 unless a `next` URL contradicts it (its page
5090
+ * param − 1). A missing/unparseable Link header yields page 1.
5091
+ *
5092
+ * @param next - The `rel="next"` URL, if present
5093
+ * @param prev - The `rel="prev"` URL, if present
5094
+ * @returns The 1-indexed current page
5095
+ */
5096
+ private _deriveCurrentPage;
5097
+ /**
5098
+ * Derive `from` as the 1-indexed offset of the first item on this page
5099
+ *
5100
+ * Computed from `currentPage` × `perPage` on full pages; on the last
5101
+ * page (no `next` link) it counts back from the total
5102
+ * (`total - items + 1`), which also covers single-page responses.
5103
+ *
5104
+ * @param data - The items on the current page
5105
+ * @param currentPage - The current page number
5106
+ * @param next - The `rel="next"` URL, if present
5107
+ * @param perPage - The page size (may be undefined)
5108
+ * @param total - The total item count (may be undefined)
5109
+ * @returns The 1-indexed `from` index, or undefined when inputs insufficient
5110
+ */
5111
+ private _deriveFrom;
5112
+ /**
5113
+ * Derive `to` as the 1-indexed offset of the last item on this page
5114
+ *
5115
+ * Clamped to `total` so the last page does not report past the end;
5116
+ * on the last page (no `next` link) it is simply the total.
5117
+ *
5118
+ * @param data - The items on the current page
5119
+ * @param currentPage - The current page number
5120
+ * @param next - The `rel="next"` URL, if present
5121
+ * @param perPage - The page size (may be undefined)
5122
+ * @param total - The total item count (may be undefined)
5123
+ * @returns The 1-indexed `to` index, or undefined when inputs insufficient
5124
+ */
5125
+ private _deriveTo;
5126
+ /**
5127
+ * Extract the `page` query param from a navigation URL
5128
+ *
5129
+ * @param url - The URL to inspect (possibly undefined)
5130
+ * @returns The parsed page number, or undefined
5131
+ */
5132
+ private _pageParam;
5133
+ /**
5134
+ * Parse a non-negative integer count header value
5135
+ *
5136
+ * @param value - Raw header value (possibly null/undefined)
5137
+ * @returns The parsed count, or undefined when absent or unparseable
5138
+ */
5139
+ private _parseCount;
5140
+ /**
5141
+ * Extract the `rel="next"` / `rel="prev"` URLs from an RFC 5988
5142
+ * `Link` header value
5143
+ *
5144
+ * Tolerates any ordering, extra relations (`rel="collection"` etc.
5145
+ * are ignored), and a missing header.
5146
+ *
5147
+ * @param value - Raw header value (possibly null/undefined)
5148
+ * @returns The navigation URLs found, keyed by relation
5149
+ */
5150
+ private _parseLinkHeader;
5151
+ }
5152
+
5153
+ export { ApiPlatformRequestStrategy, ApiPlatformResponseOptions, ApiPlatformResponseStrategy, DirectusRequestStrategy, DirectusResponseOptions, DirectusResponseStrategy, DrfRequestStrategy, DrfResponseOptions, DrfResponseStrategy, DriverEnum, FeathersRequestStrategy, FeathersResponseOptions, FeathersResponseStrategy, FilterOperatorEnum, InvalidFilterOperatorValueError, InvalidLimitError, InvalidPageNumberError, InvalidResourceNameError, JsonApiRequestStrategy, JsonApiResponseOptions, JsonApiResponseStrategy, JsonServerRequestStrategy, JsonServerResponseOptions, JsonServerResponseStrategy, KeyNotFoundError, LaravelRequestStrategy, LaravelResponseStrategy, NG_QUBEE_DRIVER, NG_QUBEE_REQUEST_OPTIONS, NG_QUBEE_REQUEST_STRATEGY, NG_QUBEE_RESPONSE_OPTIONS, NG_QUBEE_RESPONSE_STRATEGY, NestjsRequestStrategy, NestjsResponseOptions, NestjsResponseStrategy, NestjsxCrudRequestStrategy, NestjsxCrudResponseOptions, NestjsxCrudResponseStrategy, NgQubeeModule, NgQubeeService, OdataRequestStrategy, OdataResponseOptions, OdataResponseStrategy, PaginatedCollection, PaginationModeEnum, PaginationNotSyncedError, PaginationService, PayloadRequestStrategy, PayloadResponseOptions, PayloadResponseStrategy, PocketbaseRequestStrategy, PocketbaseResponseOptions, PocketbaseResponseStrategy, PostgrestRequestStrategy, PostgrestResponseStrategy, QueryBuilderOptions, ResponseOptions, SieveRequestStrategy, SieveResponseOptions, SieveResponseStrategy, SortEnum, SpatieRequestStrategy, SpatieResponseStrategy, SpringRequestStrategy, SpringResponseOptions, SpringResponseStrategy, StrapiRequestStrategy, StrapiResponseOptions, StrapiResponseStrategy, UnselectableModelError, UnsupportedEmbeddedError, UnsupportedFieldSelectionError, UnsupportedFilterError, UnsupportedFilterOperatorError, UnsupportedIncludesError, UnsupportedSearchError, UnsupportedSelectError, UnsupportedSortError, WordpressRequestStrategy, WordpressResponseStrategy, buildNgQubeeProviders, provideNgQubee, provideNgQubeeInstance, readHeader };
5154
+ export type { Embedded, HeaderBag, IConfig, IFields, IFilters, INestState, IOperatorFilter, IPage, IPaginatedObject, IPaginationConfig, IQueryBuilderConfig, IQueryBuilderState, IRequestStrategy, IResponseStrategy, ISort };