@topolo/sdk 0.4.8 → 0.4.10
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 +38 -17
- package/dist/index.d.cts +12 -9
- package/dist/index.d.ts +12 -9
- package/dist/index.js +38 -17
- package/package.json +1 -1
- package/src/applications.generated.ts +10 -6
- package/src/client.test.ts +34 -19
- package/src/client.ts +47 -7
- package/src/index.test.ts +1 -1
- 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(
|
|
@@ -2414,8 +2422,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
2414
2422
|
return entries;
|
|
2415
2423
|
}
|
|
2416
2424
|
/**
|
|
2417
|
-
* List callable actions from the live
|
|
2418
|
-
* the
|
|
2425
|
+
* List callable actions from the live registry-canonical catalog (definitions
|
|
2426
|
+
* from the Catalog registry, scoping from Auth). The response is scoped to the
|
|
2427
|
+
* caller's organization and effective permissions; no bundled/static app
|
|
2419
2428
|
* action list is used as an authorization source.
|
|
2420
2429
|
*/
|
|
2421
2430
|
async listActions(options = {}) {
|
|
@@ -2424,7 +2433,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2424
2433
|
return this.actionCatalogCache.entries;
|
|
2425
2434
|
}
|
|
2426
2435
|
const res = await this.request({
|
|
2427
|
-
service: "
|
|
2436
|
+
service: "developers",
|
|
2428
2437
|
method: "GET",
|
|
2429
2438
|
path: "/api/actions/catalog",
|
|
2430
2439
|
...options.service ? { query: { service: options.service } } : {}
|
|
@@ -2604,6 +2613,17 @@ function serviceKeyCandidates2(slug) {
|
|
|
2604
2613
|
function normalizeLookupKey(value) {
|
|
2605
2614
|
return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
2606
2615
|
}
|
|
2616
|
+
function deriveDevelopersFromAuth(authUrl) {
|
|
2617
|
+
if (!authUrl) return null;
|
|
2618
|
+
try {
|
|
2619
|
+
const url = new URL(authUrl);
|
|
2620
|
+
if (!/^auth\./i.test(url.hostname)) return null;
|
|
2621
|
+
url.hostname = url.hostname.replace(/^auth\./i, "developers.");
|
|
2622
|
+
return url.origin;
|
|
2623
|
+
} catch {
|
|
2624
|
+
return null;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2607
2627
|
function catalogEntryMatches(entry, needle) {
|
|
2608
2628
|
return catalogEntryLookupKeys(entry).includes(needle);
|
|
2609
2629
|
}
|
|
@@ -2648,6 +2668,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
2648
2668
|
readOnly: Boolean(a.read_only ?? a.readOnly),
|
|
2649
2669
|
destructive: Boolean(a.destructive),
|
|
2650
2670
|
requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
|
|
2671
|
+
agentAccess: pickAgentAccess(a.agent_access ?? a.agentAccess),
|
|
2651
2672
|
status: pickActionString(a.status),
|
|
2652
2673
|
sourceRevision: pickActionString(a.source_revision) ?? pickActionString(a.sourceRevision)
|
|
2653
2674
|
};
|
|
@@ -2658,6 +2679,9 @@ function isHttpMethod(value) {
|
|
|
2658
2679
|
function pickActionString(value) {
|
|
2659
2680
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
2660
2681
|
}
|
|
2682
|
+
function pickAgentAccess(value) {
|
|
2683
|
+
return value === "auto" || value === "confirm" || value === "off" ? value : null;
|
|
2684
|
+
}
|
|
2661
2685
|
function plainObject(value) {
|
|
2662
2686
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2663
2687
|
}
|
|
@@ -2751,7 +2775,6 @@ var CrmModule = class {
|
|
|
2751
2775
|
constructor(client) {
|
|
2752
2776
|
this.client = client;
|
|
2753
2777
|
}
|
|
2754
|
-
client;
|
|
2755
2778
|
async listContacts(options = {}) {
|
|
2756
2779
|
const res = await this.client.request({
|
|
2757
2780
|
service: "crm",
|
|
@@ -2799,7 +2822,6 @@ var IdentityModule = class {
|
|
|
2799
2822
|
constructor(client) {
|
|
2800
2823
|
this.client = client;
|
|
2801
2824
|
}
|
|
2802
|
-
client;
|
|
2803
2825
|
/** Returns the resolved user, organization, and permission set for the
|
|
2804
2826
|
* current credential. Equivalent to `TopoloClient.introspect()` — exposed
|
|
2805
2827
|
* on this module for symmetry with the other domain modules. */
|
|
@@ -2813,7 +2835,6 @@ var P2PModule = class {
|
|
|
2813
2835
|
constructor(client) {
|
|
2814
2836
|
this.client = client;
|
|
2815
2837
|
}
|
|
2816
|
-
client;
|
|
2817
2838
|
// Directory ----------------------------------------------------------------
|
|
2818
2839
|
async listDirectory(options = {}) {
|
|
2819
2840
|
const res = await this.client.request({
|
package/dist/index.d.cts
CHANGED
|
@@ -145,6 +145,8 @@ interface TopoloActionCatalogEntry {
|
|
|
145
145
|
readOnly: boolean;
|
|
146
146
|
destructive: boolean;
|
|
147
147
|
requiresConfirmation: boolean;
|
|
148
|
+
/** Agent access tier. null/absent when the catalog does not specify one (consumers derive a default). */
|
|
149
|
+
agentAccess?: 'auto' | 'confirm' | 'off' | null;
|
|
148
150
|
status: string | null;
|
|
149
151
|
sourceRevision: string | null;
|
|
150
152
|
}
|
|
@@ -206,8 +208,9 @@ declare class TopoloClient {
|
|
|
206
208
|
*/
|
|
207
209
|
listServices(): Promise<TopoloServiceCatalogEntry[]>;
|
|
208
210
|
/**
|
|
209
|
-
* List callable actions from the live
|
|
210
|
-
* the
|
|
211
|
+
* List callable actions from the live registry-canonical catalog (definitions
|
|
212
|
+
* from the Catalog registry, scoping from Auth). The response is scoped to the
|
|
213
|
+
* caller's organization and effective permissions; no bundled/static app
|
|
211
214
|
* action list is used as an authorization source.
|
|
212
215
|
*/
|
|
213
216
|
listActions(options?: {
|
|
@@ -277,7 +280,7 @@ declare const SERVICES: {
|
|
|
277
280
|
readonly mail: "https://mail.topolo.app";
|
|
278
281
|
readonly mdm: "https://mdm.topolo.app";
|
|
279
282
|
readonly messages: "https://messages.topolo.app";
|
|
280
|
-
readonly nexus: "https://nexus
|
|
283
|
+
readonly nexus: "https://nexus.topolo.app";
|
|
281
284
|
readonly notify: "https://notify.topolo.app";
|
|
282
285
|
readonly observability_watch: "https://observability-watch.topolo.app";
|
|
283
286
|
readonly one: "https://www.topolo.app";
|
|
@@ -702,8 +705,8 @@ declare const APPLICATIONS: {
|
|
|
702
705
|
}, {
|
|
703
706
|
readonly name: "topolo-feed-api";
|
|
704
707
|
readonly kind: "worker";
|
|
705
|
-
readonly apiUrl:
|
|
706
|
-
readonly domains: readonly [];
|
|
708
|
+
readonly apiUrl: "https://feed-api.topolo.app";
|
|
709
|
+
readonly domains: readonly ["feed-api.topolo.app"];
|
|
707
710
|
readonly routes: readonly [];
|
|
708
711
|
readonly serviceId: null;
|
|
709
712
|
}, {
|
|
@@ -985,8 +988,8 @@ declare const APPLICATIONS: {
|
|
|
985
988
|
}, {
|
|
986
989
|
readonly name: "topolo-nexus-gateway";
|
|
987
990
|
readonly kind: "worker";
|
|
988
|
-
readonly apiUrl: "https://nexus
|
|
989
|
-
readonly domains: readonly ["nexus
|
|
991
|
+
readonly apiUrl: "https://nexus.topolo.app";
|
|
992
|
+
readonly domains: readonly ["nexus.topolo.app"];
|
|
990
993
|
readonly routes: readonly [];
|
|
991
994
|
readonly serviceId: "nexus";
|
|
992
995
|
}];
|
|
@@ -1245,8 +1248,8 @@ declare const APPLICATIONS: {
|
|
|
1245
1248
|
}, {
|
|
1246
1249
|
readonly name: "topolo-socialize-api";
|
|
1247
1250
|
readonly kind: "worker";
|
|
1248
|
-
readonly apiUrl:
|
|
1249
|
-
readonly domains: readonly [];
|
|
1251
|
+
readonly apiUrl: "https://socialize-api.topolo.app";
|
|
1252
|
+
readonly domains: readonly ["socialize-api.topolo.app"];
|
|
1250
1253
|
readonly routes: readonly [];
|
|
1251
1254
|
readonly serviceId: null;
|
|
1252
1255
|
}, {
|
package/dist/index.d.ts
CHANGED
|
@@ -145,6 +145,8 @@ interface TopoloActionCatalogEntry {
|
|
|
145
145
|
readOnly: boolean;
|
|
146
146
|
destructive: boolean;
|
|
147
147
|
requiresConfirmation: boolean;
|
|
148
|
+
/** Agent access tier. null/absent when the catalog does not specify one (consumers derive a default). */
|
|
149
|
+
agentAccess?: 'auto' | 'confirm' | 'off' | null;
|
|
148
150
|
status: string | null;
|
|
149
151
|
sourceRevision: string | null;
|
|
150
152
|
}
|
|
@@ -206,8 +208,9 @@ declare class TopoloClient {
|
|
|
206
208
|
*/
|
|
207
209
|
listServices(): Promise<TopoloServiceCatalogEntry[]>;
|
|
208
210
|
/**
|
|
209
|
-
* List callable actions from the live
|
|
210
|
-
* the
|
|
211
|
+
* List callable actions from the live registry-canonical catalog (definitions
|
|
212
|
+
* from the Catalog registry, scoping from Auth). The response is scoped to the
|
|
213
|
+
* caller's organization and effective permissions; no bundled/static app
|
|
211
214
|
* action list is used as an authorization source.
|
|
212
215
|
*/
|
|
213
216
|
listActions(options?: {
|
|
@@ -277,7 +280,7 @@ declare const SERVICES: {
|
|
|
277
280
|
readonly mail: "https://mail.topolo.app";
|
|
278
281
|
readonly mdm: "https://mdm.topolo.app";
|
|
279
282
|
readonly messages: "https://messages.topolo.app";
|
|
280
|
-
readonly nexus: "https://nexus
|
|
283
|
+
readonly nexus: "https://nexus.topolo.app";
|
|
281
284
|
readonly notify: "https://notify.topolo.app";
|
|
282
285
|
readonly observability_watch: "https://observability-watch.topolo.app";
|
|
283
286
|
readonly one: "https://www.topolo.app";
|
|
@@ -702,8 +705,8 @@ declare const APPLICATIONS: {
|
|
|
702
705
|
}, {
|
|
703
706
|
readonly name: "topolo-feed-api";
|
|
704
707
|
readonly kind: "worker";
|
|
705
|
-
readonly apiUrl:
|
|
706
|
-
readonly domains: readonly [];
|
|
708
|
+
readonly apiUrl: "https://feed-api.topolo.app";
|
|
709
|
+
readonly domains: readonly ["feed-api.topolo.app"];
|
|
707
710
|
readonly routes: readonly [];
|
|
708
711
|
readonly serviceId: null;
|
|
709
712
|
}, {
|
|
@@ -985,8 +988,8 @@ declare const APPLICATIONS: {
|
|
|
985
988
|
}, {
|
|
986
989
|
readonly name: "topolo-nexus-gateway";
|
|
987
990
|
readonly kind: "worker";
|
|
988
|
-
readonly apiUrl: "https://nexus
|
|
989
|
-
readonly domains: readonly ["nexus
|
|
991
|
+
readonly apiUrl: "https://nexus.topolo.app";
|
|
992
|
+
readonly domains: readonly ["nexus.topolo.app"];
|
|
990
993
|
readonly routes: readonly [];
|
|
991
994
|
readonly serviceId: "nexus";
|
|
992
995
|
}];
|
|
@@ -1245,8 +1248,8 @@ declare const APPLICATIONS: {
|
|
|
1245
1248
|
}, {
|
|
1246
1249
|
readonly name: "topolo-socialize-api";
|
|
1247
1250
|
readonly kind: "worker";
|
|
1248
|
-
readonly apiUrl:
|
|
1249
|
-
readonly domains: readonly [];
|
|
1251
|
+
readonly apiUrl: "https://socialize-api.topolo.app";
|
|
1252
|
+
readonly domains: readonly ["socialize-api.topolo.app"];
|
|
1250
1253
|
readonly routes: readonly [];
|
|
1251
1254
|
readonly serviceId: null;
|
|
1252
1255
|
}, {
|
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(
|
|
@@ -2359,8 +2367,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
2359
2367
|
return entries;
|
|
2360
2368
|
}
|
|
2361
2369
|
/**
|
|
2362
|
-
* List callable actions from the live
|
|
2363
|
-
* the
|
|
2370
|
+
* List callable actions from the live registry-canonical catalog (definitions
|
|
2371
|
+
* from the Catalog registry, scoping from Auth). The response is scoped to the
|
|
2372
|
+
* caller's organization and effective permissions; no bundled/static app
|
|
2364
2373
|
* action list is used as an authorization source.
|
|
2365
2374
|
*/
|
|
2366
2375
|
async listActions(options = {}) {
|
|
@@ -2369,7 +2378,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2369
2378
|
return this.actionCatalogCache.entries;
|
|
2370
2379
|
}
|
|
2371
2380
|
const res = await this.request({
|
|
2372
|
-
service: "
|
|
2381
|
+
service: "developers",
|
|
2373
2382
|
method: "GET",
|
|
2374
2383
|
path: "/api/actions/catalog",
|
|
2375
2384
|
...options.service ? { query: { service: options.service } } : {}
|
|
@@ -2549,6 +2558,17 @@ function serviceKeyCandidates2(slug) {
|
|
|
2549
2558
|
function normalizeLookupKey(value) {
|
|
2550
2559
|
return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
2551
2560
|
}
|
|
2561
|
+
function deriveDevelopersFromAuth(authUrl) {
|
|
2562
|
+
if (!authUrl) return null;
|
|
2563
|
+
try {
|
|
2564
|
+
const url = new URL(authUrl);
|
|
2565
|
+
if (!/^auth\./i.test(url.hostname)) return null;
|
|
2566
|
+
url.hostname = url.hostname.replace(/^auth\./i, "developers.");
|
|
2567
|
+
return url.origin;
|
|
2568
|
+
} catch {
|
|
2569
|
+
return null;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2552
2572
|
function catalogEntryMatches(entry, needle) {
|
|
2553
2573
|
return catalogEntryLookupKeys(entry).includes(needle);
|
|
2554
2574
|
}
|
|
@@ -2593,6 +2613,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
2593
2613
|
readOnly: Boolean(a.read_only ?? a.readOnly),
|
|
2594
2614
|
destructive: Boolean(a.destructive),
|
|
2595
2615
|
requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
|
|
2616
|
+
agentAccess: pickAgentAccess(a.agent_access ?? a.agentAccess),
|
|
2596
2617
|
status: pickActionString(a.status),
|
|
2597
2618
|
sourceRevision: pickActionString(a.source_revision) ?? pickActionString(a.sourceRevision)
|
|
2598
2619
|
};
|
|
@@ -2603,6 +2624,9 @@ function isHttpMethod(value) {
|
|
|
2603
2624
|
function pickActionString(value) {
|
|
2604
2625
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
2605
2626
|
}
|
|
2627
|
+
function pickAgentAccess(value) {
|
|
2628
|
+
return value === "auto" || value === "confirm" || value === "off" ? value : null;
|
|
2629
|
+
}
|
|
2606
2630
|
function plainObject(value) {
|
|
2607
2631
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2608
2632
|
}
|
|
@@ -2696,7 +2720,6 @@ var CrmModule = class {
|
|
|
2696
2720
|
constructor(client) {
|
|
2697
2721
|
this.client = client;
|
|
2698
2722
|
}
|
|
2699
|
-
client;
|
|
2700
2723
|
async listContacts(options = {}) {
|
|
2701
2724
|
const res = await this.client.request({
|
|
2702
2725
|
service: "crm",
|
|
@@ -2744,7 +2767,6 @@ var IdentityModule = class {
|
|
|
2744
2767
|
constructor(client) {
|
|
2745
2768
|
this.client = client;
|
|
2746
2769
|
}
|
|
2747
|
-
client;
|
|
2748
2770
|
/** Returns the resolved user, organization, and permission set for the
|
|
2749
2771
|
* current credential. Equivalent to `TopoloClient.introspect()` — exposed
|
|
2750
2772
|
* on this module for symmetry with the other domain modules. */
|
|
@@ -2758,7 +2780,6 @@ var P2PModule = class {
|
|
|
2758
2780
|
constructor(client) {
|
|
2759
2781
|
this.client = client;
|
|
2760
2782
|
}
|
|
2761
|
-
client;
|
|
2762
2783
|
// Directory ----------------------------------------------------------------
|
|
2763
2784
|
async listDirectory(options = {}) {
|
|
2764
2785
|
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
|
@@ -151,6 +151,8 @@ export interface TopoloActionCatalogEntry {
|
|
|
151
151
|
readOnly: boolean;
|
|
152
152
|
destructive: boolean;
|
|
153
153
|
requiresConfirmation: boolean;
|
|
154
|
+
/** Agent access tier. null/absent when the catalog does not specify one (consumers derive a default). */
|
|
155
|
+
agentAccess?: 'auto' | 'confirm' | 'off' | null;
|
|
154
156
|
status: string | null;
|
|
155
157
|
sourceRevision: string | null;
|
|
156
158
|
}
|
|
@@ -337,10 +339,20 @@ export class TopoloClient {
|
|
|
337
339
|
throw new TopoloSdkError('unknown_service', 'service is required');
|
|
338
340
|
}
|
|
339
341
|
|
|
340
|
-
|
|
342
|
+
// auth + developers are DIRECT services resolved without a catalog lookup:
|
|
343
|
+
// developers serves the catalog itself, so resolving it via the catalog would
|
|
344
|
+
// recurse infinitely.
|
|
345
|
+
if (serviceKey === 'auth' || serviceKey === 'developers') {
|
|
341
346
|
const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
|
|
342
347
|
if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
|
|
343
|
-
|
|
348
|
+
// The catalog host (developers) defaults to the SAME environment as auth.
|
|
349
|
+
// Apps configure only `auth` per-env; derive developers from it (auth.X ->
|
|
350
|
+
// developers.X) so the catalog cutover needs no per-app config change.
|
|
351
|
+
if (serviceKey === 'developers') {
|
|
352
|
+
const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl('auth', this.serviceUrls));
|
|
353
|
+
if (derived) return { serviceKey, baseUrl: derived };
|
|
354
|
+
}
|
|
355
|
+
return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey as 'auth' | 'developers'] };
|
|
344
356
|
}
|
|
345
357
|
|
|
346
358
|
const live = await this.resolveLiveCallableService(serviceKey);
|
|
@@ -386,7 +398,8 @@ export class TopoloClient {
|
|
|
386
398
|
const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
|
|
387
399
|
if (configuredServiceId) return configuredServiceId;
|
|
388
400
|
|
|
389
|
-
|
|
401
|
+
// app-catalog greenfield: by-slug resolver lives on the Catalog (developers).
|
|
402
|
+
const authBase = resolveServiceUrl('developers', this.serviceUrls);
|
|
390
403
|
const cacheKey = `${authBase}|${normalized}`;
|
|
391
404
|
const now = Date.now();
|
|
392
405
|
const cached = TopoloClient.serviceIdCache.get(cacheKey);
|
|
@@ -450,10 +463,13 @@ export class TopoloClient {
|
|
|
450
463
|
return this.serviceCatalogCache.entries;
|
|
451
464
|
}
|
|
452
465
|
|
|
466
|
+
// app-catalog greenfield: the catalog moved to the Catalog (developers)
|
|
467
|
+
// service. Auth no longer owns the registry; it only provides scoping, which
|
|
468
|
+
// the Catalog composes server-side. Consumers pass the per-env developers URL.
|
|
453
469
|
const res = await this.request<
|
|
454
470
|
| { data?: { services?: unknown[] } }
|
|
455
471
|
| { services?: unknown[] }
|
|
456
|
-
>({ service: '
|
|
472
|
+
>({ service: 'developers', method: 'GET', path: '/api/services/catalog' });
|
|
457
473
|
const raw =
|
|
458
474
|
(res as { data?: { services?: unknown[] } }).data?.services ??
|
|
459
475
|
(res as { services?: unknown[] }).services;
|
|
@@ -525,8 +541,9 @@ export class TopoloClient {
|
|
|
525
541
|
}
|
|
526
542
|
|
|
527
543
|
/**
|
|
528
|
-
* List callable actions from the live
|
|
529
|
-
* the
|
|
544
|
+
* List callable actions from the live registry-canonical catalog (definitions
|
|
545
|
+
* from the Catalog registry, scoping from Auth). The response is scoped to the
|
|
546
|
+
* caller's organization and effective permissions; no bundled/static app
|
|
530
547
|
* action list is used as an authorization source.
|
|
531
548
|
*/
|
|
532
549
|
async listActions(options: { service?: string } = {}): Promise<TopoloActionCatalogEntry[]> {
|
|
@@ -539,7 +556,7 @@ export class TopoloClient {
|
|
|
539
556
|
| { data?: { actions?: unknown[] } }
|
|
540
557
|
| { actions?: unknown[] }
|
|
541
558
|
>({
|
|
542
|
-
service: '
|
|
559
|
+
service: 'developers',
|
|
543
560
|
method: 'GET',
|
|
544
561
|
path: '/api/actions/catalog',
|
|
545
562
|
...(options.service ? { query: { service: options.service } } : {}),
|
|
@@ -774,6 +791,24 @@ function normalizeLookupKey(value: string): string {
|
|
|
774
791
|
return String(value || '').trim().toLowerCase().replace(/\s+/g, '-');
|
|
775
792
|
}
|
|
776
793
|
|
|
794
|
+
/**
|
|
795
|
+
* Derive the Catalog (developers) base URL from a configured auth base URL by
|
|
796
|
+
* swapping the leading `auth` host label for `developers` (auth.stg.topolo.us ->
|
|
797
|
+
* developers.stg.topolo.us). Returns null if the auth URL is absent or doesn't
|
|
798
|
+
* start with the `auth.` label, so the caller falls back to the bundled default.
|
|
799
|
+
*/
|
|
800
|
+
function deriveDevelopersFromAuth(authUrl: string | null | undefined): string | null {
|
|
801
|
+
if (!authUrl) return null;
|
|
802
|
+
try {
|
|
803
|
+
const url = new URL(authUrl);
|
|
804
|
+
if (!/^auth\./i.test(url.hostname)) return null;
|
|
805
|
+
url.hostname = url.hostname.replace(/^auth\./i, 'developers.');
|
|
806
|
+
return url.origin;
|
|
807
|
+
} catch {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
777
812
|
function catalogEntryMatches(entry: TopoloServiceCatalogEntry, needle: string): boolean {
|
|
778
813
|
return catalogEntryLookupKeys(entry).includes(needle);
|
|
779
814
|
}
|
|
@@ -823,6 +858,7 @@ function normalizeActionCatalogEntry(item: unknown): TopoloActionCatalogEntry |
|
|
|
823
858
|
readOnly: Boolean(a.read_only ?? a.readOnly),
|
|
824
859
|
destructive: Boolean(a.destructive),
|
|
825
860
|
requiresConfirmation: Boolean(a.requires_confirmation ?? a.requiresConfirmation),
|
|
861
|
+
agentAccess: pickAgentAccess(a.agent_access ?? a.agentAccess),
|
|
826
862
|
status: pickActionString(a.status),
|
|
827
863
|
sourceRevision: pickActionString(a.source_revision) ?? pickActionString(a.sourceRevision),
|
|
828
864
|
};
|
|
@@ -836,6 +872,10 @@ function pickActionString(value: unknown): string | null {
|
|
|
836
872
|
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
837
873
|
}
|
|
838
874
|
|
|
875
|
+
function pickAgentAccess(value: unknown): 'auto' | 'confirm' | 'off' | null {
|
|
876
|
+
return value === 'auto' || value === 'confirm' || value === 'off' ? value : null;
|
|
877
|
+
}
|
|
878
|
+
|
|
839
879
|
function plainObject(value: unknown): Record<string, unknown> {
|
|
840
880
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
841
881
|
? value as Record<string, unknown>
|
package/src/index.test.ts
CHANGED
|
@@ -63,7 +63,7 @@ describe('createTopolo', () => {
|
|
|
63
63
|
describe('package surface re-exports', () => {
|
|
64
64
|
it('re-exports the error classes from the barrel', () => {
|
|
65
65
|
expect(new TopoloAuthError('x').name).toBe('TopoloAuthError');
|
|
66
|
-
expect(new TopoloHttpError('auth', '/p', 500).name).toBe('TopoloHttpError');
|
|
66
|
+
expect(new TopoloHttpError('auth', '/p', 500, null).name).toBe('TopoloHttpError');
|
|
67
67
|
expect(new TopoloPermissionError('nope', []).name).toBe('TopoloPermissionError');
|
|
68
68
|
});
|
|
69
69
|
});
|
|
@@ -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",
|