@topolo/sdk 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -125,16 +125,20 @@ export const APPLICATION_REQUIREMENTS = [
125
125
  'Browser applications should feel like one platform and should not create isolated navigation or account surfaces.',
126
126
  appliesTo: ['browser'],
127
127
  evidence: [
128
- '@topolo/ui-kit is used for the platform shell where the app has authenticated UI',
129
- 'The shared launcher/app switcher is reachable from authenticated app chrome',
130
- 'Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu',
128
+ '@topolo-io/app-shell is used for the platform shell where the app has authenticated UI',
129
+ '@topolo-io/app-launcher keeps the shared launcher/app switcher reachable from authenticated app chrome',
130
+ 'Account menu and sign-out behavior use TopoloAppShell or the ui-kit TopoloAccountMenu primitive',
131
+ 'Repeated app body surfaces use shared TopoloAppLayout primitives instead of local stat cards, empty states, or status badges',
132
+ 'Local preview supports deterministic visual QA fixtures for overview, list, detail, settings, and empty/loading states',
131
133
  'Authenticated app body content stays inside the viewport on mobile and desktop widths',
132
134
  ],
133
135
  implementation: [
134
- 'Use TopoloAppShell for authenticated app layouts',
135
- 'Use TopoloAccountMenu when a partial-shell surface still needs account chrome',
136
+ 'Use @topolo-io/app-shell TopoloAppShell for authenticated app layouts',
137
+ 'Use the @topolo-io/ui-kit TopoloAccountMenu primitive when a partial-shell surface still needs account chrome',
136
138
  'Pass app-specific account actions as additive menu items instead of forking dropdowns',
137
139
  'Use shared design tokens and platform UI primitives before creating local equivalents',
140
+ 'Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns',
141
+ 'Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state',
138
142
  'Keep mobile and desktop shell behavior aligned with the platform UI kit',
139
143
  'Constrain app-owned workspace wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow',
140
144
  ],
@@ -49,17 +49,15 @@ export const APPLICATIONS = {
49
49
  "agent.topolo.app"
50
50
  ],
51
51
  "routes": [],
52
- "serviceId": null
52
+ "serviceId": "agent"
53
53
  },
54
54
  {
55
55
  "name": "topolo-agent-api",
56
56
  "kind": "worker",
57
- "apiUrl": "https://agent-api.topolo.app",
58
- "domains": [
59
- "agent-api.topolo.app"
60
- ],
57
+ "apiUrl": null,
58
+ "domains": [],
61
59
  "routes": [],
62
- "serviceId": "agent"
60
+ "serviceId": null
63
61
  }
64
62
  ]
65
63
  },
@@ -179,17 +177,15 @@ export const APPLICATIONS = {
179
177
  "bytes.topolo.app"
180
178
  ],
181
179
  "routes": [],
182
- "serviceId": null
180
+ "serviceId": "bytes"
183
181
  },
184
182
  {
185
183
  "name": "topolo-bytes-api",
186
184
  "kind": "worker",
187
- "apiUrl": "https://bytes-api.topolo.app",
188
- "domains": [
189
- "bytes-api.topolo.app"
190
- ],
185
+ "apiUrl": null,
186
+ "domains": [],
191
187
  "routes": [],
192
- "serviceId": "bytes"
188
+ "serviceId": null
193
189
  }
194
190
  ]
195
191
  },
@@ -288,12 +284,10 @@ export const APPLICATIONS = {
288
284
  {
289
285
  "name": "topolo-commerce-api",
290
286
  "kind": "worker",
291
- "apiUrl": "https://commerce-api.topolo.app",
292
- "domains": [
293
- "commerce-api.topolo.app"
294
- ],
287
+ "apiUrl": null,
288
+ "domains": [],
295
289
  "routes": [],
296
- "serviceId": "commerce"
290
+ "serviceId": null
297
291
  },
298
292
  {
299
293
  "name": "topolo-commerce-guest-web",
@@ -313,7 +307,7 @@ export const APPLICATIONS = {
313
307
  "commerce.topolo.app"
314
308
  ],
315
309
  "routes": [],
316
- "serviceId": null
310
+ "serviceId": "commerce"
317
311
  },
318
312
  {
319
313
  "name": "topolo-commerce-station-web",
@@ -400,12 +394,10 @@ export const APPLICATIONS = {
400
394
  {
401
395
  "name": "topolo-crm-api",
402
396
  "kind": "worker",
403
- "apiUrl": "https://crm-api.topolo.app",
404
- "domains": [
405
- "crm-api.topolo.app"
406
- ],
397
+ "apiUrl": null,
398
+ "domains": [],
407
399
  "routes": [],
408
- "serviceId": "crm"
400
+ "serviceId": null
409
401
  },
410
402
  {
411
403
  "name": "topolo-crm-app",
@@ -415,7 +407,7 @@ export const APPLICATIONS = {
415
407
  "crm.topolo.app"
416
408
  ],
417
409
  "routes": [],
418
- "serviceId": null
410
+ "serviceId": "crm"
419
411
  }
420
412
  ]
421
413
  },
@@ -543,7 +535,7 @@ export const APPLICATIONS = {
543
535
  "feed-api.topolo.app"
544
536
  ],
545
537
  "routes": [],
546
- "serviceId": "feed"
538
+ "serviceId": null
547
539
  },
