@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/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
- constructor(message, code = "auth_error") {
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
- constructor(message, required) {
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
- constructor(service, path, status, body, message) {
117
- super("http_error", message ?? `HTTP ${status} from ${service}${path}`);
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
- if (res.status === 401) throw new TopoloAuthError(describeError(parsed, "Unauthorized"));
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(resolvedService.serviceKey, opts.path, res.status, parsed);
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 (!isRecord2(item)) continue;
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 = isRecord2(response) && isRecord2(response.data) ? response.data : response;
2902
- if (!isRecord2(payload) || !Array.isArray(payload.resources)) {
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 (!isRecord2(input)) return null;
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 (!isRecord2(input)) return null;
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: isRecord2(input.metadata) ? input.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 (!isRecord2(binding)) return [];
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 isRecord2(value) {
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 (!isRecord2(value)) return { nextCursor: null, hasMore: false };
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 (!isRecord2(item)) return null;
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 explicitQuery = plainObject(remaining.query);
3406
- delete remaining.query;
3407
- const explicitBody = Object.prototype.hasOwnProperty.call(remaining, "body") ? remaining.body : void 0;
3408
- delete remaining.body;
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 isRecord3(value) {
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 (!isRecord3(schema)) return false;
4016
+ if (!isRecord4(schema)) return false;
3606
4017
  if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
3607
- if (schema.type === "array" && isRecord3(schema.items) && Object.keys(schema.items).length > 0) return true;
3608
- if (isRecord3(schema.properties) && Object.keys(schema.properties).length > 0) return true;
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 (!isRecord3(schema) || depth > 5) return null;
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" || isRecord3(schema.properties)) {
3704
- const properties = isRecord3(schema.properties) ? schema.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: 422, code: "invalid_input", recovery: "Correct the payload using inputSchema, then validate before retrying." }
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 = isRecord3(action.agentContract) ? structuredClone(action.agentContract) : {};
3768
- const recovery = isRecord3(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
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: isRecord3(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
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: Array.isArray(existing.errors) && existing.errors.length > 0 ? existing.errors : defaultErrors(action),
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: isRecord3(action?.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: isRecord3(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
3802
- agentContract: isRecord3(contract) && isRecord3(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
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 (!isRecord4(response)) {
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 (!isRecord4(widget)) {
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 (!isRecord4(widget.data)) {
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 (!isRecord4(snapshot)) {
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 (!isRecord4(metric)) {
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 isRecord4(value) {
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,