@topolo/sdk 0.13.0 → 0.14.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/dist/index.js CHANGED
@@ -3237,7 +3237,7 @@ var TopoloOAuth = class {
3237
3237
  };
3238
3238
 
3239
3239
  // src/widget-validation.ts
3240
- var CONTRACT_VERSION = "2.0.0";
3240
+ var CONTRACT_VERSION = "3.0.0";
3241
3241
  var WIDGET_TYPES = ["stats", "chart", "activity", "notifications", "actions"];
3242
3242
  var CONTEXTS = ["personal", "organization", "workspace", "resource"];
3243
3243
  var SIZES = ["compact", "medium", "wide", "full"];
@@ -3248,6 +3248,9 @@ var AGGREGATIONS = ["last", "sum", "average", "minimum", "maximum", "count", "di
3248
3248
  var CHART_TYPES = ["line", "bar", "area", "pie", "donut"];
3249
3249
  var SEVERITIES = ["info", "warning", "error", "success"];
3250
3250
  var RECOVERY_MODES = ["not_applicable", "retry", "compensating_action"];
3251
+ var VISUALIZATION_FAMILIES = ["number", "line", "area", "bar", "donut", "table", "funnel", "timeline", "actions"];
3252
+ var WORKSPACE_SELECTOR_MODES = ["none", "optional", "required"];
3253
+ var DIMENSION_VALUE_TYPES = ["string", "number", "boolean", "date", "datetime"];
3251
3254
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3252
3255
  function validateTopoloWidgetResponse(response) {
3253
3256
  const errors = [];
@@ -3298,6 +3301,7 @@ function validateWidget(value, path, errors) {
3298
3301
  validateActions(value.controls, `${path}.controls`, errors);
3299
3302
  validateLocalization(value.localization, `${path}.localization`, errors);
3300
3303
  validateAccessibility(value.accessibility, `${path}.accessibility`, errors);
3304
+ validateContribution(value.contribution, `${path}.contribution`, value, errors);
3301
3305
  switch (value.type) {
3302
3306
  case "stats":
3303
3307
  validateMetrics(value.metrics, `${path}.metrics`, errors, requiresContent(value));
@@ -3435,6 +3439,208 @@ function validateAccessibility(value, path, errors) {
3435
3439
  nonEmptyString(value.label, `${path}.label`, errors);
3436
3440
  optionalNonEmptyString(value.description, `${path}.description`, errors);
3437
3441
  }
3442
+ function validateContribution(value, path, widget, errors) {
3443
+ if (!recordValue(value, path, errors)) return;
3444
+ nonEmptyString(value.summary, `${path}.summary`, errors);
3445
+ if (recordValue(value.identity, `${path}.identity`, errors)) {
3446
+ exactValue(value.identity.iconSource, "catalog", `${path}.identity.iconSource`, errors);
3447
+ }
3448
+ if (recordValue(value.workspaceSelector, `${path}.workspaceSelector`, errors)) {
3449
+ enumValue(value.workspaceSelector.mode, WORKSPACE_SELECTOR_MODES, `${path}.workspaceSelector.mode`, errors);
3450
+ nonEmptyString(value.workspaceSelector.resourceType, `${path}.workspaceSelector.resourceType`, errors);
3451
+ nonEmptyString(value.workspaceSelector.label, `${path}.workspaceSelector.label`, errors);
3452
+ booleanValue(value.workspaceSelector.supportsMultiple, `${path}.workspaceSelector.supportsMultiple`, errors);
3453
+ if (value.workspaceSelector.mode === "required" && requiresContent(widget) && isRecord5(widget.resource) && typeof widget.resource.resourceId !== "string") {
3454
+ errors.push(`${path}.workspaceSelector requires a bound resourceId for ready or stale data`);
3455
+ }
3456
+ }
3457
+ const metricKeys = validateMetricDefinitions(value.metricDefinitions, `${path}.metricDefinitions`, errors);
3458
+ const seriesKeys = validateSeriesDefinitions(value.seriesDefinitions, `${path}.seriesDefinitions`, metricKeys, errors);
3459
+ const dimensionKeys = validateDimensionDefinitions(value.dimensions, `${path}.dimensions`, errors);
3460
+ const lifecycleKeys = validateLifecycleStages(value.lifecycleStages, `${path}.lifecycleStages`, errors);
3461
+ const eventKeys = validateEventDefinitions(value.events, `${path}.events`, errors);
3462
+ const actionKeys = widgetActionKeys(widget);
3463
+ validateDatasets(value.datasets, `${path}.datasets`, {
3464
+ metricKeys,
3465
+ seriesKeys,
3466
+ dimensionKeys,
3467
+ eventKeys,
3468
+ actionKeys
3469
+ }, errors);
3470
+ enumArray(
3471
+ value.compatibleVisualizations,
3472
+ VISUALIZATION_FAMILIES,
3473
+ `${path}.compatibleVisualizations`,
3474
+ errors,
3475
+ true
3476
+ );
3477
+ enumValue(value.defaultVisualization, VISUALIZATION_FAMILIES, `${path}.defaultVisualization`, errors);
3478
+ if (typeof value.defaultVisualization === "string" && Array.isArray(value.compatibleVisualizations) && !value.compatibleVisualizations.includes(value.defaultVisualization)) {
3479
+ errors.push(`${path}.defaultVisualization must be included in compatibleVisualizations`);
3480
+ }
3481
+ if (Array.isArray(value.compatibleVisualizations)) {
3482
+ for (const family of value.compatibleVisualizations) {
3483
+ if (family === "number" && metricKeys.size === 0) errors.push(`${path}.compatibleVisualizations number requires a metric definition`);
3484
+ if ((family === "line" || family === "area") && seriesKeys.size === 0) errors.push(`${path}.compatibleVisualizations ${family} requires a series definition`);
3485
+ if (family === "donut" && (metricKeys.size === 0 || dimensionKeys.size === 0)) errors.push(`${path}.compatibleVisualizations donut requires metric and dimension definitions`);
3486
+ if (family === "funnel" && lifecycleKeys.size < 2) errors.push(`${path}.compatibleVisualizations funnel requires at least two lifecycle stages`);
3487
+ if (family === "timeline" && eventKeys.size === 0) errors.push(`${path}.compatibleVisualizations timeline requires an event definition`);
3488
+ if (family === "actions" && actionKeys.size === 0) errors.push(`${path}.compatibleVisualizations actions requires a published action`);
3489
+ if (family === "bar" && metricKeys.size === 0 && seriesKeys.size === 0) errors.push(`${path}.compatibleVisualizations bar requires a metric or series definition`);
3490
+ }
3491
+ }
3492
+ validateRuntimeSemantics(widget, path, { metricKeys, seriesKeys, dimensionKeys }, errors);
3493
+ }
3494
+ function validateMetricDefinitions(value, path, errors) {
3495
+ const keys = /* @__PURE__ */ new Set();
3496
+ if (!arrayValue(value, path, errors)) return keys;
3497
+ value.forEach((definition, index) => {
3498
+ const itemPath = `${path}[${index}]`;
3499
+ if (!recordValue(definition, itemPath, errors)) return;
3500
+ nonEmptyString(definition.key, `${itemPath}.key`, errors);
3501
+ nonEmptyString(definition.label, `${itemPath}.label`, errors);
3502
+ nonEmptyString(definition.description, `${itemPath}.description`, errors);
3503
+ nonEmptyString(definition.unit, `${itemPath}.unit`, errors);
3504
+ enumValue(definition.semantic, SEMANTICS, `${itemPath}.semantic`, errors);
3505
+ enumValue(definition.aggregation, AGGREGATIONS, `${itemPath}.aggregation`, errors);
3506
+ addUniqueKey(definition.key, itemPath, keys, errors);
3507
+ });
3508
+ return keys;
3509
+ }
3510
+ function validateSeriesDefinitions(value, path, metricKeys, errors) {
3511
+ const keys = /* @__PURE__ */ new Set();
3512
+ if (!arrayValue(value, path, errors)) return keys;
3513
+ value.forEach((definition, index) => {
3514
+ const itemPath = `${path}[${index}]`;
3515
+ if (!recordValue(definition, itemPath, errors)) return;
3516
+ nonEmptyString(definition.key, `${itemPath}.key`, errors);
3517
+ nonEmptyString(definition.label, `${itemPath}.label`, errors);
3518
+ nonEmptyString(definition.metricKey, `${itemPath}.metricKey`, errors);
3519
+ nonEmptyString(definition.unit, `${itemPath}.unit`, errors);
3520
+ addUniqueKey(definition.key, itemPath, keys, errors);
3521
+ if (typeof definition.metricKey === "string" && !metricKeys.has(definition.metricKey)) {
3522
+ errors.push(`${itemPath}.metricKey must reference a declared metric definition`);
3523
+ }
3524
+ });
3525
+ return keys;
3526
+ }
3527
+ function validateDimensionDefinitions(value, path, errors) {
3528
+ const keys = /* @__PURE__ */ new Set();
3529
+ if (!arrayValue(value, path, errors)) return keys;
3530
+ value.forEach((definition, index) => {
3531
+ const itemPath = `${path}[${index}]`;
3532
+ if (!recordValue(definition, itemPath, errors)) return;
3533
+ nonEmptyString(definition.key, `${itemPath}.key`, errors);
3534
+ nonEmptyString(definition.label, `${itemPath}.label`, errors);
3535
+ nonEmptyString(definition.description, `${itemPath}.description`, errors);
3536
+ enumValue(definition.valueType, DIMENSION_VALUE_TYPES, `${itemPath}.valueType`, errors);
3537
+ addUniqueKey(definition.key, itemPath, keys, errors);
3538
+ });
3539
+ return keys;
3540
+ }
3541
+ function validateLifecycleStages(value, path, errors) {
3542
+ const keys = /* @__PURE__ */ new Set();
3543
+ const orders = /* @__PURE__ */ new Set();
3544
+ if (!arrayValue(value, path, errors)) return keys;
3545
+ value.forEach((stage, index) => {
3546
+ const itemPath = `${path}[${index}]`;
3547
+ if (!recordValue(stage, itemPath, errors)) return;
3548
+ nonEmptyString(stage.key, `${itemPath}.key`, errors);
3549
+ nonEmptyString(stage.label, `${itemPath}.label`, errors);
3550
+ nonNegativeInteger(stage.order, `${itemPath}.order`, errors);
3551
+ booleanValue(stage.terminal, `${itemPath}.terminal`, errors);
3552
+ addUniqueKey(stage.key, itemPath, keys, errors);
3553
+ if (typeof stage.order === "number") {
3554
+ if (orders.has(stage.order)) errors.push(`${itemPath}.order must be unique`);
3555
+ orders.add(stage.order);
3556
+ }
3557
+ });
3558
+ return keys;
3559
+ }
3560
+ function validateEventDefinitions(value, path, errors) {
3561
+ const keys = /* @__PURE__ */ new Set();
3562
+ if (!arrayValue(value, path, errors)) return keys;
3563
+ value.forEach((event, index) => {
3564
+ const itemPath = `${path}[${index}]`;
3565
+ if (!recordValue(event, itemPath, errors)) return;
3566
+ nonEmptyString(event.key, `${itemPath}.key`, errors);
3567
+ nonEmptyString(event.label, `${itemPath}.label`, errors);
3568
+ nonEmptyString(event.description, `${itemPath}.description`, errors);
3569
+ addUniqueKey(event.key, itemPath, keys, errors);
3570
+ });
3571
+ return keys;
3572
+ }
3573
+ function validateDatasets(value, path, references, errors) {
3574
+ const ids = /* @__PURE__ */ new Set();
3575
+ if (!arrayValue(value, path, errors, true)) return;
3576
+ value.forEach((dataset, index) => {
3577
+ const itemPath = `${path}[${index}]`;
3578
+ if (!recordValue(dataset, itemPath, errors)) return;
3579
+ nonEmptyString(dataset.id, `${itemPath}.id`, errors);
3580
+ nonEmptyString(dataset.label, `${itemPath}.label`, errors);
3581
+ nonEmptyString(dataset.description, `${itemPath}.description`, errors);
3582
+ nonEmptyString(dataset.grain, `${itemPath}.grain`, errors);
3583
+ addUniqueKey(dataset.id, itemPath, ids, errors);
3584
+ const fields = ["metricKeys", "seriesKeys", "dimensionKeys", "eventKeys", "actionIds"];
3585
+ for (const field of fields) stringArray2(dataset[field], `${itemPath}.${field}`, errors);
3586
+ const totalReferences = fields.reduce((total, field) => total + (Array.isArray(dataset[field]) ? dataset[field].length : 0), 0);
3587
+ if (totalReferences === 0) errors.push(`${itemPath} must reference at least one declared metric, series, dimension, event, or action`);
3588
+ validateReferences(dataset.metricKeys, references.metricKeys, `${itemPath}.metricKeys`, errors);
3589
+ validateReferences(dataset.seriesKeys, references.seriesKeys, `${itemPath}.seriesKeys`, errors);
3590
+ validateReferences(dataset.dimensionKeys, references.dimensionKeys, `${itemPath}.dimensionKeys`, errors);
3591
+ validateReferences(dataset.eventKeys, references.eventKeys, `${itemPath}.eventKeys`, errors);
3592
+ validateReferences(dataset.actionIds, references.actionKeys, `${itemPath}.actionIds`, errors);
3593
+ });
3594
+ }
3595
+ function validateReferences(value, known, path, errors) {
3596
+ if (!Array.isArray(value)) return;
3597
+ for (const reference of value) {
3598
+ if (typeof reference === "string" && !known.has(reference)) errors.push(`${path} references undeclared key ${reference}`);
3599
+ }
3600
+ }
3601
+ function widgetActionKeys(widget) {
3602
+ const keys = /* @__PURE__ */ new Set();
3603
+ for (const collection of [widget.controls, widget.actions]) {
3604
+ if (!Array.isArray(collection)) continue;
3605
+ for (const action of collection) if (isRecord5(action) && typeof action.id === "string") keys.add(action.id);
3606
+ }
3607
+ return keys;
3608
+ }
3609
+ function validateRuntimeSemantics(widget, contributionPath, declared, errors) {
3610
+ const metrics = Array.isArray(widget.metrics) ? widget.metrics : [];
3611
+ for (const metric of metrics) {
3612
+ if (!isRecord5(metric) || typeof metric.key !== "string") continue;
3613
+ if (!declared.metricKeys.has(metric.key)) errors.push(`${contributionPath}.metricDefinitions is missing runtime metric ${metric.key}`);
3614
+ validateRuntimeDimensions(metric.dimensions, declared.dimensionKeys, contributionPath, errors);
3615
+ if (isRecord5(metric.series)) {
3616
+ if (typeof metric.series.id === "string" && !declared.seriesKeys.has(metric.series.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${metric.series.id}`);
3617
+ validateSeriesDimensions(metric.series, declared.dimensionKeys, contributionPath, errors);
3618
+ }
3619
+ }
3620
+ const series2 = Array.isArray(widget.series) ? widget.series : [];
3621
+ for (const item of series2) {
3622
+ if (!isRecord5(item)) continue;
3623
+ if (typeof item.id === "string" && !declared.seriesKeys.has(item.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${item.id}`);
3624
+ validateSeriesDimensions(item, declared.dimensionKeys, contributionPath, errors);
3625
+ }
3626
+ }
3627
+ function validateSeriesDimensions(series2, known, path, errors) {
3628
+ if (!Array.isArray(series2.points)) return;
3629
+ for (const point of series2.points) if (isRecord5(point)) validateRuntimeDimensions(point.dimensions, known, path, errors);
3630
+ }
3631
+ function validateRuntimeDimensions(value, known, path, errors) {
3632
+ if (!Array.isArray(value)) return;
3633
+ for (const dimension2 of value) {
3634
+ if (isRecord5(dimension2) && typeof dimension2.key === "string" && !known.has(dimension2.key)) {
3635
+ errors.push(`${path}.dimensions is missing runtime dimension ${dimension2.key}`);
3636
+ }
3637
+ }
3638
+ }
3639
+ function addUniqueKey(value, path, keys, errors) {
3640
+ if (typeof value !== "string" || value.trim().length === 0) return;
3641
+ if (keys.has(value)) errors.push(`${path}.key must be unique`);
3642
+ keys.add(value);
3643
+ }
3438
3644
  function validateMetrics(value, path, errors, requireOne) {
3439
3645
  if (!arrayValue(value, path, errors, requireOne)) return;
3440
3646
  value.forEach((metric, index) => {
@@ -3463,12 +3669,12 @@ function validatePeriod(value, path, errors) {
3463
3669
  }
3464
3670
  function validateDimensions(value, path, errors) {
3465
3671
  if (!arrayValue(value, path, errors)) return;
3466
- value.forEach((dimension, index) => {
3672
+ value.forEach((dimension2, index) => {
3467
3673
  const itemPath = `${path}[${index}]`;
3468
- if (!recordValue(dimension, itemPath, errors)) return;
3469
- nonEmptyString(dimension.key, `${itemPath}.key`, errors);
3470
- nonEmptyString(dimension.label, `${itemPath}.label`, errors);
3471
- nonEmptyString(dimension.value, `${itemPath}.value`, errors);
3674
+ if (!recordValue(dimension2, itemPath, errors)) return;
3675
+ nonEmptyString(dimension2.key, `${itemPath}.key`, errors);
3676
+ nonEmptyString(dimension2.label, `${itemPath}.label`, errors);
3677
+ nonEmptyString(dimension2.value, `${itemPath}.value`, errors);
3472
3678
  });
3473
3679
  }
3474
3680
  function validateComparison(value, path, errors) {
@@ -3482,7 +3688,7 @@ function validateComparison(value, path, errors) {
3482
3688
  }
3483
3689
  function validateSeriesCollection(value, path, errors, requireOne) {
3484
3690
  if (!arrayValue(value, path, errors, requireOne)) return;
3485
- value.forEach((series, index) => validateSeries(series, `${path}[${index}]`, errors));
3691
+ value.forEach((series2, index) => validateSeries(series2, `${path}[${index}]`, errors));
3486
3692
  }
3487
3693
  function validateSeries(value, path, errors) {
3488
3694
  if (!recordValue(value, path, errors)) return;
@@ -3602,7 +3808,7 @@ function isRecord5(value) {
3602
3808
  // src/widgets.ts
3603
3809
  var TOPOLO_WIDGET_ENDPOINT = "/api/widget";
3604
3810
  var TOPOLO_WIDGET_REFRESH_INTERVAL_MS = 6e4;
3605
- var TOPOLO_WIDGET_CONTRACT_VERSION = "2.0.0";
3811
+ var TOPOLO_WIDGET_CONTRACT_VERSION = "3.0.0";
3606
3812
  function defineTopoloStatsWidget(widget) {
3607
3813
  return { ...widget, type: "stats" };
3608
3814
  }
@@ -3680,6 +3886,478 @@ function toIsoTimestamp(input) {
3680
3886
  return input ?? (/* @__PURE__ */ new Date()).toISOString();
3681
3887
  }
3682
3888
 
3889
+ // src/widget-output-schema.ts
3890
+ var nonEmptyString2 = { type: "string", minLength: 1 };
3891
+ var stringArray3 = { type: "array", items: nonEmptyString2 };
3892
+ var actionBinding = {
3893
+ type: "object",
3894
+ properties: {
3895
+ id: nonEmptyString2,
3896
+ label: nonEmptyString2,
3897
+ actionId: nonEmptyString2,
3898
+ resourceType: nonEmptyString2,
3899
+ resourceId: nonEmptyString2,
3900
+ input: { type: "object" },
3901
+ requiresConfirmation: { type: "boolean" },
3902
+ destructive: { type: "boolean" },
3903
+ effects: stringArray3,
3904
+ verification: stringArray3,
3905
+ recovery: {
3906
+ type: "object",
3907
+ properties: {
3908
+ mode: { enum: ["not_applicable", "retry", "compensating_action"] },
3909
+ actionId: nonEmptyString2,
3910
+ instructions: nonEmptyString2
3911
+ },
3912
+ required: ["mode", "instructions"],
3913
+ additionalProperties: false
3914
+ }
3915
+ },
3916
+ required: [
3917
+ "id",
3918
+ "label",
3919
+ "actionId",
3920
+ "resourceType",
3921
+ "input",
3922
+ "requiresConfirmation",
3923
+ "destructive",
3924
+ "effects",
3925
+ "verification",
3926
+ "recovery"
3927
+ ],
3928
+ additionalProperties: false
3929
+ };
3930
+ var dimension = {
3931
+ type: "object",
3932
+ properties: { key: nonEmptyString2, label: nonEmptyString2, value: nonEmptyString2 },
3933
+ required: ["key", "label", "value"],
3934
+ additionalProperties: false
3935
+ };
3936
+ var period = {
3937
+ type: "object",
3938
+ properties: {
3939
+ start: nonEmptyString2,
3940
+ end: nonEmptyString2,
3941
+ timezone: nonEmptyString2
3942
+ },
3943
+ required: ["start", "end", "timezone"],
3944
+ additionalProperties: false
3945
+ };
3946
+ var series = {
3947
+ type: "object",
3948
+ properties: {
3949
+ id: nonEmptyString2,
3950
+ label: nonEmptyString2,
3951
+ unit: nonEmptyString2,
3952
+ points: {
3953
+ type: "array",
3954
+ items: {
3955
+ type: "object",
3956
+ properties: {
3957
+ timestamp: nonEmptyString2,
3958
+ value: { type: "number" },
3959
+ dimensions: { type: "array", items: dimension }
3960
+ },
3961
+ required: ["timestamp", "value", "dimensions"],
3962
+ additionalProperties: false
3963
+ }
3964
+ }
3965
+ },
3966
+ required: ["id", "label", "unit", "points"],
3967
+ additionalProperties: false
3968
+ };
3969
+ var contribution = {
3970
+ type: "object",
3971
+ properties: {
3972
+ summary: nonEmptyString2,
3973
+ identity: {
3974
+ type: "object",
3975
+ properties: { iconSource: { const: "catalog" } },
3976
+ required: ["iconSource"],
3977
+ additionalProperties: false
3978
+ },
3979
+ workspaceSelector: {
3980
+ type: "object",
3981
+ properties: {
3982
+ mode: { enum: ["none", "optional", "required"] },
3983
+ resourceType: nonEmptyString2,
3984
+ label: nonEmptyString2,
3985
+ supportsMultiple: { type: "boolean" }
3986
+ },
3987
+ required: ["mode", "resourceType", "label", "supportsMultiple"],
3988
+ additionalProperties: false
3989
+ },
3990
+ metricDefinitions: {
3991
+ type: "array",
3992
+ items: {
3993
+ type: "object",
3994
+ properties: {
3995
+ key: nonEmptyString2,
3996
+ label: nonEmptyString2,
3997
+ description: nonEmptyString2,
3998
+ unit: nonEmptyString2,
3999
+ semantic: {
4000
+ enum: ["gauge", "counter", "rate", "duration", "currency", "percentage"]
4001
+ },
4002
+ aggregation: {
4003
+ enum: ["last", "sum", "average", "minimum", "maximum", "count", "distinct_count"]
4004
+ }
4005
+ },
4006
+ required: ["key", "label", "description", "unit", "semantic", "aggregation"],
4007
+ additionalProperties: false
4008
+ }
4009
+ },
4010
+ seriesDefinitions: {
4011
+ type: "array",
4012
+ items: {
4013
+ type: "object",
4014
+ properties: {
4015
+ key: nonEmptyString2,
4016
+ label: nonEmptyString2,
4017
+ metricKey: nonEmptyString2,
4018
+ unit: nonEmptyString2
4019
+ },
4020
+ required: ["key", "label", "metricKey", "unit"],
4021
+ additionalProperties: false
4022
+ }
4023
+ },
4024
+ dimensions: {
4025
+ type: "array",
4026
+ items: {
4027
+ type: "object",
4028
+ properties: {
4029
+ key: nonEmptyString2,
4030
+ label: nonEmptyString2,
4031
+ description: nonEmptyString2,
4032
+ valueType: { enum: ["string", "number", "boolean", "date", "datetime"] }
4033
+ },
4034
+ required: ["key", "label", "description", "valueType"],
4035
+ additionalProperties: false
4036
+ }
4037
+ },
4038
+ lifecycleStages: {
4039
+ type: "array",
4040
+ items: {
4041
+ type: "object",
4042
+ properties: {
4043
+ key: nonEmptyString2,
4044
+ label: nonEmptyString2,
4045
+ order: { type: "number" },
4046
+ terminal: { type: "boolean" }
4047
+ },
4048
+ required: ["key", "label", "order", "terminal"],
4049
+ additionalProperties: false
4050
+ }
4051
+ },
4052
+ events: {
4053
+ type: "array",
4054
+ items: {
4055
+ type: "object",
4056
+ properties: {
4057
+ key: nonEmptyString2,
4058
+ label: nonEmptyString2,
4059
+ description: nonEmptyString2
4060
+ },
4061
+ required: ["key", "label", "description"],
4062
+ additionalProperties: false
4063
+ }
4064
+ },
4065
+ datasets: {
4066
+ type: "array",
4067
+ minItems: 1,
4068
+ items: {
4069
+ type: "object",
4070
+ properties: {
4071
+ id: nonEmptyString2,
4072
+ label: nonEmptyString2,
4073
+ description: nonEmptyString2,
4074
+ grain: nonEmptyString2,
4075
+ metricKeys: stringArray3,
4076
+ seriesKeys: stringArray3,
4077
+ dimensionKeys: stringArray3,
4078
+ eventKeys: stringArray3,
4079
+ actionIds: stringArray3
4080
+ },
4081
+ required: [
4082
+ "id",
4083
+ "label",
4084
+ "description",
4085
+ "grain",
4086
+ "metricKeys",
4087
+ "seriesKeys",
4088
+ "dimensionKeys",
4089
+ "eventKeys",
4090
+ "actionIds"
4091
+ ],
4092
+ additionalProperties: false
4093
+ }
4094
+ },
4095
+ compatibleVisualizations: {
4096
+ type: "array",
4097
+ minItems: 1,
4098
+ items: {
4099
+ enum: ["number", "line", "area", "bar", "donut", "table", "funnel", "timeline", "actions"]
4100
+ }
4101
+ },
4102
+ defaultVisualization: {
4103
+ enum: ["number", "line", "area", "bar", "donut", "table", "funnel", "timeline", "actions"]
4104
+ }
4105
+ },
4106
+ required: [
4107
+ "summary",
4108
+ "identity",
4109
+ "workspaceSelector",
4110
+ "metricDefinitions",
4111
+ "seriesDefinitions",
4112
+ "dimensions",
4113
+ "lifecycleStages",
4114
+ "events",
4115
+ "datasets",
4116
+ "compatibleVisualizations",
4117
+ "defaultVisualization"
4118
+ ],
4119
+ additionalProperties: false
4120
+ };
4121
+ var widgetItem = {
4122
+ type: "object",
4123
+ properties: {
4124
+ id: nonEmptyString2,
4125
+ version: nonEmptyString2,
4126
+ appId: nonEmptyString2,
4127
+ serviceName: nonEmptyString2,
4128
+ lastUpdated: nonEmptyString2,
4129
+ type: { enum: ["stats", "chart", "activity", "notifications", "actions"] },
4130
+ presentation: {
4131
+ type: "object",
4132
+ properties: {
4133
+ title: nonEmptyString2,
4134
+ description: nonEmptyString2,
4135
+ contexts: {
4136
+ type: "array",
4137
+ minItems: 1,
4138
+ items: { enum: ["personal", "organization", "workspace", "resource"] }
4139
+ },
4140
+ supportedSizes: {
4141
+ type: "array",
4142
+ minItems: 1,
4143
+ items: { enum: ["compact", "medium", "wide", "full"] }
4144
+ },
4145
+ defaultSize: { enum: ["compact", "medium", "wide", "full"] }
4146
+ },
4147
+ required: ["title", "contexts", "supportedSizes", "defaultSize"],
4148
+ additionalProperties: false
4149
+ },
4150
+ resource: {
4151
+ type: "object",
4152
+ properties: {
4153
+ resourceType: nonEmptyString2,
4154
+ resourceId: nonEmptyString2,
4155
+ requiredPermissions: stringArray3
4156
+ },
4157
+ required: ["resourceType", "requiredPermissions"],
4158
+ additionalProperties: false
4159
+ },
4160
+ state: {
4161
+ type: "object",
4162
+ properties: {
4163
+ status: { enum: ["ready", "empty", "unavailable", "stale", "error"] },
4164
+ observedAt: nonEmptyString2,
4165
+ lastSuccessfulAt: nonEmptyString2,
4166
+ code: nonEmptyString2,
4167
+ message: nonEmptyString2
4168
+ },
4169
+ required: ["status", "observedAt"],
4170
+ additionalProperties: false
4171
+ },
4172
+ refresh: {
4173
+ type: "object",
4174
+ properties: {
4175
+ intervalSeconds: { type: "number" },
4176
+ timeoutSeconds: { type: "number" },
4177
+ maxAgeSeconds: { type: "number" },
4178
+ staleWhileRevalidateSeconds: { type: "number" }
4179
+ },
4180
+ required: ["intervalSeconds", "timeoutSeconds", "maxAgeSeconds", "staleWhileRevalidateSeconds"],
4181
+ additionalProperties: false
4182
+ },
4183
+ configuration: {
4184
+ type: "object",
4185
+ properties: {
4186
+ allowMultipleInstances: { type: "boolean" },
4187
+ fields: {
4188
+ type: "array",
4189
+ items: {
4190
+ type: "object",
4191
+ properties: {
4192
+ key: nonEmptyString2,
4193
+ label: nonEmptyString2,
4194
+ type: { enum: ["string", "number", "boolean", "select", "multi-select", "date-range"] },
4195
+ required: { type: "boolean" },
4196
+ options: {
4197
+ type: "array",
4198
+ items: {
4199
+ type: "object",
4200
+ properties: { value: nonEmptyString2, label: nonEmptyString2 },
4201
+ required: ["value", "label"],
4202
+ additionalProperties: false
4203
+ }
4204
+ }
4205
+ },
4206
+ required: ["key", "label", "type", "required"],
4207
+ additionalProperties: false
4208
+ }
4209
+ },
4210
+ defaults: { type: "object" }
4211
+ },
4212
+ required: ["allowMultipleInstances", "fields", "defaults"],
4213
+ additionalProperties: false
4214
+ },
4215
+ drilldowns: {
4216
+ type: "array",
4217
+ items: {
4218
+ type: "object",
4219
+ properties: { id: nonEmptyString2, label: nonEmptyString2, href: nonEmptyString2 },
4220
+ required: ["id", "label", "href"],
4221
+ additionalProperties: false
4222
+ }
4223
+ },
4224
+ controls: { type: "array", items: actionBinding },
4225
+ localization: {
4226
+ type: "object",
4227
+ properties: {
4228
+ locale: nonEmptyString2,
4229
+ titleKey: nonEmptyString2,
4230
+ descriptionKey: nonEmptyString2,
4231
+ title: nonEmptyString2,
4232
+ description: nonEmptyString2
4233
+ },
4234
+ required: ["locale", "titleKey"],
4235
+ additionalProperties: false
4236
+ },
4237
+ accessibility: {
4238
+ type: "object",
4239
+ properties: { label: nonEmptyString2, description: nonEmptyString2 },
4240
+ required: ["label"],
4241
+ additionalProperties: false
4242
+ },
4243
+ contribution,
4244
+ metrics: {
4245
+ type: "array",
4246
+ items: {
4247
+ type: "object",
4248
+ properties: {
4249
+ key: nonEmptyString2,
4250
+ label: nonEmptyString2,
4251
+ value: { type: "number" },
4252
+ unit: nonEmptyString2,
4253
+ semantic: {
4254
+ enum: ["gauge", "counter", "rate", "duration", "currency", "percentage"]
4255
+ },
4256
+ aggregation: {
4257
+ enum: ["last", "sum", "average", "minimum", "maximum", "count", "distinct_count"]
4258
+ },
4259
+ period,
4260
+ dimensions: { type: "array", items: dimension },
4261
+ series
4262
+ },
4263
+ required: ["key", "label", "value", "unit", "semantic", "aggregation", "period", "dimensions"],
4264
+ additionalProperties: false
4265
+ }
4266
+ },
4267
+ chartType: { enum: ["line", "bar", "area", "pie", "donut"] },
4268
+ series: { type: "array", items: series },
4269
+ activities: { type: "array", items: { type: "object" } },
4270
+ notifications: { type: "array", items: { type: "object" } },
4271
+ unreadCount: { type: "number" },
4272
+ actions: { type: "array", items: actionBinding }
4273
+ },
4274
+ required: [
4275
+ "id",
4276
+ "version",
4277
+ "appId",
4278
+ "serviceName",
4279
+ "lastUpdated",
4280
+ "type",
4281
+ "presentation",
4282
+ "resource",
4283
+ "state",
4284
+ "refresh",
4285
+ "configuration",
4286
+ "drilldowns",
4287
+ "controls",
4288
+ "localization",
4289
+ "accessibility",
4290
+ "contribution"
4291
+ ],
4292
+ allOf: [
4293
+ { if: { properties: { type: { const: "stats" } } }, then: { required: ["metrics"] } },
4294
+ { if: { properties: { type: { const: "chart" } } }, then: { required: ["chartType", "series"] } },
4295
+ { if: { properties: { type: { const: "activity" } } }, then: { required: ["activities"] } },
4296
+ {
4297
+ if: { properties: { type: { const: "notifications" } } },
4298
+ then: { required: ["notifications", "unreadCount"] }
4299
+ },
4300
+ { if: { properties: { type: { const: "actions" } } }, then: { required: ["actions"] } }
4301
+ ],
4302
+ additionalProperties: false
4303
+ };
4304
+ var TOPOLO_WIDGET_OUTPUT_SCHEMA = Object.freeze({
4305
+ $schema: "https://json-schema.org/draft/2020-12/schema",
4306
+ type: "object",
4307
+ properties: {
4308
+ success: { type: "boolean", const: true },
4309
+ contractVersion: { type: "string", const: "3.0.0" },
4310
+ appId: nonEmptyString2,
4311
+ serviceName: nonEmptyString2,
4312
+ generatedAt: nonEmptyString2,
4313
+ widgets: { type: "array", minItems: 1, items: widgetItem },
4314
+ workspace: {
4315
+ anyOf: [
4316
+ {
4317
+ type: "object",
4318
+ properties: {
4319
+ id: { anyOf: [{ type: "string" }, { type: "number" }] },
4320
+ name: nonEmptyString2
4321
+ },
4322
+ required: ["id", "name"],
4323
+ additionalProperties: true
4324
+ },
4325
+ { type: "null" }
4326
+ ]
4327
+ },
4328
+ workspaces: {
4329
+ type: "array",
4330
+ items: {
4331
+ type: "object",
4332
+ properties: {
4333
+ id: { anyOf: [{ type: "string" }, { type: "number" }] },
4334
+ name: nonEmptyString2,
4335
+ isDefault: { type: "boolean" }
4336
+ },
4337
+ required: ["id", "name"],
4338
+ additionalProperties: false
4339
+ }
4340
+ }
4341
+ },
4342
+ required: ["success", "contractVersion", "appId", "serviceName", "generatedAt", "widgets"],
4343
+ additionalProperties: false
4344
+ });
4345
+ function isTopoloWidgetOutputSchema(schema) {
4346
+ if (!isRecord6(schema) || !isRecord6(schema.properties)) return false;
4347
+ const properties = schema.properties;
4348
+ const widgetItemSchema = isRecord6(properties.widgets) && isRecord6(properties.widgets.items) ? properties.widgets.items : void 0;
4349
+ const widgetProperties = widgetItemSchema && isRecord6(widgetItemSchema.properties) ? widgetItemSchema.properties : void 0;
4350
+ const contributionSchema = widgetProperties && isRecord6(widgetProperties.contribution) ? widgetProperties.contribution : void 0;
4351
+ const contractVersion = isRecord6(properties.contractVersion) ? properties.contractVersion.const : void 0;
4352
+ return schema.type === "object" && contractVersion === "3.0.0" && isRecord6(properties.widgets) && properties.widgets.type === "array" && widgetItemSchema?.type === "object" && hasRequired(schema, "contractVersion") && hasRequired(schema, "widgets") && hasRequired(widgetItemSchema, "contribution") && hasRequired(contributionSchema, "datasets") && hasRequired(contributionSchema, "compatibleVisualizations") && hasRequired(contributionSchema, "defaultVisualization");
4353
+ }
4354
+ function isRecord6(value) {
4355
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4356
+ }
4357
+ function hasRequired(schema, field) {
4358
+ return isRecord6(schema) && Array.isArray(schema.required) && schema.required.includes(field);
4359
+ }
4360
+
3683
4361
  // src/index.ts
3684
4362
  function createTopolo(options) {
3685
4363
  const client = new TopoloClient(options);
@@ -3700,6 +4378,7 @@ export {
3700
4378
  TOPOLO_RUNTIME_ENVIRONMENTS,
3701
4379
  TOPOLO_WIDGET_CONTRACT_VERSION,
3702
4380
  TOPOLO_WIDGET_ENDPOINT,
4381
+ TOPOLO_WIDGET_OUTPUT_SCHEMA,
3703
4382
  TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
3704
4383
  TopoloAuthError,
3705
4384
  TopoloClient,
@@ -3737,6 +4416,7 @@ export {
3737
4416
  extractTopoloServiceErrorDetails,
3738
4417
  formatDirectoryApplicationIds,
3739
4418
  friendlyAppId,
4419
+ isTopoloWidgetOutputSchema,
3740
4420
  listApplicationDirectory,
3741
4421
  parseTopoloActionUploadContract,
3742
4422
  preferExactActionMatches,