@topolo/sdk 0.4.8 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +30 -14
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +30 -14
- package/package.json +1 -1
- package/src/applications.generated.ts +10 -6
- package/src/client.test.ts +34 -19
- package/src/client.ts +36 -4
- package/src/services.generated.ts +1 -1
package/dist/index.cjs
CHANGED
|
@@ -140,7 +140,7 @@ var SERVICES = {
|
|
|
140
140
|
mail: "https://mail.topolo.app",
|
|
141
141
|
mdm: "https://mdm.topolo.app",
|
|
142
142
|
messages: "https://messages.topolo.app",
|
|
143
|
-
nexus: "https://nexus
|
|
143
|
+
nexus: "https://nexus.topolo.app",
|
|
144
144
|
notify: "https://notify.topolo.app",
|
|
145
145
|
observability_watch: "https://observability-watch.topolo.app",
|
|
146
146
|
one: "https://www.topolo.app",
|
|
@@ -691,8 +691,10 @@ var APPLICATIONS = {
|
|
|
691
691
|
{
|
|
692
692
|
"name": "topolo-feed-api",
|
|
693
693
|
"kind": "worker",
|
|
694
|
-
"apiUrl":
|
|
695
|
-
"domains": [
|
|
694
|
+
"apiUrl": "https://feed-api.topolo.app",
|
|
695
|
+
"domains": [
|
|
696
|
+
"feed-api.topolo.app"
|
|
697
|
+
],
|
|
696
698
|
"routes": [],
|
|
697
699
|
"serviceId": null
|
|
698
700
|
},
|
|
@@ -1063,9 +1065,9 @@ var APPLICATIONS = {
|
|
|
1063
1065
|
{
|
|
1064
1066
|
"name": "topolo-nexus-gateway",
|
|
1065
1067
|
"kind": "worker",
|
|
1066
|
-
"apiUrl": "https://nexus
|
|
1068
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
1067
1069
|
"domains": [
|
|
1068
|
-
"nexus
|
|
1070
|
+
"nexus.topolo.app"
|
|
1069
1071
|
],
|
|
1070
1072
|
"routes": [],
|
|
1071
1073
|
"serviceId": "nexus"
|
|
@@ -1425,8 +1427,10 @@ var APPLICATIONS = {
|
|
|
1425
1427
|
{
|
|
1426
1428
|
"name": "topolo-socialize-api",
|
|
1427
1429
|
"kind": "worker",
|
|
1428
|
-
"apiUrl":
|
|
1429
|
-
"domains": [
|
|
1430
|
+
"apiUrl": "https://socialize-api.topolo.app",
|
|
1431
|
+
"domains": [
|
|
1432
|
+
"socialize-api.topolo.app"
|
|
1433
|
+
],
|
|
1430
1434
|
"routes": [],
|
|
1431
1435
|
"serviceId": null
|
|
1432
1436
|
},
|
|
@@ -2276,10 +2280,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
2276
2280
|
if (!serviceKey) {
|
|
2277
2281
|
throw new TopoloSdkError("unknown_service", "service is required");
|
|
2278
2282
|
}
|
|
2279
|
-
if (serviceKey === "auth") {
|
|
2283
|
+
if (serviceKey === "auth" || serviceKey === "developers") {
|
|
2280
2284
|
const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
|
|
2281
2285
|
if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
|
|
2282
|
-
|
|
2286
|
+
if (serviceKey === "developers") {
|
|
2287
|
+
const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl("auth", this.serviceUrls));
|
|
2288
|
+
if (derived) return { serviceKey, baseUrl: derived };
|
|
2289
|
+
}
|
|
2290
|
+
return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey] };
|
|
2283
2291
|
}
|
|
2284
2292
|
const live = await this.resolveLiveCallableService(serviceKey);
|
|
2285
2293
|
if (live) {
|
|
@@ -2319,7 +2327,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2319
2327
|
if (!normalized) return null;
|
|
2320
2328
|
const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
|
|
2321
2329
|
if (configuredServiceId) return configuredServiceId;
|
|
2322
|
-
const authBase = resolveServiceUrl("
|
|
2330
|
+
const authBase = resolveServiceUrl("developers", this.serviceUrls);
|
|
2323
2331
|
const cacheKey = `${authBase}|${normalized}`;
|
|
2324
2332
|
const now = Date.now();
|
|
2325
2333
|
const cached = _TopoloClient.serviceIdCache.get(cacheKey);
|
|
@@ -2373,7 +2381,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2373
2381
|
if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
|
|
2374
2382
|
return this.serviceCatalogCache.entries;
|
|
2375
2383
|
}
|
|
2376
|
-
const res = await this.request({ service: "
|
|
2384
|
+
const res = await this.request({ service: "developers", method: "GET", path: "/api/services/catalog" });
|
|
2377
2385
|
const raw = res.data?.services ?? res.services;
|
|
2378
2386
|
if (!Array.isArray(raw)) {
|
|
2379
2387
|
throw new TopoloSdkError(
|
|
@@ -2604,6 +2612,17 @@ function serviceKeyCandidates2(slug) {
|
|
|
2604
2612
|
function normalizeLookupKey(value) {
|
|
2605
2613
|
return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
2606
2614
|
}
|
|
2615
|
+
function deriveDevelopersFromAuth(authUrl) {
|
|
2616
|
+
if (!authUrl) return null;
|
|
2617
|
+
try {
|
|
2618
|
+
const url = new URL(authUrl);
|
|
2619
|
+
if (!/^auth\./i.test(url.hostname)) return null;
|
|
2620
|
+
url.hostname = url.hostname.replace(/^auth\./i, "developers.");
|
|
2621
|
+
return url.origin;
|
|
2622
|
+
} catch {
|
|
2623
|
+
return null;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2607
2626
|
function catalogEntryMatches(entry, needle) {
|
|
2608
2627
|
return catalogEntryLookupKeys(entry).includes(needle);
|
|
2609
2628
|
}
|
|
@@ -2751,7 +2770,6 @@ var CrmModule = class {
|
|
|
2751
2770
|
constructor(client) {
|
|
2752
2771
|
this.client = client;
|
|
2753
2772
|
}
|
|
2754
|
-
client;
|
|
2755
2773
|
async listContacts(options = {}) {
|
|
2756
2774
|
const res = await this.client.request({
|
|
2757
2775
|
service: "crm",
|
|
@@ -2799,7 +2817,6 @@ var IdentityModule = class {
|
|
|
2799
2817
|
constructor(client) {
|
|
2800
2818
|
this.client = client;
|
|
2801
2819
|
}
|
|
2802
|
-
client;
|
|
2803
2820
|
/** Returns the resolved user, organization, and permission set for the
|
|
2804
2821
|
* current credential. Equivalent to `TopoloClient.introspect()` — exposed
|
|
2805
2822
|
* on this module for symmetry with the other domain modules. */
|
|
@@ -2813,7 +2830,6 @@ var P2PModule = class {
|
|
|
2813
2830
|
constructor(client) {
|
|
2814
2831
|
this.client = client;
|
|
2815
2832
|
}
|
|
2816
|
-
client;
|
|
2817
2833
|
// Directory ----------------------------------------------------------------
|
|
2818
2834
|
async listDirectory(options = {}) {
|
|
2819
2835
|
const res = await this.client.request({
|
package/dist/index.d.cts
CHANGED
|
@@ -277,7 +277,7 @@ declare const SERVICES: {
|
|
|
277
277
|
readonly mail: "https://mail.topolo.app";
|
|
278
278
|
readonly mdm: "https://mdm.topolo.app";
|
|
279
279
|
readonly messages: "https://messages.topolo.app";
|
|
280
|
-
readonly nexus: "https://nexus
|
|
280
|
+
readonly nexus: "https://nexus.topolo.app";
|
|
281
281
|
readonly notify: "https://notify.topolo.app";
|
|
282
282
|
readonly observability_watch: "https://observability-watch.topolo.app";
|
|
283
283
|
readonly one: "https://www.topolo.app";
|
|
@@ -702,8 +702,8 @@ declare const APPLICATIONS: {
|
|
|
702
702
|
}, {
|
|
703
703
|
readonly name: "topolo-feed-api";
|
|
704
704
|
readonly kind: "worker";
|
|
705
|
-
readonly apiUrl:
|
|
706
|
-
readonly domains: readonly [];
|
|
705
|
+
readonly apiUrl: "https://feed-api.topolo.app";
|
|
706
|
+
readonly domains: readonly ["feed-api.topolo.app"];
|
|
707
707
|
readonly routes: readonly [];
|
|
708
708
|
readonly serviceId: null;
|
|
709
709
|
}, {
|
|
@@ -985,8 +985,8 @@ declare const APPLICATIONS: {
|
|
|
985
985
|
}, {
|
|
986
986
|
readonly name: "topolo-nexus-gateway";
|
|
987
987
|
readonly kind: "worker";
|
|
988
|
-
readonly apiUrl: "https://nexus
|
|
989
|
-
readonly domains: readonly ["nexus
|
|
988
|
+
readonly apiUrl: "https://nexus.topolo.app";
|
|
989
|
+
readonly domains: readonly ["nexus.topolo.app"];
|
|
990
990
|
readonly routes: readonly [];
|
|
991
991
|
readonly serviceId: "nexus";
|
|
992
992
|
}];
|
|
@@ -1245,8 +1245,8 @@ declare const APPLICATIONS: {
|
|
|
1245
1245
|
}, {
|
|
1246
1246
|
readonly name: "topolo-socialize-api";
|
|
1247
1247
|
readonly kind: "worker";
|
|
1248
|
-
readonly apiUrl:
|
|
1249
|
-
readonly domains: readonly [];
|
|
1248
|
+
readonly apiUrl: "https://socialize-api.topolo.app";
|
|
1249
|
+
readonly domains: readonly ["socialize-api.topolo.app"];
|
|
1250
1250
|
readonly routes: readonly [];
|
|
1251
1251
|
readonly serviceId: null;
|
|
1252
1252
|
}, {
|
package/dist/index.d.ts
CHANGED
|
@@ -277,7 +277,7 @@ declare const SERVICES: {
|
|
|
277
277
|
readonly mail: "https://mail.topolo.app";
|
|
278
278
|
readonly mdm: "https://mdm.topolo.app";
|
|
279
279
|
readonly messages: "https://messages.topolo.app";
|
|
280
|
-
readonly nexus: "https://nexus
|
|
280
|
+
readonly nexus: "https://nexus.topolo.app";
|
|
281
281
|
readonly notify: "https://notify.topolo.app";
|
|
282
282
|
readonly observability_watch: "https://observability-watch.topolo.app";
|
|
283
283
|
readonly one: "https://www.topolo.app";
|
|
@@ -702,8 +702,8 @@ declare const APPLICATIONS: {
|
|
|
702
702
|
}, {
|
|
703
703
|
readonly name: "topolo-feed-api";
|
|
704
704
|
readonly kind: "worker";
|
|
705
|
-
readonly apiUrl:
|
|
706
|
-
readonly domains: readonly [];
|
|
705
|
+
readonly apiUrl: "https://feed-api.topolo.app";
|
|
706
|
+
readonly domains: readonly ["feed-api.topolo.app"];
|
|
707
707
|
readonly routes: readonly [];
|
|
708
708
|
readonly serviceId: null;
|
|
709
709
|
}, {
|
|
@@ -985,8 +985,8 @@ declare const APPLICATIONS: {
|
|
|
985
985
|
}, {
|
|
986
986
|
readonly name: "topolo-nexus-gateway";
|
|
987
987
|
readonly kind: "worker";
|
|
988
|
-
readonly apiUrl: "https://nexus
|
|
989
|
-
readonly domains: readonly ["nexus
|
|
988
|
+
readonly apiUrl: "https://nexus.topolo.app";
|
|
989
|
+
readonly domains: readonly ["nexus.topolo.app"];
|
|
990
990
|
readonly routes: readonly [];
|
|
991
991
|
readonly serviceId: "nexus";
|
|
992
992
|
}];
|
|
@@ -1245,8 +1245,8 @@ declare const APPLICATIONS: {
|
|
|
1245
1245
|
}, {
|
|
1246
1246
|
readonly name: "topolo-socialize-api";
|
|
1247
1247
|
readonly kind: "worker";
|
|
1248
|
-
readonly apiUrl:
|
|
1249
|
-
readonly domains: readonly [];
|
|
1248
|
+
readonly apiUrl: "https://socialize-api.topolo.app";
|
|
1249
|
+
readonly domains: readonly ["socialize-api.topolo.app"];
|
|
1250
1250
|
readonly routes: readonly [];
|
|
1251
1251
|
readonly serviceId: null;
|
|
1252
1252
|
}, {
|
package/dist/index.js
CHANGED
|
@@ -85,7 +85,7 @@ var SERVICES = {
|
|
|
85
85
|
mail: "https://mail.topolo.app",
|
|
86
86
|
mdm: "https://mdm.topolo.app",
|
|
87
87
|
messages: "https://messages.topolo.app",
|
|
88
|
-
nexus: "https://nexus
|
|
88
|
+
nexus: "https://nexus.topolo.app",
|
|
89
89
|
notify: "https://notify.topolo.app",
|
|
90
90
|
observability_watch: "https://observability-watch.topolo.app",
|
|
91
91
|
one: "https://www.topolo.app",
|
|
@@ -636,8 +636,10 @@ var APPLICATIONS = {
|
|
|
636
636
|
{
|
|
637
637
|
"name": "topolo-feed-api",
|
|
638
638
|
"kind": "worker",
|
|
639
|
-
"apiUrl":
|
|
640
|
-
"domains": [
|
|
639
|
+
"apiUrl": "https://feed-api.topolo.app",
|
|
640
|
+
"domains": [
|
|
641
|
+
"feed-api.topolo.app"
|
|
642
|
+
],
|
|
641
643
|
"routes": [],
|
|
642
644
|
"serviceId": null
|
|
643
645
|
},
|
|
@@ -1008,9 +1010,9 @@ var APPLICATIONS = {
|
|
|
1008
1010
|
{
|
|
1009
1011
|
"name": "topolo-nexus-gateway",
|
|
1010
1012
|
"kind": "worker",
|
|
1011
|
-
"apiUrl": "https://nexus
|
|
1013
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
1012
1014
|
"domains": [
|
|
1013
|
-
"nexus
|
|
1015
|
+
"nexus.topolo.app"
|
|
1014
1016
|
],
|
|
1015
1017
|
"routes": [],
|
|
1016
1018
|
"serviceId": "nexus"
|
|
@@ -1370,8 +1372,10 @@ var APPLICATIONS = {
|
|
|
1370
1372
|
{
|
|
1371
1373
|
"name": "topolo-socialize-api",
|
|
1372
1374
|
"kind": "worker",
|
|
1373
|
-
"apiUrl":
|
|
1374
|
-
"domains": [
|
|
1375
|
+
"apiUrl": "https://socialize-api.topolo.app",
|
|
1376
|
+
"domains": [
|
|
1377
|
+
"socialize-api.topolo.app"
|
|
1378
|
+
],
|
|
1375
1379
|
"routes": [],
|
|
1376
1380
|
"serviceId": null
|
|
1377
1381
|
},
|
|
@@ -2221,10 +2225,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
2221
2225
|
if (!serviceKey) {
|
|
2222
2226
|
throw new TopoloSdkError("unknown_service", "service is required");
|
|
2223
2227
|
}
|
|
2224
|
-
if (serviceKey === "auth") {
|
|
2228
|
+
if (serviceKey === "auth" || serviceKey === "developers") {
|
|
2225
2229
|
const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
|
|
2226
2230
|
if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
|
|
2227
|
-
|
|
2231
|
+
if (serviceKey === "developers") {
|
|
2232
|
+
const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl("auth", this.serviceUrls));
|
|
2233
|
+
if (derived) return { serviceKey, baseUrl: derived };
|
|
2234
|
+
}
|
|
2235
|
+
return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey] };
|
|
2228
2236
|
}
|
|
2229
2237
|
const live = await this.resolveLiveCallableService(serviceKey);
|
|
2230
2238
|
if (live) {
|
|
@@ -2264,7 +2272,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2264
2272
|
if (!normalized) return null;
|
|
2265
2273
|
const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
|
|
2266
2274
|
if (configuredServiceId) return configuredServiceId;
|
|
2267
|
-
const authBase = resolveServiceUrl("
|
|
2275
|
+
const authBase = resolveServiceUrl("developers", this.serviceUrls);
|
|
2268
2276
|
const cacheKey = `${authBase}|${normalized}`;
|
|
2269
2277
|
const now = Date.now();
|
|
2270
2278
|
const cached = _TopoloClient.serviceIdCache.get(cacheKey);
|
|
@@ -2318,7 +2326,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2318
2326
|
if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
|
|
2319
2327
|
return this.serviceCatalogCache.entries;
|
|
2320
2328
|
}
|
|
2321
|
-
const res = await this.request({ service: "
|
|
2329
|
+
const res = await this.request({ service: "developers", method: "GET", path: "/api/services/catalog" });
|
|
2322
2330
|
const raw = res.data?.services ?? res.services;
|
|
2323
2331
|
if (!Array.isArray(raw)) {
|
|
2324
2332
|
throw new TopoloSdkError(
|
|
@@ -2549,6 +2557,17 @@ function serviceKeyCandidates2(slug) {
|
|
|
2549
2557
|
function normalizeLookupKey(value) {
|
|
2550
2558
|
return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
2551
2559
|
}
|
|
2560
|
+
function deriveDevelopersFromAuth(authUrl) {
|
|
2561
|
+
if (!authUrl) return null;
|
|
2562
|
+
try {
|
|
2563
|
+
const url = new URL(authUrl);
|
|
2564
|
+
if (!/^auth\./i.test(url.hostname)) return null;
|
|
2565
|
+
url.hostname = url.hostname.replace(/^auth\./i, "developers.");
|
|
2566
|
+
return url.origin;
|
|
2567
|
+
} catch {
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2552
2571
|
function catalogEntryMatches(entry, needle) {
|
|
2553
2572
|
return catalogEntryLookupKeys(entry).includes(needle);
|
|
2554
2573
|
}
|
|
@@ -2696,7 +2715,6 @@ var CrmModule = class {
|
|
|
2696
2715
|
constructor(client) {
|
|
2697
2716
|
this.client = client;
|
|
2698
2717
|
}
|
|
2699
|
-
client;
|
|
2700
2718
|
async listContacts(options = {}) {
|
|
2701
2719
|
const res = await this.client.request({
|
|
2702
2720
|
service: "crm",
|
|
@@ -2744,7 +2762,6 @@ var IdentityModule = class {
|
|
|
2744
2762
|
constructor(client) {
|
|
2745
2763
|
this.client = client;
|
|
2746
2764
|
}
|
|
2747
|
-
client;
|
|
2748
2765
|
/** Returns the resolved user, organization, and permission set for the
|
|
2749
2766
|
* current credential. Equivalent to `TopoloClient.introspect()` — exposed
|
|
2750
2767
|
* on this module for symmetry with the other domain modules. */
|
|
@@ -2758,7 +2775,6 @@ var P2PModule = class {
|
|
|
2758
2775
|
constructor(client) {
|
|
2759
2776
|
this.client = client;
|
|
2760
2777
|
}
|
|
2761
|
-
client;
|
|
2762
2778
|
// Directory ----------------------------------------------------------------
|
|
2763
2779
|
async listDirectory(options = {}) {
|
|
2764
2780
|
const res = await this.client.request({
|
package/package.json
CHANGED
|
@@ -530,8 +530,10 @@ export const APPLICATIONS = {
|
|
|
530
530
|
{
|
|
531
531
|
"name": "topolo-feed-api",
|
|
532
532
|
"kind": "worker",
|
|
533
|
-
"apiUrl":
|
|
534
|
-
"domains": [
|
|
533
|
+
"apiUrl": "https://feed-api.topolo.app",
|
|
534
|
+
"domains": [
|
|
535
|
+
"feed-api.topolo.app"
|
|
536
|
+
],
|
|
535
537
|
"routes": [],
|
|
536
538
|
"serviceId": null
|
|
537
539
|
},
|
|
@@ -902,9 +904,9 @@ export const APPLICATIONS = {
|
|
|
902
904
|
{
|
|
903
905
|
"name": "topolo-nexus-gateway",
|
|
904
906
|
"kind": "worker",
|
|
905
|
-
"apiUrl": "https://nexus
|
|
907
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
906
908
|
"domains": [
|
|
907
|
-
"nexus
|
|
909
|
+
"nexus.topolo.app"
|
|
908
910
|
],
|
|
909
911
|
"routes": [],
|
|
910
912
|
"serviceId": "nexus"
|
|
@@ -1264,8 +1266,10 @@ export const APPLICATIONS = {
|
|
|
1264
1266
|
{
|
|
1265
1267
|
"name": "topolo-socialize-api",
|
|
1266
1268
|
"kind": "worker",
|
|
1267
|
-
"apiUrl":
|
|
1268
|
-
"domains": [
|
|
1269
|
+
"apiUrl": "https://socialize-api.topolo.app",
|
|
1270
|
+
"domains": [
|
|
1271
|
+
"socialize-api.topolo.app"
|
|
1272
|
+
],
|
|
1269
1273
|
"routes": [],
|
|
1270
1274
|
"serviceId": null
|
|
1271
1275
|
},
|
package/src/client.test.ts
CHANGED
|
@@ -401,7 +401,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
|
|
|
401
401
|
credential: { kind: 'api_key', apiKey: 'x' },
|
|
402
402
|
agent: AGENT,
|
|
403
403
|
fetch: impl,
|
|
404
|
-
serviceUrls: { auth: 'https://auth.example.com' } as never,
|
|
404
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' } as never,
|
|
405
405
|
serviceIds: { notify: 'service_notify' },
|
|
406
406
|
});
|
|
407
407
|
const id = await client.getServiceIdBySlug('topolo-notify');
|
|
@@ -413,7 +413,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
|
|
|
413
413
|
const { impl, calls } = stubFetch([
|
|
414
414
|
{ body: { success: true, data: { service_id: 'service_abc123', api_base_url: 'https://x-api' } } },
|
|
415
415
|
]);
|
|
416
|
-
const client = freshClient(impl, { auth: 'https://auth.example.com' });
|
|
416
|
+
const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
|
|
417
417
|
const id = await client.getServiceIdBySlug('external-service');
|
|
418
418
|
expect(id).toBe('service_abc123');
|
|
419
419
|
expect(calls[0]!.url).toBe('https://auth.example.com/api/services/by-slug/external-service');
|
|
@@ -425,7 +425,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
|
|
|
425
425
|
|
|
426
426
|
it('returns null on 404 and caches the negative result', async () => {
|
|
427
427
|
const { impl, calls } = stubFetch([{ status: 404, body: { success: false } }]);
|
|
428
|
-
const client = freshClient(impl, { auth: 'https://auth.example.com' });
|
|
428
|
+
const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
|
|
429
429
|
expect(await client.getServiceIdBySlug('nope')).toBeNull();
|
|
430
430
|
expect(await client.getServiceIdBySlug('nope')).toBeNull();
|
|
431
431
|
expect(calls.length).toBe(1);
|
|
@@ -435,7 +435,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
|
|
|
435
435
|
const { impl, calls } = stubFetch([
|
|
436
436
|
{ body: { success: true, data: { service_id: 'service_external' } } },
|
|
437
437
|
]);
|
|
438
|
-
const client = freshClient(impl, { auth: 'https://auth.example.com' });
|
|
438
|
+
const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
|
|
439
439
|
expect(await client.getServiceIdBySlug('external-service')).toBe('service_external');
|
|
440
440
|
expect(await client.getServiceIdBySlug('external-service')).toBe('service_external');
|
|
441
441
|
expect(calls.length).toBe(1);
|
|
@@ -445,7 +445,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
|
|
|
445
445
|
const { impl, calls } = stubFetch([
|
|
446
446
|
{ body: { success: true, data: { service_id: 'service_x' } } },
|
|
447
447
|
]);
|
|
448
|
-
const client = freshClient(impl, { auth: 'https://auth.example.com' });
|
|
448
|
+
const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
|
|
449
449
|
await client.getServiceIdBySlug(' EXTERNAL-SERVICE ');
|
|
450
450
|
expect(calls[0]!.url).toBe('https://auth.example.com/api/services/by-slug/external-service');
|
|
451
451
|
});
|
|
@@ -467,7 +467,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
|
|
|
467
467
|
credential: { kind: 'api_key', apiKey: 'x' },
|
|
468
468
|
agent: AGENT,
|
|
469
469
|
fetch: impl,
|
|
470
|
-
serviceUrls: { auth: 'https://auth.example.com' } as never,
|
|
470
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' } as never,
|
|
471
471
|
});
|
|
472
472
|
expect(await client.getServiceIdBySlug('flaky')).toBeNull();
|
|
473
473
|
// Second call retries — earlier null was not cached.
|
|
@@ -523,7 +523,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
523
523
|
credential: { kind: 'access_token', accessToken: 'tok_abc' },
|
|
524
524
|
agent: AGENT,
|
|
525
525
|
fetch: impl,
|
|
526
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
526
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
527
527
|
});
|
|
528
528
|
const out = await client.listServices();
|
|
529
529
|
expect(out).toEqual([
|
|
@@ -574,7 +574,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
574
574
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
575
575
|
agent: AGENT,
|
|
576
576
|
fetch: impl,
|
|
577
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
577
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
578
578
|
});
|
|
579
579
|
await expect(client.listServices()).rejects.toBeInstanceOf(TopoloHttpError);
|
|
580
580
|
});
|
|
@@ -605,7 +605,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
605
605
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
606
606
|
agent: AGENT,
|
|
607
607
|
fetch: impl,
|
|
608
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
608
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
609
609
|
});
|
|
610
610
|
|
|
611
611
|
await client.request({ service: 'mail', path: '/api/health' });
|
|
@@ -640,7 +640,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
640
640
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
641
641
|
agent: AGENT,
|
|
642
642
|
fetch: impl,
|
|
643
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
643
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
644
644
|
});
|
|
645
645
|
|
|
646
646
|
await client.request({ service: 'crm', path: '/health' });
|
|
@@ -649,6 +649,21 @@ describe('TopoloClient.listServices', () => {
|
|
|
649
649
|
expect(calls[1]!.url).toBe('https://crm-api.topolo.app/health');
|
|
650
650
|
});
|
|
651
651
|
|
|
652
|
+
it('derives the catalog (developers) host from the auth url when developers is unset', async () => {
|
|
653
|
+
const { impl, calls } = stubFetch([{ body: { data: { services: [] } } }]);
|
|
654
|
+
const client = new TopoloClient({
|
|
655
|
+
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
656
|
+
agent: AGENT,
|
|
657
|
+
fetch: impl,
|
|
658
|
+
// Fleet apps configure only `auth` per-env; the catalog host is derived.
|
|
659
|
+
serviceUrls: { auth: 'https://auth.stg.topolo.us' },
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
await client.listServices();
|
|
663
|
+
|
|
664
|
+
expect(calls[0]!.url).toBe('https://developers.stg.topolo.us/api/services/catalog');
|
|
665
|
+
});
|
|
666
|
+
|
|
652
667
|
it('prefers live catalog api_base_url for dynamic services', async () => {
|
|
653
668
|
const { impl, calls } = stubFetch([
|
|
654
669
|
{
|
|
@@ -675,7 +690,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
675
690
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
676
691
|
agent: AGENT,
|
|
677
692
|
fetch: impl,
|
|
678
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
693
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
679
694
|
});
|
|
680
695
|
|
|
681
696
|
await client.request({ service: 'future', path: '/health' });
|
|
@@ -708,7 +723,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
708
723
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
709
724
|
agent: AGENT,
|
|
710
725
|
fetch: impl,
|
|
711
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
726
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
712
727
|
});
|
|
713
728
|
|
|
714
729
|
await expect(client.request({ service: 'mail', path: '/api/health' })).rejects.toMatchObject({
|
|
@@ -742,7 +757,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
742
757
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
743
758
|
agent: AGENT,
|
|
744
759
|
fetch: impl,
|
|
745
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
760
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
746
761
|
});
|
|
747
762
|
|
|
748
763
|
await expect(client.request({ service: 'future-app', path: '/api/health' })).rejects.toMatchObject({
|
|
@@ -777,7 +792,7 @@ describe('TopoloClient.listServices', () => {
|
|
|
777
792
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
778
793
|
agent: AGENT,
|
|
779
794
|
fetch: impl,
|
|
780
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
795
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
781
796
|
});
|
|
782
797
|
|
|
783
798
|
await client.request({ service: 'warehouse', path: '/api/one' });
|
|
@@ -798,7 +813,7 @@ describe('TopoloClient action catalog', () => {
|
|
|
798
813
|
credential: { kind: 'access_token', accessToken: 'tok_abc' },
|
|
799
814
|
agent: AGENT,
|
|
800
815
|
fetch: impl,
|
|
801
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
816
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
802
817
|
});
|
|
803
818
|
const actions = await client.listActions();
|
|
804
819
|
expect(actions.map((action) => action.name)).toEqual([
|
|
@@ -815,7 +830,7 @@ describe('TopoloClient action catalog', () => {
|
|
|
815
830
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
816
831
|
agent: AGENT,
|
|
817
832
|
fetch: impl,
|
|
818
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
833
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
819
834
|
});
|
|
820
835
|
await client.callAction('messages.list', { mailboxId: 'primary', q: 'Ada', page: 2 });
|
|
821
836
|
const call = requestCalls(calls)[0]!;
|
|
@@ -833,7 +848,7 @@ describe('TopoloClient action catalog', () => {
|
|
|
833
848
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
834
849
|
agent: AGENT,
|
|
835
850
|
fetch: impl,
|
|
836
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
851
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
837
852
|
});
|
|
838
853
|
await client.callAction('topolo_mail_messages_get', {
|
|
839
854
|
mailboxId: 'primary',
|
|
@@ -853,7 +868,7 @@ describe('TopoloClient action catalog', () => {
|
|
|
853
868
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
854
869
|
agent: AGENT,
|
|
855
870
|
fetch: impl,
|
|
856
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
871
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
857
872
|
});
|
|
858
873
|
await expect(
|
|
859
874
|
client.callAction('messages.send', { mailboxId: 'primary', subject: 'Hello' }),
|
|
@@ -866,7 +881,7 @@ describe('TopoloClient action catalog', () => {
|
|
|
866
881
|
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
867
882
|
agent: AGENT,
|
|
868
883
|
fetch: impl,
|
|
869
|
-
serviceUrls: { auth: 'https://auth.example.com' },
|
|
884
|
+
serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
|
|
870
885
|
});
|
|
871
886
|
await client.callAction('messages.send', { mailboxId: 'primary', subject: 'Hello' }, { confirm: true });
|
|
872
887
|
const call = requestCalls(calls)[0]!;
|
package/src/client.ts
CHANGED
|
@@ -337,10 +337,20 @@ export class TopoloClient {
|
|
|
337
337
|
throw new TopoloSdkError('unknown_service', 'service is required');
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
|
|
340
|
+
// auth + developers are DIRECT services resolved without a catalog lookup:
|
|
341
|
+
// developers serves the catalog itself, so resolving it via the catalog would
|
|
342
|
+
// recurse infinitely.
|
|
343
|
+
if (serviceKey === 'auth' || serviceKey === 'developers') {
|
|
341
344
|
const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
|
|
342
345
|
if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
|
|
343
|
-
|
|
346
|
+
// The catalog host (developers) defaults to the SAME environment as auth.
|
|
347
|
+
// Apps configure only `auth` per-env; derive developers from it (auth.X ->
|
|
348
|
+
// developers.X) so the catalog cutover needs no per-app config change.
|
|
349
|
+
if (serviceKey === 'developers') {
|
|
350
|
+
const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl('auth', this.serviceUrls));
|
|
351
|
+
if (derived) return { serviceKey, baseUrl: derived };
|
|
352
|
+
}
|
|
353
|
+
return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey as 'auth' | 'developers'] };
|
|
344
354
|
}
|
|
345
355
|
|
|
346
356
|
const live = await this.resolveLiveCallableService(serviceKey);
|
|
@@ -386,7 +396,8 @@ export class TopoloClient {
|
|
|
386
396
|
const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
|
|
387
397
|
if (configuredServiceId) return configuredServiceId;
|
|
388
398
|
|
|
389
|
-
|
|
399
|
+
// app-catalog greenfield: by-slug resolver lives on the Catalog (developers).
|
|
400
|
+
const authBase = resolveServiceUrl('developers', this.serviceUrls);
|
|
390
401
|
const cacheKey = `${authBase}|${normalized}`;
|
|
391
402
|
const now = Date.now();
|
|
392
403
|
const cached = TopoloClient.serviceIdCache.get(cacheKey);
|
|
@@ -450,10 +461,13 @@ export class TopoloClient {
|
|
|
450
461
|
return this.serviceCatalogCache.entries;
|
|
451
462
|
}
|
|
452
463
|
|
|
464
|
+
// app-catalog greenfield: the catalog moved to the Catalog (developers)
|
|
465
|
+
// service. Auth no longer owns the registry; it only provides scoping, which
|
|
466
|
+
// the Catalog composes server-side. Consumers pass the per-env developers URL.
|
|
453
467
|
const res = await this.request<
|
|
454
468
|
| { data?: { services?: unknown[] } }
|
|
455
469
|
| { services?: unknown[] }
|
|
456
|
-
>({ service: '
|
|
470
|
+
>({ service: 'developers', method: 'GET', path: '/api/services/catalog' });
|
|
457
471
|
const raw =
|
|
458
472
|
(res as { data?: { services?: unknown[] } }).data?.services ??
|
|
459
473
|
(res as { services?: unknown[] }).services;
|
|
@@ -774,6 +788,24 @@ function normalizeLookupKey(value: string): string {
|
|
|
774
788
|
return String(value || '').trim().toLowerCase().replace(/\s+/g, '-');
|
|
775
789
|
}
|
|
776
790
|
|
|
791
|
+
/**
|
|
792
|
+
* Derive the Catalog (developers) base URL from a configured auth base URL by
|
|
793
|
+
* swapping the leading `auth` host label for `developers` (auth.stg.topolo.us ->
|
|
794
|
+
* developers.stg.topolo.us). Returns null if the auth URL is absent or doesn't
|
|
795
|
+
* start with the `auth.` label, so the caller falls back to the bundled default.
|
|
796
|
+
*/
|
|
797
|
+
function deriveDevelopersFromAuth(authUrl: string | null | undefined): string | null {
|
|
798
|
+
if (!authUrl) return null;
|
|
799
|
+
try {
|
|
800
|
+
const url = new URL(authUrl);
|
|
801
|
+
if (!/^auth\./i.test(url.hostname)) return null;
|
|
802
|
+
url.hostname = url.hostname.replace(/^auth\./i, 'developers.');
|
|
803
|
+
return url.origin;
|
|
804
|
+
} catch {
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
777
809
|
function catalogEntryMatches(entry: TopoloServiceCatalogEntry, needle: string): boolean {
|
|
778
810
|
return catalogEntryLookupKeys(entry).includes(needle);
|
|
779
811
|
}
|
|
@@ -30,7 +30,7 @@ export const SERVICES = {
|
|
|
30
30
|
mail: "https://mail.topolo.app",
|
|
31
31
|
mdm: "https://mdm.topolo.app",
|
|
32
32
|
messages: "https://messages.topolo.app",
|
|
33
|
-
nexus: "https://nexus
|
|
33
|
+
nexus: "https://nexus.topolo.app",
|
|
34
34
|
notify: "https://notify.topolo.app",
|
|
35
35
|
observability_watch: "https://observability-watch.topolo.app",
|
|
36
36
|
one: "https://www.topolo.app",
|