@cxtms/cx-schema 1.8.1 → 1.9.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.
Files changed (72) hide show
  1. package/README.md +34 -34
  2. package/dist/cli.js +2408 -177
  3. package/dist/cli.js.map +1 -1
  4. package/dist/types.d.ts +2 -0
  5. package/dist/types.d.ts.map +1 -1
  6. package/dist/validator.d.ts +8 -0
  7. package/dist/validator.d.ts.map +1 -1
  8. package/dist/validator.js +54 -2
  9. package/dist/validator.js.map +1 -1
  10. package/dist/workflowValidator.d.ts +4 -0
  11. package/dist/workflowValidator.d.ts.map +1 -1
  12. package/dist/workflowValidator.js +28 -2
  13. package/dist/workflowValidator.js.map +1 -1
  14. package/package.json +2 -2
  15. package/schemas/components/appComponent.json +8 -0
  16. package/schemas/components/module.json +31 -2
  17. package/schemas/components/timelineGrid.json +4 -0
  18. package/schemas/schemas.json +12 -0
  19. package/schemas/workflows/tasks/authentication.json +26 -12
  20. package/schemas/workflows/workflow.json +0 -4
  21. package/scripts/postinstall.js +16 -13
  22. package/{.claude/skills/cx-core → skills/cxtms-developer}/SKILL.md +16 -14
  23. package/skills/cxtms-developer/ref-cli-auth.md +120 -0
  24. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-accounting.md +7 -0
  25. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-commodity.md +12 -0
  26. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-contact.md +10 -0
  27. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-geography.md +34 -1
  28. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-order-sub.md +13 -0
  29. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-order.md +14 -0
  30. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-rate.md +8 -0
  31. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-shared.md +9 -0
  32. package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-warehouse.md +5 -0
  33. package/skills/cxtms-developer/ref-graphql-query.md +320 -0
  34. package/{.claude/skills/cx-module → skills/cxtms-module-builder}/SKILL.md +112 -37
  35. package/{.claude/skills/cx-module → skills/cxtms-module-builder}/ref-components-data.md +7 -0
  36. package/{.claude/skills/cx-module → skills/cxtms-module-builder}/ref-components-display.md +17 -0
  37. package/{.claude/skills/cx-module → skills/cxtms-module-builder}/ref-components-forms.md +7 -1
  38. package/{.claude/skills/cx-module → skills/cxtms-module-builder}/ref-components-interactive.md +11 -0
  39. package/{.claude/skills/cx-module → skills/cxtms-module-builder}/ref-components-layout.md +11 -0
  40. package/{.claude/skills/cx-module → skills/cxtms-module-builder}/ref-components-specialized.md +50 -0
  41. package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/SKILL.md +139 -30
  42. package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/ref-communication.md +8 -0
  43. package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/ref-entity.md +43 -0
  44. package/skills/cxtms-workflow-builder/ref-expressions-ncalc.md +128 -0
  45. package/skills/cxtms-workflow-builder/ref-expressions-template.md +159 -0
  46. package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/ref-flow.md +8 -1
  47. package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/ref-other.md +9 -0
  48. package/templates/module-configuration.yaml +23 -89
  49. package/templates/module-form.yaml +3 -3
  50. package/templates/module-grid.yaml +3 -3
  51. package/templates/module-select.yaml +3 -3
  52. package/templates/module.yaml +3 -2
  53. package/templates/workflow-api-tracking.yaml +1 -1
  54. package/templates/workflow-basic.yaml +1 -1
  55. package/templates/workflow-document.yaml +1 -1
  56. package/templates/workflow-entity-trigger.yaml +1 -1
  57. package/templates/workflow-ftp-edi.yaml +1 -1
  58. package/templates/workflow-ftp-tracking.yaml +1 -1
  59. package/templates/workflow-mcp-tool.yaml +1 -1
  60. package/templates/workflow-public-api.yaml +1 -1
  61. package/templates/workflow-scheduled.yaml +1 -1
  62. package/templates/workflow-utility.yaml +1 -1
  63. package/templates/workflow-webhook.yaml +1 -1
  64. package/templates/workflow.yaml +1 -1
  65. package/.claude/skills/cx-workflow/ref-expressions.md +0 -272
  66. /package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-job.md +0 -0
  67. /package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-notification.md +0 -0
  68. /package/{.claude/skills/cx-core → skills/cxtms-developer}/ref-entity-organization.md +0 -0
  69. /package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/ref-accounting.md +0 -0
  70. /package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/ref-filetransfer.md +0 -0
  71. /package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/ref-query.md +0 -0
  72. /package/{.claude/skills/cx-workflow → skills/cxtms-workflow-builder}/ref-utilities.md +0 -0
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Configuration Module
2
- # Generated by cx-cli create module --template configuration
2
+ # Generated by cxtms create module --template configuration
3
3
 
