@inwink-mcp/backoffice-extension-helpers 1.0.11 → 1.0.13

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.
@@ -1,16 +1,16 @@
1
- ◂ Retour à [README](./README.md)
1
+ ◂ Back to [README](./README.md)
2
2
 
3
- # inwink JSON Query Language
3
+ # inwink JSON query language
4
4
 
5
5
  The query language is inspired by MongoDB syntax. Expressions are JSON objects where `$`-prefixed keys are operators and non-prefixed keys are entity property names.
6
6
 
7
- Utilisé par les `filters`, `orders` et `selects` des opérations [query](./query.md), [update](./update.md) (massupdate) et [delete](./delete.md) (massdelete).
7
+ Used by the `filters`, `orders` and `selects` of the [query](./query.md), [update](./update.md) (massupdate), [delete](./delete.md) (massdelete) and [history](./history.md) operations, and by the `filters` of [aggregate](./aggregate.md).
8
8
 
9
- ## Predicate Expressions (filters)
9
+ ## Filters
10
10
 
11
11
  ### Comparison operators
12
12
 
13
- | Operator | Aliases | Meaning |
13
+ | Operator | Alias | Meaning |
14
14
  |---|---|---|
15
15
  | `$eq` | `$equal` | Equals |
16
16
  | `$neq` | `$notEqual` | Not equals |
