@topolo/sdk 0.9.6 → 0.9.8
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 +370 -46
- package/dist/index.d.cts +82 -8
- package/dist/index.d.ts +82 -8
- package/dist/index.js +367 -46
- package/dist/node/credential-store.cjs +30 -3
- package/dist/node/credential-store.js +30 -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
|
|
|
@@ -333,7 +375,9 @@ var APPLICATIONS = {
|
|
|
333
375
|
"domains": [
|
|
334
376
|
"brand.topolo.app"
|
|
335
377
|
],
|
|
336
|
-
"routes": [
|
|
378
|
+
"routes": [
|
|
379
|
+
"brand.topolo.app"
|
|
380
|
+
],
|
|
337
381
|
"appId": "brand"
|
|
338
382
|
},
|
|
339
383
|
{
|
|
@@ -505,7 +549,9 @@ var APPLICATIONS = {
|
|
|
505
549
|
"domains": [
|
|
506
550
|
"chat.topolo.app"
|
|
507
551
|
],
|
|
508
|
-
"routes": [
|
|
552
|
+
"routes": [
|
|
553
|
+
"chat.topolo.app"
|
|
554
|
+
],
|
|
509
555
|
"appId": "chat"
|
|
510
556
|
},
|
|
511
557
|
{
|
|
@@ -1065,7 +1111,9 @@ var APPLICATIONS = {
|
|
|
1065
1111
|
"domains": [
|
|
1066
1112
|
"mail.topolo.app"
|
|
1067
1113
|
],
|
|
1068
|
-
"routes": [
|
|
1114
|
+
"routes": [
|
|
1115
|
+
"mail.topolo.app"
|
|
1116
|
+
],
|
|
1069
1117
|
"appId": "mail"
|
|
1070
1118
|
},
|
|
1071
1119
|
{
|
|
@@ -1416,7 +1464,9 @@ var APPLICATIONS = {
|
|
|
1416
1464
|
"domains": [
|
|
1417
1465
|
"quro.topolo.app"
|
|
1418
1466
|
],
|
|
1419
|
-
"routes": [
|
|
1467
|
+
"routes": [
|
|
1468
|
+
"quro.topolo.app"
|
|
1469
|
+
],
|
|
1420
1470
|
"appId": null
|
|
1421
1471
|
}
|
|
1422
1472
|
]
|
|
@@ -2498,6 +2548,168 @@ function isMultipleOf(value, divisor) {
|
|
|
2498
2548
|
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
|
|
2499
2549
|
}
|
|
2500
2550
|
|
|
2551
|
+
// src/action-upload.ts
|
|
2552
|
+
function parseTopoloActionUploadContract(value) {
|
|
2553
|
+
if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
|
|
2554
|
+
throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
|
|
2555
|
+
}
|
|
2556
|
+
const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
|
|
2557
|
+
const fileNameField = requiredField(value.fileNameField, "fileNameField");
|
|
2558
|
+
const maxBytes = Number(value.maxBytes);
|
|
2559
|
+
const acceptedContentTypes = Array.isArray(value.acceptedContentTypes) ? value.acceptedContentTypes.map((item) => String(item).trim().toLowerCase()).filter(Boolean) : [];
|
|
2560
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0 || acceptedContentTypes.length === 0) {
|
|
2561
|
+
throw new TopoloSdkError("invalid_upload_contract", "The action upload contract must declare maxBytes and acceptedContentTypes.");
|
|
2562
|
+
}
|
|
2563
|
+
if (value.profile === "inline-base64") {
|
|
2564
|
+
return {
|
|
2565
|
+
profile: "inline-base64",
|
|
2566
|
+
contentField: requiredField(value.contentField, "contentField"),
|
|
2567
|
+
contentTypeField,
|
|
2568
|
+
fileNameField,
|
|
2569
|
+
maxBytes,
|
|
2570
|
+
acceptedContentTypes
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
const uploadMethod = value.uploadMethod === "PUT" || value.uploadMethod === "POST" ? value.uploadMethod : null;
|
|
2574
|
+
const uploadUrlPath = requiredPath(value.uploadUrlPath, "uploadUrlPath");
|
|
2575
|
+
const complete = value.complete === void 0 ? void 0 : parseCompleteContract(value.complete);
|
|
2576
|
+
if (!uploadMethod) throw new TopoloSdkError("invalid_upload_contract", "The action upload contract has an invalid uploadMethod.");
|
|
2577
|
+
return {
|
|
2578
|
+
profile: "url-session",
|
|
2579
|
+
fileNameField,
|
|
2580
|
+
contentTypeField,
|
|
2581
|
+
sizeField: requiredField(value.sizeField, "sizeField"),
|
|
2582
|
+
maxBytes,
|
|
2583
|
+
acceptedContentTypes,
|
|
2584
|
+
uploadUrlPath,
|
|
2585
|
+
uploadMethod,
|
|
2586
|
+
...complete ? { complete } : {}
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
function prepareTopoloActionUploadInput(contractValue, content, input, options) {
|
|
2590
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2591
|
+
if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
|
|
2592
|
+
const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
2593
|
+
if (bytes.byteLength === 0) {
|
|
2594
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2595
|
+
}
|
|
2596
|
+
if (bytes.byteLength > contract.maxBytes) {
|
|
2597
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2598
|
+
}
|
|
2599
|
+
return mapUploadInput(contract, bytesToBase64(bytes), input, options);
|
|
2600
|
+
}
|
|
2601
|
+
function prepareTopoloActionBase64UploadInput(contractValue, contentBase64, input, options) {
|
|
2602
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2603
|
+
if (contract.profile !== "inline-base64") throw unsupportedInlineProfile();
|
|
2604
|
+
if (!isCanonicalBase64(contentBase64)) {
|
|
2605
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
2606
|
+
}
|
|
2607
|
+
const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
|
|
2608
|
+
const byteLength = contentBase64.length / 4 * 3 - padding;
|
|
2609
|
+
if (byteLength === 0) throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2610
|
+
if (byteLength > contract.maxBytes) {
|
|
2611
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2612
|
+
}
|
|
2613
|
+
return mapUploadInput(contract, contentBase64, input, options);
|
|
2614
|
+
}
|
|
2615
|
+
function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, options) {
|
|
2616
|
+
const contract = parseTopoloActionUploadContract(contractValue);
|
|
2617
|
+
if (contract.profile !== "url-session") {
|
|
2618
|
+
throw new TopoloSdkError("invalid_upload_contract", "This action does not publish a URL-session upload contract.");
|
|
2619
|
+
}
|
|
2620
|
+
validateUploadMetadata(contract, byteLength, options);
|
|
2621
|
+
return {
|
|
2622
|
+
contract,
|
|
2623
|
+
input: {
|
|
2624
|
+
...input,
|
|
2625
|
+
[contract.fileNameField]: options.fileName.trim(),
|
|
2626
|
+
[contract.contentTypeField]: options.contentType.trim().toLowerCase(),
|
|
2627
|
+
[contract.sizeField]: byteLength
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
}
|
|
2631
|
+
function topoloUploadResponseValue(response, path) {
|
|
2632
|
+
return path.split(".").reduce((value, segment) => isRecord2(value) ? value[segment] : void 0, response);
|
|
2633
|
+
}
|
|
2634
|
+
function decodeTopoloActionBase64(contentBase64) {
|
|
2635
|
+
if (!isCanonicalBase64(contentBase64)) {
|
|
2636
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must be canonical base64.");
|
|
2637
|
+
}
|
|
2638
|
+
const binary = atob(contentBase64);
|
|
2639
|
+
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
2640
|
+
}
|
|
2641
|
+
function mapUploadInput(contract, contentBase64, input, options) {
|
|
2642
|
+
validateUploadMetadata(contract, Math.max(1, decodedBase64Size(contentBase64)), options);
|
|
2643
|
+
const fileName = options.fileName.trim();
|
|
2644
|
+
const contentType = options.contentType.trim().toLowerCase();
|
|
2645
|
+
return {
|
|
2646
|
+
...input,
|
|
2647
|
+
[contract.contentField]: contentBase64,
|
|
2648
|
+
[contract.contentTypeField]: contentType,
|
|
2649
|
+
[contract.fileNameField]: fileName
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
function validateUploadMetadata(contract, byteLength, options) {
|
|
2653
|
+
const fileName = options.fileName.trim();
|
|
2654
|
+
const contentType = options.contentType.trim().toLowerCase();
|
|
2655
|
+
if (!fileName) throw new TopoloSdkError("invalid_upload_file_name", "Upload fileName must not be empty.");
|
|
2656
|
+
if (!Number.isSafeInteger(byteLength) || byteLength <= 0) {
|
|
2657
|
+
throw new TopoloSdkError("invalid_upload_content", "Upload content must not be empty.");
|
|
2658
|
+
}
|
|
2659
|
+
if (byteLength > contract.maxBytes) {
|
|
2660
|
+
throw new TopoloSdkError("upload_too_large", `Upload content exceeds the ${contract.maxBytes}-byte action limit.`);
|
|
2661
|
+
}
|
|
2662
|
+
if (!matchesContentType(contentType, contract.acceptedContentTypes)) {
|
|
2663
|
+
throw new TopoloSdkError("unsupported_upload_content_type", `Content type "${contentType}" is not accepted by this action.`);
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
function decodedBase64Size(contentBase64) {
|
|
2667
|
+
const padding = contentBase64.endsWith("==") ? 2 : contentBase64.endsWith("=") ? 1 : 0;
|
|
2668
|
+
return contentBase64.length / 4 * 3 - padding;
|
|
2669
|
+
}
|
|
2670
|
+
function parseCompleteContract(value) {
|
|
2671
|
+
if (!isRecord2(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
|
|
2672
|
+
return {
|
|
2673
|
+
actionId: requiredPath(value.actionId, "complete.actionId"),
|
|
2674
|
+
inputField: requiredField(value.inputField, "complete.inputField"),
|
|
2675
|
+
responsePath: requiredPath(value.responsePath, "complete.responsePath")
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
function requiredPath(value, name) {
|
|
2679
|
+
const path = typeof value === "string" ? value.trim() : "";
|
|
2680
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path)) {
|
|
2681
|
+
throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
|
|
2682
|
+
}
|
|
2683
|
+
return path;
|
|
2684
|
+
}
|
|
2685
|
+
function unsupportedInlineProfile() {
|
|
2686
|
+
return new TopoloSdkError("invalid_upload_contract", "This action does not publish an inline-base64 upload contract.");
|
|
2687
|
+
}
|
|
2688
|
+
function isCanonicalBase64(value) {
|
|
2689
|
+
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);
|
|
2690
|
+
}
|
|
2691
|
+
function requiredField(value, name) {
|
|
2692
|
+
const field = typeof value === "string" ? value.trim() : "";
|
|
2693
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(field)) {
|
|
2694
|
+
throw new TopoloSdkError("invalid_upload_contract", `The action upload contract has an invalid ${name}.`);
|
|
2695
|
+
}
|
|
2696
|
+
return field;
|
|
2697
|
+
}
|
|
2698
|
+
function matchesContentType(contentType, accepted) {
|
|
2699
|
+
return accepted.some((candidate) => candidate === "*/*" || candidate === contentType || candidate.endsWith("/*") && contentType.startsWith(candidate.slice(0, -1)));
|
|
2700
|
+
}
|
|
2701
|
+
function bytesToBase64(bytes) {
|
|
2702
|
+
let binary = "";
|
|
2703
|
+
const chunkSize = 32768;
|
|
2704
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
2705
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
|
|
2706
|
+
}
|
|
2707
|
+
return btoa(binary);
|
|
2708
|
+
}
|
|
2709
|
+
function isRecord2(value) {
|
|
2710
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2501
2713
|
// src/client.ts
|
|
2502
2714
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
2503
2715
|
var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
|
|
@@ -2575,6 +2787,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
2575
2787
|
if (v !== void 0) url.searchParams.set(k, String(v));
|
|
2576
2788
|
}
|
|
2577
2789
|
}
|
|
2790
|
+
if (opts.body !== void 0 && opts.rawBody !== void 0) {
|
|
2791
|
+
throw new TopoloSdkError("invalid_request_body", "body and rawBody are mutually exclusive.");
|
|
2792
|
+
}
|
|
2578
2793
|
const headers = new Headers();
|
|
2579
2794
|
headers.set("Accept", "application/json");
|
|
2580
2795
|
if (opts.body !== void 0) headers.set("Content-Type", "application/json");
|
|
@@ -2609,7 +2824,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2609
2824
|
return await this.fetchImpl(urlStr, {
|
|
2610
2825
|
method,
|
|
2611
2826
|
headers: new Headers(headers),
|
|
2612
|
-
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
2827
|
+
body: opts.rawBody ?? (opts.body !== void 0 ? JSON.stringify(opts.body) : null),
|
|
2613
2828
|
signal
|
|
2614
2829
|
});
|
|
2615
2830
|
} finally {
|
|
@@ -2657,14 +2872,32 @@ var TopoloClient = class _TopoloClient {
|
|
|
2657
2872
|
error: message,
|
|
2658
2873
|
status: res.status
|
|
2659
2874
|
});
|
|
2660
|
-
|
|
2875
|
+
const errorContext = {
|
|
2876
|
+
status: res.status,
|
|
2877
|
+
body: parsed,
|
|
2878
|
+
service: resolvedService.serviceKey,
|
|
2879
|
+
path: opts.path,
|
|
2880
|
+
requestId: res.headers.get("X-Topolo-Request-Id") || requestId,
|
|
2881
|
+
retryAfter: res.headers.get("Retry-After") || void 0
|
|
2882
|
+
};
|
|
2883
|
+
if (res.status === 401) {
|
|
2884
|
+
throw new TopoloAuthError(describeError(parsed, "Unauthorized"), "auth_error", errorContext);
|
|
2885
|
+
}
|
|
2661
2886
|
if (res.status === 403) {
|
|
2662
2887
|
throw new TopoloPermissionError(
|
|
2663
2888
|
describeError(parsed, "Permission denied"),
|
|
2664
|
-
extractRequired(parsed)
|
|
2889
|
+
extractRequired(parsed),
|
|
2890
|
+
errorContext
|
|
2665
2891
|
);
|
|
2666
2892
|
}
|
|
2667
|
-
throw new TopoloHttpError(
|
|
2893
|
+
throw new TopoloHttpError(
|
|
2894
|
+
resolvedService.serviceKey,
|
|
2895
|
+
opts.path,
|
|
2896
|
+
res.status,
|
|
2897
|
+
parsed,
|
|
2898
|
+
message,
|
|
2899
|
+
errorContext
|
|
2900
|
+
);
|
|
2668
2901
|
}
|
|
2669
2902
|
this.emit({
|
|
2670
2903
|
phase: "response",
|
|
@@ -2823,7 +3056,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2823
3056
|
}
|
|
2824
3057
|
const entries = [];
|
|
2825
3058
|
for (const item of raw) {
|
|
2826
|
-
if (!
|
|
3059
|
+
if (!isRecord3(item)) continue;
|
|
2827
3060
|
const s = item;
|
|
2828
3061
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
2829
3062
|
if (!appId) continue;
|
|
@@ -2898,8 +3131,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2898
3131
|
"app_topolo_auth.service_api_key_resources.list",
|
|
2899
3132
|
{ appId: service.appId }
|
|
2900
3133
|
);
|
|
2901
|
-
const payload =
|
|
2902
|
-
if (!
|
|
3134
|
+
const payload = isRecord3(response) && isRecord3(response.data) ? response.data : response;
|
|
3135
|
+
if (!isRecord3(payload) || !Array.isArray(payload.resources)) {
|
|
2903
3136
|
throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
|
|
2904
3137
|
}
|
|
2905
3138
|
return {
|
|
@@ -3005,6 +3238,71 @@ var TopoloClient = class _TopoloClient {
|
|
|
3005
3238
|
const entry = await this.getAction(action);
|
|
3006
3239
|
return this.executeResolvedAction(entry, input, options);
|
|
3007
3240
|
}
|
|
3241
|
+
async uploadAction(action, content, input, options) {
|
|
3242
|
+
const entry = await this.getAction(action);
|
|
3243
|
+
const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
|
|
3244
|
+
if (contract.profile === "url-session") {
|
|
3245
|
+
const bytes = content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
3246
|
+
return this.executeUrlSessionUpload(entry, contract, bytes, input, options);
|
|
3247
|
+
}
|
|
3248
|
+
const uploadInput = prepareTopoloActionUploadInput(entry.agentContract?.upload, content, input, options);
|
|
3249
|
+
return this.executeResolvedAction(entry, uploadInput, options);
|
|
3250
|
+
}
|
|
3251
|
+
async uploadActionBase64(action, contentBase64, input, options) {
|
|
3252
|
+
const entry = await this.getAction(action);
|
|
3253
|
+
const contract = parseTopoloActionUploadContract(entry.agentContract?.upload);
|
|
3254
|
+
if (contract.profile === "url-session") {
|
|
3255
|
+
return this.executeUrlSessionUpload(entry, contract, decodeTopoloActionBase64(contentBase64), input, options);
|
|
3256
|
+
}
|
|
3257
|
+
const uploadInput = prepareTopoloActionBase64UploadInput(entry.agentContract?.upload, contentBase64, input, options);
|
|
3258
|
+
return this.executeResolvedAction(entry, uploadInput, options);
|
|
3259
|
+
}
|
|
3260
|
+
async executeUrlSessionUpload(entry, contract, bytes, input, options) {
|
|
3261
|
+
const prepared = prepareTopoloUrlSessionUploadInput(contract, bytes.byteLength, input, options);
|
|
3262
|
+
const initiation = await this.executeResolvedAction(entry, prepared.input, options);
|
|
3263
|
+
const uploadUrl = topoloUploadResponseValue(initiation, contract.uploadUrlPath);
|
|
3264
|
+
if (typeof uploadUrl !== "string" || !uploadUrl.trim()) {
|
|
3265
|
+
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.uploadUrlPath}.`);
|
|
3266
|
+
}
|
|
3267
|
+
await this.putUrlSessionContent(entry, uploadUrl, contract.uploadMethod, bytes, options);
|
|
3268
|
+
if (!contract.complete) return initiation;
|
|
3269
|
+
const completionValue = topoloUploadResponseValue(initiation, contract.complete.responsePath);
|
|
3270
|
+
if (typeof completionValue !== "string" || !completionValue.trim()) {
|
|
3271
|
+
throw new TopoloSdkError("invalid_upload_response", `Upload action response is missing ${contract.complete.responsePath}.`);
|
|
3272
|
+
}
|
|
3273
|
+
const completion = await this.getAction(contract.complete.actionId);
|
|
3274
|
+
return this.executeResolvedAction(completion, {
|
|
3275
|
+
...options.completionInput ?? {},
|
|
3276
|
+
[contract.complete.inputField]: completionValue
|
|
3277
|
+
}, options);
|
|
3278
|
+
}
|
|
3279
|
+
async putUrlSessionContent(entry, uploadUrl, method, bytes, options) {
|
|
3280
|
+
const service = entry.appSlug || entry.appId;
|
|
3281
|
+
const resolved = entry.apiBaseUrl ? { serviceKey: normalizeLookupKey(service), baseUrl: requireHttpOrigin(entry.apiBaseUrl) } : await this.resolveRequestService(service);
|
|
3282
|
+
const target = new URL(uploadUrl, resolved.baseUrl.endsWith("/") ? resolved.baseUrl : `${resolved.baseUrl}/`);
|
|
3283
|
+
const body = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
3284
|
+
if (target.origin === new URL(resolved.baseUrl).origin) {
|
|
3285
|
+
await this.request({
|
|
3286
|
+
service,
|
|
3287
|
+
baseUrl: resolved.baseUrl,
|
|
3288
|
+
path: target.toString(),
|
|
3289
|
+
method,
|
|
3290
|
+
rawBody: body,
|
|
3291
|
+
headers: { "Content-Type": options.contentType.trim().toLowerCase() },
|
|
3292
|
+
resource: options.resource,
|
|
3293
|
+
confirm: options.confirm
|
|
3294
|
+
});
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
const response = await this.fetchImpl(target, {
|
|
3298
|
+
method,
|
|
3299
|
+
headers: { "Content-Type": options.contentType.trim().toLowerCase() },
|
|
3300
|
+
body
|
|
3301
|
+
});
|
|
3302
|
+
if (!response.ok) {
|
|
3303
|
+
throw new TopoloHttpError(service, target.pathname, response.status, await response.text(), `Upload transport failed with HTTP ${response.status}.`);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3008
3306
|
async validateActionInput(action, input = {}) {
|
|
3009
3307
|
const entry = await this.getAction(action);
|
|
3010
3308
|
return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, input) };
|
|
@@ -3163,7 +3461,7 @@ function resolveConfiguredAppId(slug, appIds) {
|
|
|
3163
3461
|
return null;
|
|
3164
3462
|
}
|
|
3165
3463
|
function normalizeApiKeyResourceMetadata(input) {
|
|
3166
|
-
if (!
|
|
3464
|
+
if (!isRecord3(input)) return null;
|
|
3167
3465
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
3168
3466
|
const label = readString(input.label);
|
|
3169
3467
|
const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
|
|
@@ -3197,7 +3495,7 @@ function normalizeApiKeyResourceMetadataList(input) {
|
|
|
3197
3495
|
return resources;
|
|
3198
3496
|
}
|
|
3199
3497
|
function normalizeBindableResource(input) {
|
|
3200
|
-
if (!
|
|
3498
|
+
if (!isRecord3(input)) return null;
|
|
3201
3499
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
3202
3500
|
const resourceId = readString(input.resourceId ?? input.resource_id);
|
|
3203
3501
|
if (!resourceType || !resourceId) return null;
|
|
@@ -3208,13 +3506,13 @@ function normalizeBindableResource(input) {
|
|
|
3208
3506
|
slug: readOptionalString(input.slug),
|
|
3209
3507
|
status: readOptionalString(input.status),
|
|
3210
3508
|
organizationId: readOptionalString(input.organizationId ?? input.organization_id),
|
|
3211
|
-
metadata:
|
|
3509
|
+
metadata: isRecord3(input.metadata) ? input.metadata : {}
|
|
3212
3510
|
};
|
|
3213
3511
|
}
|
|
3214
3512
|
function normalizeResourceBindings(input) {
|
|
3215
3513
|
if (!Array.isArray(input)) return [];
|
|
3216
3514
|
return input.flatMap((binding) => {
|
|
3217
|
-
if (!
|
|
3515
|
+
if (!isRecord3(binding)) return [];
|
|
3218
3516
|
const resourceType = readString(binding.resourceType ?? binding.resource_type);
|
|
3219
3517
|
const resourceId = readString(binding.resourceId ?? binding.resource_id);
|
|
3220
3518
|
return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
|
|
@@ -3227,7 +3525,7 @@ function readOptionalString(value) {
|
|
|
3227
3525
|
const normalized = readString(value);
|
|
3228
3526
|
return normalized || null;
|
|
3229
3527
|
}
|
|
3230
|
-
function
|
|
3528
|
+
function isRecord3(value) {
|
|
3231
3529
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3232
3530
|
}
|
|
3233
3531
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
@@ -3299,14 +3597,14 @@ function catalogSearchQuery(options) {
|
|
|
3299
3597
|
return query;
|
|
3300
3598
|
}
|
|
3301
3599
|
function normalizeCatalogPage(value) {
|
|
3302
|
-
if (!
|
|
3600
|
+
if (!isRecord3(value)) return { nextCursor: null, hasMore: false };
|
|
3303
3601
|
return {
|
|
3304
3602
|
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
3305
3603
|
hasMore: value.hasMore === true
|
|
3306
3604
|
};
|
|
3307
3605
|
}
|
|
3308
3606
|
function normalizeServiceCatalogEntry(item) {
|
|
3309
|
-
if (!
|
|
3607
|
+
if (!isRecord3(item)) return null;
|
|
3310
3608
|
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
3311
3609
|
if (!appId) return null;
|
|
3312
3610
|
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
@@ -3564,7 +3862,7 @@ var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
|
|
|
3564
3862
|
["restore", "archive"],
|
|
3565
3863
|
["unpublish", "publish"]
|
|
3566
3864
|
]);
|
|
3567
|
-
function
|
|
3865
|
+
function isRecord4(value) {
|
|
3568
3866
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3569
3867
|
}
|
|
3570
3868
|
function nonEmptyStrings(value) {
|
|
@@ -3574,9 +3872,15 @@ function slugify(value) {
|
|
|
3574
3872
|
return String(value || "").trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3575
3873
|
}
|
|
3576
3874
|
function singularize(value) {
|
|
3875
|
+
const irregular = /* @__PURE__ */ new Map([
|
|
3876
|
+
["analyses", "analysis"],
|
|
3877
|
+
["buses", "bus"],
|
|
3878
|
+
["statuses", "status"]
|
|
3879
|
+
]);
|
|
3880
|
+
if (irregular.has(value)) return irregular.get(value);
|
|
3577
3881
|
if (value.endsWith("ies") && value.length > 3) return `${value.slice(0, -3)}y`;
|
|
3578
|
-
if (
|
|
3579
|
-
if (value.endsWith("s") &&
|
|
3882
|
+
if (/(?:ches|shes|sses|xes|zes)$/.test(value)) return value.slice(0, -2);
|
|
3883
|
+
if (value.endsWith("s") && !/(?:ss|us|is)$/.test(value)) return value.slice(0, -1);
|
|
3580
3884
|
return value || "resource";
|
|
3581
3885
|
}
|
|
3582
3886
|
function operationFor(action) {
|
|
@@ -3590,11 +3894,16 @@ function resourceFor(action) {
|
|
|
3590
3894
|
const pathToken = String(action.path || "").split("/").filter((token) => token && token !== "api" && !token.startsWith("{")).find((token) => !OPERATION_TOKENS.has(slugify(token)));
|
|
3591
3895
|
return singularize(slugify(pathToken || "resource").replaceAll("-", "_"));
|
|
3592
3896
|
}
|
|
3897
|
+
function collectionFor(action, resource) {
|
|
3898
|
+
const named = String(action.name || "").split(/[._:-]+/).filter(Boolean).find((token) => !OPERATION_TOKENS.has(token));
|
|
3899
|
+
if (named) return slugify(named).replaceAll("-", "_");
|
|
3900
|
+
return resource.endsWith("s") ? resource : `${resource}s`;
|
|
3901
|
+
}
|
|
3593
3902
|
function isConcreteOutputSchema(schema) {
|
|
3594
|
-
if (!
|
|
3903
|
+
if (!isRecord4(schema)) return false;
|
|
3595
3904
|
if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
|
|
3596
|
-
if (schema.type === "array" &&
|
|
3597
|
-
if (
|
|
3905
|
+
if (schema.type === "array" && isRecord4(schema.items) && Object.keys(schema.items).length > 0) return true;
|
|
3906
|
+
if (isRecord4(schema.properties) && Object.keys(schema.properties).length > 0) return true;
|
|
3598
3907
|
return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
|
|
3599
3908
|
}
|
|
3600
3909
|
function resourceSchema(resource) {
|
|
@@ -3620,6 +3929,7 @@ function derivedOutputSchema(action) {
|
|
|
3620
3929
|
"x-topolo-contract-precision": "structural"
|
|
3621
3930
|
};
|
|
3622
3931
|
if (operation === "list" || operation === "search") {
|
|
3932
|
+
const collection = collectionFor(action, resource);
|
|
3623
3933
|
return {
|
|
3624
3934
|
title: `${action.title || action.name} response`,
|
|
3625
3935
|
description: `Collection response returned by ${action.name}. The service may return the collection directly or in an envelope.`,
|
|
@@ -3630,7 +3940,7 @@ function derivedOutputSchema(action) {
|
|
|
3630
3940
|
properties: {
|
|
3631
3941
|
data: { oneOf: [records, { type: "object", additionalProperties: true }] },
|
|
3632
3942
|
items: records,
|
|
3633
|
-
[
|
|
3943
|
+
[collection]: records,
|
|
3634
3944
|
count: { type: "integer", minimum: 0 },
|
|
3635
3945
|
nextCursor: { type: ["string", "null"] }
|
|
3636
3946
|
},
|
|
@@ -3674,7 +3984,7 @@ function derivedOutputSchema(action) {
|
|
|
3674
3984
|
};
|
|
3675
3985
|
}
|
|
3676
3986
|
function sampleForSchema(schema, depth = 0) {
|
|
3677
|
-
if (!
|
|
3987
|
+
if (!isRecord4(schema) || depth > 5) return null;
|
|
3678
3988
|
if (schema.const !== void 0) return schema.const;
|
|
3679
3989
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
3680
3990
|
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1);
|
|
@@ -3688,8 +3998,8 @@ function sampleForSchema(schema, depth = 0) {
|
|
|
3688
3998
|
return schema.minLength > 1 ? "example".padEnd(schema.minLength, "x") : "example";
|
|
3689
3999
|
}
|
|
3690
4000
|
if (schema.type === "null") return null;
|
|
3691
|
-
if (schema.type === "object" ||
|
|
3692
|
-
const properties =
|
|
4001
|
+
if (schema.type === "object" || isRecord4(schema.properties)) {
|
|
4002
|
+
const properties = isRecord4(schema.properties) ? schema.properties : {};
|
|
3693
4003
|
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3694
4004
|
const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1)]);
|
|
3695
4005
|
return Object.fromEntries(entries);
|
|
@@ -3739,21 +4049,32 @@ function defaultErrors(action) {
|
|
|
3739
4049
|
const errors = [
|
|
3740
4050
|
{ status: 401, code: "authentication_required", recovery: "Authenticate again or provide a valid Topolo API key." },
|
|
3741
4051
|
{ status: 403, code: "permission_denied", recovery: `Grant the credential permission ${action.permission || action.permissionName || "required by this action"} and the required resource binding.` },
|
|
3742
|
-
{ status:
|
|
4052
|
+
{ status: 429, code: "rate_limited", recovery: "Wait for Retry-After when present, then retry without parallel duplicate calls." },
|
|
4053
|
+
{ status: 422, code: "invalid_input", recovery: "Correct the payload using inputSchema, then validate before retrying." },
|
|
4054
|
+
{ status: 503, code: "dependency_unavailable", recovery: "Retry the action. If it continues, inspect the structured error details using the request ID." }
|
|
3743
4055
|
];
|
|
3744
4056
|
if (action.method !== "GET") {
|
|
3745
4057
|
errors.push({ status: 409, code: "state_conflict", recovery: "Read the current resource state, reconcile the intended change, and retry only if still valid." });
|
|
3746
4058
|
}
|
|
3747
4059
|
return errors;
|
|
3748
4060
|
}
|
|
4061
|
+
function mergeErrors(defaults, declared) {
|
|
4062
|
+
const merged = [...defaults];
|
|
4063
|
+
for (const error of Array.isArray(declared) ? declared : []) {
|
|
4064
|
+
const index = merged.findIndex((candidate) => candidate.status === error?.status && candidate.code === error?.code);
|
|
4065
|
+
if (index === -1) merged.push(error);
|
|
4066
|
+
else merged[index] = error;
|
|
4067
|
+
}
|
|
4068
|
+
return merged;
|
|
4069
|
+
}
|
|
3749
4070
|
function enrichActionContract(manifest, action, allActions = manifest.actions || []) {
|
|
3750
4071
|
const appId = String(manifest.appId || manifest.app_id || "").trim();
|
|
3751
4072
|
const service = slugify(manifest.slug || appId.replace(/^app_/, "").replaceAll("_", "-") || manifest.name);
|
|
3752
4073
|
const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
|
|
3753
4074
|
const resource = resourceFor(action);
|
|
3754
4075
|
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3755
|
-
const existing =
|
|
3756
|
-
const recovery =
|
|
4076
|
+
const existing = isRecord4(action.agentContract) ? structuredClone(action.agentContract) : {};
|
|
4077
|
+
const recovery = isRecord4(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
|
|
3757
4078
|
const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
|
|
3758
4079
|
const inputExample = sampleForSchema(action.inputSchema);
|
|
3759
4080
|
const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples : [{ input: inputExample ?? {}, expected: `A response matching the published outputSchema for ${action.name}.` }];
|
|
@@ -3765,9 +4086,9 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3765
4086
|
...existing,
|
|
3766
4087
|
docsUrl,
|
|
3767
4088
|
...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
|
|
3768
|
-
resource:
|
|
4089
|
+
resource: isRecord4(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
|
|
3769
4090
|
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.`],
|
|
3770
|
-
errors:
|
|
4091
|
+
errors: mergeErrors(defaultErrors(action), existing.errors),
|
|
3771
4092
|
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"}.`],
|
|
3772
4093
|
examples,
|
|
3773
4094
|
recovery,
|
|
@@ -3779,15 +4100,15 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3779
4100
|
function actionContractCompleteness(action) {
|
|
3780
4101
|
const contract = action?.agentContract;
|
|
3781
4102
|
return {
|
|
3782
|
-
inputSchema:
|
|
4103
|
+
inputSchema: isRecord4(action?.inputSchema),
|
|
3783
4104
|
outputSchema: isConcreteOutputSchema(action?.outputSchema),
|
|
3784
4105
|
effects: nonEmptyStrings(contract?.effects),
|
|
3785
4106
|
errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
|
|
3786
4107
|
docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
|
|
3787
4108
|
examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
|
|
3788
4109
|
verification: nonEmptyStrings(contract?.verify),
|
|
3789
|
-
recovery:
|
|
3790
|
-
agentContract:
|
|
4110
|
+
recovery: isRecord4(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
|
|
4111
|
+
agentContract: isRecord4(contract) && isRecord4(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
|
|
3791
4112
|
};
|
|
3792
4113
|
}
|
|
3793
4114
|
|
|
@@ -4192,7 +4513,7 @@ function createTopoloWidgetError(error) {
|
|
|
4192
4513
|
}
|
|
4193
4514
|
function validateTopoloWidgetResponse(response) {
|
|
4194
4515
|
const errors = [];
|
|
4195
|
-
if (!
|
|
4516
|
+
if (!isRecord5(response)) {
|
|
4196
4517
|
return { ok: false, errors: ["response must be an object"] };
|
|
4197
4518
|
}
|
|
4198
4519
|
if (typeof response.success !== "boolean") {
|
|
@@ -4211,7 +4532,7 @@ function validateTopoloWidgetResponse(response) {
|
|
|
4211
4532
|
return { ok: errors.length === 0, errors };
|
|
4212
4533
|
}
|
|
4213
4534
|
function validateWidget(widget, path, errors) {
|
|
4214
|
-
if (!
|
|
4535
|
+
if (!isRecord5(widget)) {
|
|
4215
4536
|
errors.push(`${path} must be an object`);
|
|
4216
4537
|
return;
|
|
4217
4538
|
}
|
|
@@ -4238,7 +4559,7 @@ function validateWidget(widget, path, errors) {
|
|
|
4238
4559
|
case "chart":
|
|
4239
4560
|
validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
|
|
4240
4561
|
validateNonEmptyString(widget.title, `${path}.title`, errors);
|
|
4241
|
-
if (!
|
|
4562
|
+
if (!isRecord5(widget.data)) {
|
|
4242
4563
|
errors.push(`${path}.data must be an object`);
|
|
4243
4564
|
} else {
|
|
4244
4565
|
validateArray2(widget.data.labels, `${path}.data.labels`, errors);
|
|
@@ -4254,7 +4575,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
4254
4575
|
errors.push(`${path} must be an object`);
|
|
4255
4576
|
return;
|
|
4256
4577
|
}
|
|
4257
|
-
if (!
|
|
4578
|
+
if (!isRecord5(snapshot)) {
|
|
4258
4579
|
errors.push(`${path} must be an object`);
|
|
4259
4580
|
return;
|
|
4260
4581
|
}
|
|
@@ -4274,7 +4595,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
4274
4595
|
}
|
|
4275
4596
|
snapshot.availableMetrics.forEach((metric, index) => {
|
|
4276
4597
|
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
4277
|
-
if (!
|
|
4598
|
+
if (!isRecord5(metric)) {
|
|
4278
4599
|
errors.push(`${metricPath} must be an object`);
|
|
4279
4600
|
return;
|
|
4280
4601
|
}
|
|
@@ -4295,7 +4616,7 @@ function validateArray2(value, path, errors) {
|
|
|
4295
4616
|
errors.push(`${path} must be an array`);
|
|
4296
4617
|
}
|
|
4297
4618
|
}
|
|
4298
|
-
function
|
|
4619
|
+
function isRecord5(value) {
|
|
4299
4620
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4300
4621
|
}
|
|
4301
4622
|
function toIsoTimestamp(input) {
|
|
@@ -4347,6 +4668,9 @@ function createTopolo(options) {
|
|
|
4347
4668
|
defineTopoloStatsWidget,
|
|
4348
4669
|
digestTopoloActionCatalog,
|
|
4349
4670
|
enrichActionContract,
|
|
4671
|
+
parseTopoloActionUploadContract,
|
|
4672
|
+
prepareTopoloActionBase64UploadInput,
|
|
4673
|
+
prepareTopoloActionUploadInput,
|
|
4350
4674
|
requirementsForApplication,
|
|
4351
4675
|
resolveCatalogServiceUrl,
|
|
4352
4676
|
resolveServiceUrl,
|