@dcupl/common 2.0.0-beta.20 → 2.0.0-beta.22

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.
@@ -69,7 +69,8 @@ export declare class DcuplQueryBuilder {
69
69
  * - {@link replaceQuery} - Replace complete query
70
70
  *
71
71
  * @param queryToApply - Query, condition, or array to apply
72
- * @param options - Mode: 'add' (default) or 'set' (replace)
72
+ * @param options - Mode: 'add' (default) or 'set' (replace). In 'add' mode a group
73
+ * without `groupKey` is always appended as a new group, never merged into another.
73
74
  * @param relevantQuery - Target query (defaults to current)
74
75
  * @returns Updated query
75
76
  */
@@ -187,7 +188,10 @@ export declare class DcuplQueryBuilder {
187
188
  /**
188
189
  * Add query condition(s) to the current query.
189
190
  * Accepts single condition or array of conditions.
190
- * Each condition is added to its respective attribute group.
191
+ * Each condition is added to its respective attribute group (the group whose
192
+ * groupKey equals the condition's attribute; created as an `'or'` group if missing).
193
+ * To combine conditions in an unkeyed group, use {@link addGroup} — unkeyed groups
194
+ * are always appended as new groups and never merged.
191
195
  *
192
196
  * Clearer alternative to `applyQuery(condition, { mode: 'add' })`.
193
197
  *
@@ -209,7 +213,13 @@ export declare class DcuplQueryBuilder {
209
213
  addCondition(condition: DcuplQuery | DcuplQuery[]): DcuplGlobalQueryOptions<any>;
210
214
  /**
211
215
  * Add a query group to the current query.
212
- * If a group with the same groupKey exists, extends it; otherwise creates new group.
216
+ * - Keyed group (`groupKey` set): if a group with the same groupKey exists, its queries
217
+ * are extended and its groupType is replaced by the given one (if set); otherwise a new
218
+ * group is created.
219
+ * - Unkeyed group (no `groupKey`): always appended as a new group; it is never merged
220
+ * into another group, so each unkeyed group keeps its own groupType.
221
+ *
222
+ * A newly created group without `groupType` is stored as `'or'`.
213
223
  *
214
224
  * @param group - Query group to add
215
225
  * @returns Updated query
@@ -12,9 +12,59 @@ export declare class QueryManager {
12
12
  registerCustomOperator(operator: string, fn: CustomOperatorFn): void;
13
13
  transformValue(value: any, transformers: DcuplQueryTransformOptions[]): any;
14
14
  private evaluateQuery;
15
+ /** A missing value never compares — in JS `null >= 0` is true. */
16
+ private compareValue;
17
+ /**
18
+ * The value a condition compares against. A dotted path that runs through a
19
+ * list (a multi-valued reference, e.g. `memberships.role`) fans out into the
20
+ * list of leaf values, the same values the attribute's inverse index holds —
21
+ * `lodash.get` cannot step into an array and would answer `undefined`. An
22
+ * index (`tags.0`) or `length` still reads the list itself, as with `get`.
23
+ */
24
+ private getAttributeValue;
25
+ /**
26
+ * Applies `predicate` to a single value, or to each value of a list per
27
+ * `arrayValueHandling`. `every` needs at least one value: an empty list
28
+ * matches nothing (and so matches under `invert`).
29
+ */
30
+ private matchValues;
31
+ /**
32
+ * `eq` against a list compares each value, as the inverse index does; a
33
+ * query value that is itself a list still compares the whole list.
34
+ */
35
+ private queryEq;
36
+ /**
37
+ * `in`: a value matches if it equals (as `eq` compares) any listed value; a
38
+ * list of values matches per `arrayValueHandling`. An empty list matches nothing.
39
+ */
40
+ private queryIn;
41
+ /**
42
+ * The inverse-index key a query value looks up: a string, or the key of a
43
+ * `{ key }` object. Anything else has no key and is compared by a scan.
44
+ */
45
+ private getIndexKey;
46
+ /**
47
+ * Equality the way the inverse index keys values: a string (or `{ key }`)
48
+ * query value matches a reference by its key and a date by its ISO string.
49
+ */
50
+ private equalsQueryValue;
51
+ /**
52
+ * An inverse index answers `eq` as "some value equals", untransformed — only
53
+ * use it when that is the question.
54
+ */
55
+ private canUseIndex;
15
56
  private evaluateEqOperator;
57
+ /**
58
+ * `in` from the inverse index when it can answer: the union of the index
59
+ * sets of the listed values. A value the index cannot look up (not a string
60
+ * or `{ key }`, or not in the index) is answered by a scan of the rest, so
61
+ * the result equals the scan's.
62
+ */
63
+ private evaluateInOperator;
64
+ /** Evaluates `query` on each item, resolving a deep path's references first. */
65
+ private scanDataset;
16
66
  private equalRegexFn;
17
- queryFind(entryValue: any | any[], queryValue: any, arrayValueHandling: 'some' | 'every'): boolean;
67
+ queryFind(entryValue: any | any[], queryValue: any, arrayValueHandling: 'some' | 'every', regexFlags?: string): boolean;
18
68
  private validateQuery;
19
69
  private getEvaluatedQueryDataset;
20
70
  private handleArrayStarQuery;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `url` with the values of secret query parameters replaced by `<redacted>`.
3
+ *
4
+ * For error messages and log lines only — the request itself keeps the real values. The app
5
+ * loader appends the project `api-key` to every dcupl CDN request, and a failed fetch used to
6
+ * carry that URL verbatim into the log output and the quality error. Works on the raw string so
7
+ * nothing else is re-encoded or reordered. Same parameter set as console-api's `redactUrl`.
8
+ */
9
+ export declare function redactUrl(url: string): string;
@@ -11,6 +11,14 @@ export type DcuplFacetOptions = {
11
11
  * When omitted the fast (insertion-order) path is preserved.
12
12
  */
13
13
  sort?: 'size-desc' | 'size-asc' | 'value-asc' | 'value-desc';
14
+ /**
15
+ * Key of the query group to leave out when counting (disjunctive facets):
16
+ * each facet value is counted under every other group of the current query,
17
+ * so selecting one value does not collapse its siblings.
18
+ * Defaults to the group keyed by `attribute`. A key that matches no group
19
+ * leaves nothing out. `selected` reflects this group's conditions on `attribute`.
20
+ */
21
+ excludeGroup?: string;
14
22
  };
15
23
  export type DcuplFacet = {
16
24
  key: string;
@@ -38,6 +38,11 @@ export type DcuplFilterOptions = CatalogApiOptions & {
38
38
  * last. `value-desc` keeps `undefined` last as well.
39
39
  */
40
40
  sort?: 'size-desc' | 'size-asc' | 'value-asc' | 'value-desc';
41
+ /**
42
+ * Key of the query group left out when counting facet entries.
43
+ * Defaults to the group keyed by the faceted attribute. See `DcuplFacetOptions.excludeGroup`.
44
+ */
45
+ excludeGroup?: string;
41
46
  };
42
47
  export type DcuplFiltersOptions = DcuplFilterOptions & {
43
48
  filterKeys?: string[];
@@ -7,12 +7,13 @@ import { Projection, SortingProjection } from './projection.types.js';
7
7
  * - `removeWhitespace`: Remove all whitespace characters
8
8
  */
9
9
  export type DcuplQueryTransformOptions = 'lowercase' | 'trim' | 'removeWhitespace';
10
- export declare const DcuplQueryOperatorsAsArray: readonly ["eq", "find", "gt", "gte", "lt", "lte", "typeof", "isTruthy", "size"];
10
+ export declare const DcuplQueryOperatorsAsArray: readonly ["eq", "in", "find", "gt", "gte", "lt", "lte", "typeof", "isTruthy", "size"];
11
11
  /**
12
12
  * Query operators for filtering data.
13
13
  *
14
14
  * Available operators:
15
15
  * - `eq`: Equality check - matches exact values
16
+ * - `in`: Membership check - matches if the value equals any value of a list (`eq`'s equality)
16
17
  * - `find`: Regex / exact / object match - bare string is exact-equality, `/regex/` for substring/pattern, object for property match
17
18
  * - `gt`: Greater than - numeric/date comparison (exclusive)
18
19
  * - `gte`: Greater than or equal - numeric/date comparison (inclusive)
@@ -27,6 +28,9 @@ export declare const DcuplQueryOperatorsAsArray: readonly ["eq", "find", "gt", "
27
28
  * // Equality
28
29
  * { operator: 'eq', attribute: 'status', value: 'active' }
29
30
  *
31
+ * // Any of several values
32
+ * { operator: 'in', attribute: 'status', value: ['active', 'pending'] }
33
+ *
30
34
  * // Numeric comparison
31
35
  * { operator: 'gte', attribute: 'price', value: 100 }
32
36
  *
@@ -79,7 +83,13 @@ type DcuplQueryBase = {
79
83
  */
80
84
  transform?: DcuplQueryTransformOptions[];
81
85
  /**
82
- * Invert the query result (NOT operation).
86
+ * Invert the query result (NOT operation). Applies to every built-in operator.
87
+ * A missing (`undefined`/`null`) value matches neither way, except for `eq`,
88
+ * `in`, `typeof` and `isTruthy`, which compare against it. A list of values (an
89
+ * `Array<…>` property or a dotted path through a multi-valued reference) is
90
+ * checked per value instead: an empty list, or a `null` in it, simply does not
91
+ * match, so `invert` does match it (`memberships.user.daysInactive gte 60`
92
+ * inverted includes items with no memberships).
83
93
  * @example
84
94
  * ```typescript
85
95
  * // Find items where status is NOT 'active'
@@ -88,9 +98,14 @@ type DcuplQueryBase = {
88
98
  */
89
99
  invert?: boolean;
90
100
  /**
91
- * When querying array values, determines match behavior.
101
+ * When querying array values, determines match behavior. Array values are
102
+ * `Array<…>` properties and dotted paths through a multi-valued reference
103
+ * (`memberships.role` compares the `role` of every membership).
92
104
  * - `some`: Match if at least one array element matches (default)
93
- * - `every`: Match only if all array elements match
105
+ * - `every`: Match only if all array elements match — and there is at least
106
+ * one: an empty array (or a reference with no items) never matches
107
+ *
108
+ * On a reference path, leaves that are `undefined` are skipped.
94
109
  *
95
110
  * @example
96
111
  * ```typescript
@@ -127,14 +142,58 @@ type DcuplQueryEq = DcuplQueryBase & {
127
142
  */
128
143
  value: any;
129
144
  };
145
+ /**
146
+ * Membership query - matches if the attribute's value equals **any** value of
147
+ * the list, using the same equality as `eq`: a string (or `{ key }`) matches a
148
+ * reference by its key and a date by its ISO string. It answers like an `or`
149
+ * group of one `eq` condition per listed value.
150
+ *
151
+ * - On an `Array<…>` property or a dotted path through a multi-valued
152
+ * reference, `arrayValueHandling: 'some'` (default) matches if at least one
153
+ * value is listed; `'every'` if every value is listed (and there is at least one).
154
+ * - `options.invert` is NOT IN; like `eq`, a missing value then matches.
155
+ * - `options.transform` applies to the attribute value and to every listed value.
156
+ * - `null` in the list matches `null`, like `eq` with `null`.
157
+ * - An empty list matches nothing (and everything under `invert`).
158
+ * - `value` must be an array; anything else throws. (`eq` with an array value
159
+ * compares the whole array instead.)
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * // Items whose status is 'active' or 'pending'
164
+ * { operator: 'in', attribute: 'status', value: ['active', 'pending'] }
165
+ *
166
+ * // Through references (2 hops)
167
+ * { operator: 'in', attribute: 'customer.address.city', value: ['Vienna', 'Graz'] }
168
+ *
169
+ * // Reference keys, as strings or { key }
170
+ * { operator: 'in', attribute: 'category', value: ['cat-1', { key: 'cat-2' }] }
171
+ *
172
+ * // Every tag is one of the listed ones
173
+ * { operator: 'in', attribute: 'tags', value: ['sale', 'new'], options: { arrayValueHandling: 'every' } }
174
+ *
175
+ * // NOT IN
176
+ * { operator: 'in', attribute: 'status', value: ['archived', 'deleted'], options: { invert: true } }
177
+ * ```
178
+ */
179
+ type DcuplQueryIn = DcuplQueryBase & {
180
+ operator: 'in';
181
+ /**
182
+ * The values to match; any one of them matching is enough.
183
+ * Note: For indexed search optimization, use string or { key: string } values.
184
+ */
185
+ value: unknown[];
186
+ };
130
187
  /**
131
188
  * Find query - regex / exact / object match.
132
189
  *
133
190
  * Matching depends on the shape of `value`:
134
191
  * - **Bare string** (e.g. `'search'`) — exact-equality against the *whole* field
135
192
  * value, NOT a substring. `find` with `'Ball'` does not match `'Soccer Ball'`.
136
- * - **Slash-delimited regex** (e.g. `'/search/'`, `'/^foo/i'`) — JS regex literal;
137
- * use this for substring and pattern matching.
193
+ * - **Slash-delimited regex** (e.g. `'/search/'`, `'/^foo/'`) — the text between the
194
+ * slashes is a JS regex; use this for substring and pattern matching. Flags go in
195
+ * `options.regexFlags` (`'/^foo/'` + `regexFlags: 'i'`), not after the closing
196
+ * slash: `'/^foo/i'` is a bare string and matches exactly.
138
197
  * - **Object** (e.g. `{ key: 'value' }`) — property matching against the field.
139
198
  *
140
199
  * @example
@@ -142,6 +201,9 @@ type DcuplQueryEq = DcuplQueryBase & {
142
201
  * // Substring search — slash-delimited regex (bare string would be exact-match)
143
202
  * { operator: 'find', attribute: 'description', value: '/search/' }
144
203
  *
204
+ * // Case-insensitive substring search
205
+ * { operator: 'find', attribute: 'description', value: '/search/', options: { regexFlags: 'i' } }
206
+ *
145
207
  * // Exact-match the whole field value
146
208
  * { operator: 'find', attribute: 'status', value: 'active' }
147
209
  *
@@ -155,6 +217,13 @@ type DcuplQueryFind = DcuplQueryBase & {
155
217
  * Bare string (exact-match), `/regex/` (substring/pattern), or object (property match).
156
218
  */
157
219
  value: Record<string, unknown> | string;
220
+ options?: {
221
+ /**
222
+ * Flags for a `/regex/` value, e.g. `'i'` for case-insensitive. Ignored for any
223
+ * other value; invalid flags match nothing.
224
+ */
225
+ regexFlags?: string;
226
+ };
158
227
  };
159
228
  /**
160
229
  * Numeric/date comparison queries.
@@ -260,7 +329,7 @@ type DcuplQuerySize = DcuplQueryBase & {
260
329
  * };
261
330
  * ```
262
331
  */
263
- export type DcuplQuery = DcuplQueryEq | DcuplQueryNumbers | DcuplQueryTypeOf | DcuplQueryIsTruthy | DcuplQuerySize | DcuplQueryFind;
332
+ export type DcuplQuery = DcuplQueryEq | DcuplQueryIn | DcuplQueryNumbers | DcuplQueryTypeOf | DcuplQueryIsTruthy | DcuplQuerySize | DcuplQueryFind;
264
333
  /**
265
334
  * Query group - combines multiple conditions with AND/OR logic.
266
335
  * Groups can be nested to create complex filter expressions.
@@ -311,13 +380,24 @@ export type DcuplQueryGroup<ChildQuery extends DcuplQuery | DcuplQueryGroup = an
311
380
  */
312
381
  __type?: 'group';
313
382
  /**
314
- * Combination logic for conditions in this group.
315
- * Defaults to 'and' if not specified.
383
+ * Combination logic for conditions in this group. Set it explicitly; when omitted,
384
+ * the effective behaviour depends on where the group ends up:
385
+ * - Groups created by the query builder (`addGroup` / `setGroup` / `apply(group)` for a
386
+ * group not yet present) are stored as `'or'`. When `addGroup` extends an existing keyed
387
+ * group, that group keeps its current groupType.
388
+ * - The root query (`groupKey: 'root'`) is evaluated as `'and'`.
389
+ * - A nested group that reaches evaluation without a groupType (e.g. passed inside a full
390
+ * query via `apply({ groupKey: 'root', ... })`, `init` or `execute`, or stored by
391
+ * `setGroup` replacing an existing group) is intersected like `'and'`, with one
392
+ * difference: if the running intersection becomes empty, the matches of the next
393
+ * condition replace it instead of the result staying empty.
316
394
  */
317
395
  groupType?: DcuplQueryType;
318
396
  /**
319
397
  * Optional identifier for this group.
320
- * Useful for tracking or removing specific query groups.
398
+ * Useful for tracking or removing specific query groups. Adding a group with an
399
+ * existing groupKey extends that group; a group without groupKey is always added
400
+ * as a new group.
321
401
  */
322
402
  groupKey?: string;
323
403
  /**
@@ -412,7 +492,10 @@ export type DcuplGlobalManyItemOptions<T> = {
412
492
  };
413
493
  /**
414
494
  * Options for executing queries on a list instance.
415
- * Combines query conditions with data retrieval options.
495
+ * Combines optional query conditions with data retrieval options.
496
+ *
497
+ * `queries` is optional: without it the list's own (live) query is used, so
498
+ * item options alone (`projection`, `start`, `count`, `sort`) are valid.
416
499
  *
417
500
  * @see ListQueryOptions - Type alias with clearer naming
418
501
  *
@@ -426,9 +509,12 @@ export type DcuplGlobalManyItemOptions<T> = {
426
509
  * count: 10
427
510
  * };
428
511
  * const results = list.catalog.query.execute(options);
512
+ *
513
+ * // The list's own query, projected
514
+ * const projected = list.catalog.query.execute({ projection: { $: false, name: true } });
429
515
  * ```
430
516
  */
431
- export type DcuplListQueryOptions<T> = DcuplItemOptions<T> & DcuplQueryGroup;
517
+ export type DcuplListQueryOptions<T> = DcuplItemOptions<T> & Partial<DcuplQueryGroup>;
432
518
  /**
433
519
  * Options for retrieving a specific item from a list instance.
434
520
  */
@@ -7,6 +7,11 @@ type SuggestionOptionsBase = {
7
7
  excludeUndefineds?: boolean;
8
8
  excludeNulls?: boolean;
9
9
  transform?: DcuplQueryTransformOptions[];
10
+ /**
11
+ * Flags for a `/pattern/` value, e.g. `'i'` for case-insensitive. Ignored for any
12
+ * other value; invalid flags suggest nothing.
13
+ */
14
+ regexFlags?: string;
10
15
  };
11
16
  type SuggestionOptionsFiltered = SuggestionOptionsBase & {
12
17
  relevantData?: 'filtered';