@@ -18,38 +18,53 @@ Utilisé par les `filters`, `orders` et `selects` des opérations [query](./quer
18
18
  | `$gte` | `$greaterThanOrEqual` | Greater than or equal |
19
19
  | `$lt` | `$lessThan` | Less than |
20
20
  | `$lte` | `$lessThanOrEqual` | Less than or equal |
21
+ | `$in` | — | In the list: `{ "p1": { "$in": ["v1", "v2"] } }` |
21
22
 
22
23
  **Implicit equality:** `{ "p1": "v1" }` is equivalent to `{ "p1": { "$eq": "v1" } }`.
23
24
 
25
+ ⚠️ **This table is exhaustive: use only these names, exactly as written.** When unsure about a negation, compose with `$not`
26
+ (see "Logical operators") rather than guessing a name.
27
+
24
28
  ### Logical operators
25
29
 
26
30
  - `$and`: `{ "$and": [ { "p1": "v1" }, { "p2": "v2" } ] }`
27
31
  - `$or`: `{ "$or": [ { "p1": "v1" }, { "p2": "v2" } ] }`
28
32
  - `$not`: negates a predicate
29
33
 
30
- ### Text operators (under `$text` namespace)
34
+ ### Text operators (`$text` namespace)
35
+
36
+ **Predicates** — used directly as a filter:
37
+
38
+ | Operator | Example | Returns |
39
+ |---|---|---|
40
+ | `$contains` | `{ "title": { "$text": { "$contains": "val" } } }` | true if `title` contains "val" |
41
+ | `$startsWith` | `{ "title": { "$text": { "$startsWith": "val" } } }` | true if `title` starts with "val" |
42
+ | `$endsWith` | `{ "title": { "$text": { "$endsWith": "val" } } }` | true if `title` ends with "val" |
43
+
44
+ **Value expressions** — not predicates: they return a value (number or string), so they must be compared, and
45
+ fluent chaining does not apply (see "Verbose binary operator syntax"):
31
46
 
32
47
  | Operator | Example | Returns |
33
48
  |---|---|---|
34
- | `$contains` | `{ "p1": { "$text.$contains": "val" } }` | true if p1 contains "val" |
35
- | `$startsWith` | `{ "p1": { "$text.$startsWith": "val" } }` | true if p1 starts with "val" |
36
- | `$endWith` | `{ "p1": { "$text.$endWith": "val" } }` | true if p1 ends with "val" |
37
- | `$indexOf` | `{ "p1": { "$text.$indexOf": "val" } }` | position of first occurrence, -1 if none |
38
- | `$substring` | `{ "p1": { "$text.$substring": { "$startIndex": 1, "$length": 3 } } }` | substring extraction |
39
- | `$length` | `{ "p1": { "$text.$length": {} } }` | length of the string |
49
+ | `$length` | `{ "$gt": { "$left": { "title": { "$text": { "$length": {} } } }, "$right": 60 } }` | length of the string |
50
+ | `$indexOf` | `{ "$gte": { "$left": { "title": { "$text": { "$indexOf": "val" } } }, "$right": 0 } }` | position of the first occurrence, -1 if none |
51
+ | `$substring` | `{ "$eq": { "$left": { "title": { "$text": { "$substring": { "$startIndex": 1, "$length": 3 } } } }, "$right": "upl" } }` | substring |
40
52
 
41
- Alternate syntax: `{ "p1": { "$text": { "$contains": "val" } } }` is equivalent.
53
+ `$substring` takes `$startIndex` (integer, 0-based, required) and `$length` (integer, optional — up to the end
54
+ of the string if absent).
42
55
 
43
56
  ### Collection operators
44
57
 
45
- - `$any`: `{ "relateds": { "p1": "v1" } }` — true if at least one item in the collection matches.
46
- - `$all`: true if every item in the collection matches.
58
+ To filter on a **to-many** relation, the quantifier is **required**: it sits between the collection name and
59
+ the predicate.
47
60
 
48
- ### Date and time operators
61
+ - `$any`: `{ "relateds": { "$any": { "p1": "v1" } } }` — true if **at least one** item matches.
62
+ - `$all`: `{ "relateds": { "$all": { "p1": "v1" } } }` — true if **every** item matches.
63
+
64
+ ### Date/time operators
49
65
 
50
66
  | Operator | Purpose |
51
67
  |---|---|
52
- | `$now` | Current date/time |
53
68
  | `$utcNow` | Current UTC date/time |
54
69
  | `$days`, `$hours`, `$minutes`, `$seconds`, `$milliseconds`, `$ticks`, `$timespan` | Duration construction |
55
70
 
@@ -65,24 +80,90 @@ Alternate syntax: `{ "p1": { "$text": { "$contains": "val" } } }` is equivalent.
65
80
 
66
81
  **Example — sum of two properties:** `{ "p1": { "$add": { "p2": {} } } }`
67
82
 
68
- ## Value Expressions
83
+ ### Value expressions
69
84
 
70
85
  A property is referenced by nesting JSON keys terminated with `{}`:
71
86
 
72
87
  - `{ "p1": {} }` → value of `p1`
73
- - `{ "p1": { "p2": {} } }` → value of `p1.p2` (deep access)
88
+ - `{ "p1": { "p2": {} } }` → value of `p1.p2` (deep access through a **to-one** relation)
89
+
90
+ Deep access through a **to-many** relation must go through `$any` / `$all` (see "Collection operators"). And
91
+ this deep-access syntax is **not** accepted everywhere: `aggregate` restricts it further — see the "What can be
92
+ traversed" table in [aggregate.md](./aggregate.md).
74
93
 
75
- ## Verbose Binary Operator Syntax
94
+ ### Verbose binary operator syntax
76
95
 
77
- When fluent chaining doesn't work (e.g. when an operand is a terminal expression), use:
96
+ When fluent chaining does not work (e.g. an operand is a terminal expression):
78
97
 
79
98
  ```json
80
99
  { "$operator": { "$left": { ... }, "$right": { ... } } }
81
100
  ```
82
101
 
83
- ## Ordering
102
+ ### Localizable properties and `$meta`
103
+
104
+ Some properties (such as `title`, `description`) are localized objects: `{ "fr": "...", "en": "..." }`. By default, operations apply to the event's default language.
105
+
106
+ To force a specific language, wrap the expression in `$meta`:
107
+
108
+ ```json
109
+ {
110
+ "$meta": {
111
+ "lang": "en",
112
+ "$token": { "title": { "$text.$contains": "search term" } }
113
+ }
114
+ }
115
+ ```
116
+
117
+ Nested `$meta` operators are cumulative; the closest ancestor `$meta` wins when a localizable property is evaluated.
118
+
119
+ ### JSON fields (type `Object`, 95)
120
+
121
+ A field that `inwink_mcp_get_entity_description` returns with `"type": 95` stores a JSON document. Its name depends on the entity
122
+ (e.g. `configuration` on `MailTemplate`); the description gives that name, but not the structure of the document.
123
+ Two filter forms apply. Below, `<field>` is the name of the JSON field.
124
+
125
+ **1. Text anywhere in the document (default form).** It only needs the field name. `$meta` with
126
+ `"as": "string"` reads the field as its serialized JSON, to which the `$text` predicates apply:
127
+
128
+ ```json
129
+ { "$meta": { "as": "string", "$token": { "<field>": { "$text": { "$contains": "searched text" } } } } }
130
+ ```
131
+
132
+ Example on `MailTemplate`:
133
+ `{ "$meta": { "as": "string", "$token": { "configuration": { "$text": { "$contains": "isRegistered" } } } } }`.
134
+
135
+ The search covers the whole JSON text: it matches a template variable as well as a key name, an attribute value
136
+ or HTML. It is case-insensitive.
137
+
138
+ **2. Value at a known path in the document.** If you know the structure, the keys under `<field>` are the keys
139
+ of the document: descend to a scalar value (string, number, boolean), without going through an array, then
140
+ apply a comparison or a `$text` predicate. Examples on `MailTemplate`:
141
+
142
+ ```json
143
+ { "configuration": { "maildata": { "relationEntityName": "order" } } }
144
+ { "configuration": { "maildata": { "senderEmail": { "$text": { "$contains": "@example.com" } } } } }
145
+ ```
146
+
147
+ To learn the structure, read the field on a single row (`query` with `page.size` 1 and `selects` limited to that
148
+ field): a document can weigh tens of KB. If the searched value sits in an array of the document, or if its
149
+ location varies from one row to another, use form 1.
150
+
151
+ ⚠️ **Under a JSON field, a `$`-prefixed key that is not an operator from this page is read as a document
152
+ key.** The filter then runs without error and returns 0 results. Write the operators exactly as shown above.
153
+ A 0 obtained with another form does not prove the value is absent.
154
+
155
+ Related errors:
156
+
157
+ - `400 … cannot convert value of type 'JObject' to string`: a `$text` predicate was applied to the field
158
+ itself. Wrap it in `$meta` `"as": "string"` (form 1) or descend to a scalar value (form 2).
159
+ - `500` on a filter that targets a JSON field: the form used is not one of the two above. Rewrite the filter
160
+ using form 1 or 2.
84
161
 
85
- The `orders` array contains objects with `desc` (boolean) and `value` (a value expression):
162
+ ---
163
+
164
+ ## Sorting (orders)
165
+
166
+ The `orders` array holds objects with `desc` (boolean) and `value` (a value expression):
86
167
 
87
168
  ```json
88
169
  {
@@ -92,29 +173,37 @@ The `orders` array contains objects with `desc` (boolean) and `value` (a value e
92
173
  }
93
174
  ```
94
175
 
95
- ## Projection Expressions (selects)
176
+ - `value` references a field using the value expression syntax: `{ "field": {} }`.
177
+ - `desc: true` for descending order; `false` or absent for ascending.
178
+
179
+ ### Sorting on a related entity
180
+
181
+ A **to-one** relation is traversed by nesting, including on a localizable field:
96
182
 
97
183
  ```json
98
- { "$all": true, "secretField": false }
184
+ { "orders": [ { "desc": true, "value": { "member": { "lastname": {} } } } ] }
99
185
  ```
100
186
 
101
- - `true` = include, `false` = exclude.
102
- - `$all` sets the default for all properties.
103
- - Nested objects define joins/expands: `{ "sessions": { "$all": true } }`
187
+ Entities whose relation is empty come first in ascending order.
104
188
 
105
- ## Localizable Properties and `$meta`
189
+ A **to-many** relation is **not** sortable.
106
190
 
107
- Some properties (like `title`, `description`) are localized objects: `{ "fr": "...", "en": "..." }`. By default, operations apply to the event's default language.
191
+ ---
108
192
 
109
- To force a specific language, wrap the expression in `$meta`:
193
+ ## Projection (selects)
110
194
 
111
195
  ```json
112
- {
113
- "$meta": {
114
- "lang": "en",
115
- "$token": { "title": { "$text.$contains": "search term" } }
116
- }
117
- }
196
+ { "$all": true, "secretField": false }
118
197
  ```
119
198
 
120
- Nested `$meta` operators are cumulative; the closest ancestor `$meta` wins when a localizable property is evaluated.
199
+ - `true` = include, `false` = exclude.
200
+ - `$all` sets the default for all properties.
201
+ - Nested objects define joins/expansions: `{ "sessions": { "$all": true } }`.
202
+
203
+ Related entities are **never loaded automatically** — they must be requested explicitly in `selects`.
204
+
205
+ ### Null values
206
+
207
+ A field whose value is null can be **left out of the row**, even when it is selected by name; other null fields come back as `null`. Both mean null. An absent field is not the sign of a wrong name: a name that does not exist on the entity fails the whole request with `400 … property resolution error`. A field selected by name, with no error, and absent from the row is therefore null.
208
+
209
+ To list the rows where a field is null, filter with `{ "<field>": null }`.
@@ -1,10 +1,10 @@
1
- ◂ Retour à [README](./README.md)
1
+ ◂ Back to [README](./README.md)
2
2
 
3
3
  # Query & Count (read entities)
4
4
 
5
- Pour la syntaxe des `filters`, `orders` et `selects`, voir [query-language.md](./query-language.md).
5
+ For the `filters`, `orders` and `selects` syntax, see [query-language.md](./query-language.md).
6
6
 
7
- ## Query (read entities)
7
+ ## Query
8
8
 
9
9
  ```
10
10
  POST {api}/{shardId}/{entity}/query
@@ -22,19 +22,19 @@ POST {api}/{shardId}/{entity}/query
22
22
  ```
23
23
 
24
24
  - `page` — **required**. `index` starts at 0, `size` between 1 and 300 (recommended: 50).
25
- - `filters` — optional. A predicate expression (see "Query Language" section below).
26
- - `orders` — optional. An array of order clauses (see "Ordering" below). If omitted, the API sorts by `id` when available.
27
- - `selects` — optional. A projection expression (see "Projection" below). Defaults to `{ "$all": true }`.
25
+ - `filters` — optional. A predicate (see [query-language.md](./query-language.md#filters)).
26
+ - `orders` — optional. Array of sort clauses (see [query-language.md](./query-language.md#sorting-orders)). If omitted, the API sorts by `id` when possible.
27
+ - `selects` — optional. A projection (see [query-language.md](./query-language.md#projection-selects)). Default: `{ "$all": true }`.
28
28
 
29
29
  **Response:** `{ "data": [ ...entities ] }`
30
30
 
31
31
  ### Pagination
32
32
 
33
- The API uses **stateless offset-based pagination** (no cursor). To fetch all records, increment `page.index` until `data.length < page.size`. Because pagination is stateless, **duplicates can occur across pages** — always deduplicate results by `id`. Missing records can also happen if the sort property is non-unique or mutable; omitting `orders` lets the API pick the safest sort.
33
+ **Stateless offset-based** pagination (no cursor). To fetch everything, increment `page.index` until `data.length < page.size`. Because pagination is stateless, **duplicates can appear across pages** — always deduplicate by `id` (`useInwinkPagedQuery` does it). Records can also be missed if the sort property is non-unique or mutable; omitting `orders` lets the API pick the safest sort.
34
34
 
35
35
  ### Selecting specific fields
36
36
 
37
- Return only specific properties:
37
+ Example on the EventAPI `person` entity — field names are specific to each entity and do not carry over from one API to another.
38
38
 
39
39
  ```json
40
40
  {
@@ -55,7 +55,7 @@ Return only specific properties:
55
55
  }
56
56
  ```
57
57
 
58
- Related entities are **never loaded automatically** — you must explicitly request them in `selects`.
58
+ Related entities are **never loaded automatically** — they must be requested explicitly in `selects`.
59
59
 
60
60
  ---
61
61
 
@@ -65,4 +65,6 @@ Related entities are **never loaded automatically** — you must explicitly requ
65
65
  POST {api}/{shardId}/{entity}/count
66
66
  ```
67
67
 
68
- Same body as query (supports `filters` and `page`) but without `selects`. Returns a **single integer**.
68
+ Same body as query (supports `filters` and `page`) but **without** `selects`. Returns a **single integer**.
69
+
70
+ Best for "how many …" questions. For a breakdown (GROUP BY), use [aggregate.md](./aggregate.md) rather than counting page by page.
@@ -1,4 +1,4 @@
1
- ◂ Retour à [README](./README.md)
1
+ ◂ Back to [README](./README.md)
2
2
 
3
3
  # Edit & Massupdate
4
4
 
@@ -17,9 +17,11 @@ POST {api}/{shardId}/{entity}/{entityId}
17
17
  }
18
18
  ```
19
19
 
20
- **Important:** This is a **PATCH**, not a replace. Only properties present in the payload are updated. To clear a property, explicitly set it to `null`.
20
+ **Important:** this is a **PATCH** in meaning, sent with `POST` — not a replacement. Only the properties present in the payload are updated. To clear a property, set it explicitly to `null`.
21
21
 
22
- `selects` contrôle les propriétés retournées (voir [query-language.md](./query-language.md)).
22
+ `selects` controls the returned properties (see [query-language.md](./query-language.md#projection-selects)).
23
+
24
+ > Changing the **definition** of an entity's fields (adding a custom field, for instance) is not an entity edit: see [entity-template.md](./entity-template.md).
23
25
 
24
26
  ---
25
27
 
@@ -29,6 +31,6 @@ POST {api}/{shardId}/{entity}/{entityId}
29
31
  POST {api}/{shardId}/{entity}/massupdate
30
32
  ```
31
33
 
32
- Bulk EDIT like `UPDATE … WHERE` in SQL. S'appuie sur un filtre (voir [query-language.md](./query-language.md)).
34
+ Bulk EDIT, like `UPDATE … WHERE` in SQL. Relies on a filter (see [query-language.md](./query-language.md#filters)).
33
35
 
34
- > Pour mettre à jour un lot d'entités identifiées une à une, voir aussi [import.md](./import.md).
36
+ > To update a batch of entities identified one by one, see also [import.md](./import.md).