@xpert-ai/plugin-live-dashboard 0.4.0 → 0.5.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.
Files changed (55) hide show
  1. package/.xpertai-plugin/plugin.json +1 -1
  2. package/README.md +4 -0
  3. package/dist/lib/agent.schema.d.ts +179 -25
  4. package/dist/lib/agent.schema.js +10 -9
  5. package/dist/lib/archive.service.d.ts +145 -0
  6. package/dist/lib/archive.service.js +133 -0
  7. package/dist/lib/content.provider.d.ts +1 -1
  8. package/dist/lib/dashboard-archive.d.ts +70 -0
  9. package/dist/lib/dashboard-archive.js +118 -0
  10. package/dist/lib/dashboard-sources.d.ts +69 -0
  11. package/dist/lib/dashboard-sources.js +37 -0
  12. package/dist/lib/dashboard-view.schema.d.ts +503 -88
  13. package/dist/lib/dashboard-view.schema.js +2 -1
  14. package/dist/lib/dashboard.schema.d.ts +379 -58
  15. package/dist/lib/dashboard.schema.js +18 -1
  16. package/dist/lib/dashboard.service.d.ts +72 -17
  17. package/dist/lib/dashboard.service.js +21 -3
  18. package/dist/lib/document-package.d.ts +19 -4
  19. package/dist/lib/editing-patch.d.ts +3 -0
  20. package/dist/lib/editing-patch.js +2 -1
  21. package/dist/lib/editing-session.d.ts +4 -4
  22. package/dist/lib/editing.d.ts +202 -19
  23. package/dist/lib/editing.js +5 -2
  24. package/dist/lib/html-validation-feedback.d.ts +7 -0
  25. package/dist/lib/html-validation-feedback.js +19 -0
  26. package/dist/lib/html-validation.js +2 -1
  27. package/dist/lib/inspection-host.d.ts +7 -0
  28. package/dist/lib/inspection-host.js +48 -0
  29. package/dist/lib/inspection-runtime.d.ts +2 -0
  30. package/dist/lib/inspection-runtime.js +77 -0
  31. package/dist/lib/json-input.d.ts +4 -0
  32. package/dist/lib/json-input.js +21 -0
  33. package/dist/lib/middleware.js +3 -2
  34. package/dist/lib/plugin.js +2 -1
  35. package/dist/lib/presentation.provider.d.ts +24 -9
  36. package/dist/lib/presentation.provider.js +2 -1
  37. package/dist/lib/projects.schema.d.ts +14 -0
  38. package/dist/lib/projects.schema.js +2 -1
  39. package/dist/lib/projects.service.d.ts +2 -0
  40. package/dist/lib/projects.service.js +10 -0
  41. package/dist/lib/source-executions.d.ts +196 -0
  42. package/dist/lib/source-executions.js +25 -0
  43. package/dist/lib/source-validation.d.ts +2 -0
  44. package/dist/lib/source-validation.js +21 -0
  45. package/dist/lib/view.provider.d.ts +211 -12
  46. package/dist/lib/view.provider.js +43 -9
  47. package/dist/remote/app.css +1 -1
  48. package/dist/remote/app.js +99 -26
  49. package/dist/remote/build-manifest.json +42 -27
  50. package/docs/docs.json +3 -1
  51. package/docs/sources-inspect-portability-acceptance.mdx +67 -0
  52. package/docs/sources-inspect-portability.mdx +115 -0
  53. package/package.json +6 -5
  54. package/skills/live-dashboard/SKILL.md +5 -4
  55. package/skills/live-dashboard/references/source-inspection.md +17 -0
