@gmag11/nodered-mcp-server 1.0.1

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.
Files changed (89) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +162 -0
  3. package/index.js +133 -0
  4. package/package.json +58 -0
  5. package/resources/skills/nodered-flow-builder/SKILL.md +659 -0
  6. package/resources/skills/nodered-flow-layout/SKILL.md +395 -0
  7. package/resources/skills/nodered-flowfuse-dashboard/SKILL.md +941 -0
  8. package/resources/skills/nodered-fundamentals/SKILL.md +323 -0
  9. package/resources/skills/nodered-jsonata/SKILL.md +1039 -0
  10. package/resources/skills/nodered-mustache/SKILL.md +588 -0
  11. package/resources/skills/nodered-node-reference/SKILL.md +1020 -0
  12. package/resources/skills/nodered-node-reference/examples/common.json +113 -0
  13. package/resources/skills/nodered-node-reference/examples/network.json +107 -0
  14. package/resources/skills/nodered-node-reference/examples/parser.json +147 -0
  15. package/resources/skills/nodered-node-reference/examples/sequence.json +141 -0
  16. package/resources/skills/nodered-node-reference/examples/storage.json +104 -0
  17. package/resources/skills/nodered-patterns/SKILL.md +414 -0
  18. package/resources/skills/nodered-patterns/examples/error-handler.json +72 -0
  19. package/resources/skills/nodered-patterns/examples/http-endpoint.json +42 -0
  20. package/resources/skills/nodered-patterns/examples/mqtt-subscriber.json +47 -0
  21. package/resources/skills/nodered-patterns/examples/timer-flow.json +50 -0
  22. package/resources/skills/nodered-subflows/SKILL.md +261 -0
  23. package/resources/skills/nodered-uibuilder/SKILL.md +500 -0
  24. package/src/auth/api-key-verifier.js +36 -0
  25. package/src/auth/composite-verifier.js +59 -0
  26. package/src/auth/config.js +106 -0
  27. package/src/auth/oauth-clients-store.js +107 -0
  28. package/src/auth/oauth-provider.js +149 -0
  29. package/src/auth/oauth-token-store.js +312 -0
  30. package/src/nodered/auth.js +158 -0
  31. package/src/nodered/client.js +199 -0
  32. package/src/nodered/comms-client.js +500 -0
  33. package/src/renderer/colors.js +161 -0
  34. package/src/renderer/geometry.js +115 -0
  35. package/src/renderer/html-builder.js +571 -0
  36. package/src/renderer/index.js +51 -0
  37. package/src/renderer/ir-builder.js +161 -0
  38. package/src/renderer/layout.js +126 -0
  39. package/src/renderer/mermaid-builder.js +109 -0
  40. package/src/renderer/svg-builder.js +228 -0
  41. package/src/schemas/responses.js +283 -0
  42. package/src/server.js +844 -0
  43. package/src/skills/loader.js +84 -0
  44. package/src/staging-store.js +258 -0
  45. package/src/tools/add-nodes-to-group.js +216 -0
  46. package/src/tools/connect-nodes.js +115 -0
  47. package/src/tools/constants.js +45 -0
  48. package/src/tools/create-flow.js +87 -0
  49. package/src/tools/create-node.js +126 -0
  50. package/src/tools/create-subflow-instance.js +123 -0
  51. package/src/tools/create-subflow.js +101 -0
  52. package/src/tools/delete-context.js +60 -0
  53. package/src/tools/delete-flow.js +81 -0
  54. package/src/tools/delete-group.js +116 -0
  55. package/src/tools/delete-node.js +73 -0
  56. package/src/tools/delete-subflow.js +103 -0
  57. package/src/tools/deploy.js +94 -0
  58. package/src/tools/disconnect-nodes.js +158 -0
  59. package/src/tools/export-flow.js +161 -0
  60. package/src/tools/export-subflow.js +78 -0
  61. package/src/tools/flow-utils.js +376 -0
  62. package/src/tools/get-config-nodes.js +86 -0
  63. package/src/tools/get-context.js +76 -0
  64. package/src/tools/get-flow-diagram.js +99 -0
  65. package/src/tools/get-flow-nodes.js +116 -0
  66. package/src/tools/get-flows.js +74 -0
  67. package/src/tools/get-node-detail.js +77 -0
  68. package/src/tools/get-node-type-detail.js +92 -0
  69. package/src/tools/get-palette-nodes.js +63 -0
  70. package/src/tools/get-staging-status.js +34 -0
  71. package/src/tools/get-subflow-detail.js +110 -0
  72. package/src/tools/get-subflows.js +105 -0
  73. package/src/tools/import-flow.js +310 -0
  74. package/src/tools/inject-message.js +117 -0
  75. package/src/tools/install-node.js +31 -0
  76. package/src/tools/read-debug-messages.js +155 -0
  77. package/src/tools/refresh-staging.js +62 -0
  78. package/src/tools/remove-nodes-from-group.js +162 -0
  79. package/src/tools/render-staging.js +69 -0
  80. package/src/tools/response-utils.js +42 -0
  81. package/src/tools/search-nodes.js +134 -0
  82. package/src/tools/uninstall-node.js +31 -0
  83. package/src/tools/update-flow.js +95 -0
  84. package/src/tools/update-group.js +77 -0
  85. package/src/tools/update-node.js +132 -0
  86. package/src/tools/update-subflow.js +84 -0
  87. package/src/transport/http.js +252 -0
  88. package/src/transport/stdio.js +16 -0
  89. package/src/transport/ws-server.js +223 -0
