@carthooks/arcubase-cli 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/bundle/arcubase-admin.mjs +1669 -163
  2. package/bundle/arcubase.mjs +1669 -163
  3. package/dist/generated/command_registry.generated.d.ts +300 -0
  4. package/dist/generated/command_registry.generated.d.ts.map +1 -1
  5. package/dist/generated/command_registry.generated.js +416 -0
  6. package/dist/generated/help_examples.generated.d.ts +191 -0
  7. package/dist/generated/help_examples.generated.d.ts.map +1 -1
  8. package/dist/generated/help_examples.generated.js +277 -0
  9. package/dist/generated/type_index.generated.d.ts +44 -1
  10. package/dist/generated/type_index.generated.d.ts.map +1 -1
  11. package/dist/generated/type_index.generated.js +46 -1
  12. package/dist/generated/zod_registry.generated.d.ts +32 -1
  13. package/dist/generated/zod_registry.generated.d.ts.map +1 -1
  14. package/dist/generated/zod_registry.generated.js +41 -0
  15. package/dist/runtime/dev_sdk_gen.d.ts +9 -0
  16. package/dist/runtime/dev_sdk_gen.d.ts.map +1 -0
  17. package/dist/runtime/dev_sdk_gen.js +319 -0
  18. package/dist/runtime/execute.d.ts.map +1 -1
  19. package/dist/runtime/execute.js +384 -32
  20. package/dist/runtime/zod_registry.d.ts.map +1 -1
  21. package/dist/runtime/zod_registry.js +66 -0
  22. package/package.json +1 -1
  23. package/sdk-dist/api/admin/index.ts +1 -0
  24. package/sdk-dist/api/admin/ingress.ts +11 -0
  25. package/sdk-dist/api/admin/public-link.ts +48 -0
  26. package/sdk-dist/api/shared-link/form.ts +42 -1
  27. package/sdk-dist/api/user/index.ts +0 -1
  28. package/sdk-dist/docs/runtime-reference/access-rule.md +19 -0
  29. package/sdk-dist/docs/runtime-reference/dashboard.md +15 -0
  30. package/sdk-dist/docs/runtime-reference/dev-sdk-gen.md +41 -0
  31. package/sdk-dist/docs/runtime-reference/help-examples/admin/access-rule/bulk-apply.json +105 -0
  32. package/sdk-dist/docs/runtime-reference/help-examples/admin/dashboard/update-layout.json +16 -0
  33. package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/create.json +33 -0
  34. package/sdk-dist/docs/runtime-reference/help-examples/admin/widget/preview-data.json +42 -0
  35. package/sdk-dist/docs/runtime-reference/widgets.md +40 -0
  36. package/sdk-dist/generated/command_registry.generated.ts +416 -0
  37. package/sdk-dist/generated/help_examples.generated.ts +277 -0
  38. package/sdk-dist/generated/type_index.generated.ts +46 -1
  39. package/sdk-dist/generated/zod_registry.generated.ts +62 -0
  40. package/sdk-dist/types/app.ts +37 -1
  41. package/sdk-dist/types/common.ts +77 -35
  42. package/sdk-dist/types/index.ts +1 -1
  43. package/sdk-dist/types/ingress.ts +31 -0
  44. package/sdk-dist/types/public-link.ts +10 -0
  45. package/sdk-dist/types/shared-link.ts +16 -1
  46. package/sdk-dist/types/user-action.ts +1 -43
  47. package/src/generated/command_registry.generated.ts +416 -0
  48. package/src/generated/help_examples.generated.ts +277 -0
  49. package/src/generated/type_index.generated.ts +46 -1
  50. package/src/generated/zod_registry.generated.ts +62 -0
  51. package/src/runtime/dev_sdk_gen.ts +360 -0
  52. package/src/runtime/execute.ts +484 -52
  53. package/src/runtime/zod_registry.ts +66 -0
  54. package/src/tests/command_registry.test.ts +21 -2
  55. package/src/tests/dev_sdk_gen.test.ts +226 -0
  56. package/src/tests/docs_readability.test.ts +15 -0
  57. package/src/tests/execute_validation.test.ts +215 -0
  58. package/src/tests/help.test.ts +83 -0
  59. package/sdk-dist/api/user/copilot.ts +0 -20
  60. package/sdk-dist/types/copilot.ts +0 -34
