@topolo/sdk 0.9.3 → 0.9.5
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 +365 -39
- package/dist/index.d.cts +65 -29
- package/dist/index.d.ts +65 -29
- package/dist/index.js +363 -39
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -132,7 +132,9 @@ var APPLICATIONS = {
|
|
|
132
132
|
"domains": [
|
|
133
133
|
"admin.topolo.app"
|
|
134
134
|
],
|
|
135
|
-
"routes": [
|
|
135
|
+
"routes": [
|
|
136
|
+
"admin.topolo.app"
|
|
137
|
+
],
|
|
136
138
|
"appId": null
|
|
137
139
|
},
|
|
138
140
|
{
|
|
@@ -180,7 +182,7 @@ var APPLICATIONS = {
|
|
|
180
182
|
"appDir": "TopoloAuth",
|
|
181
183
|
"name": "topolo-auth",
|
|
182
184
|
"packageName": "topolo-auth",
|
|
183
|
-
"productionUrl": "https://auth.topolo.app
|
|
185
|
+
"productionUrl": "https://auth.topolo.app",
|
|
184
186
|
"services": [
|
|
185
187
|
"auth"
|
|
186
188
|
],
|
|
@@ -216,7 +218,9 @@ var APPLICATIONS = {
|
|
|
216
218
|
"domains": [
|
|
217
219
|
"backup.topolo.app"
|
|
218
220
|
],
|
|
219
|
-
"routes": [
|
|
221
|
+
"routes": [
|
|
222
|
+
"backup.topolo.app"
|
|
223
|
+
],
|
|
220
224
|
"appId": "backup"
|
|
221
225
|
}
|
|
222
226
|
]
|
|
@@ -268,9 +272,7 @@ var APPLICATIONS = {
|
|
|
268
272
|
"domains": [
|
|
269
273
|
"brand.topolo.app"
|
|
270
274
|
],
|
|
271
|
-
"routes": [
|
|
272
|
-
"brand.topolo.app"
|
|
273
|
-
],
|
|
275
|
+
"routes": [],
|
|
274
276
|
"appId": "brand"
|
|
275
277
|
},
|
|
276
278
|
{
|
|
@@ -322,7 +324,9 @@ var APPLICATIONS = {
|
|
|
322
324
|
"domains": [
|
|
323
325
|
"bytes.topolo.app"
|
|
324
326
|
],
|
|
325
|
-
"routes": [
|
|
327
|
+
"routes": [
|
|
328
|
+
"bytes.topolo.app"
|
|
329
|
+
],
|
|
326
330
|
"appId": "bytes"
|
|
327
331
|
},
|
|
328
332
|
{
|
|
@@ -352,7 +356,9 @@ var APPLICATIONS = {
|
|
|
352
356
|
"domains": [
|
|
353
357
|
"calendar.topolo.app"
|
|
354
358
|
],
|
|
355
|
-
"routes": [
|
|
359
|
+
"routes": [
|
|
360
|
+
"calendar.topolo.app"
|
|
361
|
+
],
|
|
356
362
|
"appId": "calendar"
|
|
357
363
|
},
|
|
358
364
|
{
|
|
@@ -382,9 +388,7 @@ var APPLICATIONS = {
|
|
|
382
388
|
"domains": [
|
|
383
389
|
"campaigns.topolo.app"
|
|
384
390
|
],
|
|
385
|
-
"routes": [
|
|
386
|
-
"campaigns.topolo.app"
|
|
387
|
-
],
|
|
391
|
+
"routes": [],
|
|
388
392
|
"appId": "campaigns"
|
|
389
393
|
},
|
|
390
394
|
{
|
|
@@ -474,7 +478,9 @@ var APPLICATIONS = {
|
|
|
474
478
|
"domains": [
|
|
475
479
|
"guest.topolo.app"
|
|
476
480
|
],
|
|
477
|
-
"routes": [
|
|
481
|
+
"routes": [
|
|
482
|
+
"guest.topolo.app"
|
|
483
|
+
],
|
|
478
484
|
"appId": null
|
|
479
485
|
},
|
|
480
486
|
{
|
|
@@ -484,7 +490,9 @@ var APPLICATIONS = {
|
|
|
484
490
|
"domains": [
|
|
485
491
|
"commerce.topolo.app"
|
|
486
492
|
],
|
|
487
|
-
"routes": [
|
|
493
|
+
"routes": [
|
|
494
|
+
"commerce.topolo.app"
|
|
495
|
+
],
|
|
488
496
|
"appId": "commerce"
|
|
489
497
|
},
|
|
490
498
|
{
|
|
@@ -494,7 +502,9 @@ var APPLICATIONS = {
|
|
|
494
502
|
"domains": [
|
|
495
503
|
"station-commerce.topolo.app"
|
|
496
504
|
],
|
|
497
|
-
"routes": [
|
|
505
|
+
"routes": [
|
|
506
|
+
"station-commerce.topolo.app"
|
|
507
|
+
],
|
|
498
508
|
"appId": null
|
|
499
509
|
}
|
|
500
510
|
]
|
|
@@ -672,7 +682,9 @@ var APPLICATIONS = {
|
|
|
672
682
|
"domains": [
|
|
673
683
|
"docs.topolo.app"
|
|
674
684
|
],
|
|
675
|
-
"routes": [
|
|
685
|
+
"routes": [
|
|
686
|
+
"docs.topolo.app"
|
|
687
|
+
],
|
|
676
688
|
"appId": null
|
|
677
689
|
},
|
|
678
690
|
{
|
|
@@ -702,7 +714,9 @@ var APPLICATIONS = {
|
|
|
702
714
|
"domains": [
|
|
703
715
|
"feed-analytics-api.topolo.app"
|
|
704
716
|
],
|
|
705
|
-
"routes": [
|
|
717
|
+
"routes": [
|
|
718
|
+
"feed-analytics-api.topolo.app"
|
|
719
|
+
],
|
|
706
720
|
"appId": null
|
|
707
721
|
},
|
|
708
722
|
{
|
|
@@ -712,7 +726,9 @@ var APPLICATIONS = {
|
|
|
712
726
|
"domains": [
|
|
713
727
|
"feed-api.topolo.app"
|
|
714
728
|
],
|
|
715
|
-
"routes": [
|
|
729
|
+
"routes": [
|
|
730
|
+
"feed-api.topolo.app"
|
|
731
|
+
],
|
|
716
732
|
"appId": null
|
|
717
733
|
},
|
|
718
734
|
{
|
|
@@ -722,7 +738,9 @@ var APPLICATIONS = {
|
|
|
722
738
|
"domains": [
|
|
723
739
|
"feed.topolo.app"
|
|
724
740
|
],
|
|
725
|
-
"routes": [
|
|
741
|
+
"routes": [
|
|
742
|
+
"feed.topolo.app"
|
|
743
|
+
],
|
|
726
744
|
"appId": "feed"
|
|
727
745
|
}
|
|
728
746
|
]
|
|
@@ -834,7 +852,9 @@ var APPLICATIONS = {
|
|
|
834
852
|
"domains": [
|
|
835
853
|
"home.topolo.app"
|
|
836
854
|
],
|
|
837
|
-
"routes": [
|
|
855
|
+
"routes": [
|
|
856
|
+
"home.topolo.app"
|
|
857
|
+
],
|
|
838
858
|
"appId": "home"
|
|
839
859
|
},
|
|
840
860
|
{
|
|
@@ -924,7 +944,9 @@ var APPLICATIONS = {
|
|
|
924
944
|
"domains": [
|
|
925
945
|
"learn.topolo.app"
|
|
926
946
|
],
|
|
927
|
-
"routes": [
|
|
947
|
+
"routes": [
|
|
948
|
+
"learn.topolo.app"
|
|
949
|
+
],
|
|
928
950
|
"appId": "learn"
|
|
929
951
|
},
|
|
930
952
|
{
|
|
@@ -1016,7 +1038,9 @@ var APPLICATIONS = {
|
|
|
1016
1038
|
"domains": [
|
|
1017
1039
|
"mdm.topolo.app"
|
|
1018
1040
|
],
|
|
1019
|
-
"routes": [
|
|
1041
|
+
"routes": [
|
|
1042
|
+
"mdm.topolo.app"
|
|
1043
|
+
],
|
|
1020
1044
|
"appId": "mdm"
|
|
1021
1045
|
}
|
|
1022
1046
|
]
|
|
@@ -1038,7 +1062,9 @@ var APPLICATIONS = {
|
|
|
1038
1062
|
"domains": [
|
|
1039
1063
|
"messages.topolo.app"
|
|
1040
1064
|
],
|
|
1041
|
-
"routes": [
|
|
1065
|
+
"routes": [
|
|
1066
|
+
"messages.topolo.app"
|
|
1067
|
+
],
|
|
1042
1068
|
"appId": "messages"
|
|
1043
1069
|
},
|
|
1044
1070
|
{
|
|
@@ -1072,11 +1098,13 @@ var APPLICATIONS = {
|
|
|
1072
1098
|
{
|
|
1073
1099
|
"name": "topolo-nexus-dashboard",
|
|
1074
1100
|
"kind": "worker",
|
|
1075
|
-
"apiUrl":
|
|
1101
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
1076
1102
|
"domains": [
|
|
1077
1103
|
"nexus.topolo.app"
|
|
1078
1104
|
],
|
|
1079
|
-
"routes": [
|
|
1105
|
+
"routes": [
|
|
1106
|
+
"nexus.topolo.app"
|
|
1107
|
+
],
|
|
1080
1108
|
"appId": null
|
|
1081
1109
|
},
|
|
1082
1110
|
{
|
|
@@ -1204,7 +1232,9 @@ var APPLICATIONS = {
|
|
|
1204
1232
|
"domains": [
|
|
1205
1233
|
"p2p.topolo.app"
|
|
1206
1234
|
],
|
|
1207
|
-
"routes": [
|
|
1235
|
+
"routes": [
|
|
1236
|
+
"p2p.topolo.app"
|
|
1237
|
+
],
|
|
1208
1238
|
"appId": "p2p"
|
|
1209
1239
|
},
|
|
1210
1240
|
{
|
|
@@ -1401,7 +1431,9 @@ var APPLICATIONS = {
|
|
|
1401
1431
|
"domains": [
|
|
1402
1432
|
"studio.topolo.app"
|
|
1403
1433
|
],
|
|
1404
|
-
"routes": [
|
|
1434
|
+
"routes": [
|
|
1435
|
+
"studio.topolo.app"
|
|
1436
|
+
],
|
|
1405
1437
|
"appId": "social_studio"
|
|
1406
1438
|
},
|
|
1407
1439
|
{
|
|
@@ -1439,7 +1471,9 @@ var APPLICATIONS = {
|
|
|
1439
1471
|
"domains": [
|
|
1440
1472
|
"socialize.topolo.app"
|
|
1441
1473
|
],
|
|
1442
|
-
"routes": [
|
|
1474
|
+
"routes": [
|
|
1475
|
+
"socialize.topolo.app"
|
|
1476
|
+
],
|
|
1443
1477
|
"appId": "socialize"
|
|
1444
1478
|
},
|
|
1445
1479
|
{
|
|
@@ -1449,7 +1483,9 @@ var APPLICATIONS = {
|
|
|
1449
1483
|
"domains": [
|
|
1450
1484
|
"socialize-api.topolo.app"
|
|
1451
1485
|
],
|
|
1452
|
-
"routes": [
|
|
1486
|
+
"routes": [
|
|
1487
|
+
"socialize-api.topolo.app"
|
|
1488
|
+
],
|
|
1453
1489
|
"appId": null
|
|
1454
1490
|
},
|
|
1455
1491
|
{
|
|
@@ -1509,7 +1545,9 @@ var APPLICATIONS = {
|
|
|
1509
1545
|
"domains": [
|
|
1510
1546
|
"status.topolo.app"
|
|
1511
1547
|
],
|
|
1512
|
-
"routes": [
|
|
1548
|
+
"routes": [
|
|
1549
|
+
"status.topolo.app"
|
|
1550
|
+
],
|
|
1513
1551
|
"appId": "status"
|
|
1514
1552
|
}
|
|
1515
1553
|
]
|
|
@@ -1625,7 +1663,9 @@ var APPLICATIONS = {
|
|
|
1625
1663
|
"domains": [
|
|
1626
1664
|
"voice.topolo.app"
|
|
1627
1665
|
],
|
|
1628
|
-
"routes": [
|
|
1666
|
+
"routes": [
|
|
1667
|
+
"voice.topolo.app"
|
|
1668
|
+
],
|
|
1629
1669
|
"appId": "voice"
|
|
1630
1670
|
},
|
|
1631
1671
|
{
|
|
@@ -1676,7 +1716,9 @@ var APPLICATIONS = {
|
|
|
1676
1716
|
"domains": [
|
|
1677
1717
|
"web.topolo.app"
|
|
1678
1718
|
],
|
|
1679
|
-
"routes": [
|
|
1719
|
+
"routes": [
|
|
1720
|
+
"web.topolo.app"
|
|
1721
|
+
],
|
|
1680
1722
|
"appId": "web"
|
|
1681
1723
|
}
|
|
1682
1724
|
]
|
|
@@ -1755,7 +1797,7 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1755
1797
|
"Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
|
|
1756
1798
|
"Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
|
|
1757
1799
|
"Keep mobile and desktop shell behavior aligned with the platform UI kit",
|
|
1758
|
-
"Constrain app
|
|
1800
|
+
"Constrain the app workspace-view layout wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
|
|
1759
1801
|
]
|
|
1760
1802
|
},
|
|
1761
1803
|
{
|
|
@@ -3386,8 +3428,9 @@ async function digestTopoloActionCatalog(actions, appId) {
|
|
|
3386
3428
|
return {
|
|
3387
3429
|
actionId: action.actionId || `${appId}.${action.name}`,
|
|
3388
3430
|
authorizationMode,
|
|
3389
|
-
|
|
3431
|
+
allowedServices: authorizationMode === "service" || authorizationMode === "runtime" ? allowedServices : [],
|
|
3390
3432
|
inputSchema: action.inputSchema,
|
|
3433
|
+
agentContract: action.agentContract ?? {},
|
|
3391
3434
|
method: action.method,
|
|
3392
3435
|
outputSchema: action.outputSchema,
|
|
3393
3436
|
path: action.path,
|
|
@@ -3402,6 +3445,285 @@ async function digestTopoloActionCatalog(actions, appId) {
|
|
|
3402
3445
|
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
3403
3446
|
}
|
|
3404
3447
|
|
|
3448
|
+
// src/action-contract-enrichment.mjs
|
|
3449
|
+
var OPERATION_TOKENS = /* @__PURE__ */ new Set([
|
|
3450
|
+
"add",
|
|
3451
|
+
"archive",
|
|
3452
|
+
"build",
|
|
3453
|
+
"call",
|
|
3454
|
+
"cancel",
|
|
3455
|
+
"claim",
|
|
3456
|
+
"complete",
|
|
3457
|
+
"create",
|
|
3458
|
+
"delete",
|
|
3459
|
+
"disable",
|
|
3460
|
+
"enable",
|
|
3461
|
+
"execute",
|
|
3462
|
+
"export",
|
|
3463
|
+
"get",
|
|
3464
|
+
"import",
|
|
3465
|
+
"ingest",
|
|
3466
|
+
"invite",
|
|
3467
|
+
"list",
|
|
3468
|
+
"move",
|
|
3469
|
+
"preview",
|
|
3470
|
+
"publish",
|
|
3471
|
+
"read",
|
|
3472
|
+
"refresh",
|
|
3473
|
+
"remove",
|
|
3474
|
+
"restore",
|
|
3475
|
+
"retry",
|
|
3476
|
+
"review",
|
|
3477
|
+
"run",
|
|
3478
|
+
"scaffold",
|
|
3479
|
+
"search",
|
|
3480
|
+
"send",
|
|
3481
|
+
"set",
|
|
3482
|
+
"submit",
|
|
3483
|
+
"sync",
|
|
3484
|
+
"transfer",
|
|
3485
|
+
"unpublish",
|
|
3486
|
+
"update",
|
|
3487
|
+
"upload",
|
|
3488
|
+
"validate"
|
|
3489
|
+
]);
|
|
3490
|
+
var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
|
|
3491
|
+
["archive", "restore"],
|
|
3492
|
+
["create", "delete"],
|
|
3493
|
+
["disable", "enable"],
|
|
3494
|
+
["enable", "disable"],
|
|
3495
|
+
["publish", "unpublish"],
|
|
3496
|
+
["remove", "add"],
|
|
3497
|
+
["restore", "archive"],
|
|
3498
|
+
["unpublish", "publish"]
|
|
3499
|
+
]);
|
|
3500
|
+
function isRecord3(value) {
|
|
3501
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3502
|
+
}
|
|
3503
|
+
function nonEmptyStrings(value) {
|
|
3504
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.trim());
|
|
3505
|
+
}
|
|
3506
|
+
function slugify(value) {
|
|
3507
|
+
return String(value || "").trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3508
|
+
}
|
|
3509
|
+
function singularize(value) {
|
|
3510
|
+
if (value.endsWith("ies") && value.length > 3) return `${value.slice(0, -3)}y`;
|
|
3511
|
+
if (value.endsWith("sses")) return value.slice(0, -2);
|
|
3512
|
+
if (value.endsWith("s") && !value.endsWith("ss")) return value.slice(0, -1);
|
|
3513
|
+
return value || "resource";
|
|
3514
|
+
}
|
|
3515
|
+
function operationFor(action) {
|
|
3516
|
+
const tokens = String(action.name || "").split(/[._:-]+/).filter(Boolean);
|
|
3517
|
+
return [...tokens].reverse().find((token) => OPERATION_TOKENS.has(token)) || String(action.method || "GET").toLowerCase();
|
|
3518
|
+
}
|
|
3519
|
+
function resourceFor(action) {
|
|
3520
|
+
const nameTokens = String(action.name || "").split(/[._:-]+/).filter(Boolean);
|
|
3521
|
+
const named = nameTokens.find((token) => !OPERATION_TOKENS.has(token));
|
|
3522
|
+
if (named) return singularize(slugify(named).replaceAll("-", "_"));
|
|
3523
|
+
const pathToken = String(action.path || "").split("/").filter((token) => token && token !== "api" && !token.startsWith("{")).find((token) => !OPERATION_TOKENS.has(slugify(token)));
|
|
3524
|
+
return singularize(slugify(pathToken || "resource").replaceAll("-", "_"));
|
|
3525
|
+
}
|
|
3526
|
+
function isConcreteOutputSchema(schema) {
|
|
3527
|
+
if (!isRecord3(schema)) return false;
|
|
3528
|
+
if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
|
|
3529
|
+
if (schema.type === "array" && isRecord3(schema.items) && Object.keys(schema.items).length > 0) return true;
|
|
3530
|
+
if (isRecord3(schema.properties) && Object.keys(schema.properties).length > 0) return true;
|
|
3531
|
+
return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
|
|
3532
|
+
}
|
|
3533
|
+
function resourceSchema(resource) {
|
|
3534
|
+
return {
|
|
3535
|
+
type: "object",
|
|
3536
|
+
title: `${resource.replaceAll("_", " ")} record`,
|
|
3537
|
+
properties: {
|
|
3538
|
+
id: {
|
|
3539
|
+
type: "string",
|
|
3540
|
+
description: `Stable identifier when the ${resource.replaceAll("_", " ")} exposes one.`
|
|
3541
|
+
}
|
|
3542
|
+
},
|
|
3543
|
+
additionalProperties: true
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
function derivedOutputSchema(action) {
|
|
3547
|
+
const resource = resourceFor(action);
|
|
3548
|
+
const operation = operationFor(action);
|
|
3549
|
+
const record = resourceSchema(resource);
|
|
3550
|
+
const records = { type: "array", items: record };
|
|
3551
|
+
const provenance = {
|
|
3552
|
+
"x-topolo-contract-source": "derived-action-metadata",
|
|
3553
|
+
"x-topolo-contract-precision": "structural"
|
|
3554
|
+
};
|
|
3555
|
+
if (operation === "list" || operation === "search") {
|
|
3556
|
+
return {
|
|
3557
|
+
title: `${action.title || action.name} response`,
|
|
3558
|
+
description: `Collection response returned by ${action.name}. The service may return the collection directly or in an envelope.`,
|
|
3559
|
+
oneOf: [
|
|
3560
|
+
records,
|
|
3561
|
+
{
|
|
3562
|
+
type: "object",
|
|
3563
|
+
properties: {
|
|
3564
|
+
data: { oneOf: [records, { type: "object", additionalProperties: true }] },
|
|
3565
|
+
items: records,
|
|
3566
|
+
[resource.endsWith("s") ? resource : `${resource}s`]: records,
|
|
3567
|
+
count: { type: "integer", minimum: 0 },
|
|
3568
|
+
nextCursor: { type: ["string", "null"] }
|
|
3569
|
+
},
|
|
3570
|
+
additionalProperties: true
|
|
3571
|
+
}
|
|
3572
|
+
],
|
|
3573
|
+
...provenance
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
3576
|
+
if (operation === "get" || operation === "read") {
|
|
3577
|
+
return {
|
|
3578
|
+
title: `${action.title || action.name} response`,
|
|
3579
|
+
description: `Resource response returned by ${action.name}. The service may return the resource directly or in an envelope.`,
|
|
3580
|
+
oneOf: [
|
|
3581
|
+
record,
|
|
3582
|
+
{
|
|
3583
|
+
type: "object",
|
|
3584
|
+
properties: {
|
|
3585
|
+
data: record,
|
|
3586
|
+
[resource]: record
|
|
3587
|
+
},
|
|
3588
|
+
additionalProperties: true
|
|
3589
|
+
}
|
|
3590
|
+
],
|
|
3591
|
+
...provenance
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3594
|
+
return {
|
|
3595
|
+
type: "object",
|
|
3596
|
+
title: `${action.title || action.name} response`,
|
|
3597
|
+
description: `Operation result returned by ${action.name}.`,
|
|
3598
|
+
properties: {
|
|
3599
|
+
success: { type: "boolean" },
|
|
3600
|
+
id: { type: "string" },
|
|
3601
|
+
data: { oneOf: [record, { type: "object", additionalProperties: true }] },
|
|
3602
|
+
result: { type: "object", additionalProperties: true },
|
|
3603
|
+
message: { type: "string" }
|
|
3604
|
+
},
|
|
3605
|
+
additionalProperties: true,
|
|
3606
|
+
...provenance
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
function sampleForSchema(schema, depth = 0) {
|
|
3610
|
+
if (!isRecord3(schema) || depth > 5) return null;
|
|
3611
|
+
if (schema.const !== void 0) return schema.const;
|
|
3612
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
3613
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1);
|
|
3614
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return sampleForSchema(schema.anyOf[0], depth + 1);
|
|
3615
|
+
if (schema.type === "array") return [];
|
|
3616
|
+
if (schema.type === "boolean") return false;
|
|
3617
|
+
if (schema.type === "integer" || schema.type === "number") return schema.minimum ?? 0;
|
|
3618
|
+
if (schema.type === "string") {
|
|
3619
|
+
if (schema.format === "email") return "agent@example.com";
|
|
3620
|
+
if (schema.format === "uri" || schema.format === "url") return "https://example.com";
|
|
3621
|
+
return schema.minLength > 1 ? "example".padEnd(schema.minLength, "x") : "example";
|
|
3622
|
+
}
|
|
3623
|
+
if (schema.type === "null") return null;
|
|
3624
|
+
if (schema.type === "object" || isRecord3(schema.properties)) {
|
|
3625
|
+
const properties = isRecord3(schema.properties) ? schema.properties : {};
|
|
3626
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3627
|
+
const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1)]);
|
|
3628
|
+
return Object.fromEntries(entries);
|
|
3629
|
+
}
|
|
3630
|
+
return null;
|
|
3631
|
+
}
|
|
3632
|
+
function actionPrefix(name, operation) {
|
|
3633
|
+
const tokens = String(name || "").split(/[._:-]+/).filter(Boolean);
|
|
3634
|
+
const index = tokens.lastIndexOf(operation);
|
|
3635
|
+
if (index === -1) return tokens.join(".");
|
|
3636
|
+
return tokens.filter((_, tokenIndex) => tokenIndex !== index).join(".");
|
|
3637
|
+
}
|
|
3638
|
+
function findInverseActionId(action, allActions, appId) {
|
|
3639
|
+
const operation = operationFor(action);
|
|
3640
|
+
if (operation === "update" || operation === "set" || operation === "move") {
|
|
3641
|
+
return `${appId}.${action.name}`;
|
|
3642
|
+
}
|
|
3643
|
+
const inverse = INVERSE_OPERATIONS.get(operation);
|
|
3644
|
+
if (!inverse) return null;
|
|
3645
|
+
const prefix = actionPrefix(action.name, operation);
|
|
3646
|
+
const match = allActions.find((candidate) => operationFor(candidate) === inverse && actionPrefix(candidate.name, inverse) === prefix);
|
|
3647
|
+
return match ? `${appId}.${match.name}` : null;
|
|
3648
|
+
}
|
|
3649
|
+
function recoveryFor(action, allActions, appId) {
|
|
3650
|
+
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3651
|
+
if (readOnly) {
|
|
3652
|
+
return {
|
|
3653
|
+
mode: "not_applicable",
|
|
3654
|
+
instructions: "This action does not change service state."
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
const actionId = findInverseActionId(action, allActions, appId);
|
|
3658
|
+
if (actionId) {
|
|
3659
|
+
return {
|
|
3660
|
+
mode: "compensating_action",
|
|
3661
|
+
actionId,
|
|
3662
|
+
requiresSnapshot: actionId === `${appId}.${action.name}`,
|
|
3663
|
+
instructions: actionId === `${appId}.${action.name}` ? "Capture the current resource before the call, then invoke this action with the captured values to restore it." : `Invoke ${actionId} with the affected resource identifier after confirming the rollback.`
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
return {
|
|
3667
|
+
mode: "irreversible",
|
|
3668
|
+
instructions: "No safe automatic inverse is published. Confirm the change and retain any required export or snapshot before calling."
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
function defaultErrors(action) {
|
|
3672
|
+
const errors = [
|
|
3673
|
+
{ status: 401, code: "authentication_required", recovery: "Authenticate again or provide a valid Topolo API key." },
|
|
3674
|
+
{ status: 403, code: "permission_denied", recovery: `Grant the credential permission ${action.permission || action.permissionName || "required by this action"} and the required resource binding.` },
|
|
3675
|
+
{ status: 422, code: "invalid_input", recovery: "Correct the payload using inputSchema, then validate before retrying." }
|
|
3676
|
+
];
|
|
3677
|
+
if (action.method !== "GET") {
|
|
3678
|
+
errors.push({ status: 409, code: "state_conflict", recovery: "Read the current resource state, reconcile the intended change, and retry only if still valid." });
|
|
3679
|
+
}
|
|
3680
|
+
return errors;
|
|
3681
|
+
}
|
|
3682
|
+
function enrichActionContract(manifest, action, allActions = manifest.actions || []) {
|
|
3683
|
+
const appId = String(manifest.appId || manifest.app_id || "").trim();
|
|
3684
|
+
const service = slugify(manifest.slug || appId.replace(/^app_/, "").replaceAll("_", "-") || manifest.name);
|
|
3685
|
+
const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
|
|
3686
|
+
const resource = resourceFor(action);
|
|
3687
|
+
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3688
|
+
const existing = isRecord3(action.agentContract) ? structuredClone(action.agentContract) : {};
|
|
3689
|
+
const recovery = isRecord3(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
|
|
3690
|
+
const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
|
|
3691
|
+
const inputExample = sampleForSchema(action.inputSchema);
|
|
3692
|
+
const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples : [{ input: inputExample ?? {}, expected: `A response matching the published outputSchema for ${action.name}.` }];
|
|
3693
|
+
const docsUrl = `https://docs.topolo.app/reference/actions/?service=${encodeURIComponent(service)}&action=${encodeURIComponent(action.name)}`;
|
|
3694
|
+
const existingDocsUrl = typeof existing.docsUrl === "string" && existing.docsUrl.trim() ? existing.docsUrl.trim() : void 0;
|
|
3695
|
+
return {
|
|
3696
|
+
outputSchema,
|
|
3697
|
+
agentContract: {
|
|
3698
|
+
...existing,
|
|
3699
|
+
docsUrl,
|
|
3700
|
+
...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
|
|
3701
|
+
resource: isRecord3(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
|
|
3702
|
+
effects: nonEmptyStrings(existing.effects) ? existing.effects : [readOnly ? `Reads ${resource.replaceAll("_", " ")} data without changing service state.` : action.destructive === true || action.method === "DELETE" ? `Permanently removes or invalidates ${resource.replaceAll("_", " ")} state.` : `Changes ${resource.replaceAll("_", " ")} state in the selected Topolo resource context.`],
|
|
3703
|
+
errors: Array.isArray(existing.errors) && existing.errors.length > 0 ? existing.errors : defaultErrors(action),
|
|
3704
|
+
verify: nonEmptyStrings(existing.verify) ? existing.verify : [`Validate the response against outputSchema and ${readOnly ? "confirm it belongs to the selected resource context" : "re-read the affected resource to confirm the intended state"}.`],
|
|
3705
|
+
examples,
|
|
3706
|
+
recovery,
|
|
3707
|
+
...rollbackActionId ? { rollbackActionId } : {},
|
|
3708
|
+
contractSource: Object.hasOwn(outputSchema, "x-topolo-contract-source") ? "derived-action-metadata" : "application-declared"
|
|
3709
|
+
}
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
function actionContractCompleteness(action) {
|
|
3713
|
+
const contract = action?.agentContract;
|
|
3714
|
+
return {
|
|
3715
|
+
inputSchema: isRecord3(action?.inputSchema),
|
|
3716
|
+
outputSchema: isConcreteOutputSchema(action?.outputSchema),
|
|
3717
|
+
effects: nonEmptyStrings(contract?.effects),
|
|
3718
|
+
errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
|
|
3719
|
+
docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
|
|
3720
|
+
examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
|
|
3721
|
+
verification: nonEmptyStrings(contract?.verify),
|
|
3722
|
+
recovery: isRecord3(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
|
|
3723
|
+
agentContract: isRecord3(contract) && isRecord3(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3405
3727
|
// src/modules/crm.ts
|
|
3406
3728
|
var CrmModule = class {
|
|
3407
3729
|
constructor(client) {
|
|
@@ -3803,7 +4125,7 @@ function createTopoloWidgetError(error) {
|
|
|
3803
4125
|
}
|
|
3804
4126
|
function validateTopoloWidgetResponse(response) {
|
|
3805
4127
|
const errors = [];
|
|
3806
|
-
if (!
|
|
4128
|
+
if (!isRecord4(response)) {
|
|
3807
4129
|
return { ok: false, errors: ["response must be an object"] };
|
|
3808
4130
|
}
|
|
3809
4131
|
if (typeof response.success !== "boolean") {
|
|
@@ -3822,7 +4144,7 @@ function validateTopoloWidgetResponse(response) {
|
|
|
3822
4144
|
return { ok: errors.length === 0, errors };
|
|
3823
4145
|
}
|
|
3824
4146
|
function validateWidget(widget, path, errors) {
|
|
3825
|
-
if (!
|
|
4147
|
+
if (!isRecord4(widget)) {
|
|
3826
4148
|
errors.push(`${path} must be an object`);
|
|
3827
4149
|
return;
|
|
3828
4150
|
}
|
|
@@ -3849,7 +4171,7 @@ function validateWidget(widget, path, errors) {
|
|
|
3849
4171
|
case "chart":
|
|
3850
4172
|
validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
|
|
3851
4173
|
validateNonEmptyString(widget.title, `${path}.title`, errors);
|
|
3852
|
-
if (!
|
|
4174
|
+
if (!isRecord4(widget.data)) {
|
|
3853
4175
|
errors.push(`${path}.data must be an object`);
|
|
3854
4176
|
} else {
|
|
3855
4177
|
validateArray2(widget.data.labels, `${path}.data.labels`, errors);
|
|
@@ -3865,7 +4187,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
3865
4187
|
errors.push(`${path} must be an object`);
|
|
3866
4188
|
return;
|
|
3867
4189
|
}
|
|
3868
|
-
if (!
|
|
4190
|
+
if (!isRecord4(snapshot)) {
|
|
3869
4191
|
errors.push(`${path} must be an object`);
|
|
3870
4192
|
return;
|
|
3871
4193
|
}
|
|
@@ -3885,7 +4207,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
3885
4207
|
}
|
|
3886
4208
|
snapshot.availableMetrics.forEach((metric, index) => {
|
|
3887
4209
|
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
3888
|
-
if (!
|
|
4210
|
+
if (!isRecord4(metric)) {
|
|
3889
4211
|
errors.push(`${metricPath} must be an object`);
|
|
3890
4212
|
return;
|
|
3891
4213
|
}
|
|
@@ -3906,7 +4228,7 @@ function validateArray2(value, path, errors) {
|
|
|
3906
4228
|
errors.push(`${path} must be an array`);
|
|
3907
4229
|
}
|
|
3908
4230
|
}
|
|
3909
|
-
function
|
|
4231
|
+
function isRecord4(value) {
|
|
3910
4232
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3911
4233
|
}
|
|
3912
4234
|
function toIsoTimestamp(input) {
|
|
@@ -3943,6 +4265,7 @@ export {
|
|
|
3943
4265
|
TopoloOAuth,
|
|
3944
4266
|
TopoloPermissionError,
|
|
3945
4267
|
TopoloSdkError,
|
|
4268
|
+
actionContractCompleteness,
|
|
3946
4269
|
applicationRequirementScopes,
|
|
3947
4270
|
auditAllApplicationRequirements,
|
|
3948
4271
|
auditApplicationRequirements,
|
|
@@ -3955,6 +4278,7 @@ export {
|
|
|
3955
4278
|
defineTopoloRecentActivityWidget,
|
|
3956
4279
|
defineTopoloStatsWidget,
|
|
3957
4280
|
digestTopoloActionCatalog,
|
|
4281
|
+
enrichActionContract,
|
|
3958
4282
|
requirementsForApplication,
|
|
3959
4283
|
resolveCatalogServiceUrl,
|
|
3960
4284
|
resolveServiceUrl,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/sdk",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"topoloPublishedVersion": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
|
+
"topoloPublishedVersion": "0.9.5",
|
|
5
5
|
"description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -23,13 +23,16 @@
|
|
|
23
23
|
"dist"
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
|
-
"
|
|
26
|
+
"generate:services": "node scripts/generate-services.mjs",
|
|
27
|
+
"check:generated": "node scripts/check-generated.mjs",
|
|
28
|
+
"prebuild": "node scripts/generate-services.mjs",
|
|
27
29
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean && tsup src/node/credential-store.ts --format esm,cjs --dts --out-dir dist/node",
|
|
28
30
|
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
29
|
-
"typecheck": "tsc --noEmit",
|
|
31
|
+
"typecheck": "node scripts/generate-services.mjs && tsc --noEmit",
|
|
30
32
|
"test": "vitest run",
|
|
31
33
|
"test:watch": "vitest",
|
|
32
|
-
"prepublishOnly": "npm run build",
|
|
34
|
+
"prepublishOnly": "npm run build && npm run verify:package",
|
|
35
|
+
"verify:package": "node ../../tooling/verify-npm-package.mjs .",
|
|
33
36
|
"coverage": "vitest run --coverage"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|