@xpert-ai/plugin-live-dashboard 0.5.0 → 0.6.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 (58) hide show
  1. package/.xpertai-plugin/plugin.json +1 -1
  2. package/README.md +4 -2
  3. package/dist/index.js +3 -1
  4. package/dist/lib/agent.schema.d.ts +9 -9
  5. package/dist/lib/agent.schema.js +2 -1
  6. package/dist/lib/archive.service.d.ts +26 -2
  7. package/dist/lib/archive.service.js +17 -3
  8. package/dist/lib/bounded-map.d.ts +2 -0
  9. package/dist/lib/bounded-map.js +25 -0
  10. package/dist/lib/capacity.d.ts +14 -0
  11. package/dist/lib/capacity.js +16 -0
  12. package/dist/lib/dashboard-archive.d.ts +1 -1
  13. package/dist/lib/dashboard-queries.d.ts +1 -1
  14. package/dist/lib/dashboard-queries.js +11 -6
  15. package/dist/lib/dashboard-receipt.js +1 -1
  16. package/dist/lib/dashboard-sources.js +3 -2
  17. package/dist/lib/dashboard-view.schema.d.ts +20 -20
  18. package/dist/lib/dashboard.schema.d.ts +18 -18
  19. package/dist/lib/dashboard.schema.js +2 -1
  20. package/dist/lib/dashboard.service.d.ts +24 -5
  21. package/dist/lib/dashboard.service.js +58 -8
  22. package/dist/lib/definition-tools.d.ts +4 -4
  23. package/dist/lib/document-host.js +1 -1
  24. package/dist/lib/document-package.d.ts +1 -1
  25. package/dist/lib/editing-patch.js +2 -1
  26. package/dist/lib/editing.d.ts +11 -10
  27. package/dist/lib/editing.js +5 -1
  28. package/dist/lib/filter-evidence.d.ts +28 -0
  29. package/dist/lib/filter-evidence.js +3 -2
  30. package/dist/lib/html-guidance.d.ts +1 -0
  31. package/dist/lib/html-guidance.js +2 -1
  32. package/dist/lib/html-validation.js +1 -1
  33. package/dist/lib/middleware.d.ts +3 -1
  34. package/dist/lib/middleware.js +89 -11
  35. package/dist/lib/plugin.js +2 -1
  36. package/dist/lib/presentation.provider.d.ts +1 -1
  37. package/dist/lib/preview-retry.d.ts +4 -3
  38. package/dist/lib/preview-retry.js +13 -7
  39. package/dist/lib/prompt-workflows.js +1 -1
  40. package/dist/lib/query-session.d.ts +9 -0
  41. package/dist/lib/query-session.js +64 -0
  42. package/dist/lib/save-requests.d.ts +118 -0
  43. package/dist/lib/save-requests.js +199 -0
  44. package/dist/lib/source-executions.d.ts +243 -2
  45. package/dist/lib/source-executions.js +29 -5
  46. package/dist/lib/view.provider.d.ts +38 -3
  47. package/dist/lib/view.provider.js +20 -4
  48. package/dist/remote/app.js +114 -65
  49. package/dist/remote/build-manifest.json +37 -32
  50. package/dist/sandbox-actions/action.json +1 -1
  51. package/dist/sandbox-actions/bundle/host.js +63 -14
  52. package/dist/sandbox-actions/bundle/validate.mjs +2 -2
  53. package/dist/validation/host-entry.d.ts +5 -1
  54. package/dist/validation/host-entry.js +21 -4
  55. package/docs/docs.json +2 -0
  56. package/docs/large-dashboards-acceptance.mdx +74 -0
  57. package/docs/large-dashboards.mdx +80 -0
  58. package/package.json +5 -5