548
540
  {
549
541
  "name": "topolo-feed-web",
@@ -553,7 +545,7 @@ export const APPLICATIONS = {
553
545
  "feed.topolo.app"
554
546
  ],
555
547
  "routes": [],
556
- "serviceId": null
548
+ "serviceId": "feed"
557
549
  }
558
550
  ]
559
551
  },
@@ -605,17 +597,15 @@ export const APPLICATIONS = {
605
597
  "forecast.topolo.app"
606
598
  ],
607
599
  "routes": [],
608
- "serviceId": null
600
+ "serviceId": "forecast"
609
601
  },
610
602
  {
611
603
  "name": "topolo-forecast-api",
612
604
  "kind": "worker",
613
- "apiUrl": "https://forecast-api.topolo.app",
614
- "domains": [
615
- "forecast-api.topolo.app"
616
- ],
605
+ "apiUrl": null,
606
+ "domains": [],
617
607
  "routes": [],
618
- "serviceId": "forecast"
608
+ "serviceId": null
619
609
  }
620
610
  ]
621
611
  },
@@ -757,17 +747,15 @@ export const APPLICATIONS = {
757
747
  "learn.topolo.app"
758
748
  ],
759
749
  "routes": [],
760
- "serviceId": null
750
+ "serviceId": "learn"
761
751
  },
762
752
  {
763
753
  "name": "topolo-learn-api",
764
754
  "kind": "worker",
765
- "apiUrl": "https://learn-api.topolo.app",
766
- "domains": [
767
- "learn-api.topolo.app"
768
- ],
755
+ "apiUrl": null,
756
+ "domains": [],
769
757
  "routes": [],
770
- "serviceId": "learn"
758
+ "serviceId": null
771
759
  }
772
760
  ]
773
761
  },
@@ -813,17 +801,15 @@ export const APPLICATIONS = {
813
801
  "mail.topolo.app"
814
802
  ],
815
803
  "routes": [],
816
- "serviceId": null
804
+ "serviceId": "mail"
817
805
  },
818
806
  {
819
807
  "name": "topolo-mail-api",
820
808
  "kind": "worker",
821
- "apiUrl": "https://mail-api.topolo.app",
822
- "domains": [
823
- "mail-api.topolo.app"
824
- ],
809
+ "apiUrl": null,
810
+ "domains": [],
825
811
  "routes": [],
826
- "serviceId": "mail"
812
+ "serviceId": null
827
813
  }
828
814
  ]
829
815
  },
@@ -840,12 +826,10 @@ export const APPLICATIONS = {
840
826
  {
841
827
  "name": "topolo-mdm-api",
842
828
  "kind": "worker",
843
- "apiUrl": "https://mdm-api.topolo.app",
844
- "domains": [
845
- "mdm-api.topolo.app"
846
- ],
829
+ "apiUrl": null,
830
+ "domains": [],
847
831
  "routes": [],
848
- "serviceId": "mdm"
832
+ "serviceId": null
849
833
  },
850
834
  {
851
835
  "name": "topolo-mdm-console",
@@ -855,7 +839,7 @@ export const APPLICATIONS = {
855
839
  "mdm.topolo.app"
856
840
  ],
857
841
  "routes": [],
858
- "serviceId": null
842
+ "serviceId": "mdm"
859
843
  }
860
844
  ]
861
845
  },
