@topolo/sdk 0.11.10 → 0.12.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.
- package/README.md +62 -10
- package/dist/index.cjs +449 -127
- package/dist/index.d.cts +184 -61
- package/dist/index.d.ts +184 -61
- package/dist/index.js +446 -125
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -27,6 +27,7 @@ __export(index_exports, {
|
|
|
27
27
|
P2PModule: () => P2PModule,
|
|
28
28
|
TOPOLO_ENVIRONMENT_SERVICE_URLS: () => TOPOLO_ENVIRONMENT_SERVICE_URLS,
|
|
29
29
|
TOPOLO_RUNTIME_ENVIRONMENTS: () => TOPOLO_RUNTIME_ENVIRONMENTS,
|
|
30
|
+
TOPOLO_WIDGET_CONTRACT_VERSION: () => TOPOLO_WIDGET_CONTRACT_VERSION,
|
|
30
31
|
TOPOLO_WIDGET_ENDPOINT: () => TOPOLO_WIDGET_ENDPOINT,
|
|
31
32
|
TOPOLO_WIDGET_REFRESH_INTERVAL_MS: () => TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
|
|
32
33
|
TopoloAuthError: () => TopoloAuthError,
|
|
@@ -53,10 +54,10 @@ __export(index_exports, {
|
|
|
53
54
|
createTopoloAgentExecutionContext: () => createTopoloAgentExecutionContext,
|
|
54
55
|
createTopoloWidgetError: () => createTopoloWidgetError,
|
|
55
56
|
createTopoloWidgetResponse: () => createTopoloWidgetResponse,
|
|
57
|
+
defineTopoloActionsWidget: () => defineTopoloActionsWidget,
|
|
58
|
+
defineTopoloActivityWidget: () => defineTopoloActivityWidget,
|
|
56
59
|
defineTopoloChartWidget: () => defineTopoloChartWidget,
|
|
57
60
|
defineTopoloNotificationsWidget: () => defineTopoloNotificationsWidget,
|
|
58
|
-
defineTopoloQuickActionsWidget: () => defineTopoloQuickActionsWidget,
|
|
59
|
-
defineTopoloRecentActivityWidget: () => defineTopoloRecentActivityWidget,
|
|
60
61
|
defineTopoloStatsWidget: () => defineTopoloStatsWidget,
|
|
61
62
|
digestTopoloActionCatalog: () => digestTopoloActionCatalog,
|
|
62
63
|
encodeTopoloActionBase64: () => encodeTopoloActionBase64,
|
|
@@ -1757,7 +1758,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
1757
1758
|
};
|
|
1758
1759
|
}
|
|
1759
1760
|
planResolvedAction(entry, input, options) {
|
|
1760
|
-
const
|
|
1761
|
+
const inputValidation = validateTopoloActionInput(entry.inputSchema, input);
|
|
1762
|
+
const resourceIssue = validateActionResource(entry, options.resource);
|
|
1763
|
+
const validation = resourceIssue ? { valid: false, errors: [...inputValidation.errors, resourceIssue] } : inputValidation;
|
|
1761
1764
|
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
1762
1765
|
const confirmationRequired = agentAccess === "confirm" || entry.requiresConfirmation || entry.destructive;
|
|
1763
1766
|
let request = null;
|
|
@@ -1816,6 +1819,13 @@ var TopoloClient = class _TopoloClient {
|
|
|
1816
1819
|
`Topolo action "${entry.actionId}" input is invalid: ${validation.errors.map((issue) => `${issue.path} ${issue.message}`).join("; ")}`
|
|
1817
1820
|
);
|
|
1818
1821
|
}
|
|
1822
|
+
const resourceIssue = validateActionResource(entry, options.resource);
|
|
1823
|
+
if (resourceIssue) {
|
|
1824
|
+
throw new TopoloSdkError(
|
|
1825
|
+
"invalid_action_resource",
|
|
1826
|
+
`Topolo action "${entry.actionId}" resource context is invalid: ${resourceIssue.message}`
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1819
1829
|
const mapped = mapActionInput(entry, input);
|
|
1820
1830
|
const service = entry.appSlug || entry.appId;
|
|
1821
1831
|
const result = await this.request({
|
|
@@ -1878,6 +1888,32 @@ var TopoloClient = class _TopoloClient {
|
|
|
1878
1888
|
};
|
|
1879
1889
|
}
|
|
1880
1890
|
};
|
|
1891
|
+
function validateActionResource(entry, resource) {
|
|
1892
|
+
const permissionName = entry.permissionName || entry.requiredPermission.split(".").at(-1) || "";
|
|
1893
|
+
if (!permissionName.startsWith("workspace:")) return null;
|
|
1894
|
+
if (!resource) {
|
|
1895
|
+
return {
|
|
1896
|
+
path: "$.resource",
|
|
1897
|
+
code: "required",
|
|
1898
|
+
message: "Select a workspace with the application resource catalog before planning or calling this action."
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
if (resource.resourceType !== "workspace") {
|
|
1902
|
+
return {
|
|
1903
|
+
path: "$.resource.resourceType",
|
|
1904
|
+
code: "const",
|
|
1905
|
+
message: 'This action requires resourceType "workspace".'
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
if (!resource.resourceId.trim()) {
|
|
1909
|
+
return {
|
|
1910
|
+
path: "$.resource.resourceId",
|
|
1911
|
+
code: "required",
|
|
1912
|
+
message: "This action requires a workspace resourceId."
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
return null;
|
|
1916
|
+
}
|
|
1881
1917
|
function resolveConfiguredAppId(slug, appIds) {
|
|
1882
1918
|
for (const serviceKey of serviceKeyCandidates2(slug)) {
|
|
1883
1919
|
const configured = appIds?.[serviceKey];
|
|
@@ -3267,23 +3303,385 @@ var TopoloOAuth = class {
|
|
|
3267
3303
|
}
|
|
3268
3304
|
};
|
|
3269
3305
|
|
|
3306
|
+
// src/widget-validation.ts
|
|
3307
|
+
var CONTRACT_VERSION = "2.0.0";
|
|
3308
|
+
var WIDGET_TYPES = ["stats", "chart", "activity", "notifications", "actions"];
|
|
3309
|
+
var CONTEXTS = ["personal", "organization", "workspace", "resource"];
|
|
3310
|
+
var SIZES = ["compact", "medium", "wide", "full"];
|
|
3311
|
+
var STATES = ["ready", "empty", "unavailable", "stale", "error"];
|
|
3312
|
+
var FIELD_TYPES = ["string", "number", "boolean", "select", "multi-select", "date-range"];
|
|
3313
|
+
var SEMANTICS = ["gauge", "counter", "rate", "duration", "currency", "percentage"];
|
|
3314
|
+
var AGGREGATIONS = ["last", "sum", "average", "minimum", "maximum", "count", "distinct_count"];
|
|
3315
|
+
var CHART_TYPES = ["line", "bar", "area", "pie", "donut"];
|
|
3316
|
+
var SEVERITIES = ["info", "warning", "error", "success"];
|
|
3317
|
+
var RECOVERY_MODES = ["not_applicable", "retry", "compensating_action"];
|
|
3318
|
+
var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
3319
|
+
function validateTopoloWidgetResponse(response) {
|
|
3320
|
+
const errors = [];
|
|
3321
|
+
if (!isRecord5(response)) return { ok: false, errors: ["response must be an object"] };
|
|
3322
|
+
exactValue(response.contractVersion, CONTRACT_VERSION, "contractVersion", errors);
|
|
3323
|
+
nonEmptyString(response.appId, "appId", errors);
|
|
3324
|
+
nonEmptyString(response.serviceName, "serviceName", errors);
|
|
3325
|
+
isoTimestamp(response.generatedAt, "generatedAt", errors);
|
|
3326
|
+
booleanValue(response.success, "success", errors);
|
|
3327
|
+
arrayValue(response.widgets, "widgets", errors);
|
|
3328
|
+
if (response.success === true) {
|
|
3329
|
+
if (Array.isArray(response.widgets) && response.widgets.length === 0) {
|
|
3330
|
+
errors.push("widgets must contain at least one complete widget; use widget state empty for intentional no-data");
|
|
3331
|
+
}
|
|
3332
|
+
if (response.error !== void 0) errors.push("error must be omitted when success is true");
|
|
3333
|
+
} else if (response.success === false) {
|
|
3334
|
+
if (Array.isArray(response.widgets) && response.widgets.length !== 0) {
|
|
3335
|
+
errors.push("widgets must be empty when success is false");
|
|
3336
|
+
}
|
|
3337
|
+
validateApiError(response.error, "error", errors);
|
|
3338
|
+
}
|
|
3339
|
+
if (Array.isArray(response.widgets)) {
|
|
3340
|
+
response.widgets.forEach((widget, index) => validateWidget(widget, `widgets[${index}]`, errors));
|
|
3341
|
+
}
|
|
3342
|
+
return { ok: errors.length === 0, errors };
|
|
3343
|
+
}
|
|
3344
|
+
function validateApiError(value, path, errors) {
|
|
3345
|
+
if (!recordValue(value, path, errors)) return;
|
|
3346
|
+
nonEmptyString(value.code, `${path}.code`, errors);
|
|
3347
|
+
nonEmptyString(value.message, `${path}.message`, errors);
|
|
3348
|
+
booleanValue(value.retryable, `${path}.retryable`, errors);
|
|
3349
|
+
optionalIsoTimestamp(value.retryAfter, `${path}.retryAfter`, errors);
|
|
3350
|
+
}
|
|
3351
|
+
function validateWidget(value, path, errors) {
|
|
3352
|
+
if (!recordValue(value, path, errors)) return;
|
|
3353
|
+
nonEmptyString(value.id, `${path}.id`, errors);
|
|
3354
|
+
patternString(value.version, SEMVER, `${path}.version`, errors);
|
|
3355
|
+
nonEmptyString(value.appId, `${path}.appId`, errors);
|
|
3356
|
+
nonEmptyString(value.serviceName, `${path}.serviceName`, errors);
|
|
3357
|
+
enumValue(value.type, WIDGET_TYPES, `${path}.type`, errors);
|
|
3358
|
+
isoTimestamp(value.lastUpdated, `${path}.lastUpdated`, errors);
|
|
3359
|
+
validatePresentation(value.presentation, `${path}.presentation`, errors);
|
|
3360
|
+
validateResource(value.resource, `${path}.resource`, errors);
|
|
3361
|
+
validateState(value.state, `${path}.state`, errors);
|
|
3362
|
+
validateRefresh(value.refresh, `${path}.refresh`, errors);
|
|
3363
|
+
validateConfiguration(value.configuration, `${path}.configuration`, errors);
|
|
3364
|
+
validateDrilldowns(value.drilldowns, `${path}.drilldowns`, errors);
|
|
3365
|
+
validateActions(value.controls, `${path}.controls`, errors);
|
|
3366
|
+
validateLocalization(value.localization, `${path}.localization`, errors);
|
|
3367
|
+
validateAccessibility(value.accessibility, `${path}.accessibility`, errors);
|
|
3368
|
+
switch (value.type) {
|
|
3369
|
+
case "stats":
|
|
3370
|
+
validateMetrics(value.metrics, `${path}.metrics`, errors, requiresContent(value));
|
|
3371
|
+
break;
|
|
3372
|
+
case "chart":
|
|
3373
|
+
enumValue(value.chartType, CHART_TYPES, `${path}.chartType`, errors);
|
|
3374
|
+
validateSeriesCollection(value.series, `${path}.series`, errors, requiresContent(value));
|
|
3375
|
+
break;
|
|
3376
|
+
case "activity":
|
|
3377
|
+
validateActivities(value.activities, `${path}.activities`, errors, requiresContent(value));
|
|
3378
|
+
break;
|
|
3379
|
+
case "notifications":
|
|
3380
|
+
validateNotifications(value.notifications, `${path}.notifications`, errors, requiresContent(value));
|
|
3381
|
+
nonNegativeInteger(value.unreadCount, `${path}.unreadCount`, errors);
|
|
3382
|
+
break;
|
|
3383
|
+
case "actions":
|
|
3384
|
+
validateActions(value.actions, `${path}.actions`, errors, requiresContent(value));
|
|
3385
|
+
break;
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
function validatePresentation(value, path, errors) {
|
|
3389
|
+
if (!recordValue(value, path, errors)) return;
|
|
3390
|
+
nonEmptyString(value.title, `${path}.title`, errors);
|
|
3391
|
+
optionalNonEmptyString(value.description, `${path}.description`, errors);
|
|
3392
|
+
enumArray(value.contexts, CONTEXTS, `${path}.contexts`, errors, true);
|
|
3393
|
+
enumArray(value.supportedSizes, SIZES, `${path}.supportedSizes`, errors, true);
|
|
3394
|
+
enumValue(value.defaultSize, SIZES, `${path}.defaultSize`, errors);
|
|
3395
|
+
if (typeof value.defaultSize === "string" && Array.isArray(value.supportedSizes) && !value.supportedSizes.includes(value.defaultSize)) {
|
|
3396
|
+
errors.push(`${path}.defaultSize must be included in supportedSizes`);
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
function validateResource(value, path, errors) {
|
|
3400
|
+
if (!recordValue(value, path, errors)) return;
|
|
3401
|
+
nonEmptyString(value.resourceType, `${path}.resourceType`, errors);
|
|
3402
|
+
optionalNonEmptyString(value.resourceId, `${path}.resourceId`, errors);
|
|
3403
|
+
stringArray2(value.requiredPermissions, `${path}.requiredPermissions`, errors, true);
|
|
3404
|
+
}
|
|
3405
|
+
function validateState(value, path, errors) {
|
|
3406
|
+
if (!recordValue(value, path, errors)) return;
|
|
3407
|
+
enumValue(value.status, STATES, `${path}.status`, errors);
|
|
3408
|
+
isoTimestamp(value.observedAt, `${path}.observedAt`, errors);
|
|
3409
|
+
optionalIsoTimestamp(value.lastSuccessfulAt, `${path}.lastSuccessfulAt`, errors);
|
|
3410
|
+
optionalNonEmptyString(value.code, `${path}.code`, errors);
|
|
3411
|
+
optionalNonEmptyString(value.message, `${path}.message`, errors);
|
|
3412
|
+
if (["empty", "unavailable", "error"].includes(String(value.status))) {
|
|
3413
|
+
nonEmptyString(value.message, `${path}.message`, errors);
|
|
3414
|
+
}
|
|
3415
|
+
if (["unavailable", "error"].includes(String(value.status))) {
|
|
3416
|
+
nonEmptyString(value.code, `${path}.code`, errors);
|
|
3417
|
+
}
|
|
3418
|
+
if (value.status === "stale") isoTimestamp(value.lastSuccessfulAt, `${path}.lastSuccessfulAt`, errors);
|
|
3419
|
+
}
|
|
3420
|
+
function validateRefresh(value, path, errors) {
|
|
3421
|
+
if (!recordValue(value, path, errors)) return;
|
|
3422
|
+
positiveInteger(value.intervalSeconds, `${path}.intervalSeconds`, errors);
|
|
3423
|
+
positiveInteger(value.timeoutSeconds, `${path}.timeoutSeconds`, errors);
|
|
3424
|
+
positiveInteger(value.maxAgeSeconds, `${path}.maxAgeSeconds`, errors);
|
|
3425
|
+
nonNegativeInteger(value.staleWhileRevalidateSeconds, `${path}.staleWhileRevalidateSeconds`, errors);
|
|
3426
|
+
if (isPositiveNumber(value.timeoutSeconds) && isPositiveNumber(value.intervalSeconds) && value.timeoutSeconds > value.intervalSeconds) {
|
|
3427
|
+
errors.push(`${path}.timeoutSeconds must not exceed intervalSeconds`);
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
function validateConfiguration(value, path, errors) {
|
|
3431
|
+
if (!recordValue(value, path, errors)) return;
|
|
3432
|
+
booleanValue(value.allowMultipleInstances, `${path}.allowMultipleInstances`, errors);
|
|
3433
|
+
recordValue(value.defaults, `${path}.defaults`, errors);
|
|
3434
|
+
if (!arrayValue(value.fields, `${path}.fields`, errors)) return;
|
|
3435
|
+
value.fields.forEach((field, index) => {
|
|
3436
|
+
const fieldPath = `${path}.fields[${index}]`;
|
|
3437
|
+
if (!recordValue(field, fieldPath, errors)) return;
|
|
3438
|
+
nonEmptyString(field.key, `${fieldPath}.key`, errors);
|
|
3439
|
+
nonEmptyString(field.label, `${fieldPath}.label`, errors);
|
|
3440
|
+
enumValue(field.type, FIELD_TYPES, `${fieldPath}.type`, errors);
|
|
3441
|
+
booleanValue(field.required, `${fieldPath}.required`, errors);
|
|
3442
|
+
if (field.type === "select" || field.type === "multi-select") {
|
|
3443
|
+
if (!arrayValue(field.options, `${fieldPath}.options`, errors, true)) return;
|
|
3444
|
+
field.options.forEach((option, optionIndex) => {
|
|
3445
|
+
const optionPath = `${fieldPath}.options[${optionIndex}]`;
|
|
3446
|
+
if (!recordValue(option, optionPath, errors)) return;
|
|
3447
|
+
nonEmptyString(option.value, `${optionPath}.value`, errors);
|
|
3448
|
+
nonEmptyString(option.label, `${optionPath}.label`, errors);
|
|
3449
|
+
});
|
|
3450
|
+
} else if (field.options !== void 0) {
|
|
3451
|
+
errors.push(`${fieldPath}.options is only valid for select fields`);
|
|
3452
|
+
}
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
function validateDrilldowns(value, path, errors) {
|
|
3456
|
+
if (!arrayValue(value, path, errors)) return;
|
|
3457
|
+
value.forEach((drilldown, index) => {
|
|
3458
|
+
const itemPath = `${path}[${index}]`;
|
|
3459
|
+
if (!recordValue(drilldown, itemPath, errors)) return;
|
|
3460
|
+
nonEmptyString(drilldown.id, `${itemPath}.id`, errors);
|
|
3461
|
+
nonEmptyString(drilldown.label, `${itemPath}.label`, errors);
|
|
3462
|
+
nonEmptyString(drilldown.href, `${itemPath}.href`, errors);
|
|
3463
|
+
});
|
|
3464
|
+
}
|
|
3465
|
+
function validateActions(value, path, errors, requireOne = false) {
|
|
3466
|
+
if (!arrayValue(value, path, errors, requireOne)) return;
|
|
3467
|
+
value.forEach((action, index) => {
|
|
3468
|
+
const itemPath = `${path}[${index}]`;
|
|
3469
|
+
if (!recordValue(action, itemPath, errors)) return;
|
|
3470
|
+
nonEmptyString(action.id, `${itemPath}.id`, errors);
|
|
3471
|
+
nonEmptyString(action.label, `${itemPath}.label`, errors);
|
|
3472
|
+
nonEmptyString(action.actionId, `${itemPath}.actionId`, errors);
|
|
3473
|
+
nonEmptyString(action.resourceType, `${itemPath}.resourceType`, errors);
|
|
3474
|
+
optionalNonEmptyString(action.resourceId, `${itemPath}.resourceId`, errors);
|
|
3475
|
+
recordValue(action.input, `${itemPath}.input`, errors);
|
|
3476
|
+
booleanValue(action.requiresConfirmation, `${itemPath}.requiresConfirmation`, errors);
|
|
3477
|
+
booleanValue(action.destructive, `${itemPath}.destructive`, errors);
|
|
3478
|
+
stringArray2(action.effects, `${itemPath}.effects`, errors, true);
|
|
3479
|
+
stringArray2(action.verification, `${itemPath}.verification`, errors, true);
|
|
3480
|
+
validateRecovery(action.recovery, `${itemPath}.recovery`, errors);
|
|
3481
|
+
});
|
|
3482
|
+
}
|
|
3483
|
+
function validateRecovery(value, path, errors) {
|
|
3484
|
+
if (!recordValue(value, path, errors)) return;
|
|
3485
|
+
enumValue(value.mode, RECOVERY_MODES, `${path}.mode`, errors);
|
|
3486
|
+
optionalNonEmptyString(value.actionId, `${path}.actionId`, errors);
|
|
3487
|
+
nonEmptyString(value.instructions, `${path}.instructions`, errors);
|
|
3488
|
+
if (value.mode === "compensating_action" && value.actionId === void 0) {
|
|
3489
|
+
errors.push(`${path}.actionId is required for compensating_action`);
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
function validateLocalization(value, path, errors) {
|
|
3493
|
+
if (!recordValue(value, path, errors)) return;
|
|
3494
|
+
nonEmptyString(value.locale, `${path}.locale`, errors);
|
|
3495
|
+
nonEmptyString(value.titleKey, `${path}.titleKey`, errors);
|
|
3496
|
+
optionalNonEmptyString(value.descriptionKey, `${path}.descriptionKey`, errors);
|
|
3497
|
+
}
|
|
3498
|
+
function validateAccessibility(value, path, errors) {
|
|
3499
|
+
if (!recordValue(value, path, errors)) return;
|
|
3500
|
+
nonEmptyString(value.label, `${path}.label`, errors);
|
|
3501
|
+
optionalNonEmptyString(value.description, `${path}.description`, errors);
|
|
3502
|
+
}
|
|
3503
|
+
function validateMetrics(value, path, errors, requireOne) {
|
|
3504
|
+
if (!arrayValue(value, path, errors, requireOne)) return;
|
|
3505
|
+
value.forEach((metric, index) => {
|
|
3506
|
+
const itemPath = `${path}[${index}]`;
|
|
3507
|
+
if (!recordValue(metric, itemPath, errors)) return;
|
|
3508
|
+
nonEmptyString(metric.key, `${itemPath}.key`, errors);
|
|
3509
|
+
nonEmptyString(metric.label, `${itemPath}.label`, errors);
|
|
3510
|
+
finiteNumber(metric.value, `${itemPath}.value`, errors);
|
|
3511
|
+
nonEmptyString(metric.unit, `${itemPath}.unit`, errors);
|
|
3512
|
+
enumValue(metric.semantic, SEMANTICS, `${itemPath}.semantic`, errors);
|
|
3513
|
+
enumValue(metric.aggregation, AGGREGATIONS, `${itemPath}.aggregation`, errors);
|
|
3514
|
+
validatePeriod(metric.period, `${itemPath}.period`, errors);
|
|
3515
|
+
validateDimensions(metric.dimensions, `${itemPath}.dimensions`, errors);
|
|
3516
|
+
if (metric.comparison !== void 0) validateComparison(metric.comparison, `${itemPath}.comparison`, errors);
|
|
3517
|
+
if (metric.series !== void 0) validateSeries(metric.series, `${itemPath}.series`, errors);
|
|
3518
|
+
});
|
|
3519
|
+
}
|
|
3520
|
+
function validatePeriod(value, path, errors) {
|
|
3521
|
+
if (!recordValue(value, path, errors)) return;
|
|
3522
|
+
isoTimestamp(value.start, `${path}.start`, errors);
|
|
3523
|
+
isoTimestamp(value.end, `${path}.end`, errors);
|
|
3524
|
+
nonEmptyString(value.timezone, `${path}.timezone`, errors);
|
|
3525
|
+
if (isIsoTimestamp(value.start) && isIsoTimestamp(value.end) && Date.parse(value.start) > Date.parse(value.end)) {
|
|
3526
|
+
errors.push(`${path}.start must not be after end`);
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
function validateDimensions(value, path, errors) {
|
|
3530
|
+
if (!arrayValue(value, path, errors)) return;
|
|
3531
|
+
value.forEach((dimension, index) => {
|
|
3532
|
+
const itemPath = `${path}[${index}]`;
|
|
3533
|
+
if (!recordValue(dimension, itemPath, errors)) return;
|
|
3534
|
+
nonEmptyString(dimension.key, `${itemPath}.key`, errors);
|
|
3535
|
+
nonEmptyString(dimension.label, `${itemPath}.label`, errors);
|
|
3536
|
+
nonEmptyString(dimension.value, `${itemPath}.value`, errors);
|
|
3537
|
+
});
|
|
3538
|
+
}
|
|
3539
|
+
function validateComparison(value, path, errors) {
|
|
3540
|
+
if (!recordValue(value, path, errors)) return;
|
|
3541
|
+
finiteNumber(value.value, `${path}.value`, errors);
|
|
3542
|
+
finiteNumber(value.absoluteChange, `${path}.absoluteChange`, errors);
|
|
3543
|
+
optionalFiniteNumber(value.percentageChange, `${path}.percentageChange`, errors);
|
|
3544
|
+
enumValue(value.direction, ["up", "down", "flat"], `${path}.direction`, errors);
|
|
3545
|
+
nonEmptyString(value.label, `${path}.label`, errors);
|
|
3546
|
+
validatePeriod(value.period, `${path}.period`, errors);
|
|
3547
|
+
}
|
|
3548
|
+
function validateSeriesCollection(value, path, errors, requireOne) {
|
|
3549
|
+
if (!arrayValue(value, path, errors, requireOne)) return;
|
|
3550
|
+
value.forEach((series, index) => validateSeries(series, `${path}[${index}]`, errors));
|
|
3551
|
+
}
|
|
3552
|
+
function validateSeries(value, path, errors) {
|
|
3553
|
+
if (!recordValue(value, path, errors)) return;
|
|
3554
|
+
nonEmptyString(value.id, `${path}.id`, errors);
|
|
3555
|
+
nonEmptyString(value.label, `${path}.label`, errors);
|
|
3556
|
+
nonEmptyString(value.unit, `${path}.unit`, errors);
|
|
3557
|
+
if (!arrayValue(value.points, `${path}.points`, errors, true)) return;
|
|
3558
|
+
value.points.forEach((point, index) => {
|
|
3559
|
+
const itemPath = `${path}.points[${index}]`;
|
|
3560
|
+
if (!recordValue(point, itemPath, errors)) return;
|
|
3561
|
+
isoTimestamp(point.timestamp, `${itemPath}.timestamp`, errors);
|
|
3562
|
+
finiteNumber(point.value, `${itemPath}.value`, errors);
|
|
3563
|
+
validateDimensions(point.dimensions, `${itemPath}.dimensions`, errors);
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
function validateActivities(value, path, errors, requireOne) {
|
|
3567
|
+
if (!arrayValue(value, path, errors, requireOne)) return;
|
|
3568
|
+
value.forEach((activity, index) => {
|
|
3569
|
+
const itemPath = `${path}[${index}]`;
|
|
3570
|
+
if (!recordValue(activity, itemPath, errors)) return;
|
|
3571
|
+
nonEmptyString(activity.id, `${itemPath}.id`, errors);
|
|
3572
|
+
nonEmptyString(activity.title, `${itemPath}.title`, errors);
|
|
3573
|
+
optionalNonEmptyString(activity.description, `${itemPath}.description`, errors);
|
|
3574
|
+
isoTimestamp(activity.timestamp, `${itemPath}.timestamp`, errors);
|
|
3575
|
+
optionalNonEmptyString(activity.drilldownId, `${itemPath}.drilldownId`, errors);
|
|
3576
|
+
optionalNonEmptyString(activity.actionId, `${itemPath}.actionId`, errors);
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3579
|
+
function validateNotifications(value, path, errors, requireOne) {
|
|
3580
|
+
if (!arrayValue(value, path, errors, requireOne)) return;
|
|
3581
|
+
value.forEach((notification, index) => {
|
|
3582
|
+
const itemPath = `${path}[${index}]`;
|
|
3583
|
+
if (!recordValue(notification, itemPath, errors)) return;
|
|
3584
|
+
nonEmptyString(notification.id, `${itemPath}.id`, errors);
|
|
3585
|
+
nonEmptyString(notification.title, `${itemPath}.title`, errors);
|
|
3586
|
+
nonEmptyString(notification.message, `${itemPath}.message`, errors);
|
|
3587
|
+
enumValue(notification.severity, SEVERITIES, `${itemPath}.severity`, errors);
|
|
3588
|
+
isoTimestamp(notification.timestamp, `${itemPath}.timestamp`, errors);
|
|
3589
|
+
booleanValue(notification.read, `${itemPath}.read`, errors);
|
|
3590
|
+
optionalNonEmptyString(notification.drilldownId, `${itemPath}.drilldownId`, errors);
|
|
3591
|
+
optionalNonEmptyString(notification.actionId, `${itemPath}.actionId`, errors);
|
|
3592
|
+
});
|
|
3593
|
+
}
|
|
3594
|
+
function requiresContent(widget) {
|
|
3595
|
+
return isRecord5(widget.state) && (widget.state.status === "ready" || widget.state.status === "stale");
|
|
3596
|
+
}
|
|
3597
|
+
function recordValue(value, path, errors) {
|
|
3598
|
+
if (!isRecord5(value)) {
|
|
3599
|
+
errors.push(`${path} must be an object`);
|
|
3600
|
+
return false;
|
|
3601
|
+
}
|
|
3602
|
+
return true;
|
|
3603
|
+
}
|
|
3604
|
+
function arrayValue(value, path, errors, requireOne = false) {
|
|
3605
|
+
if (!Array.isArray(value)) {
|
|
3606
|
+
errors.push(`${path} must be an array`);
|
|
3607
|
+
return false;
|
|
3608
|
+
}
|
|
3609
|
+
if (requireOne && value.length === 0) errors.push(`${path} must contain at least one item`);
|
|
3610
|
+
return true;
|
|
3611
|
+
}
|
|
3612
|
+
function stringArray2(value, path, errors, requireOne = false) {
|
|
3613
|
+
if (!arrayValue(value, path, errors, requireOne)) return;
|
|
3614
|
+
value.forEach((item, index) => nonEmptyString(item, `${path}[${index}]`, errors));
|
|
3615
|
+
}
|
|
3616
|
+
function enumArray(value, allowed, path, errors, requireOne) {
|
|
3617
|
+
if (!arrayValue(value, path, errors, requireOne)) return;
|
|
3618
|
+
value.forEach((item, index) => enumValue(item, allowed, `${path}[${index}]`, errors));
|
|
3619
|
+
if (new Set(value).size !== value.length) errors.push(`${path} must not contain duplicates`);
|
|
3620
|
+
}
|
|
3621
|
+
function nonEmptyString(value, path, errors) {
|
|
3622
|
+
if (typeof value !== "string" || value.trim().length === 0) errors.push(`${path} must be a non-empty string`);
|
|
3623
|
+
}
|
|
3624
|
+
function optionalNonEmptyString(value, path, errors) {
|
|
3625
|
+
if (value !== void 0) nonEmptyString(value, path, errors);
|
|
3626
|
+
}
|
|
3627
|
+
function patternString(value, pattern, path, errors) {
|
|
3628
|
+
if (typeof value !== "string" || !pattern.test(value)) errors.push(`${path} must be a semantic version`);
|
|
3629
|
+
}
|
|
3630
|
+
function enumValue(value, allowed, path, errors) {
|
|
3631
|
+
if (typeof value !== "string" || !allowed.includes(value)) errors.push(`${path} must be one of: ${allowed.join(", ")}`);
|
|
3632
|
+
}
|
|
3633
|
+
function exactValue(value, expected, path, errors) {
|
|
3634
|
+
if (value !== expected) errors.push(`${path} must be ${expected}`);
|
|
3635
|
+
}
|
|
3636
|
+
function booleanValue(value, path, errors) {
|
|
3637
|
+
if (typeof value !== "boolean") errors.push(`${path} must be a boolean`);
|
|
3638
|
+
}
|
|
3639
|
+
function finiteNumber(value, path, errors) {
|
|
3640
|
+
if (typeof value !== "number" || !Number.isFinite(value)) errors.push(`${path} must be a finite number`);
|
|
3641
|
+
}
|
|
3642
|
+
function optionalFiniteNumber(value, path, errors) {
|
|
3643
|
+
if (value !== void 0) finiteNumber(value, path, errors);
|
|
3644
|
+
}
|
|
3645
|
+
function positiveInteger(value, path, errors) {
|
|
3646
|
+
if (!Number.isInteger(value) || Number(value) <= 0) errors.push(`${path} must be a positive integer`);
|
|
3647
|
+
}
|
|
3648
|
+
function nonNegativeInteger(value, path, errors) {
|
|
3649
|
+
if (!Number.isInteger(value) || Number(value) < 0) errors.push(`${path} must be a non-negative integer`);
|
|
3650
|
+
}
|
|
3651
|
+
function isoTimestamp(value, path, errors) {
|
|
3652
|
+
if (!isIsoTimestamp(value)) errors.push(`${path} must be an ISO timestamp`);
|
|
3653
|
+
}
|
|
3654
|
+
function optionalIsoTimestamp(value, path, errors) {
|
|
3655
|
+
if (value !== void 0) isoTimestamp(value, path, errors);
|
|
3656
|
+
}
|
|
3657
|
+
function isIsoTimestamp(value) {
|
|
3658
|
+
return typeof value === "string" && value.trim().length > 0 && Number.isFinite(Date.parse(value));
|
|
3659
|
+
}
|
|
3660
|
+
function isPositiveNumber(value) {
|
|
3661
|
+
return typeof value === "number" && value > 0;
|
|
3662
|
+
}
|
|
3663
|
+
function isRecord5(value) {
|
|
3664
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3270
3667
|
// src/widgets.ts
|
|
3271
3668
|
var TOPOLO_WIDGET_ENDPOINT = "/api/widget";
|
|
3272
3669
|
var TOPOLO_WIDGET_REFRESH_INTERVAL_MS = 6e4;
|
|
3670
|
+
var TOPOLO_WIDGET_CONTRACT_VERSION = "2.0.0";
|
|
3273
3671
|
function defineTopoloStatsWidget(widget) {
|
|
3274
3672
|
return { ...widget, type: "stats" };
|
|
3275
3673
|
}
|
|
3276
|
-
function
|
|
3277
|
-
return { ...widget, type: "
|
|
3674
|
+
function defineTopoloChartWidget(widget) {
|
|
3675
|
+
return { ...widget, type: "chart" };
|
|
3278
3676
|
}
|
|
3279
|
-
function
|
|
3280
|
-
return { ...widget, type: "
|
|
3677
|
+
function defineTopoloActivityWidget(widget) {
|
|
3678
|
+
return { ...widget, type: "activity" };
|
|
3281
3679
|
}
|
|
3282
3680
|
function defineTopoloNotificationsWidget(widget) {
|
|
3283
3681
|
return { ...widget, type: "notifications" };
|
|
3284
3682
|
}
|
|
3285
|
-
function
|
|
3286
|
-
return { ...widget, type: "
|
|
3683
|
+
function defineTopoloActionsWidget(widget) {
|
|
3684
|
+
return { ...widget, type: "actions" };
|
|
3287
3685
|
}
|
|
3288
3686
|
function createTopoloWidgetResponse({
|
|
3289
3687
|
appId,
|
|
@@ -3291,136 +3689,59 @@ function createTopoloWidgetResponse({
|
|
|
3291
3689
|
widgets,
|
|
3292
3690
|
now
|
|
3293
3691
|
}) {
|
|
3294
|
-
const
|
|
3295
|
-
|
|
3692
|
+
const generatedAt = toIsoTimestamp(now);
|
|
3693
|
+
const response = {
|
|
3296
3694
|
success: true,
|
|
3695
|
+
contractVersion: TOPOLO_WIDGET_CONTRACT_VERSION,
|
|
3696
|
+
appId,
|
|
3697
|
+
serviceName,
|
|
3698
|
+
generatedAt,
|
|
3297
3699
|
widgets: widgets.map((widget) => ({
|
|
3298
3700
|
...widget,
|
|
3299
3701
|
appId: widget.appId?.trim() || appId,
|
|
3300
3702
|
serviceName: widget.serviceName?.trim() || serviceName,
|
|
3301
|
-
lastUpdated: widget.lastUpdated?.trim() ||
|
|
3703
|
+
lastUpdated: widget.lastUpdated?.trim() || generatedAt
|
|
3302
3704
|
}))
|
|
3303
3705
|
};
|
|
3706
|
+
assertValidTopoloWidgetResponse(response);
|
|
3707
|
+
return response;
|
|
3304
3708
|
}
|
|
3305
|
-
function createTopoloWidgetError(
|
|
3306
|
-
|
|
3709
|
+
function createTopoloWidgetError({
|
|
3710
|
+
appId,
|
|
3711
|
+
serviceName,
|
|
3712
|
+
code,
|
|
3713
|
+
message,
|
|
3714
|
+
retryable,
|
|
3715
|
+
retryAfter,
|
|
3716
|
+
now
|
|
3717
|
+
}) {
|
|
3718
|
+
const response = {
|
|
3307
3719
|
success: false,
|
|
3720
|
+
contractVersion: TOPOLO_WIDGET_CONTRACT_VERSION,
|
|
3721
|
+
appId,
|
|
3722
|
+
serviceName,
|
|
3723
|
+
generatedAt: toIsoTimestamp(now),
|
|
3308
3724
|
widgets: [],
|
|
3309
|
-
error
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
if (!isRecord5(response)) {
|
|
3315
|
-
return { ok: false, errors: ["response must be an object"] };
|
|
3316
|
-
}
|
|
3317
|
-
if (typeof response.success !== "boolean") {
|
|
3318
|
-
errors.push("success must be a boolean");
|
|
3319
|
-
}
|
|
3320
|
-
if (!Array.isArray(response.widgets)) {
|
|
3321
|
-
errors.push("widgets must be an array");
|
|
3322
|
-
return { ok: errors.length === 0, errors };
|
|
3323
|
-
}
|
|
3324
|
-
response.widgets.forEach((widget, index) => {
|
|
3325
|
-
validateWidget(widget, `widgets[${index}]`, errors);
|
|
3326
|
-
});
|
|
3327
|
-
if (response.success === false && response.error !== void 0 && typeof response.error !== "string") {
|
|
3328
|
-
errors.push("error must be a string when provided");
|
|
3329
|
-
}
|
|
3330
|
-
return { ok: errors.length === 0, errors };
|
|
3331
|
-
}
|
|
3332
|
-
function validateWidget(widget, path, errors) {
|
|
3333
|
-
if (!isRecord5(widget)) {
|
|
3334
|
-
errors.push(`${path} must be an object`);
|
|
3335
|
-
return;
|
|
3336
|
-
}
|
|
3337
|
-
validateNonEmptyString(widget.appId, `${path}.appId`, errors);
|
|
3338
|
-
validateNonEmptyString(widget.serviceName, `${path}.serviceName`, errors);
|
|
3339
|
-
validateNonEmptyString(widget.lastUpdated, `${path}.lastUpdated`, errors);
|
|
3340
|
-
validateSnapshotMetadata(widget.snapshot, `${path}.snapshot`, errors);
|
|
3341
|
-
switch (widget.type) {
|
|
3342
|
-
case "stats":
|
|
3343
|
-
validateArray2(widget.stats, `${path}.stats`, errors);
|
|
3344
|
-
break;
|
|
3345
|
-
case "recent-activity":
|
|
3346
|
-
validateArray2(widget.activities, `${path}.activities`, errors);
|
|
3347
|
-
break;
|
|
3348
|
-
case "quick-actions":
|
|
3349
|
-
validateArray2(widget.actions, `${path}.actions`, errors);
|
|
3350
|
-
break;
|
|
3351
|
-
case "notifications":
|
|
3352
|
-
validateArray2(widget.notifications, `${path}.notifications`, errors);
|
|
3353
|
-
if (typeof widget.unreadCount !== "number") {
|
|
3354
|
-
errors.push(`${path}.unreadCount must be a number`);
|
|
3355
|
-
}
|
|
3356
|
-
break;
|
|
3357
|
-
case "chart":
|
|
3358
|
-
validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
|
|
3359
|
-
validateNonEmptyString(widget.title, `${path}.title`, errors);
|
|
3360
|
-
if (!isRecord5(widget.data)) {
|
|
3361
|
-
errors.push(`${path}.data must be an object`);
|
|
3362
|
-
} else {
|
|
3363
|
-
validateArray2(widget.data.labels, `${path}.data.labels`, errors);
|
|
3364
|
-
validateArray2(widget.data.values, `${path}.data.values`, errors);
|
|
3365
|
-
}
|
|
3366
|
-
break;
|
|
3367
|
-
default:
|
|
3368
|
-
errors.push(`${path}.type must be a supported Topolo widget type`);
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
function validateSnapshotMetadata(snapshot, path, errors) {
|
|
3372
|
-
if (snapshot === void 0) {
|
|
3373
|
-
errors.push(`${path} must be an object`);
|
|
3374
|
-
return;
|
|
3375
|
-
}
|
|
3376
|
-
if (!isRecord5(snapshot)) {
|
|
3377
|
-
errors.push(`${path} must be an object`);
|
|
3378
|
-
return;
|
|
3379
|
-
}
|
|
3380
|
-
validateNonEmptyString(snapshot.summary, `${path}.summary`, errors);
|
|
3381
|
-
if (snapshot.defaultMetricKeys !== void 0 && !Array.isArray(snapshot.defaultMetricKeys)) {
|
|
3382
|
-
errors.push(`${path}.defaultMetricKeys must be an array when provided`);
|
|
3383
|
-
}
|
|
3384
|
-
if (snapshot.highlights !== void 0 && !Array.isArray(snapshot.highlights)) {
|
|
3385
|
-
errors.push(`${path}.highlights must be an array when provided`);
|
|
3386
|
-
}
|
|
3387
|
-
if (snapshot.availableMetrics === void 0) {
|
|
3388
|
-
return;
|
|
3389
|
-
}
|
|
3390
|
-
if (!Array.isArray(snapshot.availableMetrics)) {
|
|
3391
|
-
errors.push(`${path}.availableMetrics must be an array when provided`);
|
|
3392
|
-
return;
|
|
3393
|
-
}
|
|
3394
|
-
snapshot.availableMetrics.forEach((metric, index) => {
|
|
3395
|
-
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
3396
|
-
if (!isRecord5(metric)) {
|
|
3397
|
-
errors.push(`${metricPath} must be an object`);
|
|
3398
|
-
return;
|
|
3399
|
-
}
|
|
3400
|
-
validateNonEmptyString(metric.key, `${metricPath}.key`, errors);
|
|
3401
|
-
validateNonEmptyString(metric.label, `${metricPath}.label`, errors);
|
|
3402
|
-
if (typeof metric.value !== "number" || Number.isNaN(metric.value)) {
|
|
3403
|
-
errors.push(`${metricPath}.value must be a number`);
|
|
3725
|
+
error: {
|
|
3726
|
+
code,
|
|
3727
|
+
message,
|
|
3728
|
+
retryable,
|
|
3729
|
+
...retryAfter === void 0 ? {} : { retryAfter: toIsoTimestamp(retryAfter) }
|
|
3404
3730
|
}
|
|
3405
|
-
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3731
|
+
};
|
|
3732
|
+
assertValidTopoloWidgetResponse(response);
|
|
3733
|
+
return response;
|
|
3734
|
+
}
|
|
3735
|
+
function assertValidTopoloWidgetResponse(response) {
|
|
3736
|
+
const validation = validateTopoloWidgetResponse(response);
|
|
3737
|
+
if (!validation.ok) {
|
|
3738
|
+
throw new TypeError(
|
|
3739
|
+
`Invalid Topolo widget contract: ${validation.errors.join("; ")}`
|
|
3740
|
+
);
|
|
3415
3741
|
}
|
|
3416
3742
|
}
|
|
3417
|
-
function isRecord5(value) {
|
|
3418
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3419
|
-
}
|
|
3420
3743
|
function toIsoTimestamp(input) {
|
|
3421
|
-
if (input instanceof Date)
|
|
3422
|
-
return input.toISOString();
|
|
3423
|
-
}
|
|
3744
|
+
if (input instanceof Date) return input.toISOString();
|
|
3424
3745
|
return input ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3425
3746
|
}
|
|
3426
3747
|
|
|
@@ -3443,6 +3764,7 @@ function createTopolo(options) {
|
|
|
3443
3764
|
P2PModule,
|
|
3444
3765
|
TOPOLO_ENVIRONMENT_SERVICE_URLS,
|
|
3445
3766
|
TOPOLO_RUNTIME_ENVIRONMENTS,
|
|
3767
|
+
TOPOLO_WIDGET_CONTRACT_VERSION,
|
|
3446
3768
|
TOPOLO_WIDGET_ENDPOINT,
|
|
3447
3769
|
TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
|
|
3448
3770
|
TopoloAuthError,
|
|
@@ -3469,10 +3791,10 @@ function createTopolo(options) {
|
|
|
3469
3791
|
createTopoloAgentExecutionContext,
|
|
3470
3792
|
createTopoloWidgetError,
|
|
3471
3793
|
createTopoloWidgetResponse,
|
|
3794
|
+
defineTopoloActionsWidget,
|
|
3795
|
+
defineTopoloActivityWidget,
|
|
3472
3796
|
defineTopoloChartWidget,
|
|
3473
3797
|
defineTopoloNotificationsWidget,
|
|
3474
|
-
defineTopoloQuickActionsWidget,
|
|
3475
|
-
defineTopoloRecentActivityWidget,
|
|
3476
3798
|
defineTopoloStatsWidget,
|
|
3477
3799
|
digestTopoloActionCatalog,
|
|
3478
3800
|
encodeTopoloActionBase64,
|