@dboio/cli 0.11.4 → 0.13.2
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.
- package/README.md +126 -3
- package/bin/dbo.js +4 -0
- package/package.json +1 -1
- package/plugins/claude/dbo/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/dbo/commands/dbo.md +65 -244
- package/plugins/claude/dbo/docs/_audit_required/API/all.md +40 -0
- package/plugins/claude/dbo/docs/_audit_required/API/app.md +38 -0
- package/plugins/claude/dbo/docs/_audit_required/API/athenticate.md +26 -0
- package/plugins/claude/dbo/docs/_audit_required/API/cache.md +29 -0
- package/plugins/claude/dbo/docs/_audit_required/API/content.md +14 -0
- package/plugins/claude/dbo/docs/_audit_required/API/data_source.md +28 -0
- package/plugins/claude/dbo/docs/_audit_required/API/email.md +18 -0
- package/plugins/claude/dbo/docs/_audit_required/API/input.md +25 -0
- package/plugins/claude/dbo/docs/_audit_required/API/instance.md +28 -0
- package/plugins/claude/dbo/docs/_audit_required/API/log.md +8 -0
- package/plugins/claude/dbo/docs/_audit_required/API/media.md +12 -0
- package/plugins/claude/dbo/docs/_audit_required/API/output_by_entity.md +12 -0
- package/plugins/claude/dbo/docs/_audit_required/API/upload.md +7 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-api-syntax.md +1487 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-problems-code.md +111 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-problems-performance.md +109 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-problems-syntax.md +97 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-product-market.md +119 -0
- package/plugins/claude/dbo/docs/_audit_required/dbo-white-paper.md +125 -0
- package/plugins/claude/dbo/docs/dbo-cheat-sheet.md +323 -0
- package/plugins/claude/dbo/docs/dbo-cli-readme.md +2222 -0
- package/plugins/claude/dbo/docs/dbo-core-entities.md +878 -0
- package/plugins/claude/dbo/docs/dbo-output-customsql.md +677 -0
- package/plugins/claude/dbo/docs/dbo-output-query.md +967 -0
- package/plugins/claude/dbo/skills/cli/SKILL.md +62 -246
- package/src/commands/add.js +366 -62
- package/src/commands/build.js +102 -0
- package/src/commands/clone.js +602 -139
- package/src/commands/diff.js +4 -0
- package/src/commands/init.js +16 -2
- package/src/commands/input.js +3 -1
- package/src/commands/mv.js +12 -4
- package/src/commands/push.js +265 -70
- package/src/commands/rm.js +16 -3
- package/src/commands/run.js +81 -0
- package/src/lib/config.js +39 -0
- package/src/lib/delta.js +7 -1
- package/src/lib/diff.js +24 -2
- package/src/lib/filenames.js +120 -41
- package/src/lib/ignore.js +6 -0
- package/src/lib/input-parser.js +13 -4
- package/src/lib/scripts.js +232 -0
- package/src/migrations/006-remove-uid-companion-filenames.js +181 -0
|
@@ -0,0 +1,967 @@
|
|
|
1
|
+
# Output — Query
|
|
2
|
+
|
|
3
|
+
A **Query output** is a named, configurable SQL query stored in the dbo core database. Its structure maps directly to SQL: a base entity (FROM), output values (SELECT), join records (JOIN), and filter records (WHERE / HAVING). Rendered results can be styled through a template stored in a content record.
|
|
4
|
+
|
|
5
|
+
**Output type covered**: `Type = 'Query'` (default). For `CustomSQL` see `output-customsql.md`.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Data Model
|
|
13
|
+
|
|
14
|
+
### `output` table
|
|
15
|
+
|
|
16
|
+
Query metadata and FROM table
|
|
17
|
+
|
|
18
|
+
| Column | Attributes | Purpose | Notes |
|
|
19
|
+
| :------------------ | :------------------ | :------------------------------------- | :----------------------------------------------------------- |
|
|
20
|
+
| `AppID` | `SMALLINT NN FK` | Parent app | |
|
|
21
|
+
| `Name` | `VARCHAR(100) NN` | Display name | Also used as the asset filename |
|
|
22
|
+
| `UID` | `VARCHAR(22) NN AUTOGENERATED` | Stable identifier | Auto-generated on creation; cannot be updated. Used in API calls: `/api/output/{UID}` |
|
|
23
|
+
| `Type` | `ENUM` | Output rendering mode | `Query` type required |
|
|
24
|
+
| `BaseEntityID` | `BIGINT NN FK` | Table set in the FROM clause | Required for query output |
|
|
25
|
+
| `TemplateContentID` | `BIGINT FK` | Content record holding the template | Overridable per request via `_template` or an open embed block |
|
|
26
|
+
| `Limit` | `INT` | Rows returned | |
|
|
27
|
+
| `RowsPerPage` | `INT` | Rows per page when paginating | Use with `_page=N` at request time |
|
|
28
|
+
| `Public` | `BIT default:false` | Bypass authentication | When true, no login is required to execute |
|
|
29
|
+
| `AdhocFilters` | `BIT default:false` | Allow dynamic column filtering | When true, columns without pre-configured filters can be targeted with `_filter` |
|
|
30
|
+
| `Cache` | `BIT default:false` | Cache the result set | Output structure is always cached automatically; this flag caches the results set beyond runtime. Not required to acces `:cache` modifier on render tokens. |
|
|
31
|
+
| `Parameters` | `VARCHAR(200)` | Fixed parameters injected at execution | |
|
|
32
|
+
| `DataSourceID` | `BIGINT FK` | Table data source | If entity is present in multiple envirnment schemas, this will allow one to switch between connection strings |
|
|
33
|
+
| `CustomSQL` | `TEXT` | Custom SQL body | Must be null for Query type |
|
|
34
|
+
| `Active` | `BIT default:true` | Enable or disable this output | When false, requests return an asset-not-found exception |
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### `output_value` table
|
|
39
|
+
|
|
40
|
+
One output_value record per SELECT column
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
| Column | Attributes | Purpose | Notes |
|
|
44
|
+
| :----------------------------- | :----------------------------------------------------------- | :------------------------------------- | :----------------------------------------------------------- |
|
|
45
|
+
| `OutputID` | `BIGINT NN FK` | Parent output | |
|
|
46
|
+
| `UID` | `VARCHAR(22) NN AUTOGENERATED` | Stable identifier | Auto-generated on creation; cannot be updated |
|
|
47
|
+
| `OrderNumber` | `SMALLINT NN` | Column rendering order | |
|
|
48
|
+
| `Title` | `VARCHAR(100) NN` | Column label | Not available as a column reference |
|
|
49
|
+
| `ShortName` | `VARCHAR(45)` | Token reference key | `#{value@ShortName}`; if null, only UID references this column |
|
|
50
|
+
| `ReferencedEntityColumnID` | `BIGINT FK` | Column to SELECT | |
|
|
51
|
+
| `CustomSQL` | `LONGTEXT` | Custom expression | Accommodates `CASE` statements, format, and other computations in the SELECT |
|
|
52
|
+
| `OutputValueEntityColumnRelID` | `BIGINT FK` | Join context for display column | If null, defaults to base entity |
|
|
53
|
+
| `Hide` | `BIT default:false` | Exclude from SELECT output | Useful for columns that are desrired in the `WHERE`, `HAVING`, or `ORDER BY` but not desired the `SELECT` |
|
|
54
|
+
| `Aggregate` | `ENUM( 'Group', 'Count', 'Sum', 'Average', 'Min', 'Max')` | | |
|
|
55
|
+
| `Summary` | `ENUM( 'Count', 'Sum', 'Min', 'Max', 'Average')` | Row summary aggregate | Computed across all rows; renders in `<#_summary>` |
|
|
56
|
+
| `Shared` | `BIT default:false` | Shared value optimization | When true and all rows share the same value, renders once in `<#_shared>` |
|
|
57
|
+
| `Distinct` | `BIT default:false` | Add DISTINCT to SELECT and Aggregation | |
|
|
58
|
+
| `SortOrder` | `SMALLINT` | Sort priority | 1 = primary sort; null = unsorted |
|
|
59
|
+
| `SortType` | `ENUM( 'Ascending', 'Descending')` `default:ascending` | Sort type | |
|
|
60
|
+
| `Format` | `VARCHAR(30)` | Default value format | .NET format string (e.g. `C2`, `d`) — inline token modifier overrides per request |
|
|
61
|
+
| `EnforceSecurity` | `ENUM( 'Add', 'Edit', 'Delete', 'View', 'Execute', 'Associate')` | Auto-filter rows by user access | Restricts result rows to those the user can access for the given operation; see **EnforceSecurity** below |
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### EnforceSecurity
|
|
66
|
+
When set, dbo generates an automatic WHERE IN clause at query execution time. This restricts results to rows accessible to the logged in user for the specified operation (View, Add, Edit, Delete, Execute, Associate). This access is controlled by the security table. Security access is cached at request scope. The output must include the primary key of the source entity — if absent, dbo throws an error at query execution time. For more information see authenticate.md (pending).
|
|
67
|
+
|
|
68
|
+
> **Note**: `secure=false` on an embed bypasses the Execute permission check for that embed only. It does *not* suppress `EnforceSecurity` row filtering — the WHERE IN clause is generated at query-build time, before the embed's security bypass takes effect.
|
|
69
|
+
|
|
70
|
+
###### #enf-sec
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
### `output_value_entity_column_rel` table — one record per JOIN clause
|
|
76
|
+
|
|
77
|
+
Attach to the output (`OutputID`) to define joins shared across all output values.
|
|
78
|
+
|
|
79
|
+
| Column | Attributes | Purpose | Notes |
|
|
80
|
+
| :------------------------- | :-------------------------------------------------------------- | :---------------------- | :--------------------------------------------------------------- |
|
|
81
|
+
| `OutputID` | `BIGINT NN FK` | Parent output | |
|
|
82
|
+
| `OutputValueID` | `BIGINT FK` | must be null for `Query` type output. | This column is only for `Legacy` type output's per-column join system |
|
|
83
|
+
| `UID` | `VARCHAR(22) NN` | Stable identifier | Auto-generated on creation; cannot be updated |
|
|
84
|
+
| `SourceEntityColumnID` | `BIGINT NN FK` | Join from column | |
|
|
85
|
+
| `ReferencedEntityColumnID` | `BIGINT FK` | Join to column | dbo infers ON clause table from the column; NN for `Query` type |
|
|
86
|
+
| `JoinType` | `ENUM( 'InnerJoin', 'LeftOuterJoin', 'RightOuterJoin', 'FullOuterJoin') NN` | Join type | |
|
|
87
|
+
| `OrderNumber` | `BIGINT NN` | Join sequence (1-based) | Determines the order joins are applied |
|
|
88
|
+
| `SourceJoinID` | `BIGINT FK` | Join from a prior aliased join | Self-reference — see **Child joins** below |
|
|
89
|
+
| `Alias` | `VARCHAR(50)` | Explicit table alias | Used when two joins target the same table |
|
|
90
|
+
| `CustomSQL` | `VARCHAR(1000)` | Custom ON clause | Replaces the standard column-equality condition |
|
|
91
|
+
|
|
92
|
+
> **Note**: Join columns are not required to be PK/FK pairs— as in SQL, any columns of matching type can be joined (e.g., varchar to varchar).
|
|
93
|
+
|
|
94
|
+
**Child joins**: set `SourceJoinID` to the ID of a prior join record to join from it. `SourceJoinID` is **required** when a table has been joined to elsewhere in the query. Without it, the engine cannot determine which table alias is the intended source and throws an ambiguous-source error. For simple joins, where the source table appears only once, `SourceJoinID` may be omitted.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
### `output_value_filter` table — one record per WHERE / HAVING condition
|
|
99
|
+
|
|
100
|
+
Create a filter record linked to an output value to filter on a specific column.
|
|
101
|
+
|
|
102
|
+
| Column | Attributes | Purpose | Notes |
|
|
103
|
+
| :----------------- | :------------------------------------------------------------------------------------------------------------------ | :----------------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `OutputID` | `BIGINT FK` | Output-level filter attachment | Mutually exclusive with `OutputValueID` |
|
|
105
|
+
| `OutputValueID` | `BIGINT FK` | Column-level filter attachment | Mutually exclusive with `OutputID` |
|
|
106
|
+
| `UID` | `VARCHAR(22) NN` | Stable identifier | Auto-generated on creation; cannot be updated |
|
|
107
|
+
| `OrderNumber` | `BIGINT NN` | Filter sequence (1-based) | No default — must be supplied on create |
|
|
108
|
+
| `ShortName` | `VARCHAR(45)` | URL parameter key | Required for prompted filters: `?_filter@ShortName=value` |
|
|
109
|
+
| `FilterType` | `ENUM( 'Exact', 'Contains', 'StartsWith', 'EndsWith', 'LessThan', 'LessThanOrEqualTo', 'GreaterThan', 'GreaterThanOrEqualTo') default:Exact` | Comparison type | |
|
|
110
|
+
| `PrimaryValue` | `VARCHAR(100)` | Stored filter value | Can be overriden when `Prompted` set to true. Preset strings also accepted; see **Filter Presets** |
|
|
111
|
+
| `Prompted` | `BIT default:false` | Accept URL parameter | When true, value can be set in query string — see **Filters** |
|
|
112
|
+
| `Required` | `BIT default:false` | Require a value | When true, if no prompted value is supplied at runtime, query throws exception |
|
|
113
|
+
| `Inclusive` | `BIT default:true` | Include or exclude | true = include matches (default); false = exclude matches (NOT) |
|
|
114
|
+
| `Operator` | `ENUM( 'OR', 'AND') default:OR` | Multi-value logic | Applies when multiple values are passed |
|
|
115
|
+
| `ApplyToAggregate` | `BIT default:false` | Filter aggregated column | When true, generates HAVING instead of WHERE |
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
**Output attached tokens**: Filters applied at the output level can pass values into the query without restricting the result set. Unlike row filters, these do not generate a WHERE clause — instead, the supplied value is available to calculated columns, SQL formulas, and CASE expressions within the output. Common uses include parameterized formatting, dynamic thresholds, and conditional logic that depends on request or session context.
|
|
120
|
+
|
|
121
|
+
**Filter value tokens:** Values passed to the query via `_filter` parameter are available inside CustomSQL expressions as filter_value tokens. The filter_value key uses filter uid or shortname as the reference: e.g. `#{filter_value@Shortname}`. These can be combined with output-level filters (set `OutputID` with no `OutputValueID`) which are not appended to the WHERE clause.
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## Create
|
|
130
|
+
|
|
131
|
+
Submit via `POST /api/input/submit?_confirm=true`. All output records can be created in a single transaction using pairing keys (`add1`, `add2`, …). Cross-references within the batch use `=add1` syntax. See `input.md` for details.
|
|
132
|
+
|
|
133
|
+
Column references use UID, which in the dbo core are prettified as `entityName.ColumnName` . See `schema-catalog.md` for system entity column names.
|
|
134
|
+
|
|
135
|
+
### Example — User list, filter by FirstName, join DefaultMedia, return FullPath
|
|
136
|
+
|
|
137
|
+
Five records in one transaction: output → two output values (FirstName, FullPath) → one join (user → media) → one filter (FirstName, prompted).
|
|
138
|
+
|
|
139
|
+
> **Note**: the owning app (`AppID`) must already exist — it cannot be created in the same transaction as the output records.
|
|
140
|
+
|
|
141
|
+
```css
|
|
142
|
+
POST /api/input/submit?_confirm=true
|
|
143
|
+
|
|
144
|
+
# add1 — output record (FROM clause)
|
|
145
|
+
RowID:add1;column:output.AppID = [app.UID OR app.AppID]
|
|
146
|
+
RowID:add1;column:output.Name = User List
|
|
147
|
+
RowID:add1;column:output.Type = Query
|
|
148
|
+
RowID:add1;column:output.BaseEntityID = user
|
|
149
|
+
|
|
150
|
+
# add2 — output_value: FirstName (base entity, no join needed)
|
|
151
|
+
RowID:add2;column:output_value.OutputID = add1
|
|
152
|
+
RowID:add2;column:output_value.OrderNumber = 1
|
|
153
|
+
RowID:add2;column:output_value.Title = First Name
|
|
154
|
+
RowID:add2;column:output_value.ShortName = FirstName
|
|
155
|
+
RowID:add2;column:output_value.ReferencedEntityColumnID = user.FirstName
|
|
156
|
+
|
|
157
|
+
# add3 — output_value: FullPath (from joined media table)
|
|
158
|
+
RowID:add3;column:output_value.OutputID = add1
|
|
159
|
+
RowID:add3;column:output_value.OrderNumber = 2
|
|
160
|
+
RowID:add3;column:output_value.Title = Media Path
|
|
161
|
+
RowID:add3;column:output_value.ShortName = MediaPath
|
|
162
|
+
RowID:add3;column:output_value.ReferencedEntityColumnID = media.FullPath
|
|
163
|
+
|
|
164
|
+
# add4 — output_value_entity_column_rel: join user.DefaultMediaID → media.MediaID
|
|
165
|
+
RowID:add4;column:output_value_entity_column_rel.OutputID = add1
|
|
166
|
+
RowID:add4;column:output_value_entity_column_rel.SourceEntityColumnID = user.DefaultMediaID
|
|
167
|
+
RowID:add4;column:output_value_entity_column_rel.ReferencedEntityColumnID = media.MediaID
|
|
168
|
+
RowID:add4;column:output_value_entity_column_rel.JoinType = LeftOuterJoin
|
|
169
|
+
RowID:add4;column:output_value_entity_column_rel.OrderNumber = 1
|
|
170
|
+
|
|
171
|
+
# add5 — output_value_filter: filter FirstName by prompted URL param
|
|
172
|
+
RowID:add5;column:output_value_filter.OutputValueID = add2
|
|
173
|
+
RowID:add5;column:output_value_filter.ShortName = FirstName
|
|
174
|
+
RowID:add5;column:output_value_filter.FilterType = Contains
|
|
175
|
+
RowID:add5;column:output_value_filter.Prompted = 1
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Execute with filter: `GET /api/output/[uid]?_filter@FirstName:contains=a`
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## Execute / Read
|
|
185
|
+
|
|
186
|
+
```css
|
|
187
|
+
GET /api/output/[uid]
|
|
188
|
+
GET /api/o/[uid]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Request parameters
|
|
192
|
+
|
|
193
|
+
| Parameter | Description |
|
|
194
|
+
| :------------------------------------ | :----------------------------------------------------------- |
|
|
195
|
+
| `_filter@ShortName=value` | Filter applies to pre-configured `Prompted` filter records |
|
|
196
|
+
| `_filter@ShortName:restrictive=value` | Cannot be overridden by any parameter at a higher level in the request stack |
|
|
197
|
+
| `_limit=N` | Number of rows returned |
|
|
198
|
+
| `_limit=M-N` | Range of rows returned |
|
|
199
|
+
| `_rowcount=false` | Omit pagination metadata from response — removes a second result table with `Offset`, `MaxRows`, and `TotalRowCount`; result rows are unchanged |
|
|
200
|
+
| `_template={uid\|name}` | Override template — pass a content UID or built-in template name (e.g., `json_indented`, `json`, `csv`, `xml`). See **System templates** |
|
|
201
|
+
| `_sort=ShortName:[asc\|desc]` | Sort column and direction |
|
|
202
|
+
| `_search@ShortName1,ShortName2=term` | Full-text search across specified columns. **See Performance caution —** `_search` |
|
|
203
|
+
| `_page=N` | Page number (when `RowsPerPage` is set) |
|
|
204
|
+
| `_rowsperpage=N` | Override `RowsPerPage` for this request |
|
|
205
|
+
| `_debug_sql=true` | Return the generated SQL as `text/plain` instead of executing — administrator only. See **_debug_sql** below |
|
|
206
|
+
|
|
207
|
+
> **Note**: `_filter` defaults to `:overridable` — inner embed values win over outer frame values. To lock a filter against embed override, use `_filter@Field:restrictive=value`. See **Request Stack** below.
|
|
208
|
+
|
|
209
|
+
**Performance caution — `_rowcount`**: adds `SQL_CALC_FOUND_ROWS` to the SELECT and runs a second `SELECT FOUND_ROWS()` after the main query. MySQL must count all rows matching the WHERE clause regardless of `LIMIT`, which defeats index range scans on large tables. Avoid on high-traffic or large-dataset outputs unless pagination is a hard requirement.
|
|
210
|
+
|
|
211
|
+
**Performance caution — `_search`**: generates a `CONCAT` of all specified columns (each cast to `VARCHAR`, null-handled) and applies `LIKE '%term%'` to the result. The leading wildcard prevents index use. Expensive on large tables — always pair with `_limit` to cap results. Use `@Shortname` to specify columns to reduce the CONCAT width.
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
### Request Stack
|
|
216
|
+
|
|
217
|
+
When a content record renders, each `<#_embed>` brings its own URL parameters into scope. To resolve a request parameter, dbo works through the embed chain and applies the rule for that parameter's modifier. The original HTTP request is the outermost level; nested embeds build inward from there. Parameters from outer levels are available to inner ones — the modifier determines whether an inner value holds firm, yields to an outer one, or merges with values from multiple levels.
|
|
218
|
+
|
|
219
|
+
Stack depth is capped at 100 frames.
|
|
220
|
+
|
|
221
|
+
> **Note**: Pass `_debug:embeds=true` on any content request and view source to inspect the live stack. Each embed's output includes a `RequestStack:` block showing frame depth, entity identity, and the parameters each frame contributed:
|
|
222
|
+
>
|
|
223
|
+
> ```css
|
|
224
|
+
> REQUEST + _request@_debug:embeds=true
|
|
225
|
+
> \ content Name=my-page, UID=... +
|
|
226
|
+
> \ \ #_embed url=/api/output/[uid]?_limit=2 + _limit=2 ; _request@_limit=2
|
|
227
|
+
> \ \ \ output [UID=[uid], Name=my-output] +
|
|
228
|
+
> ```
|
|
229
|
+
>
|
|
230
|
+
> `\` marks frame depth. `+` separates the frame from its contributed parameters.
|
|
231
|
+
|
|
232
|
+
#### Modifier reference
|
|
233
|
+
|
|
234
|
+
| Modifier | Behavior | Default for |
|
|
235
|
+
|----------|----------|-------------|
|
|
236
|
+
| `:restrictive` | Param value cannot be replaced by an outer frame. | `_limit` |
|
|
237
|
+
| `:overridable` | Param value can be replaced by an outer frame. | `_filter`, `_display`, `_strict`, `_request` |
|
|
238
|
+
| `:expansive` | All frames contribute — values from every matching frame are merged. | — (opt-in only) |
|
|
239
|
+
|
|
240
|
+
Unmodified parameters inherit the default modifier for their type.
|
|
241
|
+
|
|
242
|
+
#### Usage patterns
|
|
243
|
+
|
|
244
|
+
**Lock a filter against embed override:**
|
|
245
|
+
|
|
246
|
+
```http
|
|
247
|
+
GET /api/output/[uid]?_filter@Status:restrictive=active
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
In this example, the `:restrictive` modifier locks `Status` at the request frame — no nested embed using the default `:overridable` can replace it. Without it, `_filter` defaults to `:overridable` and a nested embed targeting the same filter would take precedence.
|
|
251
|
+
|
|
252
|
+
**Allow a limit to be overridden by a nested embed:**
|
|
253
|
+
|
|
254
|
+
```css
|
|
255
|
+
<#_embed url="/api/output/[uid]?_limit:overridable=10" name="my.list"/>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The `:overridable` modifier marks this limit as a default — any higher-level caller providing `_limit` takes precedence. Without it, `_limit` defaults to `:restrictive` and no outer frame can replace it.
|
|
259
|
+
|
|
260
|
+
**Accumulate values across frames:**
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
GET /api/output/[uid]?_filter@Tags:expansive=admin
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```css
|
|
267
|
+
<#_embed url="/api/output/[uid]?_filter@Tags:expansive=active" name="my.list"/>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Both values are applied — the resolved filter covers both `admin` and `active`.
|
|
271
|
+
|
|
272
|
+
> **Note**: The `output_value_filter.Operator` setting (`OR` / `AND`, default `OR`) controls how multiple prompted values are combined in SQL. This applies equally to values accumulated across frames via `:expansive` — cross-frame values merge into the same filter array and Operator governs their SQL combination.
|
|
273
|
+
|
|
274
|
+
### Request Keys
|
|
275
|
+
|
|
276
|
+
URL parameters are available as request tokens regardless of which syntax is used. Any URL parameter `key=value` resolves at `#{request@key}`. The explicit form `_request@Key=value` is equivalent — both produce the same token on the request stack.
|
|
277
|
+
|
|
278
|
+
| Syntax | URL parameter | Token |
|
|
279
|
+
|--------|---------------|-------|
|
|
280
|
+
| Concise | `?status=active` | `#{request@status}` |
|
|
281
|
+
| Verbose | `?_request@Status=active` | `#{request@Status}` |
|
|
282
|
+
| Quoted reference | `?_filter@Status=active` | `#{request@"_filter@Status"}` |
|
|
283
|
+
|
|
284
|
+
Use the concise form for application-defined parameters. Use the verbose form when the key would otherwise be parsed as a system token type — for example, when the key begins with `_filter`, `_limit`, or another reserved prefix.
|
|
285
|
+
|
|
286
|
+
###### #req-key
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
### Request Tokens
|
|
290
|
+
|
|
291
|
+
`#{request@Key}` reads the value of URL parameter `Key` from the current request context. The parameter can be supplied in either the concise or verbose form — see **Request Keys**.
|
|
292
|
+
|
|
293
|
+
```css
|
|
294
|
+
#{request@Status} ← reads ?Status= or ?_request@Status=
|
|
295
|
+
#{request@CustomerID!0} ← falls back to 0 if not present
|
|
296
|
+
#{request@Mode!#{session@DefaultMode}} ← token as default
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Parameters supplied on the outer request URL are available to inner embeds by default — they flow inward through the embed chain. A parameter set inside an embed is scoped to that embed and does not propagate outward.
|
|
300
|
+
|
|
301
|
+
When the full parameter key contains a reserved string— such as in `#{request@"_filter@Status"}` — quote the reference to prevent the parser from treating it as a property boundary. The quotes are stripped from the resolved reference; single quotes are equivalent.
|
|
302
|
+
|
|
303
|
+
> **Note**: All standard request parameters (`_filter`, `_sort`, `_limit`, `_template`, `_page`, `_rowsperpage`) are resolved via the stack on every output request.
|
|
304
|
+
|
|
305
|
+
###### #req-tok
|
|
306
|
+
|
|
307
|
+
### _debug_sql
|
|
308
|
+
|
|
309
|
+
Append `_debug_sql=true` to any output request to return the generated SQL instead of executing. The response is `text/plain` — no template rendering occurs.
|
|
310
|
+
|
|
311
|
+
Output format:
|
|
312
|
+
|
|
313
|
+
```css
|
|
314
|
+
-- Parameters:
|
|
315
|
+
|
|
316
|
+
SET @ParamName := value;
|
|
317
|
+
|
|
318
|
+
-- Query:
|
|
319
|
+
|
|
320
|
+
SELECT ... FROM ... WHERE ... LIMIT ...;
|
|
321
|
+
SELECT 0 AS `Offset`, N AS `MaxRows`, FOUND_ROWS() AS `TotalRowCount`
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
One declaration per filter parameter that has a value. Parameters with no value are omitted. Debug output always shows MySQL-style `SET @ParamName := value` syntax regardless of the actual data source type.
|
|
325
|
+
|
|
326
|
+
The second `SELECT` is a pagination metadata query present by default; omit with `_rowcount=false`.
|
|
327
|
+
|
|
328
|
+
Column aliases in the SELECT use the format `{outputValueUID}_{ShortName}`.
|
|
329
|
+
|
|
330
|
+
**Requires administrator access.** Non-administrators receive a 401 response.
|
|
331
|
+
|
|
332
|
+
###### #deb-sql
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
### System templates
|
|
336
|
+
|
|
337
|
+
Pass a system template identifier to `_template` to override the output's configured template for the request. `_template` takes precedence over the output record's `TemplateContentID`; pass a content UID to use a custom template instead.
|
|
338
|
+
|
|
339
|
+
| Identifier | Content-Type | Output structure | Notes |
|
|
340
|
+
|------|-------------|-----------------|-------|
|
|
341
|
+
| `json_indented` | `application/json` | Pretty-printed JSON array, one object per row | Direct serialization — native types preserved, column names deduplicated. Preferred for API consumption. |
|
|
342
|
+
| `json_raw` | `application/json` | Compact JSON array, one object per row | Same serialization path as `json_indented` — native types, no HTML escaping, no whitespace. |
|
|
343
|
+
| `html` | `text/html` | `<table>` with header row, one `<tr>` per row, summary row if configured | Renders `<#_summary>` when any output value has `Summary` set. |
|
|
344
|
+
| `csv` | `application/json`* | Header row + one row per result, comma-separated | *Content-Type header incorrectly returns `application/json`. Template-rendered; duplicate ShortNames produce duplicate columns. |
|
|
345
|
+
| `txt` | `text/plain` | Header row + one row per result, space-separated | Template-rendered; duplicate ShortNames produce duplicate columns. |
|
|
346
|
+
| `xml` | `text/xml` | *(empty)* | Returns HTTP 200 with empty body — template file is empty. Not usable. |
|
|
347
|
+
|
|
348
|
+
#### json_indented & json_raw
|
|
349
|
+
|
|
350
|
+
Both templates serialize the result set as a JSON array of row objects — no wrapper, no entity metadata. Column names are taken from each output value's `Title` field. Primary key columns and internal `Metadata:Count` columns are excluded.
|
|
351
|
+
|
|
352
|
+
```json
|
|
353
|
+
[
|
|
354
|
+
{ "FirstName": "Jane", "LastName": "Doe", "Email": "jane@example.com" },
|
|
355
|
+
{ "FirstName": "John", "LastName": "Smith", "Email": "john@example.com" }
|
|
356
|
+
]
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
The default `json` template wraps the result in an entity object with structural metadata:
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{
|
|
363
|
+
"recordState": "MultiRecord",
|
|
364
|
+
"metadata": { "entityID": 42, "uid": "...", "displayName": "User", ... },
|
|
365
|
+
"records": [ { ... } ]
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
`json_indented` and `json_raw` use the same serialization path — the only difference is whitespace. Both preserve native types (numbers, nulls) without HTML escaping.
|
|
370
|
+
|
|
371
|
+
**`_format_values=true`**: pass alongside `_template=json_raw` to apply token formatting to cell values before serialization. Without it, raw database values are used directly.
|
|
372
|
+
|
|
373
|
+
> **Note**: If two output values share the same `Title`, their JSON keys collide — the last value wins. Ensure output value titles are unique when using these templates.
|
|
374
|
+
|
|
375
|
+
###### #sys-tem
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
#### Custom template
|
|
380
|
+
|
|
381
|
+
Pass a content UID to load a content record as the template:
|
|
382
|
+
|
|
383
|
+
```css
|
|
384
|
+
GET /api/output/[uid]?_template=[content-uid]
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The content record's `Extension` field determines the output format: `html` → HTML, `json` → JSON, `xml` → XML, `css` → CSS, `js` → JSON, anything else → plain text.
|
|
388
|
+
|
|
389
|
+
#### Priority
|
|
390
|
+
|
|
391
|
+
1. `_template` request parameter (this section)
|
|
392
|
+
2. `TemplateContentID` on the output record
|
|
393
|
+
3. Framework default — JSON
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
## Embed Syntax
|
|
400
|
+
|
|
401
|
+
Outputs are embedded inside **content records** — content is the primary medium through which outputs are composed into pages. Output templates are themselves content records. See `content.md` (pending).
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
### Closed embed
|
|
406
|
+
|
|
407
|
+
```css
|
|
408
|
+
<#_embed url="/api/output/[uid]"
|
|
409
|
+
name="my.output"
|
|
410
|
+
target="MyOutput"/>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
### Open embed
|
|
416
|
+
|
|
417
|
+
An open embed passes inline section markup to override the output's configured template for this embed instance. The template is format-agnostic — sections can contain markup, CSV, JSON, plain text, or any structured output.
|
|
418
|
+
|
|
419
|
+
```css
|
|
420
|
+
<#_embed.list url="/api/output/[uid]" name="myoutput">
|
|
421
|
+
<#_row>
|
|
422
|
+
#{value@Name},#{value@Email}
|
|
423
|
+
</#_row>
|
|
424
|
+
<#_noresult>
|
|
425
|
+
no results
|
|
426
|
+
</#_noresult>
|
|
427
|
+
</#_embed.list>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Any alphanumeric suffix after the `.` is the **serialization id**. A serialization id is required on any open embed that contains another open embed; it must match on open and close tags and be unique at the same nesting level.
|
|
431
|
+
|
|
432
|
+
### Embed attribute reference
|
|
433
|
+
|
|
434
|
+
| Attribute | Required | Default | Notes |
|
|
435
|
+
| :---------------- | :------- | :------ | :----------------------------------------------------------- |
|
|
436
|
+
| `url` | yes | — | `/api/output/[uid]` or `/api/o/[uid]` |
|
|
437
|
+
| `name` | no | — | Static name for an embed |
|
|
438
|
+
| `target` | no | — | Named context for `:cached` tokens and `#{payload$...}`; comma-separated for multiple |
|
|
439
|
+
| `secure` | no | true | false bypasses access control for this request |
|
|
440
|
+
| `execute` | no | true | false skips the call entirely |
|
|
441
|
+
| `hide` | no | false | true executes query and caches results but does render a payload |
|
|
442
|
+
| `payload` | no | true | Controls whether the embed's rendered output is cached for `#{payload$...}` access. When `hide=true`, defaults to false — set `payload=true` explicitly to cache a hidden embed's output |
|
|
443
|
+
| `catch_exception` | no | — | Codes caught silently; true catches all |
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
###### #emb-att
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
## Template Tags
|
|
455
|
+
|
|
456
|
+
A template is a content record (`TemplateContentID`) containing section markup. Sections render in this order:
|
|
457
|
+
|
|
458
|
+
| Step | Tag | Renders when | Frequency |
|
|
459
|
+
| :--- | :----------------- | :---------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- |
|
|
460
|
+
| 1 | `<#_shared>` | Rows exist + shared columns defined | Once, before header |
|
|
461
|
+
| 2 | `<#_header>` | Defined in template | Once |
|
|
462
|
+
| 3 | `<#_row>` | Rows exist | Once per row |
|
|
463
|
+
| — | `<#_rowdelimiter>` | Rows exist | Between rows (not after last) |
|
|
464
|
+
| 4 | `<#_summary>` | Rows exist + summary values defined | Once, after last row |
|
|
465
|
+
| 5 | `<#_footer>` | Defined in template | Once |
|
|
466
|
+
| — | `<#_noresult>` | Zero rows | Once |
|
|
467
|
+
| — | `<#_empty>` | Zero rows | Once (after noresult if both defined) |
|
|
468
|
+
|
|
469
|
+
> **Note**: `<#cell>...</#cell>` defines a per-column template block. Use this pattern when writing a cross-query template that must work across outputs without referencing specific column names. Place `<#cell/>` inside `<#_row>` or `<#_header>` where column output should be injected. Within `<#cell>`, `#{title}` and `#{value}` render a column's title and returned value in this scope.
|
|
470
|
+
|
|
471
|
+
### Value tag
|
|
472
|
+
|
|
473
|
+
`<#_value.ShortName>` outputs a column value by ShortName. It supports an open form with attributes for conditional rendering:
|
|
474
|
+
|
|
475
|
+
| Attribute | Notes |
|
|
476
|
+
| :--------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
477
|
+
| `value` | Comparison value; `_null` matches null database values (not empty strings) |
|
|
478
|
+
| `modifier` | Comparison type, logic, and inversion — comma-separated for multiple. Comparison (default: exact, case-insensitive): `contains`, `startswith`, `endswith`, `lessthan`, `lessthanorequalto`, `greaterthan`, `greaterthanorequalto`. Logic: `and` (all `value` constraints must match; default is OR). Inversion: `exclude` (flips the result). |
|
|
479
|
+
|
|
480
|
+
String renders block conditionally
|
|
481
|
+
|
|
482
|
+
```css
|
|
483
|
+
-- renders WHEN Status IS NOT NULL
|
|
484
|
+
<#_value.Status>
|
|
485
|
+
status set
|
|
486
|
+
</#_value.Status>
|
|
487
|
+
|
|
488
|
+
-- renders WHEN Status IS NOT NULL
|
|
489
|
+
<#_value.Status modifier="exclude" value="_null">
|
|
490
|
+
#{value@Status}
|
|
491
|
+
</#_value.Status>
|
|
492
|
+
|
|
493
|
+
-- renders WHEN Status IS NULL
|
|
494
|
+
<#_value.Status value="_null">
|
|
495
|
+
No status set
|
|
496
|
+
</#_value.Status>
|
|
497
|
+
|
|
498
|
+
-- renders WHEN Price < 100
|
|
499
|
+
<#_value.Price modifier="lessthan" value="100">
|
|
500
|
+
under $100
|
|
501
|
+
</#_value.Price>
|
|
502
|
+
|
|
503
|
+
-- renders WHEN ( Status = 'active' OR Status = 'verified' )
|
|
504
|
+
<#_value.Status value="active,verified">
|
|
505
|
+
active and verified
|
|
506
|
+
</#_value.Status>
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
###### #tem-tag
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
## Data Tokens
|
|
516
|
+
|
|
517
|
+
### Token syntax
|
|
518
|
+
|
|
519
|
+
```css
|
|
520
|
+
#{type@reference$target!default:modifier;comment}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
> **Note**: type attribute must come first; after that order does not matter.
|
|
524
|
+
|
|
525
|
+
| Part | Symbol | Notes |
|
|
526
|
+
| :-------- | :----- | :----------------------------------------------------------- |
|
|
527
|
+
| type | — | See **Token type reference** below. Common: `value`, `id`, `uid`, `output` `session`, `request` |
|
|
528
|
+
| reference | `@` | Column ShortName, UID, output property, or named key |
|
|
529
|
+
| target | `$` | Named embed context — required with `:cached` |
|
|
530
|
+
| default | `!` | Fallback value if token resolves empty |
|
|
531
|
+
| modifier | `:` | Transforms output or controls resolution timing |
|
|
532
|
+
| comment | `;` | Unrendered string |
|
|
533
|
+
|
|
534
|
+
###### #tok-syn
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
### Token type reference
|
|
539
|
+
|
|
540
|
+
| Type | Reference syntax | Resolved in | Notes |
|
|
541
|
+
| :-------------------------- | :------------------------------------------------------------ | :--------------- | :------------------------------------------------------------------------------------------------- |
|
|
542
|
+
| `value` (`data.value`) | `@ShortName`, `@UID` | Result-set pass | Field value for current row |
|
|
543
|
+
| `id` (`data.id`) | — | Result-set pass | Numeric PK of current row's base entity |
|
|
544
|
+
| `uid` (`data.uid`) | — | Result-set pass | UID of current row's base entity |
|
|
545
|
+
| `output.row` | `@index` | Result-set pass | Current row number; `:global` for cross-page value |
|
|
546
|
+
| `output.row` | `@count:returned` | Result-set pass | Count of returned rows |
|
|
547
|
+
| `output.row` | `@count:global` | Result-set pass | Total rows across all pages; requires `RowsPerPage` |
|
|
548
|
+
| `output.page` | `@index` | Result-set pass | Current page number; requires `RowsPerPage` |
|
|
549
|
+
| `output.page` | `@count` | Result-set pass | Total page count; requires `RowsPerPage` |
|
|
550
|
+
| `output.[ColumnName]` | — | Result-set pass | Any property of the output record — e.g. `#{output.Name}`, `#{output.AppID}` |
|
|
551
|
+
| `output_value.[ColumnName]` | — | Result-set pass | Any property of the output_value record; requires `<#cell>` context |
|
|
552
|
+
| `summary` | `@ShortName` | Result-set pass | Aggregate value for the column; aggregate function configured in `output_value.Summary` |
|
|
553
|
+
| `payload` | `$Target` | Template pass | Rendered output of named embed; resolved after all embeds execute |
|
|
554
|
+
| `session` | `@UserID`, `@Email`, `@FirstName`, `@LastName`, `@uid`, others | Template pass | Resolved in TemplateContentID content render — see **Token scope** below |
|
|
555
|
+
| `request` | `@ParamName` | Template pass | URL query parameter — see **Token scope** below |
|
|
556
|
+
| `input` | `@add1.id`, `@add2.id`, … | Input batch | ID of a record created in the same input batch |
|
|
557
|
+
| `unique` | — | Template pass | Generates a GUID |
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
###### #tok-typ
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
### Token modifiers
|
|
566
|
+
|
|
567
|
+
Modifiers refine how a token resolves or renders. Multiple modifiers may be combined with comma separation. Unless noted, all modifiers apply to all token types.
|
|
568
|
+
|
|
569
|
+
| Modifier | Effect | Applies to |
|
|
570
|
+
| :--------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------- |
|
|
571
|
+
| `:cached$Target` | Defers resolution until all embeds finish executing; `$Target` names the embed context | `value`, `request`, `session` |
|
|
572
|
+
| `:raw` | Defers output injection to the final render pass via placeholder — does not re-parse token syntax within field values | all |
|
|
573
|
+
| `:no_escape` | Bypasses all format-based escaping (HTML, JSON, XML, CSV) — use for trusted raw output | all |
|
|
574
|
+
| `:escape_json` | Forces JSON-safe escaping regardless of output format — use when embedding a value in a JSON string in non-JSON output | all |
|
|
575
|
+
| `:escape_html` | Applies HTML encoding; deprecated — HTML output format already encodes by default | all |
|
|
576
|
+
| `:url_encode` | URL percent-encodes the value via `Uri.EscapeDataString()` | all |
|
|
577
|
+
| `:url_decode` | URL-decodes the value via `Uri.UnescapeDataString()` | all |
|
|
578
|
+
| `:truncate` | Truncates to 100 characters, appending "..." | all |
|
|
579
|
+
| `:global` | Returns value across all pages; requires `RowsPerPage` | `output.row` |
|
|
580
|
+
| `:uid` | 22-character base-62 UID (default `unique` format) | `unique` |
|
|
581
|
+
| `:guid` | Standard GUID format | `unique` |
|
|
582
|
+
| `:integer` | Auto-incrementing integer per render; combined with `:request`, returns a consistent random 6-digit number for the request | `unique` |
|
|
583
|
+
| `:request` | Caches the generated value for the duration of the request — same value returned on repeated use | `unique` |
|
|
584
|
+
| `:{format}` | .NET format string applied via `.ToString(format)` — see **Modifier Syntax** below | `value`, `request`, `summary` |
|
|
585
|
+
|
|
586
|
+
###### #tok-mod
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
### Value output escaping
|
|
592
|
+
|
|
593
|
+
By default, token values are escaped based on the output format: HTML output applies `HttpUtility.HtmlEncode()`, JSON output applies `JsonConvert.ToString()`, XML applies `SecurityElement.Escape()`, and plain-text applies no escaping. This is independent of token type — all types use the same format-driven escaping rules.
|
|
594
|
+
|
|
595
|
+
Three modifiers override this behavior:
|
|
596
|
+
|
|
597
|
+
**`:no_escape`** — Suppresses all format-based escaping and returns the raw value string. Use for trusted content that must render verbatim — for example, HTML markup stored in a field, or pre-escaped content injected inline.
|
|
598
|
+
|
|
599
|
+
**`:raw`** — Defers output injection to a final render pass by substituting a placeholder during template rendering, then replacing it with the resolved value after all rendering completes. Useful when a token in an embed URL would otherwise be replaced prematurely. Does not re-parse token syntax within field values — a database field containing a string like `#{content.UID}` is treated as literal text, not resolved.
|
|
600
|
+
|
|
601
|
+
**`:escape_json`** — Forces JSON-safe escaping regardless of the current output format. Use when embedding a value inside a JSON string literal in an HTML or plain-text context.
|
|
602
|
+
|
|
603
|
+
###### #val-esc
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
### Modifier Syntax
|
|
609
|
+
|
|
610
|
+
Any .NET format string used as a modifier formats the resolved value before output:
|
|
611
|
+
|
|
612
|
+
```css
|
|
613
|
+
#{value@CreatedOn:d} → short date (e.g. 1/3/2024)
|
|
614
|
+
#{value@CreatedOn:M} → month and day (e.g. January 3)
|
|
615
|
+
#{request@Price:f2} → 12.50
|
|
616
|
+
#{summary@Count:f0} → 42
|
|
617
|
+
#{value:c2} → $12.50
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
###### #mod-syn
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
### Row data
|
|
626
|
+
|
|
627
|
+
| Token | Canonical form | Returns |
|
|
628
|
+
| :----------------------------- | :------------------------ | :------------------------------------------ |
|
|
629
|
+
| `#{value@ShortName}` | `#{data.value@ShortName}` | Field value for the current row |
|
|
630
|
+
| `#{value@ShortName!fallback}` | — | With default if empty |
|
|
631
|
+
| `#{value@ShortName:no_escape}` | — | Raw unescaped value |
|
|
632
|
+
| `#{id}` | `#{data.id}` | Numeric PK of the current row's base entity |
|
|
633
|
+
| `#{uid}` | `#{data.uid}` | UID of the current row's base entity |
|
|
634
|
+
|
|
635
|
+
The short forms (`#{value@ShortName}`, `#{id}`, `#{uid}`) are backwards-compatibility aliases for `#{data.value@ShortName}`, `#{data.id}`, `#{data.uid}`.
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
### Output metadata
|
|
640
|
+
|
|
641
|
+
| Token | Returns |
|
|
642
|
+
| :----------------------------- | :------------------------------------------------------------------------------------------ |
|
|
643
|
+
| `#{output.row@index}` | Current row number (within returned rows) |
|
|
644
|
+
| `#{output.row@index:global}` | Current row number (across all pages) |
|
|
645
|
+
| `#{output.row@count:returned}` | Total returned row count |
|
|
646
|
+
| `#{output.row@count:global}` | Total row count across all pages |
|
|
647
|
+
| `#{output.page@index}` | Current page number |
|
|
648
|
+
| `#{output.page@count}` | Total page count |
|
|
649
|
+
| `#{output.PropertyName}` | Any property of the output record — e.g. `#{output.Name}`, `#{output.UID}`, `#{output.AppID}` |
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
### Output value metadata (column context)
|
|
654
|
+
|
|
655
|
+
| Token | Returns |
|
|
656
|
+
| :----------------------------- | :-------------------------------------- |
|
|
657
|
+
| `#{output_value.Title}` | Column title (`output_value.Title`) |
|
|
658
|
+
| `#{output_value.ShortName}` | Column ShortName |
|
|
659
|
+
| `#{output_value.UID}` | Column UID |
|
|
660
|
+
| `#{output_value.PropertyName}` | Any property of the output_value record |
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
### Token scope
|
|
665
|
+
|
|
666
|
+
Token resolution happens in two sequential passes:
|
|
667
|
+
|
|
668
|
+
**Content render pass** (first): The template renders with full token access — session, request, site, meta, unique, payload, and include tokens all resolve. Their values are baked into the template string before row iteration begins.
|
|
669
|
+
|
|
670
|
+
**Output row render pass** (second): Section blocks (`<#_row>`, `<#_header>`, etc.) are processed per-row. This pass resolves `value`, `id`, `uid`, `output.*`, `output_value.*`, and `summary` tokens only. Session, request, and similar tokens are not re-processed here — they were already resolved in the first pass.
|
|
671
|
+
|
|
672
|
+
The practical consequence: `#{session@UserID}` anywhere in the template (including inside `<#_row>`) resolves to the same value for every row, baked in before the row loop runs. This is expected behavior for session data. For per-row variation, use `#{value@...}` tokens backed by output values.
|
|
673
|
+
|
|
674
|
+
###### #tok-sco
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
### Summary (use in `<#_summary>` or `<#_footer>`)
|
|
679
|
+
|
|
680
|
+
The `summary` token renders an aggregate value for a column. The aggregate function — `sum`, `count`, `min`, `max`, or `average` — is configured in the `output_value.Summary` field on the database record. It cannot be specified or overridden in the template.
|
|
681
|
+
|
|
682
|
+
```css
|
|
683
|
+
#{summary@ShortName}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
`ShortName` references the output_value's ShortName. The aggregate runs over all returned rows. Place in `<#_summary>` (renders once after the last row, only when summary values are defined) or `<#_footer>`.
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
### Cached embed reads
|
|
691
|
+
|
|
692
|
+
`:cached` defers token resolution until after all embeds execute. `$Target` is required — omitting it causes ambiguous resolution when multiple embeds are active. A token will only resolve when one value is available in the cached record set.
|
|
693
|
+
|
|
694
|
+
```css
|
|
695
|
+
<#_embed url="/api/output/[uid]" target="MyOutput"/>
|
|
696
|
+
#{value@SomeField:cached$MyOutput}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
### Payload tokens
|
|
702
|
+
|
|
703
|
+
`#{payload$Target}` accesses the rendered output of a named embed after execution. The embed must have a matching `target="..."` attribute.
|
|
704
|
+
|
|
705
|
+
| Token | Returns |
|
|
706
|
+
| :------------------ | :----------------------------------------------------------------------------------------- |
|
|
707
|
+
| `#{payload$Target}` | Full rendered output of the embed (HTML, JSON, or whatever the template produces) |
|
|
708
|
+
|
|
709
|
+
Payload tokens resolve in the same deferred pass as `:cached` value tokens — after all embeds have executed.
|
|
710
|
+
|
|
711
|
+
```css
|
|
712
|
+
<#_embed url="/api/output/[uid]" target="UserData" hide="true" payload="true"/>
|
|
713
|
+
|
|
714
|
+
#{payload$UserData} → rendered output of the embed
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
`hide="true"` is commonly paired with payload reads — the embed executes and its rendered output is available via `#{payload$Target}` without rendering at embed site. Useful if you want to pass an embed's payload as a filter value in another embed's URL, for example.
|
|
718
|
+
|
|
719
|
+
###### #pay-tok
|
|
720
|
+
|
|
721
|
+
---
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
## Filters
|
|
726
|
+
|
|
727
|
+
### Static filter
|
|
728
|
+
|
|
729
|
+
Set `PrimaryValue` on an `output_value_filter` record. Always applied — no URL parameter needed.
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
### Prompted filter modifiers
|
|
734
|
+
|
|
735
|
+
Set `Prompted = 1` and `ShortName` on the filter record to enable URL parameter at request time:
|
|
736
|
+
|
|
737
|
+
```css
|
|
738
|
+
?_filter@ShortName=value
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
Multiple values: `?_filter@Status=active,pending` — combined with `OR` (default) or `AND` per `Operator` field.
|
|
742
|
+
|
|
743
|
+
Combine with `Required = 1` on the filter record to throw a 500 error if no value is supplied.
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
### Request stack and filter priority
|
|
748
|
+
|
|
749
|
+
When an output is embedded, its caller can pass filter parameters down to it. The **request stack** tracks these values across embed levels — an embed may pass filters to its embedded outputs, and those outputs may be embedded in turn.
|
|
750
|
+
|
|
751
|
+
The `:restrictive` and `:overridable` modifiers control how a filter value behaves within this stack:
|
|
752
|
+
|
|
753
|
+
| Modifier | Behavior |
|
|
754
|
+
| :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
755
|
+
| `:restrictive` | The filter value is a hard requirement. It cannot be replaced or removed by any other layer of the stack. Use when a security constraint or scoping rule must be enforced regardless of caller context. |
|
|
756
|
+
| `:overridable` | The filter value is a default. A higher layer of the request stack can replace it. Use when the output provides a sensible default that callers may legitimately need to change. |
|
|
757
|
+
|
|
758
|
+
When neither modifier is present, the default behavior is equivalent to `:overridable` — the value is applied unless overridden.
|
|
759
|
+
|
|
760
|
+
```css
|
|
761
|
+
?_filter@ShortName:restrictive=value
|
|
762
|
+
?_filter@ShortName:overridable=value
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
These modifiers are only meaningful when the output is invoked through an embed chain. For direct HTTP requests, there is no outer stack and the distinction has no effect.
|
|
766
|
+
|
|
767
|
+
###### #req-sta
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
### Ad-hoc filter modifiers
|
|
772
|
+
|
|
773
|
+
When `AdhocFilters = 1` on the output, filter type and behavior can be set inline in the URL key without pre-configured filter records. Modifiers are comma-separated after the colon; multiple values are comma-separated after `=`.
|
|
774
|
+
|
|
775
|
+
```css
|
|
776
|
+
?_filter@Field:mod1,mod2=value1,value2
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
**Comparison modifiers**
|
|
780
|
+
|
|
781
|
+
| Modifier | SQL Equivalent |
|
|
782
|
+
| :---------------------- | :---------------------------- |
|
|
783
|
+
| `:exact` | `= '[FilterValue]'` (default) |
|
|
784
|
+
| `:contains` | `LIKE '%[FilterValue]%'` |
|
|
785
|
+
| `:startswith` | `LIKE '[FilterValue]%'` |
|
|
786
|
+
| `:endswith` | `LIKE '%[FilterValue]'` |
|
|
787
|
+
| `:lessthan` | `< [FilterValue]` |
|
|
788
|
+
| `:lessthanorequalto` | `<= [FilterValue]` |
|
|
789
|
+
| `:greaterthan` | `> [FilterValue]` |
|
|
790
|
+
| `:greaterthanorequalto` | `>= [FilterValue]` |
|
|
791
|
+
|
|
792
|
+
**Inclusion modifier** (maps to `Inclusive`):
|
|
793
|
+
|
|
794
|
+
| Modifier | Effect |
|
|
795
|
+
| :--------- | :---------------------------------------------- |
|
|
796
|
+
| `:exclude` | Excludes matching rows. The default is include. |
|
|
797
|
+
|
|
798
|
+
**Operator modifier** (maps to `Operator`, applies when multiple values supplied):
|
|
799
|
+
|
|
800
|
+
| Modifier | Effect |
|
|
801
|
+
| :------- | :--------------------------- |
|
|
802
|
+
| `:and` | All values must match |
|
|
803
|
+
| `:or` | Any value matches (default) |
|
|
804
|
+
|
|
805
|
+
Modifiers can be combined in any order:
|
|
806
|
+
|
|
807
|
+
```css
|
|
808
|
+
?_filter@Price:lessthan=100
|
|
809
|
+
?_filter@Tags:contains,and=admin,active → Tags LIKE '%admin%' AND Tags LIKE '%active%'
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
#### Multi-value behavior
|
|
815
|
+
|
|
816
|
+
Multiple values are comma-separated after `=`. The `:or` / `:and` modifier controls how they combine (default: OR).
|
|
817
|
+
|
|
818
|
+
```css
|
|
819
|
+
?_filter@Status=active,pending → Status = 'active' OR Status = 'pending'
|
|
820
|
+
?_filter@Tags:contains,and=admin,active → Tags LIKE '%admin%' AND Tags LIKE '%active%'
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
**Caution**: with `:exclude` and multiple values— `:exclude` with default OR combines as `NOT x OR NOT y`, since a single field cannot simultaneously equal two different values, this condition is always true — no rows are excluded at all. To exclude rows matching *any* value, use `:and,exclude`.
|
|
824
|
+
|
|
825
|
+
```css
|
|
826
|
+
?_filter@FirstName:contains,exclude=foo,bar → NOT LIKE '%foo%' OR NOT LIKE '%bar%' (no filtering)
|
|
827
|
+
?_filter@FirstName:and,contains,exclude=foo,bar → NOT LIKE '%foo%' AND NOT LIKE '%bar%' (excludes rows containing either)
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
###### #mul-val
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
### Cross-column filter logic
|
|
835
|
+
|
|
836
|
+
Each `output_value_filter` record generates one WHERE clause condition. Multiple records are ANDed together at the clause level — there is no parenthetical grouping and no way to express OR across separate filter records via the filter system alone. The `Operator` field (`AND`/`OR`) applies only within a single multi-value prompted filter.
|
|
837
|
+
|
|
838
|
+
To express cross-column OR, conditional grouping, or comparisons between columns, use a `CASE` command in `output_value.CustomSQL` and add a filter to that column.
|
|
839
|
+
|
|
840
|
+
```sql
|
|
841
|
+
-- output_value.CustomSQL example CASE:
|
|
842
|
+
CASE
|
|
843
|
+
WHEN #{value@Amount} > #{value@Threshold}
|
|
844
|
+
THEN 1
|
|
845
|
+
ELSE 0
|
|
846
|
+
END
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
> **Note**: this is an advanced pattern. For construction steps, schema details, and worked examples see the `building-guide.md`.
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
### Filter presets
|
|
854
|
+
|
|
855
|
+
These preset values can be stored in `output_value_filter.PrimaryValue` or set as `_filter` parameter value.
|
|
856
|
+
|
|
857
|
+
| Preset | Resolves to |
|
|
858
|
+
| :----------------------------------------------------------- | :----------------------------------------------------------- |
|
|
859
|
+
| `CurrentUserID` | Current user's ID (unchanged under impersonation) |
|
|
860
|
+
| `LoggedInUserID` | Logged-in user's ID (may differ from logged-in user under impersonation) |
|
|
861
|
+
| `Now` | Current datetime |
|
|
862
|
+
| `Today`, `ThisWeek`, `ThisMonth`, `ThisYear`, `Yesterday`, `LastWeek`, `LastMonth`, `LastQuarter`, `LastYear` | Date presets generate SQL date ranges (`BETWEEN start AND end`) |
|
|
863
|
+
| `IpAddress` | Requester's IPV4 address |
|
|
864
|
+
| `SessionUID` | Current session UID |
|
|
865
|
+
| `CurrentSiteID` | Current site associated to content in request |
|
|
866
|
+
| `_security@view` | Row IDs the user has View permission for |
|
|
867
|
+
| `_security@add` | Row IDs the user has Add permission for |
|
|
868
|
+
| `_security@edit` | Row IDs the user has Edit permission for |
|
|
869
|
+
| `_security@delete` | Row IDs the user has Delete permission for |
|
|
870
|
+
| `_security@associate` | Row IDs the user has Associate permission for |
|
|
871
|
+
| `_security@execute` | Row IDs the user has Execute permission for |
|
|
872
|
+
| `_security@impersonate` | Row IDs the user has Impersonate permission for |
|
|
873
|
+
|
|
874
|
+
###### #fil-pre
|
|
875
|
+
|
|
876
|
+
## Pagination
|
|
877
|
+
|
|
878
|
+
Set `RowsPerPage` on the output record or via `_rowsperpage` parameter to control how many rows are visible per page. Pagination is activated by passing `?_page=N` at request time.
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
---
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
## Caching
|
|
887
|
+
|
|
888
|
+
`Cache = 1` caches the result set. Output structure (query config, joins, values) is cached automatically.
|
|
889
|
+
|
|
890
|
+
Output metadata is cached under two separate keys. `output:meta:[uid]` is populated during normal rendering. `secure:output:meta:[uid]` is populated when the DBO security system internally evaluates the output for row-level access control — external parameters are blocked in that mode to prevent request values from influencing security decisions. Both entries are independent and must be cleared explicitly:
|
|
891
|
+
|
|
892
|
+
```css
|
|
893
|
+
GET /api/cache/refresh?_key=output:meta:[output.UID]
|
|
894
|
+
GET /api/cache/refresh?_key=secure:output:meta:[output.UID]
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
`voidcache` is accepted on any API endpoint to flush all caches globally.
|
|
898
|
+
|
|
899
|
+
```css
|
|
900
|
+
GET /api/output/[uid]?voidcache=true
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
---
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
## Legacy
|
|
908
|
+
|
|
909
|
+
### User-defined sections
|
|
910
|
+
|
|
911
|
+
`<#SectionName>` defines a named block that calling templates can target by name OR if `<#SectionName>` maps to a output_value.Shortname, will render if that row has a value :
|
|
912
|
+
|
|
913
|
+
```css
|
|
914
|
+
<#TagName>
|
|
915
|
+
Some string which can comprise #{value@Shortname1}
|
|
916
|
+
</#TagName>
|
|
917
|
+
<#_row>
|
|
918
|
+
<#TagName/>
|
|
919
|
+
<#ShortName2>
|
|
920
|
+
#{value@ShortName2} has a value
|
|
921
|
+
</#ShortName2>
|
|
922
|
+
</#_row>
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
### Legacy token types
|
|
930
|
+
|
|
931
|
+
| Token | Current form | Notes |
|
|
932
|
+
| :----------------- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------ |
|
|
933
|
+
| `#{entity_column}` | `#{data.entity_column.uid}` | Renders the EntityColumn UID for the current column in context; source-confirmed but behavior uncertain — do not use in new templates |
|
|
934
|
+
| `#{row}` | `#{output.row@index}` | Current row number; use explicit form |
|
|
935
|
+
| `#{page}` | `#{output.page@index}` | Current page number; use explicit form |
|
|
936
|
+
| `#{count}` | `#{output.row@count:returned}` | Returned row count; use explicit form |
|
|
937
|
+
| `#{column}` | `#{data.value@ShortName}` | Field value by column reference; use `#{value@ShortName}` |
|
|
938
|
+
| `#{title}` | `#{output_value.Title}` | Column title in `<#cell>` context; use explicit form |
|
|
939
|
+
|
|
940
|
+
### Legacy columns
|
|
941
|
+
|
|
942
|
+
| Column | Table | Notes |
|
|
943
|
+
| :----------- | :------------- | :--------------------------------------------------------------------------------- |
|
|
944
|
+
| `MaxRows` | `output` | Superseded by `Limit`; both are read for backward compatibility |
|
|
945
|
+
| `DatePart` | `output_value` | Date component extraction; exists in schema, not actively supported |
|
|
946
|
+
| `OutputType` | `output_value` | Controls EntityColumn vs CustomSQL expression mode; exists in schema, not actively supported |
|
|
947
|
+
|
|
948
|
+
### Legacy URL parameters
|
|
949
|
+
|
|
950
|
+
| Parameter | Superseded by | Notes |
|
|
951
|
+
| :----------- | :------------ | :----------------------------------------------- |
|
|
952
|
+
| `_maxrows=N` | `_limit=N` | Exact equivalent — use `_limit` |
|
|
953
|
+
| `_rows=M,N` | `_limit=M-N` | Row range using hyphen separator in current form |
|
|
954
|
+
| `_template=json` | `_template=json_indented` or `_template=json_raw` | Routes through the DBO content rendering pipeline — values serialized as strings, duplicate `ShortName` values produce duplicate keys, slower than direct serialization. Prefer `json_indented` or `json_raw`. |
|
|
955
|
+
| `_escape_html=false` | `:no_escape` modifier | Disabled HTML encoding globally for the request. HTML output only. Use `:no_escape` on specific tokens instead. |
|
|
956
|
+
|
|
957
|
+
### Legacy embed syntax
|
|
958
|
+
|
|
959
|
+
`<EmbedToken>` and `<#embed>` were the predecessors to `<#_embed>`.
|
|
960
|
+
|
|
961
|
+
```css
|
|
962
|
+
<!-- Legacy -->
|
|
963
|
+
<EmbedToken url="/api/output/[uid]" name="my.output" />
|
|
964
|
+
|
|
965
|
+
<!-- Legacy -->
|
|
966
|
+
<#embed url="/api/output/[uid]" name="my.output"/>
|
|
967
|
+
```
|