@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.cjs
CHANGED
|
@@ -51,6 +51,9 @@ __export(index_exports, {
|
|
|
51
51
|
defineTopoloStatsWidget: () => defineTopoloStatsWidget,
|
|
52
52
|
digestTopoloActionCatalog: () => digestTopoloActionCatalog,
|
|
53
53
|
enrichActionContract: () => enrichActionContract,
|
|
54
|
+
parseTopoloActionUploadContract: () => parseTopoloActionUploadContract,
|
|
55
|
+
prepareTopoloActionBase64UploadInput: () => prepareTopoloActionBase64UploadInput,
|
|
56
|
+
prepareTopoloActionUploadInput: () => prepareTopoloActionUploadInput,
|
|
54
57
|
requirementsForApplication: () => requirementsForApplication,
|
|
55
58
|
resolveCatalogServiceUrl: () => resolveCatalogServiceUrl,
|
|
56
59
|
resolveServiceUrl: () => resolveServiceUrl,
|
|
@@ -89,19 +92,54 @@ var TopoloSdkError = class extends Error {
|
|
|
89
92
|
}
|
|
90
93
|
};
|
|
91
94
|
var TopoloAuthError = class extends TopoloSdkError {
|
|
92
|
-
|
|
95
|
+
status;
|
|
96
|
+
body;
|
|
97
|
+
service;
|
|
98
|
+
path;
|
|
99
|
+
requestId;
|
|
100
|
+
retryAfter;
|
|
101
|
+
constructor(message, code = "auth_error", options = {}) {
|
|
93
102
|
super(code, message);
|
|
94
103
|
this.name = "TopoloAuthError";
|
|
104
|
+
this.status = options.status ?? 401;
|
|
105
|
+
this.body = options.body;
|
|
106
|
+
this.service = options.service;
|
|
107
|
+
this.path = options.path;
|
|
108
|
+
this.requestId = options.requestId;
|
|
109
|
+
this.retryAfter = options.retryAfter;
|
|
95
110
|
}
|
|
96
111
|
};
|
|
97
112
|
var TopoloPermissionError = class extends TopoloSdkError {
|
|
98
113
|
required;
|
|
99
|
-
|
|
114
|
+
status;
|
|
115
|
+
body;
|
|
116
|
+
service;
|
|
117
|
+
path;
|
|
118
|
+
requestId;
|
|
119
|
+
retryAfter;
|
|
120
|
+
constructor(message, required, options = {}) {
|
|
100
121
|
super("permission_denied", message);
|
|
101
122
|
this.name = "TopoloPermissionError";
|
|
102
123
|
this.required = required;
|
|
124
|
+
this.status = options.status ?? 403;
|
|
125
|
+
this.body = options.body;
|
|
126
|
+
this.service = options.service;
|
|
127
|
+
this.path = options.path;
|
|
128
|
+
this.requestId = options.requestId;
|
|
129
|
+
this.retryAfter = options.retryAfter;
|
|
103
130
|
}
|
|
104
131
|
};
|
|
132
|
+
function extractHttpErrorCode(body) {
|
|
133
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return "http_error";
|
|
134
|
+
const record = body;
|
|
135
|
+
if (typeof record.code === "string" && record.code.trim()) return record.code.trim();
|
|
136
|
+
const nested = record.error;
|
|
137
|
+
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
138
|
+
const code = nested.code;
|
|
139
|
+
if (typeof code === "string" && code.trim()) return code.trim();
|
|
140
|
+
}
|
|
141
|
+
return "http_error";
|
|
142
|
+
}
|
|
105
143
|
var TopoloConfirmationRequiredError = class extends TopoloSdkError {
|
|
106
144
|
constructor(message) {
|
|
107
145
|
super("confirmation_required", message);
|
|
@@ -113,13 +151,17 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
113
151
|
body;
|
|
114
152
|
service;
|
|
115
153
|
path;
|
|
116
|
-
|
|
117
|
-
|
|
154
|
+
requestId;
|
|
155
|
+
retryAfter;
|
|
156
|
+
constructor(service, path, status, body, message, options = {}) {
|
|
157
|
+
super(extractHttpErrorCode(body), message ?? `HTTP ${status} from ${service}${path}`);
|
|
118
158
|
this.name = "TopoloHttpError";
|
|
119
159
|
this.status = status;
|
|
120
160
|
this.body = body;
|
|
121
161
|
this.service = service;
|
|
122
162
|
this.path = path;
|
|
163
|
+
this.requestId = options.requestId;
|
|
164
|
+
this.retryAfter = options.retryAfter;
|
|
123
165
|
}
|
|
124
166
|
};
|
|
125
167
|
|
|
@@ -128,6 +170,7 @@ var APP_API_URLS = {
|
|
|
128
170
|
agent: "https://agent.topolo.app",
|
|
129
171
|
auth: "https://auth.topolo.app",
|
|
130
172
|
backup: "https://backup.topolo.app",
|
|
173
|
+
blog: "https://blog.topolo.app",
|
|
131
174
|
books: "https://books.topolo.app",
|
|
132
175
|
brand: "https://brand.topolo.app",
|
|
133
176
|
bugfix: "https://bugfix.topolo.app",
|
|
@@ -140,6 +183,7 @@ var APP_API_URLS = {
|
|
|
140
183
|
compose: "https://compose.topolo.app",
|
|
141
184
|
consent: "https://consent.topolo.app",
|
|
142
185
|
crm: "https://crm.topolo.app",
|
|
186
|
+
design: "https://design.topolo.app",
|
|
143
187
|
developers: "https://developers.topolo.app",
|
|
144
188
|
director: "https://director.topolo.app",
|
|
145
189
|
feed: "https://feed.topolo.app",
|
|
@@ -225,7 +269,9 @@ var APPLICATIONS = {
|
|
|
225
269
|
"domains": [
|
|
226
270
|
"agent.topolo.app"
|
|
227
271
|
],
|
|
228
|
-
"routes": [
|
|
272
|
+
"routes": [
|
|
273
|
+
"agent.topolo.app"
|
|
274
|
+
],
|
|
229
275
|
"appId": "agent"
|
|
230
276
|
},
|
|
231
277
|
{
|
|
@@ -286,6 +332,38 @@ var APPLICATIONS = {
|
|
|
286
332
|
}
|
|
287
333
|
]
|
|
288
334
|
},
|
|
335
|
+
"blog": {
|
|
336
|
+
"id": "blog",
|
|
337
|
+
"appDir": "TopoloBlog",
|
|
338
|
+
"name": "topolo-blog",
|
|
339
|
+
"packageName": "topolo-blog",
|
|
340
|
+
"productionUrl": "https://blog.topolo.app",
|
|
341
|
+
"services": [
|
|
342
|
+
"blog"
|
|
343
|
+
],
|
|
344
|
+
"deployTargets": [
|
|
345
|
+
{
|
|
346
|
+
"name": "topolo-blog",
|
|
347
|
+
"kind": "worker",
|
|
348
|
+
"apiUrl": "https://blog.topolo.app",
|
|
349
|
+
"domains": [
|
|
350
|
+
"blog.topolo.app"
|
|
351
|
+
],
|
|
352
|
+
"routes": [
|
|
353
|
+
"blog.topolo.app"
|
|
354
|
+
],
|
|
355
|
+
"appId": "blog"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"name": "topolo-blog-api",
|
|
359
|
+
"kind": "worker",
|
|
360
|
+
"apiUrl": null,
|
|
361
|
+
"domains": [],
|
|
362
|
+
"routes": [],
|
|
363
|
+
"appId": null
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
},
|
|
289
367
|
"books": {
|
|
290
368
|
"id": "books",
|
|
291
369
|
"appDir": "TopoloBooks",
|
|
@@ -303,7 +381,9 @@ var APPLICATIONS = {
|
|
|
303
381
|
"domains": [
|
|
304
382
|
"books.topolo.app"
|
|
305
383
|
],
|
|
306
|
-
"routes": [
|
|
384
|
+
"routes": [
|
|
385
|
+
"books.topolo.app"
|
|
386
|
+
],
|
|
307
387
|
"appId": "books"
|
|
308
388
|
},
|
|
309
389
|
{
|
|
@@ -333,7 +413,9 @@ var APPLICATIONS = {
|
|
|
333
413
|
"domains": [
|
|
334
414
|
"brand.topolo.app"
|
|
335
415
|
],
|
|
336
|
-
"routes": [
|
|
416
|
+
"routes": [
|
|
417
|
+
"brand.topolo.app"
|
|
418
|
+
],
|
|
337
419
|
"appId": "brand"
|
|
338
420
|
},
|
|
339
421
|
{
|
|
@@ -451,7 +533,9 @@ var APPLICATIONS = {
|
|
|
451
533
|
"domains": [
|
|
452
534
|
"campaigns.topolo.app"
|
|
453
535
|
],
|
|
454
|
-
"routes": [
|
|
536
|
+
"routes": [
|
|
537
|
+
"campaigns.topolo.app"
|
|
538
|
+
],
|
|
455
539
|
"appId": "campaigns"
|
|
456
540
|
},
|
|
457
541
|
{
|
|
@@ -505,7 +589,9 @@ var APPLICATIONS = {
|
|
|
505
589
|
"domains": [
|
|
506
590
|
"chat.topolo.app"
|
|
507
591
|
],
|
|
508
|
-
"routes": [
|
|
592
|
+
"routes": [
|
|
593
|
+
"chat.topolo.app"
|
|
594
|
+
],
|
|
509
595
|
"appId": "chat"
|
|
510
596
|
},
|
|
511
597
|
{
|
|
@@ -591,7 +677,9 @@ var APPLICATIONS = {
|
|
|
591
677
|
"domains": [
|
|
592
678
|
"compose.topolo.app"
|
|
593
679
|
],
|
|
594
|
-
"routes": [
|
|
680
|
+
"routes": [
|
|
681
|
+
"compose.topolo.app"
|
|
682
|
+
],
|
|
595
683
|
"appId": "compose"
|
|
596
684
|
},
|
|
597
685
|
{
|
|
@@ -621,7 +709,9 @@ var APPLICATIONS = {
|
|
|
621
709
|
"domains": [
|
|
622
710
|
"consent.topolo.app"
|
|
623
711
|
],
|
|
624
|
-
"routes": [
|
|
712
|
+
"routes": [
|
|
713
|
+
"consent.topolo.app"
|
|
714
|
+
],
|
|
625
715
|
"appId": "consent"
|
|
626
716
|
},
|
|
627
717
|
{
|
|
@@ -659,11 +749,45 @@ var APPLICATIONS = {
|
|
|
659
749
|
"domains": [
|
|
660
750
|
"crm.topolo.app"
|
|
661
751
|
],
|
|
662
|
-
"routes": [
|
|
752
|
+
"routes": [
|
|
753
|
+
"crm.topolo.app"
|
|
754
|
+
],
|
|
663
755
|
"appId": "crm"
|
|
664
756
|
}
|
|
665
757
|
]
|
|
666
758
|
},
|
|
759
|
+
"design": {
|
|
760
|
+
"id": "design",
|
|
761
|
+
"appDir": "TopoloDesign",
|
|
762
|
+
"name": "topolo-design",
|
|
763
|
+
"packageName": "@topolo-io/design",
|
|
764
|
+
"productionUrl": "https://design.topolo.app",
|
|
765
|
+
"services": [
|
|
766
|
+
"design"
|
|
767
|
+
],
|
|
768
|
+
"deployTargets": [
|
|
769
|
+
{
|
|
770
|
+
"name": "topolo-design",
|
|
771
|
+
"kind": "worker",
|
|
772
|
+
"apiUrl": "https://design.topolo.app",
|
|
773
|
+
"domains": [
|
|
774
|
+
"design.topolo.app"
|
|
775
|
+
],
|
|
776
|
+
"routes": [
|
|
777
|
+
"design.topolo.app"
|
|
778
|
+
],
|
|
779
|
+
"appId": "design"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
"name": "topolo-design-api",
|
|
783
|
+
"kind": "worker",
|
|
784
|
+
"apiUrl": null,
|
|
785
|
+
"domains": [],
|
|
786
|
+
"routes": [],
|
|
787
|
+
"appId": null
|
|
788
|
+
}
|
|
789
|
+
]
|
|
790
|
+
},
|
|
667
791
|
"developers": {
|
|
668
792
|
"id": "developers",
|
|
669
793
|
"appDir": "TopoloDevelopers",
|
|
@@ -689,7 +813,9 @@ var APPLICATIONS = {
|
|
|
689
813
|
"domains": [
|
|
690
814
|
"developers.topolo.app"
|
|
691
815
|
],
|
|
692
|
-
"routes": [
|
|
816
|
+
"routes": [
|
|
817
|
+
"developers.topolo.app"
|
|
818
|
+
],
|
|
693
819
|
"appId": "developers"
|
|
694
820
|
},
|
|
695
821
|
{
|
|
@@ -719,7 +845,9 @@ var APPLICATIONS = {
|
|
|
719
845
|
"domains": [
|
|
720
846
|
"director.topolo.app"
|
|
721
847
|
],
|
|
722
|
-
"routes": [
|
|
848
|
+
"routes": [
|
|
849
|
+
"director.topolo.app"
|
|
850
|
+
],
|
|
723
851
|
"appId": "director"
|
|
724
852
|
},
|
|
725
853
|
{
|
|
@@ -827,7 +955,9 @@ var APPLICATIONS = {
|
|
|
827
955
|
"domains": [
|
|
828
956
|
"flow.topolo.app"
|
|
829
957
|
],
|
|
830
|
-
"routes": [
|
|
958
|
+
"routes": [
|
|
959
|
+
"flow.topolo.app"
|
|
960
|
+
],
|
|
831
961
|
"appId": "flow"
|
|
832
962
|
},
|
|
833
963
|
{
|
|
@@ -857,7 +987,9 @@ var APPLICATIONS = {
|
|
|
857
987
|
"domains": [
|
|
858
988
|
"forecast.topolo.app"
|
|
859
989
|
],
|
|
860
|
-
"routes": [
|
|
990
|
+
"routes": [
|
|
991
|
+
"forecast.topolo.app"
|
|
992
|
+
],
|
|
861
993
|
"appId": "forecast"
|
|
862
994
|
},
|
|
863
995
|
{
|
|
@@ -887,7 +1019,9 @@ var APPLICATIONS = {
|
|
|
887
1019
|
"domains": [
|
|
888
1020
|
"forms.topolo.app"
|
|
889
1021
|
],
|
|
890
|
-
"routes": [
|
|
1022
|
+
"routes": [
|
|
1023
|
+
"forms.topolo.app"
|
|
1024
|
+
],
|
|
891
1025
|
"appId": "forms"
|
|
892
1026
|
},
|
|
893
1027
|
{
|
|
@@ -949,7 +1083,9 @@ var APPLICATIONS = {
|
|
|
949
1083
|
"domains": [
|
|
950
1084
|
"insights.topolo.app"
|
|
951
1085
|
],
|
|
952
|
-
"routes": [
|
|
1086
|
+
"routes": [
|
|
1087
|
+
"insights.topolo.app"
|
|
1088
|
+
],
|
|
953
1089
|
"appId": "insights"
|
|
954
1090
|
},
|
|
955
1091
|
{
|
|
@@ -979,7 +1115,9 @@ var APPLICATIONS = {
|
|
|
979
1115
|
"domains": [
|
|
980
1116
|
"inventory.topolo.app"
|
|
981
1117
|
],
|
|
982
|
-
"routes": [
|
|
1118
|
+
"routes": [
|
|
1119
|
+
"inventory.topolo.app"
|
|
1120
|
+
],
|
|
983
1121
|
"appId": "inventory"
|
|
984
1122
|
},
|
|
985
1123
|
{
|
|
@@ -1065,7 +1203,9 @@ var APPLICATIONS = {
|
|
|
1065
1203
|
"domains": [
|
|
1066
1204
|
"mail.topolo.app"
|
|
1067
1205
|
],
|
|
1068
|
-
"routes": [
|
|
1206
|
+
"routes": [
|
|
1207
|
+
"mail.topolo.app"
|
|
1208
|
+
],
|
|
1069
1209
|
"appId": "mail"
|
|
1070
1210
|
},
|
|
1071
1211
|
{
|
|
@@ -1331,7 +1471,9 @@ var APPLICATIONS = {
|
|
|
1331
1471
|
"domains": [
|
|
1332
1472
|
"pay.topolo.app"
|
|
1333
1473
|
],
|
|
1334
|
-
"routes": [
|
|
1474
|
+
"routes": [
|
|
1475
|
+
"pay.topolo.app"
|
|
1476
|
+
],
|
|
1335
1477
|
"appId": "pay"
|
|
1336
1478
|
},
|
|
1337
1479
|
{
|
|
@@ -1361,7 +1503,9 @@ var APPLICATIONS = {
|
|
|
1361
1503
|
"domains": [
|
|
1362
1504
|
"people.topolo.app"
|
|
1363
1505
|
],
|
|
1364
|
-
"routes": [
|
|
1506
|
+
"routes": [
|
|
1507
|
+
"people.topolo.app"
|
|
1508
|
+
],
|
|
1365
1509
|
"appId": "people"
|
|
1366
1510
|
},
|
|
1367
1511
|
{
|
|
@@ -1416,7 +1560,9 @@ var APPLICATIONS = {
|
|
|
1416
1560
|
"domains": [
|
|
1417
1561
|
"quro.topolo.app"
|
|
1418
1562
|
],
|
|
1419
|
-
"routes": [
|
|
1563
|
+
"routes": [
|
|
1564
|
+
"quro.topolo.app"
|
|
1565
|
+
],
|
|
1420
1566
|
"appId": null
|
|
1421
1567
|
}
|
|
1422
1568
|
]
|
|
@@ -1438,7 +1584,9 @@ var APPLICATIONS = {
|
|
|
1438
1584
|
"domains": [
|
|
1439
1585
|
"roadmapper.topolo.app"
|
|
1440
1586
|
],
|
|
1441
|
-
"routes": [
|
|
1587
|
+
"routes": [
|
|
1588
|
+
"roadmapper.topolo.app"
|
|
1589
|
+
],
|
|
1442
1590
|
"appId": "roadmapper"
|
|
1443
1591
|
},
|
|
1444
1592
|
{
|
|
@@ -1468,7 +1616,9 @@ var APPLICATIONS = {
|
|
|
1468
1616
|
"domains": [
|
|
1469
1617
|
"sign.topolo.app"
|
|
1470
1618
|
],
|
|
1471
|
-
"routes": [
|
|
1619
|
+
"routes": [
|
|
1620
|
+
"sign.topolo.app"
|
|
1621
|
+
],
|
|
1472
1622
|
"appId": "sign"
|
|
1473
1623
|
},
|
|
1474
1624
|
{
|
|
@@ -1582,7 +1732,9 @@ var APPLICATIONS = {
|
|
|
1582
1732
|
"domains": [
|
|
1583
1733
|
"spaces.topolo.app"
|
|
1584
1734
|
],
|
|
1585
|
-
"routes": [
|
|
1735
|
+
"routes": [
|
|
1736
|
+
"spaces.topolo.app"
|
|
1737
|
+
],
|
|
1586
1738
|
"appId": "spaces"
|
|
1587
1739
|
},
|
|
1588
1740
|
{
|
|
@@ -1700,7 +1852,9 @@ var APPLICATIONS = {
|
|
|
1700
1852
|
"domains": [
|
|
1701
1853
|
"survey.topolo.app"
|
|
1702
1854
|
],
|
|
1703
|
-
"routes": [
|
|
1855
|
+
"routes": [
|
|
1856
|
+
"survey.topolo.app"
|
|
1857
|
+
],
|
|
1704
1858
|
"appId": "survey"
|
|
1705
1859
|
},
|
|
1706
1860
|
{
|
|
@@ -2498,6 +2652,168 @@ function isMultipleOf(value, divisor) {
|
|
|
2498
2652
|
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
|
|
2499
2653
|
}
|
|
2500
2654
|
|
|
2655
|
+
// src/action-upload.ts
|
|
2656
|
+
function parseTopoloActionUploadContract(value) {
|
|
2657
|
+
if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
|
|
2658
|
+
throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
|
|
2659
|
+
}
|
|
2660
|
+
const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
|
|
2661
|
+
const fileNameField = requiredField(value.fileNameField, "fileNameField");
|
|
2662
|
+
const maxBytes = Number(value.maxBytes);
|
|
2663
|
+
const acceptedContentTypes = Array.isArray(value.acceptedContentTypes) ? value.acceptedContentTypes.map((item) => String(item).trim().toLowerCase()).filter(Boolean) : [];
|
|
2664
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || acceptedContentTypes.length === 0) {
|
|
2665
|
+
throw new TopoloSdkError("invalid_upload_contract", "The action upload contract must declare maxBytes and acceptedContentTypes.");
|
|
2666
|
+
}
|
|
2667
|
+
if (value.profile === "inline-base64") {
|
|
2668
|
+
return {
|
|
2669
|
+
profile: "inline-base64",
|
|
2670
|
+
contentField: requiredField(value.contentField, "contentField"),
|
|
2671
|
+
contentTypeField,
|
|
2672
|
+
fileNameField,
|
|
2673
|
+
maxBytes,
|
|
2674
|
+
acceptedContentTypes
|
|
2675
|
+
};
|
|
2676
|
+
}
|
|
2677
|
+
const uploadMethod = value.uploadMethod === "PUT" || value.uploadMethod === "POST" ? value.uploadMethod : null;
|
|
2678
|
+
const uploadUrlPath = requiredPath(value.uploadUrlPath, "uploadUrlPath");
|
|
2679
|
+
const complete = value.complete === void 0 ? void 0 : parseCompleteContract(value.complete);
|
|
2680
|
+
if (!uploadMethod) throw new TopoloSdkError("invalid_upload_contract", "The action upload contract has an invalid uploadMethod.");
|
|
2681
|
+
return {
|
|
2682
|
+
profile: "url-session",
|
|
2683
|
+
fileNameField,
|
|
2684
|
+
contentTypeField,
|
|
2685
|
+
sizeField: requiredField(value.sizeField, "sizeField"),
|
|
2686
|
+
maxBytes,
|
|
2687
|
+
acceptedContentTypes,
|
|
2688
|
+
uploadUrlPath,
|
|
2689
|
+
uploadMethod,
|
|
2690
|
+
...complete ? { complete } : {}
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
function prepareTopoloActionUploadInput(contractValue, content, input, options) {
|
|
2694
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2695
|
+
if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
|
|
2696
|
+
const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
2697
|
+
if (bytes.byteLength === 0) {
|
|
2698
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2699
|
+
}
|
|
2700
|
+
if (bytes.byteLength > contract.maxBytes) {
|
|
2701
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2702
|
+
}
|
|
2703
|
+
return mapUploadInput(contract, bytesToBase64(bytes), input, options);
|
|
2704
|
+
}
|
|
2705
|
+
function prepareTopoloActionBase64UploadInput(contractValue, contentBase64, input, options) {
|
|
2706
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2707
|
+
if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
|
|
2708
|
+
if (!isCanonicalBase64(contentBase64)) {
|
|
2709
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
2710
|
+
}
|
|
2711
|
+
const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
|
|
2712
|
+
const byteLength = contentBase64.length / 4 * 3 - padding;
|
|
2713
|
+
if (byteLength === 0) throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2714
|
+
if (byteLength > contract.maxBytes) {
|
|
2715
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2716
|
+
}
|
|
2717
|
+
return mapUploadInput(contract, contentBase64, input, options);
|
|
2718
|
+
}
|
|
2719
|
+
function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, options) {
|
|
2720
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2721
|
+
if (contract.profile !== "url-session") {
|
|
2722
|
+
throw new TopoloSdkError("invalid_upload_contract", "This action does not publish a URL-session upload contract.");
|
|
2723
|
+
}
|
|
2724
|
+
validateUploadMetadata(contract, byteLength, options);
|
|
2725
|
+
return {
|
|
2726
|
+
contract,
|
|
2727
|
+
input: {
|
|
2728
|
+
...input,
|
|
2729
|
+
[contract.fileNameField]: options.fileName.trim(),
|
|
2730
|
+
[contract.contentTypeField]: options.contentType.trim().toLowerCase(),
|
|
2731
|
+
[contract.sizeField]: byteLength
|
|
2732
|
+
}
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
function topoloUploadResponseValue(response, path) {
|
|
2736
|
+
return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
|
|
2737
|
+
}
|
|
2738
|
+
function decodeTopoloActionBase64(contentBase64) {
|
|
2739
|
+
if (!isCanonicalBase64(contentBase64)) {
|
|
2740
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
2741
|
+
}
|
|
2742
|
+
const binary = atob(contentBase64);
|
|
2743
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
2744
|
+
}
|
|
2745
|
+
function mapUploadInput(contract, contentBase64, input, options) {
|
|
2746
|
+
validateUploadMetadata(contract, Math.max(1, decodedBase64Size(contentBase64)), options);
|
|
2747
|
+
const fileName = options.fileName.trim();
|
|
2748
|
+
const contentType = options.contentType.trim().toLowerCase();
|
|
2749
|
+
return {
|
|
2750
|
+
...input,
|
|
2751
|
+
[contract.contentField]: contentBase64,
|
|
2752
|
+
[contract.contentTypeField]: contentType,
|
|
2753
|
+
[contract.fileNameField]: fileName
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
function validateUploadMetadata(contract, byteLength, options) {
|
|
2757
|
+
const fileName = options.fileName.trim();
|
|
2758
|
+
const contentType = options.contentType.trim().toLowerCase();
|
|
2759
|
+
if (!fileName) throw new TopoloSdkError("invalid_upload_file_name", "Upload fileName must not be empty.");
|
|
2760
|
+
if (!Number.isSafeInteger(byteLength) || byteLength <= 0) {
|
|
2761
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2762
|
+
}
|
|
2763
|
+
if (byteLength > contract.maxBytes) {
|
|
2764
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2765
|
+
}
|
|
2766
|
+
if (!matchesContentType(contentType, contract.acceptedContentTypes)) {
|
|
2767
|
+
throw new TopoloSdkError("unsupported_upload_content_type", `Content type "${contentType}" is not accepted by this action.`);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
function decodedBase64Size(contentBase64) {
|
|
2771
|
+
const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
|
|
2772
|
+
return contentBase64.length / 4 * 3 - padding;
|
|
2773
|
+
}
|
|
2774
|
+
function parseCompleteContract(value) {
|
|
2775
|
+
if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
|
|
2776
|
+
return {
|
|
2777
|
+
actionId: requiredPath(value.actionId, "complete.actionId"),
|
|
2778
|
+
inputField: requiredField(value.inputField, "complete.inputField"),
|
|
2779
|
+
responsePath: requiredPath(value.responsePath, "complete.responsePath")
|
|
2780
|
+
};
|
|
2781
|
+
}
|
|
2782
|
+
function requiredPath(value, name) {
|
|
2783
|
+
const path = typeof value === "string" ? value.trim() : "";
|
|
2784
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path)) {
|
|
2785
|
+
throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
|
|
2786
|
+
}
|
|
2787
|
+
return path;
|
|
2788
|
+
}
|
|
2789
|
+
function unsupportedInlineProfile() {
|
|
2790
|
+
return new TopoloSdkError("invalid_upload_contract", "This action does not publish an inline-base64 upload contract.");
|
|
2791
|
+
}
|
|
2792
|
+
function isCanonicalBase64(value) {
|
|
2793
|
+
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);
|
|
2794
|
+
}
|
|
2795
|
+
function requiredField(value, name) {
|
|
2796
|
+
const field = typeof value === "string" ? value.trim() : "";
|
|
2797
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
|
|
2798
|
+
throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
|
|
2799
|
+
}
|
|
2800
|
+
return field;
|
|
2801
|
+
}
|
|
2802
|
+
function matchesContentType(contentType, accepted) {
|
|
2803
|
+
return accepted.some((candidate) => candidate === "*/*" || candidate === contentType || candidate.endsWith("/*") && contentType.startsWith(candidate.slice(0, -1)));
|
|
2804
|
+
}
|
|
2805
|
+
function bytesToBase64(bytes) {
|
|
2806
|
+
let binary = "";
|
|
2807
|
+
const chunkSize = 32768;
|
|
2808
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
2809
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
|
|
2810
|
+
}
|
|
2811
|
+
return btoa(binary);
|
|
2812
|
+
}
|
|
2813
|
+
function isRecord2(value) {
|
|
2814
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2501
2817
|
// src/client.ts
|
|
2502
2818
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
2503
2819
|
var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
|
|
@@ -2575,6 +2891,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
2575
2891
|
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
2576
2892
|
}
|
|
2577
2893
|
}
|
|
2894
|
+
if (opts.body !== void 0 && opts.rawBody !== void 0) {
|
|
2895
|
+
throw new TopoloSdkError("invalid_request_body", "body and rawBody are mutually exclusive.");
|
|
2896
|
+
}
|
|
2578
2897
|
const headers = new Headers();
|
|
2579
2898
|
headers.set("Accept", "application/json");
|
|
2580
2899
|
if (opts.body !== void 0) headers.set("Content-Type", "application/json");
|
|
@@ -2609,7 +2928,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2609
2928
|
return await this.fetchImpl(urlStr, {
|
|
2610
2929
|
method,
|
|
2611
2930
|
headers: new Headers(headers),
|
|
2612
|
-
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
2931
|
+
body: opts.rawBody ?? (opts.body !== void 0 ? JSON.stringify(opts.body) : null),
|
|
2613
2932
|
signal
|
|
2614
2933
|
});
|
|
2615
2934
|
} finally {
|
|
@@ -2657,14 +2976,32 @@ var TopoloClient = class _TopoloClient {
|
|
|
2657
2976
|
error: message,
|
|
2658
2977
|
status: res.status
|
|
2659
2978
|
});
|
|
2660
|
-
|
|
2979
|
+
const errorContext = {
|
|
2980
|
+
status: res.status,
|
|
2981
|
+
body: parsed,
|
|
2982
|
+
service: resolvedService.serviceKey,
|
|
2983
|
+
path: opts.path,
|
|
2984
|
+
requestId: res.headers.get("X-Topolo-Request-Id") || requestId,
|
|
2985
|
+
retryAfter: res.headers.get("Retry-After") || void 0
|
|
2986
|
+
};
|
|
2987
|
+
if (res.status === 401) {
|
|
2988
|
+
throw new TopoloAuthError(describeError(parsed, "Unauthorized"), "auth_error", errorContext);
|
|
2989
|
+
}
|
|
2661
2990
|
if (res.status === 403) {
|
|
2662
2991
|
throw new TopoloPermissionError(
|
|
2663
2992
|
describeError(parsed, "Permission denied"),
|
|
2664
|
-
extractRequired(parsed)
|
|
2993
|
+
extractRequired(parsed),
|
|
2994
|
+
errorContext
|
|
2665
2995
|
);
|
|
2666
2996
|
}
|
|
2667
|
-
throw new TopoloHttpError(
|
|
2997
|
+
throw new TopoloHttpError(
|
|
2998
|
+
resolvedService.serviceKey,
|
|
2999
|
+
opts.path,
|
|
3000
|
+
res.status,
|
|
3001
|
+
parsed,
|
|
3002
|
+
message,
|
|
3003
|
+
errorContext
|
|
3004
|
+
);
|
|
2668
3005
|
}
|
|
2669
3006
|
this.emit({
|
|
2670
3007
|
phase: "response",
|
|
@@ -2823,7 +3160,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2823
3160
|
}
|
|
2824
3161
|
const entries = [];
|
|
2825
3162
|
for (const item of raw) {
|
|
2826
|
-
if (!
|
|
3163
|
+
if (!isRecord3(item)) continue;
|
|
2827
3164
|
const s = item;
|
|
2828
3165
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
2829
3166
|
if (!appId) continue;
|
|
@@ -2898,8 +3235,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2898
3235
|
"app_topolo_auth.service_api_key_resources.list",
|
|
2899
3236
|
{ appId: service.appId }
|
|
2900
3237
|
);
|
|
2901
|
-
const payload =
|
|
2902
|
-
if (!
|
|
3238
|
+
const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
|
|
3239
|
+
if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
|
|
2903
3240
|
throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
|
|
2904
3241
|
}
|
|
2905
3242
|
return {
|
|
@@ -3005,6 +3342,71 @@ var TopoloClient = class _TopoloClient {
|
|
|
3005
3342
|
const entry = await this.getAction(action);
|
|
3006
3343
|
return this.executeResolvedAction(entry, input, options);
|
|
3007
3344
|
}
|
|
3345
|
+
async uploadAction(action, content, input, options) {
|
|
3346
|
+
const entry = await this.getAction(action);
|
|
3347
|
+
const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
|
|
3348
|
+
if (contract.profile === "url-session") {
|
|
3349
|
+
const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
3350
|
+
return this.executeUrlSessionUpload(entry, contract, bytes, input, options);
|
|
3351
|
+
}
|
|
3352
|
+
const uploadInput = prepareTopoloActionUploadInput(entry.agentContract?.upload, content, input, options);
|
|
3353
|
+
return this.executeResolvedAction(entry, uploadInput, options);
|
|
3354
|
+
}
|
|
3355
|
+
async uploadActionBase64(action, contentBase64, input, options) {
|
|
3356
|
+
const entry = await this.getAction(action);
|
|
3357
|
+
const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
|
|
3358
|
+
if (contract.profile === "url-session") {
|
|
3359
|
+
return this.executeUrlSessionUpload(entry, contract, decodeTopoloActionBase64(contentBase64), input, options);
|
|
3360
|
+
}
|
|
3361
|
+
const uploadInput = prepareTopoloActionBase64UploadInput(entry.agentContract?.upload, contentBase64, input, options);
|
|
3362
|
+
return this.executeResolvedAction(entry, uploadInput, options);
|
|
3363
|
+
}
|
|
3364
|
+
async executeUrlSessionUpload(entry, contract, bytes, input, options) {
|
|
3365
|
+
const prepared = prepareTopoloUrlSessionUploadInput(contract, bytes.byteLength, input, options);
|
|
3366
|
+
const initiation = await this.executeResolvedAction(entry, prepared.input, options);
|
|
3367
|
+
const uploadUrl = topoloUploadResponseValue(initiation, contract.uploadUrlPath);
|
|
3368
|
+
if (typeof uploadUrl !== "string" || !uploadUrl.trim()) {
|
|
3369
|
+
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.uploadUrlPath}.`);
|
|
3370
|
+
}
|
|
3371
|
+
await this.putUrlSessionContent(entry, uploadUrl, contract.uploadMethod, bytes, options);
|
|
3372
|
+
if (!contract.complete) return initiation;
|
|
3373
|
+
const completionValue = topoloUploadResponseValue(initiation, contract.complete.responsePath);
|
|
3374
|
+
if (typeof completionValue !== "string" || !completionValue.trim()) {
|
|
3375
|
+
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.complete.responsePath}.`);
|
|
3376
|
+
}
|
|
3377
|
+
const completion = await this.getAction(contract.complete.actionId);
|
|
3378
|
+
return this.executeResolvedAction(completion, {
|
|
3379
|
+
...options.completionInput ?? {},
|
|
3380
|
+
[contract.complete.inputField]: completionValue
|
|
3381
|
+
}, options);
|
|
3382
|
+
}
|
|
3383
|
+
async putUrlSessionContent(entry, uploadUrl, method, bytes, options) {
|
|
3384
|
+
const service = entry.appSlug || entry.appId;
|
|
3385
|
+
const resolved = entry.apiBaseUrl ? { serviceKey: normalizeLookupKey(service), baseUrl: requireHttpOrigin(entry.apiBaseUrl) } : await this.resolveRequestService(service);
|
|
3386
|
+
const target = new URL(uploadUrl, resolved.baseUrl.endsWith("/") ? resolved.baseUrl : `${resolved.baseUrl}/`);
|
|
3387
|
+
const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
3388
|
+
if (target.origin === new URL(resolved.baseUrl).origin) {
|
|
3389
|
+
await this.request({
|
|
3390
|
+
service,
|
|
3391
|
+
baseUrl: resolved.baseUrl,
|
|
3392
|
+
path: target.toString(),
|
|
3393
|
+
method,
|
|
3394
|
+
rawBody: body,
|
|
3395
|
+
headers: { "Content-Type": options.contentType.trim().toLowerCase() },
|
|
3396
|
+
resource: options.resource,
|
|
3397
|
+
confirm: options.confirm
|
|
3398
|
+
});
|
|
3399
|
+
return;
|
|
3400
|
+
}
|
|
3401
|
+
const response = await this.fetchImpl(target, {
|
|
3402
|
+
method,
|
|
3403
|
+
headers: { "Content-Type": options.contentType.trim().toLowerCase() },
|
|
3404
|
+
body
|
|
3405
|
+
});
|
|
3406
|
+
if (!response.ok) {
|
|
3407
|
+
throw new TopoloHttpError(service, target.pathname, response.status, await response.text(), `Upload transport failed with HTTP ${response.status}.`);
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3008
3410
|
async validateActionInput(action, input = {}) {
|
|
3009
3411
|
const entry = await this.getAction(action);
|
|
3010
3412
|
return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
|
|
@@ -3163,7 +3565,7 @@ function resolveConfiguredAppId(slug, appIds) {
|
|
|
3163
3565
|
return null;
|
|
3164
3566
|
}
|
|
3165
3567
|
function normalizeApiKeyResourceMetadata(input) {
|
|
3166
|
-
if (!
|
|
3568
|
+
if (!isRecord3(input)) return null;
|
|
3167
3569
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
3168
3570
|
const label = readString(input.label);
|
|
3169
3571
|
const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
|
|
@@ -3197,7 +3599,7 @@ function normalizeApiKeyResourceMetadataList(input) {
|
|
|
3197
3599
|
return resources;
|
|
3198
3600
|
}
|
|
3199
3601
|
function normalizeBindableResource(input) {
|
|
3200
|
-
if (!
|
|
3602
|
+
if (!isRecord3(input)) return null;
|
|
3201
3603
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
3202
3604
|
const resourceId = readString(input.resourceId ?? input.resource_id);
|
|
3203
3605
|
if (!resourceType || !resourceId) return null;
|
|
@@ -3208,13 +3610,13 @@ function normalizeBindableResource(input) {
|
|
|
3208
3610
|
slug: readOptionalString(input.slug),
|
|
3209
3611
|
status: readOptionalString(input.status),
|
|
3210
3612
|
organizationId: readOptionalString(input.organizationId ?? input.organization_id),
|
|
3211
|
-
metadata:
|
|
3613
|
+
metadata: isRecord3(input.metadata) ? input.metadata : {}
|
|
3212
3614
|
};
|
|
3213
3615
|
}
|
|
3214
3616
|
function normalizeResourceBindings(input) {
|
|
3215
3617
|
if (!Array.isArray(input)) return [];
|
|
3216
3618
|
return input.flatMap((binding) => {
|
|
3217
|
-
if (!
|
|
3619
|
+
if (!isRecord3(binding)) return [];
|
|
3218
3620
|
const resourceType = readString(binding.resourceType ?? binding.resource_type);
|
|
3219
3621
|
const resourceId = readString(binding.resourceId ?? binding.resource_id);
|
|
3220
3622
|
return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
|
|
@@ -3227,7 +3629,7 @@ function readOptionalString(value) {
|
|
|
3227
3629
|
const normalized = readString(value);
|
|
3228
3630
|
return normalized || null;
|
|
3229
3631
|
}
|
|
3230
|
-
function
|
|
3632
|
+
function isRecord3(value) {
|
|
3231
3633
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3232
3634
|
}
|
|
3233
3635
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
@@ -3299,14 +3701,14 @@ function catalogSearchQuery(options) {
|
|
|
3299
3701
|
return query;
|
|
3300
3702
|
}
|
|
3301
3703
|
function normalizeCatalogPage(value) {
|
|
3302
|
-
if (!
|
|
3704
|
+
if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
|
|
3303
3705
|
return {
|
|
3304
3706
|
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
3305
3707
|
hasMore: value.hasMore === true
|
|
3306
3708
|
};
|
|
3307
3709
|
}
|
|
3308
3710
|
function normalizeServiceCatalogEntry(item) {
|
|
3309
|
-
if (!
|
|
3711
|
+
if (!isRecord3(item)) return null;
|
|
3310
3712
|
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
3311
3713
|
if (!appId) return null;
|
|
3312
3714
|
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
@@ -3402,10 +3804,19 @@ function mapActionInput(action, input) {
|
|
|
3402
3804
|
delete remaining[key];
|
|
3403
3805
|
return encodeURIComponent(String(value));
|
|
3404
3806
|
});
|
|
3405
|
-
const
|
|
3406
|
-
|
|
3407
|
-
const
|
|
3408
|
-
|
|
3807
|
+
const schemaProperties = plainObject(plainObject(action.inputSchema).properties);
|
|
3808
|
+
const schemaOwnsQuery = Object.prototype.hasOwnProperty.call(schemaProperties, "query");
|
|
3809
|
+
const schemaOwnsBody = Object.prototype.hasOwnProperty.call(schemaProperties, "body");
|
|
3810
|
+
let explicitQuery = {};
|
|
3811
|
+
if (!schemaOwnsQuery) {
|
|
3812
|
+
explicitQuery = plainObject(remaining.query);
|
|
3813
|
+
delete remaining.query;
|
|
3814
|
+
}
|
|
3815
|
+
let explicitBody;
|
|
3816
|
+
if (!schemaOwnsBody) {
|
|
3817
|
+
explicitBody = Object.prototype.hasOwnProperty.call(remaining, "body") ? remaining.body : void 0;
|
|
3818
|
+
delete remaining.body;
|
|
3819
|
+
}
|
|
3409
3820
|
if (action.method === "GET") {
|
|
3410
3821
|
return {
|
|
3411
3822
|
path,
|
|
@@ -3564,7 +3975,7 @@ var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
|
|
|
3564
3975
|
["restore", "archive"],
|
|
3565
3976
|
["unpublish", "publish"]
|
|
3566
3977
|
]);
|
|
3567
|
-
function
|
|
3978
|
+
function isRecord4(value) {
|
|
3568
3979
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3569
3980
|
}
|
|
3570
3981
|
function nonEmptyStrings(value) {
|
|
@@ -3602,10 +4013,10 @@ function collectionFor(action, resource) {
|
|
|
3602
4013
|
return resource.endsWith("s") ? resource : `${resource}s`;
|
|
3603
4014
|
}
|
|
3604
4015
|
function isConcreteOutputSchema(schema) {
|
|
3605
|
-
if (!
|
|
4016
|
+
if (!isRecord4(schema)) return false;
|
|
3606
4017
|
if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
|
|
3607
|
-
if (schema.type === "array" &&
|
|
3608
|
-
if (
|
|
4018
|
+
if (schema.type === "array" && isRecord4(schema.items) && Object.keys(schema.items).length > 0) return true;
|
|
4019
|
+
if (isRecord4(schema.properties) && Object.keys(schema.properties).length > 0) return true;
|
|
3609
4020
|
return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
|
|
3610
4021
|
}
|
|
3611
4022
|
function resourceSchema(resource) {
|
|
@@ -3686,7 +4097,7 @@ function derivedOutputSchema(action) {
|
|
|
3686
4097
|
};
|
|
3687
4098
|
}
|
|
3688
4099
|
function sampleForSchema(schema, depth = 0) {
|
|
3689
|
-
if (!
|
|
4100
|
+
if (!isRecord4(schema) || depth > 5) return null;
|
|
3690
4101
|
if (schema.const !== void 0) return schema.const;
|
|
3691
4102
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
3692
4103
|
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1);
|
|
@@ -3700,8 +4111,8 @@ function sampleForSchema(schema, depth = 0) {
|
|
|
3700
4111
|
return schema.minLength > 1 ? "example".padEnd(schema.minLength, "x") : "example";
|
|
3701
4112
|
}
|
|
3702
4113
|
if (schema.type === "null") return null;
|
|
3703
|
-
if (schema.type === "object" ||
|
|
3704
|
-
const properties =
|
|
4114
|
+
if (schema.type === "object" || isRecord4(schema.properties)) {
|
|
4115
|
+
const properties = isRecord4(schema.properties) ? schema.properties : {};
|
|
3705
4116
|
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3706
4117
|
const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1)]);
|
|
3707
4118
|
return Object.fromEntries(entries);
|
|
@@ -3751,21 +4162,32 @@ function defaultErrors(action) {
|
|
|
3751
4162
|
const errors = [
|
|
3752
4163
|
{ status: 401, code: "authentication_required", recovery: "Authenticate again or provide a valid Topolo API key." },
|
|
3753
4164
|
{ status: 403, code: "permission_denied", recovery: `Grant the credential permission ${action.permission || action.permissionName || "required by this action"} and the required resource binding.` },
|
|
3754
|
-
{ status:
|
|
4165
|
+
{ status: 429, code: "rate_limited", recovery: "Wait for Retry-After when present, then retry without parallel duplicate calls." },
|
|
4166
|
+
{ status: 422, code: "invalid_input", recovery: "Correct the payload using inputSchema, then validate before retrying." },
|
|
4167
|
+
{ status: 503, code: "dependency_unavailable", recovery: "Retry the action. If it continues, inspect the structured error details using the request ID." }
|
|
3755
4168
|
];
|
|
3756
4169
|
if (action.method !== "GET") {
|
|
3757
4170
|
errors.push({ status: 409, code: "state_conflict", recovery: "Read the current resource state, reconcile the intended change, and retry only if still valid." });
|
|
3758
4171
|
}
|
|
3759
4172
|
return errors;
|
|
3760
4173
|
}
|
|
4174
|
+
function mergeErrors(defaults, declared) {
|
|
4175
|
+
const merged = [...defaults];
|
|
4176
|
+
for (const error of Array.isArray(declared) ? declared : []) {
|
|
4177
|
+
const index = merged.findIndex((candidate) => candidate.status === error?.status && candidate.code === error?.code);
|
|
4178
|
+
if (index === -1) merged.push(error);
|
|
4179
|
+
else merged[index] = error;
|
|
4180
|
+
}
|
|
4181
|
+
return merged;
|
|
4182
|
+
}
|
|
3761
4183
|
function enrichActionContract(manifest, action, allActions = manifest.actions || []) {
|
|
3762
4184
|
const appId = String(manifest.appId || manifest.app_id || "").trim();
|
|
3763
4185
|
const service = slugify(manifest.slug || appId.replace(/^app_/, "").replaceAll("_", "-") || manifest.name);
|
|
3764
4186
|
const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
|
|
3765
4187
|
const resource = resourceFor(action);
|
|
3766
4188
|
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3767
|
-
const existing =
|
|
3768
|
-
const recovery =
|
|
4189
|
+
const existing = isRecord4(action.agentContract) ? structuredClone(action.agentContract) : {};
|
|
4190
|
+
const recovery = isRecord4(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
|
|
3769
4191
|
const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
|
|
3770
4192
|
const inputExample = sampleForSchema(action.inputSchema);
|
|
3771
4193
|
const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples : [{ input: inputExample ?? {}, expected: `A response matching the published outputSchema for ${action.name}.` }];
|
|
@@ -3777,9 +4199,9 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3777
4199
|
...existing,
|
|
3778
4200
|
docsUrl,
|
|
3779
4201
|
...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
|
|
3780
|
-
resource:
|
|
4202
|
+
resource: isRecord4(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
|
|
3781
4203
|
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.`],
|
|
3782
|
-
errors:
|
|
4204
|
+
errors: mergeErrors(defaultErrors(action), existing.errors),
|
|
3783
4205
|
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"}.`],
|
|
3784
4206
|
examples,
|
|
3785
4207
|
recovery,
|
|
@@ -3791,15 +4213,15 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3791
4213
|
function actionContractCompleteness(action) {
|
|
3792
4214
|
const contract = action?.agentContract;
|
|
3793
4215
|
return {
|
|
3794
|
-
inputSchema:
|
|
4216
|
+
inputSchema: isRecord4(action?.inputSchema),
|
|
3795
4217
|
outputSchema: isConcreteOutputSchema(action?.outputSchema),
|
|
3796
4218
|
effects: nonEmptyStrings(contract?.effects),
|
|
3797
4219
|
errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
|
|
3798
4220
|
docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
|
|
3799
4221
|
examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
|
|
3800
4222
|
verification: nonEmptyStrings(contract?.verify),
|
|
3801
|
-
recovery:
|
|
3802
|
-
agentContract:
|
|
4223
|
+
recovery: isRecord4(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
|
|
4224
|
+
agentContract: isRecord4(contract) && isRecord4(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
|
|
3803
4225
|
};
|
|
3804
4226
|
}
|
|
3805
4227
|
|
|
@@ -4204,7 +4626,7 @@ function createTopoloWidgetError(error) {
|
|
|
4204
4626
|
}
|
|
4205
4627
|
function validateTopoloWidgetResponse(response) {
|
|
4206
4628
|
const errors = [];
|
|
4207
|
-
if (!
|
|
4629
|
+
if (!isRecord5(response)) {
|
|
4208
4630
|
return { ok: false, errors: ["response must be an object"] };
|
|
4209
4631
|
}
|
|
4210
4632
|
if (typeof response.success !== "boolean") {
|
|
@@ -4223,7 +4645,7 @@ function validateTopoloWidgetResponse(response) {
|
|
|
4223
4645
|
return { ok: errors.length === 0, errors };
|
|
4224
4646
|
}
|
|
4225
4647
|
function validateWidget(widget, path, errors) {
|
|
4226
|
-
if (!
|
|
4648
|
+
if (!isRecord5(widget)) {
|
|
4227
4649
|
errors.push(`${path} must be an object`);
|
|
4228
4650
|
return;
|
|
4229
4651
|
}
|
|
@@ -4250,7 +4672,7 @@ function validateWidget(widget, path, errors) {
|
|
|
4250
4672
|
case "chart":
|
|
4251
4673
|
validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
|
|
4252
4674
|
validateNonEmptyString(widget.title, `${path}.title`, errors);
|
|
4253
|
-
if (!
|
|
4675
|
+
if (!isRecord5(widget.data)) {
|
|
4254
4676
|
errors.push(`${path}.data must be an object`);
|
|
4255
4677
|
} else {
|
|
4256
4678
|
validateArray2(widget.data.labels, `${path}.data.labels`, errors);
|
|
@@ -4266,7 +4688,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
4266
4688
|
errors.push(`${path} must be an object`);
|
|
4267
4689
|
return;
|
|
4268
4690
|
}
|
|
4269
|
-
if (!
|
|
4691
|
+
if (!isRecord5(snapshot)) {
|
|
4270
4692
|
errors.push(`${path} must be an object`);
|
|
4271
4693
|
return;
|
|
4272
4694
|
}
|
|
@@ -4286,7 +4708,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
4286
4708
|
}
|
|
4287
4709
|
snapshot.availableMetrics.forEach((metric, index) => {
|
|
4288
4710
|
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
4289
|
-
if (!
|
|
4711
|
+
if (!isRecord5(metric)) {
|
|
4290
4712
|
errors.push(`${metricPath} must be an object`);
|
|
4291
4713
|
return;
|
|
4292
4714
|
}
|
|
@@ -4307,7 +4729,7 @@ function validateArray2(value, path, errors) {
|
|
|
4307
4729
|
errors.push(`${path} must be an array`);
|
|
4308
4730
|
}
|
|
4309
4731
|
}
|
|
4310
|
-
function
|
|
4732
|
+
function isRecord5(value) {
|
|
4311
4733
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4312
4734
|
}
|
|
4313
4735
|
function toIsoTimestamp(input) {
|
|
@@ -4359,6 +4781,9 @@ function createTopolo(options) {
|
|
|
4359
4781
|
defineTopoloStatsWidget,
|
|
4360
4782
|
digestTopoloActionCatalog,
|
|
4361
4783
|
enrichActionContract,
|
|
4784
|
+
parseTopoloActionUploadContract,
|
|
4785
|
+
prepareTopoloActionBase64UploadInput,
|
|
4786
|
+
prepareTopoloActionUploadInput,
|
|
4362
4787
|
requirementsForApplication,
|
|
4363
4788
|
resolveCatalogServiceUrl,
|
|
4364
4789
|
resolveServiceUrl,
|