@xpert-ai/plugin-live-dashboard 0.0.0-stage → 0.1.1
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/.xpertai-plugin/plugin.json +8 -0
- package/README.md +108 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +11 -0
- package/dist/lib/agent.schema.d.ts +568 -0
- package/dist/lib/agent.schema.js +52 -0
- package/dist/lib/dashboard-icon.d.ts +5 -0
- package/dist/lib/dashboard-icon.js +8 -0
- package/dist/lib/dashboard-navigation.d.ts +38 -0
- package/dist/lib/dashboard-navigation.js +18 -0
- package/dist/lib/dashboard-queries.d.ts +8 -0
- package/dist/lib/dashboard-queries.js +39 -0
- package/dist/lib/dashboard-receipt.d.ts +11 -0
- package/dist/lib/dashboard-receipt.js +35 -0
- package/dist/lib/dashboard.schema.d.ts +816 -0
- package/dist/lib/dashboard.schema.js +74 -0
- package/dist/lib/dashboard.service.d.ts +243 -0
- package/dist/lib/dashboard.service.js +185 -0
- package/dist/lib/document-libraries.d.ts +8 -0
- package/dist/lib/document-libraries.js +41 -0
- package/dist/lib/document-package.d.ts +41 -0
- package/dist/lib/document-package.js +25 -0
- package/dist/lib/editing.d.ts +403 -0
- package/dist/lib/editing.js +114 -0
- package/dist/lib/filter-evidence.d.ts +71 -0
- package/dist/lib/filter-evidence.js +60 -0
- package/dist/lib/html-guidance.d.ts +1 -0
- package/dist/lib/html-guidance.js +4 -0
- package/dist/lib/html-validation.d.ts +19 -0
- package/dist/lib/html-validation.js +136 -0
- package/dist/lib/middleware.d.ts +10 -0
- package/dist/lib/middleware.js +225 -0
- package/dist/lib/plugin.d.ts +2 -0
- package/dist/lib/plugin.js +22 -0
- package/dist/lib/presentation.provider.d.ts +122 -0
- package/dist/lib/presentation.provider.js +72 -0
- package/dist/lib/remote-entry.d.ts +4 -0
- package/dist/lib/remote-entry.js +12 -0
- package/dist/lib/renderer.d.ts +58 -0
- package/dist/lib/renderer.js +32 -0
- package/dist/lib/resource-card.d.ts +21 -0
- package/dist/lib/resource-card.js +33 -0
- package/dist/lib/save-evidence.d.ts +24 -0
- package/dist/lib/save-evidence.js +99 -0
- package/dist/lib/sharing.service.d.ts +93 -0
- package/dist/lib/sharing.service.js +71 -0
- package/dist/lib/view.provider.d.ts +235 -0
- package/dist/lib/view.provider.js +103 -0
- package/dist/libraries/ECHARTS-LICENSE.txt +222 -0
- package/dist/libraries/ECHARTS-NOTICE.txt +5 -0
- package/dist/libraries/THREE-LICENSE.txt +21 -0
- package/dist/libraries/catalog.json +1 -0
- package/dist/remote/app.css +1 -0
- package/dist/remote/app.js +253 -0
- package/dist/remote/build-manifest.json +63 -0
- package/dist/remote/renderer-source.json +5 -0
- package/dist/remote/renderer.html +4653 -0
- package/dist/sandbox-actions/action.json +10 -0
- package/dist/sandbox-actions/bundle/host.js +149 -0
- package/dist/sandbox-actions/bundle/runner.mjs +15 -0
- package/dist/sandbox-actions/bundle/validate.mjs +140 -0
- package/dist/validation/host-entry.d.ts +1 -0
- package/dist/validation/host-entry.js +6 -0
- package/docs/AGENTS.md +9 -0
- package/docs/acceptance.md +89 -0
- package/docs/agent-acceptance.md +96 -0
- package/docs/chat-resource-cards.mdx +60 -0
- package/docs/docs.json +49 -0
- package/docs/drafts-acceptance.mdx +42 -0
- package/docs/drafts.mdx +35 -0
- package/docs/editing.mdx +63 -0
- package/docs/evolution-acceptance.mdx +98 -0
- package/docs/file-authoring-acceptance.mdx +62 -0
- package/docs/file-authoring.mdx +49 -0
- package/docs/file-datasets-acceptance.mdx +65 -0
- package/docs/fixed-build-multimodel-acceptance.mdx +129 -0
- package/docs/frontend-libraries-acceptance.mdx +86 -0
- package/docs/frontend-libraries.mdx +57 -0
- package/docs/html-acceptance.md +86 -0
- package/docs/html-mode.md +76 -0
- package/docs/index.mdx +47 -0
- package/docs/retry-regression-acceptance.mdx +112 -0
- package/docs/team-sharing-acceptance.mdx +66 -0
- package/docs/team-sharing.mdx +44 -0
- package/docs/verification.mdx +91 -0
- package/examples/assistant-middleware.yaml +28 -0
- package/examples/data-xpert-sales.json +118 -0
- package/examples/postgres-sales.json +93 -0
- package/index.cjs +1 -0
- package/package.json +73 -3
- package/skills/live-dashboard/SKILL.md +71 -0
- package/skills/live-dashboard/agents/xpertai.yaml +21 -0
- package/skills/live-dashboard/references/data-validation.md +15 -0
- package/skills/live-dashboard/references/html-lifecycle.md +129 -0
- package/skills/live-dashboard/references/html-runtime.md +53 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queryBindingSchema = exports.querySchema = exports.saveSchema = exports.controlsSchema = exports.dashboardChangesSchema = exports.dashboardSchema = void 0;
|
|
4
|
+
const v3_1 = require("zod/v3");
|
|
5
|
+
const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
|
|
6
|
+
const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
|
|
7
|
+
const id = v3_1.z.string().regex(/^[A-Za-z][A-Za-z0-9_.-]{0,79}$/);
|
|
8
|
+
exports.dashboardSchema = v3_1.z.object({
|
|
9
|
+
schema: v3_1.z.literal('xpert.live-dashboard/v1'), id: v3_1.z.string().uuid(), title: v3_1.z.string().min(1).max(160),
|
|
10
|
+
bindings: v3_1.z.array(analytics_1.analyticsBindingSchema).min(1).max(8),
|
|
11
|
+
presentation: v3_1.z.object({ type: v3_1.z.literal('html'), html: artifact_document_1.artifactDocumentHtml, dependencies: artifact_document_1.artifactDocumentDependencyLocks.optional() }).strict().optional(),
|
|
12
|
+
controls: v3_1.z.array(v3_1.z.object({ controlId: id, type: v3_1.z.enum(['string', 'number', 'boolean', 'date']),
|
|
13
|
+
defaultValue: v3_1.z.union([v3_1.z.string(), v3_1.z.number(), v3_1.z.boolean(), v3_1.z.null()]).optional(),
|
|
14
|
+
allowedValues: v3_1.z.array(v3_1.z.union([v3_1.z.string(), v3_1.z.number(), v3_1.z.boolean(), v3_1.z.null()])).max(200).optional(),
|
|
15
|
+
label: v3_1.z.string().min(1).max(160) }).strict()).max(24),
|
|
16
|
+
charts: v3_1.z.array(v3_1.z.object({ id, title: v3_1.z.string().min(1).max(160), type: v3_1.z.enum(['bar', 'line', 'area', 'pie', 'scatter']),
|
|
17
|
+
datasetId: id, x: id, y: id, color: id.optional() }).strict()).max(20),
|
|
18
|
+
tables: v3_1.z.array(v3_1.z.object({ id, title: v3_1.z.string().min(1).max(160), datasetId: id, fields: v3_1.z.array(id).min(1).max(30) }).strict()).max(20)
|
|
19
|
+
}).strict().superRefine((manifest, ctx) => {
|
|
20
|
+
const unique = (values, name) => {
|
|
21
|
+
if (new Set(values).size !== values.length)
|
|
22
|
+
ctx.addIssue({ code: 'custom', message: `Duplicate ${name}` });
|
|
23
|
+
};
|
|
24
|
+
unique(manifest.bindings.map((binding) => binding.id), 'binding');
|
|
25
|
+
unique(manifest.bindings.map((binding) => binding.output.datasetId), 'dataset');
|
|
26
|
+
unique(manifest.controls.map((control) => control.controlId), 'control');
|
|
27
|
+
unique([...manifest.charts, ...manifest.tables].map((view) => view.id), 'view');
|
|
28
|
+
if (!manifest.presentation && !manifest.charts.length && !manifest.tables.length)
|
|
29
|
+
ctx.addIssue({ code: 'custom', message: 'At least one chart or table is required' });
|
|
30
|
+
if (manifest.presentation && (manifest.charts.length || manifest.tables.length))
|
|
31
|
+
ctx.addIssue({ code: 'custom', message: 'HTML presentation owns its charts; use empty charts/tables arrays' });
|
|
32
|
+
for (const control of manifest.controls) {
|
|
33
|
+
const parameters = manifest.bindings.flatMap((binding) => binding.parameters.filter((parameter) => parameter.controlId === control.controlId));
|
|
34
|
+
if (!parameters.length)
|
|
35
|
+
ctx.addIssue({ code: 'custom', message: 'Control is not bound to a query parameter' });
|
|
36
|
+
const values = [...(control.defaultValue !== undefined ? [control.defaultValue] : []), ...(control.allowedValues ?? [])];
|
|
37
|
+
if (values.some((value) => value !== null && (control.type === 'number' ? typeof value !== 'number' || !Number.isFinite(value) :
|
|
38
|
+
control.type === 'boolean' ? typeof value !== 'boolean' : typeof value !== 'string')))
|
|
39
|
+
ctx.addIssue({ code: 'custom', message: 'Control value has an incompatible type' });
|
|
40
|
+
if (control.allowedValues && control.defaultValue !== undefined && !control.allowedValues.includes(control.defaultValue))
|
|
41
|
+
ctx.addIssue({ code: 'custom', message: 'Default control value is not allowed' });
|
|
42
|
+
for (const parameter of parameters) {
|
|
43
|
+
if (parameter.defaultValue !== undefined && control.defaultValue !== undefined && parameter.defaultValue !== control.defaultValue)
|
|
44
|
+
ctx.addIssue({ code: 'custom', message: 'Control and parameter defaults disagree' });
|
|
45
|
+
// Match platform resolveAnalyticsParameters: null is governed by required, not the scalar allowlist.
|
|
46
|
+
if (values.some((value) => value === null ? parameter.required : parameter.allowedValues && !parameter.allowedValues.includes(value)))
|
|
47
|
+
ctx.addIssue({ code: 'custom', message: 'Control values conflict with parameter restrictions' });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const binding of manifest.bindings)
|
|
51
|
+
for (const parameter of binding.parameters) {
|
|
52
|
+
const control = manifest.controls.find((control) => control.controlId === parameter.controlId);
|
|
53
|
+
if (!control || control.type !== parameter.type)
|
|
54
|
+
ctx.addIssue({ code: 'custom', message: 'Control mapping is missing or has an incompatible type' });
|
|
55
|
+
}
|
|
56
|
+
for (const view of [...manifest.charts, ...manifest.tables]) {
|
|
57
|
+
const output = manifest.bindings.find((binding) => binding.output.datasetId === view.datasetId)?.output;
|
|
58
|
+
const fields = 'fields' in view ? view.fields : [view.x, view.y, ...(view.color ? [view.color] : [])];
|
|
59
|
+
if (!output || fields.some((id) => !output.fields.some((field) => field.id === id)))
|
|
60
|
+
ctx.addIssue({ code: 'custom', message: 'View references an undeclared Dataset field' });
|
|
61
|
+
if ('y' in view && !output?.fields.some((field) => field.id === view.y && ['float64', 'int64', 'decimal'].includes(field.logicalType)))
|
|
62
|
+
ctx.addIssue({ code: 'custom', message: 'Chart measure must be numeric' });
|
|
63
|
+
if ('y' in view && view.type !== 'scatter' && output?.grain.some((id) => id !== view.x && id !== view.color))
|
|
64
|
+
ctx.addIssue({ code: 'custom', message: 'Chart must encode every Dataset grain dimension in x/color; use another aggregated binding for totals' });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
/** Updates never choose identity or source scope; those come from the authorized saved version. */
|
|
68
|
+
exports.dashboardChangesSchema = exports.dashboardSchema.innerType().pick({ title: true, bindings: true,
|
|
69
|
+
controls: true, charts: true, tables: true, presentation: true }).partial().strict();
|
|
70
|
+
exports.controlsSchema = v3_1.z.record(v3_1.z.union([v3_1.z.string().max(4000), v3_1.z.number().finite(), v3_1.z.boolean(), v3_1.z.null()]));
|
|
71
|
+
exports.saveSchema = v3_1.z.object({ manifest: exports.dashboardSchema, expectedVersionId: v3_1.z.string().uuid().nullable(), expectedRevision: v3_1.z.number().int().nonnegative().default(0) }).strict();
|
|
72
|
+
exports.querySchema = v3_1.z.object({ artifactId: v3_1.z.string().uuid(), versionId: v3_1.z.string().uuid().optional(), draftRevision: v3_1.z.number().int().positive().optional(), controls: exports.controlsSchema.default({}) }).strict();
|
|
73
|
+
/** Generated pages select a saved Binding by ID. Query text, provider and source overrides are rejected. */
|
|
74
|
+
exports.queryBindingSchema = exports.querySchema.extend({ bindingId: id }).strict();
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { AnalyticsValue, AnalyticsDiscoveryRequest, AnalyticsQueryRequest, XpertResolvedViewHostContext } from '@xpert-ai/contracts';
|
|
2
|
+
import { type RuntimeCapabilityRegistry } from '@xpert-ai/plugin-sdk';
|
|
3
|
+
import { type DashboardManifest, type DashboardChanges } from './dashboard.schema';
|
|
4
|
+
import { HtmlDashboardValidation } from './html-validation';
|
|
5
|
+
export declare const PLUGIN_NAME = "@xpert-ai/plugin-live-dashboard";
|
|
6
|
+
export declare class DashboardService {
|
|
7
|
+
private readonly runtime;
|
|
8
|
+
private readonly htmlValidation;
|
|
9
|
+
constructor(runtime: RuntimeCapabilityRegistry, htmlValidation: HtmlDashboardValidation);
|
|
10
|
+
sources(context: XpertResolvedViewHostContext): Promise<import("@xpert-ai/contracts").AnalyticsSourceSummary[]>;
|
|
11
|
+
discover(context: XpertResolvedViewHostContext, input: AnalyticsDiscoveryRequest): Promise<import("@xpert-ai/contracts").AnalyticsDiscoveryResult>;
|
|
12
|
+
preview(context: XpertResolvedViewHostContext, input: AnalyticsQueryRequest): Promise<import("@xpert-ai/contracts").AnalyticalDataset>;
|
|
13
|
+
list(context: XpertResolvedViewHostContext): Promise<{
|
|
14
|
+
title: string | null | undefined;
|
|
15
|
+
artifactId: string;
|
|
16
|
+
versionId: string | null | undefined;
|
|
17
|
+
versionNumber: number | undefined;
|
|
18
|
+
updatedAt: string | Date | undefined;
|
|
19
|
+
}[]>;
|
|
20
|
+
save(context: XpertResolvedViewHostContext, manifest: DashboardManifest, expectedVersionId: string | null, expectedRevision?: number): Promise<{
|
|
21
|
+
artifactId: string;
|
|
22
|
+
draftId: string;
|
|
23
|
+
draftRevision: number;
|
|
24
|
+
baseVersionId: string | null;
|
|
25
|
+
status: "draft";
|
|
26
|
+
title: string;
|
|
27
|
+
}>;
|
|
28
|
+
open(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number): Promise<{
|
|
29
|
+
manifest: {
|
|
30
|
+
schema: "xpert.live-dashboard/v1";
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
|
|
34
|
+
controls: {
|
|
35
|
+
type: "string" | "number" | "boolean" | "date";
|
|
36
|
+
controlId: string;
|
|
37
|
+
label: string;
|
|
38
|
+
defaultValue?: string | number | boolean | null | undefined;
|
|
39
|
+
allowedValues?: (string | number | boolean | null)[] | undefined;
|
|
40
|
+
}[];
|
|
41
|
+
charts: {
|
|
42
|
+
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
43
|
+
id: string;
|
|
44
|
+
title: string;
|
|
45
|
+
datasetId: string;
|
|
46
|
+
x: string;
|
|
47
|
+
y: string;
|
|
48
|
+
color?: string | undefined;
|
|
49
|
+
}[];
|
|
50
|
+
tables: {
|
|
51
|
+
id: string;
|
|
52
|
+
title: string;
|
|
53
|
+
datasetId: string;
|
|
54
|
+
fields: string[];
|
|
55
|
+
}[];
|
|
56
|
+
presentation?: {
|
|
57
|
+
type: "html";
|
|
58
|
+
html: string;
|
|
59
|
+
dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
|
|
60
|
+
} | undefined;
|
|
61
|
+
};
|
|
62
|
+
draftId?: string | undefined;
|
|
63
|
+
draftRevision?: number | undefined;
|
|
64
|
+
baseVersionId?: string | null | undefined;
|
|
65
|
+
isDraft?: boolean | undefined;
|
|
66
|
+
title: string | null | undefined;
|
|
67
|
+
updatedAt: string | Date | undefined;
|
|
68
|
+
artifactId: string;
|
|
69
|
+
versionId: string | null | undefined;
|
|
70
|
+
versionNumber: number | undefined;
|
|
71
|
+
}>;
|
|
72
|
+
/** Trusted View transport only. Agent read tools receive locks, never megabytes of library code. */
|
|
73
|
+
openDocument(context: XpertResolvedViewHostContext, artifactId: string, versionId?: string, draftRevision?: number): Promise<{
|
|
74
|
+
manifest: {
|
|
75
|
+
schema: "xpert.live-dashboard/v1";
|
|
76
|
+
id: string;
|
|
77
|
+
title: string;
|
|
78
|
+
bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
|
|
79
|
+
controls: {
|
|
80
|
+
type: "string" | "number" | "boolean" | "date";
|
|
81
|
+
controlId: string;
|
|
82
|
+
label: string;
|
|
83
|
+
defaultValue?: string | number | boolean | null | undefined;
|
|
84
|
+
allowedValues?: (string | number | boolean | null)[] | undefined;
|
|
85
|
+
}[];
|
|
86
|
+
charts: {
|
|
87
|
+
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
88
|
+
id: string;
|
|
89
|
+
title: string;
|
|
90
|
+
datasetId: string;
|
|
91
|
+
x: string;
|
|
92
|
+
y: string;
|
|
93
|
+
color?: string | undefined;
|
|
94
|
+
}[];
|
|
95
|
+
tables: {
|
|
96
|
+
id: string;
|
|
97
|
+
title: string;
|
|
98
|
+
datasetId: string;
|
|
99
|
+
fields: string[];
|
|
100
|
+
}[];
|
|
101
|
+
presentation?: {
|
|
102
|
+
type: "html";
|
|
103
|
+
html: string;
|
|
104
|
+
dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
|
|
105
|
+
} | undefined;
|
|
106
|
+
};
|
|
107
|
+
dependencies: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyAsset[];
|
|
108
|
+
draftId?: string | undefined;
|
|
109
|
+
draftRevision?: number | undefined;
|
|
110
|
+
baseVersionId?: string | null | undefined;
|
|
111
|
+
isDraft?: boolean | undefined;
|
|
112
|
+
title: string | null | undefined;
|
|
113
|
+
updatedAt: string | Date | undefined;
|
|
114
|
+
artifactId: string;
|
|
115
|
+
versionId: string | null | undefined;
|
|
116
|
+
versionNumber: number | undefined;
|
|
117
|
+
}>;
|
|
118
|
+
history(context: XpertResolvedViewHostContext, artifactId: string): Promise<{
|
|
119
|
+
current: boolean;
|
|
120
|
+
artifactId: string;
|
|
121
|
+
versionId: string | null | undefined;
|
|
122
|
+
versionNumber: number | undefined;
|
|
123
|
+
title: string | null | undefined;
|
|
124
|
+
updatedAt: string | Date | undefined;
|
|
125
|
+
}[]>;
|
|
126
|
+
update(context: XpertResolvedViewHostContext, artifactId: string, expectedRevision: number, changes: DashboardChanges, expectedCurrentVersionId: string | null): Promise<{
|
|
127
|
+
artifactId: string;
|
|
128
|
+
draftId: string;
|
|
129
|
+
draftRevision: number;
|
|
130
|
+
baseVersionId: string | null;
|
|
131
|
+
status: "draft";
|
|
132
|
+
title: string;
|
|
133
|
+
}>;
|
|
134
|
+
publish(context: XpertResolvedViewHostContext, artifactId: string, expectedRevision: number, expectedCurrentVersionId: string | null): Promise<{
|
|
135
|
+
artifactId: string;
|
|
136
|
+
versionId: string;
|
|
137
|
+
versionNumber: number;
|
|
138
|
+
title: string | null | undefined;
|
|
139
|
+
}>;
|
|
140
|
+
query(context: XpertResolvedViewHostContext, artifactId: string, versionId: string | undefined, controls: Record<string, AnalyticsValue>, draftRevision?: number): Promise<{
|
|
141
|
+
payload?: {
|
|
142
|
+
ok: boolean;
|
|
143
|
+
widget_type: string;
|
|
144
|
+
surface: string;
|
|
145
|
+
sources: never[];
|
|
146
|
+
manifest: {
|
|
147
|
+
version: number;
|
|
148
|
+
surface: string;
|
|
149
|
+
title: string;
|
|
150
|
+
cards: never[];
|
|
151
|
+
filters: never[];
|
|
152
|
+
sources: never[];
|
|
153
|
+
charts: {
|
|
154
|
+
id: string;
|
|
155
|
+
title: string;
|
|
156
|
+
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
157
|
+
dataset: string;
|
|
158
|
+
encodings: {
|
|
159
|
+
color?: {
|
|
160
|
+
field: string;
|
|
161
|
+
} | undefined;
|
|
162
|
+
x: {
|
|
163
|
+
field: string;
|
|
164
|
+
};
|
|
165
|
+
y: {
|
|
166
|
+
field: string;
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
}[];
|
|
170
|
+
tables: {
|
|
171
|
+
id: string;
|
|
172
|
+
title: string;
|
|
173
|
+
dataset: string;
|
|
174
|
+
columns: {
|
|
175
|
+
field: string;
|
|
176
|
+
label: string;
|
|
177
|
+
}[];
|
|
178
|
+
}[];
|
|
179
|
+
blocks: ({
|
|
180
|
+
id: string;
|
|
181
|
+
type: string;
|
|
182
|
+
chartId: string;
|
|
183
|
+
} | {
|
|
184
|
+
id: string;
|
|
185
|
+
type: string;
|
|
186
|
+
tableId: string;
|
|
187
|
+
})[];
|
|
188
|
+
};
|
|
189
|
+
snapshot: {
|
|
190
|
+
version: number;
|
|
191
|
+
generatedAt: string;
|
|
192
|
+
status: string;
|
|
193
|
+
datasets: any;
|
|
194
|
+
};
|
|
195
|
+
} | undefined;
|
|
196
|
+
datasets: import("@xpert-ai/contracts").AnalyticalDataset[];
|
|
197
|
+
manifest: {
|
|
198
|
+
schema: "xpert.live-dashboard/v1";
|
|
199
|
+
id: string;
|
|
200
|
+
title: string;
|
|
201
|
+
bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
|
|
202
|
+
controls: {
|
|
203
|
+
type: "string" | "number" | "boolean" | "date";
|
|
204
|
+
controlId: string;
|
|
205
|
+
label: string;
|
|
206
|
+
defaultValue?: string | number | boolean | null | undefined;
|
|
207
|
+
allowedValues?: (string | number | boolean | null)[] | undefined;
|
|
208
|
+
}[];
|
|
209
|
+
charts: {
|
|
210
|
+
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
211
|
+
id: string;
|
|
212
|
+
title: string;
|
|
213
|
+
datasetId: string;
|
|
214
|
+
x: string;
|
|
215
|
+
y: string;
|
|
216
|
+
color?: string | undefined;
|
|
217
|
+
}[];
|
|
218
|
+
tables: {
|
|
219
|
+
id: string;
|
|
220
|
+
title: string;
|
|
221
|
+
datasetId: string;
|
|
222
|
+
fields: string[];
|
|
223
|
+
}[];
|
|
224
|
+
presentation?: {
|
|
225
|
+
type: "html";
|
|
226
|
+
html: string;
|
|
227
|
+
dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
|
|
228
|
+
} | undefined;
|
|
229
|
+
};
|
|
230
|
+
draftId?: string | undefined;
|
|
231
|
+
draftRevision?: number | undefined;
|
|
232
|
+
baseVersionId?: string | null | undefined;
|
|
233
|
+
isDraft?: boolean | undefined;
|
|
234
|
+
title: string | null | undefined;
|
|
235
|
+
updatedAt: string | Date | undefined;
|
|
236
|
+
artifactId: string;
|
|
237
|
+
versionId: string | null | undefined;
|
|
238
|
+
versionNumber: number | undefined;
|
|
239
|
+
}>;
|
|
240
|
+
queryBinding(context: XpertResolvedViewHostContext, artifactId: string, versionId: string | undefined, bindingId: string, input: Record<string, AnalyticsValue>, draftRevision?: number): Promise<import("@xpert-ai/contracts").AnalyticalDataset>;
|
|
241
|
+
private execute;
|
|
242
|
+
private probeControls;
|
|
243
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DashboardService = exports.PLUGIN_NAME = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const node_crypto_1 = require("node:crypto");
|
|
18
|
+
const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
|
|
19
|
+
const dashboard_schema_1 = require("./dashboard.schema");
|
|
20
|
+
const renderer_1 = require("./renderer");
|
|
21
|
+
const dashboard_queries_1 = require("./dashboard-queries");
|
|
22
|
+
const html_validation_1 = require("./html-validation");
|
|
23
|
+
const document_libraries_1 = require("./document-libraries");
|
|
24
|
+
const document_package_1 = require("./document-package");
|
|
25
|
+
exports.PLUGIN_NAME = '@xpert-ai/plugin-live-dashboard';
|
|
26
|
+
const RESOURCE_TYPE = 'live-dashboard';
|
|
27
|
+
const MIME_TYPE = 'application/json';
|
|
28
|
+
let DashboardService = class DashboardService {
|
|
29
|
+
constructor(runtime, htmlValidation) {
|
|
30
|
+
this.runtime = runtime;
|
|
31
|
+
this.htmlValidation = htmlValidation;
|
|
32
|
+
}
|
|
33
|
+
sources(context) { return this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).list(actor(context)); }
|
|
34
|
+
discover(context, input) { return this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).discover(actor(context), input); }
|
|
35
|
+
async preview(context, input) {
|
|
36
|
+
const dataset = await this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).query(actor(context), input);
|
|
37
|
+
await this.probeControls(context, input.binding, input.controls ?? {});
|
|
38
|
+
return dataset;
|
|
39
|
+
}
|
|
40
|
+
async list(context) {
|
|
41
|
+
actor(context);
|
|
42
|
+
const result = await this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability).listArtifacts({ pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE, resourceIdPrefix: hostPrefix(context), status: 'active', pageSize: 100, includeCurrentVersion: true });
|
|
43
|
+
const items = result.items.filter((item) => item.resourceId.startsWith(hostPrefix(context)) && item.userId === context.userId);
|
|
44
|
+
const drafts = new Map((await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).list(items.map(item => item.id))).map(draft => [draft.artifactId, draft]));
|
|
45
|
+
return items.map(item => ({ ...summary(item), ...(drafts.has(item.id) ? { title: drafts.get(item.id).title ?? item.title } : {}) }));
|
|
46
|
+
}
|
|
47
|
+
async save(context, manifest, expectedVersionId, expectedRevision = 0) {
|
|
48
|
+
const scopedActor = actor(context);
|
|
49
|
+
const parsed = dashboard_schema_1.dashboardSchema.parse(manifest);
|
|
50
|
+
const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
|
|
51
|
+
const source = { pluginName: exports.PLUGIN_NAME, resourceType: RESOURCE_TYPE, resourceId: `${hostPrefix(context)}${parsed.id}` };
|
|
52
|
+
const existing = await artifacts.findArtifactBySource(source);
|
|
53
|
+
if (existing && existing.userId !== scopedActor.userId)
|
|
54
|
+
throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
|
|
55
|
+
// A failed first upload can leave an Artifact with no saved content; revision 0 initializes it.
|
|
56
|
+
const previous = existing && (existing.currentVersionId || expectedRevision > 0) ? await this.openDocument(context, existing.id) : null;
|
|
57
|
+
const dependencies = await (0, document_libraries_1.resolveLibraryAssets)(parsed.presentation?.dependencies ?? [], previous?.dependencies);
|
|
58
|
+
const buffer = (0, document_package_1.encodeDashboardPackage)(parsed, dependencies);
|
|
59
|
+
if (buffer.byteLength > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES)
|
|
60
|
+
throw new common_1.BadRequestException('DASHBOARD_MANIFEST_TOO_LARGE');
|
|
61
|
+
const digest = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
|
|
62
|
+
if ((existing?.currentVersionId ?? null) !== expectedVersionId)
|
|
63
|
+
throw new common_1.ConflictException('DASHBOARD_VERSION_CONFLICT');
|
|
64
|
+
// Recheck current data access even for an idempotent retry; the platform enforces the final CAS under a row lock.
|
|
65
|
+
const preview = await this.execute(context, parsed, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])));
|
|
66
|
+
if (!parsed.presentation)
|
|
67
|
+
(0, renderer_1.toDataAnalyticsPayload)(parsed, preview);
|
|
68
|
+
for (const binding of parsed.bindings)
|
|
69
|
+
await this.probeControls(context, binding, Object.fromEntries(parsed.controls.filter((control) => control.defaultValue !== undefined).map((control) => [control.controlId, control.defaultValue ?? null])));
|
|
70
|
+
const htmlValidation = await this.htmlValidation.validate(context, parsed, preview, dependencies);
|
|
71
|
+
const artifact = existing ?? await artifacts.createArtifact({ source, kind: 'file', title: parsed.title,
|
|
72
|
+
scope: { ...scopedActor, ...(context.hostType === 'project' ? { projectId: context.hostId } : { xpertId: context.hostId }) } });
|
|
73
|
+
const files = this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability);
|
|
74
|
+
const scope = { tenantId: scopedActor.tenantId, userId: scopedActor.userId, catalog: 'users' };
|
|
75
|
+
const file = await files.uploadBuffer({ ...scope, buffer, originalName: 'dashboard.json', fileName: `${digest}.json`, folder: `live-dashboards/${parsed.id}`, mimeType: MIME_TYPE });
|
|
76
|
+
const result = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).save({ artifactId: artifact.id, expectedRevision,
|
|
77
|
+
expectedCurrentVersionId: expectedVersionId, mimeType: MIME_TYPE, title: parsed.title, fileName: 'dashboard.json', sha256: digest,
|
|
78
|
+
...(htmlValidation ? { metadata: { htmlValidation } } : {}),
|
|
79
|
+
workspaceFileRef: { source: 'platform.workspace.files', ...scope, filePath: file.filePath, workspacePath: file.workspacePath } });
|
|
80
|
+
return { artifactId: artifact.id, draftId: result.id, draftRevision: result.revision, baseVersionId: result.baseVersionId, status: 'draft', title: parsed.title };
|
|
81
|
+
}
|
|
82
|
+
async open(context, artifactId, versionId, draftRevision) {
|
|
83
|
+
const { dependencies, ...saved } = await this.openDocument(context, artifactId, versionId, draftRevision);
|
|
84
|
+
return saved;
|
|
85
|
+
}
|
|
86
|
+
/** Trusted View transport only. Agent read tools receive locks, never megabytes of library code. */
|
|
87
|
+
async openDocument(context, artifactId, versionId, draftRevision) {
|
|
88
|
+
actor(context);
|
|
89
|
+
const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
|
|
90
|
+
const artifact = await artifacts.getArtifact(artifactId);
|
|
91
|
+
assertOwned(context, artifact);
|
|
92
|
+
const draft = !versionId || draftRevision !== undefined ? await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).get(artifactId) : null;
|
|
93
|
+
if (draftRevision !== undefined && draft?.revision !== draftRevision)
|
|
94
|
+
throw new common_1.ConflictException('DASHBOARD_DRAFT_CONFLICT');
|
|
95
|
+
const version = draft ?? (versionId ? (await artifacts.listArtifactVersions({ artifactId, status: 'active' })).find((item) => item.id === versionId) : artifact.currentVersion);
|
|
96
|
+
if (!version?.workspaceFileRef || version.mimeType !== MIME_TYPE)
|
|
97
|
+
throw new common_1.BadRequestException('DASHBOARD_VERSION_UNAVAILABLE');
|
|
98
|
+
const file = await this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability).readBuffer(version.workspaceFileRef);
|
|
99
|
+
if (file.buffer.byteLength > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES)
|
|
100
|
+
throw new common_1.BadRequestException('DASHBOARD_MANIFEST_TOO_LARGE');
|
|
101
|
+
if (!version.sha256 || (0, node_crypto_1.createHash)('sha256').update(file.buffer).digest('hex') !== version.sha256)
|
|
102
|
+
throw new common_1.BadRequestException('DASHBOARD_VERSION_CONTENT_CHANGED');
|
|
103
|
+
const { manifest, dependencies } = (0, document_package_1.decodeDashboardPackage)(file.buffer);
|
|
104
|
+
if (artifact.resourceId !== `${hostPrefix(context)}${manifest.id}`)
|
|
105
|
+
throw new common_1.BadRequestException('DASHBOARD_IDENTITY_MISMATCH');
|
|
106
|
+
return { ...summary(artifact, 'versionNumber' in version ? version : undefined),
|
|
107
|
+
...(draft ? { draftId: draft.id, draftRevision: draft.revision, baseVersionId: draft.baseVersionId,
|
|
108
|
+
isDraft: draft.revision !== draft.publishedRevision, title: draft.title, updatedAt: draft.updatedAt } : {}), manifest, dependencies };
|
|
109
|
+
}
|
|
110
|
+
async history(context, artifactId) {
|
|
111
|
+
actor(context);
|
|
112
|
+
const artifacts = this.runtime.require(plugin_sdk_1.ArtifactsRuntimeCapability);
|
|
113
|
+
const artifact = await artifacts.getArtifact(artifactId);
|
|
114
|
+
assertOwned(context, artifact);
|
|
115
|
+
const versions = await artifacts.listArtifactVersions({ artifactId, status: 'active' });
|
|
116
|
+
return versions.map(version => ({ ...summary(artifact, version), current: version.id === artifact.currentVersionId }))
|
|
117
|
+
.sort((a, b) => (b.versionNumber ?? 0) - (a.versionNumber ?? 0));
|
|
118
|
+
}
|
|
119
|
+
async update(context, artifactId, expectedRevision, changes, expectedCurrentVersionId) {
|
|
120
|
+
const saved = await this.open(context, artifactId);
|
|
121
|
+
if ((saved.draftRevision ?? 0) !== expectedRevision || (saved.baseVersionId ?? saved.versionId ?? null) !== expectedCurrentVersionId)
|
|
122
|
+
throw new common_1.ConflictException('DASHBOARD_DRAFT_CONFLICT');
|
|
123
|
+
return this.save(context, dashboard_schema_1.dashboardSchema.parse({ ...saved.manifest, ...dashboard_schema_1.dashboardChangesSchema.parse(changes) }), saved.baseVersionId ?? saved.versionId ?? null, expectedRevision);
|
|
124
|
+
}
|
|
125
|
+
async publish(context, artifactId, expectedRevision, expectedCurrentVersionId) {
|
|
126
|
+
const saved = await this.open(context, artifactId, undefined, expectedRevision);
|
|
127
|
+
// Revalidate current source access and bindings before publishing the exact reviewed bytes.
|
|
128
|
+
await this.execute(context, saved.manifest, {});
|
|
129
|
+
const version = await this.runtime.require(plugin_sdk_1.ArtifactDraftsRuntimeCapability).publish({ artifactId, expectedRevision, expectedCurrentVersionId });
|
|
130
|
+
return { artifactId, versionId: version.id, versionNumber: version.versionNumber, title: version.title };
|
|
131
|
+
}
|
|
132
|
+
async query(context, artifactId, versionId, controls, draftRevision) {
|
|
133
|
+
const saved = await this.open(context, artifactId, versionId, draftRevision);
|
|
134
|
+
const datasets = await this.execute(context, saved.manifest, controls);
|
|
135
|
+
return { ...saved, datasets, ...(saved.manifest.presentation ? {} : { payload: (0, renderer_1.toDataAnalyticsPayload)(saved.manifest, datasets) }) };
|
|
136
|
+
}
|
|
137
|
+
async queryBinding(context, artifactId, versionId, bindingId, input, draftRevision) {
|
|
138
|
+
const saved = await this.open(context, artifactId, versionId, draftRevision);
|
|
139
|
+
return (0, dashboard_queries_1.queryDashboardBinding)(this.runtime, actor(context), saved.manifest, bindingId, input);
|
|
140
|
+
}
|
|
141
|
+
execute(context, manifest, input) {
|
|
142
|
+
return (0, dashboard_queries_1.queryDashboard)(this.runtime, actor(context), manifest, input);
|
|
143
|
+
}
|
|
144
|
+
async probeControls(context, binding, input) {
|
|
145
|
+
const controls = Object.fromEntries(binding.parameters.map((parameter) => [parameter.controlId,
|
|
146
|
+
Object.hasOwn(input, parameter.controlId) ? input[parameter.controlId] : parameter.defaultValue ?? null]));
|
|
147
|
+
// A null default can bypass a native predicate entirely. Verify a real selection too,
|
|
148
|
+
// using declared values only; never invent a business filter for open-ended controls.
|
|
149
|
+
for (const parameter of binding.parameters) {
|
|
150
|
+
const current = controls[parameter.controlId];
|
|
151
|
+
const alternative = parameter.allowedValues?.find((value) => value !== null && value !== current);
|
|
152
|
+
const samples = [...(alternative !== undefined ? [alternative] : []),
|
|
153
|
+
...(current !== null && parameter.allowedValues?.includes(null) ? [null] : [])];
|
|
154
|
+
for (const value of samples) {
|
|
155
|
+
try {
|
|
156
|
+
await this.runtime.require(plugin_sdk_1.AnalyticsRuntimeCapability).query(actor(context), { binding, controls: { ...controls, [parameter.controlId]: value } });
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
throw new common_1.BadRequestException(`DASHBOARD_FILTER_PREVIEW_FAILED: ${parameter.controlId}; a declared non-default filter failed. Inspect the native parameter type and operator before retrying.`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
exports.DashboardService = DashboardService;
|
|
166
|
+
exports.DashboardService = DashboardService = __decorate([
|
|
167
|
+
(0, common_1.Injectable)(),
|
|
168
|
+
__param(0, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
|
|
169
|
+
__metadata("design:paramtypes", [Object, html_validation_1.HtmlDashboardValidation])
|
|
170
|
+
], DashboardService);
|
|
171
|
+
function actor(context) {
|
|
172
|
+
if (!context.tenantId || !context.organizationId || !context.userId || !['agent', 'project'].includes(context.hostType))
|
|
173
|
+
throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
|
|
174
|
+
return { tenantId: context.tenantId, organizationId: context.organizationId, userId: context.userId };
|
|
175
|
+
}
|
|
176
|
+
function hostPrefix(context) { return `${context.hostType}/${context.hostId}/`; }
|
|
177
|
+
function assertOwned(context, artifact) {
|
|
178
|
+
if (artifact.pluginName !== exports.PLUGIN_NAME || artifact.resourceType !== RESOURCE_TYPE || !artifact.resourceId.startsWith(hostPrefix(context)) ||
|
|
179
|
+
artifact.tenantId !== context.tenantId || artifact.organizationId !== context.organizationId || artifact.userId !== context.userId)
|
|
180
|
+
throw new common_1.ForbiddenException('DASHBOARD_ACCESS_DENIED');
|
|
181
|
+
}
|
|
182
|
+
function summary(artifact, version = artifact.currentVersion) {
|
|
183
|
+
return { artifactId: artifact.id, versionId: version?.id ?? artifact.currentVersionId, versionNumber: version?.versionNumber,
|
|
184
|
+
title: version?.title ?? artifact.title, updatedAt: artifact.updatedAt };
|
|
185
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from 'zod/v3';
|
|
2
|
+
import { type ArtifactDocumentDependencyAsset, type ArtifactDocumentDependencyLock } from '@xpert-ai/plugin-sdk/artifact-document';
|
|
3
|
+
/** The plugin owns its catalog. The platform owns verification and isolated execution. */
|
|
4
|
+
export declare const libraryRequestsSchema: z.ZodEffects<z.ZodArray<z.ZodEnum<["echarts", "three"]>, "many">, ("echarts" | "three")[], ("echarts" | "three")[]>;
|
|
5
|
+
export type LibraryRequests = z.output<typeof libraryRequestsSchema>;
|
|
6
|
+
export declare function bundledLibraries(): Promise<ArtifactDocumentDependencyAsset[]>;
|
|
7
|
+
export declare function libraryLocks(ids: LibraryRequests): Promise<ArtifactDocumentDependencyLock[]>;
|
|
8
|
+
export declare function resolveLibraryAssets(locks: ArtifactDocumentDependencyLock[], previous?: ArtifactDocumentDependencyAsset[], loadCatalog?: typeof bundledLibraries): Promise<ArtifactDocumentDependencyAsset[]>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.libraryRequestsSchema = void 0;
|
|
4
|
+
exports.bundledLibraries = bundledLibraries;
|
|
5
|
+
exports.libraryLocks = libraryLocks;
|
|
6
|
+
exports.resolveLibraryAssets = resolveLibraryAssets;
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const v3_1 = require("zod/v3");
|
|
10
|
+
const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
|
|
11
|
+
/** The plugin owns its catalog. The platform owns verification and isolated execution. */
|
|
12
|
+
exports.libraryRequestsSchema = v3_1.z.array(v3_1.z.enum(['echarts', 'three'])).max(2)
|
|
13
|
+
.refine(ids => new Set(ids).size === ids.length, 'Duplicate library');
|
|
14
|
+
async function bundledLibraries() {
|
|
15
|
+
return (0, artifact_document_1.verifyArtifactDocumentDependencies)(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(__dirname, '../libraries/catalog.json'), 'utf8')));
|
|
16
|
+
}
|
|
17
|
+
async function libraryLocks(ids) {
|
|
18
|
+
const catalog = await bundledLibraries();
|
|
19
|
+
return exports.libraryRequestsSchema.parse(ids).map(id => {
|
|
20
|
+
const asset = catalog.find(item => item.id === id);
|
|
21
|
+
if (!asset)
|
|
22
|
+
throw new Error('DASHBOARD_LIBRARY_UNAVAILABLE');
|
|
23
|
+
const { source, ...lock } = asset;
|
|
24
|
+
return lock;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async function resolveLibraryAssets(locks, previous = [], loadCatalog = bundledLibraries) {
|
|
28
|
+
const requested = artifact_document_1.artifactDocumentDependencyLocks.parse(locks);
|
|
29
|
+
if (!requested.length)
|
|
30
|
+
return [];
|
|
31
|
+
const hasPrevious = (lock) => previous.some(item => item.id === lock.id && item.version === lock.version && item.sha256 === lock.sha256 && item.global === lock.global && item.format === lock.format);
|
|
32
|
+
const catalog = requested.every(hasPrevious) ? [] : await loadCatalog();
|
|
33
|
+
const assets = requested.map(lock => {
|
|
34
|
+
// Authorized prior bytes take precedence, including after a plugin/library upgrade.
|
|
35
|
+
const asset = [...previous, ...catalog].find(item => item.id === lock.id && item.version === lock.version && item.sha256 === lock.sha256 && item.global === lock.global && item.format === lock.format);
|
|
36
|
+
if (!asset)
|
|
37
|
+
throw new Error(`DASHBOARD_LIBRARY_LOCK_UNAVAILABLE: ${lock.id}@${lock.version}`);
|
|
38
|
+
return asset;
|
|
39
|
+
});
|
|
40
|
+
return (0, artifact_document_1.verifyArtifactDocumentDependencies)(assets, requested);
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ArtifactDocumentDependencyAsset } from '@xpert-ai/plugin-sdk/artifact-document';
|
|
2
|
+
import { type DashboardManifest } from './dashboard.schema';
|
|
3
|
+
export declare const MAX_DASHBOARD_PACKAGE_BYTES = 9000000;
|
|
4
|
+
/** Definition and exact library bytes share one immutable Artifact Version and content checksum. */
|
|
5
|
+
export declare function encodeDashboardPackage(manifest: DashboardManifest, dependencies: ArtifactDocumentDependencyAsset[]): Buffer<ArrayBuffer>;
|
|
6
|
+
export declare function decodeDashboardPackage(bytes: Buffer): {
|
|
7
|
+
manifest: {
|
|
8
|
+
schema: "xpert.live-dashboard/v1";
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
|
|
12
|
+
controls: {
|
|
13
|
+
type: "string" | "number" | "boolean" | "date";
|
|
14
|
+
controlId: string;
|
|
15
|
+
label: string;
|
|
16
|
+
defaultValue?: string | number | boolean | null | undefined;
|
|
17
|
+
allowedValues?: (string | number | boolean | null)[] | undefined;
|
|
18
|
+
}[];
|
|
19
|
+
charts: {
|
|
20
|
+
type: "bar" | "line" | "area" | "pie" | "scatter";
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
datasetId: string;
|
|
24
|
+
x: string;
|
|
25
|
+
y: string;
|
|
26
|
+
color?: string | undefined;
|
|
27
|
+
}[];
|
|
28
|
+
tables: {
|
|
29
|
+
id: string;
|
|
30
|
+
title: string;
|
|
31
|
+
datasetId: string;
|
|
32
|
+
fields: string[];
|
|
33
|
+
}[];
|
|
34
|
+
presentation?: {
|
|
35
|
+
type: "html";
|
|
36
|
+
html: string;
|
|
37
|
+
dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
|
|
38
|
+
} | undefined;
|
|
39
|
+
};
|
|
40
|
+
dependencies: ArtifactDocumentDependencyAsset[];
|
|
41
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_DASHBOARD_PACKAGE_BYTES = void 0;
|
|
4
|
+
exports.encodeDashboardPackage = encodeDashboardPackage;
|
|
5
|
+
exports.decodeDashboardPackage = decodeDashboardPackage;
|
|
6
|
+
const v3_1 = require("zod/v3");
|
|
7
|
+
const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
|
|
8
|
+
const dashboard_schema_1 = require("./dashboard.schema");
|
|
9
|
+
exports.MAX_DASHBOARD_PACKAGE_BYTES = 9_000_000;
|
|
10
|
+
const storedSchema = v3_1.z.union([
|
|
11
|
+
v3_1.z.object({ schema: v3_1.z.literal('xpert.live-dashboard.package/v1'), manifest: dashboard_schema_1.dashboardSchema, dependencies: artifact_document_1.artifactDocumentDependencyAssets }).strict(),
|
|
12
|
+
dashboard_schema_1.dashboardSchema.transform(manifest => ({ manifest, dependencies: [] }))
|
|
13
|
+
]);
|
|
14
|
+
/** Definition and exact library bytes share one immutable Artifact Version and content checksum. */
|
|
15
|
+
function encodeDashboardPackage(manifest, dependencies) {
|
|
16
|
+
(0, artifact_document_1.verifyArtifactDocumentDependencies)(dependencies, manifest.presentation?.dependencies ?? []);
|
|
17
|
+
return Buffer.from(JSON.stringify(dependencies.length ? { schema: 'xpert.live-dashboard.package/v1', manifest, dependencies } : manifest));
|
|
18
|
+
}
|
|
19
|
+
function decodeDashboardPackage(bytes) {
|
|
20
|
+
if (bytes.byteLength > exports.MAX_DASHBOARD_PACKAGE_BYTES)
|
|
21
|
+
throw new Error('DASHBOARD_PACKAGE_TOO_LARGE');
|
|
22
|
+
const stored = storedSchema.parse(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)));
|
|
23
|
+
const dependencies = (0, artifact_document_1.verifyArtifactDocumentDependencies)(stored.dependencies, stored.manifest.presentation?.dependencies ?? []);
|
|
24
|
+
return { manifest: stored.manifest, dependencies };
|
|
25
|
+
}
|