@topolo/sdk 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/dist/index.cjs +447 -188
- package/dist/index.d.cts +463 -271
- package/dist/index.d.ts +463 -271
- package/dist/index.js +447 -188
- package/package.json +1 -1
- package/src/application-requirements.ts +16 -5
- package/src/applications.generated.ts +349 -171
- package/src/client.test.ts +103 -0
- package/src/client.ts +114 -19
- package/src/services.generated.ts +4 -2
- package/src/services.test.ts +11 -5
package/src/client.test.ts
CHANGED
|
@@ -415,6 +415,109 @@ describe('TopoloClient.listServices', () => {
|
|
|
415
415
|
expect(out.some((e) => e.serviceId === 'admin')).toBe(true);
|
|
416
416
|
expect(out.every((e) => typeof e.serviceId === 'string')).toBe(true);
|
|
417
417
|
});
|
|
418
|
+
|
|
419
|
+
it('resolves future API services from the live catalog during request routing', async () => {
|
|
420
|
+
const { impl, calls } = stubFetch([
|
|
421
|
+
{
|
|
422
|
+
body: {
|
|
423
|
+
data: {
|
|
424
|
+
services: [
|
|
425
|
+
{
|
|
426
|
+
service_id: 'srv_future',
|
|
427
|
+
app_slug: 'topolo-warehouse',
|
|
428
|
+
name: 'Warehouse',
|
|
429
|
+
base_url: 'https://warehouse.topolo.app',
|
|
430
|
+
surface_kind: 'api',
|
|
431
|
+
status: 'active',
|
|
432
|
+
},
|
|
433
|
+
],
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
{ body: { ok: true } },
|
|
438
|
+
]);
|
|
439
|
+
const client = new TopoloClient({
|
|
440
|
+
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
441
|
+
agent: AGENT,
|
|
442
|
+
fetch: impl,
|
|
443
|
+
serviceUrls: { auth: 'https://auth.example.com' },
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
await client.request({ service: 'warehouse', path: '/api/health' });
|
|
447
|
+
|
|
448
|
+
expect(calls[0]!.url).toBe('https://auth.example.com/api/services/catalog');
|
|
449
|
+
expect(calls[1]!.url).toBe('https://warehouse.topolo.app/api/health');
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('does not treat live browser applications as generic API services', async () => {
|
|
453
|
+
const { impl } = stubFetch([
|
|
454
|
+
{
|
|
455
|
+
body: {
|
|
456
|
+
data: {
|
|
457
|
+
services: [
|
|
458
|
+
{
|
|
459
|
+
service_id: 'srv_future_app',
|
|
460
|
+
app_slug: 'topolo-future-app',
|
|
461
|
+
name: 'Future App',
|
|
462
|
+
base_url: 'https://future-app.topolo.app',
|
|
463
|
+
surface_kind: 'application',
|
|
464
|
+
status: 'active',
|
|
465
|
+
launchable: true,
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
]);
|
|
472
|
+
const client = new TopoloClient({
|
|
473
|
+
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
474
|
+
agent: AGENT,
|
|
475
|
+
fetch: impl,
|
|
476
|
+
serviceUrls: { auth: 'https://auth.example.com' },
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
await expect(client.request({ service: 'future-app', path: '/api/health' })).rejects.toMatchObject({
|
|
480
|
+
code: 'unknown_service',
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('caches the live catalog per client for repeated dynamic service calls', async () => {
|
|
485
|
+
const { impl, calls } = stubFetch([
|
|
486
|
+
{
|
|
487
|
+
body: {
|
|
488
|
+
data: {
|
|
489
|
+
services: [
|
|
490
|
+
{
|
|
491
|
+
service_id: 'srv_future',
|
|
492
|
+
app_slug: 'topolo-warehouse',
|
|
493
|
+
name: 'Warehouse',
|
|
494
|
+
base_url: 'https://warehouse.topolo.app',
|
|
495
|
+
surface_kind: 'api',
|
|
496
|
+
status: 'active',
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{ body: { ok: true } },
|
|
503
|
+
{ body: { ok: true } },
|
|
504
|
+
]);
|
|
505
|
+
const client = new TopoloClient({
|
|
506
|
+
credential: { kind: 'api_key', apiKey: 'dak_xyz' },
|
|
507
|
+
agent: AGENT,
|
|
508
|
+
fetch: impl,
|
|
509
|
+
serviceUrls: { auth: 'https://auth.example.com' },
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
await client.request({ service: 'warehouse', path: '/api/one' });
|
|
513
|
+
await client.request({ service: 'warehouse', path: '/api/two' });
|
|
514
|
+
|
|
515
|
+
expect(calls.map((call) => call.url)).toEqual([
|
|
516
|
+
'https://auth.example.com/api/services/catalog',
|
|
517
|
+
'https://warehouse.topolo.app/api/one',
|
|
518
|
+
'https://warehouse.topolo.app/api/two',
|
|
519
|
+
]);
|
|
520
|
+
});
|
|
418
521
|
});
|
|
419
522
|
|
|
420
523
|
// Silence unused-import warning if the bundler later tree-shakes these
|
package/src/client.ts
CHANGED
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
type AgentIdentity,
|
|
6
6
|
type TopoloCredential,
|
|
7
7
|
} from './auth.js';
|
|
8
|
-
import { TopoloAuthError, TopoloHttpError, TopoloPermissionError } from './errors.js';
|
|
8
|
+
import { TopoloAuthError, TopoloHttpError, TopoloPermissionError, TopoloSdkError } from './errors.js';
|
|
9
9
|
import {
|
|
10
|
+
DEFAULT_SERVICE_URLS,
|
|
10
11
|
APPLICATIONS,
|
|
11
12
|
resolveServiceUrl,
|
|
12
13
|
type ServiceId,
|
|
@@ -16,9 +17,9 @@ export interface TopoloClientOptions {
|
|
|
16
17
|
credential: TopoloCredential;
|
|
17
18
|
agent: AgentIdentity;
|
|
18
19
|
/** Per-service URL overrides. Useful for staging/dev. */
|
|
19
|
-
serviceUrls?:
|
|
20
|
+
serviceUrls?: Record<string, string>;
|
|
20
21
|
/** Per-service TopoloAuth service id overrides from the owning app/deployment. */
|
|
21
|
-
serviceIds?:
|
|
22
|
+
serviceIds?: Record<string, string>;
|
|
22
23
|
/**
|
|
23
24
|
* When true, mutating helpers (POST/PUT/PATCH/DELETE) require callers to pass
|
|
24
25
|
* `{ confirm: true }`. Defaults to true. Set false only for trusted surfaces.
|
|
@@ -41,7 +42,7 @@ export type TopoloDebugEvent =
|
|
|
41
42
|
| {
|
|
42
43
|
phase: 'request';
|
|
43
44
|
method: string;
|
|
44
|
-
service:
|
|
45
|
+
service: string;
|
|
45
46
|
path: string;
|
|
46
47
|
url: string;
|
|
47
48
|
requestId: string;
|
|
@@ -49,7 +50,7 @@ export type TopoloDebugEvent =
|
|
|
49
50
|
| {
|
|
50
51
|
phase: 'response';
|
|
51
52
|
method: string;
|
|
52
|
-
service:
|
|
53
|
+
service: string;
|
|
53
54
|
path: string;
|
|
54
55
|
url: string;
|
|
55
56
|
requestId: string;
|
|
@@ -59,7 +60,7 @@ export type TopoloDebugEvent =
|
|
|
59
60
|
| {
|
|
60
61
|
phase: 'error';
|
|
61
62
|
method: string;
|
|
62
|
-
service:
|
|
63
|
+
service: string;
|
|
63
64
|
path: string;
|
|
64
65
|
url: string;
|
|
65
66
|
requestId: string;
|
|
@@ -69,7 +70,7 @@ export type TopoloDebugEvent =
|
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
export interface RequestOptions {
|
|
72
|
-
service:
|
|
73
|
+
service: string;
|
|
73
74
|
path: string;
|
|
74
75
|
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
75
76
|
query?: Record<string, string | number | boolean | undefined>;
|
|
@@ -85,6 +86,7 @@ const WRITE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
|
85
86
|
|
|
86
87
|
const POSITIVE_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
87
88
|
const NEGATIVE_CACHE_TTL_MS = 60 * 1000; // 1 minute
|
|
89
|
+
const CATALOG_CACHE_TTL_MS = 60 * 1000; // 1 minute
|
|
88
90
|
|
|
89
91
|
interface ServiceIdCacheEntry {
|
|
90
92
|
value: string | null;
|
|
@@ -110,12 +112,13 @@ export interface TopoloServiceCatalogEntry {
|
|
|
110
112
|
export class TopoloClient {
|
|
111
113
|
private readonly credential: TopoloCredential;
|
|
112
114
|
private readonly agent: AgentIdentity;
|
|
113
|
-
private readonly serviceUrls?:
|
|
114
|
-
private readonly serviceIds?:
|
|
115
|
+
private readonly serviceUrls?: Record<string, string>;
|
|
116
|
+
private readonly serviceIds?: Record<string, string>;
|
|
115
117
|
private readonly requireConfirmForWrites: boolean;
|
|
116
118
|
private readonly timeoutMs: number;
|
|
117
119
|
private readonly fetchImpl: typeof fetch;
|
|
118
120
|
private readonly debug?: (event: TopoloDebugEvent) => void;
|
|
121
|
+
private serviceCatalogCache: { entries: TopoloServiceCatalogEntry[]; expiresAt: number } | null = null;
|
|
119
122
|
|
|
120
123
|
/**
|
|
121
124
|
* Process-wide cache for slug → service-id lookups. Keyed by
|
|
@@ -169,7 +172,8 @@ export class TopoloClient {
|
|
|
169
172
|
);
|
|
170
173
|
}
|
|
171
174
|
|
|
172
|
-
const
|
|
175
|
+
const resolvedService = await this.resolveRequestService(opts.service);
|
|
176
|
+
const baseUrl = resolvedService.baseUrl;
|
|
173
177
|
const url = new URL(opts.path, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);
|
|
174
178
|
if (opts.query) {
|
|
175
179
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
@@ -180,7 +184,7 @@ export class TopoloClient {
|
|
|
180
184
|
const headers = new Headers();
|
|
181
185
|
headers.set('Accept', 'application/json');
|
|
182
186
|
if (opts.body !== undefined) headers.set('Content-Type', 'application/json');
|
|
183
|
-
const platformServiceId = resolveConfiguredServiceId(
|
|
187
|
+
const platformServiceId = resolveConfiguredServiceId(resolvedService.serviceKey, this.serviceIds);
|
|
184
188
|
if (platformServiceId) headers.set('X-Service-ID', platformServiceId);
|
|
185
189
|
|
|
186
190
|
applyAuthHeaders(headers, this.credential);
|
|
@@ -199,7 +203,7 @@ export class TopoloClient {
|
|
|
199
203
|
|
|
200
204
|
const urlStr = url.toString();
|
|
201
205
|
const startedAt = Date.now();
|
|
202
|
-
this.emit({ phase: 'request', method, service:
|
|
206
|
+
this.emit({ phase: 'request', method, service: resolvedService.serviceKey, path: opts.path, url: urlStr, requestId });
|
|
203
207
|
|
|
204
208
|
let res: Response;
|
|
205
209
|
try {
|
|
@@ -213,7 +217,7 @@ export class TopoloClient {
|
|
|
213
217
|
this.emit({
|
|
214
218
|
phase: 'error',
|
|
215
219
|
method,
|
|
216
|
-
service:
|
|
220
|
+
service: resolvedService.serviceKey,
|
|
217
221
|
path: opts.path,
|
|
218
222
|
url: urlStr,
|
|
219
223
|
requestId,
|
|
@@ -236,7 +240,7 @@ export class TopoloClient {
|
|
|
236
240
|
this.emit({
|
|
237
241
|
phase: 'error',
|
|
238
242
|
method,
|
|
239
|
-
service:
|
|
243
|
+
service: resolvedService.serviceKey,
|
|
240
244
|
path: opts.path,
|
|
241
245
|
url: urlStr,
|
|
242
246
|
requestId,
|
|
@@ -251,13 +255,13 @@ export class TopoloClient {
|
|
|
251
255
|
extractRequired(parsed),
|
|
252
256
|
);
|
|
253
257
|
}
|
|
254
|
-
throw new TopoloHttpError(
|
|
258
|
+
throw new TopoloHttpError(resolvedService.serviceKey, opts.path, res.status, parsed);
|
|
255
259
|
}
|
|
256
260
|
|
|
257
261
|
this.emit({
|
|
258
262
|
phase: 'response',
|
|
259
263
|
method,
|
|
260
|
-
service:
|
|
264
|
+
service: resolvedService.serviceKey,
|
|
261
265
|
path: opts.path,
|
|
262
266
|
url: urlStr,
|
|
263
267
|
requestId,
|
|
@@ -268,6 +272,37 @@ export class TopoloClient {
|
|
|
268
272
|
return parsed as T;
|
|
269
273
|
}
|
|
270
274
|
|
|
275
|
+
private async resolveRequestService(service: string): Promise<{ serviceKey: string; baseUrl: string }> {
|
|
276
|
+
const serviceKey = normalizeLookupKey(service);
|
|
277
|
+
if (!serviceKey) {
|
|
278
|
+
throw new TopoloSdkError('unknown_service', 'service is required');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const overrideUrl = resolveConfiguredServiceUrl(serviceKey, this.serviceUrls);
|
|
282
|
+
if (overrideUrl) return { serviceKey, baseUrl: overrideUrl };
|
|
283
|
+
|
|
284
|
+
const staticUrl = DEFAULT_SERVICE_URLS[serviceKey as ServiceId];
|
|
285
|
+
if (staticUrl) return { serviceKey, baseUrl: staticUrl };
|
|
286
|
+
|
|
287
|
+
const live = await this.resolveLiveCallableService(serviceKey);
|
|
288
|
+
if (live) return live;
|
|
289
|
+
|
|
290
|
+
throw new TopoloSdkError(
|
|
291
|
+
'unknown_service',
|
|
292
|
+
`Unknown Topolo service "${service}". Run service discovery first and pass a callable service id or slug.`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private async resolveLiveCallableService(serviceKey: string): Promise<{ serviceKey: string; baseUrl: string } | null> {
|
|
297
|
+
const catalog = await this.listServices();
|
|
298
|
+
const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
|
|
299
|
+
if (!match?.baseUrl || !isCallableCatalogEntry(match)) return null;
|
|
300
|
+
return {
|
|
301
|
+
serviceKey: normalizeLookupKey(match.slug || match.name || match.serviceId) || serviceKey,
|
|
302
|
+
baseUrl: match.baseUrl,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
271
306
|
/**
|
|
272
307
|
* Resolve a service-slug → opaque service-id via caller config or TopoloAuth.
|
|
273
308
|
*
|
|
@@ -349,6 +384,11 @@ export class TopoloClient {
|
|
|
349
384
|
* authoritative; the snapshot is a floor, not a cap.
|
|
350
385
|
*/
|
|
351
386
|
async listServices(): Promise<TopoloServiceCatalogEntry[]> {
|
|
387
|
+
const now = Date.now();
|
|
388
|
+
if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
|
|
389
|
+
return this.serviceCatalogCache.entries;
|
|
390
|
+
}
|
|
391
|
+
|
|
352
392
|
try {
|
|
353
393
|
const res = await this.request<
|
|
354
394
|
| { data?: { services?: unknown[] } }
|
|
@@ -381,7 +421,13 @@ export class TopoloClient {
|
|
|
381
421
|
launchable: s.launchable === true,
|
|
382
422
|
});
|
|
383
423
|
}
|
|
384
|
-
if (entries.length > 0)
|
|
424
|
+
if (entries.length > 0) {
|
|
425
|
+
this.serviceCatalogCache = {
|
|
426
|
+
entries,
|
|
427
|
+
expiresAt: now + CATALOG_CACHE_TTL_MS,
|
|
428
|
+
};
|
|
429
|
+
return entries;
|
|
430
|
+
}
|
|
385
431
|
}
|
|
386
432
|
} catch {
|
|
387
433
|
// Fall through to the generated snapshot.
|
|
@@ -453,7 +499,7 @@ export class TopoloClient {
|
|
|
453
499
|
|
|
454
500
|
function resolveConfiguredServiceId(
|
|
455
501
|
slug: string,
|
|
456
|
-
serviceIds?:
|
|
502
|
+
serviceIds?: Record<string, string>,
|
|
457
503
|
): string | null {
|
|
458
504
|
for (const serviceKey of serviceKeyCandidates(slug)) {
|
|
459
505
|
const configured = serviceIds?.[serviceKey as ServiceId];
|
|
@@ -467,8 +513,24 @@ function resolveConfiguredServiceId(
|
|
|
467
513
|
return null;
|
|
468
514
|
}
|
|
469
515
|
|
|
516
|
+
function resolveConfiguredServiceUrl(
|
|
517
|
+
slug: string,
|
|
518
|
+
serviceUrls?: Record<string, string>,
|
|
519
|
+
): string | null {
|
|
520
|
+
for (const serviceKey of serviceKeyCandidates(slug)) {
|
|
521
|
+
const configured = serviceUrls?.[serviceKey];
|
|
522
|
+
if (typeof configured === 'string' && configured.trim()) return configured.trim();
|
|
523
|
+
|
|
524
|
+
const envKey = `TOPOLO_SERVICE_URL_${serviceKey.replace(/\./g, '_').toUpperCase()}`;
|
|
525
|
+
const envValue = typeof process !== 'undefined' ? process.env?.[envKey] : undefined;
|
|
526
|
+
if (typeof envValue === 'string' && envValue.trim()) return envValue.trim();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
|
|
470
532
|
function serviceKeyCandidates(slug: string): string[] {
|
|
471
|
-
const normalized = slug
|
|
533
|
+
const normalized = normalizeLookupKey(slug);
|
|
472
534
|
const withoutTopolo = normalized.startsWith('topolo-')
|
|
473
535
|
? normalized.slice('topolo-'.length)
|
|
474
536
|
: normalized;
|
|
@@ -480,6 +542,39 @@ function serviceKeyCandidates(slug: string): string[] {
|
|
|
480
542
|
])];
|
|
481
543
|
}
|
|
482
544
|
|
|
545
|
+
function normalizeLookupKey(value: string): string {
|
|
546
|
+
return String(value || '').trim().toLowerCase();
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function catalogEntryMatches(entry: TopoloServiceCatalogEntry, needle: string): boolean {
|
|
550
|
+
return catalogEntryLookupKeys(entry).includes(needle);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function catalogEntryLookupKeys(entry: TopoloServiceCatalogEntry): string[] {
|
|
554
|
+
const values = [entry.serviceId, entry.slug, entry.name].filter((value): value is string => (
|
|
555
|
+
typeof value === 'string' && value.trim().length > 0
|
|
556
|
+
));
|
|
557
|
+
const keys = new Set<string>();
|
|
558
|
+
for (const value of values) {
|
|
559
|
+
const normalized = normalizeLookupKey(value);
|
|
560
|
+
if (!normalized) continue;
|
|
561
|
+
keys.add(normalized);
|
|
562
|
+
keys.add(normalized.replace(/-/g, '_'));
|
|
563
|
+
if (normalized.startsWith('topolo-')) {
|
|
564
|
+
const withoutTopolo = normalized.slice('topolo-'.length);
|
|
565
|
+
keys.add(withoutTopolo);
|
|
566
|
+
keys.add(withoutTopolo.replace(/-/g, '_'));
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return [...keys];
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function isCallableCatalogEntry(entry: TopoloServiceCatalogEntry): boolean {
|
|
573
|
+
const kind = normalizeLookupKey(entry.surfaceKind || '');
|
|
574
|
+
if (kind === 'api' || kind === 'technical_service' || kind === 'service') return true;
|
|
575
|
+
return Boolean(entry.slug && Object.prototype.hasOwnProperty.call(DEFAULT_SERVICE_URLS, catalogEntryLookupKeys(entry)[0] ?? ''));
|
|
576
|
+
}
|
|
577
|
+
|
|
483
578
|
interface AuthMePayload {
|
|
484
579
|
credentialType?: 'access_token' | 'api_key';
|
|
485
580
|
user: RawUser;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export const SERVICES = {
|
|
6
6
|
agent: "https://agent-api.topolo.app",
|
|
7
7
|
auth: "https://auth.topolo.app",
|
|
8
|
+
backup: "https://backup.topolo.app",
|
|
8
9
|
books: "https://books.topolo.app",
|
|
9
10
|
bugfix: "https://bugfix.topolo.app",
|
|
10
11
|
bytes: "https://bytes-api.topolo.app",
|
|
@@ -15,7 +16,6 @@ export const SERVICES = {
|
|
|
15
16
|
compose: "https://compose.topolo.app",
|
|
16
17
|
consent: "https://consent.topolo.app",
|
|
17
18
|
crm: "https://crm-api.topolo.app",
|
|
18
|
-
customer_success: "https://success.topolo.app",
|
|
19
19
|
developers: "https://developers.topolo.app",
|
|
20
20
|
director: "https://director.topolo.app",
|
|
21
21
|
feed: "https://feed-api.topolo.app",
|
|
@@ -29,9 +29,10 @@ export const SERVICES = {
|
|
|
29
29
|
localize: "https://localize.topolo.app",
|
|
30
30
|
mail: "https://mail.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",
|
|
34
34
|
notify: "https://notify.topolo.app",
|
|
35
|
+
observability_watch: "https://observability-watch.topolo.app",
|
|
35
36
|
one: "https://one.topolo.app",
|
|
36
37
|
p2p: "https://p2p.topolo.app",
|
|
37
38
|
pay: "https://pay.topolo.app",
|
|
@@ -45,6 +46,7 @@ export const SERVICES = {
|
|
|
45
46
|
socialize: "https://socialize-api.topolo.app",
|
|
46
47
|
spaces: "https://spaces.topolo.app",
|
|
47
48
|
status: "https://status.topolo.app",
|
|
49
|
+
success: "https://success.topolo.app",
|
|
48
50
|
support: "https://support.topolo.app",
|
|
49
51
|
survey: "https://survey.topolo.app",
|
|
50
52
|
voice: "https://voice.topolo.app",
|
package/src/services.test.ts
CHANGED
|
@@ -21,12 +21,10 @@ const EXPECTED_APPLICATION_IDS = [
|
|
|
21
21
|
'calendar',
|
|
22
22
|
'capacity',
|
|
23
23
|
'chat',
|
|
24
|
-
'cloudcontrol',
|
|
25
24
|
'commerce',
|
|
26
25
|
'compose',
|
|
27
26
|
'consent',
|
|
28
27
|
'crm',
|
|
29
|
-
'customer_success',
|
|
30
28
|
'developers',
|
|
31
29
|
'director',
|
|
32
30
|
'docs',
|
|
@@ -41,7 +39,7 @@ const EXPECTED_APPLICATION_IDS = [
|
|
|
41
39
|
'localize',
|
|
42
40
|
'mail',
|
|
43
41
|
'mdm',
|
|
44
|
-
'
|
|
42
|
+
'messages',
|
|
45
43
|
'nexus',
|
|
46
44
|
'notify',
|
|
47
45
|
'observability_watch',
|
|
@@ -57,6 +55,7 @@ const EXPECTED_APPLICATION_IDS = [
|
|
|
57
55
|
'socialize',
|
|
58
56
|
'spaces',
|
|
59
57
|
'status',
|
|
58
|
+
'success',
|
|
60
59
|
'support',
|
|
61
60
|
'survey',
|
|
62
61
|
'voice',
|
|
@@ -75,7 +74,7 @@ describe('service registry', () => {
|
|
|
75
74
|
web: 'https://web-api.topolo.app',
|
|
76
75
|
});
|
|
77
76
|
expect(DEFAULT_SERVICE_URLS).toMatchObject({
|
|
78
|
-
|
|
77
|
+
success: 'https://success.topolo.app',
|
|
79
78
|
forms: 'https://forms.topolo.app',
|
|
80
79
|
survey: 'https://survey.topolo.app',
|
|
81
80
|
});
|
|
@@ -118,14 +117,21 @@ describe('application catalog', () => {
|
|
|
118
117
|
}
|
|
119
118
|
});
|
|
120
119
|
|
|
121
|
-
it('treats TopoloWeb runtime as a deploy
|
|
120
|
+
it('treats TopoloWeb runtime as a standalone deploy, not an agent-callable API service', () => {
|
|
122
121
|
expect(APPLICATIONS.web.services).toEqual(['web']);
|
|
123
122
|
expect(
|
|
124
123
|
APPLICATIONS.web.deployTargets.find((target) => target.name === 'topolo-web-api')?.serviceId,
|
|
125
124
|
).toBe('web');
|
|
125
|
+
// topolo-web-runtime (Astro, sites.topolo.app) deploys from its own wrangler
|
|
126
|
+
// config and is intentionally listed as non-callable, so it can never be an
|
|
127
|
+
// agent-callable service.
|
|
126
128
|
expect(
|
|
127
129
|
APPLICATIONS.web.deployTargets.find((target) => target.name === 'topolo-web-runtime')?.serviceId,
|
|
128
130
|
).toBeNull();
|
|
131
|
+
const callableTargetNames = APPLICATIONS.web.deployTargets
|
|
132
|
+
.filter((target) => target.serviceId)
|
|
133
|
+
.map((target) => target.name);
|
|
134
|
+
expect(callableTargetNames).not.toContain('topolo-web-runtime');
|
|
129
135
|
});
|
|
130
136
|
|
|
131
137
|
it('treats TopoloFeed analytics as telemetry, not an agent-callable API service', () => {
|