@@ -920,9 +904,9 @@ export const APPLICATIONS = {
920
904
  {
921
905
  "name": "topolo-nexus-gateway",
922
906
  "kind": "worker",
923
- "apiUrl": "https://nexus-api.topolo.app",
907
+ "apiUrl": "https://nexus.topolo.app",
924
908
  "domains": [
925
- "nexus-api.topolo.app"
909
+ "nexus.topolo.app"
926
910
  ],
927
911
  "routes": [],
928
912
  "serviceId": "nexus"
@@ -1114,36 +1098,6 @@ export const APPLICATIONS = {
1114
1098
  }
1115
1099
  ]
1116
1100
  },
1117
- "pulse": {
1118
- "id": "pulse",
1119
- "appDir": "TopoloPulse",
1120
- "name": "topolo-pulse",
1121
- "packageName": "topolo-pulse",
1122
- "productionUrl": "https://pulse.topolo.app",
1123
- "services": [
1124
- "pulse"
1125
- ],
1126
- "deployTargets": [
1127
- {
1128
- "name": "topolo-pulse",
1129
- "kind": "worker",
1130
- "apiUrl": "https://pulse.topolo.app",
1131
- "domains": [
1132
- "pulse.topolo.app"
1133
- ],
1134
- "routes": [],
1135
- "serviceId": "pulse"
1136
- },
1137
- {
1138
- "name": "topolo-pulse-api",
1139
- "kind": "worker",
1140
- "apiUrl": null,
1141
- "domains": [],
1142
- "routes": [],
1143
- "serviceId": null
1144
- }
1145
- ]
1146
- },
1147
1101
  "quro": {
1148
1102
  "id": "quro",
1149
1103
  "appDir": "TopoloQuro",
@@ -1209,17 +1163,15 @@ export const APPLICATIONS = {
1209
1163
  "roadmapper.topolo.app"
1210
1164
  ],
1211
1165
  "routes": [],
1212
- "serviceId": null
1166
+ "serviceId": "roadmapper"
1213
1167
  },
1214
1168
  {
1215
1169
  "name": "topolo-roadmapper-api",
1216
1170
  "kind": "worker",
1217
- "apiUrl": "https://roadmapper-api.topolo.app",
1218
- "domains": [
1219
- "roadmapper-api.topolo.app"
1220
- ],
1171
+ "apiUrl": null,
1172
+ "domains": [],
1221
1173
  "routes": [],
1222
- "serviceId": "roadmapper"
1174
+ "serviceId": null
1223
1175
  }
1224
1176
  ]
1225
1177
  },
@@ -1271,17 +1223,15 @@ export const APPLICATIONS = {
1271
1223
  "studio.topolo.app"
1272
1224
  ],
1273
1225
  "routes": [],
1274
- "serviceId": null
1226
+ "serviceId": "social_studio"
1275
1227
  },
1276
1228
  {
1277
1229
  "name": "topolo-social-studio-api",
1278
1230
  "kind": "worker",
1279
- "apiUrl": "https://studio-api.topolo.app",
1280
- "domains": [
1281
- "studio-api.topolo.app"
1282
- ],
1231
+ "apiUrl": null,
1232
+ "domains": [],
1283
1233
  "routes": [],
1284
- "serviceId": "social_studio"
1234
+ "serviceId": null
1285
1235
  },
1286
1236
  {
1287
1237
  "name": "topolo-social-studio-queue",
@@ -1311,7 +1261,7 @@ export const APPLICATIONS = {
1311
1261
  "socialize.topolo.app"
1312
1262
  ],
1313
1263
  "routes": [],
1314
- "serviceId": null
1264
+ "serviceId": "socialize"
1315
1265
  },
1316
1266
  {
1317
1267
  "name": "topolo-socialize-api",
@@ -1321,7 +1271,7 @@ export const APPLICATIONS = {
1321
1271
  "socialize-api.topolo.app"
1322
1272
  ],
1323
1273
  "routes": [],
1324
- "serviceId": "socialize"
1274
+ "serviceId": null
1325
1275
  },
1326
1276
  {
1327
1277
  "name": "topolo-socialize-scheduler",
@@ -1522,12 +1472,10 @@ export const APPLICATIONS = {
1522
1472
  {
1523
1473
  "name": "topolo-web-api",
1524
1474
  "kind": "worker",
1525
- "apiUrl": "https://web-api.topolo.app",
1526
- "domains": [
1527
- "web-api.topolo.app"
1528
- ],
1475
+ "apiUrl": null,
1476
+ "domains": [],
1529
1477
  "routes": [],
1530
- "serviceId": "web"
1478
+ "serviceId": null
1531
1479
  },
1532
1480
  {
1533
1481
  "name": "topolo-web-runtime",
@@ -1550,7 +1498,7 @@ export const APPLICATIONS = {
1550
1498
  "web.topolo.app"
1551
1499
  ],
1552
1500
  "routes": [],
1553
- "serviceId": null
1501
+ "serviceId": "web"
1554
1502
  }
1555
1503
  ]
1556
1504
  }