4
4
  module:
5
5
  name: "{{displayNameNoSpaces}}"
@@ -8,103 +8,37 @@ module:
8
8
  en-US: "{{displayName}}"
9
9
  description:
10
10
  en-US: "{{displayName}} configuration module"
11
- application: CargoXplorer
12
- fileName: "{{fileName}}"
11
+ application: System
13
12
 
14
13
  components:
15
- - name: {{displayNameNoSpaces}}/Configuration
14
+ - name: Configurations/{{displayNameNoSpaces}}
16
15
  displayName:
17
- en-US: "{{displayName}}"
16
+ en-US: "{{displayName}} Config"
17
+ platforms:
18
+ - web
18
19
  layout:
19
- component: form
20
- name: {{name}}Form
20
+ component: layout
21
+ name: {{displayNameNoSpaces}}ConfigLayout
21
22
  props:
23
+ cols: 1
22
24
  title:
23
- en-US: "{{displayName}}"
24
- queries:
25
- - name: getConfiguration
26
- query:
27
- command: |
28
- query GetConfiguration($organizationId: Int!) {
29
- configuration(organizationId: $organizationId) {
30
- settingName
31
- maxRetries
32
- enableNotifications
33
- }
34
- }
35
- variables:
36
- organizationId: "\{{organizationId}}"
37
- initialValues:
38
- fromQuery:
39
- name: getConfiguration
40
- path: configuration
41
- append:
42
- settingName: "Default Setting"
43
- maxRetries: 3
44
- enableNotifications: true
45
- validationSchema:
46
- settingName:
47
- type: string
48
- required: true
49
- maxRetries:
50
- type: number
51
- required: true
52
- enableNotifications:
53
- type: boolean
54
- onSubmit:
55
- - type: mutation
56
- props:
57
- command: |
58
- mutation SaveConfiguration($organizationId: Int!, $input: ConfigurationInput!) {
59
- saveConfiguration(organizationId: $organizationId, input: $input) {
60
- success
61
- }
62
- }
63
- variables:
64
- organizationId: "\{{organizationId}}"
65
- input: "\{{formValues}}"
66
- - type: notification
67
- props:
68
- message:
69
- en-US: "{{displayName}} saved successfully"
70
- severity: success
25
+ en-US: "{{displayName}} Configuration"
26
+ icon: Create
27
+ permission: System/OrganizationConfigs/Update
71
28
  children:
72
29
  - component: field
73
- name: settingName
74
- props:
75
- label:
76
- en-US: "Setting Name"
77
- type: text
78
- required: true
79
- - component: field
80
- name: maxRetries
81
- props:
82
- label:
83
- en-US: "Max Retries"
84
- type: number
85
- - component: field
86
- name: enableNotifications
30
+ name: customValues.enabled
87
31
  props:
88
- label:
89
- en-US: "Enable Notifications"
90
32
  type: checkbox
33
+ label:
34
+ en-US: "Enable {{displayName}}"
91
35
 