@@ -0,0 +1,199 @@
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.DashboardSaveRequests = exports.waitSaveRequestSchema = exports.saveRequestSchema = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const node_crypto_1 = require("node:crypto");
18
+ const promises_1 = require("node:timers/promises");
19
+ const v3_1 = require("zod/v3");
20
+ const plugin_sdk_1 = require("@xpert-ai/plugin-sdk");
21
+ const artifact_document_1 = require("@xpert-ai/plugin-sdk/artifact-document");
22
+ const dashboard_service_1 = require("./dashboard.service");
23
+ const dashboard_schema_1 = require("./dashboard.schema");
24
+ const TTL = 86400, MAX_RUNTIME = 10 * 60_000, WAIT_MS = 20_000;
25
+ const hash = (value) => (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
26
+ const key = (scope, id) => {
27
+ if (!scope.tenantId || !scope.organizationId)
28
+ throw new Error('DASHBOARD_SAVE_REQUEST_DENIED');
29
+ return `live_dashboard:save:${scope.tenantId}:${scope.organizationId}:${id}`;
30
+ };
31
+ const contextSchema = v3_1.z.object({ tenantId: v3_1.z.string().uuid(), organizationId: v3_1.z.string().uuid(), userId: v3_1.z.string().uuid(),
32
+ hostType: v3_1.z.enum(['agent', 'project']), hostId: v3_1.z.string().uuid(), projectId: v3_1.z.string().uuid().optional() }).strict();
33
+ const fileSchema = v3_1.z.object({ filePath: v3_1.z.string(), workspacePath: v3_1.z.string(), sha256: v3_1.z.string(), size: v3_1.z.number() }).strict();
34
+ const receiptSchema = v3_1.z.object({ artifactId: v3_1.z.string().uuid(), draftId: v3_1.z.string(), draftRevision: v3_1.z.number(), baseVersionId: v3_1.z.string().nullable(), status: v3_1.z.literal('draft'), title: v3_1.z.string() });
35
+ const sourceFileSchema = v3_1.z.object({ path: v3_1.z.string().max(300), sha256: v3_1.z.string().regex(/^[a-f0-9]{64}$/) }).strict();
36
+ const recordSchema = v3_1.z.object({ requestId: v3_1.z.string().uuid(), jobId: v3_1.z.string(), context: contextSchema, file: fileSchema,
37
+ manifestId: v3_1.z.string().uuid(), expectedRevision: v3_1.z.number(), state: v3_1.z.enum(['queued', 'running', 'succeeded', 'failed']), stage: v3_1.z.string(), completed: v3_1.z.number(), total: v3_1.z.number(),
38
+ fingerprint: v3_1.z.string(), createdAt: v3_1.z.number(), deadline: v3_1.z.number(), error: v3_1.z.string().optional(), dashboard: receiptSchema.optional(), sourceFile: sourceFileSchema.optional() }).strict();
39
+ const inputSchema = v3_1.z.object({ manifest: dashboard_schema_1.dashboardSchema, expectedVersionId: v3_1.z.string().uuid().nullable(), expectedRevision: v3_1.z.number().int().nonnegative(),
40
+ dependencies: artifact_document_1.artifactDocumentDependencyAssets.optional() }).strict();
41
+ exports.saveRequestSchema = v3_1.z.object({ requestId: v3_1.z.string().uuid() }).strict();
42
+ exports.waitSaveRequestSchema = exports.saveRequestSchema.extend({ cursor: v3_1.z.string().min(1).max(100) }).strict();
43
+ function trustedScope(context) {
44
+ return contextSchema.parse({ tenantId: context.tenantId, organizationId: context.organizationId, userId: context.userId,
45
+ hostType: context.hostType, hostId: context.hostId,
46
+ ...(context.hostType === 'project' ? { projectId: context.hostId } : context.runtimeScope?.projectId ? { projectId: context.runtimeScope.projectId } : {}) });
47
+ }
48
+ function restoredScope(scope) {
49
+ return { ...scope, slots: [], ...(scope.projectId ? { runtimeScope: { projectId: scope.projectId, conversationId: null,
50
+ dataScopeKey: `project:${scope.projectId}`, workspaceFiles: { catalog: 'projects', scopeId: scope.projectId, projectId: scope.projectId } } } : {}) };
51
+ }
52
+ /** Durable save orchestration; domain validation and CAS remain in DashboardService. */
53
+ let DashboardSaveRequests = class DashboardSaveRequests {
54
+ constructor(service, runtime, queue) {
55
+ this.service = service;
56
+ this.runtime = runtime;
57
+ this.queue = queue;
58
+ }
59
+ async submit(context, manifest, expectedVersionId, expectedRevision, idempotencyKey, sourceFile, dependencies) {
60
+ await this.service.authorizeProject(context, 'write');
61
+ const scope = trustedScope(context), parsed = inputSchema.parse({ manifest, expectedVersionId, expectedRevision, dependencies });
62
+ const fingerprint = hash(JSON.stringify({ ...parsed, manifest: { ...parsed.manifest, ...(expectedRevision === 0 ? { id: 'new' } : {}) }, sourceFile }));
63
+ const previousCount = await this.service.previousBindingCount(context, parsed.manifest);
64
+ this.service.assertCapacity(parsed.manifest.bindings.length, previousCount);
65
+ const redis = await this.queue.getRedis(), files = this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability);
66
+ const dedupe = `${key(scope, 'submission')}:${hash(JSON.stringify([scope, idempotencyKey]))}`;
67
+ const previous = await redis.get(dedupe);
68
+ if (previous) {
69
+ if ((await this.read(context, previous)).fingerprint !== fingerprint)
70
+ throw new Error('DASHBOARD_SAVE_IDEMPOTENCY_CONFLICT');
71
+ return this.get(context, previous);
72
+ }
73
+ const requestId = (0, node_crypto_1.randomUUID)(), buffer = Buffer.from(JSON.stringify(parsed));
74
+ if (buffer.length > 12_000_000)
75
+ throw new Error('DASHBOARD_MANIFEST_TOO_LARGE');
76
+ const stored = await files.uploadBuffer({ tenantId: scope.tenantId, userId: scope.userId, catalog: 'users', buffer,
77
+ originalName: 'save.json', fileName: 'save.json', folder: `live-dashboard-save/${requestId}`, mimeType: 'application/json' });
78
+ const record = { requestId, jobId: `dashboard-save-${requestId}`, context: scope,
79
+ file: { filePath: stored.filePath, workspacePath: stored.workspacePath, sha256: hash(buffer), size: buffer.length },
80
+ fingerprint, manifestId: manifest.id, expectedRevision, state: 'queued', stage: 'queued', completed: 0, total: manifest.bindings.length, createdAt: Date.now(), deadline: Date.now() + MAX_RUNTIME,
81
+ ...(sourceFile ? { sourceFile: sourceFileSchema.parse(sourceFile) } : {}) };
82
+ const winner = await redis.eval("local id=redis.call('GET',KEYS[1]); if id then return id end; redis.call('SET',KEYS[2],ARGV[2],'EX',ARGV[3]); redis.call('SET',KEYS[1],ARGV[1],'EX',ARGV[3]); return ARGV[1]", 2, dedupe, key(scope, requestId), requestId, JSON.stringify(record), TTL);
83
+ if (winner !== requestId) {
84
+ await files.deleteFile(this.reference(record));
85
+ if ((await this.read(context, String(winner))).fingerprint !== fingerprint)
86
+ throw new Error('DASHBOARD_SAVE_IDEMPOTENCY_CONFLICT');
87
+ return this.get(context, String(winner));
88
+ }
89
+ // Recovery can enqueue the same deterministic job if the process stops at this boundary.
90
+ await this.enqueue(record);
91
+ return this.snapshot(record);
92
+ }
93
+ reference(record) {
94
+ return { source: 'platform.workspace.files', tenantId: record.context.tenantId, userId: record.context.userId, catalog: 'users',
95
+ filePath: record.file.filePath, workspacePath: record.file.workspacePath };
96
+ }
97
+ async enqueue(record) {
98
+ await this.queue.enqueue({ pluginName: dashboard_service_1.PLUGIN_NAME, queueName: 'save-validation', jobName: 'save', payload: { requestId: record.requestId },
99
+ tenantId: record.context.tenantId, organizationId: record.context.organizationId, userId: record.context.userId,
100
+ scopeKey: 'system:global', jobId: record.jobId, attempts: 1, removeOnComplete: { age: TTL, count: 1000 }, removeOnFail: { age: TTL, count: 1000 } });
101
+ }
102
+ async read(context, requestId) {
103
+ v3_1.z.string().uuid().parse(requestId);
104
+ const redis = await this.queue.getRedis(), value = await redis.get(key(context, requestId));
105
+ if (!value)
106
+ throw new Error('DASHBOARD_SAVE_REQUEST_UNAVAILABLE');
107
+ const record = recordSchema.parse(JSON.parse(value));
108
+ if (JSON.stringify(trustedScope(context)) !== JSON.stringify(record.context))
109
+ throw new Error('DASHBOARD_SAVE_REQUEST_DENIED');
110
+ return record;
111
+ }
112
+ snapshot(record) {
113
+ const terminal = record.state === 'succeeded' || record.state === 'failed';
114
+ const result = { requestId: record.requestId, state: record.state, stage: record.stage, completed: record.completed, total: record.total,
115
+ terminal, ...(record.dashboard ? { dashboard: record.dashboard, ...(record.sourceFile ? { sourceFile: record.sourceFile } : {}) } : {}),
116
+ ...(record.error ? { error: record.error } : {}), nextAction: terminal ? 'report_result' : 'wait' };
117
+ return { ...result, cursor: hash(JSON.stringify(result)) };
118
+ }
119
+ async get(context, requestId) {
120
+ await this.service.authorizeProject(context);
121
+ let record = await this.read(context, requestId);
122
+ if (record.state === 'queued' || record.state === 'running') {
123
+ const job = await this.queue.getJob({ jobId: record.jobId });
124
+ if (!job && record.deadline > Date.now())
125
+ await this.enqueue(record);
126
+ else if (job?.state === 'failed' || !job && record.deadline <= Date.now()) {
127
+ // A completed domain receipt always wins a racing observation.
128
+ record = await this.read(context, requestId);
129
+ if (record.state !== 'succeeded') {
130
+ const receipt = await this.service.savedRequestReceipt(context, record.manifestId, record.requestId, record.expectedRevision);
131
+ record = receipt ? { ...record, state: 'succeeded', stage: 'saved', completed: record.total, dashboard: receipt }
132
+ : { ...record, state: 'failed', error: 'DASHBOARD_SAVE_JOB_FAILED: retrieve the current draft before retrying; no new save receipt was confirmed.' };
133
+ await (await this.queue.getRedis()).set(key(record.context, requestId), JSON.stringify(record), 'EX', TTL);
134
+ await this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability).deleteFile(this.reference(record)).catch(() => undefined);
135
+ }
136
+ }
137
+ }
138
+ return this.snapshot(record);
139
+ }
140
+ async wait(context, requestId, cursor, signal) {
141
+ const until = Date.now() + WAIT_MS;
142
+ let current = await this.get(context, requestId);
143
+ while (!current.terminal && current.cursor === cursor && Date.now() < until) {
144
+ await (0, promises_1.setTimeout)(Math.min(2000, until - Date.now()), undefined, { signal });
145
+ current = this.snapshot(await this.read(context, requestId));
146
+ }
147
+ return current.terminal || current.cursor !== cursor ? current : this.get(context, requestId);
148
+ }
149
+ async handle(job, identity) {
150
+ const requestId = v3_1.z.string().uuid().parse(job.data.requestId), redis = await this.queue.getRedis();
151
+ let record = recordSchema.parse(JSON.parse(await redis.get(key(identity, requestId)) ?? 'null'));
152
+ if (record.context.tenantId !== identity.tenantId || record.context.organizationId !== identity.organizationId || record.context.userId !== identity.userId)
153
+ throw new Error('DASHBOARD_SAVE_REQUEST_DENIED');
154
+ if (record.state === 'succeeded')
155
+ return;
156
+ let committed = false, terminalPersisted = false;
157
+ const persist = async () => { await redis.set(key(record.context, requestId), JSON.stringify(record), 'EX', TTL); };
158
+ const files = this.runtime.require(plugin_sdk_1.WorkspaceFilesRuntimeCapability);
159
+ try {
160
+ const file = await files.readBuffer(this.reference(record));
161
+ if (file.buffer.length !== record.file.size || hash(file.buffer) !== record.file.sha256)
162
+ throw new Error('DASHBOARD_SAVE_INPUT_CHANGED');
163
+ const input = inputSchema.parse(JSON.parse(file.buffer.toString('utf8')));
164
+ const dashboard = await this.service.save(restoredScope(record.context), input.manifest, input.expectedVersionId, input.expectedRevision, input.dependencies, { requestId, progress: async (stage, completed, total) => {
165
+ if (Date.now() >= record.deadline)
166
+ throw new Error('DASHBOARD_SAVE_DEADLINE');
167
+ record = { ...record, state: 'running', stage, completed, total };
168
+ await persist();
169
+ } });
170
+ committed = true;
171
+ record = { ...record, state: 'succeeded', stage: 'saved', completed: record.total, dashboard };
172
+ await persist();
173
+ terminalPersisted = true;
174
+ }
175
+ catch (error) {
176
+ // Keep frozen input for recovery when the Artifact CAS succeeded but status persistence did not.
177
+ if (committed)
178
+ throw new Error('DASHBOARD_SAVE_COMMIT_UNCONFIRMED');
179
+ // Do not persist native provider messages or private query configuration.
180
+ const code = error instanceof Error ? error.message.match(/^DASHBOARD_[A-Z_]+/)?.[0] : undefined;
181
+ record = { ...record, state: 'failed', error: code ?? 'DASHBOARD_SAVE_VALIDATION_FAILED' };
182
+ await persist();
183
+ terminalPersisted = true;
184
+ throw new Error(record.error);
185
+ }
186
+ finally {
187
+ if (terminalPersisted)
188
+ await files.deleteFile(this.reference(record)).catch(() => undefined);
189
+ }
190
+ }
191
+ };
192
+ exports.DashboardSaveRequests = DashboardSaveRequests;
193
+ exports.DashboardSaveRequests = DashboardSaveRequests = __decorate([
194
+ (0, common_1.Injectable)(),
195
+ (0, plugin_sdk_1.PluginJobProcessor)({ pluginName: dashboard_service_1.PLUGIN_NAME, queueName: 'save-validation', jobName: 'save', concurrency: 2 }),
196
+ __param(1, (0, common_1.Inject)(plugin_sdk_1.XPERT_RUNTIME_CAPABILITIES_TOKEN)),
197
+ __param(2, (0, common_1.Inject)(plugin_sdk_1.MANAGED_QUEUE_SERVICE_TOKEN)),
198
+ __metadata("design:paramtypes", [dashboard_service_1.DashboardService, Object, Object])
199
+ ], DashboardSaveRequests);
@@ -169,20 +169,261 @@ export declare const sourceDatasetSchema: z.ZodObject<{
169
169
  }, z.ZodTypeAny, "passthrough">>;