@@ -143,6 +143,40 @@ describe('TopoloClient.request', () => {
143
143
  expect(headers.get('Authorization')).toBeNull();
144
144
  });
145
145
 
146
+ it('adds canonical resource headers for resource-scoped API keys', async () => {
147
+ const { impl, calls } = stubFetch([MAIL_CATALOG, { body: { ok: true } }]);
148
+ const client = new TopoloClient({
149
+ credential: { kind: 'api_key', apiKey: 'dak_xyz' },
150
+ agent: AGENT,
151
+ fetch: impl,
152
+ });
153
+ await client.request({
154
+ service: 'mail',
155
+ path: '/api/mail/mailboxes/mbx_1/messages',
156
+ resource: { resourceType: 'mailbox', resourceId: 'mbx_1' },
157
+ });
158
+ const headers = new Headers(requestCalls(calls)[0]!.init.headers);
159
+ expect(headers.get('X-Topolo-Resource-Type')).toBe('mailbox');
160
+ expect(headers.get('X-Topolo-Resource-ID')).toBe('mbx_1');
161
+ });
162
+
163
+ it('passes arbitrary cataloged workspace aliases through resource headers', async () => {
164
+ const { impl, calls } = stubFetch([MAIL_CATALOG, { body: { ok: true } }]);
165
+ const client = new TopoloClient({
166
+ credential: { kind: 'api_key', apiKey: 'dak_xyz' },
167
+ agent: AGENT,
168
+ fetch: impl,
169
+ });
170
+ await client.request({
171
+ service: 'mail',
172
+ path: '/api/example',
173
+ resource: { resourceType: 'studio_portfolio', resourceId: 'portfolio_1' },
174
+ });
175
+ const headers = new Headers(requestCalls(calls)[0]!.init.headers);
176
+ expect(headers.get('X-Topolo-Resource-Type')).toBe('studio_portfolio');
177
+ expect(headers.get('X-Topolo-Resource-ID')).toBe('portfolio_1');
178
+ });
179
+
146
180
  it('refuses mutating requests without confirm', async () => {
147
181
  const { impl } = stubFetch([]);
148
182
  const client = new TopoloClient({
@@ -367,7 +401,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
367
401
  credential: { kind: 'api_key', apiKey: 'x' },
368
402
  agent: AGENT,
369
403
  fetch: impl,
370
- serviceUrls: { auth: 'https://auth.example.com' } as never,
404
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' } as never,
371
405
  serviceIds: { notify: 'service_notify' },
372
406
  });
373
407
  const id = await client.getServiceIdBySlug('topolo-notify');
@@ -379,7 +413,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
379
413
  const { impl, calls } = stubFetch([
380
414
  { body: { success: true, data: { service_id: 'service_abc123', api_base_url: 'https://x-api' } } },
381
415
  ]);
382
- const client = freshClient(impl, { auth: 'https://auth.example.com' });
416
+ const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
383
417
  const id = await client.getServiceIdBySlug('external-service');
384
418
  expect(id).toBe('service_abc123');
385
419
  expect(calls[0]!.url).toBe('https://auth.example.com/api/services/by-slug/external-service');
@@ -391,7 +425,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
391
425
 
392
426
  it('returns null on 404 and caches the negative result', async () => {
393
427
  const { impl, calls } = stubFetch([{ status: 404, body: { success: false } }]);
394
- const client = freshClient(impl, { auth: 'https://auth.example.com' });
428
+ const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
395
429
  expect(await client.getServiceIdBySlug('nope')).toBeNull();
396
430
  expect(await client.getServiceIdBySlug('nope')).toBeNull();
397
431
  expect(calls.length).toBe(1);
@@ -401,7 +435,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
401
435
  const { impl, calls } = stubFetch([
402
436
  { body: { success: true, data: { service_id: 'service_external' } } },
403
437
  ]);
404
- const client = freshClient(impl, { auth: 'https://auth.example.com' });
438
+ const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
405
439
  expect(await client.getServiceIdBySlug('external-service')).toBe('service_external');
406
440
  expect(await client.getServiceIdBySlug('external-service')).toBe('service_external');
407
441
  expect(calls.length).toBe(1);
@@ -411,7 +445,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
411
445
  const { impl, calls } = stubFetch([
412
446
  { body: { success: true, data: { service_id: 'service_x' } } },
413
447
  ]);
414
- const client = freshClient(impl, { auth: 'https://auth.example.com' });
448
+ const client = freshClient(impl, { auth: 'https://auth.example.com', developers: 'https://auth.example.com' });
415
449
  await client.getServiceIdBySlug(' EXTERNAL-SERVICE ');
416
450
  expect(calls[0]!.url).toBe('https://auth.example.com/api/services/by-slug/external-service');
417
451
  });
@@ -433,7 +467,7 @@ describe('TopoloClient.getServiceIdBySlug', () => {
433
467
  credential: { kind: 'api_key', apiKey: 'x' },
434
468
  agent: AGENT,
435
469
  fetch: impl,
436
- serviceUrls: { auth: 'https://auth.example.com' } as never,
470
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' } as never,
437
471
  });
438
472
  expect(await client.getServiceIdBySlug('flaky')).toBeNull();
439
473
  // Second call retries — earlier null was not cached.
@@ -458,6 +492,26 @@ describe('TopoloClient.listServices', () => {
458
492
  status: 'active',
459
493
  surface_kind: 'application',
460
494
  launchable: true,
495
+ api_key_resource: {
496
+ resourceType: 'brand',
497
+ label: 'Brands',
498
+ emptyLabel: 'All brands in the organization',
499
+ resourceConcept: 'workspace',
500
+ canonicalResourceType: 'workspace',
501
+ defaultEntitlement: 'enterprise',
502
+ exceptionPolicy: 'app_or_organization',
503
+ },
504
+ api_key_resources: [
505
+ {
506
+ resourceType: 'brand',
507
+ label: 'Brands',
508
+ emptyLabel: 'All brands in the organization',
509
+ resourceConcept: 'workspace',
510
+ canonicalResourceType: 'workspace',
511
+ defaultEntitlement: 'enterprise',
512
+ exceptionPolicy: 'app_or_organization',
513
+ },
514
+ ],
461
515
  granted_permissions: ['srv_wRCnLLbtvmVT.dashboard:read'],
462
516
  },
463
517
  ],
@@ -469,7 +523,7 @@ describe('TopoloClient.listServices', () => {
469
523
  credential: { kind: 'access_token', accessToken: 'tok_abc' },
470
524
  agent: AGENT,
471
525
  fetch: impl,
472
- serviceUrls: { auth: 'https://auth.example.com' },
526
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
473
527
  });
474
528
  const out = await client.listServices();
475
529
  expect(out).toEqual([
@@ -483,6 +537,26 @@ describe('TopoloClient.listServices', () => {
483
537
  status: 'active',
484
538
  surfaceKind: 'application',
485
539
  launchable: true,
540
+ apiKeyResource: {
541
+ resourceType: 'brand',
542
+ label: 'Brands',
543
+ emptyLabel: 'All brands in the organization',
544
+ resourceConcept: 'workspace',
545
+ canonicalResourceType: 'workspace',
546
+ defaultEntitlement: 'enterprise',
547
+ exceptionPolicy: 'app_or_organization',
548
+ },
549
+ apiKeyResources: [
550
+ {
551
+ resourceType: 'brand',
552
+ label: 'Brands',
553
+ emptyLabel: 'All brands in the organization',
554
+ resourceConcept: 'workspace',
555
+ canonicalResourceType: 'workspace',
556
+ defaultEntitlement: 'enterprise',
557
+ exceptionPolicy: 'app_or_organization',
558
+ },
559
+ ],
486
560
  permissions: ['srv_wRCnLLbtvmVT.dashboard:read'],
487
561
  },
488
562
  ]);
@@ -500,7 +574,7 @@ describe('TopoloClient.listServices', () => {
500
574
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
501
575
  agent: AGENT,
502
576
  fetch: impl,
503
- serviceUrls: { auth: 'https://auth.example.com' },
577
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
504
578
  });
505
579
  await expect(client.listServices()).rejects.toBeInstanceOf(TopoloHttpError);
506
580
  });
@@ -531,7 +605,7 @@ describe('TopoloClient.listServices', () => {
531
605
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
532
606
  agent: AGENT,
533
607
  fetch: impl,
534
- serviceUrls: { auth: 'https://auth.example.com' },
608
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
535
609
  });
536
610
 
537
611
  await client.request({ service: 'mail', path: '/api/health' });
@@ -566,7 +640,7 @@ describe('TopoloClient.listServices', () => {
566
640
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
567
641
  agent: AGENT,
568
642
  fetch: impl,
569
- serviceUrls: { auth: 'https://auth.example.com' },
643
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
570
644
  });
571
645
 
572
646
  await client.request({ service: 'crm', path: '/health' });
@@ -575,6 +649,21 @@ describe('TopoloClient.listServices', () => {
575
649
  expect(calls[1]!.url).toBe('https://crm-api.topolo.app/health');
576
650
  });
577
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
+
578
667
  it('prefers live catalog api_base_url for dynamic services', async () => {
579
668
  const { impl, calls } = stubFetch([
580
669
  {
@@ -601,7 +690,7 @@ describe('TopoloClient.listServices', () => {
601
690
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
602
691
  agent: AGENT,
603
692
  fetch: impl,
604
- serviceUrls: { auth: 'https://auth.example.com' },
693
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
605
694
  });
606
695
 
607
696
  await client.request({ service: 'future', path: '/health' });
@@ -634,7 +723,7 @@ describe('TopoloClient.listServices', () => {
634
723
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
635
724
  agent: AGENT,
636
725
  fetch: impl,
637
- serviceUrls: { auth: 'https://auth.example.com' },
726
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
638
727
  });
639
728
 
640
729
  await expect(client.request({ service: 'mail', path: '/api/health' })).rejects.toMatchObject({
@@ -668,7 +757,7 @@ describe('TopoloClient.listServices', () => {
668
757
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
669
758
  agent: AGENT,
670
759
  fetch: impl,
671
- serviceUrls: { auth: 'https://auth.example.com' },
760
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
672
761
  });
673
762
 
674
763
  await expect(client.request({ service: 'future-app', path: '/api/health' })).rejects.toMatchObject({
@@ -703,7 +792,7 @@ describe('TopoloClient.listServices', () => {
703
792
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
704
793
  agent: AGENT,
705
794
  fetch: impl,
706
- serviceUrls: { auth: 'https://auth.example.com' },
795
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
707
796
  });
708
797
 
709
798
  await client.request({ service: 'warehouse', path: '/api/one' });
@@ -724,7 +813,7 @@ describe('TopoloClient action catalog', () => {
724
813
  credential: { kind: 'access_token', accessToken: 'tok_abc' },
725
814
  agent: AGENT,
726
815
  fetch: impl,
727
- serviceUrls: { auth: 'https://auth.example.com' },
816
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
728
817
  });
729
818
  const actions = await client.listActions();
730
819
  expect(actions.map((action) => action.name)).toEqual([
@@ -741,7 +830,7 @@ describe('TopoloClient action catalog', () => {
741
830
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
742
831
  agent: AGENT,
743
832
  fetch: impl,
744
- serviceUrls: { auth: 'https://auth.example.com' },
833
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
745
834
  });
746
835
  await client.callAction('messages.list', { mailboxId: 'primary', q: 'Ada', page: 2 });
747
836
  const call = requestCalls(calls)[0]!;
@@ -759,7 +848,7 @@ describe('TopoloClient action catalog', () => {
759
848
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
760
849
  agent: AGENT,
761
850
  fetch: impl,
762
- serviceUrls: { auth: 'https://auth.example.com' },
851
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
763
852
  });
764
853
  await client.callAction('topolo_mail_messages_get', {
765
854
  mailboxId: 'primary',
@@ -779,7 +868,7 @@ describe('TopoloClient action catalog', () => {
779
868
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
780
869
  agent: AGENT,
781
870
  fetch: impl,
782
- serviceUrls: { auth: 'https://auth.example.com' },
871
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
783
872
  });
784
873
  await expect(
785
874
  client.callAction('messages.send', { mailboxId: 'primary', subject: 'Hello' }),
@@ -792,7 +881,7 @@ describe('TopoloClient action catalog', () => {
792
881
  credential: { kind: 'api_key', apiKey: 'dak_xyz' },
793
882
  agent: AGENT,
794
883
  fetch: impl,
795
- serviceUrls: { auth: 'https://auth.example.com' },
884
+ serviceUrls: { auth: 'https://auth.example.com', developers: 'https://auth.example.com' },
796
885
  });
797
886
  await client.callAction('messages.send', { mailboxId: 'primary', subject: 'Hello' }, { confirm: true });
798
887
  const call = requestCalls(calls)[0]!;