@happyvertical/smrt-reports 0.55.23 → 0.56.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/AGENTS.md +26 -3
- package/README.md +9 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +471 -112
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +174 -5
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +154 -0
- package/dist/playground.js.map +1 -0
- package/dist/recipes.d.ts +55 -0
- package/dist/recipes.d.ts.map +1 -1
- package/dist/runtime-report.d.ts +9 -0
- package/dist/runtime-report.d.ts.map +1 -1
- package/dist/runtime-server.d.ts +77 -0
- package/dist/runtime-server.d.ts.map +1 -0
- package/dist/runtime-spec.d.ts +2 -1
- package/dist/runtime-spec.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +203 -11
- package/dist/state.js +1 -1
- package/dist/state.js.map +1 -1
- package/dist/svelte/ReportConfirmation.svelte +99 -0
- package/dist/svelte/ReportConfirmation.svelte.d.ts +15 -0
- package/dist/svelte/ReportConfirmation.svelte.d.ts.map +1 -0
- package/dist/svelte/ReportDetail.svelte +69 -0
- package/dist/svelte/ReportDetail.svelte.d.ts +11 -0
- package/dist/svelte/ReportDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/ReportPicker.svelte +73 -0
- package/dist/svelte/ReportPicker.svelte.d.ts +16 -0
- package/dist/svelte/ReportPicker.svelte.d.ts.map +1 -0
- package/dist/svelte/ReportResult.svelte +69 -0
- package/dist/svelte/ReportResult.svelte.d.ts +10 -0
- package/dist/svelte/ReportResult.svelte.d.ts.map +1 -0
- package/dist/svelte/RuntimeReportChartWidget.svelte +20 -0
- package/dist/svelte/RuntimeReportChartWidget.svelte.d.ts +11 -0
- package/dist/svelte/RuntimeReportChartWidget.svelte.d.ts.map +1 -0
- package/dist/svelte/RuntimeReportDetailRoute.svelte +67 -0
- package/dist/svelte/RuntimeReportDetailRoute.svelte.d.ts +21 -0
- package/dist/svelte/RuntimeReportDetailRoute.svelte.d.ts.map +1 -0
- package/dist/svelte/RuntimeReportToolResult.svelte +26 -0
- package/dist/svelte/RuntimeReportToolResult.svelte.d.ts +8 -0
- package/dist/svelte/RuntimeReportToolResult.svelte.d.ts.map +1 -0
- package/dist/svelte/RuntimeReportWidgetOptions.svelte +58 -0
- package/dist/svelte/RuntimeReportWidgetOptions.svelte.d.ts +14 -0
- package/dist/svelte/RuntimeReportWidgetOptions.svelte.d.ts.map +1 -0
- package/dist/svelte/RuntimeReportsRoute.svelte +67 -0
- package/dist/svelte/RuntimeReportsRoute.svelte.d.ts +19 -0
- package/dist/svelte/RuntimeReportsRoute.svelte.d.ts.map +1 -0
- package/dist/svelte/SavedReports.svelte +114 -0
- package/dist/svelte/SavedReports.svelte.d.ts +11 -0
- package/dist/svelte/SavedReports.svelte.d.ts.map +1 -0
- package/dist/svelte/browser-client.d.ts +10 -0
- package/dist/svelte/browser-client.d.ts.map +1 -0
- package/dist/svelte/browser-client.js +16 -0
- package/dist/svelte/client.d.ts +15 -0
- package/dist/svelte/client.d.ts.map +1 -0
- package/dist/svelte/client.js +179 -0
- package/dist/svelte/index.d.ts +15 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +13 -0
- package/dist/svelte/messages.d.ts +72 -0
- package/dist/svelte/messages.d.ts.map +1 -0
- package/dist/svelte/messages.js +72 -0
- package/dist/svelte/playground.d.ts +132 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +62 -0
- package/dist/svelte/preview-data.d.ts +7 -0
- package/dist/svelte/preview-data.d.ts.map +1 -0
- package/dist/svelte/preview-data.js +59 -0
- package/dist/svelte/result.d.ts +8 -0
- package/dist/svelte/result.d.ts.map +1 -0
- package/dist/svelte/result.js +99 -0
- package/dist/svelte/spec.d.ts +5 -0
- package/dist/svelte/spec.d.ts.map +1 -0
- package/dist/svelte/spec.js +282 -0
- package/dist/svelte/tool-result.d.ts +11 -0
- package/dist/svelte/tool-result.d.ts.map +1 -0
- package/dist/svelte/tool-result.js +19 -0
- package/dist/svelte/types.d.ts +38 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +1 -0
- package/package.json +36 -12
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
packageName: string;
|
|
3
|
+
displayName: string;
|
|
4
|
+
description: string;
|
|
5
|
+
entries: ({
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
loadComponent: () => Promise<import("svelte").Component<import("./SavedReports.svelte").Props, {}, "">>;
|
|
10
|
+
props: {
|
|
11
|
+
client: import("./types.js").RuntimeReportsClient;
|
|
12
|
+
id?: undefined;
|
|
13
|
+
locale?: undefined;
|
|
14
|
+
proposal?: undefined;
|
|
15
|
+
confirm?: undefined;
|
|
16
|
+
reportId?: undefined;
|
|
17
|
+
measure?: undefined;
|
|
18
|
+
dimension?: undefined;
|
|
19
|
+
onchange?: undefined;
|
|
20
|
+
serverAvailable?: undefined;
|
|
21
|
+
result?: undefined;
|
|
22
|
+
};
|
|
23
|
+
order: number;
|
|
24
|
+
} | {
|
|
25
|
+
id: string;
|
|
26
|
+
title: string;
|
|
27
|
+
description: string;
|
|
28
|
+
loadComponent: () => Promise<import("svelte").Component<import("./ReportDetail.svelte").Props, {}, "">>;
|
|
29
|
+
props: {
|
|
30
|
+
id: string;
|
|
31
|
+
client: import("./types.js").RuntimeReportsClient;
|
|
32
|
+
locale: string;
|
|
33
|
+
proposal?: undefined;
|
|
34
|
+
confirm?: undefined;
|
|
35
|
+
reportId?: undefined;
|
|
36
|
+
measure?: undefined;
|
|
37
|
+
dimension?: undefined;
|
|
38
|
+
onchange?: undefined;
|
|
39
|
+
serverAvailable?: undefined;
|
|
40
|
+
result?: undefined;
|
|
41
|
+
};
|
|
42
|
+
order: number;
|
|
43
|
+
} | {
|
|
44
|
+
id: string;
|
|
45
|
+
title: string;
|
|
46
|
+
description: string;
|
|
47
|
+
loadComponent: () => Promise<import("svelte").Component<import("./ReportConfirmation.svelte").Props, {}, "">>;
|
|
48
|
+
props: {
|
|
49
|
+
proposal: import("./types.js").RuntimeReportProposal;
|
|
50
|
+
confirm: () => Promise<import("./types.js").SavedRuntimeReport>;
|
|
51
|
+
client?: undefined;
|
|
52
|
+
id?: undefined;
|
|
53
|
+
locale?: undefined;
|
|
54
|
+
reportId?: undefined;
|
|
55
|
+
measure?: undefined;
|
|
56
|
+
dimension?: undefined;
|
|
57
|
+
onchange?: undefined;
|
|
58
|
+
serverAvailable?: undefined;
|
|
59
|
+
result?: undefined;
|
|
60
|
+
};
|
|
61
|
+
order: number;
|
|
62
|
+
} | {
|
|
63
|
+
id: string;
|
|
64
|
+
title: string;
|
|
65
|
+
description: string;
|
|
66
|
+
loadComponent: () => Promise<import("svelte").Component<import("./ReportPicker.svelte").Props, {}, "">>;
|
|
67
|
+
props: {
|
|
68
|
+
client: import("./types.js").RuntimeReportsClient;
|
|
69
|
+
reportId: string;
|
|
70
|
+
measure: string;
|
|
71
|
+
dimension: string;
|
|
72
|
+
onchange: () => void;
|
|
73
|
+
id?: undefined;
|
|
74
|
+
locale?: undefined;
|
|
75
|
+
proposal?: undefined;
|
|
76
|
+
confirm?: undefined;
|
|
77
|
+
serverAvailable?: undefined;
|
|
78
|
+
result?: undefined;
|
|
79
|
+
};
|
|
80
|
+
order: number;
|
|
81
|
+
} | {
|
|
82
|
+
id: string;
|
|
83
|
+
title: string;
|
|
84
|
+
description: string;
|
|
85
|
+
loadComponent: () => Promise<import("svelte").Component<import("./RuntimeReportsRoute.svelte").Props, {}, "">>;
|
|
86
|
+
props: {
|
|
87
|
+
serverAvailable: boolean;
|
|
88
|
+
client?: undefined;
|
|
89
|
+
id?: undefined;
|
|
90
|
+
locale?: undefined;
|
|
91
|
+
proposal?: undefined;
|
|
92
|
+
confirm?: undefined;
|
|
93
|
+
reportId?: undefined;
|
|
94
|
+
measure?: undefined;
|
|
95
|
+
dimension?: undefined;
|
|
96
|
+
onchange?: undefined;
|
|
97
|
+
result?: undefined;
|
|
98
|
+
};
|
|
99
|
+
order: number;
|
|
100
|
+
} | {
|
|
101
|
+
id: string;
|
|
102
|
+
title: string;
|
|
103
|
+
description: string;
|
|
104
|
+
loadComponent: () => Promise<import("svelte").Component<import("./ReportResult.svelte").Props, {}, "">>;
|
|
105
|
+
props: {
|
|
106
|
+
result: {
|
|
107
|
+
rows: never[];
|
|
108
|
+
rowCount: number;
|
|
109
|
+
version: 1;
|
|
110
|
+
specHash: string;
|
|
111
|
+
title: string;
|
|
112
|
+
columns: import("../runtime-compiler.js").RuntimeReportColumn[];
|
|
113
|
+
truncated: boolean;
|
|
114
|
+
ranAt: string;
|
|
115
|
+
chart: import("../runtime-spec.js").RuntimeReportChart;
|
|
116
|
+
};
|
|
117
|
+
client?: undefined;
|
|
118
|
+
id?: undefined;
|
|
119
|
+
locale?: undefined;
|
|
120
|
+
proposal?: undefined;
|
|
121
|
+
confirm?: undefined;
|
|
122
|
+
reportId?: undefined;
|
|
123
|
+
measure?: undefined;
|
|
124
|
+
dimension?: undefined;
|
|
125
|
+
onchange?: undefined;
|
|
126
|
+
serverAvailable?: undefined;
|
|
127
|
+
};
|
|
128
|
+
order: number;
|
|
129
|
+
})[];
|
|
130
|
+
};
|
|
131
|
+
export default _default;
|
|
132
|
+
//# sourceMappingURL=playground.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playground.d.ts","sourceRoot":"","sources":["../../src/svelte/playground.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,wBAkEE"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { client, proposal, result, saved } from './preview-data.js';
|
|
2
|
+
export default {
|
|
3
|
+
packageName: '@happyvertical/smrt-reports',
|
|
4
|
+
displayName: 'Reports',
|
|
5
|
+
description: 'Saved runtime reports and live results.',
|
|
6
|
+
entries: [
|
|
7
|
+
{
|
|
8
|
+
id: 'saved-reports',
|
|
9
|
+
title: 'Saved reports',
|
|
10
|
+
description: 'Shared reports, author filter, and archive controls.',
|
|
11
|
+
loadComponent: () => import('./SavedReports.svelte').then((m) => m.default),
|
|
12
|
+
props: { client: client() },
|
|
13
|
+
order: 1,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'report-detail',
|
|
17
|
+
title: 'Report detail',
|
|
18
|
+
description: 'Money values with chart and table views.',
|
|
19
|
+
loadComponent: () => import('./ReportDetail.svelte').then((m) => m.default),
|
|
20
|
+
props: { id: saved.id, client: client(), locale: 'en-US' },
|
|
21
|
+
order: 2,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'report-confirmation',
|
|
25
|
+
title: 'Report confirmation',
|
|
26
|
+
description: 'Review a bounded proposal before saving.',
|
|
27
|
+
loadComponent: () => import('./ReportConfirmation.svelte').then((m) => m.default),
|
|
28
|
+
props: { proposal, confirm: async () => saved },
|
|
29
|
+
order: 3,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'report-picker',
|
|
33
|
+
title: 'Report picker',
|
|
34
|
+
description: 'Choose a saved report and its measure.',
|
|
35
|
+
loadComponent: () => import('./ReportPicker.svelte').then((m) => m.default),
|
|
36
|
+
props: {
|
|
37
|
+
client: client(),
|
|
38
|
+
reportId: saved.id,
|
|
39
|
+
measure: 'total',
|
|
40
|
+
dimension: 'region',
|
|
41
|
+
onchange: () => { },
|
|
42
|
+
},
|
|
43
|
+
order: 4,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'reports-unavailable',
|
|
47
|
+
title: 'Reports unavailable',
|
|
48
|
+
description: 'Static hosts do not make server requests.',
|
|
49
|
+
loadComponent: () => import('./RuntimeReportsRoute.svelte').then((m) => m.default),
|
|
50
|
+
props: { serverAvailable: false },
|
|
51
|
+
order: 5,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'report-empty',
|
|
55
|
+
title: 'Empty report',
|
|
56
|
+
description: 'A valid report with no matching rows.',
|
|
57
|
+
loadComponent: () => import('./ReportResult.svelte').then((m) => m.default),
|
|
58
|
+
props: { result: { ...result, rows: [], rowCount: 0 } },
|
|
59
|
+
order: 6,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RuntimeReportProposal, RuntimeReportResult, RuntimeReportSpec, RuntimeReportsClient, SavedRuntimeReport } from './types.js';
|
|
2
|
+
export declare const spec: RuntimeReportSpec;
|
|
3
|
+
export declare const result: RuntimeReportResult;
|
|
4
|
+
export declare const saved: SavedRuntimeReport;
|
|
5
|
+
export declare const proposal: RuntimeReportProposal;
|
|
6
|
+
export declare function client(overrides?: Partial<RuntimeReportsClient>): RuntimeReportsClient;
|
|
7
|
+
//# sourceMappingURL=preview-data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-data.d.ts","sourceRoot":"","sources":["../../src/svelte/preview-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,IAAI,EAAE,iBAWlB,CAAC;AACF,eAAO,MAAM,MAAM,EAAE,mBAmBpB,CAAC;AACF,eAAO,MAAM,KAAK,EAAE,kBAQnB,CAAC;AACF,eAAO,MAAM,QAAQ,EAAE,qBAOtB,CAAC;AACF,wBAAgB,MAAM,CACpB,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,oBAAoB,CAStB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const spec = {
|
|
2
|
+
version: 1,
|
|
3
|
+
title: 'Sales by region',
|
|
4
|
+
source: 'sales',
|
|
5
|
+
dimensions: [{ field: 'region', as: 'region' }],
|
|
6
|
+
measures: [{ fn: 'sum', field: 'amount', as: 'total' }],
|
|
7
|
+
filters: [],
|
|
8
|
+
having: [],
|
|
9
|
+
sort: [],
|
|
10
|
+
limit: 100,
|
|
11
|
+
chart: { type: 'bar', x: 'region', y: ['total'] },
|
|
12
|
+
};
|
|
13
|
+
export const result = {
|
|
14
|
+
version: 1,
|
|
15
|
+
specHash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
16
|
+
title: spec.title,
|
|
17
|
+
columns: [
|
|
18
|
+
{ id: 'region', label: 'Region', role: 'dimension', type: 'string' },
|
|
19
|
+
{
|
|
20
|
+
id: 'total',
|
|
21
|
+
label: 'Total',
|
|
22
|
+
role: 'measure',
|
|
23
|
+
type: 'integer',
|
|
24
|
+
format: 'money',
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
rows: [{ region: 'West', total: 12345 }],
|
|
28
|
+
rowCount: 1,
|
|
29
|
+
truncated: false,
|
|
30
|
+
ranAt: '2026-10-13T12:00:00Z',
|
|
31
|
+
chart: spec.chart,
|
|
32
|
+
};
|
|
33
|
+
export const saved = {
|
|
34
|
+
id: 'report-1',
|
|
35
|
+
title: spec.title,
|
|
36
|
+
sourceId: 'sales',
|
|
37
|
+
createdByUserId: 'alice',
|
|
38
|
+
status: 'active',
|
|
39
|
+
spec,
|
|
40
|
+
canArchive: true,
|
|
41
|
+
};
|
|
42
|
+
export const proposal = {
|
|
43
|
+
status: 'awaiting_confirmation',
|
|
44
|
+
specHash: result.specHash,
|
|
45
|
+
spec,
|
|
46
|
+
preview: result,
|
|
47
|
+
sourceLabel: 'Sales',
|
|
48
|
+
confirmPath: '/api/_reports/runtime/confirm',
|
|
49
|
+
};
|
|
50
|
+
export function client(overrides = {}) {
|
|
51
|
+
return {
|
|
52
|
+
list: async () => [saved],
|
|
53
|
+
get: async () => saved,
|
|
54
|
+
run: async () => result,
|
|
55
|
+
archive: async () => { },
|
|
56
|
+
confirm: async () => saved,
|
|
57
|
+
...overrides,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RuntimeReportResult } from './types.js';
|
|
2
|
+
export declare function reportError(error: unknown): 'reports.denied' | 'reports.invalid' | 'reports.error';
|
|
3
|
+
/** Validate untrusted result data before formatting, including integer precision. */
|
|
4
|
+
export declare function validReportResult(value: unknown): value is RuntimeReportResult;
|
|
5
|
+
export declare function reportCell(value: unknown, column: RuntimeReportResult['columns'][number], locale: string, currency: string): string;
|
|
6
|
+
/** Narrow the UI projection; canonical spec validation remains server-owned. */
|
|
7
|
+
export declare function validReportProposal(value: unknown): value is import('./types.js').RuntimeReportProposal;
|
|
8
|
+
//# sourceMappingURL=result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/svelte/result.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,GACb,gBAAgB,GAAG,iBAAiB,GAAG,eAAe,CAQxD;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,mBAAmB,CAkD9B;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAC9C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,MAAM,CAcR;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,OAAO,YAAY,EAAE,qBAAqB,CA8BrD"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { formatWidgetValue } from '@happyvertical/smrt-ui/charts/format';
|
|
2
|
+
import { validRuntimeReportSpec } from './spec.js';
|
|
3
|
+
export function reportError(error) {
|
|
4
|
+
if (error && typeof error === 'object') {
|
|
5
|
+
const e = error;
|
|
6
|
+
if (e.status === 401 || e.status === 403 || e.code === 'unknown_field')
|
|
7
|
+
return 'reports.denied';
|
|
8
|
+
if (e.code === 'invalid_result')
|
|
9
|
+
return 'reports.invalid';
|
|
10
|
+
}
|
|
11
|
+
return 'reports.error';
|
|
12
|
+
}
|
|
13
|
+
/** Validate untrusted result data before formatting, including integer precision. */
|
|
14
|
+
export function validReportResult(value) {
|
|
15
|
+
if (!value || typeof value !== 'object')
|
|
16
|
+
return false;
|
|
17
|
+
const r = value;
|
|
18
|
+
return (r.version === 1 &&
|
|
19
|
+
typeof r.title === 'string' &&
|
|
20
|
+
typeof r.specHash === 'string' &&
|
|
21
|
+
typeof r.ranAt === 'string' &&
|
|
22
|
+
typeof r.truncated === 'boolean' &&
|
|
23
|
+
Array.isArray(r.columns) &&
|
|
24
|
+
r.columns.length > 0 &&
|
|
25
|
+
r.columns.length <= 10 &&
|
|
26
|
+
new Set(r.columns.map((c) => c?.id)).size === r.columns.length &&
|
|
27
|
+
r.columns.every((c) => c &&
|
|
28
|
+
typeof c.id === 'string' &&
|
|
29
|
+
typeof c.label === 'string' &&
|
|
30
|
+
['dimension', 'measure'].includes(c.role) &&
|
|
31
|
+
['string', 'integer', 'decimal', 'boolean', 'datetime', 'id'].includes(c.type)) &&
|
|
32
|
+
Array.isArray(r.rows) &&
|
|
33
|
+
r.rows.length <= 1000 &&
|
|
34
|
+
r.rowCount === r.rows.length &&
|
|
35
|
+
r.rows.every((row) => row &&
|
|
36
|
+
typeof row === 'object' &&
|
|
37
|
+
r.columns.every((c) => {
|
|
38
|
+
const v = row[c.id];
|
|
39
|
+
if (v === null)
|
|
40
|
+
return true;
|
|
41
|
+
if (c.type === 'integer')
|
|
42
|
+
return typeof v === 'number' && Number.isSafeInteger(v);
|
|
43
|
+
if (c.type === 'decimal')
|
|
44
|
+
return typeof v === 'number' && Number.isFinite(v);
|
|
45
|
+
if (c.type === 'boolean')
|
|
46
|
+
return typeof v === 'boolean';
|
|
47
|
+
return typeof v === 'string';
|
|
48
|
+
})) &&
|
|
49
|
+
!!r.chart &&
|
|
50
|
+
['bar', 'line', 'table', 'pie', 'stat'].includes(r.chart.type) &&
|
|
51
|
+
(r.chart.y === undefined ||
|
|
52
|
+
(Array.isArray(r.chart.y) &&
|
|
53
|
+
r.chart.y.every((id) => r.columns.some((c) => c.id === id && c.role === 'measure')))) &&
|
|
54
|
+
(r.chart.x === undefined || r.columns.some((c) => c.id === r.chart.x)));
|
|
55
|
+
}
|
|
56
|
+
export function reportCell(value, column, locale, currency) {
|
|
57
|
+
if (value === null || value === undefined)
|
|
58
|
+
return '–';
|
|
59
|
+
if (typeof value === 'number')
|
|
60
|
+
return formatWidgetValue(value, {
|
|
61
|
+
format: column.format === 'money'
|
|
62
|
+
? 'money'
|
|
63
|
+
: column.type === 'integer'
|
|
64
|
+
? 'integer'
|
|
65
|
+
: 'decimal',
|
|
66
|
+
locale,
|
|
67
|
+
currency,
|
|
68
|
+
});
|
|
69
|
+
return String(value);
|
|
70
|
+
}
|
|
71
|
+
/** Narrow the UI projection; canonical spec validation remains server-owned. */
|
|
72
|
+
export function validReportProposal(value) {
|
|
73
|
+
if (!value || typeof value !== 'object')
|
|
74
|
+
return false;
|
|
75
|
+
const p = value;
|
|
76
|
+
if (p.status !== 'awaiting_confirmation' ||
|
|
77
|
+
typeof p.specHash !== 'string' ||
|
|
78
|
+
!/^[a-f0-9]{64}$/.test(p.specHash) ||
|
|
79
|
+
typeof p.sourceLabel !== 'string' ||
|
|
80
|
+
p.sourceLabel.length < 1 ||
|
|
81
|
+
p.sourceLabel.length > 120 ||
|
|
82
|
+
typeof p.confirmPath !== 'string' ||
|
|
83
|
+
p.confirmPath.length > 2048 ||
|
|
84
|
+
!p.confirmPath.startsWith('/') ||
|
|
85
|
+
p.confirmPath.startsWith('//') ||
|
|
86
|
+
!validReportResult(p.preview) ||
|
|
87
|
+
p.preview.specHash !== p.specHash ||
|
|
88
|
+
!p.spec)
|
|
89
|
+
return false;
|
|
90
|
+
if (!validRuntimeReportSpec(p.spec) || p.preview.title !== p.spec.title)
|
|
91
|
+
return false;
|
|
92
|
+
const roles = new Map([
|
|
93
|
+
...p.spec.dimensions.map((entry) => [entry.as, 'dimension']),
|
|
94
|
+
...p.spec.measures.map((entry) => [entry.as, 'measure']),
|
|
95
|
+
]);
|
|
96
|
+
return (p.preview.columns.length === roles.size &&
|
|
97
|
+
p.preview.columns.every((column) => roles.get(column.id) === column.role) &&
|
|
98
|
+
JSON.stringify(p.preview.chart) === JSON.stringify(p.spec.chart));
|
|
99
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RuntimeReportSpec } from './types.js';
|
|
2
|
+
/** Browser-safe validation of the server's already-normalized closed grammar. */
|
|
3
|
+
export declare function validRuntimeReportSpec(value: unknown): value is RuntimeReportSpec;
|
|
4
|
+
export declare function runtimeReportSpecFromWire(value: unknown): RuntimeReportSpec | null;
|
|
5
|
+
//# sourceMappingURL=spec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../src/svelte/spec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA+JpD,iFAAiF;AACjF,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,iBAAiB,CA4I5B;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,GACb,iBAAiB,GAAG,IAAI,CAW1B"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
const IDENTIFIER = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
2
|
+
const ALIAS = /^[a-z][a-z0-9_]*$/;
|
|
3
|
+
const SOURCE = /^[A-Za-z@][A-Za-z0-9_@./:-]*$/;
|
|
4
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: mirrors the server grammar's printable-text guard
|
|
5
|
+
const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
6
|
+
const BUCKETS = new Set([
|
|
7
|
+
'minute',
|
|
8
|
+
'hour',
|
|
9
|
+
'day',
|
|
10
|
+
'week',
|
|
11
|
+
'month',
|
|
12
|
+
'quarter',
|
|
13
|
+
'year',
|
|
14
|
+
]);
|
|
15
|
+
const MEASURES = new Set([
|
|
16
|
+
'count',
|
|
17
|
+
'countDistinct',
|
|
18
|
+
'sum',
|
|
19
|
+
'avg',
|
|
20
|
+
'min',
|
|
21
|
+
'max',
|
|
22
|
+
]);
|
|
23
|
+
const FILTERS = new Set([
|
|
24
|
+
'eq',
|
|
25
|
+
'ne',
|
|
26
|
+
'gt',
|
|
27
|
+
'gte',
|
|
28
|
+
'lt',
|
|
29
|
+
'lte',
|
|
30
|
+
'in',
|
|
31
|
+
'notIn',
|
|
32
|
+
'contains',
|
|
33
|
+
'isNull',
|
|
34
|
+
'isNotNull',
|
|
35
|
+
]);
|
|
36
|
+
const HAVING = new Set(['eq', 'ne', 'gt', 'gte', 'lt', 'lte']);
|
|
37
|
+
const CHARTS = new Set(['table', 'bar', 'line', 'pie', 'stat']);
|
|
38
|
+
const RESERVED = new Set([
|
|
39
|
+
'all',
|
|
40
|
+
'and',
|
|
41
|
+
'as',
|
|
42
|
+
'asc',
|
|
43
|
+
'between',
|
|
44
|
+
'by',
|
|
45
|
+
'case',
|
|
46
|
+
'cast',
|
|
47
|
+
'check',
|
|
48
|
+
'column',
|
|
49
|
+
'constraint',
|
|
50
|
+
'create',
|
|
51
|
+
'cross',
|
|
52
|
+
'default',
|
|
53
|
+
'delete',
|
|
54
|
+
'desc',
|
|
55
|
+
'distinct',
|
|
56
|
+
'drop',
|
|
57
|
+
'else',
|
|
58
|
+
'end',
|
|
59
|
+
'except',
|
|
60
|
+
'exists',
|
|
61
|
+
'false',
|
|
62
|
+
'for',
|
|
63
|
+
'foreign',
|
|
64
|
+
'from',
|
|
65
|
+
'full',
|
|
66
|
+
'group',
|
|
67
|
+
'having',
|
|
68
|
+
'in',
|
|
69
|
+
'inner',
|
|
70
|
+
'insert',
|
|
71
|
+
'intersect',
|
|
72
|
+
'into',
|
|
73
|
+
'is',
|
|
74
|
+
'join',
|
|
75
|
+
'left',
|
|
76
|
+
'like',
|
|
77
|
+
'limit',
|
|
78
|
+
'natural',
|
|
79
|
+
'not',
|
|
80
|
+
'null',
|
|
81
|
+
'offset',
|
|
82
|
+
'on',
|
|
83
|
+
'or',
|
|
84
|
+
'order',
|
|
85
|
+
'outer',
|
|
86
|
+
'primary',
|
|
87
|
+
'references',
|
|
88
|
+
'right',
|
|
89
|
+
'select',
|
|
90
|
+
'set',
|
|
91
|
+
'table',
|
|
92
|
+
'then',
|
|
93
|
+
'to',
|
|
94
|
+
'true',
|
|
95
|
+
'union',
|
|
96
|
+
'unique',
|
|
97
|
+
'update',
|
|
98
|
+
'user',
|
|
99
|
+
'using',
|
|
100
|
+
'values',
|
|
101
|
+
'when',
|
|
102
|
+
'where',
|
|
103
|
+
'with',
|
|
104
|
+
]);
|
|
105
|
+
function record(value) {
|
|
106
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
107
|
+
return null;
|
|
108
|
+
const prototype = Object.getPrototypeOf(value);
|
|
109
|
+
return prototype === Object.prototype || prototype === null
|
|
110
|
+
? value
|
|
111
|
+
: null;
|
|
112
|
+
}
|
|
113
|
+
function keys(value, allowed) {
|
|
114
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
115
|
+
}
|
|
116
|
+
function text(value, max, optional = false) {
|
|
117
|
+
if (value === undefined)
|
|
118
|
+
return optional;
|
|
119
|
+
if (typeof value !== 'string')
|
|
120
|
+
return false;
|
|
121
|
+
const trimmed = value.trim();
|
|
122
|
+
return (trimmed.length > 0 &&
|
|
123
|
+
trimmed.length <= max &&
|
|
124
|
+
trimmed === value &&
|
|
125
|
+
!CONTROL.test(trimmed));
|
|
126
|
+
}
|
|
127
|
+
function field(value) {
|
|
128
|
+
return (typeof value === 'string' && value.length <= 64 && IDENTIFIER.test(value));
|
|
129
|
+
}
|
|
130
|
+
function alias(value) {
|
|
131
|
+
return (typeof value === 'string' &&
|
|
132
|
+
value.length <= 40 &&
|
|
133
|
+
ALIAS.test(value) &&
|
|
134
|
+
!RESERVED.has(value));
|
|
135
|
+
}
|
|
136
|
+
function scalar(value) {
|
|
137
|
+
return ((typeof value === 'string' &&
|
|
138
|
+
value.length <= 256 &&
|
|
139
|
+
!value.includes('\u0000')) ||
|
|
140
|
+
(typeof value === 'number' && Number.isFinite(value)) ||
|
|
141
|
+
typeof value === 'boolean');
|
|
142
|
+
}
|
|
143
|
+
/** Browser-safe validation of the server's already-normalized closed grammar. */
|
|
144
|
+
export function validRuntimeReportSpec(value) {
|
|
145
|
+
const spec = record(value);
|
|
146
|
+
if (!spec ||
|
|
147
|
+
!keys(spec, [
|
|
148
|
+
'version',
|
|
149
|
+
'title',
|
|
150
|
+
'description',
|
|
151
|
+
'source',
|
|
152
|
+
'dimensions',
|
|
153
|
+
'measures',
|
|
154
|
+
'filters',
|
|
155
|
+
'having',
|
|
156
|
+
'sort',
|
|
157
|
+
'limit',
|
|
158
|
+
'chart',
|
|
159
|
+
]) ||
|
|
160
|
+
spec.version !== 1 ||
|
|
161
|
+
!text(spec.title, 120) ||
|
|
162
|
+
!text(spec.description, 500, true) ||
|
|
163
|
+
typeof spec.source !== 'string' ||
|
|
164
|
+
spec.source.length === 0 ||
|
|
165
|
+
spec.source.length > 120 ||
|
|
166
|
+
!SOURCE.test(spec.source) ||
|
|
167
|
+
!Array.isArray(spec.dimensions) ||
|
|
168
|
+
spec.dimensions.length > 4 ||
|
|
169
|
+
!Array.isArray(spec.measures) ||
|
|
170
|
+
spec.measures.length < 1 ||
|
|
171
|
+
spec.measures.length > 6 ||
|
|
172
|
+
!Array.isArray(spec.filters) ||
|
|
173
|
+
spec.filters.length > 12 ||
|
|
174
|
+
!Array.isArray(spec.having) ||
|
|
175
|
+
spec.having.length > 4 ||
|
|
176
|
+
!Array.isArray(spec.sort) ||
|
|
177
|
+
spec.sort.length > 3 ||
|
|
178
|
+
!Number.isSafeInteger(spec.limit) ||
|
|
179
|
+
spec.limit < 1 ||
|
|
180
|
+
spec.limit > 1000)
|
|
181
|
+
return false;
|
|
182
|
+
const dimensions = new Set();
|
|
183
|
+
const measures = new Set();
|
|
184
|
+
const aliases = new Set();
|
|
185
|
+
for (const value of spec.dimensions) {
|
|
186
|
+
const item = record(value);
|
|
187
|
+
if (!item ||
|
|
188
|
+
!keys(item, ['field', 'as', 'bucket']) ||
|
|
189
|
+
!field(item.field) ||
|
|
190
|
+
!alias(item.as) ||
|
|
191
|
+
(item.bucket !== undefined &&
|
|
192
|
+
(typeof item.bucket !== 'string' || !BUCKETS.has(item.bucket))) ||
|
|
193
|
+
aliases.has(item.as))
|
|
194
|
+
return false;
|
|
195
|
+
aliases.add(item.as);
|
|
196
|
+
dimensions.add(item.as);
|
|
197
|
+
}
|
|
198
|
+
for (const value of spec.measures) {
|
|
199
|
+
const item = record(value);
|
|
200
|
+
if (!item ||
|
|
201
|
+
!keys(item, ['fn', 'field', 'as']) ||
|
|
202
|
+
typeof item.fn !== 'string' ||
|
|
203
|
+
!MEASURES.has(item.fn) ||
|
|
204
|
+
!alias(item.as) ||
|
|
205
|
+
aliases.has(item.as) ||
|
|
206
|
+
(item.field !== undefined && !field(item.field)) ||
|
|
207
|
+
(item.fn !== 'count' && item.field === undefined))
|
|
208
|
+
return false;
|
|
209
|
+
aliases.add(item.as);
|
|
210
|
+
measures.add(item.as);
|
|
211
|
+
}
|
|
212
|
+
for (const value of spec.filters) {
|
|
213
|
+
const item = record(value);
|
|
214
|
+
if (!item ||
|
|
215
|
+
!keys(item, ['field', 'op', 'value']) ||
|
|
216
|
+
!field(item.field) ||
|
|
217
|
+
typeof item.op !== 'string' ||
|
|
218
|
+
!FILTERS.has(item.op))
|
|
219
|
+
return false;
|
|
220
|
+
if (item.op === 'isNull' || item.op === 'isNotNull') {
|
|
221
|
+
if (item.value !== undefined)
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
else if (item.op === 'in' || item.op === 'notIn') {
|
|
225
|
+
if (!Array.isArray(item.value) ||
|
|
226
|
+
item.value.length < 1 ||
|
|
227
|
+
item.value.length > 100 ||
|
|
228
|
+
!item.value.every(scalar))
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
else if (!scalar(item.value) ||
|
|
232
|
+
(item.op === 'contains' && typeof item.value !== 'string'))
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
for (const value of spec.having) {
|
|
236
|
+
const item = record(value);
|
|
237
|
+
if (!item ||
|
|
238
|
+
!keys(item, ['measure', 'op', 'value']) ||
|
|
239
|
+
!alias(item.measure) ||
|
|
240
|
+
!measures.has(item.measure) ||
|
|
241
|
+
typeof item.op !== 'string' ||
|
|
242
|
+
!HAVING.has(item.op) ||
|
|
243
|
+
typeof item.value !== 'number' ||
|
|
244
|
+
!Number.isFinite(item.value))
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
for (const value of spec.sort) {
|
|
248
|
+
const item = record(value);
|
|
249
|
+
if (!item ||
|
|
250
|
+
!keys(item, ['by', 'direction']) ||
|
|
251
|
+
!alias(item.by) ||
|
|
252
|
+
!aliases.has(item.by) ||
|
|
253
|
+
(item.direction !== 'asc' && item.direction !== 'desc'))
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const chart = record(spec.chart);
|
|
257
|
+
if (!chart ||
|
|
258
|
+
!keys(chart, ['type', 'x', 'y']) ||
|
|
259
|
+
typeof chart.type !== 'string' ||
|
|
260
|
+
!CHARTS.has(chart.type) ||
|
|
261
|
+
(chart.x !== undefined && (!alias(chart.x) || !dimensions.has(chart.x))) ||
|
|
262
|
+
(chart.y !== undefined &&
|
|
263
|
+
(!Array.isArray(chart.y) ||
|
|
264
|
+
chart.y.length > 6 ||
|
|
265
|
+
!chart.y.every((name) => alias(name) && measures.has(name)))))
|
|
266
|
+
return false;
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
export function runtimeReportSpecFromWire(value) {
|
|
270
|
+
let candidate = value;
|
|
271
|
+
if (typeof value === 'string') {
|
|
272
|
+
if (value.length > 64 * 1024)
|
|
273
|
+
return null;
|
|
274
|
+
try {
|
|
275
|
+
candidate = JSON.parse(value);
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return validRuntimeReportSpec(candidate) ? candidate : null;
|
|
282
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Component } from 'svelte';
|
|
2
|
+
import type { RuntimeReportProposal } from './types.js';
|
|
3
|
+
export interface ToolResultMessage {
|
|
4
|
+
toolCallData?: unknown;
|
|
5
|
+
}
|
|
6
|
+
export declare function runtimeReportProposalFromMessage(message: ToolResultMessage): RuntimeReportProposal | null;
|
|
7
|
+
/** Host selector for AssistantDock's generic `toolResultComponent` seam. */
|
|
8
|
+
export declare function runtimeReportToolResultComponent(message: ToolResultMessage): Component<{
|
|
9
|
+
message: ToolResultMessage;
|
|
10
|
+
}> | undefined;
|
|
11
|
+
//# sourceMappingURL=tool-result.d.ts.map
|