@dboio/cli 0.11.4 → 0.15.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.
- package/README.md +183 -3
- package/bin/dbo.js +6 -0
- package/package.json +1 -1
- package/plugins/claude/dbo/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/dbo/commands/dbo.md +66 -243
- 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 +2279 -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 +63 -246
- package/src/commands/add.js +373 -64
- package/src/commands/build.js +102 -0
- package/src/commands/clone.js +719 -212
- package/src/commands/deploy.js +9 -2
- package/src/commands/diff.js +7 -3
- package/src/commands/init.js +16 -2
- package/src/commands/input.js +3 -1
- package/src/commands/login.js +30 -4
- package/src/commands/mv.js +28 -7
- package/src/commands/push.js +298 -78
- package/src/commands/rm.js +21 -6
- package/src/commands/run.js +81 -0
- package/src/commands/tag.js +65 -0
- package/src/lib/config.js +67 -0
- package/src/lib/delta.js +7 -1
- package/src/lib/deploy-config.js +137 -0
- package/src/lib/diff.js +28 -5
- package/src/lib/filenames.js +198 -54
- package/src/lib/ignore.js +6 -0
- package/src/lib/input-parser.js +13 -4
- package/src/lib/scaffold.js +1 -1
- package/src/lib/scripts.js +232 -0
- package/src/lib/tagging.js +380 -0
- package/src/lib/toe-stepping.js +2 -1
- package/src/migrations/006-remove-uid-companion-filenames.js +181 -0
- package/src/migrations/007-natural-entity-companion-filenames.js +165 -0
- package/src/migrations/008-metadata-uid-in-suffix.js +70 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
# Output — CustomSQL
|
|
2
|
+
|
|
3
|
+
A **CustomSQL output** executes a raw SQL body stored in `output.CustomSQL` rather than constructing a query from output value configuration. Use when the required query cannot be expressed through the join model — e.g. needs subqueries, window functions, unions, transactions, etc.
|
|
4
|
+
|
|
5
|
+
**Output type covered**: `Type = 'CustomSQL'`. For the standard join model see `output-query.local.md`. For schema-driven column selection see `output-entity.md`.
|
|
6
|
+
|
|
7
|
+
**Endpoint**: `GET /api/output/{uid}`
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Data Model
|
|
14
|
+
|
|
15
|
+
### `output` table
|
|
16
|
+
|
|
17
|
+
| Column | Attributes | Purpose | Notes |
|
|
18
|
+
| :------------------ | :----------------------------- | :---------------------------------- | :-------------------------------------------------------------------------------------- |
|
|
19
|
+
| `AppID` | `SMALLINT NN FK` | Parent app | |
|
|
20
|
+
| `Name` | `VARCHAR(100) NN` | Display name | Also used as the asset filename |
|
|
21
|
+
| `UID` | `VARCHAR(22) NN AUTOGENERATED` | Stable identifier | Auto-generated on creation; cannot be updated. Used in API calls: `/api/output/{UID}` |
|
|
22
|
+
| `Type` | `ENUM` | Output rendering mode | Must be `CustomSQL` for this mode |
|
|
23
|
+
| `BaseEntityID` | `BIGINT NN FK` | Data source reference | Required — determines which database connection the SQL executes against. Not used for query construction. |
|
|
24
|
+
| `TemplateContentID` | `BIGINT FK` | Content record holding the template | Overridable per request via `_template` or an open embed block |
|
|
25
|
+
| `Public` | `BIT default:false` | Bypass authentication | When true, no login is required to execute |
|
|
26
|
+
| `Cache` | `BIT default:false` | Cache the result set | Output structure is always cached automatically; this flag caches the result set beyond runtime |
|
|
27
|
+
| `Parameters` | `VARCHAR(200)` | Fixed parameters injected at execution | Format: `key=value&key2=value2` |
|
|
28
|
+
| `DataSourceID` | `BIGINT FK` | Override data source | If set, overrides the data source derived from `BaseEntityID` |
|
|
29
|
+
| `CustomSQL` | `TEXT` | SQL body | Full SQL statement. Before execution: `#{data_source:uid}` tokens are resolved; filter variables (`@ShortName`) are declared and assigned in a syntax appropriate to the data source type. |
|
|
30
|
+
| `Active` | `BIT default:true` | Enable or disable this output | When false, requests return an asset-not-found exception |
|
|
31
|
+
|
|
32
|
+
> **Note**: `Limit`, `MaxRows`, and `RowsPerPage` are not applied to CustomSQL outputs — paging logic runs only for Query-type outputs.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### `output_value_filter` table
|
|
37
|
+
|
|
38
|
+
Each filter record declares a SQL variable (`@ShortName`) available in the SQL body. Reference it directly: `WHERE col = @ShortName`. A variable declaration is prepended before the query **only when a value is present** — from `PrimaryValue`, `output.Parameters`, or a `_filter@ShortName` request parameter. If no value is supplied, no declaration is generated and the variable is unset. The declaration syntax is determined by the data source type ( `data_source.DataSourceTypeID`, resolved via `entity.DataSourceID` ).
|
|
39
|
+
|
|
40
|
+
| Column | Attributes | Purpose | Notes |
|
|
41
|
+
| :------------- | :----------------------------------------------------------- | :-------------------- | :----------------------------------------------------------- |
|
|
42
|
+
| `OutputID` | `BIGINT FK` | Parent output | |
|
|
43
|
+
| `UID` | `VARCHAR(22) NN` | Stable identifier | Auto-generated on creation; cannot be updated |
|
|
44
|
+
| `ShortName` | `VARCHAR(45)` | Variable name | Injects `@ShortName` into the SQL body |
|
|
45
|
+
| `FilterPreset` | `ENUM` | System-provided value | Overrides `PrimaryValue`; see **Filter Presets** below |
|
|
46
|
+
| `PrimaryValue` | `VARCHAR(100)` | Static value | Always applied when set; overridable by `_filter` if `Prompted = 1` |
|
|
47
|
+
| `Prompted` | `BIT default:false` | Accept URL parameter | When true, value can be supplied via `?_filter@ShortName=value` |
|
|
48
|
+
| `Required` | `BIT default:false` | Require a value | When true and no value supplied, query returns no results |
|
|
49
|
+
|
|
50
|
+
> **Note**: `FilterType`, `Operator`, and `Inclusive` are not applicable in CustomSQL mode — comparisons are expressed in the SQL body.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## Writing the SQL Body
|
|
57
|
+
|
|
58
|
+
The `output.CustomSQL` field holds the full SQL statement. Token substitution is applied before execution.
|
|
59
|
+
|
|
60
|
+
```sql
|
|
61
|
+
SELECT
|
|
62
|
+
u.UserID,
|
|
63
|
+
u.FirstName,
|
|
64
|
+
u.LastName,
|
|
65
|
+
r.RoleName
|
|
66
|
+
FROM user u
|
|
67
|
+
INNER JOIN user_role ur ON ur.UserID = u.UserID
|
|
68
|
+
INNER JOIN role r ON r.RoleID = ur.RoleID
|
|
69
|
+
WHERE u.Active = 1
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
### Column aliases and token references
|
|
75
|
+
|
|
76
|
+
SQL column aliases become the resolution keys for `#{value@...}` tokens in the template. There are no `output_value` records with `ShortName` to map — the alias is used directly.
|
|
77
|
+
|
|
78
|
+
```sql
|
|
79
|
+
SELECT u.FirstName AS FirstName, u.LastName AS LastName
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`#{value@FirstName}` and `#{value@LastName}` resolve in the output template.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### Filter variables
|
|
87
|
+
|
|
88
|
+
Each `output_value_filter` record with a `ShortName` declares a SQL variable. Reference it in the SQL body as `@ShortName`:
|
|
89
|
+
|
|
90
|
+
```sql
|
|
91
|
+
WHERE col1 = @Status AND col2 = @UserID
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A variable declaration is prepended before the query only when a value is present. If no value is supplied and `Required = 0`, no declaration is generated — write the SQL accordingly.
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
### Data source tokens
|
|
99
|
+
|
|
100
|
+
To reference a physical database name in the SQL body — for cross-database queries — use the data source token:
|
|
101
|
+
|
|
102
|
+
```sql
|
|
103
|
+
SELECT *
|
|
104
|
+
FROM #{data_source.PhysicalName@core}.dbo.user
|
|
105
|
+
WHERE Active = 1
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`#{data_source.PhysicalName@shortname}` resolves to the physical database name of the data source whose `ShortName` matches. `core` refers to the instance's primary data source. This token is resolved at query-build time by a dedicated utility, separate from the standard token render pipeline.
|
|
109
|
+
|
|
110
|
+
Available properties:
|
|
111
|
+
|
|
112
|
+
| Property | Returns |
|
|
113
|
+
| :------------- | :----------------------------- |
|
|
114
|
+
| `.PhysicalName`| Physical database name |
|
|
115
|
+
| `.Name` | Display name |
|
|
116
|
+
| `.UID` | Stable identifier |
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
### Stored procedures
|
|
121
|
+
|
|
122
|
+
The SQL body can call a stored procedure:
|
|
123
|
+
|
|
124
|
+
```sql
|
|
125
|
+
EXEC dbo.sp_GetActiveUsers @Active = 1
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`BaseEntityID` determines which data source the call executes against. Filter injection is not available for stored procedure calls.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
## Execute / Read
|
|
135
|
+
|
|
136
|
+
```css
|
|
137
|
+
GET /api/output/[uid]
|
|
138
|
+
GET /api/o/[uid] ← shorthand
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
### Request parameters
|
|
144
|
+
|
|
145
|
+
| Parameter | Description |
|
|
146
|
+
| :------------------------------------ | :----------------------------------------------------------- |
|
|
147
|
+
| `_filter@ShortName=value` | Sets the `@ShortName` variable for the matching filter record — requires `Prompted = 1` |
|
|
148
|
+
| `_filter@ShortName:restrictive=value` | Cannot be overridden by any parameter at a higher level in the request stack |
|
|
149
|
+
| `_template={uid\|name}` | Override template — pass a content UID or built-in template name. See **System templates** |
|
|
150
|
+
| `_debug_sql=true` | Return the generated SQL as `text/plain` instead of executing — administrator only. See **_debug_sql** below |
|
|
151
|
+
|
|
152
|
+
> **Note**: `_rowcount`, `_limit`, `_sort`, `_search`, `_page`, and `_rowsperpage` are not applicable to CustomSQL outputs — query structure, ordering, and pagination are controlled by the SQL body.
|
|
153
|
+
|
|
154
|
+
> **Note**: `_filter` defaults to `:overridable` — inner embed values win over outer frame values. To lock a filter, use `_filter@ShortName:restrictive=value`. See **Request Stack** below.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
### Request Stack
|
|
159
|
+
|
|
160
|
+
When a content record renders, each `<#_embed>` pushes a new frame onto the request stack carrying that embed's URL parameters. When a CustomSQL output resolves a request parameter, it walks the stack and applies the resolution rule for that parameter's modifier. The base HTTP request occupies the outermost frame; embed frames stack inward from there.
|
|
161
|
+
|
|
162
|
+
Stack depth is capped at 100 frames.
|
|
163
|
+
|
|
164
|
+
> **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:
|
|
165
|
+
>
|
|
166
|
+
> ```css
|
|
167
|
+
> REQUEST + _request@_debug:embeds=true
|
|
168
|
+
> \ content Name=my-page, UID=... +
|
|
169
|
+
> \ \ #_embed url=/api/output/[uid]?_filter@Status=active + _filter@Status=active
|
|
170
|
+
> \ \ \ output [UID=[uid], Name=my-output] +
|
|
171
|
+
> ```
|
|
172
|
+
>
|
|
173
|
+
> `\` marks frame depth. `+` separates the frame from its contributed parameters.
|
|
174
|
+
|
|
175
|
+
#### Modifier reference
|
|
176
|
+
|
|
177
|
+
| Modifier | Behavior | Default for |
|
|
178
|
+
|----------|----------|-------------|
|
|
179
|
+
| `:restrictive` | Cannot be replaced by an outer frame. | `_limit` |
|
|
180
|
+
| `:overridable` | Can be replaced by an outer frame. | `_filter`, `_display`, `_strict`, `_request` |
|
|
181
|
+
| `:expansive` | All frames contribute — values from every matching frame are merged. | — (opt-in only) |
|
|
182
|
+
|
|
183
|
+
Unmodified parameters inherit the default modifier for their type.
|
|
184
|
+
|
|
185
|
+
#### Usage patterns
|
|
186
|
+
|
|
187
|
+
**Lock a filter against embed override:**
|
|
188
|
+
|
|
189
|
+
```http
|
|
190
|
+
GET /api/output/[uid]?_filter@Status:restrictive=active
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
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.
|
|
194
|
+
|
|
195
|
+
**Allow a limit to be overridden by a nested embed:**
|
|
196
|
+
|
|
197
|
+
```html
|
|
198
|
+
<#_embed url="/api/output/[uid]?_limit:overridable=10" name="my.list"/>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
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.
|
|
202
|
+
|
|
203
|
+
**Accumulate values across frames:**
|
|
204
|
+
|
|
205
|
+
```http
|
|
206
|
+
GET /api/output/[uid]?_filter@Tags:expansive=admin
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```html
|
|
210
|
+
<#_embed url="/api/output/[uid]?_filter@Tags:expansive=active" name="my.list"/>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Both values are applied — the resolved filter covers both `admin` and `active`.
|
|
214
|
+
|
|
215
|
+
> **Note**: When multiple prompted values are supplied for the same filter, the `:and` or `:or` modifier controls how they are combined. This applies equally to values accumulated across frames via `:expansive`. Default is OR.
|
|
216
|
+
|
|
217
|
+
#### Stack-eligible parameters
|
|
218
|
+
|
|
219
|
+
Any parameter prefixed `_` participates in the stack. `_filter` and `_template` are resolved via the stack on every CustomSQL output request.
|
|
220
|
+
|
|
221
|
+
###### #req-sta
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
### _debug_sql
|
|
226
|
+
|
|
227
|
+
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.
|
|
228
|
+
|
|
229
|
+
Output format:
|
|
230
|
+
|
|
231
|
+
```css
|
|
232
|
+
-- Parameters:
|
|
233
|
+
|
|
234
|
+
SET @ShortName := value;
|
|
235
|
+
|
|
236
|
+
-- Query:
|
|
237
|
+
|
|
238
|
+
[SQL body with data_source tokens resolved and filter variables declared]
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
One declaration per filter variable that has a value. Variables with no value are omitted. Debug output always shows MySQL-style `SET @ShortName := value` syntax regardless of the actual data source type.
|
|
242
|
+
|
|
243
|
+
**Requires administrator access.** Non-administrators receive a 401 response.
|
|
244
|
+
|
|
245
|
+
###### #deb-sql
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
### System templates
|
|
250
|
+
|
|
251
|
+
Pass a system template identifier to `_template` to override the output's configured template for the request.
|
|
252
|
+
|
|
253
|
+
**Verified working formats:**
|
|
254
|
+
|
|
255
|
+
| Identifier | Content-Type | Output structure | Notes |
|
|
256
|
+
| :-------------- | :------------------ | :-------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
|
|
257
|
+
| `json_indented` | `application/json` | Pretty-printed JSON array, one object per row | Direct serialization — native types preserved, column names deduplicated. Preferred for API consumption. |
|
|
258
|
+
| `json_raw` | `application/json` | Compact JSON array, one object per row | Same serialization path as `json_indented` — native types, no HTML escaping, no whitespace. |
|
|
259
|
+
| `html` | `text/html` | `<table>` with header row, one `<tr>` per row | |
|
|
260
|
+
| `csv` | `application/json`* | Header row + one row per result, comma-separated | *Content-Type header incorrectly returns `application/json`. Template-rendered; duplicate aliases produce duplicate columns. |
|
|
261
|
+
| `txt` | `text/plain` | Header row + one row per result, space-separated | Template-rendered; duplicate aliases produce duplicate columns. |
|
|
262
|
+
| `xml` | `text/xml` | *(empty)* | Returns HTTP 200 with empty body — template file is empty. Not usable. |
|
|
263
|
+
|
|
264
|
+
###### #sys-tem
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
#### Custom template
|
|
269
|
+
|
|
270
|
+
Pass a content UID to load a content record as the template:
|
|
271
|
+
|
|
272
|
+
```http
|
|
273
|
+
GET /api/output/[uid]?_template=[content-uid]
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
The content record's `Extension` field determines the output format: `html` → HTML, `json` → JSON, `xml` → XML, `css` → CSS, `js` → JSON, anything else → plain text.
|
|
277
|
+
|
|
278
|
+
#### Priority
|
|
279
|
+
|
|
280
|
+
1. `_template` request parameter (this section)
|
|
281
|
+
2. `TemplateContentID` on the output record
|
|
282
|
+
3. Framework default — JSON
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
## Embed Syntax
|
|
289
|
+
|
|
290
|
+
CustomSQL outputs are embedded inside **content records** using the same embed syntax as all output types. See `content.md` (pending).
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
### Closed embed
|
|
295
|
+
|
|
296
|
+
```css
|
|
297
|
+
<#_embed url="/api/output/[uid]"
|
|
298
|
+
name="my.output"
|
|
299
|
+
target="MyOutput"/>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
### Open embed
|
|
305
|
+
|
|
306
|
+
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.
|
|
307
|
+
|
|
308
|
+
```css
|
|
309
|
+
<#_embed.list url="/api/output/[uid]" name="myoutput">
|
|
310
|
+
<#_row>
|
|
311
|
+
#{value@FirstName},#{value@LastName}
|
|
312
|
+
</#_row>
|
|
313
|
+
<#_noresult>
|
|
314
|
+
no results
|
|
315
|
+
</#_noresult>
|
|
316
|
+
</#_embed.list>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
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.
|
|
320
|
+
|
|
321
|
+
### Embed attribute reference
|
|
322
|
+
|
|
323
|
+
| Attribute | Required | Default | Notes |
|
|
324
|
+
| :---------------- | :------- | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
325
|
+
| `url` | yes | — | `/api/output/[uid]` or `/api/o/[uid]` |
|
|
326
|
+
| `name` | no | — | Static name for an embed |
|
|
327
|
+
| `target` | no | — | Named context for `:cached` tokens and `#{payload$...}`; comma-separated for multiple |
|
|
328
|
+
| `secure` | no | true | false bypasses access control for this request |
|
|
329
|
+
| `execute` | no | true | false skips the call entirely |
|
|
330
|
+
| `hide` | no | false | true executes query and caches results but does render a payload |
|
|
331
|
+
| `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 |
|
|
332
|
+
| `catch_exception` | no | — | Codes caught silently; true catches all |
|
|
333
|
+
|
|
334
|
+
URL query params after `?` are passed through to the embedded output.
|
|
335
|
+
|
|
336
|
+
###### #emb-att
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
## Template Tags
|
|
344
|
+
|
|
345
|
+
A template is a content record (`TemplateContentID`) containing section markup. Sections render in this order:
|
|
346
|
+
|
|
347
|
+
| Step | Tag | Renders when | Frequency |
|
|
348
|
+
| :--- | :----------------- | :---------------------------------- | :------------------------------------ |
|
|
349
|
+
| 1 | `<#_shared>` | Rows exist + shared columns defined | Once, before header |
|
|
350
|
+
| 2 | `<#_header>` | Defined in template | Once |
|
|
351
|
+
| 3 | `<#_row>` | Rows exist | Once per row |
|
|
352
|
+
| — | `<#_rowdelimiter>` | Rows exist | Between rows (not after last) |
|
|
353
|
+
| 4 | `<#_summary>` | Rows exist + summary values defined | Once, after last row |
|
|
354
|
+
| 5 | `<#_footer>` | Defined in template | Once |
|
|
355
|
+
| — | `<#_noresult>` | Zero rows | Once |
|
|
356
|
+
| — | `<#_empty>` | Zero rows | Once (after noresult if both defined) |
|
|
357
|
+
|
|
358
|
+
> **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.
|
|
359
|
+
|
|
360
|
+
### Value tag
|
|
361
|
+
|
|
362
|
+
`<#_value.Alias>` outputs a column value by SQL alias. It supports an open form with attributes for conditional rendering:
|
|
363
|
+
|
|
364
|
+
| Attribute | Notes |
|
|
365
|
+
| :--------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
366
|
+
| `value` | Comparison value; `_null` matches null database values (not empty strings) |
|
|
367
|
+
| `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). |
|
|
368
|
+
|
|
369
|
+
String renders block conditionally
|
|
370
|
+
|
|
371
|
+
```css
|
|
372
|
+
-- renders WHEN Status IS NOT NULL
|
|
373
|
+
<#_value.Status>
|
|
374
|
+
status set
|
|
375
|
+
</#_value.Status>
|
|
376
|
+
|
|
377
|
+
-- renders WHEN Status IS NOT NULL
|
|
378
|
+
<#_value.Status modifier="exclude" value="_null">
|
|
379
|
+
#{value@Status}
|
|
380
|
+
</#_value.Status>
|
|
381
|
+
|
|
382
|
+
-- renders WHEN Status IS NULL
|
|
383
|
+
<#_value.Status value="_null">
|
|
384
|
+
No status set
|
|
385
|
+
</#_value.Status>
|
|
386
|
+
|
|
387
|
+
-- renders WHEN Price < 100
|
|
388
|
+
<#_value.Price modifier="lessthan" value="100">
|
|
389
|
+
under $100
|
|
390
|
+
</#_value.Price>
|
|
391
|
+
|
|
392
|
+
-- renders WHEN ( Status = 'active' OR Status = 'verified' )
|
|
393
|
+
<#_value.Status value="active,verified">
|
|
394
|
+
active and verified
|
|
395
|
+
</#_value.Status>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
###### #tem-tag
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
## Data Tokens
|
|
405
|
+
|
|
406
|
+
### Token syntax
|
|
407
|
+
|
|
408
|
+
```css
|
|
409
|
+
#{type@reference$target!default:modifier;comment}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
> **Note**: type attribute must come first; after that order does not matter.
|
|
413
|
+
|
|
414
|
+
| Part | Symbol | Notes |
|
|
415
|
+
| :-------- | :----- | :----------------------------------------------------------- |
|
|
416
|
+
| type | — | See **Token type reference** below. Common: `value`, `id`, `uid`, `output`, `session`, `request` |
|
|
417
|
+
| reference | `@` | SQL column alias, output property, or named key |
|
|
418
|
+
| target | `$` | Named embed context — required with `:cached` |
|
|
419
|
+
| default | `!` | Fallback value if token resolves empty |
|
|
420
|
+
| modifier | `:` | Transforms output or controls resolution timing |
|
|
421
|
+
| comment | `;` | Unrendered string |
|
|
422
|
+
|
|
423
|
+
###### #tok-syn
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
### Token type reference
|
|
428
|
+
|
|
429
|
+
| Type | Reference syntax | Resolved in | Notes |
|
|
430
|
+
| :--------------------- | :------------------------------------------------------------ | :--------------- | :------------------------------------------------------------------------------------------------- |
|
|
431
|
+
| `value` (`data.value`) | `@Alias` | Result-set pass | Field value for current row — reference is the SQL column alias |
|
|
432
|
+
| `id` (`data.id`) | — | Result-set pass | Numeric PK of current row's base entity |
|
|
433
|
+
| `uid` (`data.uid`) | — | Result-set pass | UID of current row's base entity |
|
|
434
|
+
| `output.row` | `@index` | Result-set pass | Current row number; `:global` for cross-page value |
|
|
435
|
+
| `output.row` | `@count:returned` | Result-set pass | Count of returned rows |
|
|
436
|
+
| `output.[ColumnName]` | — | Result-set pass | Any property of the output record — e.g. `#{output.Name}`, `#{output.AppID}` |
|
|
437
|
+
| `payload` | `$Target` | Template pass | Rendered output of named embed; resolved after all embeds execute |
|
|
438
|
+
| `session` | `@UserID`, `@Email`, `@FirstName`, `@LastName`, `@uid`, others | Template pass | Resolved in template content render — see **Token scope** below |
|
|
439
|
+
| `request` | `@ParamName` | Template pass | URL query parameter — see **Token scope** below |
|
|
440
|
+
| `input` | `@add1.id`, `@add2.id`, … | Input batch | ID of a record created in the same input batch |
|
|
441
|
+
| `unique` | — | Template pass | Generates a GUID |
|
|
442
|
+
|
|
443
|
+
###### #tok-typ
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
### Value token modifiers
|
|
448
|
+
|
|
449
|
+
| Modifier | Effect |
|
|
450
|
+
| :--------------- | :---------------------------------------------------------------------------------------------------------------------------------- |
|
|
451
|
+
| `:cached$Target` | Defers token resolution until all embeds finish executing; `$Target` specifies which embed's context to read from when multiple embeds are present |
|
|
452
|
+
| `:no_escape` | Bypasses context-aware HTML escaping — use for trusted raw output |
|
|
453
|
+
| `:raw` | Deferred injection via placeholder (final render pass) |
|
|
454
|
+
| `:{format}` | Any .NET format string applied via `.ToString(format)` — see **Modifier Syntax** below |
|
|
455
|
+
|
|
456
|
+
###### #val-mod
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
### Modifier Syntax
|
|
461
|
+
|
|
462
|
+
Any .NET format string used as a modifier formats the resolved value before output:
|
|
463
|
+
|
|
464
|
+
```css
|
|
465
|
+
#{value@CreatedOn:d} → short date (e.g. 1/3/2024)
|
|
466
|
+
#{value@CreatedOn:M} → month and day (e.g. January 3)
|
|
467
|
+
#{request@Price:f2} → 12.50
|
|
468
|
+
#{value:c2} → $12.50
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Format strings are locale-dependent (server locale).
|
|
472
|
+
|
|
473
|
+
###### #mod-syn
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
### Row data
|
|
478
|
+
|
|
479
|
+
| Token | Canonical form | Returns |
|
|
480
|
+
| :-------------------------- | :------------------------ | :------------------------------------------ |
|
|
481
|
+
| `#{value@Alias}` | `#{data.value@Alias}` | Field value for the current row |
|
|
482
|
+
| `#{value@Alias!fallback}` | — | With default if empty |
|
|
483
|
+
| `#{value@Alias:no_escape}` | — | Raw unescaped value |
|
|
484
|
+
|
|
485
|
+
`Alias` is the SQL column alias from the `SELECT` clause.
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
### Output metadata
|
|
490
|
+
|
|
491
|
+
| Token | Returns |
|
|
492
|
+
| :----------------------------- | :------------------------------------------------------------------------------------------ |
|
|
493
|
+
| `#{output.row@index}` | Current row number (within returned rows) |
|
|
494
|
+
| `#{output.row@count:returned}` | Total returned row count |
|
|
495
|
+
| `#{output.PropertyName}` | Any property of the output record — e.g. `#{output.Name}`, `#{output.UID}`, `#{output.AppID}` |
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
### Token scope
|
|
500
|
+
|
|
501
|
+
Token resolution happens in two sequential passes:
|
|
502
|
+
|
|
503
|
+
**Content render pass** (first): The template content record (`TemplateContentID`) 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.
|
|
504
|
+
|
|
505
|
+
**Output row render pass** (second): Section blocks (`<#_row>`, `<#_header>`, etc.) are processed per-row. This pass resolves `value`, `id`, `uid`, and `output.*` tokens only. Session, request, and similar tokens are not re-processed here — they were already resolved in the first pass.
|
|
506
|
+
|
|
507
|
+
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.
|
|
508
|
+
|
|
509
|
+
###### #tok-sco
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
### Cached embed reads
|
|
514
|
+
|
|
515
|
+
`:cached` defers token resolution until after all embeds execute. `$Target` is required — omitting it causes ambiguous resolution when multiple embeds are active.
|
|
516
|
+
|
|
517
|
+
```css
|
|
518
|
+
<#_embed url="/api/output/[uid]" target="MyOutput"/>
|
|
519
|
+
#{value@SomeField:cached$MyOutput}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
### Payload tokens
|
|
525
|
+
|
|
526
|
+
`#{payload$Target}` accesses the rendered output of a named embed after execution. The embed must have a matching `target="..."` attribute.
|
|
527
|
+
|
|
528
|
+
| Token | Returns |
|
|
529
|
+
| :------------------ | :-------------------------------------------------------------------------------- |
|
|
530
|
+
| `#{payload$Target}` | Full rendered output of the embed (HTML, JSON, or whatever the template produces) |
|
|
531
|
+
|
|
532
|
+
Payload tokens resolve in the same deferred pass as `:cached` value tokens — after all embeds have executed.
|
|
533
|
+
|
|
534
|
+
```css
|
|
535
|
+
<#_embed url="/api/output/[uid]" target="UserData" hide="true" payload="true"/>
|
|
536
|
+
|
|
537
|
+
#{payload$UserData} → rendered output of the embed
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
###### #pay-tok
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
## Filters
|
|
547
|
+
|
|
548
|
+
Filters are driven by `output_value_filter` records attached to the output. Each record declares an `@ShortName` variable used directly in the SQL body.
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
### Prompted filter
|
|
553
|
+
|
|
554
|
+
Set `Prompted = 1` and `ShortName` on a filter record to accept a URL parameter at request time:
|
|
555
|
+
|
|
556
|
+
```css
|
|
557
|
+
?_filter@ShortName=value
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
The value is injected as a single `@ShortName` variable. Multiple values or compound logic must be handled in the SQL body.
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
### Request stack and filter priority
|
|
565
|
+
|
|
566
|
+
When a CustomSQL output is embedded, its caller can pass filter parameters down to it. The **request stack** tracks these values across embed levels.
|
|
567
|
+
|
|
568
|
+
The `:restrictive` and `:overridable` modifiers control how a filter value behaves within this stack:
|
|
569
|
+
|
|
570
|
+
| Modifier | Behavior |
|
|
571
|
+
| :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
572
|
+
| `: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. |
|
|
573
|
+
| `: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. |
|
|
574
|
+
|
|
575
|
+
When neither modifier is present, the default behavior is equivalent to `:overridable` — the value is applied unless overridden.
|
|
576
|
+
|
|
577
|
+
```css
|
|
578
|
+
?_filter@ShortName:restrictive=value
|
|
579
|
+
?_filter@ShortName:overridable=value
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
### Filter presets
|
|
585
|
+
|
|
586
|
+
These preset values can be stored in `output_value_filter.PrimaryValue` or passed as `_filter` parameter values.
|
|
587
|
+
|
|
588
|
+
| Preset | Resolves to |
|
|
589
|
+
| :----------------------------------------------------------- | :----------------------------------------------------------- |
|
|
590
|
+
| `CurrentUserID` | Current user's ID (may differ from logged-in user under impersonation) |
|
|
591
|
+
| `LoggedInUserID` | Logged-in user's ID (unchanged under impersonation) |
|
|
592
|
+
| `Now` | Current datetime |
|
|
593
|
+
| `Today`, `ThisWeek`, `ThisMonth`, `ThisYear`, `Yesterday`, `LastWeek`, `LastMonth`, `LastQuarter`, `LastYear` | Date presets generate SQL date ranges (`BETWEEN start AND end`) |
|
|
594
|
+
| `IpAddress` | Requester's IPV4 address |
|
|
595
|
+
| `SessionUID` | Current session UID |
|
|
596
|
+
| `CurrentSiteID` | Current site associated to content in request |
|
|
597
|
+
| `_security@view` | Row IDs the user has View permission for |
|
|
598
|
+
| `_security@add` | Row IDs the user has Add permission for |
|
|
599
|
+
| `_security@edit` | Row IDs the user has Edit permission for |
|
|
600
|
+
| `_security@delete` | Row IDs the user has Delete permission for |
|
|
601
|
+
| `_security@associate` | Row IDs the user has Associate permission for |
|
|
602
|
+
| `_security@execute` | Row IDs the user has Execute permission for |
|
|
603
|
+
| `_security@impersonate` | Row IDs the user has Impersonate permission for |
|
|
604
|
+
|
|
605
|
+
###### #fil-pre
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
## Row Limit
|
|
612
|
+
|
|
613
|
+
`output.Limit` and `output.MaxRows` are not applied to CustomSQL outputs — paging logic runs only for Query-type outputs. Write `LIMIT` directly in the SQL body:
|
|
614
|
+
|
|
615
|
+
```sql
|
|
616
|
+
SELECT u.UserID, u.FirstName
|
|
617
|
+
FROM user u
|
|
618
|
+
LIMIT 100
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
## Caching
|
|
626
|
+
|
|
627
|
+
`Cache = 1` caches the result set. Output structure (SQL body, filter config) is cached automatically.
|
|
628
|
+
|
|
629
|
+
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:
|
|
630
|
+
|
|
631
|
+
```http
|
|
632
|
+
GET /api/cache/refresh?_key=output:meta:[output.UID]
|
|
633
|
+
GET /api/cache/refresh?_key=secure:output:meta:[output.UID]
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
`voidcache` flushes all caches globally:
|
|
637
|
+
|
|
638
|
+
```http
|
|
639
|
+
GET /api/output/[uid]?voidcache=true
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
## Legacy
|
|
647
|
+
|
|
648
|
+
### Legacy filter injection syntax
|
|
649
|
+
|
|
650
|
+
`#{filter:targetField:uid_or_shortname}` was the mechanism for placing filter conditions inside a CustomSQL body. Superseded by `@ShortName` variable injection.
|
|
651
|
+
|
|
652
|
+
```sql
|
|
653
|
+
-- Legacy
|
|
654
|
+
WHERE #{filter:u.RoleID:RoleID}
|
|
655
|
+
|
|
656
|
+
-- Current
|
|
657
|
+
WHERE u.RoleID = @RoleID
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### Legacy URL parameters
|
|
661
|
+
|
|
662
|
+
| Parameter | Superseded by | Notes |
|
|
663
|
+
| :---------------- | :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
664
|
+
| `_maxrows=N` | Write `LIMIT N` in SQL body | Not equivalent — `_maxrows` was never applied to CustomSQL outputs |
|
|
665
|
+
| `_template=json` | `_template=json_indented` or `_template=json_raw` | Routes through the DBO content rendering pipeline — values serialized as strings, duplicate aliases produce duplicate keys, slower than direct serialization. Prefer `json_indented` or `json_raw`. |
|
|
666
|
+
|
|
667
|
+
### Legacy embed syntax
|
|
668
|
+
|
|
669
|
+
`<EmbedToken>` and `<#embed>` were the predecessors to `<#_embed>`.
|
|
670
|
+
|
|
671
|
+
```css
|
|
672
|
+
<!-- Legacy -->
|
|
673
|
+
<EmbedToken url="/api/output/[uid]" name="my.output" />
|
|
674
|
+
|
|
675
|
+
<!-- Legacy -->
|
|
676
|
+
<#embed url="/api/output/[uid]" name="my.output"/>
|
|
677
|
+
```
|