@@ -0,0 +1,48 @@
1
+ import { getDefaultClient } from '../../factory'
2
+ import type {
3
+ EntityPublicLinkType,
4
+ PublicLinkConfigItemVO,
5
+ PublicLinkConfigReqVO,
6
+ PublicLinkConfigRespVO,
7
+ PublicRecordLinkRespVO
8
+ } from '../../types/public-link'
9
+
10
+ // @endpoint GET /api/apps/:app_id/entity/:entity_id/public-links
11
+ // @controller PublicLink.ConfigList
12
+ export async function getPublicLinkConfigs(
13
+ appId: string,
14
+ entityId: string
15
+ ): Promise<PublicLinkConfigRespVO> {
16
+ return getDefaultClient().get(`/apps/${appId}/entity/${entityId}/public-links`)
17
+ }
18
+
19
+ // @endpoint PUT /api/apps/:app_id/entity/:entity_id/public-links/:type
20
+ // @controller PublicLink.ConfigSave
21
+ export async function savePublicLinkConfig(
22
+ appId: string,
23
+ entityId: string,
24
+ type: EntityPublicLinkType,
25
+ data: PublicLinkConfigReqVO
26
+ ): Promise<PublicLinkConfigItemVO> {
27
+ return getDefaultClient().put(`/apps/${appId}/entity/${entityId}/public-links/${type}`, data)
28
+ }
29
+
30
+ // @endpoint POST /api/apps/:app_id/entity/:entity_id/public-links/:type/regenerate
31
+ // @controller PublicLink.ConfigRegenerate
32
+ export async function regeneratePublicLinkConfig(
33
+ appId: string,
34
+ entityId: string,
35
+ type: EntityPublicLinkType
36
+ ): Promise<PublicLinkConfigItemVO> {
37
+ return getDefaultClient().post(`/apps/${appId}/entity/${entityId}/public-links/${type}/regenerate`)
38
+ }
39
+
40
+ // @endpoint POST /api/apps/:app_id/entity/:entity_id/public-links/record/:row_id
41
+ // @controller PublicLink.RecordLinkCreate
42
+ export async function createPublicRecordLink(
43
+ appId: string,
44
+ entityId: string,
45
+ rowId: number | string
46
+ ): Promise<PublicRecordLinkRespVO> {
47
+ return getDefaultClient().post(`/apps/${appId}/entity/${entityId}/public-links/record/${rowId}`)
48
+ }
@@ -1,16 +1,19 @@
1
1
  import { getDefaultClient } from '../../factory'