@@ -0,0 +1,133 @@
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.importBrowseSchema = exports.DashboardArchiveService = exports.importInputSchema = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const node_crypto_1 = require("node:crypto");
18
+ const v3_1 = require("zod/v3");
19
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
20
+ const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
21
+ const dashboard_service_1 = require("./dashboard.service");
22
+ const dashboard_schema_1 = require("./dashboard.schema");
23
+ const dashboard_archive_1 = require("./dashboard-archive");
24
+ const document_package_1 = require("./document-package");
25
+ exports.importInputSchema = v3_1.z.object({ importId: v3_1.z.string().uuid(),
26
+ bindings: v3_1.z.array(v3_1.z.object({ bindingId: v3_1.z.string(), source: analytics_1.analyticsSourceRefSchema, payload: analytics_1.analyticsBindingObjectSchema.shape.payload }).strict()).max(8)
27
+ }).strict();
28
+ const importBrowseSchema = v3_1.z.object({ importId: v3_1.z.string().uuid(), bindingId: v3_1.z.string(), source: analytics_1.analyticsSourceRefSchema, request: analytics_1.analyticsOptionsSchema.default({}) }).strict();
29
+ exports.importBrowseSchema = importBrowseSchema;
30
+ let DashboardArchiveService = class DashboardArchiveService {
31
+ constructor(dashboards, runtime) {
32
+ this.dashboards = dashboards;
33
+ this.runtime = runtime;
34
+ this.imports = new Map();
35
+ }
36
+ async authorize(context) {
37
+ if (!(await this.dashboards.access(context)).canEdit)
38
+ throw new common_1.ForbiddenException('DASHBOARD_DEFINITION_ACCESS_DENIED');
39
+ }
40
+ session(context, id) {
41
+ const session = this.imports.get(id);
42
+ if (!session || session.scope !== importScope(context) || Date.now() - session.created > 30 * 60_000)
43
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_EXPIRED');
44
+ return session;
45
+ }
46
+ async prepare(context, bytes) {
47
+ await this.authorize(context);
48
+ const document = (0, dashboard_archive_1.decodeDashboardArchive)(bytes);
49
+ for (const [key, session] of this.imports)
50
+ if (Date.now() - session.created > 30 * 60_000)
51
+ this.imports.delete(key);
52
+ if (this.imports.size >= 16)
53
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_LIMIT');
54
+ const importId = (0, node_crypto_1.randomUUID)();
55
+ this.imports.set(importId, { ...document, scope: importScope(context), created: Date.now(), newId: (0, node_crypto_1.randomUUID)(), busy: false });
56
+ return { importId, manifest: document.manifest, requirements: (0, dashboard_archive_1.dashboardRequirements)(document.manifest), sources: await this.dashboards.sources(context) };
57
+ }
58
+ async browse(context, input) {
59
+ await this.authorize(context);
60
+ const session = this.session(context, input.importId);
61
+ const binding = session.manifest.bindings.find(item => item.id === input.bindingId);
62
+ if (!binding)
63
+ throw new common_1.BadRequestException('DASHBOARD_BINDING_DENIED');
64
+ return this.dashboards.discover(context, { source: input.source, providerId: binding.providerId, request: input.request });
65
+ }
66
+ async commit(context, input) {
67
+ await this.authorize(context);
68
+ const session = this.session(context, input.importId);
69
+ if (session.busy)
70
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_RUNNING');
71
+ if (new Set(input.bindings.map(item => item.bindingId)).size !== input.bindings.length || input.bindings.some(item => !session.manifest.bindings.some(binding => binding.id === item.bindingId)))
72
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_BINDINGS_INVALID');
73
+ const manifest = dashboard_schema_1.dashboardSchema.parse({ ...session.manifest, id: session.newId,
74
+ bindings: session.manifest.bindings.map(binding => {
75
+ const replacement = input.bindings.find(item => item.bindingId === binding.id);
76
+ return replacement ? { ...binding, source: replacement.source, payload: replacement.payload } : binding;
77
+ }) });
78
+ const hash = (0, node_crypto_1.createHash)('sha256').update((0, document_package_1.encodeDashboardPackage)(manifest, session.dependencies)).digest('hex');
79
+ if (session.committed) {
80
+ if (session.committed.hash !== hash)
81
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_ALREADY_COMPLETED');
82
+ // Revalidate ownership and current access before returning an idempotent receipt.
83
+ await this.dashboards.open(context, session.committed.receipt.artifactId);
84
+ return session.committed.receipt;
85
+ }
86
+ session.busy = true;
87
+ try {
88
+ const receipt = await this.dashboards.save(context, manifest, null, 0, session.dependencies);
89
+ session.committed = { hash, receipt };
90
+ return receipt;
91
+ }
92
+ finally {
93
+ session.busy = false;
94
+ }
95
+ }
96
+ async discard(context, id) {
97
+ await this.authorize(context);
98
+ const session = this.session(context, id);
99
+ if (session.busy)
100
+ throw new common_1.BadRequestException('DASHBOARD_IMPORT_RUNNING');
101
+ this.imports.delete(id);
102
+ return { discarded: true };
103
+ }
104
+ async exportFile(context, fileKey) {
105
+ await this.authorize(context);
106
+ const match = /^archive\/([a-f0-9-]{36})\/(draft|version)\/([a-f0-9-]+)$/.exec(fileKey);
107
+ if (!match)
108
+ throw new common_1.BadRequestException('DASHBOARD_EXPORT_TARGET_INVALID');
109
+ const artifactId = v3_1.z.string().uuid().parse(match[1]);
110
+ const versionId = match[2] === 'version' ? v3_1.z.string().uuid().parse(match[3]) : undefined;
111
+ const revision = match[2] === 'draft' ? v3_1.z.coerce.number().int().positive().parse(match[3]) : undefined;
112
+ const saved = await this.dashboards.openDocument(context, artifactId, versionId, revision, versionId ? 'published' : 'draft');
113
+ const buffer = (0, dashboard_archive_1.encodeDashboardArchive)(saved.manifest, saved.dependencies);
114
+ const fileName = `${saved.manifest.title.replace(/[\\/\u0000-\u001f]/g, '_').slice(0, 100)}.xpert-dashboard.zip`;
115
+ const project = (0, dashboard_service_1.projectId)(context), hash = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
116
+ const scope = { tenantId: context.tenantId, organizationId: context.organizationId, userId: context.userId,
117
+ ...(project ? { catalog: 'projects', scopeId: project, projectId: project } : { catalog: 'xperts', scopeId: context.hostId, xpertId: context.hostId }) };
118
+ const uploaded = await this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability).uploadBuffer({ ...scope, buffer, originalName: fileName,
119
+ fileName: `${hash}.zip`, folder: `dashboards/exports/${artifactId}`, mimeType: 'application/zip' });
120
+ const reference = { ...scope, source: 'platform.workspace.files', filePath: uploaded.filePath,
121
+ workspacePath: uploaded.filePath, originalName: fileName, mimeType: 'application/zip', size: buffer.length };
122
+ return { reference, fileName, mimeType: 'application/zip', size: buffer.length };
123
+ }
124
+ };
125
+ exports.DashboardArchiveService = DashboardArchiveService;
126
+ exports.DashboardArchiveService = DashboardArchiveService = __decorate([
127
+ (0, common_1.Injectable)(),
128
+ __param(1, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
129
+ __metadata("design:paramtypes", [dashboard_service_1.DashboardService, Object])
130
+ ], DashboardArchiveService);
131
+ function importScope(context) {
132
+ return JSON.stringify([context.tenantId, context.organizationId, context.userId, context.hostType, context.hostId, (0, dashboard_service_1.projectId)(context)]);
133
+ }
@@ -15,7 +15,7 @@ export declare class DashboardContentProvider implements IContentResourceProvide
15
15
  providerKey: string;
16
16
  resourceId: string;
17
17
  title: string;
18
- role: "owner" | "manager" | "editor" | "member";
18
+ role: "owner" | "manager" | "editor" | "member" | "assistant";
19
19
  canWrite: boolean;
20
20
  canManage: boolean;
21
21
  }>;