@@ -0,0 +1,1039 @@
1
+ ---
2
+ name: nodered-jsonata
3
+ description: >-
4
+ Comprehensive reference for the JSONata expression language (v2.2.0).
5
+ Covers syntax, path navigation, predicates, operators, all built-in function
6
+ categories (string, numeric, aggregation, boolean, array, object, date/time,
7
+ higher-order), programming constructs (variables, functions, recursion),
8
+ the processing model (sequences, flattening), and the JavaScript embedding API.
9
+ Includes Node-RED-specific usage notes for change, switch, and inject nodes.
10
+ ---
11
+
12
+ # JSONata Expression Language Reference
13
+
14
+ Comprehensive reference for JSONata — a lightweight query and transformation language for JSON data. JSONata is **Node-RED's native expression language**, used extensively in the "change" node, "switch" node, and wherever JSONata expressions are evaluated against `msg` objects.
15
+
16
+ JSONata is inspired by XPath 3.1 location path semantics. It is a **superset of JSON** — any valid JSON document is also a valid JSONata expression. This property enables using JSON as a template for output and replacing parts with expressions.
17
+
18
+ > **Version:** JSONata 2.2.0 (the version bundled with current Node-RED releases)
19
+ > **Try it:** [try.jsonata.org](http://try.jsonata.org/)
20
+ > **Docs:** [docs.jsonata.org](https://docs.jsonata.org/)
21
+
22
+ ---
23
+
24
+ ## Sample JSON Document
25
+
26
+ The following JSON document is used in examples throughout this reference:
27
+
28
+ ```json
29
+ {
30
+ "FirstName": "Fred",
31
+ "Surname": "Smith",
32
+ "Age": 28,
33
+ "Address": {
34
+ "Street": "Hursley Park",
35
+ "City": "Winchester",
36
+ "Postcode": "SO21 2JN"
37
+ },
38
+ "Phone": [
39
+ { "type": "home", "number": "0203 544 1234" },
40
+ { "type": "office", "number": "01962 001234" },
41
+ { "type": "office", "number": "01962 001235" },
42
+ { "type": "mobile", "number": "077 7700 1234" }
43
+ ],
44
+ "Email": [
45
+ { "type": "work", "address": ["fred.smith@my-work.com", "fsmith@my-work.com"] },
46
+ { "type": "home", "address": ["freddy@my-social.com", "frederic.smith@very-serious.com"] }
47
+ ],
48
+ "Other": {
49
+ "Over 18 ?": true,
50
+ "Misc": null,
51
+ "Alternative.Address": {
52
+ "Street": "Brick Lane",
53
+ "City": "London",
54
+ "Postcode": "E1 6RF"
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Path Navigation
63
+
64
+ ### Dot Notation — Object Fields
65
+
66
+ Navigate into nested objects using dot `.` separators. If a field is not found, the expression returns nothing (JavaScript `undefined`) — **no errors are thrown**.
67
+
68
+ | Expression | Result |
69
+ |---|---|
70
+ | `Surname` | `"Smith"` |
71
+ | `Age` | `28` |
72
+ | `Address.City` | `"Winchester"` |
73
+ | `Other.Misc` | `null` |
74
+ | `Other.Nothing` | _(nothing)_ |
75
+
76
+ Fields containing whitespace or reserved tokens must be enclosed in backticks:
77
+
78
+ | Expression | Result |
79
+ |---|---|
80
+ | `` Other.`Over 18 ?` `` | `true` |
81
+ | `` Other.`Alternative.Address`.City `` | `"London"` |
82
+
83
+ ### Bracket Notation — Array Indexing
84
+
85
+ Use `[index]` to access array elements. Indexes are **zero-offset**. Negative indexes count from the end of the array. Non-integer indexes are rounded down. If an index exceeds the array size, nothing is selected.
86
+
87
+ | Expression | Result |
88
+ |---|---|
89
+ | `Phone[0]` | `{ "type": "home", "number": "0203 544 1234" }` |
90
+ | `Phone[1]` | `{ "type": "office", "number": "01962 001234" }` |
91
+ | `Phone[-1]` | `{ "type": "mobile", "number": "077 7700 1234" }` |
92
+ | `Phone[-2]` | `{ "type": "office", "number": "01962 001235" }` |
93
+ | `Phone[8]` | _(nothing)_ |
94
+
95
+ **Array of indexes** selects a range:
96
+
97
+ | Expression | Result |
98
+ |---|---|
99
+ | `Phone[[0..1]]` | First two phone objects |
100
+
101
+ > **Important:** `Phone.number` selects the `number` field from ALL items → `["0203 544 1234", "01962 001234", ...]`. Use `(Phone.number)[0]` to get just the first number.
102
+
103
+ ### Wildcards
104
+
105
+ | Symbol | Meaning |
106
+ |---|---|
107
+ | `*` | Selects all values of all properties in the context object |
108
+ | `**` | Descendant wildcard — recursively traverses all levels |
109
+
110
+ | Expression | Result |
111
+ |---|---|
112
+ | `Address.*` | `["Hursley Park", "Winchester", "SO21 2JN"]` |
113
+ | `*.Postcode` | `"SO21 2JN"` |
114
+ | `**.Postcode` | `["SO21 2JN", "E1 6RF"]` |
115
+
116
+ ### Context and Root References
117
+
118
+ | Symbol | Meaning |
119
+ |---|---|
120
+ | `$` | The current context value at any point in the input hierarchy |
121
+ | `$$` | The root of the input JSON (breaks out of current context) |
122
+
123
+ | Expression | Result |
124
+ |---|---|
125
+ | `$[0]` | First item in top-level array (when input is an array) |
126
+ | `$$.FirstName` | `"Fred"` (access root from anywhere) |
127
+
128
+ ---
129
+
130
+ ## Predicates
131
+
132
+ Filter selected items using `[expr]` where `expr` evaluates to a Boolean. The expression is evaluated relative to the current context item.
133
+
134
+ | Expression | Result |
135
+ |---|---|
136
+ | `Phone[type='mobile']` | The mobile phone object |
137
+ | `Phone[type='mobile'].number` | `"077 7700 1234"` |
138
+ | `Phone[type='office'].number` | `["01962 001234", "01962 001235"]` |
139
+
140
+ **Combining predicates with Boolean operators:**
141
+
142
+ | Expression | Meaning |
143
+ |---|---|
144
+ | `Phone[type='home' or type='mobile'].number` | Home and mobile numbers |
145
+
146
+ ### Singleton Array and Value Equivalence
147
+
148
+ Any non-array value and an array containing just that value are treated as equivalent. To **force an array result**, add `[]` at any step in the path:
149
+
150
+ | Expression | Result |
151
+ |---|---|
152
+ | `Address.City` | `"Winchester"` (single value) |
153
+ | `Address[].City` | `["Winchester"]` (always an array) |
154
+ | `Phone[][type='home'].number` | `["0203 544 1234"]` |
155
+
156
+ ---
157
+
158
+ ## Operators
159
+
160
+ ### Numeric Operators
161
+
162
+ | Operator | Description | Example | Result |
163
+ |---|---|---|---|
164
+ | `+` | Addition | `5 + 2` | `7` |
165
+ | `-` | Subtraction / Negation | `5 - 2` / `-42` | `3` / `-42` |
166
+ | `*` | Multiplication | `5 * 2` | `10` |
167
+ | `/` | Division | `5 / 2` | `2.5` |
168
+ | `%` | Modulo (remainder) | `5 % 2` | `1` |
169
+ | `..` | Range generator (inside `[]`) | `[1..5]` | `[1, 2, 3, 4, 5]` |
170
+
171
+ Range examples:
172
+ - `[1..3, 7..9]` → `[1, 2, 3, 7, 8, 9]`
173
+ - `[1..$count(Items)].("Item " & $)` → dynamic numbered list
174
+ - `[1..5].($*$)` → `[1, 4, 9, 16, 25]`
175
+
176
+ ### Comparison Operators
177
+
178
+ | Operator | Description | Example |
179
+ |---|---|---|
180
+ | `=` | Deep equality | `1+1 = 2` → `true` |
181
+ | `!=` | Not equals | `"Hello" != "World"` → `true` |
182
+ | `>` | Greater than | `22/7 > 3` → `true` |
183
+ | `<` | Less than | `22/7 < 3` → `false` |
184
+ | `>=` | Greater than or equals | `5 >= 5` → `true` |
185
+ | `<=` | Less than or equals | `5 <= 5` → `true` |
186
+ | `in` | Array inclusion | `"world" in ["hello", "world"]` → `true` |
187
+
188
+ > `in` treats a scalar RHS as a singleton array: `"hello" in "hello"` → `true`
189
+
190
+ ### Boolean Operators
191
+
192
+ | Operator | Description | Example |
193
+ |---|---|---|
194
+ | `and` | Boolean AND | `price < 50 and section="diy"` |
195
+ | `or` | Boolean OR | `price < 10 or section="diy"` |
196
+
197
+ > Non-Boolean operands are cast using `$boolean()` rules. Boolean NOT is the `$not()` function, not an operator.
198
+
199
+ ### String Concatenation
200
+
201
+ | Operator | Description | Example | Result |
202
+ |---|---|---|---|
203
+ | `&` | String concatenation | `"Hello" & " " & "World"` | `"Hello World"` |
204
+
205
+ > Non-string operands are cast using `$string()` rules.
206
+
207
+ ### Conditional Operators
208
+
209
+ | Operator | Syntax | Description |
210
+ |---|---|---|
211
+ | Ternary | `test ? exprT : exprF` | If `test` is true, evaluate `exprT`; else `exprF` |
212
+ | Elvis/Default | `expr ?: default` | Returns `expr` if its effective Boolean is `true`; else `default` |
213
+ | Coalescing | `expr ?? default` | Returns `expr` if it is defined (not `undefined`); else `default` |
214
+
215
+ ```jsonata
216
+ Price < 50 ? "Cheap" : "Expensive"
217
+ foo.bar ?: 'default' -- fallback if falsy
218
+ foo.bar ?? 42 -- fallback only if undefined
219
+ 0 ?: 1 -- 0 is falsy → 1
220
+ 0 ?? 1 -- 0 is defined → 0
221
+ '' ?? 'fallback' -- '' is defined → ''
222
+ ```
223
+
224
+ ### Variable Binding, Chaining, and Transform
225
+
226
+ | Operator | Syntax | Description |
227
+ |---|---|---|
228
+ | Variable binding | `$var := expr` | Binds `expr` to `$var` (block-scoped) |
229
+ | Function chaining | `value ~> func` | Passes LHS as first argument to RHS function |
230
+ | Object transform | `` head ~> \| location \| update [, delete] \| `` | Deep-copies `head`, applies `update` to matched `location`(s) |
231
+
232
+ ```jsonata
233
+ -- Variable binding
234
+ $five := 5
235
+ $square := function($n) { $n * $n }
236
+
237
+ -- Function chaining
238
+ Customer.Email ~> $substringAfter("@") ~> $substringBefore(".") ~> $uppercase()
239
+
240
+ -- Function composition via chaining
241
+ $uppertrim := $trim ~> $uppercase
242
+ $uppertrim(" Hello World ") -- "HELLO WORLD"
243
+
244
+ -- Object transform: increase all Product prices by 20%
245
+ payload ~> |Account.Order.Product|{'Price': Price * 1.2}|
246
+
247
+ -- Transform with property deletion
248
+ $ ~> |Account.Order.Product|{'Total': Price * Quantity}, ['Price', 'Quantity']|
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Path Operators
254
+
255
+ JSONata's path expression is a **declarative functional language** based on map/filter/reduce. Each stage in the path processes the result sequence from the previous stage.
256
+
257
+ | Stage | Syntax | Action |
258
+ |---|---|---|
259
+ | **Map** | `seq.expr` | Evaluates RHS expression in context of each item. Flattens results. |
260
+ | **Filter** | `seq[expr]` | Keeps items where predicate `expr` is `true` |
261
+ | **Order-by** | `seq^(expr)` | Sorts sequence by criteria (ascending default; `>` for descending) |
262
+ | **Reduce** | `seq{key: value, ...}` | Groups and aggregates into a single object |
263
+ | **Wildcard** | `*` | Selects all property values of context object |
264
+ | **Descendants** | `**` | Recursively selects all descendant values |
265
+ | **Parent** | `%` | Selects the enclosing object of the context value |
266
+ | **Position var** | `# $var` | Binds variable to current position (zero offset) in sequence |
267
+ | **Context var** | `@ $var` | Binds variable to current context item (only after map) |
268
+
269
+ ### Order-by Examples
270
+
271
+ ```jsonata
272
+ Account.Order.Product^(Price) -- ascending by price
273
+ Account.Order.Product^(>Price) -- descending by price
274
+ Account.Order.Product^(>Price, <Quantity) -- desc price, then asc quantity
275
+ ```
276
+
277
+ ### Parent Operator
278
+
279
+ ```jsonata
280
+ Account.Order.Product.{
281
+ 'Product': `Product Name`,
282
+ 'Order': %.OrderID,
283
+ 'Account': %.%.`Account Name`
284
+ }
285
+ ```
286
+
287
+ ### Context Variable Binding (Data Joins)
288
+
289
+ ```jsonata
290
+ library.loans@$l.books@$b[$l.isbn=$b.isbn].{
291
+ 'title': $b.title,
292
+ 'customer': $l.customer
293
+ }
294
+ ```
295
+
296
+ ### Positional Variable Binding
297
+
298
+ ```jsonata
299
+ library.books#$i['Kernighan' in authors].{
300
+ 'title': title,
301
+ 'index': $i
302
+ }
303
+ ```
304
+
305
+ ---
306
+
307
+ ## Query Composition
308
+
309
+ In JSONata, **everything is an expression**. Expressions comprise values, functions and operators which, when evaluated, produce a resulting value. Functions and operators are applied to values which themselves can be the results of evaluating sub-expressions — the language is fully composable to any level of complexity.
310
+
311
+ ### Parenthesized Expressions and Blocks
312
+
313
+ Parentheses `()` serve three purposes:
314
+
315
+ 1. **Override operator precedence** — `(5 + 3) * 4` → `32`
316
+ 2. **Compute complex expressions on a context value** — `Product.(Price * Quantity)` — both `Price` and `Quantity` are resolved relative to each `Product`
317
+ 3. **Code blocks** — multiple expressions separated by semicolons; the last expression's result is returned:
318
+
319
+ ```jsonata
320
+ (
321
+ $pi := 3.14159;
322
+ $r := 5;
323
+ $pi * $r * $r -- returned result
324
+ )
325
+ ```
326
+
327
+ Blocks also define a **scope frame** for variables. Variables bound inside parentheses go out of scope when the block ends.
328
+
329
+ ---
330
+
331
+ ## Processing Model
332
+
333
+ ### Type System
334
+
335
+ JSONata supports 7 types:
336
+ - `string`, `number`, `boolean`, `null`, `object`, `array`, `function`
337
+
338
+ ### Sequence Flattening Rules
339
+
340
+ 1. **Empty sequence** = "nothing" (won't appear in output, won't create object properties)
341
+ 2. **Singleton sequence** = equivalent to the value itself
342
+ 3. **Multi-value sequence** = represented as a JSON array in output
343
+ 4. **Nested sequences** = flattened (child values pulled up to parent level)
344
+
345
+ > **Key difference:** An array from input JSON or `[...]` constructor is an **array value**, not a sequence. It won't be flattened unless it becomes context for a subsequent expression.
346
+
347
+ ### Path Processing Stages
348
+
349
+ | Stage | Syntax | Precedence |
350
+ |---|---|---|
351
+ | Map | `seq.expr` | Lower than Filter |
352
+ | Filter | `seq[expr]` | Binds tighter than Map |
353
+ | Sort (Order-by) | `seq^(expr)` | Lowest precedence |
354
+ | Index | `seq#$var` | — |
355
+ | Join (Context bind) | `seq@$var` | After a Map stage only |
356
+ | Reduce | `seq{...}` | Terminates the path expression |
357
+
358
+ ---
359
+
360
+ ## String Functions
361
+
362
+ All string functions with signatures. Functions with `-` in their signature can be invoked without an explicit first argument, using the context value instead.
363
+
364
+ | Function | Signature | Description |
365
+ |---|---|---|
366
+ | `$string()` | `<x-:s>` | Cast to string (JSON.stringify rules) |
367
+ | `$length()` | `<s-:n>` | Character count |
368
+ | `$substring()` | `<s-n?:s>` | `$substring(str, start[, length])` |
369
+ | `$substringBefore()` | `<s-s:s>` | Substring before first occurrence of chars |
370
+ | `$substringAfter()` | `<s-s:s>` | Substring after first occurrence of chars |
371
+ | `$uppercase()` | `<s-:s>` | Convert to uppercase |
372
+ | `$lowercase()` | `<s-:s>` | Convert to lowercase |
373
+ | `$trim()` | `<s-:s>` | Normalize whitespace |
374
+ | `$pad()` | `<sn?s?:s>` | `$pad(str, width[, char])` — right pad if width>0, left if <0 |
375
+ | `$contains()` | `<s-s:s>` | `true` if `str` contains `pattern` (string or regex) |
376
+ | `$split()` | `<s-sn?:a<s>>` | `$split(str, separator[, limit])` |
377
+ | `$join()` | `<a<s>s?:s>` | Join array with optional separator |
378
+ | `$match()` | `<s-sn?:a>` | Returns array of match objects `{match, index, groups}` |
379
+ | `$replace()` | `<s-s<sf>n?:s>` | Replace `pattern` with `replacement` (string/function) |
380
+ | `$eval()` | `<ss?:x>` | Parse and evaluate string as JSONata |
381
+ | `$base64encode()` | `<s-:s>` | Base64 encode ASCII string |
382
+ | `$base64decode()` | `<s-:s>` | Base64 decode to UTF-8 string |
383
+ | `$encodeUrlComponent()` | `<s-:s>` | URL-encode component |
384
+ | `$encodeUrl()` | `<s-:s>` | URL-encode full URL |
385
+ | `$decodeUrlComponent()` | `<s-:s>` | URL-decode component |
386
+ | `$decodeUrl()` | `<s-:s>` | URL-decode full URL |
387
+
388
+ ### Regex Usage with String Functions
389
+
390
+ ```jsonata
391
+ -- Contains with regex
392
+ $contains("abracadabra", /a.*a/) -- true
393
+ $contains("Hello World", /wo/i) -- true (case-insensitive)
394
+
395
+ -- Split with regex
396
+ $split("too much, punctuation. hard; to read", /[ ,.;]+/)
397
+ -- ["too", "much", "punctuation", "hard", "to", "read"]
398
+
399
+ -- Match with regex (returns match objects)
400
+ $match("ababbabbcc", /a(b+)/)
401
+ -- [{"match":"ab","index":0,"groups":["b"]}, ...]
402
+
403
+ -- Replace with regex and captured groups
404
+ $replace("John Smith", /(\w+)\s(\w+)/, "$2, $1")
405
+ -- "Smith, John"
406
+
407
+ -- Replace with function (temperature conversion)
408
+ $replace("temperature = 68F today", /(\d+)F/, function($m) {
409
+ ($number($m.groups[0]) - 32) * 5/9 & "C"
410
+ })
411
+ -- "temperature = 20C today"
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Regular Expressions
417
+
418
+ JSONata provides first-class support for regular expressions using the familiar slash delimiter syntax:
419
+
420
+ ```
421
+ /regex/flags
422
+ ```
423
+
424
+ Where `flags` can be:
425
+ - `i` — case-insensitive matching
426
+ - `m` — multiline matching (both can be combined: `/regex/im`)
427
+
428
+ > **Key insight:** `/regex/` evaluates to a **function** — a "matcher function". When invoked on a string, it returns a match object with `{match, start, end, groups, next()}`. The `next()` function advances to the next match.
429
+
430
+ ### Regex-Aware Functions
431
+
432
+ Four built-in functions accept regex patterns: `$match()`, `$contains()`, `$split()`, `$replace()`.
433
+
434
+ ### Regex in Query Predicates (Shortcut)
435
+
436
+ Use the chain operator `~>` for concise predicate matching:
437
+
438
+ ```jsonata
439
+ Account.Order.Product[`Product Name` ~> /hat/i]
440
+ -- Matches all products with 'hat' in their name (case-insensitive)
441
+ ```
442
+
443
+ The `~>` operator passes the field value to the regex matcher function, returning `true`/`false`.
444
+
445
+ ### Generic Matcher Functions
446
+
447
+ Since `/regex/` is just a function generator, you can bind regexes to variables and invoke them:
448
+
449
+ ```jsonata
450
+ $matcher := /[a-z]*an[a-z]*/i
451
+ $matcher('A man, a plan, a canal, Panama!')
452
+ -- {"match": "man", "start": 2, "end": 4, "groups": [], "next": <function>}
453
+ ```
454
+
455
+ ### Custom Matchers
456
+
457
+ The regex-aware functions work with any function conforming to the matcher contract. You can write custom matchers as JSONata lambdas or extension functions and pass them to `$match`, `$contains`, `$split`, and `$replace`.
458
+
459
+ ### Other String Examples
460
+
461
+ ```jsonata
462
+ $substring("Hello World", 3) -- "lo World"
463
+ $substring("Hello World", 3, 5) -- "lo Wo"
464
+ $substring("Hello World", -4) -- "orld"
465
+ $pad("foo", 5) -- "foo "
466
+ $pad("foo", -5, "#") -- "##foo"
467
+ $trim(" Hello \n World ") -- "Hello World"
468
+ $eval("[1,$string(2),3]") -- [1, "2", 3]
469
+ $base64encode("myuser:mypass") -- "bXl1c2VyOm15cGFzcw=="
470
+ ```
471
+
472
+ ---
473
+
474
+ ## Numeric Functions
475
+
476
+ | Function | Signature | Description |
477
+ |---|---|---|
478
+ | `$number()` | `<x-:n>` | Cast to number (supports hex `0x`, octal `0o`, binary `0b`) |
479
+ | `$abs()` | `<n-:n>` | Absolute value |
480
+ | `$floor()` | `<n-:n>` | Round down to nearest integer |
481
+ | `$ceil()` | `<n-:n>` | Round up to nearest integer |
482
+ | `$round()` | `<nn?:n>` | Round to `precision` decimal places (default 0) |
483
+ | `$power()` | `<nn-:n>` | `base` raised to `exponent` |
484
+ | `$sqrt()` | `<n-:n>` | Square root |
485
+ | `$random()` | `<>:n` | Pseudo-random number 0 ≤ n < 1 |
486
+ | `$formatNumber()` | `<ns-:s>` | Format number using XPath picture string |
487
+ | `$formatBase()` | `<nn?:s>` | Format integer in given radix (2-36) |
488
+ | `$formatInteger()` | `<ns-:s>` | Format integer using XPath picture string |
489
+ | `$parseInteger()` | `<ss-:n>` | Parse formatted integer string to number |
490
+
491
+ ### Rounding Semantics
492
+
493
+ `$round()` uses **round half to even** (banker's rounding), the default IEEE 754 mode:
494
+
495
+ ```jsonata
496
+ $round(123.456) -- 123
497
+ $round(123.456, 2) -- 123.46
498
+ $round(123.456, -1) -- 120 (round to tens)
499
+ $round(11.5) -- 12 (half → even)
500
+ $round(12.5) -- 12 (half → even)
501
+ $round(125, -1) -- 120 (half → even)
502
+ ```
503
+
504
+ ### Number Formatting
505
+
506
+ ```jsonata
507
+ $formatNumber(12345.6, '#,###.00') -- "12,345.60"
508
+ $formatNumber(1234.5678, "00.000e0") -- "12.346e2"
509
+ $formatNumber(-34.555, "#0.00;(#0.00)") -- "(34.56)"
510
+ $formatNumber(0.14, "01%") -- "14%"
511
+ $formatBase(100, 2) -- "1100100"
512
+ $formatBase(2555, 16) -- "9fb"
513
+ $formatInteger(2789, 'w') -- "two thousand, seven hundred and eighty-nine"
514
+ $formatInteger(1999, 'I') -- "MCMXCIX"
515
+ $parseInteger("twelve thousand, four hundred and seventy-six", 'w') -- 12476
516
+ ```
517
+
518
+ ---
519
+
520
+ ## Aggregation Functions
521
+
522
+ | Function | Signature | Description |
523
+ |---|---|---|
524
+ | `$sum()` | `<a<n>:n>` | Arithmetic sum of array of numbers |
525
+ | `$max()` | `<a<n>:n>` | Maximum number in array |
526
+ | `$min()` | `<a<n>:n>` | Minimum number in array |
527
+ | `$average()` | `<a<n>:n>` | Mean value of array of numbers |
528
+
529
+ ```jsonata
530
+ $sum([5, 1, 3, 7, 4]) -- 20
531
+ $max([5, 1, 3, 7, 4]) -- 7
532
+ $min([5, 1, 3, 7, 4]) -- 1
533
+ $average([5, 1, 3, 7, 4]) -- 4
534
+
535
+ -- In path expressions
536
+ $sum(Account.Order.Product.(Price*Quantity)) -- total of all price×quantity
537
+ ```
538
+
539
+ ---
540
+
541
+ ## Boolean Functions
542
+
543
+ | Function | Signature | Description |
544
+ |---|---|---|
545
+ | `$boolean()` | `<x-:b>` | Cast to Boolean |
546
+ | `$not()` | `<x-:b>` | Boolean NOT (argument cast first) |
547
+ | `$exists()` | `<x-:b>` | `true` if expression evaluates to a value |
548
+
549
+ ### `$boolean()` Casting Rules
550
+
551
+ | Value | Result |
552
+ |---|---|
553
+ | Boolean | unchanged |
554
+ | string: empty `""` | `false` |
555
+ | string: non-empty | `true` |
556
+ | number: `0` | `false` |
557
+ | number: non-zero | `true` |
558
+ | `null` | `false` |
559
+ | array: empty | `false` |
560
+ | array: contains member that casts to `true` | `true` |
561
+ | array: all members cast to `false` | `false` |
562
+ | object: empty | `false` |
563
+ | object: non-empty | `true` |
564
+ | function | `false` |
565
+
566
+ ---
567
+
568
+ ## Array Functions
569
+
570
+ | Function | Signature | Description |
571
+ |---|---|---|
572
+ | `$count()` | `<x-:n>` | Number of items (non-arrays count as 1) |
573
+ | `$append()` | `<xx:a>` | Concatenate two arrays (or values) |
574
+ | `$sort()` | `<a<f>?:a>` | Sort array; optional comparator `function($l, $r)` |
575
+ | `$reverse()` | `<a:a>` | Reverse array order |
576
+ | `$shuffle()` | `<a:a>` | Random shuffle |
577
+ | `$distinct()` | `<a:a>` | Remove duplicates (deep equality) |
578
+ | `$zip()` | `<a+>` | Convolve arrays: `$zip([1,2,3], [4,5,6])` → `[[1,4],[2,5],[3,6]]` |
579
+
580
+ ```jsonata
581
+ $count([1,2,3,1]) -- 4
582
+ $append([1,2,3], [4,5,6]) -- [1,2,3,4,5,6]
583
+ $reverse(["Hello", "World"]) -- ["World", "Hello"]
584
+ $shuffle([1..9]) -- [6, 8, 2, 3, 9, 5, 1, 4, 7] (random)
585
+ $distinct([1,2,3,3,4,3,5]) -- [1, 2, 3, 4, 5]
586
+ $zip([1,2,3], [4,5], [7,8,9]) -- [[1,4,7], [2,5,8]] (shortest array wins)
587
+
588
+ -- Custom sort comparator
589
+ $sort(Account.Order.Product, function($l, $r) {
590
+ $l.Description.Weight > $r.Description.Weight
591
+ })
592
+ ```
593
+
594
+ ---
595
+
596
+ ## Object Functions
597
+
598
+ | Function | Signature | Description |
599
+ |---|---|---|
600
+ | `$keys()` | `<o-:a<s>>` | Array of keys in object (de-duplicated for arrays of objects) |
601
+ | `$lookup()` | `<os-:x>` | Value for key in object(s) |
602
+ | `$spread()` | `<o-:a<o>>` | Split object into array of single-key objects |
603
+ | `$merge()` | `<a<o>:o>` | Merge array of objects (last key wins on conflict) |
604
+ | `$each()` | `<of:a>` | Apply function to each key/value pair |
605
+ | `$error()` | `<s-:>` | Throw error with message |
606
+ | `$assert()` | `<bs-:>` | Throw error if condition is `false` |
607
+ | `$type()` | `<x-:s>` | Return type string: "null", "number", "string", "boolean", "array", "object", "function" |
608
+
609
+ ```jsonata
610
+ $keys(Address) -- ["Street", "City", "Postcode"]
611
+ $lookup(Address, "City") -- "Winchester"
612
+ $merge([{"a":1},{"b":2},{"a":3}]) -- {"a":3, "b":2}
613
+ $each(Address, function($v, $k) { $k & ": " & $v })
614
+ -- ["Street: Hursley Park", "City: Winchester", "Postcode: SO21 2JN"]
615
+ $type("hello") -- "string"
616
+ $type([1,2,3]) -- "array"
617
+ ```
618
+
619
+ ---
620
+
621
+ ## Date/Time Functions
622
+
623
+ JSON does not have a date type. The convention is **ISO 8601 string format** (`"2018-12-10T13:45:00.000Z"`). JSONata follows this convention.
624
+
625
+ | Function | Signature | Description |
626
+ |---|---|---|
627
+ | `$now()` | `<s?s?:s>` | Current UTC timestamp (same value for all calls in one expression) |
628
+ | `$millis()` | `<>:n` | Milliseconds since Unix epoch |
629
+ | `$fromMillis()` | `<ns?s?:s>` | Format milliseconds to string using optional picture string |
630
+ | `$toMillis()` | `<ss?:n>` | Parse timestamp string to milliseconds using optional picture string |
631
+
632
+ ### Picture String Notation
633
+
634
+ Uses XPath F&O 3.1 `fn:format-dateTime` picture string syntax. Common components:
635
+
636
+ | Component | Meaning | Example |
637
+ |---|---|---|
638
+ | `[Y]` / `[Y0001]` | Year | `2018` |
639
+ | `[M]` / `[M01]` | Month | `12` / `05` |
640
+ | `[D]` / `[D01]` | Day | `10` / `05` |
641
+ | `[H]` / `[H01]` / `[h#1]` | Hour (24h / 24h padded / 12h) | `13` / `13` / `1pm` |
642
+ | `[m]` / `[m01]` | Minute | `45` / `05` |
643
+ | `[s]` / `[s01]` | Second | `00` / `05` |
644
+ | `[P]` | AM/PM marker | `pm` |
645
+ | `[z]` | Timezone | `GMT-05:00` |
646
+ | `[FNn]` | Full day name | `Monday` |
647
+ | `[MNn]` | Full month name | `December` |
648
+
649
+ ```jsonata
650
+ $now() -- "2017-05-15T15:12:59.152Z"
651
+ $millis() -- 1502700297574
652
+ $fromMillis(1510067557121) -- "2017-11-07T15:12:37.121Z"
653
+ $fromMillis(1510067557121, '[M01]/[D01]/[Y0001] [h#1]:[m01][P]') -- "11/07/2017 3:12pm"
654
+ $fromMillis(1510067557121, '[H01]:[m01]:[s01] [z]', '-0500') -- "10:12:37 GMT-05:00"
655
+ $toMillis("2017-11-07T15:07:54.972Z") -- 1510067274972
656
+ $toMillis('10/12/2018', '[D]/[M]/[Y]') -- parse EU format
657
+ ```
658
+
659
+ ---
660
+
661
+ ## Higher-Order Functions
662
+
663
+ Functions that take functions as arguments.
664
+
665
+ | Function | Signature | Description |
666
+ |---|---|---|
667
+ | `$map()` | `<af<a?>?:a>` | Apply function to each array item |
668
+ | `$filter()` | `<af<a?>?:a>` | Keep items where function returns `true` |
669
+ | `$single()` | `<af<a?>?:x>` | Return the single item matching predicate; error if not exactly one |
670
+ | `$reduce()` | `<af<*?>f?:x>` | Fold/accumulate: `$reduce(array, function($acc, $v, $i, $a)[, init])` |
671
+ | `$sift()` | `<of<o?>?:o>` | Keep object key/value pairs where function returns `true` |
672
+
673
+ ### Callback Signatures
674
+
675
+ All array callbacks: `function(value [, index [, array]])`
676
+
677
+ `$sift` callback: `function(value [, key [, object]])`
678
+
679
+ ### Examples
680
+
681
+ ```jsonata
682
+ -- Map
683
+ $map([1,2,3], function($v) { $v * 2 }) -- [2, 4, 6]
684
+
685
+ -- Filter
686
+ $filter(Account.Order.Product, function($v, $i, $a) {
687
+ $v.Price > $average($a.Price)
688
+ })
689
+
690
+ -- Single
691
+ $single(Account.Order.Product, function($v) {
692
+ $v.SKU = "0406654608"
693
+ })
694
+
695
+ -- Reduce (product of all numbers)
696
+ $reduce([1..5], function($i, $j){ $i * $j }) -- 120
697
+
698
+ -- Sift (keep only keys starting with "Product")
699
+ Account.Order.Product.$sift(function($v, $k) { $k ~> /^Product/ })
700
+ ```
701
+
702
+ ---
703
+
704
+ ## Programming Constructs
705
+
706
+ ### Comments
707
+
708
+ ```jsonata
709
+ /* Single or multi-line comments using C-style delimiters */
710
+ ```
711
+
712
+ ### Conditional Logic
713
+
714
+ Three conditional forms:
715
+ - **Ternary:** `test ? exprT : exprF`
716
+ - **Elvis (Default):** `expr ?: default` — equivalent to `expr ? expr : default`
717
+ - **Coalescing:** `expr ?? default` — equivalent to `$exists(expr) ? expr : default`
718
+
719
+ ```jsonata
720
+ Account.Order.Product.{
721
+ `Product Name`: $.Price > 100 ? "Premium" : "Basic",
722
+ `Category`: $.Category ?: "Uncategorized"
723
+ }
724
+ ```
725
+
726
+ ### Variables
727
+
728
+ Variable names start with `$`. Two built-in variables: `$` (context) and `$$` (root).
729
+
730
+ Variable binding uses `:=`, scoped to the current block:
731
+
732
+ ```jsonata
733
+ (
734
+ $pi := 3.14159;
735
+ $r := 5;
736
+ $pi * $r * $r
737
+ )
738
+ ```
739
+
740
+ ### Functions
741
+
742
+ **Lambda definition:**
743
+ ```jsonata
744
+ function($l, $w, $h) { $l * $w * $h }
745
+ ```
746
+
747
+ **Assign and invoke:**
748
+ ```jsonata
749
+ (
750
+ $volume := function($l, $w, $h) { $l * $w * $h };
751
+ $volume(10, 10, 5) -- 500
752
+ )
753
+ ```
754
+
755
+ ### Function Signatures
756
+
757
+ Optional type annotations for parameter validation:
758
+
759
+ ```jsonata
760
+ <params:return>
761
+ ```
762
+
763
+ | Symbol | Type |
764
+ |---|---|
765
+ | `b` | Boolean |
766
+ | `n` | number |
767
+ | `s` | string |
768
+ | `l` | null |
769
+ | `a` | array |
770
+ | `o` | object |
771
+ | `f` | function |
772
+ | `u` | (bnsl) — Boolean, number, string, null |
773
+ | `j` | (bnsloa) — any JSON type |
774
+ | `x` | (bnsloaf) — any type |
775
+ | `a<s>` | array of strings |
776
+ | `+` | one or more arguments of this type |
777
+ | `?` | optional argument |
778
+ | `-` | use context if missing |
779
+
780
+ Examples of built-in signatures:
781
+ - `$count` → `<a:n>`
782
+ - `$sum` → `<a<n>:n>`
783
+ - `$reduce` → `<fa<j>:j>` (takes function + array of JSON, returns JSON)
784
+
785
+ ### Recursion and Tail Call Optimization
786
+
787
+ ```jsonata
788
+ -- Standard recursive factorial
789
+ $factorial := function($x) { $x <= 1 ? 1 : $x * $factorial($x-1) }
790
+
791
+ -- Tail-recursive factorial (optimized to a loop)
792
+ $factorial := function($x) {(
793
+ $iter := function($x, $acc) {
794
+ $x <= 1 ? $acc : $iter($x - 1, $x * $acc)
795
+ };
796
+ $iter($x, 1)
797
+ )}
798
+ ```
799
+
800
+ > Tail-recursive functions run in constant stack space and won't overflow.
801
+
802
+ ### Closures
803
+
804
+ Functions capture their defining environment (context value + variable bindings):
805
+
806
+ ```jsonata
807
+ Account.(
808
+ $AccName := function() { $.'Account Name' };
809
+ Order[OrderID = 'order104'].Product.{
810
+ 'Account': $AccName(),
811
+ 'SKU-' & $string(ProductID): $.'Product Name'
812
+ }
813
+ )
814
+ ```
815
+
816
+ ### Partial Function Application
817
+
818
+ Replace arguments with `?` to create new functions:
819
+
820
+ ```jsonata
821
+ $first5 := $substring(?, 0, 5)
822
+ $first5("Hello, World") -- "Hello"
823
+
824
+ -- Chaining partial applications
825
+ $firstN := $substring(?, 0, ?)
826
+ $first5 := $firstN(?, 5)
827
+
828
+ -- Function composition with partials
829
+ $first5Capitalized := $substring(?, 0, 5) ~> $uppercase(?)
830
+ $first5Capitalized(Address.City) -- "WINCH"
831
+ ```
832
+
833
+ ### Functions as First-Class Values
834
+
835
+ ```jsonata
836
+ $twice := function($f) { function($x) { $f($f($x)) } }
837
+ $add3 := function($y) { $y + 3 }
838
+ $add6 := $twice($add3)
839
+ $add6(7) -- 13
840
+ ```
841
+
842
+ ---
843
+
844
+ ## Object Construction and Grouping
845
+
846
+ ### Array Constructors
847
+
848
+ Use `[...]` to build arrays. Commas separate expressions:
849
+
850
+ ```jsonata
851
+ [Address, Other.`Alternative.Address`].City -- ["Winchester", "London"]
852
+ Email.[address] -- preserves nested structure per email object
853
+ ```
854
+
855
+ ### Object Constructors
856
+
857
+ Use `{key: value, ...}` to build objects. Keys can be expressions that evaluate to strings:
858
+
859
+ ```jsonata
860
+ Phone.{type: number}
861
+ -- [{"home":"0203 544 1234"},{"office":"01962 001234"}, ...]
862
+
863
+ Phone{type: number}
864
+ -- {"home":"0203 544 1234","office":["01962 001234","01962 001235"],"mobile":"077 7700 1234"}
865
+ ```
866
+
867
+ > **Key difference:** `Phone.{type: number}` (with dot) creates one object per phone. `Phone{type: number}` (without dot) groups into a single object.
868
+
869
+ ### Grouping with Object Key Expressions
870
+
871
+ The object constructor is also the **group-by** mechanism. The key expression determines grouping; value expressions aggregate:
872
+
873
+ ```jsonata
874
+ -- Group products by name, collect prices
875
+ Account.Order.Product{`Product Name`: Price}
876
+ -- {"Bowler Hat":[34.45,34.45],"Trilby hat":21.67,"Cloak":107.99}
877
+
878
+ -- Group with aggregated total per product
879
+ Account.Order.Product{`Product Name`: $sum($.(Price*Quantity))}
880
+ -- {"Bowler Hat":206.7,"Trilby hat":21.67,"Cloak":107.99}
881
+ ```
882
+
883
+ ### JSON Literals
884
+
885
+ JSONata is a superset of JSON — you can use a JSON document as a template:
886
+
887
+ ```jsonata
888
+ {"name": FirstName & " " & Surname, "age": Age, "city": Address.City}
889
+ -- {"name":"Fred Smith","age":28,"city":"Winchester"}
890
+ ```
891
+
892
+ ---
893
+
894
+ ## JavaScript Embedding API
895
+
896
+ For embedding JSONata in Node.js applications (reference implementation).
897
+
898
+ ### Installation
899
+
900
+ ```bash
901
+ npm install jsonata
902
+ ```
903
+
904
+ ### Basic Usage
905
+
906
+ ```javascript
907
+ const jsonata = require('jsonata');
908
+
909
+ const data = {
910
+ example: [
911
+ {value: 4},
912
+ {value: 7},
913
+ {value: 13}
914
+ ]
915
+ };
916
+
917
+ (async () => {
918
+ const expression = jsonata('$sum(example.value)');
919
+ const result = await expression.evaluate(data); // 24
920
+ })();
921
+ ```
922
+
923
+ ### API Reference
924
+
925
+ #### `jsonata(str[, options])`
926
+ Parse and compile an expression. Returns an expression object. Throws on syntax errors.
927
+
928
+ #### `expression.evaluate(input[, bindings[, callback]])`
929
+ Evaluate against `input` data. Returns a Promise resolving to the result.
930
+
931
+ ```javascript
932
+ await jsonata("$a + $b()").evaluate({}, {a: 4, b: () => 78});
933
+ // 82
934
+ ```
935
+
936
+ #### `expression.assign(name, value)`
937
+ Permanently bind a value to a name in the expression.
938
+
939
+ ```javascript
940
+ expression.assign("a", 4);
941
+ expression.assign("b", () => 1);
942
+ await expression.evaluate({}); // 5
943
+ ```
944
+
945
+ #### `expression.registerFunction(name, implementation[, signature])`
946
+ Register a custom function with optional type signature:
947
+
948
+ ```javascript
949
+ expression.registerFunction("add", (a, b) => a + b, "<nn:n>");
950
+ await expression.evaluate({}); // 10066
951
+ ```
952
+
953
+ ### Guardrails
954
+
955
+ Protect against runaway expressions with configurable limits:
956
+
957
+ ```javascript
958
+ const options = {
959
+ stack: 500, // max eval-apply cycle depth
960
+ timeout: 1000, // max execution time in ms
961
+ sequence: 1e6, // max sequence length
962
+ RegexEngine: SafeRegExp // custom regex processor for ReDoS protection
963
+ };
964
+ const expression = jsonata('<expr>', options);
965
+ ```
966
+
967
+ | Guardrail | Error Code | Description |
968
+ |---|---|---|
969
+ | `stack` | `D1011` | Max depth of eval-apply cycle |
970
+ | `timeout` | `D1012` | Max milliseconds of execution |
971
+ | `sequence` | `D2015` | Max items in any sequence |
972
+ | `RegexEngine` | — | Custom regex constructor for ReDoS protection |
973
+
974
+ ---
975
+
976
+ ## Node-RED Integration
977
+
978
+ JSONata is the **default expression language** in Node-RED. Key usage points:
979
+
980
+ ### Change Node — "Set" with JSONata Expression
981
+
982
+ The "change" node has a "JSONata" expression type. The expression receives the full `msg` object as its context (`$$` = `msg`).
983
+
984
+ ```jsonata
985
+ -- Transform msg.payload
986
+ payload.temperature & "°C"
987
+
988
+ -- Access other msg properties
989
+ "Topic: " & topic & ", Value: " & payload
990
+
991
+ -- Create a new object from msg
992
+ {"sensor": topic, "value": payload, "timestamp": $now()}
993
+ ```
994
+
995
+ ### Switch Node — Condition Expressions
996
+
997
+ The "switch" node supports JSONata expressions for conditions. The expression must return `true`/`false`:
998
+
999
+ ```jsonata
1000
+ payload.temperature > 30
1001
+ $contains(topic, "alarm")
1002
+ $exists(payload.error)
1003
+ ```
1004
+
1005
+ ### Context Access in Node-RED
1006
+
1007
+ The `msg` object is the root `$$`. All msg properties are directly accessible by name:
1008
+ - `payload` → `msg.payload`
1009
+ - `topic` → `msg.topic`
1010
+ - `payload.sensor.value` → nested access within payload
1011
+
1012
+ ### Common Node-RED JSONata Patterns
1013
+
1014
+ ```jsonata
1015
+ -- Extract and rename fields
1016
+ {"device": topic, "reading": payload, "unit": "°C"}
1017
+
1018
+ -- Conditional transformation
1019
+ payload > 100 ? "HIGH" : payload > 50 ? "MEDIUM" : "LOW"
1020
+
1021
+ -- Filter array within payload
1022
+ payload[value > 10]
1023
+
1024
+ -- Aggregate array data
1025
+ $sum(payload.readings)
1026
+ $average(payload.*.temperature)
1027
+
1028
+ -- Timestamp enrichment
1029
+ {"data": payload, "receivedAt": $now()}
1030
+
1031
+ -- Default value pattern
1032
+ payload.value ?: 0
1033
+
1034
+ -- Check if property exists
1035
+ $exists(payload.error) ? "Error: " & payload.error : "OK"
1036
+
1037
+ -- Merge multiple properties
1038
+ $merge([payload.metadata, {"processed": true, "timestamp": $now()}])
1039
+ ```