92
- routes:
93
- - name: configuration
94
- path: /{{name}}
95
- component: {{displayNameNoSpaces}}/Configuration
96
- props:
97
- title:
98
- en-US: "{{displayName}}"
99
-
100
- permissions:
101
- - name: "{{displayNameNoSpaces}}/View"
102
- displayName:
103
- en-US: "View {{displayName}}"
104
- roles:
105
- - Admin
106
- - name: "{{displayNameNoSpaces}}/Edit"
36
+ configurations:
37
+ - configName: "apps.{{name}}"
107
38
  displayName:
108
- en-US: "Edit {{displayName}}"
109
- roles:
110
- - Admin
39
+ en-US: "{{displayName}}"
40
+ description:
41
+ en-US: "{{displayName}} configuration"
42
+ component: "Configurations/{{displayNameNoSpaces}}"
43
+ defaultValue:
44
+ enabled: false
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Form Module
2
- # Generated by cx-cli create module --template form
2
+ # Generated by cxtms create module --template form
3
3
 
4
4
  module:
5
5
  name: "{{displayNameNoSpaces}}"
@@ -8,8 +8,8 @@ module:
8
8
  en-US: "{{displayName}}"
9
9
  description:
10
10
  en-US: "{{displayName}} module"
11
- application: CargoXplorer
12
- fileName: "{{fileName}}"
11
+ application: System
12
+ filePath: "{{fileName}}"
13
13
 
14
14
  components:
15
15
  - name: {{displayNameNoSpaces}}/Detail
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Module
2
- # Generated by cx-cli create module --template grid
2
+ # Generated by cxtms create module --template grid
3
3
 
4
4
  module:
5
5
  name: "{{displayNameNoSpaces}}"
@@ -8,8 +8,8 @@ module:
8
8
  en-US: "{{displayName}}"
9
9
  description:
10
10
  en-US: "{{displayName}} module"
11
- application: CargoXplorer
12
- fileName: "{{fileName}}"
11
+ application: System
12
+ filePath: "{{fileName}}"
13
13
 
14
14
  components:
15
15
  - name: {{displayNameNoSpaces}}/List
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Select Module
2
- # Generated by cx-cli create module --template select
2
+ # Generated by cxtms create module --template select
3
3
 
4
4
  module:
5
5
  name: "{{displayNameNoSpaces}}"
@@ -8,8 +8,8 @@ module:
8
8
  en-US: "{{displayName}}"
9
9
  description:
10
10
  en-US: "{{displayName}} async select component"
11
- application: CargoXplorer
12
- fileName: "{{fileName}}"
11
+ application: System
12
+ filePath: "{{fileName}}"
13
13
 
14
14
  components:
15
15
  - name: {{displayNameNoSpaces}}/Select
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Module
2
- # Generated by cx-cli create module
2
+ # Generated by cxtms create module
3
3
 
4
4
  module:
5
5
  name: "{{name}}"
@@ -8,7 +8,8 @@ module:
8
8
  description: "{{displayName}} module"
9
9
  icon: "folder"
10
10
  version: "1.0"
11
- fileName: "{{fileName}}"
11
+ application: System
12
+ filePath: "{{fileName}}"
12
13
 
13
14
  entities:
14
15
  - name: {{name}}
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template api-tracking
2
+ # Generated by cxtms create workflow --template api-tracking
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template basic
2
+ # Generated by cxtms create workflow --template basic
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template document
2
+ # Generated by cxtms create workflow --template document
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template entity-trigger
2
+ # Generated by cxtms create workflow --template entity-trigger
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template ftp-edi
2
+ # Generated by cxtms create workflow --template ftp-edi
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template ftp-tracking
2
+ # Generated by cxtms create workflow --template ftp-tracking
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template mcp-tool
2
+ # Generated by cxtms create workflow --template mcp-tool
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template public-api
2
+ # Generated by cxtms create workflow --template public-api
3
3
  #
4
4
  # This workflow exposes a public REST API endpoint.
5
5
  # The api section defines the route, method, authentication, and OpenAPI metadata.
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template scheduled
2
+ # Generated by cxtms create workflow --template scheduled
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template utility
2
+ # Generated by cxtms create workflow --template utility
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow --template webhook
2
+ # Generated by cxtms create workflow --template webhook
3
3
  #
