@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.cjs CHANGED
@@ -29,6 +29,7 @@ __export(index_exports, {
29
29
  TOPOLO_RUNTIME_ENVIRONMENTS: () => TOPOLO_RUNTIME_ENVIRONMENTS,
30
30
  TOPOLO_WIDGET_CONTRACT_VERSION: () => TOPOLO_WIDGET_CONTRACT_VERSION,
31
31
  TOPOLO_WIDGET_ENDPOINT: () => TOPOLO_WIDGET_ENDPOINT,
32
+ TOPOLO_WIDGET_OUTPUT_SCHEMA: () => TOPOLO_WIDGET_OUTPUT_SCHEMA,
32
33
  TOPOLO_WIDGET_REFRESH_INTERVAL_MS: () => TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
33
34
  TopoloAuthError: () => TopoloAuthError,
34
35
  TopoloClient: () => TopoloClient,
@@ -66,6 +67,7 @@ __export(index_exports, {
66
67
  extractTopoloServiceErrorDetails: () => extractTopoloServiceErrorDetails,
67
68
  formatDirectoryApplicationIds: () => formatDirectoryApplicationIds,
68
69
  friendlyAppId: () => friendlyAppId,
70
+ isTopoloWidgetOutputSchema: () => isTopoloWidgetOutputSchema,
69
71
  listApplicationDirectory: () => listApplicationDirectory,
70
72
  parseTopoloActionUploadContract: () => parseTopoloActionUploadContract,
71
73
  preferExactActionMatches: () => preferExactActionMatches,
@@ -3321,7 +3323,7 @@ var TopoloOAuth = class {
3321
3323
  };
3322
3324
 
3323
3325
  // src/widget-validation.ts
3324
- var CONTRACT_VERSION = "2.0.0";
3326
+ var CONTRACT_VERSION = "3.0.0";
3325
3327
  var WIDGET_TYPES = ["stats", "chart", "activity", "notifications", "actions"];
3326
3328
  var CONTEXTS = ["personal", "organization", "workspace", "resource"];
3327
3329
  var SIZES = ["compact", "medium", "wide", "full"];
@@ -3332,6 +3334,9 @@ var AGGREGATIONS = ["last", "sum", "average", "minimum", "maximum", "count", "di
3332
3334
  var CHART_TYPES = ["line", "bar", "area", "pie", "donut"];
3333
3335
  var SEVERITIES = ["info", "warning", "error", "success"];
3334
3336
  var RECOVERY_MODES = ["not_applicable", "retry", "compensating_action"];
3337
+ var VISUALIZATION_FAMILIES = ["number", "line", "area", "bar", "donut", "table", "funnel", "timeline", "actions"];
3338
+ var WORKSPACE_SELECTOR_MODES = ["none", "optional", "required"];
3339
+ var DIMENSION_VALUE_TYPES = ["string", "number", "boolean", "date", "datetime"];
3335
3340
  var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
3336
3341
  function validateTopoloWidgetResponse(response) {
3337
3342
  const errors = [];
@@ -3382,6 +3387,7 @@ function validateWidget(value, path, errors) {
3382
3387
  validateActions(value.controls, `${path}.controls`, errors);
3383
3388
  validateLocalization(value.localization, `${path}.localization`, errors);
3384
3389
  validateAccessibility(value.accessibility, `${path}.accessibility`, errors);
3390
+ validateContribution(value.contribution, `${path}.contribution`, value, errors);
3385
3391
  switch (value.type) {
3386
3392
  case "stats":
3387
3393
  validateMetrics(value.metrics, `${path}.metrics`, errors, requiresContent(value));
@@ -3519,6 +3525,208 @@ function validateAccessibility(value, path, errors) {
3519
3525
  nonEmptyString(value.label, `${path}.label`, errors);
3520
3526
  optionalNonEmptyString(value.description, `${path}.description`, errors);
3521
3527
  }
3528
+ function validateContribution(value, path, widget, errors) {
3529
+ if (!recordValue(value, path, errors)) return;
3530
+ nonEmptyString(value.summary, `${path}.summary`, errors);
3531
+ if (recordValue(value.identity, `${path}.identity`, errors)) {
3532
+ exactValue(value.identity.iconSource, "catalog", `${path}.identity.iconSource`, errors);
3533
+ }
3534
+ if (recordValue(value.workspaceSelector, `${path}.workspaceSelector`, errors)) {
3535
+ enumValue(value.workspaceSelector.mode, WORKSPACE_SELECTOR_MODES, `${path}.workspaceSelector.mode`, errors);
3536
+ nonEmptyString(value.workspaceSelector.resourceType, `${path}.workspaceSelector.resourceType`, errors);
3537
+ nonEmptyString(value.workspaceSelector.label, `${path}.workspaceSelector.label`, errors);
3538
+ booleanValue(value.workspaceSelector.supportsMultiple, `${path}.workspaceSelector.supportsMultiple`, errors);
3539
+ if (value.workspaceSelector.mode === "required" && requiresContent(widget) && isRecord5(widget.resource) && typeof widget.resource.resourceId !== "string") {
3540
+ errors.push(`${path}.workspaceSelector requires a bound resourceId for ready or stale data`);
3541
+ }
3542
+ }
3543
+ const metricKeys = validateMetricDefinitions(value.metricDefinitions, `${path}.metricDefinitions`, errors);
3544
+ const seriesKeys = validateSeriesDefinitions(value.seriesDefinitions, `${path}.seriesDefinitions`, metricKeys, errors);
3545
+ const dimensionKeys = validateDimensionDefinitions(value.dimensions, `${path}.dimensions`, errors);
3546
+ const lifecycleKeys = validateLifecycleStages(value.lifecycleStages, `${path}.lifecycleStages`, errors);
3547
+ const eventKeys = validateEventDefinitions(value.events, `${path}.events`, errors);
3548
+ const actionKeys = widgetActionKeys(widget);
3549
+ validateDatasets(value.datasets, `${path}.datasets`, {
3550
+ metricKeys,
3551
+ seriesKeys,
3552
+ dimensionKeys,
3553
+ eventKeys,
3554
+ actionKeys
3555
+ }, errors);
3556
+ enumArray(
3557
+ value.compatibleVisualizations,
3558
+ VISUALIZATION_FAMILIES,
3559
+ `${path}.compatibleVisualizations`,
3560
+ errors,
3561
+ true
3562
+ );
3563
+ enumValue(value.defaultVisualization, VISUALIZATION_FAMILIES, `${path}.defaultVisualization`, errors);
3564
+ if (typeof value.defaultVisualization === "string" && Array.isArray(value.compatibleVisualizations) && !value.compatibleVisualizations.includes(value.defaultVisualization)) {
3565
+ errors.push(`${path}.defaultVisualization must be included in compatibleVisualizations`);
3566
+ }
3567
+ if (Array.isArray(value.compatibleVisualizations)) {
3568
+ for (const family of value.compatibleVisualizations) {
3569
+ if (family === "number" && metricKeys.size === 0) errors.push(`${path}.compatibleVisualizations number requires a metric definition`);
3570
+ if ((family === "line" || family === "area") && seriesKeys.size === 0) errors.push(`${path}.compatibleVisualizations ${family} requires a series definition`);
3571
+ if (family === "donut" && (metricKeys.size === 0 || dimensionKeys.size === 0)) errors.push(`${path}.compatibleVisualizations donut requires metric and dimension definitions`);
3572
+ if (family === "funnel" && lifecycleKeys.size < 2) errors.push(`${path}.compatibleVisualizations funnel requires at least two lifecycle stages`);
3573
+ if (family === "timeline" && eventKeys.size === 0) errors.push(`${path}.compatibleVisualizations timeline requires an event definition`);
3574
+ if (family === "actions" && actionKeys.size === 0) errors.push(`${path}.compatibleVisualizations actions requires a published action`);
3575
+ if (family === "bar" && metricKeys.size === 0 && seriesKeys.size === 0) errors.push(`${path}.compatibleVisualizations bar requires a metric or series definition`);
3576
+ }
3577
+ }
3578
+ validateRuntimeSemantics(widget, path, { metricKeys, seriesKeys, dimensionKeys }, errors);
3579
+ }
3580
+ function validateMetricDefinitions(value, path, errors) {
3581
+ const keys = /* @__PURE__ */ new Set();
3582
+ if (!arrayValue(value, path, errors)) return keys;
3583
+ value.forEach((definition, index) => {
3584
+ const itemPath = `${path}[${index}]`;
3585
+ if (!recordValue(definition, itemPath, errors)) return;
3586
+ nonEmptyString(definition.key, `${itemPath}.key`, errors);
3587
+ nonEmptyString(definition.label, `${itemPath}.label`, errors);
3588
+ nonEmptyString(definition.description, `${itemPath}.description`, errors);
3589
+ nonEmptyString(definition.unit, `${itemPath}.unit`, errors);
3590
+ enumValue(definition.semantic, SEMANTICS, `${itemPath}.semantic`, errors);
3591
+ enumValue(definition.aggregation, AGGREGATIONS, `${itemPath}.aggregation`, errors);
3592
+ addUniqueKey(definition.key, itemPath, keys, errors);
3593
+ });
3594
+ return keys;
3595
+ }
3596
+ function validateSeriesDefinitions(value, path, metricKeys, errors) {
3597
+ const keys = /* @__PURE__ */ new Set();
3598
+ if (!arrayValue(value, path, errors)) return keys;
3599
+ value.forEach((definition, index) => {
3600
+ const itemPath = `${path}[${index}]`;
3601
+ if (!recordValue(definition, itemPath, errors)) return;
3602
+ nonEmptyString(definition.key, `${itemPath}.key`, errors);
3603
+ nonEmptyString(definition.label, `${itemPath}.label`, errors);
3604
+ nonEmptyString(definition.metricKey, `${itemPath}.metricKey`, errors);
3605
+ nonEmptyString(definition.unit, `${itemPath}.unit`, errors);
3606
+ addUniqueKey(definition.key, itemPath, keys, errors);
3607
+ if (typeof definition.metricKey === "string" && !metricKeys.has(definition.metricKey)) {
3608
+ errors.push(`${itemPath}.metricKey must reference a declared metric definition`);
3609
+ }
3610
+ });
3611
+ return keys;
3612
+ }
3613
+ function validateDimensionDefinitions(value, path, errors) {
3614
+ const keys = /* @__PURE__ */ new Set();
3615
+ if (!arrayValue(value, path, errors)) return keys;
3616
+ value.forEach((definition, index) => {
3617
+ const itemPath = `${path}[${index}]`;
3618
+ if (!recordValue(definition, itemPath, errors)) return;
3619
+ nonEmptyString(definition.key, `${itemPath}.key`, errors);
3620
+ nonEmptyString(definition.label, `${itemPath}.label`, errors);
3621
+ nonEmptyString(definition.description, `${itemPath}.description`, errors);
3622
+ enumValue(definition.valueType, DIMENSION_VALUE_TYPES, `${itemPath}.valueType`, errors);
3623
+ addUniqueKey(definition.key, itemPath, keys, errors);
3624
+ });
3625
+ return keys;
3626
+ }
3627
+ function validateLifecycleStages(value, path, errors) {
3628
+ const keys = /* @__PURE__ */ new Set();
3629
+ const orders = /* @__PURE__ */ new Set();
3630
+ if (!arrayValue(value, path, errors)) return keys;
3631
+ value.forEach((stage, index) => {
3632
+ const itemPath = `${path}[${index}]`;
3633
+ if (!recordValue(stage, itemPath, errors)) return;
3634
+ nonEmptyString(stage.key, `${itemPath}.key`, errors);
3635
+ nonEmptyString(stage.label, `${itemPath}.label`, errors);
3636
+ nonNegativeInteger(stage.order, `${itemPath}.order`, errors);
3637
+ booleanValue(stage.terminal, `${itemPath}.terminal`, errors);
3638
+ addUniqueKey(stage.key, itemPath, keys, errors);
3639
+ if (typeof stage.order === "number") {
3640
+ if (orders.has(stage.order)) errors.push(`${itemPath}.order must be unique`);
3641
+ orders.add(stage.order);
3642
+ }
3643
+ });
3644
+ return keys;
3645
+ }
3646
+ function validateEventDefinitions(value, path, errors) {
3647
+ const keys = /* @__PURE__ */ new Set();
3648
+ if (!arrayValue(value, path, errors)) return keys;
3649
+ value.forEach((event, index) => {
3650
+ const itemPath = `${path}[${index}]`;
3651
+ if (!recordValue(event, itemPath, errors)) return;
3652
+ nonEmptyString(event.key, `${itemPath}.key`, errors);
3653
+ nonEmptyString(event.label, `${itemPath}.label`, errors);
3654
+ nonEmptyString(event.description, `${itemPath}.description`, errors);
3655
+ addUniqueKey(event.key, itemPath, keys, errors);
3656
+ });
3657
+ return keys;
3658
+ }
3659
+ function validateDatasets(value, path, references, errors) {
3660
+ const ids = /* @__PURE__ */ new Set();
3661
+ if (!arrayValue(value, path, errors, true)) return;
3662
+ value.forEach((dataset, index) => {
3663
+ const itemPath = `${path}[${index}]`;
3664
+ if (!recordValue(dataset, itemPath, errors)) return;
3665
+ nonEmptyString(dataset.id, `${itemPath}.id`, errors);
3666
+ nonEmptyString(dataset.label, `${itemPath}.label`, errors);
3667
+ nonEmptyString(dataset.description, `${itemPath}.description`, errors);
3668
+ nonEmptyString(dataset.grain, `${itemPath}.grain`, errors);
3669
+ addUniqueKey(dataset.id, itemPath, ids, errors);
3670
+ const fields = ["metricKeys", "seriesKeys", "dimensionKeys", "eventKeys", "actionIds"];
3671
+ for (const field of fields) stringArray2(dataset[field], `${itemPath}.${field}`, errors);
3672
+ const totalReferences = fields.reduce((total, field) => total + (Array.isArray(dataset[field]) ? dataset[field].length : 0), 0);
3673
+ if (totalReferences === 0) errors.push(`${itemPath} must reference at least one declared metric, series, dimension, event, or action`);
3674
+ validateReferences(dataset.metricKeys, references.metricKeys, `${itemPath}.metricKeys`, errors);
3675
+ validateReferences(dataset.seriesKeys, references.seriesKeys, `${itemPath}.seriesKeys`, errors);
3676
+ validateReferences(dataset.dimensionKeys, references.dimensionKeys, `${itemPath}.dimensionKeys`, errors);
3677
+ validateReferences(dataset.eventKeys, references.eventKeys, `${itemPath}.eventKeys`, errors);
3678
+ validateReferences(dataset.actionIds, references.actionKeys, `${itemPath}.actionIds`, errors);
3679
+ });
3680
+ }
3681
+ function validateReferences(value, known, path, errors) {
3682
+ if (!Array.isArray(value)) return;
3683
+ for (const reference of value) {
3684
+ if (typeof reference === "string" && !known.has(reference)) errors.push(`${path} references undeclared key ${reference}`);
3685
+ }
3686
+ }
3687
+ function widgetActionKeys(widget) {
3688
+ const keys = /* @__PURE__ */ new Set();
3689
+ for (const collection of [widget.controls, widget.actions]) {
3690
+ if (!Array.isArray(collection)) continue;
3691
+ for (const action of collection) if (isRecord5(action) && typeof action.id === "string") keys.add(action.id);
3692
+ }
3693
+ return keys;
3694
+ }
3695
+ function validateRuntimeSemantics(widget, contributionPath, declared, errors) {
3696
+ const metrics = Array.isArray(widget.metrics) ? widget.metrics : [];
3697
+ for (const metric of metrics) {
3698
+ if (!isRecord5(metric) || typeof metric.key !== "string") continue;
3699
+ if (!declared.metricKeys.has(metric.key)) errors.push(`${contributionPath}.metricDefinitions is missing runtime metric ${metric.key}`);
3700
+ validateRuntimeDimensions(metric.dimensions, declared.dimensionKeys, contributionPath, errors);
3701
+ if (isRecord5(metric.series)) {
3702
+ if (typeof metric.series.id === "string" && !declared.seriesKeys.has(metric.series.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${metric.series.id}`);
3703
+ validateSeriesDimensions(metric.series, declared.dimensionKeys, contributionPath, errors);
3704
+ }
3705
+ }
3706
+ const series2 = Array.isArray(widget.series) ? widget.series : [];
3707
+ for (const item of series2) {
3708
+ if (!isRecord5(item)) continue;
3709
+ if (typeof item.id === "string" && !declared.seriesKeys.has(item.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${item.id}`);
3710
+ validateSeriesDimensions(item, declared.dimensionKeys, contributionPath, errors);
3711
+ }
3712
+ }
3713
+ function validateSeriesDimensions(series2, known, path, errors) {
3714
+ if (!Array.isArray(series2.points)) return;
3715
+ for (const point of series2.points) if (isRecord5(point)) validateRuntimeDimensions(point.dimensions, known, path, errors);
3716
+ }
3717
+ function validateRuntimeDimensions(value, known, path, errors) {
3718
+ if (!Array.isArray(value)) return;
3719
+ for (const dimension2 of value) {
3720
+ if (isRecord5(dimension2) && typeof dimension2.key === "string" && !known.has(dimension2.key)) {
3721
+ errors.push(`${path}.dimensions is missing runtime dimension ${dimension2.key}`);
3722
+ }
3723
+ }
3724
+ }
3725
+ function addUniqueKey(value, path, keys, errors) {
3726
+ if (typeof value !== "string" || value.trim().length === 0) return;
3727
+ if (keys.has(value)) errors.push(`${path}.key must be unique`);
3728
+ keys.add(value);
3729
+ }
3522
3730
  function validateMetrics(value, path, errors, requireOne) {
3523
3731
  if (!arrayValue(value, path, errors, requireOne)) return;
3524
3732
  value.forEach((metric, index) => {
@@ -3547,12 +3755,12 @@ function validatePeriod(value, path, errors) {
3547
3755
  }
3548
3756
  function validateDimensions(value, path, errors) {
3549
3757
  if (!arrayValue(value, path, errors)) return;
3550
- value.forEach((dimension, index) => {
3758
+ value.forEach((dimension2, index) => {
3551
3759
  const itemPath = `${path}[${index}]`;
3552
- if (!recordValue(dimension, itemPath, errors)) return;
3553
- nonEmptyString(dimension.key, `${itemPath}.key`, errors);
3554
- nonEmptyString(dimension.label, `${itemPath}.label`, errors);
3555
- nonEmptyString(dimension.value, `${itemPath}.value`, errors);
3760
+ if (!recordValue(dimension2, itemPath, errors)) return;
3761
+ nonEmptyString(dimension2.key, `${itemPath}.key`, errors);
3762
+ nonEmptyString(dimension2.label, `${itemPath}.label`, errors);
3763
+ nonEmptyString(dimension2.value, `${itemPath}.value`, errors);
3556
3764
  });
3557
3765
  }
3558
3766
  function validateComparison(value, path, errors) {
@@ -3566,7 +3774,7 @@ function validateComparison(value, path, errors) {
3566
3774
  }
3567
3775
  function validateSeriesCollection(value, path, errors, requireOne) {
3568
3776
  if (!arrayValue(value, path, errors, requireOne)) return;
3569
- value.forEach((series, index) => validateSeries(series, `${path}[${index}]`, errors));
3777
+ value.forEach((series2, index) => validateSeries(series2, `${path}[${index}]`, errors));
3570
3778
  }
3571
3779
  function validateSeries(value, path, errors) {
3572
3780
  if (!recordValue(value, path, errors)) return;
@@ -3686,7 +3894,7 @@ function isRecord5(value) {
3686
3894
  // src/widgets.ts
3687
3895
  var TOPOLO_WIDGET_ENDPOINT = "/api/widget";
3688
3896
  var TOPOLO_WIDGET_REFRESH_INTERVAL_MS = 6e4;
3689
- var TOPOLO_WIDGET_CONTRACT_VERSION = "2.0.0";
3897
+ var TOPOLO_WIDGET_CONTRACT_VERSION = "3.0.0";
3690
3898
  function defineTopoloStatsWidget(widget) {
3691
3899
  return { ...widget, type: "stats" };
3692
3900
  }
@@ -3764,6 +3972,478 @@ function toIsoTimestamp(input) {
3764
3972
  return input ?? (/* @__PURE__ */ new Date()).toISOString();
3765
3973
  }
3766
3974
 
3975
+ // src/widget-output-schema.ts
3976
+ var nonEmptyString2 = { type: "string", minLength: 1 };
3977
+ var stringArray3 = { type: "array", items: nonEmptyString2 };
3978
+ var actionBinding = {
3979
+ type: "object",
3980
+ properties: {
3981
+ id: nonEmptyString2,
3982
+ label: nonEmptyString2,
3983
+ actionId: nonEmptyString2,
3984
+ resourceType: nonEmptyString2,
3985
+ resourceId: nonEmptyString2,
3986
+ input: { type: "object" },
3987
+ requiresConfirmation: { type: "boolean" },
3988
+ destructive: { type: "boolean" },
3989
+ effects: stringArray3,
3990
+ verification: stringArray3,
3991
+ recovery: {
3992
+ type: "object",
3993
+ properties: {
3994
+ mode: { enum: ["not_applicable", "retry", "compensating_action"] },
3995
+ actionId: nonEmptyString2,
3996
+ instructions: nonEmptyString2
3997
+ },
3998
+ required: ["mode", "instructions"],
3999
+ additionalProperties: false
4000
+ }
4001
+ },
4002
+ required: [
4003
+ "id",
4004
+ "label",
4005
+ "actionId",
4006
+ "resourceType",
4007
+ "input",
4008
+ "requiresConfirmation",
4009
+ "destructive",
4010
+ "effects",
4011
+ "verification",
4012
+ "recovery"
4013
+ ],
4014
+ additionalProperties: false
4015
+ };
4016
+ var dimension = {
4017
+ type: "object",
4018
+ properties: { key: nonEmptyString2, label: nonEmptyString2, value: nonEmptyString2 },
4019
+ required: ["key", "label", "value"],
4020
+ additionalProperties: false
4021
+ };
4022
+ var period = {
4023
+ type: "object",
4024
+ properties: {
4025
+ start: nonEmptyString2,
4026
+ end: nonEmptyString2,
4027
+ timezone: nonEmptyString2
4028
+ },
4029
+ required: ["start", "end", "timezone"],
4030
+ additionalProperties: false
4031
+ };
4032
+ var series = {
4033
+ type: "object",
4034
+ properties: {
4035
+ id: nonEmptyString2,
4036
+ label: nonEmptyString2,
4037
+ unit: nonEmptyString2,
4038
+ points: {
4039
+ type: "array",
4040
+ items: {
4041
+ type: "object",
4042
+ properties: {
4043
+ timestamp: nonEmptyString2,
4044
+ value: { type: "number" },
4045
+ dimensions: { type: "array", items: dimension }
4046
+ },
4047
+ required: ["timestamp", "value", "dimensions"],
4048
+ additionalProperties: false
4049
+ }
4050
+ }
4051
+ },
4052
+ required: ["id", "label", "unit", "points"],
4053
+ additionalProperties: false
4054
+ };
4055
+ var contribution = {
4056
+ type: "object",
4057
+ properties: {
4058
+ summary: nonEmptyString2,
4059
+ identity: {
4060
+ type: "object",
4061
+ properties: { iconSource: { const: "catalog" } },
4062
+ required: ["iconSource"],
4063
+ additionalProperties: false
4064
+ },
4065
+ workspaceSelector: {
4066
+ type: "object",
4067
+ properties: {
4068
+ mode: { enum: ["none", "optional", "required"] },
4069
+ resourceType: nonEmptyString2,
4070
+ label: nonEmptyString2,
4071
+ supportsMultiple: { type: "boolean" }
4072
+ },
4073
+ required: ["mode", "resourceType", "label", "supportsMultiple"],
4074
+ additionalProperties: false
4075
+ },
4076
+ metricDefinitions: {
4077
+ type: "array",
4078
+ items: {
4079
+ type: "object",
4080
+ properties: {
4081
+ key: nonEmptyString2,
4082
+ label: nonEmptyString2,
4083
+ description: nonEmptyString2,
4084
+ unit: nonEmptyString2,
4085
+ semantic: {
4086
+ enum: ["gauge", "counter", "rate", "duration", "currency", "percentage"]
4087
+ },
4088
+ aggregation: {
4089
+ enum: ["last", "sum", "average", "minimum", "maximum", "count", "distinct_count"]
4090
+ }
4091
+ },
4092
+ required: ["key", "label", "description", "unit", "semantic", "aggregation"],
4093
+ additionalProperties: false
4094
+ }
4095
+ },
4096
+ seriesDefinitions: {
4097
+ type: "array",
4098
+ items: {
4099
+ type: "object",
4100
+ properties: {
4101
+ key: nonEmptyString2,
4102
+ label: nonEmptyString2,
4103
+ metricKey: nonEmptyString2,
4104
+ unit: nonEmptyString2
4105
+ },
4106
+ required: ["key", "label", "metricKey", "unit"],
4107
+ additionalProperties: false
4108
+ }
4109
+ },
4110
+ dimensions: {
4111
+ type: "array",
4112
+ items: {
4113
+ type: "object",
4114
+ properties: {
4115
+ key: nonEmptyString2,
4116
+ label: nonEmptyString2,
4117
+ description: nonEmptyString2,
4118
+ valueType: { enum: ["string", "number", "boolean", "date", "datetime"] }
4119
+ },
4120
+ required: ["key", "label", "description", "valueType"],
4121
+ additionalProperties: false
4122
+ }
4123
+ },
4124
+ lifecycleStages: {
4125
+ type: "array",
4126
+ items: {
4127
+ type: "object",
4128
+ properties: {
4129
+ key: nonEmptyString2,
4130
+ label: nonEmptyString2,
4131
+ order: { type: "number" },
4132
+ terminal: { type: "boolean" }
4133
+ },
4134
+ required: ["key", "label", "order", "terminal"],
4135
+ additionalProperties: false
4136
+ }
4137
+ },
4138
+ events: {
4139
+ type: "array",
4140
+ items: {
4141
+ type: "object",
4142
+ properties: {
4143
+ key: nonEmptyString2,
4144
+ label: nonEmptyString2,
4145
+ description: nonEmptyString2
4146
+ },
4147
+ required: ["key", "label", "description"],
4148
+ additionalProperties: false
4149
+ }
4150
+ },
4151
+ datasets: {
4152
+ type: "array",
4153
+ minItems: 1,
4154
+ items: {
4155
+ type: "object",
4156
+ properties: {
4157
+ id: nonEmptyString2,
4158
+ label: nonEmptyString2,
4159
+ description: nonEmptyString2,
4160
+ grain: nonEmptyString2,
4161
+ metricKeys: stringArray3,
4162
+ seriesKeys: stringArray3,
4163
+ dimensionKeys: stringArray3,
4164
+ eventKeys: stringArray3,
4165
+ actionIds: stringArray3
4166
+ },
4167
+ required: [
4168
+ "id",
4169
+ "label",
4170
+ "description",
4171
+ "grain",
4172
+ "metricKeys",
4173
+ "seriesKeys",
4174
+ "dimensionKeys",
4175
+ "eventKeys",
4176
+ "actionIds"
4177
+ ],
4178
+ additionalProperties: false
4179
+ }
4180
+ },
4181
+ compatibleVisualizations: {
4182
+ type: "array",
4183
+ minItems: 1,
4184
+ items: {
4185
+ enum: ["number", "line", "area", "bar", "donut", "table", "funnel", "timeline", "actions"]
4186
+ }
4187
+ },
4188
+ defaultVisualization: {
4189
+ enum: ["number", "line", "area", "bar", "donut", "table", "funnel", "timeline", "actions"]
4190
+ }
4191
+ },
4192
+ required: [
4193
+ "summary",
4194
+ "identity",
4195
+ "workspaceSelector",
4196
+ "metricDefinitions",
4197
+ "seriesDefinitions",
4198
+ "dimensions",
4199
+ "lifecycleStages",
4200
+ "events",
4201
+ "datasets",
4202
+ "compatibleVisualizations",
4203
+ "defaultVisualization"
4204
+ ],
4205
+ additionalProperties: false
4206
+ };
4207
+ var widgetItem = {
4208
+ type: "object",
4209
+ properties: {
4210
+ id: nonEmptyString2,
4211
+ version: nonEmptyString2,
4212
+ appId: nonEmptyString2,
4213
+ serviceName: nonEmptyString2,
4214
+ lastUpdated: nonEmptyString2,
4215
+ type: { enum: ["stats", "chart", "activity", "notifications", "actions"] },
4216
+ presentation: {
4217
+ type: "object",
4218
+ properties: {
4219
+ title: nonEmptyString2,
4220
+ description: nonEmptyString2,
4221
+ contexts: {
4222
+ type: "array",
4223
+ minItems: 1,
4224
+ items: { enum: ["personal", "organization", "workspace", "resource"] }
4225
+ },
4226
+ supportedSizes: {
4227
+ type: "array",
4228
+ minItems: 1,
4229
+ items: { enum: ["compact", "medium", "wide", "full"] }
4230
+ },
4231
+ defaultSize: { enum: ["compact", "medium", "wide", "full"] }
4232
+ },
4233
+ required: ["title", "contexts", "supportedSizes", "defaultSize"],
4234
+ additionalProperties: false
4235
+ },
4236
+ resource: {
4237
+ type: "object",
4238
+ properties: {
4239
+ resourceType: nonEmptyString2,
4240
+ resourceId: nonEmptyString2,
4241
+ requiredPermissions: stringArray3
4242
+ },
4243
+ required: ["resourceType", "requiredPermissions"],
4244
+ additionalProperties: false
4245
+ },
4246
+ state: {
4247
+ type: "object",
4248
+ properties: {
4249
+ status: { enum: ["ready", "empty", "unavailable", "stale", "error"] },
4250
+ observedAt: nonEmptyString2,
4251
+ lastSuccessfulAt: nonEmptyString2,
4252
+ code: nonEmptyString2,
4253
+ message: nonEmptyString2
4254
+ },
4255
+ required: ["status", "observedAt"],
4256
+ additionalProperties: false
4257
+ },
4258
+ refresh: {
4259
+ type: "object",
4260
+ properties: {
4261
+ intervalSeconds: { type: "number" },
4262
+ timeoutSeconds: { type: "number" },
4263
+ maxAgeSeconds: { type: "number" },
4264
+ staleWhileRevalidateSeconds: { type: "number" }
4265
+ },
4266
+ required: ["intervalSeconds", "timeoutSeconds", "maxAgeSeconds", "staleWhileRevalidateSeconds"],
4267
+ additionalProperties: false
4268
+ },
4269
+ configuration: {
4270
+ type: "object",
4271
+ properties: {
4272
+ allowMultipleInstances: { type: "boolean" },
4273
+ fields: {
4274
+ type: "array",
4275
+ items: {
4276
+ type: "object",
4277
+ properties: {
4278
+ key: nonEmptyString2,
4279
+ label: nonEmptyString2,
4280
+ type: { enum: ["string", "number", "boolean", "select", "multi-select", "date-range"] },
4281
+ required: { type: "boolean" },
4282
+ options: {
4283
+ type: "array",
4284
+ items: {
4285
+ type: "object",
4286
+ properties: { value: nonEmptyString2, label: nonEmptyString2 },
4287
+ required: ["value", "label"],
4288
+ additionalProperties: false
4289
+ }
4290
+ }
4291
+ },
4292
+ required: ["key", "label", "type", "required"],
4293
+ additionalProperties: false
4294
+ }
4295
+ },
4296
+ defaults: { type: "object" }
4297
+ },
4298
+ required: ["allowMultipleInstances", "fields", "defaults"],
4299
+ additionalProperties: false
4300
+ },
4301
+ drilldowns: {
4302
+ type: "array",
4303
+ items: {
4304
+ type: "object",
4305
+ properties: { id: nonEmptyString2, label: nonEmptyString2, href: nonEmptyString2 },
4306
+ required: ["id", "label", "href"],
4307
+ additionalProperties: false
4308
+ }
4309
+ },
4310
+ controls: { type: "array", items: actionBinding },
4311
+ localization: {
4312
+ type: "object",
4313
+ properties: {
4314
+ locale: nonEmptyString2,
4315
+ titleKey: nonEmptyString2,
4316
+ descriptionKey: nonEmptyString2,
4317
+ title: nonEmptyString2,
4318
+ description: nonEmptyString2
4319
+ },
4320
+ required: ["locale", "titleKey"],
4321
+ additionalProperties: false
4322
+ },
4323
+ accessibility: {
4324
+ type: "object",
4325
+ properties: { label: nonEmptyString2, description: nonEmptyString2 },
4326
+ required: ["label"],
4327
+ additionalProperties: false
4328
+ },
4329
+ contribution,
4330
+ metrics: {
4331
+ type: "array",
4332
+ items: {
4333
+ type: "object",
4334
+ properties: {
4335
+ key: nonEmptyString2,
4336
+ label: nonEmptyString2,
4337
+ value: { type: "number" },
4338
+ unit: nonEmptyString2,
4339
+ semantic: {
4340
+ enum: ["gauge", "counter", "rate", "duration", "currency", "percentage"]
4341
+ },
4342
+ aggregation: {
4343
+ enum: ["last", "sum", "average", "minimum", "maximum", "count", "distinct_count"]
4344
+ },
4345
+ period,
4346
+ dimensions: { type: "array", items: dimension },
4347
+ series
4348
+ },
4349
+ required: ["key", "label", "value", "unit", "semantic", "aggregation", "period", "dimensions"],
4350
+ additionalProperties: false
4351
+ }
4352
+ },
4353
+ chartType: { enum: ["line", "bar", "area", "pie", "donut"] },
4354
+ series: { type: "array", items: series },
4355
+ activities: { type: "array", items: { type: "object" } },
4356
+ notifications: { type: "array", items: { type: "object" } },
4357
+ unreadCount: { type: "number" },
4358
+ actions: { type: "array", items: actionBinding }
4359
+ },
4360
+ required: [
4361
+ "id",
4362
+ "version",
4363
+ "appId",
4364
+ "serviceName",
4365
+ "lastUpdated",
4366
+ "type",
4367
+ "presentation",
4368
+ "resource",
4369
+ "state",
4370
+ "refresh",
4371
+ "configuration",
4372
+ "drilldowns",
4373
+ "controls",
4374
+ "localization",
4375
+ "accessibility",
4376
+ "contribution"
4377
+ ],
4378
+ allOf: [
4379
+ { if: { properties: { type: { const: "stats" } } }, then: { required: ["metrics"] } },
4380
+ { if: { properties: { type: { const: "chart" } } }, then: { required: ["chartType", "series"] } },
4381
+ { if: { properties: { type: { const: "activity" } } }, then: { required: ["activities"] } },
4382
+ {
4383
+ if: { properties: { type: { const: "notifications" } } },
4384
+ then: { required: ["notifications", "unreadCount"] }
4385
+ },
4386
+ { if: { properties: { type: { const: "actions" } } }, then: { required: ["actions"] } }
4387
+ ],
4388
+ additionalProperties: false
4389
+ };
4390
+ var TOPOLO_WIDGET_OUTPUT_SCHEMA = Object.freeze({
4391
+ $schema: "https://json-schema.org/draft/2020-12/schema",
4392
+ type: "object",
4393
+ properties: {
4394
+ success: { type: "boolean", const: true },
4395
+ contractVersion: { type: "string", const: "3.0.0" },
4396
+ appId: nonEmptyString2,
4397
+ serviceName: nonEmptyString2,
4398
+ generatedAt: nonEmptyString2,
4399
+ widgets: { type: "array", minItems: 1, items: widgetItem },
4400
+ workspace: {
4401
+ anyOf: [
4402
+ {
4403
+ type: "object",
4404
+ properties: {
4405
+ id: { anyOf: [{ type: "string" }, { type: "number" }] },
4406
+ name: nonEmptyString2
4407
+ },
4408
+ required: ["id", "name"],
4409
+ additionalProperties: true
4410
+ },
4411
+ { type: "null" }
4412
+ ]
4413
+ },
4414
+ workspaces: {
4415
+ type: "array",
4416
+ items: {
4417
+ type: "object",
4418
+ properties: {
4419
+ id: { anyOf: [{ type: "string" }, { type: "number" }] },
4420
+ name: nonEmptyString2,
4421
+ isDefault: { type: "boolean" }
4422
+ },
4423
+ required: ["id", "name"],
4424
+ additionalProperties: false
4425
+ }
4426
+ }
4427
+ },
4428
+ required: ["success", "contractVersion", "appId", "serviceName", "generatedAt", "widgets"],
4429
+ additionalProperties: false
4430
+ });
4431
+ function isTopoloWidgetOutputSchema(schema) {
4432
+ if (!isRecord6(schema) || !isRecord6(schema.properties)) return false;
4433
+ const properties = schema.properties;
4434
+ const widgetItemSchema = isRecord6(properties.widgets) && isRecord6(properties.widgets.items) ? properties.widgets.items : void 0;
4435
+ const widgetProperties = widgetItemSchema && isRecord6(widgetItemSchema.properties) ? widgetItemSchema.properties : void 0;
4436
+ const contributionSchema = widgetProperties && isRecord6(widgetProperties.contribution) ? widgetProperties.contribution : void 0;
4437
+ const contractVersion = isRecord6(properties.contractVersion) ? properties.contractVersion.const : void 0;
4438
+ 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");
4439
+ }
4440
+ function isRecord6(value) {
4441
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4442
+ }
4443
+ function hasRequired(schema, field) {
4444
+ return isRecord6(schema) && Array.isArray(schema.required) && schema.required.includes(field);
4445
+ }
4446
+
3767
4447
  // src/index.ts
3768
4448
  function createTopolo(options) {
3769
4449
  const client = new TopoloClient(options);
@@ -3785,6 +4465,7 @@ function createTopolo(options) {
3785
4465
  TOPOLO_RUNTIME_ENVIRONMENTS,
3786
4466
  TOPOLO_WIDGET_CONTRACT_VERSION,
3787
4467
  TOPOLO_WIDGET_ENDPOINT,
4468
+ TOPOLO_WIDGET_OUTPUT_SCHEMA,
3788
4469
  TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
3789
4470
  TopoloAuthError,
3790
4471
  TopoloClient,
@@ -3822,6 +4503,7 @@ function createTopolo(options) {
3822
4503
  extractTopoloServiceErrorDetails,
3823
4504
  formatDirectoryApplicationIds,
3824
4505
  friendlyAppId,
4506
+ isTopoloWidgetOutputSchema,
3825
4507
  listApplicationDirectory,
3826
4508
  parseTopoloActionUploadContract,
3827
4509
  preferExactActionMatches,