@topolo/sdk 0.4.10 → 0.6.0

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.
@@ -16,20 +16,20 @@ function stubFetch(responses: Array<{ status?: number; body?: unknown; contentTy
16
16
  }
17
17
 
18
18
  const AGENT = { clientName: 'topolo-test', clientVersion: '0.0.0' };
19
- const SERVICE_LOOKUP = { body: { success: true, data: { service_id: 'service_test' } } };
19
+ const SERVICE_LOOKUP = { body: { success: true, data: { app_id: 'app_test' } } };
20
20
  const MAIL_CATALOG = {
21
21
  body: {
22
22
  data: {
23
23
  services: [
24
24
  {
25
- service_id: 'srv_mail',
25
+ app_id: 'app_mail',
26
26
  app_slug: 'topolo-mail',
27
27
  name: 'Topolo Mail',
28
28
  api_base_url: 'https://mail-api.topolo.app',
29
29
  launch_url: 'https://mail.topolo.app',
30
30
  surface_kind: 'api',
31
31
  status: 'active',
32
- granted_permissions: ['srv_mail.messages:read'],
32
+ granted_permissions: ['app_mail.messages:read'],
33
33
  },
34
34
  ],
35
35
  },
@@ -40,18 +40,18 @@ const ACTION_CATALOG = {
40
40
  data: {
41
41
  actions: [
42
42
  {
43
- action_id: 'srv_mail.messages.list',
43
+ action_id: 'app_mail.messages.list',
44
44
  name: 'messages.list',
45
45
  tool_name: 'topolo_mail_messages_list',
46
46
  title: 'List messages',
47
47
  description: 'List Mail messages',
48
- service_id: 'srv_mail',
49
- service_slug: 'topolo-mail',
50
- service_name: 'Topolo Mail',
48
+ app_id: 'app_mail',
49
+ app_slug: 'topolo-mail',
50
+ app_name: 'Topolo Mail',
51
51
  method: 'GET',
52
52
  path: '/api/mail/mailboxes/{mailboxId}/messages',
53
53
  permission_name: 'messages:read',
54
- required_permission: 'srv_mail.messages:read',
54
+ required_permission: 'app_mail.messages:read',
55
55
  input_schema: { type: 'object' },
56
56
  output_schema: {},
57
57
  read_only: true,
@@ -59,18 +59,18 @@ const ACTION_CATALOG = {
59
59
  requires_confirmation: false,
60
60
  },
61
61
  {
62
- action_id: 'srv_mail.messages.get',
62
+ action_id: 'app_mail.messages.get',
63
63
  name: 'messages.get',
64
64
  tool_name: 'topolo_mail_messages_get',
65
65
  title: 'Get message',
66
66
  description: 'Get Mail message',
67
- service_id: 'srv_mail',
68
- service_slug: 'topolo-mail',
69
- service_name: 'Topolo Mail',
67
+ app_id: 'app_mail',
68
+ app_slug: 'topolo-mail',
69
+ app_name: 'Topolo Mail',
70
70
  method: 'GET',
71
71
  path: '/api/mail/mailboxes/{mailboxId}/messages/{messageId}',
72
72
  permission_name: 'messages:read',
73
- required_permission: 'srv_mail.messages:read',
73
+ required_permission: 'app_mail.messages:read',
74
74
  input_schema: { type: 'object' },
75
75
  output_schema: {},
76
76
  read_only: true,
@@ -78,18 +78,18 @@ const ACTION_CATALOG = {
78
78
  requires_confirmation: false,
79
79
  },
80
80
  {
81
- action_id: 'srv_mail.messages.send',
81
+ action_id: 'app_mail.messages.send',
82
82
  name: 'messages.send',
83
83
  tool_name: 'topolo_mail_messages_send',
84
84
  title: 'Send message',
85
85
  description: 'Send Mail message',
86
- service_id: 'srv_mail',
87
- service_slug: 'topolo-mail',
88
- service_name: 'Topolo Mail',
86
+ app_id: 'app_mail',
87
+ app_slug: 'topolo-mail',
88
+ app_name: 'Topolo Mail',
89
89
  method: 'POST',
90
90
  path: '/api/mail/mailboxes/{mailboxId}/messages',
91
91
  permission_name: 'messages:send',
92
- required_permission: 'srv_mail.messages:send',
92
+ required_permission: 'app_mail.messages:send',
93
93
  input_schema: { type: 'object' },
94
94
  output_schema: {},
95
95
  read_only: false,
@@ -110,7 +110,7 @@ function requestCalls(calls: Array<{ url: string; init: RequestInit }>) {
110
110
  }
111
111
 
112
112
  beforeEach(() => {
113
- TopoloClient.clearServiceIdCache();
113
+ TopoloClient.clearAppIdCache();
114
114
  });
115
115
 
116
116
  describe('TopoloClient.request', () => {
@@ -120,12 +120,12 @@ describe('TopoloClient.request', () => {
120
120
  credential: { kind: 'access_token', accessToken: 'tok_abc' },
121
121
  agent: AGENT,
122
122
  fetch: impl,
123
- serviceIds: { auth: 'service_auth' },
123
+ appIds: { auth: 'service_auth' },
124
124
  });
125
125
  await client.request({ service: 'auth', path: '/api/me' });
126
126
  const headers = new Headers(requestCalls(calls)[0]!.init.headers);
127
127
  expect(headers.get('Authorization')).toBe('Bearer tok_abc');
128
- expect(headers.get('X-Service-ID')).toBe('service_auth');
128
+ expect(headers.get('X-App-ID')).toBe('service_auth');
129
129
  expect(headers.get('X-Topolo-Client')).toBe('topolo-test/0.0.0');
130
130
  expect(headers.get('X-Topolo-Request-Id')).toMatch(/.+/);
131
131
  });
@@ -384,9 +384,9 @@ describe('TopoloClient.debug hook', () => {
384
384
  });
385
385
  });
386
386
 
387
- describe('TopoloClient.getServiceIdBySlug', () => {
387
+ describe('TopoloClient.getAppIdBySlug', () => {
388
388
  function freshClient(impl: typeof fetch, serviceUrls?: Record<string, string>) {
389
- TopoloClient.clearServiceIdCache();
389
+ TopoloClient.clearAppIdCache();
390
390
  return new TopoloClient({
391
391
  credential: { kind: 'api_key', apiKey: 'x' },
392
392
  agent: AGENT,
@@ -395,26 +395,26 @@ describe('TopoloClient.getServiceIdBySlug', () => {
395
395
  });
396
396
  }
397
397
 
398
- it('uses configured service ids without calling Auth', async () => {
398
+ it('uses configured app ids without calling Auth', async () => {
399
399
  const { impl, calls } = stubFetch([]);
400
400
  const client = new TopoloClient({
401
401
  credential: { kind: 'api_key', apiKey: 'x' },
402
402
  agent: AGENT,
403
403
  fetch: impl,
404
404
  serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' } as never,
405
- serviceIds: { notify: 'service_notify' },
405
+ appIds: { notify: 'service_notify' },
406
406
  });
407
- const id = await client.getServiceIdBySlug('topolo-notify');
407
+ const id = await client.getAppIdBySlug('topolo-notify');
408
408
  expect(id).toBe('service_notify');
409
409
  expect(calls).toEqual([]);
410
410
  });
411
411
 
412
412
  it('falls back to /api/services/by-slug/:slug for unknown slugs', async () => {
413
413
  const { impl, calls } = stubFetch([
414
- { body: { success: true, data: { service_id: 'service_abc123', api_base_url: 'https://x-api' } } },
414
+ { body: { success: true, data: { app_id: 'service_abc123', api_base_url: 'https://x-api' } } },
415
415
  ]);
416
416
  const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
417
- const id = await client.getServiceIdBySlug('external-service');
417
+ const id = await client.getAppIdBySlug('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');
420
420
  // No auth header — endpoint is public.
@@ -426,27 +426,27 @@ describe('TopoloClient.getServiceIdBySlug', () => {
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
428
  const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
429
- expect(await client.getServiceIdBySlug('nope')).toBeNull();
430
- expect(await client.getServiceIdBySlug('nope')).toBeNull();
429
+ expect(await client.getAppIdBySlug('nope')).toBeNull();
430
+ expect(await client.getAppIdBySlug('nope')).toBeNull();
431
431
  expect(calls.length).toBe(1);
432
432
  });
433
433
 
434
434
  it('caches positive results across calls', async () => {
435
435
  const { impl, calls } = stubFetch([
436
- { body: { success: true, data: { service_id: 'service_external' } } },
436
+ { body: { success: true, data: { app_id: 'service_external' } } },
437
437
  ]);
438
438
  const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
439
- expect(await client.getServiceIdBySlug('external-service')).toBe('service_external');
440
- expect(await client.getServiceIdBySlug('external-service')).toBe('service_external');
439
+ expect(await client.getAppIdBySlug('external-service')).toBe('service_external');
440
+ expect(await client.getAppIdBySlug('external-service')).toBe('service_external');
441
441
  expect(calls.length).toBe(1);
442
442
  });
443
443
 
444
444
  it('normalizes slug case + whitespace before hitting the network', async () => {
445
445
  const { impl, calls } = stubFetch([
446
- { body: { success: true, data: { service_id: 'service_x' } } },
446
+ { body: { success: true, data: { app_id: 'service_x' } } },
447
447
  ]);
448
448
  const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
449
- await client.getServiceIdBySlug(' EXTERNAL-SERVICE ');
449
+ await client.getAppIdBySlug(' EXTERNAL-SERVICE ');
450
450
  expect(calls[0]!.url).toBe('https://auth.example.com/api/services/by-slug/external-service');
451
451
  });
452
452
 
@@ -457,21 +457,21 @@ describe('TopoloClient.getServiceIdBySlug', () => {
457
457
  throws = false;
458
458
  throw new Error('ECONNREFUSED');
459
459
  }
460
- return new Response(JSON.stringify({ success: true, data: { service_id: 'service_y' } }), {
460
+ return new Response(JSON.stringify({ success: true, data: { app_id: 'service_y' } }), {
461
461
  status: 200,
462
462
  headers: { 'Content-Type': 'application/json' },
463
463
  });
464
464
  }) as unknown as typeof fetch;
465
- TopoloClient.clearServiceIdCache();
465
+ TopoloClient.clearAppIdCache();
466
466
  const client = new TopoloClient({
467
467
  credential: { kind: 'api_key', apiKey: 'x' },
468
468
  agent: AGENT,
469
469
  fetch: impl,
470
470
  serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' } as never,
471
471
  });
472
- expect(await client.getServiceIdBySlug('flaky')).toBeNull();
472
+ expect(await client.getAppIdBySlug('flaky')).toBeNull();
473
473
  // Second call retries — earlier null was not cached.
474
- expect(await client.getServiceIdBySlug('flaky')).toBe('service_y');
474
+ expect(await client.getAppIdBySlug('flaky')).toBe('service_y');
475
475
  });
476
476
  });
477
477
 
@@ -483,7 +483,7 @@ describe('TopoloClient.listServices', () => {
483
483
  data: {
484
484
  services: [
485
485
  {
486
- service_id: 'srv_wRCnLLbtvmVT',
486
+ app_id: 'app_topolo_director',
487
487
  app_slug: 'topolo-director',
488
488
  name: 'Topolo Director',
489
489
  description: 'Demo control plane',
@@ -512,7 +512,7 @@ describe('TopoloClient.listServices', () => {
512
512
  exceptionPolicy: 'app_or_organization',
513
513
  },
514
514
  ],
515
- granted_permissions: ['srv_wRCnLLbtvmVT.dashboard:read'],
515
+ granted_permissions: ['app_topolo_director.dashboard:read'],
516
516
  },
517
517
  ],
518
518
  },
@@ -528,7 +528,7 @@ describe('TopoloClient.listServices', () => {
528
528
  const out = await client.listServices();
529
529
  expect(out).toEqual([
530
530
  {
531
- serviceId: 'srv_wRCnLLbtvmVT',
531
+ appId: 'app_topolo_director',
532
532
  slug: 'topolo-director',
533
533
  name: 'Topolo Director',
534
534
  description: 'Demo control plane',
@@ -557,7 +557,7 @@ describe('TopoloClient.listServices', () => {
557
557
  exceptionPolicy: 'app_or_organization',
558
558
  },
559
559
  ],
560
- permissions: ['srv_wRCnLLbtvmVT.dashboard:read'],
560
+ permissions: ['app_topolo_director.dashboard:read'],
561
561
  },
562
562
  ]);
563
563
  expect(calls[0]!.url).toBe(
@@ -586,14 +586,14 @@ describe('TopoloClient.listServices', () => {
586
586
  data: {
587
587
  services: [
588
588
  {
589
- service_id: 'srv_mail',
589
+ app_id: 'app_mail',
590
590
  app_slug: 'topolo-mail',
591
591
  name: 'Topolo Mail',
592
592
  api_base_url: 'https://mail-api.topolo.app',
593
593
  launch_url: 'https://mail.topolo.app',
594
594
  surface_kind: 'application',
595
595
  status: 'active',
596
- granted_permissions: ['srv_mail.messages:read'],
596
+ granted_permissions: ['app_mail.messages:read'],
597
597
  },
598
598
  ],
599
599
  },
@@ -621,14 +621,14 @@ describe('TopoloClient.listServices', () => {
621
621
  data: {
622
622
  services: [
623
623
  {
624
- service_id: 'srv_crm',
624
+ app_id: 'app_crm',
625
625
  app_slug: 'topolo-crm',
626
626
  name: 'Topolo CRM',
627
627
  api_base_url: 'https://crm-api.topolo.app',
628
628
  launch_url: 'https://crm.topolo.app',
629
629
  surface_kind: 'application',
630
630
  status: 'active',
631
- granted_permissions: ['srv_crm.contacts:read'],
631
+ granted_permissions: ['app_crm.contacts:read'],
632
632
  },
633
633
  ],
634
634
  },
@@ -671,14 +671,14 @@ describe('TopoloClient.listServices', () => {
671
671
  data: {
672
672
  services: [
673
673
  {
674
- service_id: 'srv_future',
674
+ app_id: 'app_future',
675
675
  app_slug: 'topolo-future',
676
676
  name: 'Future',
677
677
  api_base_url: 'https://future-api.topolo.app',
678
678
  launch_url: 'https://future.topolo.app',
679
679
  surface_kind: 'api',
680
680
  status: 'active',
681
- granted_permissions: ['srv_future.read:all'],
681
+ granted_permissions: ['app_future.read:all'],
682
682
  },
683
683
  ],
684
684
  },
@@ -705,14 +705,14 @@ describe('TopoloClient.listServices', () => {
705
705
  data: {
706
706
  services: [
707
707
  {
708
- service_id: 'srv_crm',
708
+ app_id: 'app_crm',
709
709
  app_slug: 'topolo-crm',
710
710
  name: 'Topolo CRM',
711
711
  api_base_url: 'https://crm-api.topolo.app',
712
712
  launch_url: 'https://crm.topolo.app',
713
713
  surface_kind: 'api',
714
714
  status: 'active',
715
- granted_permissions: ['srv_crm.contacts:read'],
715
+ granted_permissions: ['app_crm.contacts:read'],
716
716
  },
717
717
  ],
718
718
  },
@@ -739,13 +739,13 @@ describe('TopoloClient.listServices', () => {
739
739
  data: {
740
740
  services: [
741
741
  {
742
- service_id: 'srv_future_app',
742
+ app_id: 'app_future_app',
743
743
  app_slug: 'topolo-future-app',
744
744
  name: 'Future App',
745
745
  launch_url: 'https://future-app.topolo.app',
746
746
  surface_kind: 'application',
747
747
  status: 'active',
748
- granted_permissions: ['srv_future_app.launch:read'],
748
+ granted_permissions: ['app_future_app.launch:read'],
749
749
  launchable: true,
750
750
  },
751
751
  ],
@@ -772,14 +772,14 @@ describe('TopoloClient.listServices', () => {
772
772
  data: {
773
773
  services: [
774
774
  {
775
- service_id: 'srv_future',
775
+ app_id: 'app_future',
776
776
  app_slug: 'topolo-warehouse',
777
777
  name: 'Warehouse',
778
778
  api_base_url: 'https://warehouse.topolo.app',
779
779
  launch_url: 'https://warehouse.topolo.app',
780
780
  surface_kind: 'api',
781
781
  status: 'active',
782
- granted_permissions: ['srv_future.read:all'],
782
+ granted_permissions: ['app_future.read:all'],
783
783
  },
784
784
  ],
785
785
  },
@@ -838,7 +838,7 @@ describe('TopoloClient action catalog', () => {
838
838
  expect(url.toString()).toBe('https://mail-api.topolo.app/api/mail/mailboxes/primary/messages?q=Ada&page=2');
839
839
  expect(call.init.method).toBe('GET');
840
840
  const headers = new Headers(call.init.headers);
841
- expect(headers.get('X-Topolo-Action-Id')).toBe('srv_mail.messages.list');
841
+ expect(headers.get('X-Topolo-Action-Id')).toBe('app_mail.messages.list');
842
842
  expect(headers.get('X-Topolo-Action-Name')).toBe('messages.list');
843
843
  });
844
844
 
package/src/client.ts CHANGED
@@ -7,11 +7,11 @@ import {
7
7
  } from './auth.js';
8
8
  import { TopoloAuthError, TopoloHttpError, TopoloPermissionError, TopoloSdkError } from './errors.js';
9
9
  import {
10
- DEFAULT_SERVICE_URLS,
10
+ DEFAULT_APP_URLS,
11
11
  APPLICATIONS,
12
12
  resolveCatalogServiceUrl,
13
13
  resolveServiceUrl,
14
- type ServiceId,
14
+ type AppApiId,
15
15
  } from './services.js';
16
16
 
17
17
  export interface TopoloClientOptions {
@@ -19,8 +19,8 @@ export interface TopoloClientOptions {
19
19
  agent: AgentIdentity;
20
20
  /** Per-service URL overrides. Useful for staging/dev. */
21
21
  serviceUrls?: Record<string, string>;
22
- /** Per-service TopoloAuth service id overrides from the owning app/deployment. */
23
- serviceIds?: Record<string, string>;
22
+ /** Per-service TopoloAuth app id overrides from the owning app/deployment. */
23
+ appIds?: Record<string, string>;
24
24
  /**
25
25
  * When true, mutating helpers (POST/PUT/PATCH/DELETE) require callers to pass
26
26
  * `{ confirm: true }`. Defaults to true. Set false only for trusted surfaces.
@@ -98,7 +98,7 @@ const POSITIVE_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
98
98
  const NEGATIVE_CACHE_TTL_MS = 60 * 1000; // 1 minute
99
99
  const CATALOG_CACHE_TTL_MS = 60 * 1000; // 1 minute
100
100
 
101
- interface ServiceIdCacheEntry {
101
+ interface AppIdCacheEntry {
102
102
  value: string | null;
103
103
  expiresAt: number;
104
104
  }
@@ -109,7 +109,7 @@ interface ServiceIdCacheEntry {
109
109
  * from TopoloAuth `GET /api/services/catalog`.
110
110
  */
111
111
  export interface TopoloServiceCatalogEntry {
112
- serviceId: string;
112
+ appId: string;
113
113
  slug: string | null;
114
114
  name: string | null;
115
115
  description: string | null;
@@ -139,9 +139,9 @@ export interface TopoloActionCatalogEntry {
139
139
  toolName: string | null;
140
140
  title: string;
141
141
  description: string;
142
- serviceId: string;
143
- serviceSlug: string | null;
144
- serviceName: string | null;
142
+ appId: string;
143
+ appSlug: string | null;
144
+ appName: string | null;
145
145
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
146
146
  path: string;
147
147
  permissionName: string;
@@ -161,7 +161,7 @@ export class TopoloClient {
161
161
  private readonly credential: TopoloCredential;
162
162
  private readonly agent: AgentIdentity;
163
163
  private readonly serviceUrls?: Record<string, string>;
164
- private readonly serviceIds?: Record<string, string>;
164
+ private readonly appIds?: Record<string, string>;
165
165
  private readonly requireConfirmForWrites: boolean;
166
166
  private readonly timeoutMs: number;
167
167
  private readonly fetchImpl: typeof fetch;
@@ -170,19 +170,19 @@ export class TopoloClient {
170
170
  private actionCatalogCache: { entries: TopoloActionCatalogEntry[]; expiresAt: number } | null = null;
171
171
 
172
172
  /**
173
- * Process-wide cache for slug → service-id lookups. Keyed by
173
+ * Process-wide cache for slug → app-id lookups. Keyed by
174
174
  * `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
175
175
  * the same Auth share, but staging vs prod don't poison each other.
176
- * Cleared via `TopoloClient.clearServiceIdCache()` if a test needs it.
176
+ * Cleared via `TopoloClient.clearAppIdCache()` if a test needs it.
177
177
  */
178
- private static readonly serviceIdCache = new Map<string, ServiceIdCacheEntry>();
178
+ private static readonly appIdCache = new Map<string, AppIdCacheEntry>();
179
179
 
180
180
  /**
181
- * Drop the slug→service-id cache. Test-only — production callers should
181
+ * Drop the slug→app-id cache. Test-only — production callers should
182
182
  * let the 5-minute TTL expire naturally.
183
183
  */
184
- static clearServiceIdCache(): void {
185
- TopoloClient.serviceIdCache.clear();
184
+ static clearAppIdCache(): void {
185
+ TopoloClient.appIdCache.clear();
186
186
  }
187
187
 
188
188
  constructor(options: TopoloClientOptions) {
@@ -191,7 +191,7 @@ export class TopoloClient {
191
191
  this.credential = options.credential;
192
192
  this.agent = options.agent;
193
193
  this.serviceUrls = options.serviceUrls;
194
- this.serviceIds = options.serviceIds;
194
+ this.appIds = options.appIds;
195
195
  this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
196
196
  this.timeoutMs = options.timeoutMs ?? 30_000;
197
197
  this.fetchImpl = options.fetch ?? fetch;
@@ -233,8 +233,8 @@ export class TopoloClient {
233
233
  const headers = new Headers();
234
234
  headers.set('Accept', 'application/json');
235
235
  if (opts.body !== undefined) headers.set('Content-Type', 'application/json');
236
- const platformServiceId = resolveConfiguredServiceId(resolvedService.serviceKey, this.serviceIds);
237
- if (platformServiceId) headers.set('X-Service-ID', platformServiceId);
236
+ const platformAppId = resolveConfiguredAppId(resolvedService.serviceKey, this.appIds);
237
+ if (platformAppId) headers.set('X-App-ID', platformAppId);
238
238
 
239
239
  applyAuthHeaders(headers, this.credential);
240
240
  const requestId = generateRequestId();
@@ -352,7 +352,7 @@ export class TopoloClient {
352
352
  const derived = deriveDevelopersFromAuth(resolveConfiguredServiceUrl('auth', this.serviceUrls));
353
353
  if (derived) return { serviceKey, baseUrl: derived };
354
354
  }
355
- return { serviceKey, baseUrl: DEFAULT_SERVICE_URLS[serviceKey as 'auth' | 'developers'] };
355
+ return { serviceKey, baseUrl: DEFAULT_APP_URLS[serviceKey as 'auth' | 'developers'] };
356
356
  }
357
357
 
358
358
  const live = await this.resolveLiveCallableService(serviceKey);
@@ -363,7 +363,7 @@ export class TopoloClient {
363
363
 
364
364
  throw new TopoloSdkError(
365
365
  'unknown_service',
366
- `Unknown Topolo service "${service}". Run service discovery first and pass a callable service id or slug.`,
366
+ `Unknown Topolo app API "${service}". Run app discovery first and pass a callable app id or slug.`,
367
367
  );
368
368
  }
369
369
 
@@ -379,7 +379,7 @@ export class TopoloClient {
379
379
  }
380
380
 
381
381
  /**
382
- * Resolve a service-slug → opaque service-id via caller config or TopoloAuth.
382
+ * Resolve a service-slug → opaque app-id via caller config or TopoloAuth.
383
383
  *
384
384
  * Cache: 5 minutes per process per `(slug, auth_base_url)`. Negative
385
385
  * results (404) are cached for 60s so a typo doesn't hot-loop the
@@ -390,19 +390,19 @@ export class TopoloClient {
390
390
  * This keeps the resolver usable during early bootstrap before a caller
391
391
  * has acquired an access token.
392
392
  */
393
- async getServiceIdBySlug(slug: string): Promise<string | null> {
393
+ async getAppIdBySlug(slug: string): Promise<string | null> {
394
394
  if (typeof slug !== 'string') return null;
395
395
  const normalized = slug.trim().toLowerCase();
396
396
  if (!normalized) return null;
397
397
 
398
- const configuredServiceId = resolveConfiguredServiceId(normalized, this.serviceIds);
399
- if (configuredServiceId) return configuredServiceId;
398
+ const configuredAppId = resolveConfiguredAppId(normalized, this.appIds);
399
+ if (configuredAppId) return configuredAppId;
400
400
 
401
401
  // app-catalog greenfield: by-slug resolver lives on the Catalog (developers).
402
402
  const authBase = resolveServiceUrl('developers', this.serviceUrls);
403
403
  const cacheKey = `${authBase}|${normalized}`;
404
404
  const now = Date.now();
405
- const cached = TopoloClient.serviceIdCache.get(cacheKey);
405
+ const cached = TopoloClient.appIdCache.get(cacheKey);
406
406
  if (cached && cached.expiresAt > now) {
407
407
  return cached.value;
408
408
  }
@@ -424,7 +424,7 @@ export class TopoloClient {
424
424
  }
425
425
 
426
426
  if (res.status === 404) {
427
- TopoloClient.serviceIdCache.set(cacheKey, {
427
+ TopoloClient.appIdCache.set(cacheKey, {
428
428
  value: null,
429
429
  expiresAt: now + NEGATIVE_CACHE_TTL_MS,
430
430
  });
@@ -436,11 +436,11 @@ export class TopoloClient {
436
436
  }
437
437
 
438
438
  const body = (await res.json().catch(() => null)) as
439
- | { success?: boolean; data?: { service_id?: unknown } }
439
+ | { success?: boolean; data?: { app_id?: unknown } }
440
440
  | null;
441
- const serviceId = body?.data?.service_id;
442
- const value = typeof serviceId === 'string' && serviceId ? serviceId : null;
443
- TopoloClient.serviceIdCache.set(cacheKey, {
441
+ const appId = body?.data?.app_id;
442
+ const value = typeof appId === 'string' && appId ? appId : null;
443
+ TopoloClient.appIdCache.set(cacheKey, {
444
444
  value,
445
445
  expiresAt: now + POSITIVE_CACHE_TTL_MS,
446
446
  });
@@ -483,13 +483,13 @@ export class TopoloClient {
483
483
  const entries: TopoloServiceCatalogEntry[] = [];
484
484
  for (const item of raw) {
485
485
  const s = item as Record<string, unknown>;
486
- const serviceId =
487
- typeof s.service_id === 'string'
488
- ? s.service_id
486
+ const appId =
487
+ typeof s.app_id === 'string'
488
+ ? s.app_id
489
489
  : typeof s.id === 'string'
490
490
  ? s.id
491
491
  : null;
492
- if (!serviceId) continue;
492
+ if (!appId) continue;
493
493
  const permissions = Array.isArray(s.granted_permissions)
494
494
  ? s.granted_permissions
495
495
  .filter((permission): permission is string => typeof permission === 'string' && permission.trim().length > 0)
@@ -507,7 +507,7 @@ export class TopoloClient {
507
507
  ? [apiKeyResource]
508
508
  : [];
509
509
  entries.push({
510
- serviceId,
510
+ appId,
511
511
  slug: typeof s.app_slug === 'string' ? s.app_slug : null,
512
512
  name: typeof s.name === 'string' ? s.name : null,
513
513
  description:
@@ -603,7 +603,7 @@ export class TopoloClient {
603
603
  ): Promise<T> {
604
604
  const entry = await this.getAction(action);
605
605
  const mapped = mapActionInput(entry, input);
606
- const service = entry.serviceSlug || entry.serviceId;
606
+ const service = entry.appSlug || entry.appId;
607
607
  return this.request<T>({
608
608
  service,
609
609
  method: entry.method,
@@ -629,7 +629,7 @@ export class TopoloClient {
629
629
  productionUrl?: string | null;
630
630
  }>;
631
631
  return apps.map((app) => ({
632
- serviceId: app.id,
632
+ appId: app.id,
633
633
  slug: app.id,
634
634
  name: app.name ?? app.id,
635
635
  description: null,
@@ -688,15 +688,15 @@ export class TopoloClient {
688
688
  }
689
689
  }
690
690
 
691
- function resolveConfiguredServiceId(
691
+ function resolveConfiguredAppId(
692
692
  slug: string,
693
- serviceIds?: Record<string, string>,
693
+ appIds?: Record<string, string>,
694
694
  ): string | null {
695
695
  for (const serviceKey of serviceKeyCandidates(slug)) {
696
- const configured = serviceIds?.[serviceKey as ServiceId];
696
+ const configured = appIds?.[serviceKey as AppApiId];
697
697
  if (typeof configured === 'string' && configured.trim()) return configured.trim();
698
698
 
699
- const envKey = `TOPOLO_SERVICE_ID_${serviceKey.replace(/\./g, '_').toUpperCase()}`;
699
+ const envKey = `TOPOLO_APP_ID_${serviceKey.replace(/\./g, '_').toUpperCase()}`;
700
700
  const envValue = typeof process !== 'undefined' ? process.env?.[envKey] : undefined;
701
701
  if (typeof envValue === 'string' && envValue.trim()) return envValue.trim();
702
702
  }
@@ -814,7 +814,7 @@ function catalogEntryMatches(entry: TopoloServiceCatalogEntry, needle: string):
814
814
  }
815
815
 
816
816
  function catalogEntryLookupKeys(entry: TopoloServiceCatalogEntry): string[] {
817
- const values = [entry.serviceId, entry.slug, entry.name].filter((value): value is string => (
817
+ const values = [entry.appId, entry.slug, entry.name].filter((value): value is string => (
818
818
  typeof value === 'string' && value.trim().length > 0
819
819
  ));
820
820
  const keys = new Set<string>();
@@ -833,10 +833,10 @@ function normalizeActionCatalogEntry(item: unknown): TopoloActionCatalogEntry |
833
833
  const a = item as Record<string, unknown>;
834
834
  const actionId = pickActionString(a.action_id) ?? pickActionString(a.actionId);
835
835
  const name = pickActionString(a.name);
836
- const serviceId = pickActionString(a.service_id) ?? pickActionString(a.serviceId);
836
+ const appId = pickActionString(a.app_id) ?? pickActionString(a.appId);
837
837
  const method = pickActionString(a.method)?.toUpperCase();
838
838
  const path = pickActionString(a.path);
839
- if (!actionId || !name || !serviceId || !method || !path || !isHttpMethod(method)) {
839
+ if (!actionId || !name || !appId || !method || !path || !isHttpMethod(method)) {
840
840
  return null;
841
841
  }
842
842
 
@@ -846,9 +846,9 @@ function normalizeActionCatalogEntry(item: unknown): TopoloActionCatalogEntry |
846
846
  toolName: pickActionString(a.tool_name) ?? pickActionString(a.toolName) ?? null,
847
847
  title: pickActionString(a.title) ?? name,
848
848
  description: pickActionString(a.description) ?? '',
849
- serviceId,
850
- serviceSlug: pickActionString(a.service_slug) ?? pickActionString(a.serviceSlug) ?? null,
851
- serviceName: pickActionString(a.service_name) ?? pickActionString(a.serviceName) ?? null,
849
+ appId,
850
+ appSlug: pickActionString(a.app_slug) ?? pickActionString(a.appSlug) ?? null,
851
+ appName: pickActionString(a.app_name) ?? pickActionString(a.appName) ?? null,
852
852
  method,
853
853
  path,
854
854
  permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? '',
package/src/index.test.ts CHANGED
@@ -29,7 +29,7 @@ describe('createTopolo', () => {
29
29
  const impl = vi.fn(async (url: string) => {
30
30
  calls.push(String(url));
31
31
  if (String(url).includes('/api/services/by-slug/')) {
32
- return new Response(JSON.stringify({ success: true, data: { service_id: 'service_auth' } }), {
32
+ return new Response(JSON.stringify({ success: true, data: { app_id: 'service_auth' } }), {
33
33
  status: 200,
34
34
  headers: { 'Content-Type': 'application/json' },
35
35
  });
@@ -46,12 +46,12 @@ describe('createTopolo', () => {
46
46
  );
47
47
  }) as unknown as typeof fetch;
48
48
 
49
- TopoloClient.clearServiceIdCache();
49
+ TopoloClient.clearAppIdCache();
50
50
  const topolo = createTopolo({
51
51
  credential: { kind: 'access_token', accessToken: 'tok' },
52
52
  agent: AGENT,
53
53
  fetch: impl,
54
- serviceIds: { auth: 'service_auth' },
54
+ appIds: { auth: 'service_auth' },
55
55
  });
56
56
 
57
57
  const me = await topolo.identity.whoami();