170
170
  }, z.ZodTypeAny, "passthrough">>;
171
171
  export type SourceDataset = z.infer<typeof sourceDatasetSchema>;
172
+ /** Rendering needs the complete typed contract; inspection deliberately accepts a smaller projection. */
173
+ export declare const renderDatasetSchema: z.ZodObject<{
174
+ schema: z.ZodLiteral<"xpert.analytics.dataset">;
175
+ datasetId: z.ZodString;
176
+ rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
177
+ provenance: z.ZodObject<{
178
+ executionId: z.ZodString;
179
+ bindingId: z.ZodString;
180
+ providerId: z.ZodString;
181
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
182
+ executionId: z.ZodString;
183
+ bindingId: z.ZodString;
184
+ providerId: z.ZodString;
185
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
186
+ executionId: z.ZodString;
187
+ bindingId: z.ZodString;
188
+ providerId: z.ZodString;
189
+ }, z.ZodTypeAny, "passthrough">>;
190
+ } & {
191
+ fields: z.ZodArray<z.ZodObject<{
192
+ id: z.ZodString;
193
+ label: z.ZodString;
194
+ sourceField: z.ZodString;
195
+ logicalType: z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>;
196
+ nullable: z.ZodBoolean;
197
+ role: z.ZodEnum<["dimension", "measure", "time", "identifier"]>;
198
+ semanticRef: z.ZodOptional<z.ZodString>;
199
+ unit: z.ZodOptional<z.ZodString>;
200
+ }, "strict", z.ZodTypeAny, {
201
+ id: string;
202
+ label: string;
203
+ sourceField: string;
204
+ logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
205
+ nullable: boolean;
206
+ role: "time" | "dimension" | "measure" | "identifier";
207
+ semanticRef?: string | undefined;
208
+ unit?: string | undefined;
209
+ }, {
210
+ id: string;
211
+ label: string;
212
+ sourceField: string;
213
+ logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
214
+ nullable: boolean;
215
+ role: "time" | "dimension" | "measure" | "identifier";
216
+ semanticRef?: string | undefined;
217
+ unit?: string | undefined;
218
+ }>, "many">;
219
+ grain: z.ZodArray<z.ZodString, "many">;
220
+ completeness: z.ZodObject<{
221
+ returnedRows: z.ZodNumber;
222
+ totalRows: z.ZodOptional<z.ZodNumber>;
223
+ totalAccuracy: z.ZodEnum<["exact", "estimated", "unknown"]>;
224
+ scope: z.ZodEnum<["full_query_result", "page", "sample", "top_n"]>;
225
+ truncated: z.ZodBoolean;
226
+ }, "strip", z.ZodTypeAny, {
227
+ scope: "page" | "full_query_result" | "sample" | "top_n";
228
+ truncated: boolean;
229
+ returnedRows: number;
230
+ totalAccuracy: "exact" | "unknown" | "estimated";
231
+ totalRows?: number | undefined;
232
+ }, {
233
+ scope: "page" | "full_query_result" | "sample" | "top_n";
234
+ truncated: boolean;
235
+ returnedRows: number;
236
+ totalAccuracy: "exact" | "unknown" | "estimated";
237
+ totalRows?: number | undefined;
238
+ }>;
239
+ freshness: z.ZodObject<{
240
+ executedAt: z.ZodString;
241
+ sourceFetchedAt: z.ZodString;
242
+ dataAsOf: z.ZodOptional<z.ZodString>;
243
+ }, "strip", z.ZodTypeAny, {
244
+ sourceFetchedAt: string;
245
+ executedAt: string;
246
+ dataAsOf?: string | undefined;
247
+ }, {
248
+ sourceFetchedAt: string;
249
+ executedAt: string;
250
+ dataAsOf?: string | undefined;
251
+ }>;
252
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
253
+ schema: z.ZodLiteral<"xpert.analytics.dataset">;
254
+ datasetId: z.ZodString;
255
+ rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
256
+ provenance: z.ZodObject<{
257
+ executionId: z.ZodString;
258
+ bindingId: z.ZodString;
259
+ providerId: z.ZodString;
260
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
261
+ executionId: z.ZodString;
262
+ bindingId: z.ZodString;
263
+ providerId: z.ZodString;
264
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
265
+ executionId: z.ZodString;
266
+ bindingId: z.ZodString;
267
+ providerId: z.ZodString;
268
+ }, z.ZodTypeAny, "passthrough">>;
269
+ } & {
270
+ fields: z.ZodArray<z.ZodObject<{
271
+ id: z.ZodString;
272
+ label: z.ZodString;
273
+ sourceField: z.ZodString;
274
+ logicalType: z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>;
275
+ nullable: z.ZodBoolean;
276
+ role: z.ZodEnum<["dimension", "measure", "time", "identifier"]>;
277
+ semanticRef: z.ZodOptional<z.ZodString>;
278
+ unit: z.ZodOptional<z.ZodString>;
279
+ }, "strict", z.ZodTypeAny, {
280
+ id: string;
281
+ label: string;
282
+ sourceField: string;
283
+ logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
284
+ nullable: boolean;
285
+ role: "time" | "dimension" | "measure" | "identifier";
286
+ semanticRef?: string | undefined;
287
+ unit?: string | undefined;
288
+ }, {
289
+ id: string;
290
+ label: string;
291
+ sourceField: string;
292
+ logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
293
+ nullable: boolean;
294
+ role: "time" | "dimension" | "measure" | "identifier";
295
+ semanticRef?: string | undefined;
296
+ unit?: string | undefined;
297
+ }>, "many">;
298
+ grain: z.ZodArray<z.ZodString, "many">;
299
+ completeness: z.ZodObject<{
300
+ returnedRows: z.ZodNumber;
301
+ totalRows: z.ZodOptional<z.ZodNumber>;
302
+ totalAccuracy: z.ZodEnum<["exact", "estimated", "unknown"]>;
303
+ scope: z.ZodEnum<["full_query_result", "page", "sample", "top_n"]>;
304
+ truncated: z.ZodBoolean;
305
+ }, "strip", z.ZodTypeAny, {
306
+ scope: "page" | "full_query_result" | "sample" | "top_n";
307
+ truncated: boolean;
308
+ returnedRows: number;
309
+ totalAccuracy: "exact" | "unknown" | "estimated";
310
+ totalRows?: number | undefined;
311
+ }, {
312
+ scope: "page" | "full_query_result" | "sample" | "top_n";
313
+ truncated: boolean;
314
+ returnedRows: number;
315
+ totalAccuracy: "exact" | "unknown" | "estimated";
316
+ totalRows?: number | undefined;
317
+ }>;
318
+ freshness: z.ZodObject<{
319
+ executedAt: z.ZodString;
320
+ sourceFetchedAt: z.ZodString;
321
+ dataAsOf: z.ZodOptional<z.ZodString>;
322
+ }, "strip", z.ZodTypeAny, {
323
+ sourceFetchedAt: string;
324
+ executedAt: string;
325
+ dataAsOf?: string | undefined;
326
+ }, {
327
+ sourceFetchedAt: string;
328
+ executedAt: string;
329
+ dataAsOf?: string | undefined;
330
+ }>;
331
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
332
+ schema: z.ZodLiteral<"xpert.analytics.dataset">;
333
+ datasetId: z.ZodString;
334
+ rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>, "many">;
335
+ provenance: z.ZodObject<{
336
+ executionId: z.ZodString;
337
+ bindingId: z.ZodString;
338
+ providerId: z.ZodString;
339
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
340
+ executionId: z.ZodString;
341
+ bindingId: z.ZodString;
342
+ providerId: z.ZodString;
343
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
344
+ executionId: z.ZodString;
345
+ bindingId: z.ZodString;
346
+ providerId: z.ZodString;
347
+ }, z.ZodTypeAny, "passthrough">>;
348
+ } & {
349
+ fields: z.ZodArray<z.ZodObject<{
350
+ id: z.ZodString;
351
+ label: z.ZodString;
352
+ sourceField: z.ZodString;
353
+ logicalType: z.ZodEnum<["string", "boolean", "int64", "float64", "decimal", "date", "timestamp"]>;
354
+ nullable: z.ZodBoolean;
355
+ role: z.ZodEnum<["dimension", "measure", "time", "identifier"]>;
356
+ semanticRef: z.ZodOptional<z.ZodString>;
357
+ unit: z.ZodOptional<z.ZodString>;
358
+ }, "strict", z.ZodTypeAny, {
359
+ id: string;
360
+ label: string;
361
+ sourceField: string;
362
+ logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
363
+ nullable: boolean;
364
+ role: "time" | "dimension" | "measure" | "identifier";
365
+ semanticRef?: string | undefined;
366
+ unit?: string | undefined;
367
+ }, {
368
+ id: string;
369
+ label: string;
370
+ sourceField: string;
371
+ logicalType: "string" | "boolean" | "date" | "float64" | "int64" | "decimal" | "timestamp";
372
+ nullable: boolean;
373
+ role: "time" | "dimension" | "measure" | "identifier";
374
+ semanticRef?: string | undefined;
375
+ unit?: string | undefined;
376
+ }>, "many">;
377
+ grain: z.ZodArray<z.ZodString, "many">;
378
+ completeness: z.ZodObject<{
379
+ returnedRows: z.ZodNumber;
380
+ totalRows: z.ZodOptional<z.ZodNumber>;
381
+ totalAccuracy: z.ZodEnum<["exact", "estimated", "unknown"]>;
382
+ scope: z.ZodEnum<["full_query_result", "page", "sample", "top_n"]>;
383
+ truncated: z.ZodBoolean;
384
+ }, "strip", z.ZodTypeAny, {
385
+ scope: "page" | "full_query_result" | "sample" | "top_n";
386
+ truncated: boolean;
387
+ returnedRows: number;
388
+ totalAccuracy: "exact" | "unknown" | "estimated";
389
+ totalRows?: number | undefined;
390
+ }, {
391
+ scope: "page" | "full_query_result" | "sample" | "top_n";
392
+ truncated: boolean;
393
+ returnedRows: number;
394
+ totalAccuracy: "exact" | "unknown" | "estimated";
395
+ totalRows?: number | undefined;
396
+ }>;
397
+ freshness: z.ZodObject<{
398
+ executedAt: z.ZodString;
399
+ sourceFetchedAt: z.ZodString;
400
+ dataAsOf: z.ZodOptional<z.ZodString>;
401
+ }, "strip", z.ZodTypeAny, {
402
+ sourceFetchedAt: string;
403
+ executedAt: string;
404
+ dataAsOf?: string | undefined;
405
+ }, {
406
+ sourceFetchedAt: string;
407
+ executedAt: string;
408
+ dataAsOf?: string | undefined;
409
+ }>;
410
+ }, z.ZodTypeAny, "passthrough">>;
172
411
  export interface SourceExecution {
173
412
  requestId: string;
174
- state: 'running' | 'succeeded' | 'failed';
413
+ state: 'queued' | 'running' | 'succeeded' | 'failed';
175
414
  controls: Record<string, string | number | boolean | null>;
176
415
  requestedAt: string;
416
+ startedAt?: string;
177
417
  finishedAt?: string;
178
418
  result?: SourceDataset;
179
419
  resultControls?: SourceExecution['controls'];
420
+ previewEvicted?: boolean;
180
421
  }
