@topolo/sdk 0.4.4 → 0.4.6
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 +73 -24
- package/dist/index.d.cts +21 -7
- package/dist/index.d.ts +21 -7
- package/dist/index.js +72 -24
- package/package.json +1 -1
- package/src/applications.generated.ts +6 -4
- package/src/client.test.ts +84 -9
- package/src/client.ts +23 -14
- package/src/index.ts +2 -0
- package/src/modules/p2p.test.ts +2 -1
- package/src/services.generated.ts +1 -1
- package/src/services.test.ts +31 -1
- package/src/services.ts +81 -5
package/dist/index.cjs
CHANGED
|
@@ -47,6 +47,7 @@ __export(index_exports, {
|
|
|
47
47
|
defineTopoloRecentActivityWidget: () => defineTopoloRecentActivityWidget,
|
|
48
48
|
defineTopoloStatsWidget: () => defineTopoloStatsWidget,
|
|
49
49
|
requirementsForApplication: () => requirementsForApplication,
|
|
50
|
+
resolveCatalogServiceUrl: () => resolveCatalogServiceUrl,
|
|
50
51
|
resolveServiceUrl: () => resolveServiceUrl,
|
|
51
52
|
validateTopoloWidgetResponse: () => validateTopoloWidgetResponse
|
|
52
53
|
});
|
|
@@ -136,7 +137,7 @@ var SERVICES = {
|
|
|
136
137
|
inventory: "https://inventory.topolo.app",
|
|
137
138
|
learn: "https://learn-api.topolo.app",
|
|
138
139
|
localize: "https://localize.topolo.app",
|
|
139
|
-
mail: "https://mail.topolo.app",
|
|
140
|
+
mail: "https://mail-api.topolo.app",
|
|
140
141
|
mdm: "https://mdm-api.topolo.app",
|
|
141
142
|
messages: "https://messages.topolo.app",
|
|
142
143
|
nexus: "https://nexus-api.topolo.app",
|
|
@@ -974,15 +975,17 @@ var APPLICATIONS = {
|
|
|
974
975
|
"mail.topolo.app"
|
|
975
976
|
],
|
|
976
977
|
"routes": [],
|
|
977
|
-
"serviceId":
|
|
978
|
+
"serviceId": null
|
|
978
979
|
},
|
|
979
980
|
{
|
|
980
981
|
"name": "topolo-mail-api",
|
|
981
982
|
"kind": "worker",
|
|
982
|
-
"apiUrl":
|
|
983
|
-
"domains": [
|
|
983
|
+
"apiUrl": "https://mail-api.topolo.app",
|
|
984
|
+
"domains": [
|
|
985
|
+
"mail-api.topolo.app"
|
|
986
|
+
],
|
|
984
987
|
"routes": [],
|
|
985
|
-
"serviceId":
|
|
988
|
+
"serviceId": "mail"
|
|
986
989
|
}
|
|
987
990
|
]
|
|
988
991
|
},
|
|
@@ -2099,14 +2102,60 @@ function statusRank(status) {
|
|
|
2099
2102
|
|
|
2100
2103
|
// src/services.ts
|
|
2101
2104
|
var DEFAULT_SERVICE_URLS = SERVICES;
|
|
2105
|
+
function resolveCatalogServiceUrl(entry, options = {}) {
|
|
2106
|
+
const keys = catalogServiceKeyCandidates(entry, options.serviceKey);
|
|
2107
|
+
const override = resolveServiceUrlOverride(keys, options.overrides);
|
|
2108
|
+
if (override) return override;
|
|
2109
|
+
const apiBaseUrl = firstString(entry.apiBaseUrl, entry.api_base_url);
|
|
2110
|
+
if (apiBaseUrl) return apiBaseUrl;
|
|
2111
|
+
return null;
|
|
2112
|
+
}
|
|
2102
2113
|
function resolveServiceUrl(service, overrides) {
|
|
2103
|
-
const override =
|
|
2114
|
+
const override = resolveServiceUrlOverride([service], overrides);
|
|
2104
2115
|
if (override) return override;
|
|
2105
|
-
const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, "_").toUpperCase()}`;
|
|
2106
|
-
const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
|
|
2107
|
-
if (envValue) return envValue;
|
|
2108
2116
|
return DEFAULT_SERVICE_URLS[service];
|
|
2109
2117
|
}
|
|
2118
|
+
function catalogServiceKeyCandidates(entry, preferred) {
|
|
2119
|
+
const values = [preferred, entry.slug, entry.name, entry.serviceId].filter(
|
|
2120
|
+
(value) => typeof value === "string" && value.trim().length > 0
|
|
2121
|
+
);
|
|
2122
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2123
|
+
for (const value of values) {
|
|
2124
|
+
for (const candidate of serviceKeyCandidates(value)) keys.add(candidate);
|
|
2125
|
+
}
|
|
2126
|
+
return [...keys];
|
|
2127
|
+
}
|
|
2128
|
+
function serviceKeyCandidates(value) {
|
|
2129
|
+
const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
2130
|
+
const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
|
|
2131
|
+
return [.../* @__PURE__ */ new Set([
|
|
2132
|
+
normalized,
|
|
2133
|
+
normalized.replace(/-/g, "_"),
|
|
2134
|
+
withoutTopolo,
|
|
2135
|
+
withoutTopolo.replace(/-/g, "_")
|
|
2136
|
+
])].filter(Boolean);
|
|
2137
|
+
}
|
|
2138
|
+
function resolveServiceUrlOverride(keys, overrides) {
|
|
2139
|
+
for (const key of keys) {
|
|
2140
|
+
const direct = overrides?.[key];
|
|
2141
|
+
if (direct) return direct;
|
|
2142
|
+
const envStyleKey = key.replace(/\./g, "_");
|
|
2143
|
+
const envStyle = overrides?.[envStyleKey];
|
|
2144
|
+
if (envStyle) return envStyle;
|
|
2145
|
+
}
|
|
2146
|
+
for (const key of keys) {
|
|
2147
|
+
const envKey = `TOPOLO_SERVICE_URL_${key.replace(/\./g, "_").toUpperCase()}`;
|
|
2148
|
+
const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
|
|
2149
|
+
if (envValue) return envValue;
|
|
2150
|
+
}
|
|
2151
|
+
return null;
|
|
2152
|
+
}
|
|
2153
|
+
function firstString(...values) {
|
|
2154
|
+
for (const value of values) {
|
|
2155
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
2156
|
+
}
|
|
2157
|
+
return null;
|
|
2158
|
+
}
|
|
2110
2159
|
|
|
2111
2160
|
// src/client.ts
|
|
2112
2161
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
@@ -2279,8 +2328,12 @@ var TopoloClient = class _TopoloClient {
|
|
|
2279
2328
|
async resolveLiveCallableService(serviceKey) {
|
|
2280
2329
|
const catalog = await this.listServices();
|
|
2281
2330
|
const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
|
|
2282
|
-
if (!match
|
|
2283
|
-
|
|
2331
|
+
if (!match) return null;
|
|
2332
|
+
const baseUrl = resolveCatalogServiceUrl(match, {
|
|
2333
|
+
serviceKey,
|
|
2334
|
+
overrides: this.serviceUrls
|
|
2335
|
+
});
|
|
2336
|
+
return baseUrl ? { serviceKey, baseUrl } : null;
|
|
2284
2337
|
}
|
|
2285
2338
|
/**
|
|
2286
2339
|
* Resolve a service-slug → opaque service-id via caller config or TopoloAuth.
|
|
@@ -2373,7 +2426,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2373
2426
|
slug: typeof s.app_slug === "string" ? s.app_slug : null,
|
|
2374
2427
|
name: typeof s.name === "string" ? s.name : null,
|
|
2375
2428
|
description: typeof s.description === "string" ? s.description : null,
|
|
2376
|
-
|
|
2429
|
+
apiBaseUrl: typeof s.api_base_url === "string" ? s.api_base_url : typeof s.apiBaseUrl === "string" ? s.apiBaseUrl : null,
|
|
2430
|
+
launchUrl: typeof s.launch_url === "string" ? s.launch_url : typeof s.launchUrl === "string" ? s.launchUrl : null,
|
|
2377
2431
|
status: typeof s.status === "string" ? s.status : null,
|
|
2378
2432
|
surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
|
|
2379
2433
|
launchable: s.launchable === true,
|
|
@@ -2458,7 +2512,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2458
2512
|
slug: app.id,
|
|
2459
2513
|
name: app.name ?? app.id,
|
|
2460
2514
|
description: null,
|
|
2461
|
-
|
|
2515
|
+
apiBaseUrl: null,
|
|
2516
|
+
launchUrl: app.productionUrl ?? null,
|
|
2462
2517
|
status: "bundled",
|
|
2463
2518
|
surfaceKind: "application",
|
|
2464
2519
|
launchable: Boolean(app.productionUrl),
|
|
@@ -2502,7 +2557,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2502
2557
|
}
|
|
2503
2558
|
};
|
|
2504
2559
|
function resolveConfiguredServiceId(slug, serviceIds) {
|
|
2505
|
-
for (const serviceKey of
|
|
2560
|
+
for (const serviceKey of serviceKeyCandidates2(slug)) {
|
|
2506
2561
|
const configured = serviceIds?.[serviceKey];
|
|
2507
2562
|
if (typeof configured === "string" && configured.trim()) return configured.trim();
|
|
2508
2563
|
const envKey = `TOPOLO_SERVICE_ID_${serviceKey.replace(/\./g, "_").toUpperCase()}`;
|
|
@@ -2512,7 +2567,7 @@ function resolveConfiguredServiceId(slug, serviceIds) {
|
|
|
2512
2567
|
return null;
|
|
2513
2568
|
}
|
|
2514
2569
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
2515
|
-
for (const serviceKey of
|
|
2570
|
+
for (const serviceKey of serviceKeyCandidates2(slug)) {
|
|
2516
2571
|
const configured = serviceUrls?.[serviceKey];
|
|
2517
2572
|
if (typeof configured === "string" && configured.trim()) return configured.trim();
|
|
2518
2573
|
const envKey = `TOPOLO_SERVICE_URL_${serviceKey.replace(/\./g, "_").toUpperCase()}`;
|
|
@@ -2521,7 +2576,7 @@ function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
|
2521
2576
|
}
|
|
2522
2577
|
return null;
|
|
2523
2578
|
}
|
|
2524
|
-
function
|
|
2579
|
+
function serviceKeyCandidates2(slug) {
|
|
2525
2580
|
const normalized = normalizeLookupKey(slug);
|
|
2526
2581
|
const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
|
|
2527
2582
|
return [.../* @__PURE__ */ new Set([
|
|
@@ -2543,19 +2598,12 @@ function catalogEntryLookupKeys(entry) {
|
|
|
2543
2598
|
for (const value of values) {
|
|
2544
2599
|
const normalized = normalizeLookupKey(value);
|
|
2545
2600
|
if (!normalized) continue;
|
|
2546
|
-
for (const candidate of
|
|
2601
|
+
for (const candidate of serviceKeyCandidates2(normalized)) {
|
|
2547
2602
|
keys.add(candidate);
|
|
2548
2603
|
}
|
|
2549
2604
|
}
|
|
2550
2605
|
return [...keys];
|
|
2551
2606
|
}
|
|
2552
|
-
function isCallableCatalogEntry(entry) {
|
|
2553
|
-
const kind = normalizeLookupKey(entry.surfaceKind || "");
|
|
2554
|
-
if (kind === "api" || kind === "technical_service" || kind === "service") return true;
|
|
2555
|
-
return Boolean(
|
|
2556
|
-
entry.baseUrl && catalogEntryLookupKeys(entry).some((key) => Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, key))
|
|
2557
|
-
);
|
|
2558
|
-
}
|
|
2559
2607
|
function normalizeActionCatalogEntry(item) {
|
|
2560
2608
|
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
2561
2609
|
const a = item;
|
|
@@ -3234,6 +3282,7 @@ function createTopolo(options) {
|
|
|
3234
3282
|
defineTopoloRecentActivityWidget,
|
|
3235
3283
|
defineTopoloStatsWidget,
|
|
3236
3284
|
requirementsForApplication,
|
|
3285
|
+
resolveCatalogServiceUrl,
|
|
3237
3286
|
resolveServiceUrl,
|
|
3238
3287
|
validateTopoloWidgetResponse
|
|
3239
3288
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -104,7 +104,8 @@ interface TopoloServiceCatalogEntry {
|
|
|
104
104
|
slug: string | null;
|
|
105
105
|
name: string | null;
|
|
106
106
|
description: string | null;
|
|
107
|
-
|
|
107
|
+
apiBaseUrl?: string | null;
|
|
108
|
+
launchUrl?: string | null;
|
|
108
109
|
status: string | null;
|
|
109
110
|
surfaceKind: string | null;
|
|
110
111
|
launchable: boolean;
|
|
@@ -257,7 +258,7 @@ declare const SERVICES: {
|
|
|
257
258
|
readonly inventory: "https://inventory.topolo.app";
|
|
258
259
|
readonly learn: "https://learn-api.topolo.app";
|
|
259
260
|
readonly localize: "https://localize.topolo.app";
|
|
260
|
-
readonly mail: "https://mail.topolo.app";
|
|
261
|
+
readonly mail: "https://mail-api.topolo.app";
|
|
261
262
|
readonly mdm: "https://mdm-api.topolo.app";
|
|
262
263
|
readonly messages: "https://messages.topolo.app";
|
|
263
264
|
readonly nexus: "https://nexus-api.topolo.app";
|
|
@@ -889,14 +890,14 @@ declare const APPLICATIONS: {
|
|
|
889
890
|
readonly apiUrl: "https://mail.topolo.app";
|
|
890
891
|
readonly domains: readonly ["mail.topolo.app"];
|
|
891
892
|
readonly routes: readonly [];
|
|
892
|
-
readonly serviceId:
|
|
893
|
+
readonly serviceId: null;
|
|
893
894
|
}, {
|
|
894
895
|
readonly name: "topolo-mail-api";
|
|
895
896
|
readonly kind: "worker";
|
|
896
|
-
readonly apiUrl:
|
|
897
|
-
readonly domains: readonly [];
|
|
897
|
+
readonly apiUrl: "https://mail-api.topolo.app";
|
|
898
|
+
readonly domains: readonly ["mail-api.topolo.app"];
|
|
898
899
|
readonly routes: readonly [];
|
|
899
|
-
readonly serviceId:
|
|
900
|
+
readonly serviceId: "mail";
|
|
900
901
|
}];
|
|
901
902
|
};
|
|
902
903
|
readonly mdm: {
|
|
@@ -1579,6 +1580,19 @@ declare function auditAllApplicationRequirements(applicationIds?: readonly Appli
|
|
|
1579
1580
|
*/
|
|
1580
1581
|
|
|
1581
1582
|
declare const DEFAULT_SERVICE_URLS: Record<ServiceId, string>;
|
|
1583
|
+
interface ServiceCatalogUrlEntry {
|
|
1584
|
+
serviceId?: string | null;
|
|
1585
|
+
slug?: string | null;
|
|
1586
|
+
name?: string | null;
|
|
1587
|
+
apiBaseUrl?: string | null;
|
|
1588
|
+
launchUrl?: string | null;
|
|
1589
|
+
api_base_url?: string | null;
|
|
1590
|
+
launch_url?: string | null;
|
|
1591
|
+
}
|
|
1592
|
+
declare function resolveCatalogServiceUrl(entry: ServiceCatalogUrlEntry, options?: {
|
|
1593
|
+
serviceKey?: string;
|
|
1594
|
+
overrides?: Record<string, string> | Partial<Record<ServiceId, string>>;
|
|
1595
|
+
}): string | null;
|
|
1582
1596
|
declare function resolveServiceUrl(service: ServiceId, overrides?: Partial<Record<ServiceId, string>>): string;
|
|
1583
1597
|
|
|
1584
1598
|
declare class TopoloSdkError extends Error {
|
|
@@ -2085,4 +2099,4 @@ declare function createTopolo(options: TopoloClientOptions): {
|
|
|
2085
2099
|
};
|
|
2086
2100
|
type Topolo = ReturnType<typeof createTopolo>;
|
|
2087
2101
|
|
|
2088
|
-
export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceId, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, type TopoloActionCatalogEntry, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveServiceUrl, validateTopoloWidgetResponse };
|
|
2102
|
+
export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceCatalogUrlEntry, type ServiceId, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, type TopoloActionCatalogEntry, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveCatalogServiceUrl, resolveServiceUrl, validateTopoloWidgetResponse };
|
package/dist/index.d.ts
CHANGED
|
@@ -104,7 +104,8 @@ interface TopoloServiceCatalogEntry {
|
|
|
104
104
|
slug: string | null;
|
|
105
105
|
name: string | null;
|
|
106
106
|
description: string | null;
|
|
107
|
-
|
|
107
|
+
apiBaseUrl?: string | null;
|
|
108
|
+
launchUrl?: string | null;
|
|
108
109
|
status: string | null;
|
|
109
110
|
surfaceKind: string | null;
|
|
110
111
|
launchable: boolean;
|
|
@@ -257,7 +258,7 @@ declare const SERVICES: {
|
|
|
257
258
|
readonly inventory: "https://inventory.topolo.app";
|
|
258
259
|
readonly learn: "https://learn-api.topolo.app";
|
|
259
260
|
readonly localize: "https://localize.topolo.app";
|
|
260
|
-
readonly mail: "https://mail.topolo.app";
|
|
261
|
+
readonly mail: "https://mail-api.topolo.app";
|
|
261
262
|
readonly mdm: "https://mdm-api.topolo.app";
|
|
262
263
|
readonly messages: "https://messages.topolo.app";
|
|
263
264
|
readonly nexus: "https://nexus-api.topolo.app";
|
|
@@ -889,14 +890,14 @@ declare const APPLICATIONS: {
|
|
|
889
890
|
readonly apiUrl: "https://mail.topolo.app";
|
|
890
891
|
readonly domains: readonly ["mail.topolo.app"];
|
|
891
892
|
readonly routes: readonly [];
|
|
892
|
-
readonly serviceId:
|
|
893
|
+
readonly serviceId: null;
|
|
893
894
|
}, {
|
|
894
895
|
readonly name: "topolo-mail-api";
|
|
895
896
|
readonly kind: "worker";
|
|
896
|
-
readonly apiUrl:
|
|
897
|
-
readonly domains: readonly [];
|
|
897
|
+
readonly apiUrl: "https://mail-api.topolo.app";
|
|
898
|
+
readonly domains: readonly ["mail-api.topolo.app"];
|
|
898
899
|
readonly routes: readonly [];
|
|
899
|
-
readonly serviceId:
|
|
900
|
+
readonly serviceId: "mail";
|
|
900
901
|
}];
|
|
901
902
|
};
|
|
902
903
|
readonly mdm: {
|
|
@@ -1579,6 +1580,19 @@ declare function auditAllApplicationRequirements(applicationIds?: readonly Appli
|
|
|
1579
1580
|
*/
|
|
1580
1581
|
|
|
1581
1582
|
declare const DEFAULT_SERVICE_URLS: Record<ServiceId, string>;
|
|
1583
|
+
interface ServiceCatalogUrlEntry {
|
|
1584
|
+
serviceId?: string | null;
|
|
1585
|
+
slug?: string | null;
|
|
1586
|
+
name?: string | null;
|
|
1587
|
+
apiBaseUrl?: string | null;
|
|
1588
|
+
launchUrl?: string | null;
|
|
1589
|
+
api_base_url?: string | null;
|
|
1590
|
+
launch_url?: string | null;
|
|
1591
|
+
}
|
|
1592
|
+
declare function resolveCatalogServiceUrl(entry: ServiceCatalogUrlEntry, options?: {
|
|
1593
|
+
serviceKey?: string;
|
|
1594
|
+
overrides?: Record<string, string> | Partial<Record<ServiceId, string>>;
|
|
1595
|
+
}): string | null;
|
|
1582
1596
|
declare function resolveServiceUrl(service: ServiceId, overrides?: Partial<Record<ServiceId, string>>): string;
|
|
1583
1597
|
|
|
1584
1598
|
declare class TopoloSdkError extends Error {
|
|
@@ -2085,4 +2099,4 @@ declare function createTopolo(options: TopoloClientOptions): {
|
|
|
2085
2099
|
};
|
|
2086
2100
|
type Topolo = ReturnType<typeof createTopolo>;
|
|
2087
2101
|
|
|
2088
|
-
export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceId, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, type TopoloActionCatalogEntry, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveServiceUrl, validateTopoloWidgetResponse };
|
|
2102
|
+
export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_SERVICE_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceCatalogUrlEntry, type ServiceId, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, type TopoloActionCatalogEntry, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveCatalogServiceUrl, resolveServiceUrl, validateTopoloWidgetResponse };
|
package/dist/index.js
CHANGED
|
@@ -82,7 +82,7 @@ var SERVICES = {
|
|
|
82
82
|
inventory: "https://inventory.topolo.app",
|
|
83
83
|
learn: "https://learn-api.topolo.app",
|
|
84
84
|
localize: "https://localize.topolo.app",
|
|
85
|
-
mail: "https://mail.topolo.app",
|
|
85
|
+
mail: "https://mail-api.topolo.app",
|
|
86
86
|
mdm: "https://mdm-api.topolo.app",
|
|
87
87
|
messages: "https://messages.topolo.app",
|
|
88
88
|
nexus: "https://nexus-api.topolo.app",
|
|
@@ -920,15 +920,17 @@ var APPLICATIONS = {
|
|
|
920
920
|
"mail.topolo.app"
|
|
921
921
|
],
|
|
922
922
|
"routes": [],
|
|
923
|
-
"serviceId":
|
|
923
|
+
"serviceId": null
|
|
924
924
|
},
|
|
925
925
|
{
|
|
926
926
|
"name": "topolo-mail-api",
|
|
927
927
|
"kind": "worker",
|
|
928
|
-
"apiUrl":
|
|
929
|
-
"domains": [
|
|
928
|
+
"apiUrl": "https://mail-api.topolo.app",
|
|
929
|
+
"domains": [
|
|
930
|
+
"mail-api.topolo.app"
|
|
931
|
+
],
|
|
930
932
|
"routes": [],
|
|
931
|
-
"serviceId":
|
|
933
|
+
"serviceId": "mail"
|
|
932
934
|
}
|
|
933
935
|
]
|
|
934
936
|
},
|
|
@@ -2045,14 +2047,60 @@ function statusRank(status) {
|
|
|
2045
2047
|
|
|
2046
2048
|
// src/services.ts
|
|
2047
2049
|
var DEFAULT_SERVICE_URLS = SERVICES;
|
|
2050
|
+
function resolveCatalogServiceUrl(entry, options = {}) {
|
|
2051
|
+
const keys = catalogServiceKeyCandidates(entry, options.serviceKey);
|
|
2052
|
+
const override = resolveServiceUrlOverride(keys, options.overrides);
|
|
2053
|
+
if (override) return override;
|
|
2054
|
+
const apiBaseUrl = firstString(entry.apiBaseUrl, entry.api_base_url);
|
|
2055
|
+
if (apiBaseUrl) return apiBaseUrl;
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2048
2058
|
function resolveServiceUrl(service, overrides) {
|
|
2049
|
-
const override =
|
|
2059
|
+
const override = resolveServiceUrlOverride([service], overrides);
|
|
2050
2060
|
if (override) return override;
|
|
2051
|
-
const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, "_").toUpperCase()}`;
|
|
2052
|
-
const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
|
|
2053
|
-
if (envValue) return envValue;
|
|
2054
2061
|
return DEFAULT_SERVICE_URLS[service];
|
|
2055
2062
|
}
|
|
2063
|
+
function catalogServiceKeyCandidates(entry, preferred) {
|
|
2064
|
+
const values = [preferred, entry.slug, entry.name, entry.serviceId].filter(
|
|
2065
|
+
(value) => typeof value === "string" && value.trim().length > 0
|
|
2066
|
+
);
|
|
2067
|
+
const keys = /* @__PURE__ */ new Set();
|
|
2068
|
+
for (const value of values) {
|
|
2069
|
+
for (const candidate of serviceKeyCandidates(value)) keys.add(candidate);
|
|
2070
|
+
}
|
|
2071
|
+
return [...keys];
|
|
2072
|
+
}
|
|
2073
|
+
function serviceKeyCandidates(value) {
|
|
2074
|
+
const normalized = value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
2075
|
+
const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
|
|
2076
|
+
return [.../* @__PURE__ */ new Set([
|
|
2077
|
+
normalized,
|
|
2078
|
+
normalized.replace(/-/g, "_"),
|
|
2079
|
+
withoutTopolo,
|
|
2080
|
+
withoutTopolo.replace(/-/g, "_")
|
|
2081
|
+
])].filter(Boolean);
|
|
2082
|
+
}
|
|
2083
|
+
function resolveServiceUrlOverride(keys, overrides) {
|
|
2084
|
+
for (const key of keys) {
|
|
2085
|
+
const direct = overrides?.[key];
|
|
2086
|
+
if (direct) return direct;
|
|
2087
|
+
const envStyleKey = key.replace(/\./g, "_");
|
|
2088
|
+
const envStyle = overrides?.[envStyleKey];
|
|
2089
|
+
if (envStyle) return envStyle;
|
|
2090
|
+
}
|
|
2091
|
+
for (const key of keys) {
|
|
2092
|
+
const envKey = `TOPOLO_SERVICE_URL_${key.replace(/\./g, "_").toUpperCase()}`;
|
|
2093
|
+
const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
|
|
2094
|
+
if (envValue) return envValue;
|
|
2095
|
+
}
|
|
2096
|
+
return null;
|
|
2097
|
+
}
|
|
2098
|
+
function firstString(...values) {
|
|
2099
|
+
for (const value of values) {
|
|
2100
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
2101
|
+
}
|
|
2102
|
+
return null;
|
|
2103
|
+
}
|
|
2056
2104
|
|
|
2057
2105
|
// src/client.ts
|
|
2058
2106
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
@@ -2225,8 +2273,12 @@ var TopoloClient = class _TopoloClient {
|
|
|
2225
2273
|
async resolveLiveCallableService(serviceKey) {
|
|
2226
2274
|
const catalog = await this.listServices();
|
|
2227
2275
|
const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
|
|
2228
|
-
if (!match
|
|
2229
|
-
|
|
2276
|
+
if (!match) return null;
|
|
2277
|
+
const baseUrl = resolveCatalogServiceUrl(match, {
|
|
2278
|
+
serviceKey,
|
|
2279
|
+
overrides: this.serviceUrls
|
|
2280
|
+
});
|
|
2281
|
+
return baseUrl ? { serviceKey, baseUrl } : null;
|
|
2230
2282
|
}
|
|
2231
2283
|
/**
|
|
2232
2284
|
* Resolve a service-slug → opaque service-id via caller config or TopoloAuth.
|
|
@@ -2319,7 +2371,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2319
2371
|
slug: typeof s.app_slug === "string" ? s.app_slug : null,
|
|
2320
2372
|
name: typeof s.name === "string" ? s.name : null,
|
|
2321
2373
|
description: typeof s.description === "string" ? s.description : null,
|
|
2322
|
-
|
|
2374
|
+
apiBaseUrl: typeof s.api_base_url === "string" ? s.api_base_url : typeof s.apiBaseUrl === "string" ? s.apiBaseUrl : null,
|
|
2375
|
+
launchUrl: typeof s.launch_url === "string" ? s.launch_url : typeof s.launchUrl === "string" ? s.launchUrl : null,
|
|
2323
2376
|
status: typeof s.status === "string" ? s.status : null,
|
|
2324
2377
|
surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
|
|
2325
2378
|
launchable: s.launchable === true,
|
|
@@ -2404,7 +2457,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2404
2457
|
slug: app.id,
|
|
2405
2458
|
name: app.name ?? app.id,
|
|
2406
2459
|
description: null,
|
|
2407
|
-
|
|
2460
|
+
apiBaseUrl: null,
|
|
2461
|
+
launchUrl: app.productionUrl ?? null,
|
|
2408
2462
|
status: "bundled",
|
|
2409
2463
|
surfaceKind: "application",
|
|
2410
2464
|
launchable: Boolean(app.productionUrl),
|
|
@@ -2448,7 +2502,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2448
2502
|
}
|
|
2449
2503
|
};
|
|
2450
2504
|
function resolveConfiguredServiceId(slug, serviceIds) {
|
|
2451
|
-
for (const serviceKey of
|
|
2505
|
+
for (const serviceKey of serviceKeyCandidates2(slug)) {
|
|
2452
2506
|
const configured = serviceIds?.[serviceKey];
|
|
2453
2507
|
if (typeof configured === "string" && configured.trim()) return configured.trim();
|
|
2454
2508
|
const envKey = `TOPOLO_SERVICE_ID_${serviceKey.replace(/\./g, "_").toUpperCase()}`;
|
|
@@ -2458,7 +2512,7 @@ function resolveConfiguredServiceId(slug, serviceIds) {
|
|
|
2458
2512
|
return null;
|
|
2459
2513
|
}
|
|
2460
2514
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
2461
|
-
for (const serviceKey of
|
|
2515
|
+
for (const serviceKey of serviceKeyCandidates2(slug)) {
|
|
2462
2516
|
const configured = serviceUrls?.[serviceKey];
|
|
2463
2517
|
if (typeof configured === "string" && configured.trim()) return configured.trim();
|
|
2464
2518
|
const envKey = `TOPOLO_SERVICE_URL_${serviceKey.replace(/\./g, "_").toUpperCase()}`;
|
|
@@ -2467,7 +2521,7 @@ function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
|
2467
2521
|
}
|
|
2468
2522
|
return null;
|
|
2469
2523
|
}
|
|
2470
|
-
function
|
|
2524
|
+
function serviceKeyCandidates2(slug) {
|
|
2471
2525
|
const normalized = normalizeLookupKey(slug);
|
|
2472
2526
|
const withoutTopolo = normalized.startsWith("topolo-") ? normalized.slice("topolo-".length) : normalized;
|
|
2473
2527
|
return [.../* @__PURE__ */ new Set([
|
|
@@ -2489,19 +2543,12 @@ function catalogEntryLookupKeys(entry) {
|
|
|
2489
2543
|
for (const value of values) {
|
|
2490
2544
|
const normalized = normalizeLookupKey(value);
|
|
2491
2545
|
if (!normalized) continue;
|
|
2492
|
-
for (const candidate of
|
|
2546
|
+
for (const candidate of serviceKeyCandidates2(normalized)) {
|
|
2493
2547
|
keys.add(candidate);
|
|
2494
2548
|
}
|
|
2495
2549
|
}
|
|
2496
2550
|
return [...keys];
|
|
2497
2551
|
}
|
|
2498
|
-
function isCallableCatalogEntry(entry) {
|
|
2499
|
-
const kind = normalizeLookupKey(entry.surfaceKind || "");
|
|
2500
|
-
if (kind === "api" || kind === "technical_service" || kind === "service") return true;
|
|
2501
|
-
return Boolean(
|
|
2502
|
-
entry.baseUrl && catalogEntryLookupKeys(entry).some((key) => Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, key))
|
|
2503
|
-
);
|
|
2504
|
-
}
|
|
2505
2552
|
function normalizeActionCatalogEntry(item) {
|
|
2506
2553
|
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
2507
2554
|
const a = item;
|
|
@@ -3179,6 +3226,7 @@ export {
|
|
|
3179
3226
|
defineTopoloRecentActivityWidget,
|
|
3180
3227
|
defineTopoloStatsWidget,
|
|
3181
3228
|
requirementsForApplication,
|
|
3229
|
+
resolveCatalogServiceUrl,
|
|
3182
3230
|
resolveServiceUrl,
|
|
3183
3231
|
validateTopoloWidgetResponse
|
|
3184
3232
|
};
|
package/package.json
CHANGED
|
@@ -813,15 +813,17 @@ export const APPLICATIONS = {
|
|
|
813
813
|
"mail.topolo.app"
|
|
814
814
|
],
|
|
815
815
|
"routes": [],
|
|
816
|
-
"serviceId":
|
|
816
|
+
"serviceId": null
|
|
817
817
|
},
|
|
818
818
|
{
|
|
819
819
|
"name": "topolo-mail-api",
|
|
820
820
|
"kind": "worker",
|
|
821
|
-
"apiUrl":
|
|
822
|
-
"domains": [
|
|
821
|
+
"apiUrl": "https://mail-api.topolo.app",
|
|
822
|
+
"domains": [
|
|
823
|
+
"mail-api.topolo.app"
|
|
824
|
+
],
|
|
823
825
|
"routes": [],
|
|
824
|
-
"serviceId":
|
|
826
|
+
"serviceId": "mail"
|
|
825
827
|
}
|
|
826
828
|
]
|
|
827
829
|
},
|
package/src/client.test.ts
CHANGED
|
@@ -25,7 +25,8 @@ const MAIL_CATALOG = {
|
|
|
25
25
|
service_id: 'srv_mail',
|
|
26
26
|
app_slug: 'topolo-mail',
|
|
27
27
|
name: 'Topolo Mail',
|
|
28
|
-
|
|
28
|
+
api_base_url: 'https://mail-api.topolo.app',
|
|
29
|
+
launch_url: 'https://mail.topolo.app',
|
|
29
30
|
surface_kind: 'api',
|
|
30
31
|
status: 'active',
|
|
31
32
|
granted_permissions: ['srv_mail.messages:read'],
|
|
@@ -376,7 +377,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
|
|
|
376
377
|
|
|
377
378
|
it('falls back to /api/services/by-slug/:slug for unknown slugs', async () => {
|
|
378
379
|
const { impl, calls } = stubFetch([
|
|
379
|
-
{ body: { success: true, data: { service_id: 'service_abc123',
|
|
380
|
+
{ body: { success: true, data: { service_id: 'service_abc123', api_base_url: 'https://x-api' } } },
|
|
380
381
|
]);
|
|
381
382
|
const client = freshClient(impl, { auth: 'https://auth.example.com' });
|
|
382
383
|
const id = await client.getServiceIdBySlug('external-service');
|
|
@@ -452,7 +453,8 @@ describe('TopoloClient.listServices', () => {
|
|
|
452
453
|
app_slug: 'topolo-director',
|
|
453
454
|
name: 'Topolo Director',
|
|
454
455
|
description: 'Demo control plane',
|
|
455
|
-
|
|
456
|
+
api_base_url: 'https://director-api.topolo.app',
|
|
457
|
+
launch_url: 'https://director.topolo.app',
|
|
456
458
|
status: 'active',
|
|
457
459
|
surface_kind: 'application',
|
|
458
460
|
launchable: true,
|
|
@@ -476,7 +478,8 @@ describe('TopoloClient.listServices', () => {
|
|
|
476
478
|
slug: 'topolo-director',
|
|
477
479
|
name: 'Topolo Director',
|
|
478
480
|
description: 'Demo control plane',
|
|
479
|
-
|
|
481
|
+
apiBaseUrl: 'https://director-api.topolo.app',
|
|
482
|
+
launchUrl: 'https://director.topolo.app',
|
|
480
483
|
status: 'active',
|
|
481
484
|
surfaceKind: 'application',
|
|
482
485
|
launchable: true,
|
|
@@ -512,7 +515,8 @@ describe('TopoloClient.listServices', () => {
|
|
|
512
515
|
service_id: 'srv_mail',
|
|
513
516
|
app_slug: 'topolo-mail',
|
|
514
517
|
name: 'Topolo Mail',
|
|
515
|
-
|
|
518
|
+
api_base_url: 'https://mail-api.topolo.app',
|
|
519
|
+
launch_url: 'https://mail.topolo.app',
|
|
516
520
|
surface_kind: 'application',
|
|
517
521
|
status: 'active',
|
|
518
522
|
granted_permissions: ['srv_mail.messages:read'],
|
|
@@ -533,7 +537,76 @@ describe('TopoloClient.listServices', () => {
|
|
|
533
537
|
await client.request({ service: 'mail', path: '/api/health' });
|
|
534
538
|
|
|
535
539
|
expect(calls[0]!.url).toBe('https://auth.example.com/api/services/catalog');
|
|
536
|
-
expect(calls[1]!.url).toBe('https://mail.topolo.app/api/health');
|
|
540
|
+
expect(calls[1]!.url).toBe('https://mail-api.topolo.app/api/health');
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('routes known split-surface services to the callable API URL', async () => {
|
|
544
|
+
const { impl, calls } = stubFetch([
|
|
545
|
+
{
|
|
546
|
+
body: {
|
|
547
|
+
data: {
|
|
548
|
+
services: [
|
|
549
|
+
{
|
|
550
|
+
service_id: 'srv_crm',
|
|
551
|
+
app_slug: 'topolo-crm',
|
|
552
|
+
name: 'Topolo CRM',
|
|
553
|
+
api_base_url: 'https://crm-api.topolo.app',
|
|
554
|
+
launch_url: 'https://crm.topolo.app',
|
|
555
|
+
surface_kind: 'application',
|
|
556
|
+
status: 'active',
|
|
557
|
+
granted_permissions: ['srv_crm.contacts:read'],
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
{ body: { ok: true } },
|
|
564
|
+
]);
|
|
565
|
+
const client = new TopoloClient({
|
|
566
|
+
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
567
|
+
agent: AGENT,
|
|
568
|
+
fetch: impl,
|
|
569
|
+
serviceUrls: { auth: 'https://auth.example.com' },
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
await client.request({ service: 'crm', path: '/health' });
|
|
573
|
+
|
|
574
|
+
expect(calls[0]!.url).toBe('https://auth.example.com/api/services/catalog');
|
|
575
|
+
expect(calls[1]!.url).toBe('https://crm-api.topolo.app/health');
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('prefers live catalog api_base_url for dynamic services', async () => {
|
|
579
|
+
const { impl, calls } = stubFetch([
|
|
580
|
+
{
|
|
581
|
+
body: {
|
|
582
|
+
data: {
|
|
583
|
+
services: [
|
|
584
|
+
{
|
|
585
|
+
service_id: 'srv_future',
|
|
586
|
+
app_slug: 'topolo-future',
|
|
587
|
+
name: 'Future',
|
|
588
|
+
api_base_url: 'https://future-api.topolo.app',
|
|
589
|
+
launch_url: 'https://future.topolo.app',
|
|
590
|
+
surface_kind: 'api',
|
|
591
|
+
status: 'active',
|
|
592
|
+
granted_permissions: ['srv_future.read:all'],
|
|
593
|
+
},
|
|
594
|
+
],
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
{ body: { ok: true } },
|
|
599
|
+
]);
|
|
600
|
+
const client = new TopoloClient({
|
|
601
|
+
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
602
|
+
agent: AGENT,
|
|
603
|
+
fetch: impl,
|
|
604
|
+
serviceUrls: { auth: 'https://auth.example.com' },
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
await client.request({ service: 'future', path: '/health' });
|
|
608
|
+
|
|
609
|
+
expect(calls[1]!.url).toBe('https://future-api.topolo.app/health');
|
|
537
610
|
});
|
|
538
611
|
|
|
539
612
|
it('refuses a bundled static service that is absent from the scoped live catalog', async () => {
|
|
@@ -546,7 +619,8 @@ describe('TopoloClient.listServices', () => {
|
|
|
546
619
|
service_id: 'srv_crm',
|
|
547
620
|
app_slug: 'topolo-crm',
|
|
548
621
|
name: 'Topolo CRM',
|
|
549
|
-
|
|
622
|
+
api_base_url: 'https://crm-api.topolo.app',
|
|
623
|
+
launch_url: 'https://crm.topolo.app',
|
|
550
624
|
surface_kind: 'api',
|
|
551
625
|
status: 'active',
|
|
552
626
|
granted_permissions: ['srv_crm.contacts:read'],
|
|
@@ -579,7 +653,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
579
653
|
service_id: 'srv_future_app',
|
|
580
654
|
app_slug: 'topolo-future-app',
|
|
581
655
|
name: 'Future App',
|
|
582
|
-
|
|
656
|
+
launch_url: 'https://future-app.topolo.app',
|
|
583
657
|
surface_kind: 'application',
|
|
584
658
|
status: 'active',
|
|
585
659
|
granted_permissions: ['srv_future_app.launch:read'],
|
|
@@ -612,7 +686,8 @@ describe('TopoloClient.listServices', () => {
|
|
|
612
686
|
service_id: 'srv_future',
|
|
613
687
|
app_slug: 'topolo-warehouse',
|
|
614
688
|
name: 'Warehouse',
|
|
615
|
-
|
|
689
|
+
api_base_url: 'https://warehouse.topolo.app',
|
|
690
|
+
launch_url: 'https://warehouse.topolo.app',
|
|
616
691
|
surface_kind: 'api',
|
|
617
692
|
status: 'active',
|
|
618
693
|
granted_permissions: ['srv_future.read:all'],
|
package/src/client.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { TopoloAuthError, TopoloHttpError, TopoloPermissionError, TopoloSdkError
|
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_SERVICE_URLS,
|
|
11
11
|
APPLICATIONS,
|
|
12
|
+
resolveCatalogServiceUrl,
|
|
12
13
|
resolveServiceUrl,
|
|
13
14
|
type ServiceId,
|
|
14
15
|
} from './services.js';
|
|
@@ -105,7 +106,8 @@ export interface TopoloServiceCatalogEntry {
|
|
|
105
106
|
slug: string | null;
|
|
106
107
|
name: string | null;
|
|
107
108
|
description: string | null;
|
|
108
|
-
|
|
109
|
+
apiBaseUrl?: string | null;
|
|
110
|
+
launchUrl?: string | null;
|
|
109
111
|
status: string | null;
|
|
110
112
|
surfaceKind: string | null;
|
|
111
113
|
launchable: boolean;
|
|
@@ -325,8 +327,12 @@ export class TopoloClient {
|
|
|
325
327
|
private async resolveLiveCallableService(serviceKey: string): Promise<{ serviceKey: string; baseUrl: string } | null> {
|
|
326
328
|
const catalog = await this.listServices();
|
|
327
329
|
const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
|
|
328
|
-
if (!match
|
|
329
|
-
|
|
330
|
+
if (!match) return null;
|
|
331
|
+
const baseUrl = resolveCatalogServiceUrl(match, {
|
|
332
|
+
serviceKey,
|
|
333
|
+
overrides: this.serviceUrls,
|
|
334
|
+
});
|
|
335
|
+
return baseUrl ? { serviceKey, baseUrl } : null;
|
|
330
336
|
}
|
|
331
337
|
|
|
332
338
|
/**
|
|
@@ -447,7 +453,18 @@ export class TopoloClient {
|
|
|
447
453
|
name: typeof s.name === 'string' ? s.name : null,
|
|
448
454
|
description:
|
|
449
455
|
typeof s.description === 'string' ? s.description : null,
|
|
450
|
-
|
|
456
|
+
apiBaseUrl:
|
|
457
|
+
typeof s.api_base_url === 'string'
|
|
458
|
+
? s.api_base_url
|
|
459
|
+
: typeof s.apiBaseUrl === 'string'
|
|
460
|
+
? s.apiBaseUrl
|
|
461
|
+
: null,
|
|
462
|
+
launchUrl:
|
|
463
|
+
typeof s.launch_url === 'string'
|
|
464
|
+
? s.launch_url
|
|
465
|
+
: typeof s.launchUrl === 'string'
|
|
466
|
+
? s.launchUrl
|
|
467
|
+
: null,
|
|
451
468
|
status: typeof s.status === 'string' ? s.status : null,
|
|
452
469
|
surfaceKind:
|
|
453
470
|
typeof s.surface_kind === 'string' ? s.surface_kind : null,
|
|
@@ -554,7 +571,8 @@ export class TopoloClient {
|
|
|
554
571
|
slug: app.id,
|
|
555
572
|
name: app.name ?? app.id,
|
|
556
573
|
description: null,
|
|
557
|
-
|
|
574
|
+
apiBaseUrl: null,
|
|
575
|
+
launchUrl: app.productionUrl ?? null,
|
|
558
576
|
status: 'bundled',
|
|
559
577
|
surfaceKind: 'application',
|
|
560
578
|
launchable: Boolean(app.productionUrl),
|
|
@@ -674,15 +692,6 @@ function catalogEntryLookupKeys(entry: TopoloServiceCatalogEntry): string[] {
|
|
|
674
692
|
return [...keys];
|
|
675
693
|
}
|
|
676
694
|
|
|
677
|
-
function isCallableCatalogEntry(entry: TopoloServiceCatalogEntry): boolean {
|
|
678
|
-
const kind = normalizeLookupKey(entry.surfaceKind || '');
|
|
679
|
-
if (kind === 'api' || kind === 'technical_service' || kind === 'service') return true;
|
|
680
|
-
return Boolean(
|
|
681
|
-
entry.baseUrl &&
|
|
682
|
-
catalogEntryLookupKeys(entry).some((key) => Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, key)),
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
695
|
function normalizeActionCatalogEntry(item: unknown): TopoloActionCatalogEntry | null {
|
|
687
696
|
if (!item || typeof item !== 'object' || Array.isArray(item)) return null;
|
|
688
697
|
const a = item as Record<string, unknown>;
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export {
|
|
|
17
17
|
auditApplicationRequirements,
|
|
18
18
|
applicationRequirementScopes,
|
|
19
19
|
requirementsForApplication,
|
|
20
|
+
resolveCatalogServiceUrl,
|
|
20
21
|
resolveServiceUrl,
|
|
21
22
|
type ApplicationCatalogEntry,
|
|
22
23
|
type ApplicationDeployTarget,
|
|
@@ -29,6 +30,7 @@ export {
|
|
|
29
30
|
type ApplicationRequirementScope,
|
|
30
31
|
type ApplicationRequirementStatus,
|
|
31
32
|
type ApplicationRequirementsAuditReport,
|
|
33
|
+
type ServiceCatalogUrlEntry,
|
|
32
34
|
type ServiceId,
|
|
33
35
|
} from "./services.js";
|
|
34
36
|
export {
|
package/src/modules/p2p.test.ts
CHANGED
|
@@ -22,7 +22,8 @@ function stubFetch(responses: Array<{ status?: number; body?: unknown }>) {
|
|
|
22
22
|
service_id: 'srv_p2p',
|
|
23
23
|
app_slug: 'topolo-p2p',
|
|
24
24
|
name: 'Topolo P2P',
|
|
25
|
-
|
|
25
|
+
api_base_url: 'https://p2p.topolo.app',
|
|
26
|
+
launch_url: 'https://p2p.topolo.app',
|
|
26
27
|
surface_kind: 'api',
|
|
27
28
|
status: 'active',
|
|
28
29
|
granted_permissions: ['srv_p2p.directory:read'],
|
|
@@ -27,7 +27,7 @@ export const SERVICES = {
|
|
|
27
27
|
inventory: "https://inventory.topolo.app",
|
|
28
28
|
learn: "https://learn-api.topolo.app",
|
|
29
29
|
localize: "https://localize.topolo.app",
|
|
30
|
-
mail: "https://mail.topolo.app",
|
|
30
|
+
mail: "https://mail-api.topolo.app",
|
|
31
31
|
mdm: "https://mdm-api.topolo.app",
|
|
32
32
|
messages: "https://messages.topolo.app",
|
|
33
33
|
nexus: "https://nexus-api.topolo.app",
|
package/src/services.test.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
auditAllApplicationRequirements,
|
|
8
8
|
auditApplicationRequirements,
|
|
9
9
|
applicationRequirementScopes,
|
|
10
|
+
resolveCatalogServiceUrl,
|
|
10
11
|
requirementsForApplication,
|
|
11
12
|
} from './services.js';
|
|
12
13
|
|
|
@@ -69,7 +70,7 @@ describe('service registry', () => {
|
|
|
69
70
|
books: 'https://books.topolo.app',
|
|
70
71
|
consent: 'https://consent.topolo.app',
|
|
71
72
|
feed: 'https://feed-api.topolo.app',
|
|
72
|
-
mail: 'https://mail.topolo.app',
|
|
73
|
+
mail: 'https://mail-api.topolo.app',
|
|
73
74
|
one: 'https://one.topolo.app',
|
|
74
75
|
web: 'https://web-api.topolo.app',
|
|
75
76
|
});
|
|
@@ -97,6 +98,35 @@ describe('service registry', () => {
|
|
|
97
98
|
expect(applicationServices.has(serviceId)).toBe(true);
|
|
98
99
|
}
|
|
99
100
|
});
|
|
101
|
+
|
|
102
|
+
it('resolves services from explicit catalog API URLs', () => {
|
|
103
|
+
expect(resolveCatalogServiceUrl({
|
|
104
|
+
serviceId: 'srv_crm',
|
|
105
|
+
slug: 'topolo-crm',
|
|
106
|
+
name: 'Topolo CRM',
|
|
107
|
+
launchUrl: 'https://crm.topolo.app',
|
|
108
|
+
apiBaseUrl: 'https://crm-api.topolo.app',
|
|
109
|
+
}, { serviceKey: 'crm' })).toBe('https://crm-api.topolo.app');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('does not infer callable URLs from launcher URLs', () => {
|
|
113
|
+
expect(resolveCatalogServiceUrl({
|
|
114
|
+
serviceId: 'srv_crm',
|
|
115
|
+
slug: 'topolo-crm',
|
|
116
|
+
name: 'Topolo CRM',
|
|
117
|
+
launchUrl: 'https://crm.stg.topolo.us',
|
|
118
|
+
}, { serviceKey: 'crm' })).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('prefers explicit catalog API URLs over launch URLs', () => {
|
|
122
|
+
expect(resolveCatalogServiceUrl({
|
|
123
|
+
serviceId: 'srv_future',
|
|
124
|
+
slug: 'topolo-future',
|
|
125
|
+
name: 'Future',
|
|
126
|
+
launchUrl: 'https://future.topolo.app',
|
|
127
|
+
apiBaseUrl: 'https://future-api.topolo.app',
|
|
128
|
+
})).toBe('https://future-api.topolo.app');
|
|
129
|
+
});
|
|
100
130
|
});
|
|
101
131
|
|
|
102
132
|
describe('application catalog', () => {
|
package/src/services.ts
CHANGED
|
@@ -47,16 +47,92 @@ export type { ApplicationCatalogEntry, ApplicationDeployTarget, ApplicationId };
|
|
|
47
47
|
|
|
48
48
|
export const DEFAULT_SERVICE_URLS: Record<ServiceId, string> = SERVICES;
|
|
49
49
|
|
|
50
|
+
export interface ServiceCatalogUrlEntry {
|
|
51
|
+
serviceId?: string | null;
|
|
52
|
+
slug?: string | null;
|
|
53
|
+
name?: string | null;
|
|
54
|
+
apiBaseUrl?: string | null;
|
|
55
|
+
launchUrl?: string | null;
|
|
56
|
+
api_base_url?: string | null;
|
|
57
|
+
launch_url?: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resolveCatalogServiceUrl(
|
|
61
|
+
entry: ServiceCatalogUrlEntry,
|
|
62
|
+
options: {
|
|
63
|
+
serviceKey?: string;
|
|
64
|
+
overrides?: Record<string, string> | Partial<Record<ServiceId, string>>;
|
|
65
|
+
} = {},
|
|
66
|
+
): string | null {
|
|
67
|
+
const keys = catalogServiceKeyCandidates(entry, options.serviceKey);
|
|
68
|
+
const override = resolveServiceUrlOverride(keys, options.overrides);
|
|
69
|
+
if (override) return override;
|
|
70
|
+
|
|
71
|
+
const apiBaseUrl = firstString(entry.apiBaseUrl, entry.api_base_url);
|
|
72
|
+
if (apiBaseUrl) return apiBaseUrl;
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
50
77
|
export function resolveServiceUrl(
|
|
51
78
|
service: ServiceId,
|
|
52
79
|
overrides?: Partial<Record<ServiceId, string>>,
|
|
53
80
|
): string {
|
|
54
|
-
const override =
|
|
81
|
+
const override = resolveServiceUrlOverride([service], overrides);
|
|
55
82
|
if (override) return override;
|
|
56
83
|
|
|
57
|
-
const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, '_').toUpperCase()}`;
|
|
58
|
-
const envValue = typeof process !== 'undefined' ? process.env?.[envKey] : undefined;
|
|
59
|
-
if (envValue) return envValue;
|
|
60
|
-
|
|
61
84
|
return DEFAULT_SERVICE_URLS[service];
|
|
62
85
|
}
|
|
86
|
+
|
|
87
|
+
function catalogServiceKeyCandidates(entry: ServiceCatalogUrlEntry, preferred?: string): string[] {
|
|
88
|
+
const values = [preferred, entry.slug, entry.name, entry.serviceId].filter(
|
|
89
|
+
(value): value is string => typeof value === 'string' && value.trim().length > 0,
|
|
90
|
+
);
|
|
91
|
+
const keys = new Set<string>();
|
|
92
|
+
for (const value of values) {
|
|
93
|
+
for (const candidate of serviceKeyCandidates(value)) keys.add(candidate);
|
|
94
|
+
}
|
|
95
|
+
return [...keys];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function serviceKeyCandidates(value: string): string[] {
|
|
99
|
+
const normalized = value.trim().toLowerCase().replace(/\s+/g, '-');
|
|
100
|
+
const withoutTopolo = normalized.startsWith('topolo-')
|
|
101
|
+
? normalized.slice('topolo-'.length)
|
|
102
|
+
: normalized;
|
|
103
|
+
return [...new Set([
|
|
104
|
+
normalized,
|
|
105
|
+
normalized.replace(/-/g, '_'),
|
|
106
|
+
withoutTopolo,
|
|
107
|
+
withoutTopolo.replace(/-/g, '_'),
|
|
108
|
+
])].filter(Boolean);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function resolveServiceUrlOverride(
|
|
112
|
+
keys: string[],
|
|
113
|
+
overrides?: Record<string, string> | Partial<Record<ServiceId, string>>,
|
|
114
|
+
): string | null {
|
|
115
|
+
for (const key of keys) {
|
|
116
|
+
const direct = overrides?.[key as ServiceId];
|
|
117
|
+
if (direct) return direct;
|
|
118
|
+
|
|
119
|
+
const envStyleKey = key.replace(/\./g, '_');
|
|
120
|
+
const envStyle = overrides?.[envStyleKey as ServiceId];
|
|
121
|
+
if (envStyle) return envStyle;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const key of keys) {
|
|
125
|
+
const envKey = `TOPOLO_SERVICE_URL_${key.replace(/\./g, '_').toUpperCase()}`;
|
|
126
|
+
const envValue = typeof process !== 'undefined' ? process.env?.[envKey] : undefined;
|
|
127
|
+
if (envValue) return envValue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function firstString(...values: Array<string | null | undefined>): string | null {
|
|
134
|
+
for (const value of values) {
|
|
135
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|