@topolo/sdk 0.9.7 → 0.9.9
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 +12 -0
- package/dist/index.cjs +491 -66
- package/dist/index.d.cts +149 -27
- package/dist/index.d.ts +149 -27
- package/dist/index.js +488 -66
- package/dist/node/credential-store.cjs +32 -3
- package/dist/node/credential-store.js +32 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -28,19 +28,54 @@ var TopoloSdkError = class extends Error {
|
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
var TopoloAuthError = class extends TopoloSdkError {
|
|
31
|
-
|
|
31
|
+
status;
|
|
32
|
+
body;
|
|
33
|
+
service;
|
|
34
|
+
path;
|
|
35
|
+
requestId;
|
|
36
|
+
retryAfter;
|
|
37
|
+
constructor(message, code = "auth_error", options = {}) {
|
|
32
38
|
super(code, message);
|
|
33
39
|
this.name = "TopoloAuthError";
|
|
40
|
+
this.status = options.status ?? 401;
|
|
41
|
+
this.body = options.body;
|
|
42
|
+
this.service = options.service;
|
|
43
|
+
this.path = options.path;
|
|
44
|
+
this.requestId = options.requestId;
|
|
45
|
+
this.retryAfter = options.retryAfter;
|
|
34
46
|
}
|
|
35
47
|
};
|
|
36
48
|
var TopoloPermissionError = class extends TopoloSdkError {
|
|
37
49
|
required;
|
|
38
|
-
|
|
50
|
+
status;
|
|
51
|
+
body;
|
|
52
|
+
service;
|
|
53
|
+
path;
|
|
54
|
+
requestId;
|
|
55
|
+
retryAfter;
|
|
56
|
+
constructor(message, required, options = {}) {
|
|
39
57
|
super("permission_denied", message);
|
|
40
58
|
this.name = "TopoloPermissionError";
|
|
41
59
|
this.required = required;
|
|
60
|
+
this.status = options.status ?? 403;
|
|
61
|
+
this.body = options.body;
|
|
62
|
+
this.service = options.service;
|
|
63
|
+
this.path = options.path;
|
|
64
|
+
this.requestId = options.requestId;
|
|
65
|
+
this.retryAfter = options.retryAfter;
|
|
42
66
|
}
|
|
43
67
|
};
|
|
68
|
+
function extractHttpErrorCode(body) {
|
|
69
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
|
|
70
|
+
const record = body;
|
|
71
|
+
if (typeof record.code === "string" && record.code.trim()) return record.code.trim();
|
|
72
|
+
const nested = record.error;
|
|
73
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
74
|
+
const code = nested.code;
|
|
75
|
+
if (typeof code === "string" && code.trim()) return code.trim();
|
|
76
|
+
}
|
|
77
|
+
return "http_error";
|
|
78
|
+
}
|
|
44
79
|
var TopoloConfirmationRequiredError = class extends TopoloSdkError {
|
|
45
80
|
constructor(message) {
|
|
46
81
|
super("confirmation_required", message);
|
|
@@ -52,13 +87,17 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
52
87
|
body;
|
|
53
88
|
service;
|
|
54
89
|
path;
|
|
55
|
-
|
|
56
|
-
|
|
90
|
+
requestId;
|
|
91
|
+
retryAfter;
|
|
92
|
+
constructor(service, path, status, body, message, options = {}) {
|
|
93
|
+
super(extractHttpErrorCode(body), message ?? `HTTP ${status} from ${service}${path}`);
|
|
57
94
|
this.name = "TopoloHttpError";
|
|
58
95
|
this.status = status;
|
|
59
96
|
this.body = body;
|
|
60
97
|
this.service = service;
|
|
61
98
|
this.path = path;
|
|
99
|
+
this.requestId = options.requestId;
|
|
100
|
+
this.retryAfter = options.retryAfter;
|
|
62
101
|
}
|
|
63
102
|
};
|
|
64
103
|
|
|
@@ -67,6 +106,7 @@ var APP_API_URLS = {
|
|
|
67
106
|
agent: "https://agent.topolo.app",
|
|
68
107
|
auth: "https://auth.topolo.app",
|
|
69
108
|
backup: "https://backup.topolo.app",
|
|
109
|
+
blog: "https://blog.topolo.app",
|
|
70
110
|
books: "https://books.topolo.app",
|
|
71
111
|
brand: "https://brand.topolo.app",
|
|
72
112
|
bugfix: "https://bugfix.topolo.app",
|
|
@@ -79,6 +119,7 @@ var APP_API_URLS = {
|
|
|
79
119
|
compose: "https://compose.topolo.app",
|
|
80
120
|
consent: "https://consent.topolo.app",
|
|
81
121
|
crm: "https://crm.topolo.app",
|
|
122
|
+
design: "https://design.topolo.app",
|
|
82
123
|
developers: "https://developers.topolo.app",
|
|
83
124
|
director: "https://director.topolo.app",
|
|
84
125
|
feed: "https://feed.topolo.app",
|
|
@@ -164,7 +205,9 @@ var APPLICATIONS = {
|
|
|
164
205
|
"domains": [
|
|
165
206
|
"agent.topolo.app"
|
|
166
207
|
],
|
|
167
|
-
"routes": [
|
|
208
|
+
"routes": [
|
|
209
|
+
"agent.topolo.app"
|
|
210
|
+
],
|
|
168
211
|
"appId": "agent"
|
|
169
212
|
},
|
|
170
213
|
{
|
|
@@ -225,6 +268,38 @@ var APPLICATIONS = {
|
|
|
225
268
|
}
|
|
226
269
|
]
|
|
227
270
|
},
|
|
271
|
+
"blog": {
|
|
272
|
+
"id": "blog",
|
|
273
|
+
"appDir": "TopoloBlog",
|
|
274
|
+
"name": "topolo-blog",
|
|
275
|
+
"packageName": "topolo-blog",
|
|
276
|
+
"productionUrl": "https://blog.topolo.app",
|
|
277
|
+
"services": [
|
|
278
|
+
"blog"
|
|
279
|
+
],
|
|
280
|
+
"deployTargets": [
|
|
281
|
+
{
|
|
282
|
+
"name": "topolo-blog",
|
|
283
|
+
"kind": "worker",
|
|
284
|
+
"apiUrl": "https://blog.topolo.app",
|
|
285
|
+
"domains": [
|
|
286
|
+
"blog.topolo.app"
|
|
287
|
+
],
|
|
288
|
+
"routes": [
|
|
289
|
+
"blog.topolo.app"
|
|
290
|
+
],
|
|
291
|
+
"appId": "blog"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
"name": "topolo-blog-api",
|
|
295
|
+
"kind": "worker",
|
|
296
|
+
"apiUrl": null,
|
|
297
|
+
"domains": [],
|
|
298
|
+
"routes": [],
|
|
299
|
+
"appId": null
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
},
|
|
228
303
|
"books": {
|
|
229
304
|
"id": "books",
|
|
230
305
|
"appDir": "TopoloBooks",
|
|
@@ -242,7 +317,9 @@ var APPLICATIONS = {
|
|
|
242
317
|
"domains": [
|
|
243
318
|
"books.topolo.app"
|
|
244
319
|
],
|
|
245
|
-
"routes": [
|
|
320
|
+
"routes": [
|
|
321
|
+
"books.topolo.app"
|
|
322
|
+
],
|
|
246
323
|
"appId": "books"
|
|
247
324
|
},
|
|
248
325
|
{
|
|
@@ -272,7 +349,9 @@ var APPLICATIONS = {
|
|
|
272
349
|
"domains": [
|
|
273
350
|
"brand.topolo.app"
|
|
274
351
|
],
|
|
275
|
-
"routes": [
|
|
352
|
+
"routes": [
|
|
353
|
+
"brand.topolo.app"
|
|
354
|
+
],
|
|
276
355
|
"appId": "brand"
|
|
277
356
|
},
|
|
278
357
|
{
|
|
@@ -390,7 +469,9 @@ var APPLICATIONS = {
|
|
|
390
469
|
"domains": [
|
|
391
470
|
"campaigns.topolo.app"
|
|
392
471
|
],
|
|
393
|
-
"routes": [
|
|
472
|
+
"routes": [
|
|
473
|
+
"campaigns.topolo.app"
|
|
474
|
+
],
|
|
394
475
|
"appId": "campaigns"
|
|
395
476
|
},
|
|
396
477
|
{
|
|
@@ -444,7 +525,9 @@ var APPLICATIONS = {
|
|
|
444
525
|
"domains": [
|
|
445
526
|
"chat.topolo.app"
|
|
446
527
|
],
|
|
447
|
-
"routes": [
|
|
528
|
+
"routes": [
|
|
529
|
+
"chat.topolo.app"
|
|
530
|
+
],
|
|
448
531
|
"appId": "chat"
|
|
449
532
|
},
|
|
450
533
|
{
|
|
@@ -530,7 +613,9 @@ var APPLICATIONS = {
|
|
|
530
613
|
"domains": [
|
|
531
614
|
"compose.topolo.app"
|
|
532
615
|
],
|
|
533
|
-
"routes": [
|
|
616
|
+
"routes": [
|
|
617
|
+
"compose.topolo.app"
|
|
618
|
+
],
|
|
534
619
|
"appId": "compose"
|
|
535
620
|
},
|
|
536
621
|
{
|
|
@@ -560,7 +645,9 @@ var APPLICATIONS = {
|
|
|
560
645
|
"domains": [
|
|
561
646
|
"consent.topolo.app"
|
|
562
647
|
],
|
|
563
|
-
"routes": [
|
|
648
|
+
"routes": [
|
|
649
|
+
"consent.topolo.app"
|
|
650
|
+
],
|
|
564
651
|
"appId": "consent"
|
|
565
652
|
},
|
|
566
653
|
{
|
|
@@ -598,11 +685,45 @@ var APPLICATIONS = {
|
|
|
598
685
|
"domains": [
|
|
599
686
|
"crm.topolo.app"
|
|
600
687
|
],
|
|
601
|
-
"routes": [
|
|
688
|
+
"routes": [
|
|
689
|
+
"crm.topolo.app"
|
|
690
|
+
],
|
|
602
691
|
"appId": "crm"
|
|
603
692
|
}
|
|
604
693
|
]
|
|
605
694
|
},
|
|
695
|
+
"design": {
|
|
696
|
+
"id": "design",
|
|
697
|
+
"appDir": "TopoloDesign",
|
|
698
|
+
"name": "topolo-design",
|
|
699
|
+
"packageName": "@topolo-io/design",
|
|
700
|
+
"productionUrl": "https://design.topolo.app",
|
|
701
|
+
"services": [
|
|
702
|
+
"design"
|
|
703
|
+
],
|
|
704
|
+
"deployTargets": [
|
|
705
|
+
{
|
|
706
|
+
"name": "topolo-design",
|
|
707
|
+
"kind": "worker",
|
|
708
|
+
"apiUrl": "https://design.topolo.app",
|
|
709
|
+
"domains": [
|
|
710
|
+
"design.topolo.app"
|
|
711
|
+
],
|
|
712
|
+
"routes": [
|
|
713
|
+
"design.topolo.app"
|
|
714
|
+
],
|
|
715
|
+
"appId": "design"
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
"name": "topolo-design-api",
|
|
719
|
+
"kind": "worker",
|
|
720
|
+
"apiUrl": null,
|
|
721
|
+
"domains": [],
|
|
722
|
+
"routes": [],
|
|
723
|
+
"appId": null
|
|
724
|
+
}
|
|
725
|
+
]
|
|
726
|
+
},
|
|
606
727
|
"developers": {
|
|
607
728
|
"id": "developers",
|
|
608
729
|
"appDir": "TopoloDevelopers",
|
|
@@ -628,7 +749,9 @@ var APPLICATIONS = {
|
|
|
628
749
|
"domains": [
|
|
629
750
|
"developers.topolo.app"
|
|
630
751
|
],
|
|
631
|
-
"routes": [
|
|
752
|
+
"routes": [
|
|
753
|
+
"developers.topolo.app"
|
|
754
|
+
],
|
|
632
755
|
"appId": "developers"
|
|
633
756
|
},
|
|
634
757
|
{
|
|
@@ -658,7 +781,9 @@ var APPLICATIONS = {
|
|
|
658
781
|
"domains": [
|
|
659
782
|
"director.topolo.app"
|
|
660
783
|
],
|
|
661
|
-
"routes": [
|
|
784
|
+
"routes": [
|
|
785
|
+
"director.topolo.app"
|
|
786
|
+
],
|
|
662
787
|
"appId": "director"
|
|
663
788
|
},
|
|
664
789
|
{
|
|
@@ -766,7 +891,9 @@ var APPLICATIONS = {
|
|
|
766
891
|
"domains": [
|
|
767
892
|
"flow.topolo.app"
|
|
768
893
|
],
|
|
769
|
-
"routes": [
|
|
894
|
+
"routes": [
|
|
895
|
+
"flow.topolo.app"
|
|
896
|
+
],
|
|
770
897
|
"appId": "flow"
|
|
771
898
|
},
|
|
772
899
|
{
|
|
@@ -796,7 +923,9 @@ var APPLICATIONS = {
|
|
|
796
923
|
"domains": [
|
|
797
924
|
"forecast.topolo.app"
|
|
798
925
|
],
|
|
799
|
-
"routes": [
|
|
926
|
+
"routes": [
|
|
927
|
+
"forecast.topolo.app"
|
|
928
|
+
],
|
|
800
929
|
"appId": "forecast"
|
|
801
930
|
},
|
|
802
931
|
{
|
|
@@ -826,7 +955,9 @@ var APPLICATIONS = {
|
|
|
826
955
|
"domains": [
|
|
827
956
|
"forms.topolo.app"
|
|
828
957
|
],
|
|
829
|
-
"routes": [
|
|
958
|
+
"routes": [
|
|
959
|
+
"forms.topolo.app"
|
|
960
|
+
],
|
|
830
961
|
"appId": "forms"
|
|
831
962
|
},
|
|
832
963
|
{
|
|
@@ -888,7 +1019,9 @@ var APPLICATIONS = {
|
|
|
888
1019
|
"domains": [
|
|
889
1020
|
"insights.topolo.app"
|
|
890
1021
|
],
|
|
891
|
-
"routes": [
|
|
1022
|
+
"routes": [
|
|
1023
|
+
"insights.topolo.app"
|
|
1024
|
+
],
|
|
892
1025
|
"appId": "insights"
|
|
893
1026
|
},
|
|
894
1027
|
{
|
|
@@ -918,7 +1051,9 @@ var APPLICATIONS = {
|
|
|
918
1051
|
"domains": [
|
|
919
1052
|
"inventory.topolo.app"
|
|
920
1053
|
],
|
|
921
|
-
"routes": [
|
|
1054
|
+
"routes": [
|
|
1055
|
+
"inventory.topolo.app"
|
|
1056
|
+
],
|
|
922
1057
|
"appId": "inventory"
|
|
923
1058
|
},
|
|
924
1059
|
{
|
|
@@ -1004,7 +1139,9 @@ var APPLICATIONS = {
|
|
|
1004
1139
|
"domains": [
|
|
1005
1140
|
"mail.topolo.app"
|
|
1006
1141
|
],
|
|
1007
|
-
"routes": [
|
|
1142
|
+
"routes": [
|
|
1143
|
+
"mail.topolo.app"
|
|
1144
|
+
],
|
|
1008
1145
|
"appId": "mail"
|
|
1009
1146
|
},
|
|
1010
1147
|
{
|
|
@@ -1270,7 +1407,9 @@ var APPLICATIONS = {
|
|
|
1270
1407
|
"domains": [
|
|
1271
1408
|
"pay.topolo.app"
|
|
1272
1409
|
],
|
|
1273
|
-
"routes": [
|
|
1410
|
+
"routes": [
|
|
1411
|
+
"pay.topolo.app"
|
|
1412
|
+
],
|
|
1274
1413
|
"appId": "pay"
|
|
1275
1414
|
},
|
|
1276
1415
|
{
|
|
@@ -1300,7 +1439,9 @@ var APPLICATIONS = {
|
|
|
1300
1439
|
"domains": [
|
|
1301
1440
|
"people.topolo.app"
|
|
1302
1441
|
],
|
|
1303
|
-
"routes": [
|
|
1442
|
+
"routes": [
|
|
1443
|
+
"people.topolo.app"
|
|
1444
|
+
],
|
|
1304
1445
|
"appId": "people"
|
|
1305
1446
|
},
|
|
1306
1447
|
{
|
|
@@ -1355,7 +1496,9 @@ var APPLICATIONS = {
|
|
|
1355
1496
|
"domains": [
|
|
1356
1497
|
"quro.topolo.app"
|
|
1357
1498
|
],
|
|
1358
|
-
"routes": [
|
|
1499
|
+
"routes": [
|
|
1500
|
+
"quro.topolo.app"
|
|
1501
|
+
],
|
|
1359
1502
|
"appId": null
|
|
1360
1503
|
}
|
|
1361
1504
|
]
|
|
@@ -1377,7 +1520,9 @@ var APPLICATIONS = {
|
|
|
1377
1520
|
"domains": [
|
|
1378
1521
|
"roadmapper.topolo.app"
|
|
1379
1522
|
],
|
|
1380
|
-
"routes": [
|
|
1523
|
+
"routes": [
|
|
1524
|
+
"roadmapper.topolo.app"
|
|
1525
|
+
],
|
|
1381
1526
|
"appId": "roadmapper"
|
|
1382
1527
|
},
|
|
1383
1528
|
{
|
|
@@ -1407,7 +1552,9 @@ var APPLICATIONS = {
|
|
|
1407
1552
|
"domains": [
|
|
1408
1553
|
"sign.topolo.app"
|
|
1409
1554
|
],
|
|
1410
|
-
"routes": [
|
|
1555
|
+
"routes": [
|
|
1556
|
+
"sign.topolo.app"
|
|
1557
|
+
],
|
|
1411
1558
|
"appId": "sign"
|
|
1412
1559
|
},
|
|
1413
1560
|
{
|
|
@@ -1521,7 +1668,9 @@ var APPLICATIONS = {
|
|
|
1521
1668
|
"domains": [
|
|
1522
1669
|
"spaces.topolo.app"
|
|
1523
1670
|
],
|
|
1524
|
-
"routes": [
|
|
1671
|
+
"routes": [
|
|
1672
|
+
"spaces.topolo.app"
|
|
1673
|
+
],
|
|
1525
1674
|
"appId": "spaces"
|
|
1526
1675
|
},
|
|
1527
1676
|
{
|
|
@@ -1639,7 +1788,9 @@ var APPLICATIONS = {
|
|
|
1639
1788
|
"domains": [
|
|
1640
1789
|
"survey.topolo.app"
|
|
1641
1790
|
],
|
|
1642
|
-
"routes": [
|
|
1791
|
+
"routes": [
|
|
1792
|
+
"survey.topolo.app"
|
|
1793
|
+
],
|
|
1643
1794
|
"appId": "survey"
|
|
1644
1795
|
},
|
|
1645
1796
|
{
|
|
@@ -2437,6 +2588,168 @@ function isMultipleOf(value, divisor) {
|
|
|
2437
2588
|
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
|
|
2438
2589
|
}
|
|
2439
2590
|
|
|
2591
|
+
// src/action-upload.ts
|
|
2592
|
+
function parseTopoloActionUploadContract(value) {
|
|
2593
|
+
if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
|
|
2594
|
+
throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
|
|
2595
|
+
}
|
|
2596
|
+
const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
|
|
2597
|
+
const fileNameField = requiredField(value.fileNameField, "fileNameField");
|
|
2598
|
+
const maxBytes = Number(value.maxBytes);
|
|
2599
|
+
const acceptedContentTypes = Array.isArray(value.acceptedContentTypes) ? value.acceptedContentTypes.map((item) => String(item).trim().toLowerCase()).filter(Boolean) : [];
|
|
2600
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || acceptedContentTypes.length === 0) {
|
|
2601
|
+
throw new TopoloSdkError("invalid_upload_contract", "The action upload contract must declare maxBytes and acceptedContentTypes.");
|
|
2602
|
+
}
|
|
2603
|
+
if (value.profile === "inline-base64") {
|
|
2604
|
+
return {
|
|
2605
|
+
profile: "inline-base64",
|
|
2606
|
+
contentField: requiredField(value.contentField, "contentField"),
|
|
2607
|
+
contentTypeField,
|
|
2608
|
+
fileNameField,
|
|
2609
|
+
maxBytes,
|
|
2610
|
+
acceptedContentTypes
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
const uploadMethod = value.uploadMethod === "PUT" || value.uploadMethod === "POST" ? value.uploadMethod : null;
|
|
2614
|
+
const uploadUrlPath = requiredPath(value.uploadUrlPath, "uploadUrlPath");
|
|
2615
|
+
const complete = value.complete === void 0 ? void 0 : parseCompleteContract(value.complete);
|
|
2616
|
+
if (!uploadMethod) throw new TopoloSdkError("invalid_upload_contract", "The action upload contract has an invalid uploadMethod.");
|
|
2617
|
+
return {
|
|
2618
|
+
profile: "url-session",
|
|
2619
|
+
fileNameField,
|
|
2620
|
+
contentTypeField,
|
|
2621
|
+
sizeField: requiredField(value.sizeField, "sizeField"),
|
|
2622
|
+
maxBytes,
|
|
2623
|
+
acceptedContentTypes,
|
|
2624
|
+
uploadUrlPath,
|
|
2625
|
+
uploadMethod,
|
|
2626
|
+
...complete ? { complete } : {}
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
function prepareTopoloActionUploadInput(contractValue, content, input, options) {
|
|
2630
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2631
|
+
if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
|
|
2632
|
+
const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
2633
|
+
if (bytes.byteLength === 0) {
|
|
2634
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2635
|
+
}
|
|
2636
|
+
if (bytes.byteLength > contract.maxBytes) {
|
|
2637
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2638
|
+
}
|
|
2639
|
+
return mapUploadInput(contract, bytesToBase64(bytes), input, options);
|
|
2640
|
+
}
|
|
2641
|
+
function prepareTopoloActionBase64UploadInput(contractValue, contentBase64, input, options) {
|
|
2642
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2643
|
+
if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
|
|
2644
|
+
if (!isCanonicalBase64(contentBase64)) {
|
|
2645
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
2646
|
+
}
|
|
2647
|
+
const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
|
|
2648
|
+
const byteLength = contentBase64.length / 4 * 3 - padding;
|
|
2649
|
+
if (byteLength === 0) throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2650
|
+
if (byteLength > contract.maxBytes) {
|
|
2651
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2652
|
+
}
|
|
2653
|
+
return mapUploadInput(contract, contentBase64, input, options);
|
|
2654
|
+
}
|
|
2655
|
+
function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, options) {
|
|
2656
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2657
|
+
if (contract.profile !== "url-session") {
|
|
2658
|
+
throw new TopoloSdkError("invalid_upload_contract", "This action does not publish a URL-session upload contract.");
|
|
2659
|
+
}
|
|
2660
|
+
validateUploadMetadata(contract, byteLength, options);
|
|
2661
|
+
return {
|
|
2662
|
+
contract,
|
|
2663
|
+
input: {
|
|
2664
|
+
...input,
|
|
2665
|
+
[contract.fileNameField]: options.fileName.trim(),
|
|
2666
|
+
[contract.contentTypeField]: options.contentType.trim().toLowerCase(),
|
|
2667
|
+
[contract.sizeField]: byteLength
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
function topoloUploadResponseValue(response, path) {
|
|
2672
|
+
return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
|
|
2673
|
+
}
|
|
2674
|
+
function decodeTopoloActionBase64(contentBase64) {
|
|
2675
|
+
if (!isCanonicalBase64(contentBase64)) {
|
|
2676
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
2677
|
+
}
|
|
2678
|
+
const binary = atob(contentBase64);
|
|
2679
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
2680
|
+
}
|
|
2681
|
+
function mapUploadInput(contract, contentBase64, input, options) {
|
|
2682
|
+
validateUploadMetadata(contract, Math.max(1, decodedBase64Size(contentBase64)), options);
|
|
2683
|
+
const fileName = options.fileName.trim();
|
|
2684
|
+
const contentType = options.contentType.trim().toLowerCase();
|
|
2685
|
+
return {
|
|
2686
|
+
...input,
|
|
2687
|
+
[contract.contentField]: contentBase64,
|
|
2688
|
+
[contract.contentTypeField]: contentType,
|
|
2689
|
+
[contract.fileNameField]: fileName
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
function validateUploadMetadata(contract, byteLength, options) {
|
|
2693
|
+
const fileName = options.fileName.trim();
|
|
2694
|
+
const contentType = options.contentType.trim().toLowerCase();
|
|
2695
|
+
if (!fileName) throw new TopoloSdkError("invalid_upload_file_name", "Upload fileName must not be empty.");
|
|
2696
|
+
if (!Number.isSafeInteger(byteLength) || byteLength <= 0) {
|
|
2697
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2698
|
+
}
|
|
2699
|
+
if (byteLength > contract.maxBytes) {
|
|
2700
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2701
|
+
}
|
|
2702
|
+
if (!matchesContentType(contentType, contract.acceptedContentTypes)) {
|
|
2703
|
+
throw new TopoloSdkError("unsupported_upload_content_type", `Content type "${contentType}" is not accepted by this action.`);
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
function decodedBase64Size(contentBase64) {
|
|
2707
|
+
const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
|
|
2708
|
+
return contentBase64.length / 4 * 3 - padding;
|
|
2709
|
+
}
|
|
2710
|
+
function parseCompleteContract(value) {
|
|
2711
|
+
if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
|
|
2712
|
+
return {
|
|
2713
|
+
actionId: requiredPath(value.actionId, "complete.actionId"),
|
|
2714
|
+
inputField: requiredField(value.inputField, "complete.inputField"),
|
|
2715
|
+
responsePath: requiredPath(value.responsePath, "complete.responsePath")
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
function requiredPath(value, name) {
|
|
2719
|
+
const path = typeof value === "string" ? value.trim() : "";
|
|
2720
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path)) {
|
|
2721
|
+
throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
|
|
2722
|
+
}
|
|
2723
|
+
return path;
|
|
2724
|
+
}
|
|
2725
|
+
function unsupportedInlineProfile() {
|
|
2726
|
+
return new TopoloSdkError("invalid_upload_contract", "This action does not publish an inline-base64 upload contract.");
|
|
2727
|
+
}
|
|
2728
|
+
function isCanonicalBase64(value) {
|
|
2729
|
+
return value.length > 0 && value.length % 4 === 0 && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value);
|
|
2730
|
+
}
|
|
2731
|
+
function requiredField(value, name) {
|
|
2732
|
+
const field = typeof value === "string" ? value.trim() : "";
|
|
2733
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
|
|
2734
|
+
throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
|
|
2735
|
+
}
|
|
2736
|
+
return field;
|
|
2737
|
+
}
|
|
2738
|
+
function matchesContentType(contentType, accepted) {
|
|
2739
|
+
return accepted.some((candidate) => candidate === "*/*" || candidate === contentType || candidate.endsWith("/*") && contentType.startsWith(candidate.slice(0, -1)));
|
|
2740
|
+
}
|
|
2741
|
+
function bytesToBase64(bytes) {
|
|
2742
|
+
let binary = "";
|
|
2743
|
+
const chunkSize = 32768;
|
|
2744
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
2745
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
|
|
2746
|
+
}
|
|
2747
|
+
return btoa(binary);
|
|
2748
|
+
}
|
|
2749
|
+
function isRecord2(value) {
|
|
2750
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2440
2753
|
// src/client.ts
|
|
2441
2754
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
2442
2755
|
var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
|
|
@@ -2514,6 +2827,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
2514
2827
|
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
2515
2828
|
}
|
|
2516
2829
|
}
|
|
2830
|
+
if (opts.body !== void 0 && opts.rawBody !== void 0) {
|
|
2831
|
+
throw new TopoloSdkError("invalid_request_body", "body and rawBody are mutually exclusive.");
|
|
2832
|
+
}
|
|
2517
2833
|
const headers = new Headers();
|
|
2518
2834
|
headers.set("Accept", "application/json");
|
|
2519
2835
|
if (opts.body !== void 0) headers.set("Content-Type", "application/json");
|
|
@@ -2548,7 +2864,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2548
2864
|
return await this.fetchImpl(urlStr, {
|
|
2549
2865
|
method,
|
|
2550
2866
|
headers: new Headers(headers),
|
|
2551
|
-
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
2867
|
+
body: opts.rawBody ?? (opts.body !== void 0 ? JSON.stringify(opts.body) : null),
|
|
2552
2868
|
signal
|
|
2553
2869
|
});
|
|
2554
2870
|
} finally {
|
|
@@ -2596,14 +2912,32 @@ var TopoloClient = class _TopoloClient {
|
|
|
2596
2912
|
error: message,
|
|
2597
2913
|
status: res.status
|
|
2598
2914
|
});
|
|
2599
|
-
|
|
2915
|
+
const errorContext = {
|
|
2916
|
+
status: res.status,
|
|
2917
|
+
body: parsed,
|
|
2918
|
+
service: resolvedService.serviceKey,
|
|
2919
|
+
path: opts.path,
|
|
2920
|
+
requestId: res.headers.get("X-Topolo-Request-Id") || requestId,
|
|
2921
|
+
retryAfter: res.headers.get("Retry-After") || void 0
|
|
2922
|
+
};
|
|
2923
|
+
if (res.status === 401) {
|
|
2924
|
+
throw new TopoloAuthError(describeError(parsed, "Unauthorized"), "auth_error", errorContext);
|
|
2925
|
+
}
|
|
2600
2926
|
if (res.status === 403) {
|
|
2601
2927
|
throw new TopoloPermissionError(
|
|
2602
2928
|
describeError(parsed, "Permission denied"),
|
|
2603
|
-
extractRequired(parsed)
|
|
2929
|
+
extractRequired(parsed),
|
|
2930
|
+
errorContext
|
|
2604
2931
|
);
|
|
2605
2932
|
}
|
|
2606
|
-
throw new TopoloHttpError(
|
|
2933
|
+
throw new TopoloHttpError(
|
|
2934
|
+
resolvedService.serviceKey,
|
|
2935
|
+
opts.path,
|
|
2936
|
+
res.status,
|
|
2937
|
+
parsed,
|
|
2938
|
+
message,
|
|
2939
|
+
errorContext
|
|
2940
|
+
);
|
|
2607
2941
|
}
|
|
2608
2942
|
this.emit({
|
|
2609
2943
|
phase: "response",
|
|
@@ -2762,7 +3096,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2762
3096
|
}
|
|
2763
3097
|
const entries = [];
|
|
2764
3098
|
for (const item of raw) {
|
|
2765
|
-
if (!
|
|
3099
|
+
if (!isRecord3(item)) continue;
|
|
2766
3100
|
const s = item;
|
|
2767
3101
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
2768
3102
|
if (!appId) continue;
|
|
@@ -2837,8 +3171,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2837
3171
|
"app_topolo_auth.service_api_key_resources.list",
|
|
2838
3172
|
{ appId: service.appId }
|
|
2839
3173
|
);
|
|
2840
|
-
const payload =
|
|
2841
|
-
if (!
|
|
3174
|
+
const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
|
|
3175
|
+
if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
|
|
2842
3176
|
throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
|
|
2843
3177
|
}
|
|
2844
3178
|
return {
|
|
@@ -2944,6 +3278,71 @@ var TopoloClient = class _TopoloClient {
|
|
|
2944
3278
|
const entry = await this.getAction(action);
|
|
2945
3279
|
return this.executeResolvedAction(entry, input, options);
|
|
2946
3280
|
}
|
|
3281
|
+
async uploadAction(action, content, input, options) {
|
|
3282
|
+
const entry = await this.getAction(action);
|
|
3283
|
+
const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
|
|
3284
|
+
if (contract.profile === "url-session") {
|
|
3285
|
+
const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
3286
|
+
return this.executeUrlSessionUpload(entry, contract, bytes, input, options);
|
|
3287
|
+
}
|
|
3288
|
+
const uploadInput = prepareTopoloActionUploadInput(entry.agentContract?.upload, content, input, options);
|
|
3289
|
+
return this.executeResolvedAction(entry, uploadInput, options);
|
|
3290
|
+
}
|
|
3291
|
+
async uploadActionBase64(action, contentBase64, input, options) {
|
|
3292
|
+
const entry = await this.getAction(action);
|
|
3293
|
+
const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
|
|
3294
|
+
if (contract.profile === "url-session") {
|
|
3295
|
+
return this.executeUrlSessionUpload(entry, contract, decodeTopoloActionBase64(contentBase64), input, options);
|
|
3296
|
+
}
|
|
3297
|
+
const uploadInput = prepareTopoloActionBase64UploadInput(entry.agentContract?.upload, contentBase64, input, options);
|
|
3298
|
+
return this.executeResolvedAction(entry, uploadInput, options);
|
|
3299
|
+
}
|
|
3300
|
+
async executeUrlSessionUpload(entry, contract, bytes, input, options) {
|
|
3301
|
+
const prepared = prepareTopoloUrlSessionUploadInput(contract, bytes.byteLength, input, options);
|
|
3302
|
+
const initiation = await this.executeResolvedAction(entry, prepared.input, options);
|
|
3303
|
+
const uploadUrl = topoloUploadResponseValue(initiation, contract.uploadUrlPath);
|
|
3304
|
+
if (typeof uploadUrl !== "string" || !uploadUrl.trim()) {
|
|
3305
|
+
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.uploadUrlPath}.`);
|
|
3306
|
+
}
|
|
3307
|
+
await this.putUrlSessionContent(entry, uploadUrl, contract.uploadMethod, bytes, options);
|
|
3308
|
+
if (!contract.complete) return initiation;
|
|
3309
|
+
const completionValue = topoloUploadResponseValue(initiation, contract.complete.responsePath);
|
|
3310
|
+
if (typeof completionValue !== "string" || !completionValue.trim()) {
|
|
3311
|
+
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.complete.responsePath}.`);
|
|
3312
|
+
}
|
|
3313
|
+
const completion = await this.getAction(contract.complete.actionId);
|
|
3314
|
+
return this.executeResolvedAction(completion, {
|
|
3315
|
+
...options.completionInput ?? {},
|
|
3316
|
+
[contract.complete.inputField]: completionValue
|
|
3317
|
+
}, options);
|
|
3318
|
+
}
|
|
3319
|
+
async putUrlSessionContent(entry, uploadUrl, method, bytes, options) {
|
|
3320
|
+
const service = entry.appSlug || entry.appId;
|
|
3321
|
+
const resolved = entry.apiBaseUrl ? { serviceKey: normalizeLookupKey(service), baseUrl: requireHttpOrigin(entry.apiBaseUrl) } : await this.resolveRequestService(service);
|
|
3322
|
+
const target = new URL(uploadUrl, resolved.baseUrl.endsWith("/") ? resolved.baseUrl : `${resolved.baseUrl}/`);
|
|
3323
|
+
const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
3324
|
+
if (target.origin === new URL(resolved.baseUrl).origin) {
|
|
3325
|
+
await this.request({
|
|
3326
|
+
service,
|
|
3327
|
+
baseUrl: resolved.baseUrl,
|
|
3328
|
+
path: target.toString(),
|
|
3329
|
+
method,
|
|
3330
|
+
rawBody: body,
|
|
3331
|
+
headers: { "Content-Type": options.contentType.trim().toLowerCase() },
|
|
3332
|
+
resource: options.resource,
|
|
3333
|
+
confirm: options.confirm
|
|
3334
|
+
});
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
const response = await this.fetchImpl(target, {
|
|
3338
|
+
method,
|
|
3339
|
+
headers: { "Content-Type": options.contentType.trim().toLowerCase() },
|
|
3340
|
+
body
|
|
3341
|
+
});
|
|
3342
|
+
if (!response.ok) {
|
|
3343
|
+
throw new TopoloHttpError(service, target.pathname, response.status, await response.text(), `Upload transport failed with HTTP ${response.status}.`);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
2947
3346
|
async validateActionInput(action, input = {}) {
|
|
2948
3347
|
const entry = await this.getAction(action);
|
|
2949
3348
|
return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
|
|
@@ -3102,7 +3501,7 @@ function resolveConfiguredAppId(slug, appIds) {
|
|
|
3102
3501
|
return null;
|
|
3103
3502
|
}
|
|
3104
3503
|
function normalizeApiKeyResourceMetadata(input) {
|
|
3105
|
-
if (!
|
|
3504
|
+
if (!isRecord3(input)) return null;
|
|
3106
3505
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
3107
3506
|
const label = readString(input.label);
|
|
3108
3507
|
const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
|
|
@@ -3136,7 +3535,7 @@ function normalizeApiKeyResourceMetadataList(input) {
|
|
|
3136
3535
|
return resources;
|
|
3137
3536
|
}
|
|
3138
3537
|
function normalizeBindableResource(input) {
|
|
3139
|
-
if (!
|
|
3538
|
+
if (!isRecord3(input)) return null;
|
|
3140
3539
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
3141
3540
|
const resourceId = readString(input.resourceId ?? input.resource_id);
|
|
3142
3541
|
if (!resourceType || !resourceId) return null;
|
|
@@ -3147,13 +3546,13 @@ function normalizeBindableResource(input) {
|
|
|
3147
3546
|
slug: readOptionalString(input.slug),
|
|
3148
3547
|
status: readOptionalString(input.status),
|
|
3149
3548
|
organizationId: readOptionalString(input.organizationId ?? input.organization_id),
|
|
3150
|
-
metadata:
|
|
3549
|
+
metadata: isRecord3(input.metadata) ? input.metadata : {}
|
|
3151
3550
|
};
|
|
3152
3551
|
}
|
|
3153
3552
|
function normalizeResourceBindings(input) {
|
|
3154
3553
|
if (!Array.isArray(input)) return [];
|
|
3155
3554
|
return input.flatMap((binding) => {
|
|
3156
|
-
if (!
|
|
3555
|
+
if (!isRecord3(binding)) return [];
|
|
3157
3556
|
const resourceType = readString(binding.resourceType ?? binding.resource_type);
|
|
3158
3557
|
const resourceId = readString(binding.resourceId ?? binding.resource_id);
|
|
3159
3558
|
return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
|
|
@@ -3166,7 +3565,7 @@ function readOptionalString(value) {
|
|
|
3166
3565
|
const normalized = readString(value);
|
|
3167
3566
|
return normalized || null;
|
|
3168
3567
|
}
|
|
3169
|
-
function
|
|
3568
|
+
function isRecord3(value) {
|
|
3170
3569
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3171
3570
|
}
|
|
3172
3571
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
@@ -3238,14 +3637,14 @@ function catalogSearchQuery(options) {
|
|
|
3238
3637
|
return query;
|
|
3239
3638
|
}
|
|
3240
3639
|
function normalizeCatalogPage(value) {
|
|
3241
|
-
if (!
|
|
3640
|
+
if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
|
|
3242
3641
|
return {
|
|
3243
3642
|
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
3244
3643
|
hasMore: value.hasMore === true
|
|
3245
3644
|
};
|
|
3246
3645
|
}
|
|
3247
3646
|
function normalizeServiceCatalogEntry(item) {
|
|
3248
|
-
if (!
|
|
3647
|
+
if (!isRecord3(item)) return null;
|
|
3249
3648
|
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
3250
3649
|
if (!appId) return null;
|
|
3251
3650
|
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
@@ -3341,10 +3740,19 @@ function mapActionInput(action, input) {
|
|
|
3341
3740
|
delete remaining[key];
|
|
3342
3741
|
return encodeURIComponent(String(value));
|
|
3343
3742
|
});
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
const
|
|
3347
|
-
|
|
3743
|
+
const schemaProperties = plainObject(plainObject(action.inputSchema).properties);
|
|
3744
|
+
const schemaOwnsQuery = Object.prototype.hasOwnProperty.call(schemaProperties, "query");
|
|
3745
|
+
const schemaOwnsBody = Object.prototype.hasOwnProperty.call(schemaProperties, "body");
|
|
3746
|
+
let explicitQuery = {};
|
|
3747
|
+
if (!schemaOwnsQuery) {
|
|
3748
|
+
explicitQuery = plainObject(remaining.query);
|
|
3749
|
+
delete remaining.query;
|
|
3750
|
+
}
|
|
3751
|
+
let explicitBody;
|
|
3752
|
+
if (!schemaOwnsBody) {
|
|
3753
|
+
explicitBody = Object.prototype.hasOwnProperty.call(remaining, "body") ? remaining.body : void 0;
|
|
3754
|
+
delete remaining.body;
|
|
3755
|
+
}
|
|
3348
3756
|
if (action.method === "GET") {
|
|
3349
3757
|
return {
|
|
3350
3758
|
path,
|
|
@@ -3503,7 +3911,7 @@ var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
|
|
|
3503
3911
|
["restore", "archive"],
|
|
3504
3912
|
["unpublish", "publish"]
|
|
3505
3913
|
]);
|
|
3506
|
-
function
|
|
3914
|
+
function isRecord4(value) {
|
|
3507
3915
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3508
3916
|
}
|
|
3509
3917
|
function nonEmptyStrings(value) {
|
|
@@ -3541,10 +3949,10 @@ function collectionFor(action, resource) {
|
|
|
3541
3949
|
return resource.endsWith("s") ? resource : `${resource}s`;
|
|
3542
3950
|
}
|
|
3543
3951
|
function isConcreteOutputSchema(schema) {
|
|
3544
|
-
if (!
|
|
3952
|
+
if (!isRecord4(schema)) return false;
|
|
3545
3953
|
if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
|
|
3546
|
-
if (schema.type === "array" &&
|
|
3547
|
-
if (
|
|
3954
|
+
if (schema.type === "array" && isRecord4(schema.items) && Object.keys(schema.items).length > 0) return true;
|
|
3955
|
+
if (isRecord4(schema.properties) && Object.keys(schema.properties).length > 0) return true;
|
|
3548
3956
|
return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
|
|
3549
3957
|
}
|
|
3550
3958
|
function resourceSchema(resource) {
|
|
@@ -3625,7 +4033,7 @@ function derivedOutputSchema(action) {
|
|
|
3625
4033
|
};
|
|
3626
4034
|
}
|
|
3627
4035
|
function sampleForSchema(schema, depth = 0) {
|
|
3628
|
-
if (!
|
|
4036
|
+
if (!isRecord4(schema) || depth > 5) return null;
|
|
3629
4037
|
if (schema.const !== void 0) return schema.const;
|
|
3630
4038
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
3631
4039
|
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1);
|
|
@@ -3639,8 +4047,8 @@ function sampleForSchema(schema, depth = 0) {
|
|
|
3639
4047
|
return schema.minLength > 1 ? "example".padEnd(schema.minLength, "x") : "example";
|
|
3640
4048
|
}
|
|
3641
4049
|
if (schema.type === "null") return null;
|
|
3642
|
-
if (schema.type === "object" ||
|
|
3643
|
-
const properties =
|
|
4050
|
+
if (schema.type === "object" || isRecord4(schema.properties)) {
|
|
4051
|
+
const properties = isRecord4(schema.properties) ? schema.properties : {};
|
|
3644
4052
|
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3645
4053
|
const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1)]);
|
|
3646
4054
|
return Object.fromEntries(entries);
|
|
@@ -3690,21 +4098,32 @@ function defaultErrors(action) {
|
|
|
3690
4098
|
const errors = [
|
|
3691
4099
|
{ status: 401, code: "authentication_required", recovery: "Authenticate again or provide a valid Topolo API key." },
|
|
3692
4100
|
{ status: 403, code: "permission_denied", recovery: `Grant the credential permission ${action.permission || action.permissionName || "required by this action"} and the required resource binding.` },
|
|
3693
|
-
{ status:
|
|
4101
|
+
{ status: 429, code: "rate_limited", recovery: "Wait for Retry-After when present, then retry without parallel duplicate calls." },
|
|
4102
|
+
{ status: 422, code: "invalid_input", recovery: "Correct the payload using inputSchema, then validate before retrying." },
|
|
4103
|
+
{ status: 503, code: "dependency_unavailable", recovery: "Retry the action. If it continues, inspect the structured error details using the request ID." }
|
|
3694
4104
|
];
|
|
3695
4105
|
if (action.method !== "GET") {
|
|
3696
4106
|
errors.push({ status: 409, code: "state_conflict", recovery: "Read the current resource state, reconcile the intended change, and retry only if still valid." });
|
|
3697
4107
|
}
|
|
3698
4108
|
return errors;
|
|
3699
4109
|
}
|
|
4110
|
+
function mergeErrors(defaults, declared) {
|
|
4111
|
+
const merged = [...defaults];
|
|
4112
|
+
for (const error of Array.isArray(declared) ? declared : []) {
|
|
4113
|
+
const index = merged.findIndex((candidate) => candidate.status === error?.status && candidate.code === error?.code);
|
|
4114
|
+
if (index === -1) merged.push(error);
|
|
4115
|
+
else merged[index] = error;
|
|
4116
|
+
}
|
|
4117
|
+
return merged;
|
|
4118
|
+
}
|
|
3700
4119
|
function enrichActionContract(manifest, action, allActions = manifest.actions || []) {
|
|
3701
4120
|
const appId = String(manifest.appId || manifest.app_id || "").trim();
|
|
3702
4121
|
const service = slugify(manifest.slug || appId.replace(/^app_/, "").replaceAll("_", "-") || manifest.name);
|
|
3703
4122
|
const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
|
|
3704
4123
|
const resource = resourceFor(action);
|
|
3705
4124
|
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3706
|
-
const existing =
|
|
3707
|
-
const recovery =
|
|
4125
|
+
const existing = isRecord4(action.agentContract) ? structuredClone(action.agentContract) : {};
|
|
4126
|
+
const recovery = isRecord4(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
|
|
3708
4127
|
const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
|
|
3709
4128
|
const inputExample = sampleForSchema(action.inputSchema);
|
|
3710
4129
|
const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples : [{ input: inputExample ?? {}, expected: `A response matching the published outputSchema for ${action.name}.` }];
|
|
@@ -3716,9 +4135,9 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3716
4135
|
...existing,
|
|
3717
4136
|
docsUrl,
|
|
3718
4137
|
...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
|
|
3719
|
-
resource:
|
|
4138
|
+
resource: isRecord4(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
|
|
3720
4139
|
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.`],
|
|
3721
|
-
errors:
|
|
4140
|
+
errors: mergeErrors(defaultErrors(action), existing.errors),
|
|
3722
4141
|
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"}.`],
|
|
3723
4142
|
examples,
|
|
3724
4143
|
recovery,
|
|
@@ -3730,15 +4149,15 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3730
4149
|
function actionContractCompleteness(action) {
|
|
3731
4150
|
const contract = action?.agentContract;
|
|
3732
4151
|
return {
|
|
3733
|
-
inputSchema:
|
|
4152
|
+
inputSchema: isRecord4(action?.inputSchema),
|
|
3734
4153
|
outputSchema: isConcreteOutputSchema(action?.outputSchema),
|
|
3735
4154
|
effects: nonEmptyStrings(contract?.effects),
|
|
3736
4155
|
errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
|
|
3737
4156
|
docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
|
|
3738
4157
|
examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
|
|
3739
4158
|
verification: nonEmptyStrings(contract?.verify),
|
|
3740
|
-
recovery:
|
|
3741
|
-
agentContract:
|
|
4159
|
+
recovery: isRecord4(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
|
|
4160
|
+
agentContract: isRecord4(contract) && isRecord4(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
|
|
3742
4161
|
};
|
|
3743
4162
|
}
|
|
3744
4163
|
|
|
@@ -4143,7 +4562,7 @@ function createTopoloWidgetError(error) {
|
|
|
4143
4562
|
}
|
|
4144
4563
|
function validateTopoloWidgetResponse(response) {
|
|
4145
4564
|
const errors = [];
|
|
4146
|
-
if (!
|
|
4565
|
+
if (!isRecord5(response)) {
|
|
4147
4566
|
return { ok: false, errors: ["response must be an object"] };
|
|
4148
4567
|
}
|
|
4149
4568
|
if (typeof response.success !== "boolean") {
|
|
@@ -4162,7 +4581,7 @@ function validateTopoloWidgetResponse(response) {
|
|
|
4162
4581
|
return { ok: errors.length === 0, errors };
|
|
4163
4582
|
}
|
|
4164
4583
|
function validateWidget(widget, path, errors) {
|
|
4165
|
-
if (!
|
|
4584
|
+
if (!isRecord5(widget)) {
|
|
4166
4585
|
errors.push(`${path} must be an object`);
|
|
4167
4586
|
return;
|
|
4168
4587
|
}
|
|
@@ -4189,7 +4608,7 @@ function validateWidget(widget, path, errors) {
|
|
|
4189
4608
|
case "chart":
|
|
4190
4609
|
validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
|
|
4191
4610
|
validateNonEmptyString(widget.title, `${path}.title`, errors);
|
|
4192
|
-
if (!
|
|
4611
|
+
if (!isRecord5(widget.data)) {
|
|
4193
4612
|
errors.push(`${path}.data must be an object`);
|
|
4194
4613
|
} else {
|
|
4195
4614
|
validateArray2(widget.data.labels, `${path}.data.labels`, errors);
|
|
@@ -4205,7 +4624,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
4205
4624
|
errors.push(`${path} must be an object`);
|
|
4206
4625
|
return;
|
|
4207
4626
|
}
|
|
4208
|
-
if (!
|
|
4627
|
+
if (!isRecord5(snapshot)) {
|
|
4209
4628
|
errors.push(`${path} must be an object`);
|
|
4210
4629
|
return;
|
|
4211
4630
|
}
|
|
@@ -4225,7 +4644,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
4225
4644
|
}
|
|
4226
4645
|
snapshot.availableMetrics.forEach((metric, index) => {
|
|
4227
4646
|
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
4228
|
-
if (!
|
|
4647
|
+
if (!isRecord5(metric)) {
|
|
4229
4648
|
errors.push(`${metricPath} must be an object`);
|
|
4230
4649
|
return;
|
|
4231
4650
|
}
|
|
@@ -4246,7 +4665,7 @@ function validateArray2(value, path, errors) {
|
|
|
4246
4665
|
errors.push(`${path} must be an array`);
|
|
4247
4666
|
}
|
|
4248
4667
|
}
|
|
4249
|
-
function
|
|
4668
|
+
function isRecord5(value) {
|
|
4250
4669
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4251
4670
|
}
|
|
4252
4671
|
function toIsoTimestamp(input) {
|
|
@@ -4297,6 +4716,9 @@ export {
|
|
|
4297
4716
|
defineTopoloStatsWidget,
|
|
4298
4717
|
digestTopoloActionCatalog,
|
|
4299
4718
|
enrichActionContract,
|
|
4719
|
+
parseTopoloActionUploadContract,
|
|
4720
|
+
prepareTopoloActionBase64UploadInput,
|
|
4721
|
+
prepareTopoloActionUploadInput,
|
|
4300
4722
|
requirementsForApplication,
|
|
4301
4723
|
resolveCatalogServiceUrl,
|
|
4302
4724
|
resolveServiceUrl,
|