4
4
  # Endpoint: POST /api/v2/orgs/{organizationId}/webhooks/{workflowId}
5
5
  # The endpoint is anonymous (no auth token required) and rate-limited (10/sec, 100/min per IP).
@@ -1,5 +1,5 @@
1
1
  # {{displayName}} Workflow
2
- # Generated by cx-cli create workflow
2
+ # Generated by cxtms create workflow
3
3
 
4
4
  workflow:
5
5
  workflowId: "{{uuid}}"
@@ -1,272 +0,0 @@
1
- # Variable References & Expressions
2
-
3
- There are **two distinct syntaxes** for referencing variables, used in different contexts.
4
-
5
- ## Template Expressions: `{{ path }}` (in step inputs)
6
-
7
- Used in step `inputs` values. Resolves variable paths from scoped variables.
8
-
9
- ```yaml
10
- inputs:
11
- orderId: "{{ inputs.orderId }}" # Simple reference
12
- url: "{{ chopinConfig.baseUrl }}/api/v1" # String interpolation
13
- order: "{{ Data.GetOrder.order }}" # Raw object (single {{ }})
14
- name: "Order {{ Data.GetOrder.order.orderNumber }}" # String interpolation (multiple)
15
- ```
16
-
17
- **Key behavior**: A single `{{ path }}` returns the **raw object** (preserving type). Multiple `{{ }}` in a string returns string interpolation (each resolved value is `.ToString()`).
18
-
19
- ### Type Converters (prefix in {{ }})
20
-
21
- ```yaml
22
- organizationId: "{{ int organizationId }}"
23
- amount: "{{ decimal totalAmount }}"
24
- isActive: "{{ bool isActive }}"
25
- flag: "{{ boolOrFalse someFlag }}" # null -> false
26
- flagOn: "{{ boolOrTrue someFlag }}" # null -> true
27
- notes: "{{ emptyIfNull notes }}" # null -> ""
28
- notes: "{{ nullIfEmpty notes }}" # "" or whitespace -> null
29
- config: "{{ fromJson configJsonString }}" # JSON string -> dict/array
30
- payload: "{{ toJson someObject }}" # object -> JSON string
31
- name: "{{ trim value }}"
32
- search: "{{ luceneString query }}" # escape & quote for Lucene
33
- ```
34
-
35
- | Converter | Returns | Null handling |
36
- |-----------|---------|---------------|
37
- | `string` | `string` | null. Reads `Stream` to string if value is Stream |
38
- | `int` | `int` | Throws on null |
39
- | `decimal` | `decimal` | Throws on null |
40
- | `bool` | `bool` | Throws on null |
41
- | `boolOrFalse` | `bool` | `false` if null |
42
- | `boolOrTrue` | `bool` | `true` if null |
43
- | `datetime` | `DateTime` | Throws on null |
44
- | `emptyIfNull` | same type | `""` if null, `0` for int?, `0m` for decimal? |
45
- | `nullIfEmpty` | same type | `null` if empty/whitespace string or empty collection |
46
- | `luceneString` | `string` | null |
47
- | `transliterate` | `string` | null (Unicode -> ASCII via Unidecode) |
48
- | `transliterateUa` | `string` | null (Ukrainian-specific rules) |
49
- | `fromJson` | `dict` or `array` | null. Empty string -> empty dict |
50
- | `toJson` | `string` | `""` if null |
51
- | `trim` | `string` | null |
52
- | `toLocalTime` | `string` | null. Function-style: `{{ toLocalTime datePath 'timezoneId' 'format?' }}` |
53
-
54
- ### Value Directives (in YAML input mappings)
55
-
56
- **`expression`** -- Evaluate NCalc expression as a value:
57
- ```yaml
58
- amount:
59
- expression: "[price] * [quantity]"
60
- ```
61
-
62
- **`coalesce`** -- First non-null value from a list:
63
- ```yaml
64
- displayName:
65
- coalesce:
66
- - "{{ customer.name? }}"
67
- - "{{ customer.email? }}"
68
- - "Unknown"
69
- ```
70
-
71
- **`foreach`** (value context) -- Transform collections inline:
72
- ```yaml
73
- commodities:
74
- foreach: "sourceCommodities"
75
- item: "item" # default: "item"
76
- conditions: "[item.isActive] = true" # optional NCalc filter per item
77
- continueOnError: false # optional, skip errors
78
- mapping: # dict -> List<dict>, string -> List<object>
79
- name: "{{ item.name }}"
80
- quantity: "{{ item.qty }}"
81
- ```
82
-
83
- **`switch`** (value context) -- Value-based switch (case-insensitive match):
84
- ```yaml
85
- perLb:
86
- switch: "{{ contact.commissionTier }}"
87
- cases:
88
- "tier1": "{{ rate.customValues.commission_per_lb_tier1 }}"
89
- "tier2": "{{ rate.customValues.commission_per_lb_tier2 }}"
90
- default: "0"
91
- ```
92
-
93
- **`extends`** -- Extend/merge an existing object or array:
94
- ```yaml
95
- orderData:
96
- extends: "{{ existingOrder }}" # base object or array
97
- defaultIfNull: {} # fallback if extends is null
98
- mapping: # dict: merge overrides. array: append items
99
- status: "Updated"
100
- notes: "{{ newNotes }}"
101
- ```
102
-
103
- **`resolve`** -- Entity ID lookup by querying a GraphQL collection:
104
- ```yaml
105
- customerId:
106
- resolve:
107
- entity: "Contact" # Entity type (auto-pluralized for query)
108
- filter: "name={{ customerName }}" # Lucene filter (template-parsed)
109
- field: "contactId" # Field to return (default: <entity>Id)
110
- ```
111
- Results are batched and cached per unique `entity|filter|field` combination by `ResolvePreProcessor` before step execution. Cache misses return `null`. Useful inside `foreach` mappings where many items reference the same entity — only one query per unique filter value.
112
-
113
- **`$raw`** -- Prevent template parsing (pass as-is):
114
- ```yaml
115
- template:
116
- $raw: "This {{ won't }} be parsed"
117
- ```
118
-
119
- **`$eval`** -- Parse JSON string then evaluate as template:
120
- ```yaml
121
- dynamicConfig:
122
- $eval: "{{ configJsonString }}"
123
- ```
124
-
125
- **`decrypt`** / **`encrypt`** -- AES-CBC encryption (optional key/IV, has defaults):
126
- ```yaml
127
- apiKey:
128
- decrypt:
129
- encryptedValue: "{{ encryptedApiKey }}"
130
- key: "{{ encryptionKey }}" # optional Base64 AES key
131
- initializationVector: "{{ iv }}" # optional Base64 IV
132
- ```
133
-
134
- ---
135
-
136
- ## NCalc Expressions: `[variable]` (in conditions and expression directives)
137
-
138
- Used in `conditions[].expression`, `switch` case `when`, and `expression:` value directives. Variables use **square bracket** `[name]` syntax.
139
-
140
- ```yaml
141
- conditions:
142
- - expression: "[status] = 'Active' AND [amount] > 100"
143
- - expression: "isNullOrEmpty([Data.GetOrder.order?]) = false"
144
- - expression: "any([changes], [each.key] = 'Status') = true"
145
- ```
146
-
147
- **Parameter resolution rules**:
148
- - Empty strings are converted to `null` (so `""` is treated as no value)
149
- - Numeric strings are auto-converted to `decimal` when needed (e.g., `[price] > 100` works even if price is the string `"150"`)
150
- - Dot paths resolve deep: `[Activity.Step.output.nested.field]`
151
- - Optional suffix `?` prevents errors: `[order.customer?.name?]`
152
-
153
- ### Operators
154
-
155
- | Type | Operators |
156
- |------|-----------|
157
- | Comparison | `=`, `!=`, `<>`, `<`, `>`, `<=`, `>=` |
158
- | Logical | `AND`, `OR`, `NOT` (also `&&`, `\|\|`, `!`) |
159
- | Arithmetic | `+`, `-`, `*`, `/`, `%` |
160
- | Ternary | `if(condition, trueVal, falseVal)` |
161
- | Membership | `in(value, val1, val2, ...)` |
162
-
163
- ### Iterator Variables
164
-
165
- Functions use two iterator variable names:
166
- - **`[each.*]`** -- used by: `any`, `all`, `sum`, `join` (3-arg)
167
- - **`[item.*]`** -- used by: `first`, `last`, `groupBy`
168
-
169
- ### Collection Functions
170
-
171
- | Function | Description |
172
- |----------|-------------|
173
- | `any([items], [each.prop] = 'val')` | True if any item matches expression. Without expression: checks if collection contains the value |
174
- | `all([items], [each.prop] > 0)` | True if all items match. Returns `false` for null/empty collections |
175
- | `count([items])` | Count items in list or JToken. Returns `0` for non-collections |
176
- | `sum([items], [each.amount])` | Sum values as `decimal`. Optional `[each.*]` accessor. Skips nulls |
177
- | `first([items])` or `first([items], [item.name])` | First item or evaluate expression on first item. Returns `""` if empty |
178
- | `last([items])` or `last([items], [item.name])` | Last item or evaluate expression on last item. Returns `""` if empty |
179
- | `distinct([items])` | Remove duplicates. Uses deep comparison for dictionaries |
180
- | `reverse([items])` | Reverse collection or string |
181
- | `contains([source], 'needle')` | String contains, JArray contains, list contains, or dict key/value contains |
182
- | `removeEmpty([items])` | Remove null and whitespace-only items |
183
- | `concat([list1], [list2], ...)` | Concatenate multiple collections into flat list. Variadic args. Skips nulls |
184
- | `groupBy([items], [item.cat])` | Group by one or more key expressions. Returns `[{key, items}]`. Multi-key: keys joined with `\|` |
185
- | `join([items], [each.name], ',')` | Join collection with `[each.*]` accessor and separator (3-arg) |
186
- | `join([items], ',')` | Join collection directly with separator (2-arg) |
187
- | `split([str], ' ')` | Split string by first character of separator. Returns `List<string>` |
188
- | `elementAt([items], 0)` | Get element at index (zero-based) from list |
189
-
190
- ### String Functions
191
-
192
- | Function | Description |
193
- |----------|-------------|
194
- | `isNullOrEmpty([var])` | True if null, empty string, or empty list |
195
- | `length([var])` | String length or collection count. `0` for null strings and non-collections |
196
- | `lower([name])` / `upper([code])` | Case conversion. Handles string, JToken, any `.ToString()` |
197
- | `left([code], 3)` / `right([code], 3)` | Left/right N characters. Returns full string if shorter than N |
198
- | `substring([str], 0, 5)` | Extract substring starting at position for given length |
199
- | `replace([str], 'old', 'new')` | String replacement. Returns null if any arg is null |
200
- | `trim([value])` | Trim whitespace. Returns `""` for null |
201
- | `format('{0}-{1}', [prefix], [id])` | String.Format style. Variadic args. Returns null if format is null |
202
- | `base64([value])` / `fromBase64([encoded])` | Base64 encode/decode. Handles string, byte[], JToken |
203
- | `bool([value])` | Convert to boolean: null->`false`, empty string->`false`, "true"/"false"->parsed, non-zero number->`true`, any object->`true` |
204
- | `transliterate([value])` | Unicode to ASCII (Unidecode). Returns `""` for null |
205
- | `transliterateUa([value])` | Ukrainian-specific transliteration. Returns `""` for null |
206
- | `parseAddress([address])` | Parse address -> `{StreetNumber, StreetName}`. Handles US and EU formats |
207
-
208
- ### Date Functions
209
-
210
- | Function | Description |
211
- |----------|-------------|
212
- | `parseDate([str])` | Parse date string to DateTime. Supports common formats (ISO, US, etc.) |
213
- | `now()` | Current UTC `DateTime` |
214
- | `now('yyyy-MM-dd', 'en-US')` | Formatted current time as string |
215
- | `addDays([date], 30)` | Add days (decimal, can be negative). Accepts DateTime, DateTimeOffset, string |
216
- | `addHours([date], 2)` | Add hours (decimal, can be negative). Same type handling |
217
- | `formatDate([date], 'dd/MM/yyyy', 'en-US')` | Format date with culture. Accepts DateTime or string |
218
- | `dateFromUnix([unixTime])` | Unix timestamp (seconds) -> `DateTimeOffset`. Accepts int, long, decimal, string |
219
- | `dateToUtc([date])` or `dateToUtc([date], 'en-US')` | Convert to UTC. Optional culture for string parsing |
220
- | `toLocalTime([date], 'America/Chicago')` | Convert UTC date to local time in IANA timezone. Returns `DateTimeOffset`. Null-safe |
221
-
222
- ### Business Date Math (in Lucene filter expressions)
223
-
224
- The filter engine (`FilterBy`) supports business-aware date math units in Lucene date expressions:
225
-
226
- | Unit | Aliases | Description |
227
- |------|---------|-------------|
228
- | `BHOUR` | `BHOURS` | Add/subtract business hours (respects weekly schedule + holidays) |
229
- | `BDAY` | `BDAYS` | Add/subtract business days (skips non-working days) |
230
-
231
- **Usage**: These units are used in **Lucene filter strings** (not NCalc expressions). They require an `IBusinessDateMathResolver` and are resolved via the organization's business calendar.
232
-
233
- ```
234
- dueDate: [NOW TO NOW+3BDAYS]
235
- pickupDate: [* TO NOW-8BHOURS]
236
- ```
237
-
238
- The resolver loads `CalendarBusinessHour` (weekly schedule) and `CalendarAvailabilityBlock` (holidays) for the organization's `business`-type calendar, then walks through working time segments to compute the target date.
239
-
240
- ### Math Functions (NCalc built-in)
241
-
242
- `Abs(x)`, `Ceiling(x)`, `Floor(x)`, `Round(x, decimals)`, `Min(x, y)`, `Max(x, y)`, `Pow(x, y)`, `Sqrt(x)`, `Truncate(x)`
243
-
244
- Custom: `ceiling([value])` -- same as `Ceiling` but handles type conversion to double.
245
-
246
- ### Domain Functions
247
-
248
- | Function | Description |
249
- |----------|-------------|
250
- | `convertWeight([weight], 'Kg', 'Lb')` | Weight unit conversion. Returns `decimal` rounded to 5 places |
251
- | `convertDimension([length], 'Cm', 'In')` | Dimension unit conversion. Returns `decimal` rounded to 3 places |
252
-
253
- ---
254
-
255
- ## Property Path Syntax (in collection, mapping, variable paths)
256
-
257
- Used in `collection:` (foreach), `mapping:` (outputs), and variable resolution.
258
-
259
- | Pattern | Description | Example |
260
- |---------|-------------|---------|
261
- | `a.b.c` | Dot-separated nested path | `order.customer.name` |
262
- | `prop?` | Optional access (null if missing) | `order.customer?.name?` |
263
- | `list[0]` | Array index | `items[0]` |
264
- | `list[^1]` | Index from end (last item) | `items[^1]` |
265
- | `list[*]` | Flatten/wildcard (all items) | `containers[*].commodities` |
266
- | `list[**]` | Recursive flatten (all depths) | `containerCommodities[**]` |
267
- | `list[-1]` | Depth filter (leaves only) | `tree[**][-1]` |
268
- | `list[condition]` | Filter by condition | `items[status=Active]` |
269
- | `dict['key']` | Dictionary key access | `customValues['myField']` |
270
- | `list[*].{f1 f2}` | Field selector (projection) | `items[*].{name description}` |
271
- | `list[*].{alias:source}` | Field selector with alias | `items[*].{id:commodityId}` |
272
- | `list[*].{alias:_.parent}` | Field selector referencing parent | `items[*].{parentId:_.orderId}` |