@@ -0,0 +1,70 @@
1
+ import type { DashboardManifest } from './dashboard.schema';
2
+ import type { ArtifactDocumentDependencyAsset } from '@xpert-ai/plugin-sdk/artifact-document';
3
+ export declare const MAX_DASHBOARD_ARCHIVE_BYTES = 12000000;
4
+ export declare function dashboardRequirements(manifest: DashboardManifest): {
5
+ schema: string;
6
+ version: number;
7
+ bindings: {
8
+ id: string;
9
+ providerId: string;
10
+ operation: string;
11
+ contractVersion: number;
12
+ source: import("@xpert-ai/contracts").AnalyticsSourceRef;
13
+ }[];
14
+ externalResources: string[];
15
+ dynamicResourcesMayExist: boolean;
16
+ includesQueryResults: boolean;
17
+ requiresAuthorizedHost: boolean;
18
+ };
19
+ export declare function encodeDashboardArchive(manifest: DashboardManifest, dependencies: ArtifactDocumentDependencyAsset[]): Buffer<ArrayBuffer>;
20
+ export declare function decodeDashboardArchive(bytes: Buffer): {
21
+ manifest: {
22
+ title: string;
23
+ id: string;
24
+ schema: "xpert.live-dashboard";
25
+ bindings: import("@xpert-ai/contracts").AnalyticsBinding[];
26
+ controls: {
27
+ type: "string" | "number" | "boolean" | "date";
28
+ controlId: string;
29
+ label: string;
30
+ defaultValue?: string | number | boolean | null | undefined;
31
+ allowedValues?: (string | number | boolean | null)[] | undefined;
32
+ }[];
33
+ charts: {
34
+ title: string;
35
+ type: "bar" | "line" | "area" | "pie" | "scatter";
36
+ id: string;
37
+ datasetId: string;
38
+ x: string;
39
+ y: string;
40
+ color?: string | undefined;
41
+ }[];
42
+ tables: {
43
+ title: string;
44
+ id: string;
45
+ datasetId: string;
46
+ fields: string[];
47
+ }[];
48
+ sourceMetadata?: {
49
+ bindingId: string;
50
+ title: string;
51
+ description?: string | undefined;
52
+ }[] | undefined;
53
+ components?: {
54
+ title: string;
55
+ id: string;
56
+ elementId: string;
57
+ kind: "filter" | "metric" | "chart" | "table" | "text";
58
+ sources: {
59
+ bindingId: string;
60
+ fieldIds?: string[] | undefined;
61
+ }[];
62
+ }[] | undefined;
63
+ presentation?: {
64
+ type: "html";
65
+ html: string;
66
+ dependencies?: import("@xpert-ai/plugin-sdk/artifact-document").ArtifactDocumentDependencyLock[] | undefined;
67
+ } | undefined;
68
+ };
69
+ dependencies: ArtifactDocumentDependencyAsset[];
70
+ };
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_DASHBOARD_ARCHIVE_BYTES = void 0;
4
+ exports.dashboardRequirements = dashboardRequirements;
5
+ exports.encodeDashboardArchive = encodeDashboardArchive;
6
+ exports.decodeDashboardArchive = decodeDashboardArchive;
7
+ const node_crypto_1 = require("node:crypto");
8
+ const fflate_1 = require("fflate");
9
+ const v3_1 = require("zod/v3");
10
+ const parse5_1 = require("parse5");
11
+ const document_package_1 = require("./document-package");
12
+ exports.MAX_DASHBOARD_ARCHIVE_BYTES = 12_000_000;
13
+ const entryNames = ['dashboard.json', 'dashboard.html', 'requirements.json'];
14
+ const archiveIndex = v3_1.z.object({
15
+ schema: v3_1.z.literal('xpert.live-dashboard.archive'), version: v3_1.z.literal(1),
16
+ files: v3_1.z.array(v3_1.z.object({ name: v3_1.z.enum(entryNames), bytes: v3_1.z.number().int().nonnegative().max(document_package_1.MAX_DASHBOARD_PACKAGE_BYTES), sha256: v3_1.z.string().regex(/^[a-f0-9]{64}$/) }).strict()).min(2).max(3)
17
+ }).strict();
18
+ const hash = (value) => (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
19
+ function dashboardRequirements(manifest) {
20
+ const externalResources = new Set();
21
+ const visit = (node) => {
22
+ if ('attrs' in node)
23
+ for (const attr of node.attrs) {
24
+ if (['src', 'href'].includes(attr.name) && /^(https?:)?\/\//i.test(attr.value))
25
+ externalResources.add(attr.value);
26
+ }
27
+ if ('childNodes' in node)
28
+ node.childNodes.forEach(visit);
29
+ };
30
+ if (manifest.presentation)
31
+ visit((0, parse5_1.parse)(manifest.presentation.html));
32
+ return { schema: 'xpert.live-dashboard.requirements', version: 1,
33
+ bindings: manifest.bindings.map(({ id, providerId, operation, contractVersion, source }) => ({ id, providerId, operation, contractVersion, source })),
34
+ externalResources: [...externalResources], dynamicResourcesMayExist: !!manifest.presentation,
35
+ includesQueryResults: false, requiresAuthorizedHost: true };
36
+ }
37
+ function encodeDashboardArchive(manifest, dependencies) {
38
+ const entries = {
39
+ 'dashboard.json': (0, document_package_1.encodeDashboardPackage)(manifest, dependencies),
40
+ 'requirements.json': Buffer.from(JSON.stringify(dashboardRequirements(manifest), null, 2))
41
+ };
42
+ if (entries['dashboard.json'].length > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES)
43
+ throw new Error('DASHBOARD_PACKAGE_TOO_LARGE');
44
+ if (manifest.presentation)
45
+ entries['dashboard.html'] = Buffer.from(manifest.presentation.html);
46
+ const index = { schema: 'xpert.live-dashboard.archive', version: 1,
47
+ files: Object.entries(entries).map(([name, bytes]) => ({ name, bytes: bytes.length, sha256: hash(bytes) })) };
48
+ entries['package.json'] = Buffer.from(JSON.stringify(index, null, 2));
49
+ const bytes = Buffer.from((0, fflate_1.zipSync)(entries, { level: 6 }));
50
+ if (bytes.length > exports.MAX_DASHBOARD_ARCHIVE_BYTES)
51
+ throw new Error('DASHBOARD_ARCHIVE_TOO_LARGE');
52
+ return bytes;
53
+ }
54
+ /** Never extracts paths to disk. Bound both advertised and actually inflated bytes. */
55
+ function unzipBounded(bytes) {
56
+ const files = new Map(), seen = new Set();
57
+ let total = 0, active = 0, failed;
58
+ const unzip = new fflate_1.Unzip(file => {
59
+ if (seen.has(file.name) || !['package.json', ...entryNames].includes(file.name) || (file.originalSize ?? 0) > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES)
60
+ throw new Error('DASHBOARD_ARCHIVE_ENTRY_INVALID');
61
+ seen.add(file.name);
62
+ active++;
63
+ const chunks = [];
64
+ let size = 0;
65
+ file.ondata = (error, data, final) => {
66
+ if (error) {
67
+ failed = error;
68
+ file.terminate();
69
+ return;
70
+ }
71
+ size += data.length;
72
+ total += data.length;
73
+ if (size > document_package_1.MAX_DASHBOARD_PACKAGE_BYTES || total > exports.MAX_DASHBOARD_ARCHIVE_BYTES) {
74
+ failed = new Error('DASHBOARD_ARCHIVE_TOO_LARGE');
75
+ file.terminate();
76
+ return;
77
+ }
78
+ chunks.push(data);
79
+ if (final) {
80
+ files.set(file.name, Buffer.concat(chunks));
81
+ active--;
82
+ }
83
+ };
84
+ file.start();
85
+ });
86
+ unzip.register(fflate_1.UnzipInflate);
87
+ for (let offset = 0; offset < bytes.length; offset += 1024) {
88
+ unzip.push(bytes.subarray(offset, offset + 1024), offset + 1024 >= bytes.length);
89
+ if (failed)
90
+ throw failed;
91
+ }
92
+ if (active || !files.has('package.json') || !files.has('dashboard.json') || !files.has('requirements.json'))
93
+ throw new Error('DASHBOARD_ARCHIVE_INCOMPLETE');
94
+ return files;
95
+ }
96
+ function decodeDashboardArchive(bytes) {
97
+ if (bytes.length > exports.MAX_DASHBOARD_ARCHIVE_BYTES)
98
+ throw new Error('DASHBOARD_ARCHIVE_TOO_LARGE');
99
+ if (bytes[0] !== 0x50 || bytes[1] !== 0x4b)
100
+ return (0, document_package_1.decodeDashboardPackage)(bytes);
101
+ const files = unzipBounded(bytes);
102
+ const index = archiveIndex.parse(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(files.get('package.json'))));
103
+ if (new Set(index.files.map(entry => entry.name)).size !== index.files.length || files.size !== index.files.length + 1)
104
+ throw new Error('DASHBOARD_ARCHIVE_ENTRY_INVALID');
105
+ for (const entry of index.files) {
106
+ const file = files.get(entry.name);
107
+ if (!file || file.length !== entry.bytes || hash(file) !== entry.sha256)
108
+ throw new Error('DASHBOARD_ARCHIVE_INTEGRITY_FAILED');
109
+ }
110
+ if (!index.files.some(entry => entry.name === 'dashboard.json') || !index.files.some(entry => entry.name === 'requirements.json'))
111
+ throw new Error('DASHBOARD_ARCHIVE_INCOMPLETE');
112
+ const result = (0, document_package_1.decodeDashboardPackage)(files.get('dashboard.json'));
113
+ if (result.manifest.presentation ? !files.get('dashboard.html')?.equals(Buffer.from(result.manifest.presentation.html)) : files.has('dashboard.html'))
114
+ throw new Error('DASHBOARD_ARCHIVE_HTML_MISMATCH');
115
+ if (!files.get('requirements.json')?.equals(Buffer.from(JSON.stringify(dashboardRequirements(result.manifest), null, 2))))
116
+ throw new Error('DASHBOARD_ARCHIVE_REQUIREMENTS_MISMATCH');
117
+ return result;
118
+ }
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod/v3';
2
+ import type { DashboardManifest } from './dashboard.schema';
3
+ export declare const componentId: z.ZodString;
4
+ export declare const sourceMetadataSchema: z.ZodArray<z.ZodObject<{
5
+ bindingId: z.ZodString;
6
+ title: z.ZodString;
7
+ description: z.ZodOptional<z.ZodString>;
8
+ }, "strict", z.ZodTypeAny, {
9
+ bindingId: string;
10
+ title: string;
11
+ description?: string | undefined;
12
+ }, {
13
+ bindingId: string;
14
+ title: string;
15
+ description?: string | undefined;
16
+ }>, "many">;
17
+ export declare const dashboardComponentsSchema: z.ZodArray<z.ZodObject<{
18
+ id: z.ZodString;
19
+ elementId: z.ZodString;
20
+ title: z.ZodString;
21
+ kind: z.ZodEnum<["metric", "chart", "table", "filter", "text"]>;
22
+ sources: z.ZodArray<z.ZodObject<{
23
+ bindingId: z.ZodString;
24
+ fieldIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
25
+ }, "strict", z.ZodTypeAny, {
26
+ bindingId: string;
27
+ fieldIds?: string[] | undefined;
28
+ }, {
29
+ bindingId: string;
30
+ fieldIds?: string[] | undefined;
31
+ }>, "many">;
32
+ }, "strict", z.ZodTypeAny, {
33
+ title: string;
34
+ id: string;
35
+ elementId: string;
36
+ kind: "filter" | "metric" | "chart" | "table" | "text";
37
+ sources: {
38
+ bindingId: string;
39
+ fieldIds?: string[] | undefined;
40
+ }[];
41
+ }, {
42
+ title: string;
43
+ id: string;
44
+ elementId: string;
45
+ kind: "filter" | "metric" | "chart" | "table" | "text";
46
+ sources: {
47
+ bindingId: string;
48
+ fieldIds?: string[] | undefined;
49
+ }[];
50
+ }>, "many">;
51
+ export type DashboardComponent = z.infer<typeof dashboardComponentsSchema>[number];
52
+ export declare function dashboardComponents(manifest: DashboardManifest): DashboardComponent[];
53
+ export declare function dashboardSources(manifest: DashboardManifest): {
54
+ binding: import("@xpert-ai/contracts").AnalyticsBinding;
55
+ title: string;
56
+ description: string | undefined;
57
+ components: {
58
+ title: string;
59
+ id: string;
60
+ elementId: string;
61
+ kind: "filter" | "metric" | "chart" | "table" | "text";
62
+ sources: {
63
+ bindingId: string;
64
+ fieldIds?: string[] | undefined;
65
+ }[];
66
+ }[];
67
+ }[];
68
+ /** A rendering projection never exposes provider-native query configuration to a viewer. */
69
+ export declare function publicDashboardManifest(manifest: DashboardManifest): DashboardManifest;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.dashboardComponentsSchema = exports.sourceMetadataSchema = exports.componentId = void 0;
4
+ exports.dashboardComponents = dashboardComponents;
5
+ exports.dashboardSources = dashboardSources;
6
+ exports.publicDashboardManifest = publicDashboardManifest;
7
+ const v3_1 = require("zod/v3");
8
+ exports.componentId = v3_1.z.string().regex(/^[A-Za-z][A-Za-z0-9_.-]{0,79}$/);
9
+ exports.sourceMetadataSchema = v3_1.z.array(v3_1.z.object({
10
+ bindingId: exports.componentId, title: v3_1.z.string().min(1).max(160), description: v3_1.z.string().max(2000).optional()
11
+ }).strict()).max(8);
12
+ exports.dashboardComponentsSchema = v3_1.z.array(v3_1.z.object({
13
+ id: exports.componentId, elementId: exports.componentId, title: v3_1.z.string().min(1).max(160),
14
+ kind: v3_1.z.enum(['metric', 'chart', 'table', 'filter', 'text']),
15
+ sources: v3_1.z.array(v3_1.z.object({ bindingId: exports.componentId, fieldIds: v3_1.z.array(exports.componentId).max(80).optional() }).strict()).max(8)
16
+ }).strict()).max(160);
17
+ function dashboardComponents(manifest) {
18
+ if (manifest.presentation)
19
+ return manifest.components ?? [];
20
+ return [...manifest.charts, ...manifest.tables].map(view => ({
21
+ id: view.id, elementId: view.id, title: view.title, kind: 'fields' in view ? 'table' : 'chart',
22
+ sources: manifest.bindings.filter(binding => binding.output.datasetId === view.datasetId)
23
+ .map(binding => ({ bindingId: binding.id, fieldIds: 'fields' in view ? view.fields : [view.x, view.y, ...(view.color ? [view.color] : [])] }))
24
+ }));
25
+ }
26
+ function dashboardSources(manifest) {
27
+ const components = dashboardComponents(manifest);
28
+ return manifest.bindings.map(binding => ({
29
+ binding, title: manifest.sourceMetadata?.find(item => item.bindingId === binding.id)?.title ?? binding.id,
30
+ description: manifest.sourceMetadata?.find(item => item.bindingId === binding.id)?.description,
31
+ components: components.filter(component => component.sources.some(source => source.bindingId === binding.id))
32
+ }));
33
+ }
34
+ /** A rendering projection never exposes provider-native query configuration to a viewer. */
35
+ function publicDashboardManifest(manifest) {
36
+ return { ...manifest, bindings: manifest.bindings.map(binding => ({ ...binding, payload: {} })) };
37
+ }