2
2
  import type {
3
3
  SharedLinkInitRespVO,
4
+ SharedLinkPublicQueryReqVO,
5
+ SharedLinkPublicQueryRespVO,
4
6
  SharedLinkPushMobileUploadReqVO,
5
7
  SharedLinkPushMobileUploadRespVO,
6
8
  SharedLinkSubmissionSubmitReqVO,
7
9
  SharedLinkSubmissionSubmitRespVO
8
- } from '../../types/shared-link'
10
+ } from '../../types'
9
11
  import type {
10
12
  EntityValidateUniqReqVO,
11
13
  EntityValidateUniqRespVO,
12
14
  EntityQueryRelationReqVO,
13
15
  EntityQueryRelationRespVO,
16
+ EntityQueryLinkToValuesReqVO,
14
17
  GetUploadTokenRespVO,
15
18
  UploadPreviewReqVO,
16
19
  UploadPreviewRespVO,
@@ -31,6 +34,36 @@ export async function querySharedLinkEntityRelation(
31
34
  return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-rel`, data)
32
35
  }
33
36
 
37
+ // @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-linkto-pick
38
+ // @controller SharedLink.EntityQueryLinkToPick
39
+ export async function querySharedLinkEntityLinkToPick(
40
+ appId: string,
41
+ entityId: string,
42
+ data: EntityQueryRelationReqVO
43
+ ): Promise<EntityQueryRelationRespVO> {
44
+ return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-linkto-pick`, data)
45
+ }
46
+
47
+ // @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-linkto-values
48
+ // @controller SharedLink.EntityQueryLinkToValues
49
+ export async function querySharedLinkEntityLinkToValues(
50
+ appId: string,
51
+ entityId: string,
52
+ data: EntityQueryLinkToValuesReqVO
53
+ ): Promise<EntityQueryRelationRespVO> {
54
+ return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-linkto-values`, data)
55
+ }
56
+
57
+ // @endpoint POST /link/api/form/entity/:app_id/:entity_id/query-subform-rel
58
+ // @controller SharedLink.EntityQuerySubformRelation
59
+ export async function querySharedLinkEntitySubformRelation(
60
+ appId: string,
61
+ entityId: string,
62
+ data: any
63
+ ): Promise<any> {
64
+ return getDefaultClient().post(`/form/entity/${appId}/${entityId}/query-subform-rel`, data)
65
+ }
66
+
34
67
  // @endpoint GET /link/api/form/dataset/:id/:node_id
35
68
  // @controller SharedLink.GetDataset
36
69
  export async function getSharedLinkDataset(
@@ -52,6 +85,14 @@ export async function initSharedLink(): Promise<SharedLinkInitRespVO> {
52
85
  return getDefaultClient().post('/init')
53
86
  }
54
87
 
88
+ // @endpoint POST /link/api/query
89
+ // @controller SharedLink.PublicQuery
90
+ export async function querySharedLinkPublic(
91
+ data: SharedLinkPublicQueryReqVO
92
+ ): Promise<SharedLinkPublicQueryRespVO> {
93
+ return getDefaultClient().post('/query', data)
94
+ }
95
+
55
96
  // @endpoint POST /link/api/form/upload/mobile-upload
56
97
  // @controller SharedLink.MobileUpload
57
98
  export async function uploadSharedLinkMobile(
@@ -6,7 +6,6 @@ export * from './profile'
6
6
  export * from './common'
7
7
  export * from './workflow'
8
8
  export * from './kiosk'
9
- export * from './copilot'
10
9
  export * from './flow'
11
10
  export * from './context'
12
11
  export * from './global-action'
@@ -112,6 +112,25 @@ Rules:
112
112
  - Multiple scope items are OR: any matching user, department membership, or actor membership can use the ingress.
113
113
  - Department scope includes child departments because Arcubase matches the user's department path.
114
114
 
115
+ ## Bulk Apply Across Tables
116
+
117
+ Use this when assigning or updating many access rules for one app. This is the single path for matrix-style permission setup. Do not loop `access-rule list` and `access-rule update` for every table/rule.
118
+
119
+ ```bash
120
+ arcubase-admin access-rule bulk-apply \
121
+ --app-id 2188893436 \
122
+ --body-json '{"mode":"upsert_by_name","items":[{"table_id":2188893443,"name":"Sales read write","enabled":true,"type":"form","options":{"user_scope":[{"type":"department","id":2188881201}],"policy":{"key":"custom","actions":["view","add","edit"],"all_fields_read":true,"all_fields_write":true,"condition":{"mode":"and","conditions":[{"column":"creator","operator":"$eq","value":"[[current_user]]","value_from":0,"value_relation":""}]}},"list_options":{}}},{"table_id":2188893444,"name":"Sales readonly","enabled":true,"type":"form","options":{"user_scope":[{"type":"actor","id":2188883301}],"policy":{"key":"custom","actions":["view"],"all_fields_read":true,"all_fields_write":false},"list_options":{}}}]}'
123
+ ```
124
+
125
+ Rules:
126
+
127
+ - `bulk-apply` runs in one backend transaction.
128
+ - `mode:"update_existing"` requires every item to include `rule_id`.
129
+ - `mode:"upsert_by_name"` requires every item to include `name` and must not include `rule_id`.
130
+ - Each item must include `table_id` and the full canonical `options` object.
131
+ - Numeric IDs passed as strings are normalized by the CLI for `table_id`, `rule_id`, and `options.user_scope[].id`.
132
+ - Use `dry_run:true` to preview the exact operations without writing.
133
+
115
134
  ## Verify
116
135
 
117
136
  ```bash
@@ -0,0 +1,15 @@
1
+ # Dashboard
2
+
3
+ Use `arcubase dashboard get` and `arcubase dashboard widgets` to read dashboards as a tenant user.
4
+
5
+ Use `arcubase-admin dashboard list`, `arcubase-admin dashboard create`, `arcubase-admin dashboard get-options`, `arcubase-admin dashboard update-options`, `arcubase-admin dashboard update-layout`, and `arcubase-admin dashboard rename` to manage dashboards as an admin.
6
+
7
+ Dashboard title updates use body `{"name":"..."}`.
8
+
9
+ Dashboard layout updates use a top-level JSON array of layout positions, not an object wrapper. Example:
10
+
11
+ ```json
12
+ [{"id":2188892001,"x":0,"y":0,"w":12,"h":6}]
13
+ ```
14
+
15
+ Dashboard delete and clone are not exposed in the CLI surface until backend routes are registered.
@@ -0,0 +1,41 @@
1
+ # Dev SDK Gen
2
+
3
+ Use `arcubase-admin dev sdk-gen` to generate typed TypeScript source for a Web project.
4
+
5
+ ```bash
6
+ arcubase-admin dev sdk-gen \
7
+ --app-id <app_id> \
8
+ --out src/arcubase
9
+ ```
10
+
11
+ The command reads the app schema from Arcubase through the normal `arcubase-admin` runtime auth and writes project-local source files under `--out`.
12
+
13
+ Generated code is key based. Application code uses stable `field.key` names, while generated runtime code converts those keys to internal numeric field ids.
14
+ If a field is missing `field.key`, the command initializes it through `PUT /api/apps/:app_id/entity/:entity_id/custom-keys` with a stable `field<id>` fallback before generation.
15
+
16
+ ```ts
17
+ import { createArcubaseSdk } from './arcubase'
18
+
19
+ const sdk = createArcubaseSdk({
20
+ baseURL: 'https://team.example.arcubase.co/api',
21
+ accessToken,
22
+ refreshToken,
23
+ })
24
+
25
+ await sdk.story.create({
26
+ story: 'Once upon a time',
27
+ })
28
+
29
+ await sdk.story.update(rowId, {
30
+ story: 'Updated story',
31
+ })
32
+ ```
33
+
34
+ Rules:
35
+
36
+ - missing `field.key` values are initialized through admin auth as `field<id>`
37
+ - existing `field.key` values must be stable unique TypeScript identifiers
38
+ - `field.key` is a developer API contract and should not track display labels
39
+ - entity property names use `entity.key` when available, otherwise a stable `entity<id>` fallback
40
+ - row create/update/query code should use generated types, not raw field ids
41
+ - rerun the command after schema changes and commit the regenerated source with the Web project
@@ -0,0 +1,105 @@
1
+ {
2
+ "scope": "admin",
3
+ "command": ["access-rule", "bulk-apply"],
4
+ "examples": [
5
+ {
6
+ "title": "upsert permission matrix by rule name in one transaction",
7
+ "body": {
8
+ "mode": "upsert_by_name",
9
+ "items": [
10
+ {
11
+ "table_id": 2188893443,
12
+ "name": "Customer archive - sales owns rows",
13
+ "enabled": true,
14
+ "type": "form",
15
+ "options": {
16
+ "user_scope": [{"type": "department", "id": 2188881201}],
17
+ "policy": {
18
+ "key": "custom",
19
+ "actions": ["view", "add", "edit"],
20
+ "all_fields_read": true,
21
+ "all_fields_write": true,
22
+ "condition": {
23
+ "mode": "and",
24
+ "conditions": [
25
+ {"column": "creator", "operator": "$eq", "value": "[[current_user]]", "value_from": 0, "value_relation": ""}
26
+ ]
27
+ }
28
+ },
29
+ "list_options": {}
30
+ }
31
+ },
32
+ {
33
+ "table_id": 2188893444,
34
+ "name": "Lead review - manager queue",
35
+ "enabled": true,
36
+ "type": "form",
37
+ "options": {
38
+ "user_scope": [{"type": "actor", "id": 2188883301}],
39
+ "policy": {
40
+ "key": "custom",
41
+ "actions": ["view", "edit"],
42
+ "all_fields_read": true,
43
+ "all_fields_write": true,
44
+ "condition": {
45
+ "mode": "and",
46
+ "conditions": [
47
+ {"column": ":1004", "operator": "$eq", "value": "pending", "value_from": 0, "value_relation": ""},
48
+ {
49
+ "column": "",
50
+ "operator": "",
51
+ "value": null,
52
+ "value_from": 0,
53
+ "value_relation": "",
54
+ "subset": {
55
+ "mode": "or",
56
+ "conditions": [
57
+ {"column": "creator", "operator": "$eq", "value": "[[current_user]]", "value_from": 0, "value_relation": ""},
58
+ {"column": ":1005", "operator": "$eq", "value": "high", "value_from": 0, "value_relation": ""}
59
+ ]
60
+ }
61
+ }
62
+ ]
63
+ }
64
+ },
65
+ "list_options": {}
66
+ }
67
+ }
68
+ ]
69
+ },
70
+ "notes": [
71
+ "mode upsert_by_name is for idempotent setup; rule names must be unique within each table",
72
+ "department, actor, and specific user scopes all use options.user_scope",
73
+ "row-owner isolation uses [[current_user]] in policy.condition"
74
+ ]
75
+ },
76
+ {
77
+ "title": "preview updates for existing rule ids",
78
+ "body": {
79
+ "mode": "update_existing",
80
+ "dry_run": true,
81
+ "items": [
82
+ {
83
+ "table_id": 2188893443,
84
+ "rule_id": 2188893557,
85
+ "enabled": true,
86
+ "options": {
87
+ "user_scope": [{"type": "user", "id": 2188889901}],
88
+ "policy": {
89
+ "key": "custom",
90
+ "actions": ["view"],
91
+ "all_fields_read": true,
92
+ "all_fields_write": false
93
+ },
94
+ "list_options": {}
95
+ }
96
+ }
97
+ ]
98
+ },
99
+ "notes": [
100
+ "dry_run true returns the planned operations without writing",
101
+ "update_existing requires rule_id on every item"
102
+ ]
103
+ }
104
+ ]
105
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "scope": "admin",
3
+ "command": ["dashboard", "update-layout"],
4
+ "examples": [
5
+ {
6
+ "title": "place one dashboard widget in the top-left area",
7
+ "body": [
8
+ {"id": 2188892001, "x": 0, "y": 0, "w": 12, "h": 6}
9
+ ],
10
+ "notes": [
11
+ "the request body is a top-level array",
12
+ "do not wrap the array as {\"layout\":[...]}"
13
+ ]
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "scope": "admin",
3
+ "command": ["widget", "create"],
4
+ "examples": [
5
+ {
6
+ "title": "create a dashboard chart widget",
7
+ "body": {
8
+ "type": "chart",
9
+ "options": {
10
+ "dashboard_id": 2188892001
11
+ }
12
+ },
13
+ "notes": [
14
+ "do not set options.type by hand",
15
+ "dashboard_id selects a dashboard widget",
16
+ "use widget update next to save chart options"
17
+ ]
18
+ },
19
+ {
20
+ "title": "create an ingress list widget",
21
+ "body": {
22
+ "type": "list",
23
+ "options": {
24
+ "ingress_id": 2188893001
25
+ }
26
+ },
27
+ "notes": [
28
+ "ingress_id selects an ingress widget",
29
+ "do not place ingress widgets with dashboard update-layout"
30
+ ]
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "scope": "admin",
3
+ "command": ["widget", "preview-data"],
4
+ "examples": [
5
+ {
6
+ "title": "preview dashboard chart data from a table",
7
+ "body": {
8
+ "dataSource": 2188891001,
9
+ "dataSourceType": "entity",
10
+ "dimensions": [
11
+ {"IsField": true, "FieldID": 1004, "Name": "Source"}
12
+ ],
13
+ "measures": [
14
+ {"FieldID": 1005, "Aggregate": "sum", "Name": "Total Amount"}
15
+ ]
16
+ },
17
+ "notes": [
18
+ "preview-data does not accept --widget-id",
19
+ "do not send widget_id or id in the request body",
20
+ "copy dimensions and measures from the chart query you want to preview"
21
+ ]
22
+ },
23
+ {
24
+ "title": "preview count and distinct_count in one grouped chart",
25
+ "body": {
26
+ "dataSource": 2188891001,
27
+ "dataSourceType": "entity",
28
+ "dimensions": [
29
+ {"IsField": true, "FieldID": 1004, "Name": "Source"}
30
+ ],
31
+ "measures": [
32
+ {"Aggregate": "count", "Name": "Lead Count"},
33
+ {"FieldID": 1002, "Aggregate": "distinct_count", "Name": "Distinct Customers"}
34
+ ]
35
+ },
36
+ "notes": [
37
+ "supported Aggregate values are sum, avg, max, min, count, and distinct_count",
38
+ "unsupported aggregate names fail local body validation before the request is sent"
39
+ ]
40
+ }
41
+ ]
42
+ }
@@ -0,0 +1,40 @@
1
+ # Widgets
2
+
3
+ Use `arcubase-admin widget list --app-id <app_id> --type dashboard --dashboard-id <dashboard_id>` to list dashboard widgets.
4
+
5
+ Use `arcubase-admin widget get`, `arcubase-admin widget create`, `arcubase-admin widget update`, `arcubase-admin widget update-ingress-options`, `arcubase-admin widget preview-data`, and `arcubase-admin widget delete` to manage widgets.
6
+
7
+ Use `arcubase widget data --app-id <app_id> --body-json '<FetchWidgetsDataReqVO>'` to fetch user-visible widget data.
8
+
9
+ Create dashboard widgets with `{"type":"chart","options":{"dashboard_id":<dashboard_id>}}`. Do not set `options.type` by hand; the backend derives dashboard versus ingress widget type from `dashboard_id` or `ingress_id`.
10
+
11
+ Most widget commands use `--widget-id`; `widget preview-data` is the exception. Preview data does not read a saved widget by id and does not accept `--widget-id`, `widget_id`, or `id`. Its body is the chart query itself:
12
+
13
+ ```json
14
+ {"dataSource":2188891001,"dataSourceType":"entity","dimensions":[{"IsField":true,"FieldID":1004,"Name":"Source"}],"measures":[{"FieldID":1005,"Aggregate":"sum","Name":"Total Amount"}]}
15
+ ```
16
+
17
+ Field dimensions must set `IsField:true`. If `IsField` is omitted or false while `FieldID` is present, the CLI rejects the chart request before sending it. A saved chart returned by `widget get` or `widget list` with `FieldID` and `IsField:false` is not grouped by that field; update the chart before reading grouped statistics.
18
+
19
+ Fastfail rule: `widget preview-data` accepts only the chart query body. If validation reports unrecognized keys such as `id`, `entity_id`, or `options`, do not retry with a saved widget object. Retry with `dataSource`, `dataSourceType`, `dimensions`, `measures`, and optional `conditions` only.
20
+
21
+ After `widget update`, verify persistence with `widget get` or `widget list`. The saved widget must contain non-null `options.charts`; an update response alone is not enough evidence that the chart configuration was persisted.
22
+
23
+ ## Chart measures
24
+
25
+ Dashboard chart widgets use `ChartOption.measures[]` to define statistics. Supported `Aggregate` values are:
26
+
27
+ | Aggregate | Meaning |
28
+ | --- | --- |
29
+ | `sum` | Sum the selected numeric field. |
30
+ | `avg` | Average the selected numeric field. |
31
+ | `max` | Return the maximum selected field value. |
32
+ | `min` | Return the minimum selected field value. |
33
+ | `count` | Count selected field values; if `FieldID` is omitted, count rows. |
34
+ | `distinct_count` | Count distinct selected field values. |
35
+
36
+ Multiple measures are allowed in one chart widget. Use stable `Name` aliases so the returned data can be read without guessing.
37
+
38
+ Fastfail rule: unsupported aggregate names are invalid request bodies. Do not silently rewrite them to `count`, do not retry with a guessed aggregate, and do not save the widget until the `Aggregate` value is one of the supported values above.
39
+
40
+ Grouped-statistics evidence must include the requested dimension column in returned rows. If `widget preview-data` or user-side `widget data` returns only measure columns such as totals and counts, that is total-only data, not grouped data.