181
422
  export type SourceEvent = {
182
423
  bindingId: string;
183
424
  requestId: string;
184
425
  } & ({
185
- type: 'start';
426
+ type: 'queue' | 'start';
186
427
  controls: SourceExecution['controls'];
187
428
  at: string;
188
429
  } | {
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sourceDatasetSchema = void 0;
3
+ exports.renderDatasetSchema = exports.sourceDatasetSchema = void 0;
4
4
  exports.reduceSourceExecutions = reduceSourceExecutions;
5
5
  const v3_1 = require("zod/v3");
6
+ const analytics_1 = require("@xpert-ai/plugin-sdk/analytics");
6
7
  const scalar = v3_1.z.union([v3_1.z.string(), v3_1.z.number(), v3_1.z.boolean(), v3_1.z.null()]);
7
8
  exports.sourceDatasetSchema = v3_1.z.object({
8
9
  schema: v3_1.z.literal('xpert.analytics.dataset'), datasetId: v3_1.z.string(),
@@ -11,15 +12,38 @@ exports.sourceDatasetSchema = v3_1.z.object({
11
12
  freshness: v3_1.z.object({ sourceFetchedAt: v3_1.z.string(), executedAt: v3_1.z.string().optional(), dataAsOf: v3_1.z.string().optional() }),
12
13
  provenance: v3_1.z.object({ executionId: v3_1.z.string(), bindingId: v3_1.z.string(), providerId: v3_1.z.string() }).passthrough()
13
14
  }).passthrough();
15
+ /** Rendering needs the complete typed contract; inspection deliberately accepts a smaller projection. */
16
+ exports.renderDatasetSchema = exports.sourceDatasetSchema.extend({
17
+ fields: v3_1.z.array(analytics_1.analyticsFieldSchema.required({ id: true, sourceField: true, label: true, logicalType: true, nullable: true, role: true })), grain: v3_1.z.array(v3_1.z.string()),
18
+ completeness: v3_1.z.object({ returnedRows: v3_1.z.number(), totalRows: v3_1.z.number().optional(), totalAccuracy: v3_1.z.enum(['exact', 'estimated', 'unknown']),
19
+ scope: v3_1.z.enum(['full_query_result', 'page', 'sample', 'top_n']), truncated: v3_1.z.boolean() }),
20
+ freshness: v3_1.z.object({ executedAt: v3_1.z.string(), sourceFetchedAt: v3_1.z.string(), dataAsOf: v3_1.z.string().optional() })
21
+ });
14
22
  function reduceSourceExecutions(state, event) {
15
23
  const previous = state[event.bindingId];
16
- if (event.type === 'start')
24
+ if (event.type === 'start' || event.type === 'queue')
17
25
  return { ...state, [event.bindingId]: {
18
- requestId: event.requestId, state: 'running', controls: event.controls, requestedAt: event.at,
26
+ requestId: event.requestId, state: event.type === 'queue' ? 'queued' : 'running', controls: event.controls,
27
+ requestedAt: previous?.requestId === event.requestId ? previous.requestedAt : event.at,
28
+ ...(event.type === 'start' ? { startedAt: event.at } : {}),
19
29
  result: previous?.result, resultControls: previous?.resultControls
20
30
  } };
21
31
  if (!previous || previous.requestId !== event.requestId)
22
32
  return state;
23
- return { ...state, [event.bindingId]: { ...previous, state: event.type === 'success' ? 'succeeded' : 'failed', finishedAt: event.at,
24
- ...(event.type === 'success' ? { result: { ...event.result, rows: event.result.rows.slice(0, 50) }, resultControls: previous.controls } : {}) } };
33
+ const next = { ...state, [event.bindingId]: { ...previous, state: event.type === 'success' ? 'succeeded' : 'failed', finishedAt: event.at,
34
+ ...(event.type === 'success' ? { result: preview(event.result), resultControls: previous.controls, previewEvicted: false } : {}) } };
35
+ // Limit inspection snapshots independently of the rendering result budget.
36
+ const byAge = Object.values(next).filter(item => item.result).sort((a, b) => (a.finishedAt ?? '').localeCompare(b.finishedAt ?? ''));
37
+ while (new TextEncoder().encode(JSON.stringify(Object.values(next).map(item => item.result ?? null))).byteLength > 2_000_000 && byAge.length) {
38
+ const oldest = byAge.shift();
39
+ const id = Object.keys(next).find(id => next[id] === oldest);
40
+ next[id] = { ...oldest, result: undefined, resultControls: undefined, previewEvicted: true };
41
+ }
42
+ return next;
43
+ }
44
+ function preview(result) {
45
+ const rows = result.rows.slice(0, 50);
46
+ while (new TextEncoder().encode(JSON.stringify(rows)).byteLength > 64_000)
47
+ rows.pop();
48
+ return { ...result, rows };
25
49
  }
@@ -1,3 +1,4 @@
1
+ import { DashboardSaveRequests } from './save-requests';
1
2
  import { DashboardProjectsService } from './projects.service';
2
3
  import type { XpertExtensionViewManifest, XpertResolvedViewHostContext, XpertViewActionRequest, XpertViewQuery, XpertViewFileAccessRequest } from '@xpert-ai/contracts';
3
4
  import { type IXpertViewExtensionProvider, type XpertViewFileActionFile } from '@xpert-ai/plugin-sdk';
@@ -12,7 +13,8 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
12
13
  private readonly sharing;
13
14
  private readonly projects;
14
15
  private readonly archives;
15
- constructor(service: DashboardService, sharing: DashboardSharingService, projects: DashboardProjectsService, archives: DashboardArchiveService);
16
+ private readonly saves?;
17
+ constructor(service: DashboardService, sharing: DashboardSharingService, projects: DashboardProjectsService, archives: DashboardArchiveService, saves?: DashboardSaveRequests | undefined);
16
18
  supports(context: XpertResolvedViewHostContext): boolean;
17
19
  getViewManifests(context: XpertResolvedViewHostContext, slot: string): XpertExtensionViewManifest[];
18
20
  getRemoteComponentEntry(): Promise<{
@@ -71,8 +73,8 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
71
73
  allowedValues?: (string | number | boolean | null)[] | undefined;
72
74
  }[];
73
75
  charts: {
74
- title: string;
75
76
  type: "bar" | "line" | "area" | "pie" | "scatter";
77
+ title: string;
76
78
  id: string;
77
79
  datasetId: string;
78
80
  x: string;
@@ -198,6 +200,39 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
198
200
  };
199
201
  }>;
200
202
  executeViewAction(context: XpertResolvedViewHostContext, viewKey: string, action: string, request: XpertViewActionRequest): Promise<{
203
+ success: boolean;
204
+ data: {
205
+ artifactId: string;
206
+ draftId: string;
207
+ draftRevision: number;
208
+ baseVersionId: string | null;
209
+ status: "draft";
210
+ title: string;
211
+ } | {
212
+ cursor: string;
213
+ nextAction: string;
214
+ error?: string | undefined;
215
+ sourceFile?: {
216
+ path: string;
217
+ sha256: string;
218
+ } | undefined;
219
+ dashboard?: {
220
+ status: "draft";
221
+ title: string;
222
+ artifactId: string;
223
+ draftRevision: number;
224
+ draftId: string;
225
+ baseVersionId: string | null;
226
+ } | undefined;
227
+ requestId: string;
228
+ state: "failed" | "running" | "succeeded" | "queued";
229
+ stage: string;
230
+ completed: number;
231
+ total: number;
232
+ terminal: boolean;
233
+ };
234
+ refresh: boolean;
235
+ } | {
201
236
  success: boolean;
202
237
  data: import("@xpert-ai/contracts").AnalyticsDiscoveryResult;
203
238
  refresh: boolean;
@@ -354,8 +389,8 @@ export declare class DashboardViewProvider implements IXpertViewExtensionProvide
354
389
  allowedValues?: (string | number | boolean | null)[] | undefined;
355
390
  }[];
356
391
  charts: {
357
- title: string;
358
392
  type: "bar" | "line" | "area" | "pie" | "scatter";
393
+ title: string;
359
394
  id: string;
360
395
  datasetId: string;
361
396
  x: string;