@topolo/sdk 0.4.7 → 0.4.8

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.
@@ -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
  },
@@ -538,12 +530,10 @@ export const APPLICATIONS = {
538
530
  {
539
531
  "name": "topolo-feed-api",
540
532
  "kind": "worker",
541
- "apiUrl": "https://feed-api.topolo.app",
542
- "domains": [
543
- "feed-api.topolo.app"
544
- ],
533
+ "apiUrl": null,
534
+ "domains": [],
545
535
  "routes": [],
546
- "serviceId": "feed"
536
+ "serviceId": null
547
537
  },
548
538
  {
549
539
  "name": "topolo-feed-web",
@@ -553,7 +543,7 @@ export const APPLICATIONS = {
553
543
  "feed.topolo.app"
554
544
  ],
555
545
  "routes": [],
556
- "serviceId": null
546
+ "serviceId": "feed"
557
547
  }
558
548
  ]
559
549
  },
@@ -605,17 +595,15 @@ export const APPLICATIONS = {
605
595
  "forecast.topolo.app"
606
596
  ],
607
597
  "routes": [],
608
- "serviceId": null
598
+ "serviceId": "forecast"
609
599
  },
610
600
  {
611
601
  "name": "topolo-forecast-api",
612
602
  "kind": "worker",
613
- "apiUrl": "https://forecast-api.topolo.app",
614
- "domains": [
615
- "forecast-api.topolo.app"
616
- ],
603
+ "apiUrl": null,
604
+ "domains": [],
617
605
  "routes": [],
618
- "serviceId": "forecast"
606
+ "serviceId": null
619
607
  }
620
608
  ]
621
609
  },
@@ -757,17 +745,15 @@ export const APPLICATIONS = {
757
745
  "learn.topolo.app"
758
746
  ],
759
747
  "routes": [],
760
- "serviceId": null
748
+ "serviceId": "learn"
761
749
  },
762
750
  {
763
751
  "name": "topolo-learn-api",
764
752
  "kind": "worker",
765
- "apiUrl": "https://learn-api.topolo.app",
766
- "domains": [
767
- "learn-api.topolo.app"
768
- ],
753
+ "apiUrl": null,
754
+ "domains": [],
769
755
  "routes": [],
770
- "serviceId": "learn"
756
+ "serviceId": null
771
757
  }
772
758
  ]
773
759
  },
@@ -813,17 +799,15 @@ export const APPLICATIONS = {
813
799
  "mail.topolo.app"
814
800
  ],
815
801
  "routes": [],
816
- "serviceId": null
802
+ "serviceId": "mail"
817
803
  },
818
804
  {
819
805
  "name": "topolo-mail-api",
820
806
  "kind": "worker",
821
- "apiUrl": "https://mail-api.topolo.app",
822
- "domains": [
823
- "mail-api.topolo.app"
824
- ],
807
+ "apiUrl": null,
808
+ "domains": [],
825
809
  "routes": [],
826
- "serviceId": "mail"
810
+ "serviceId": null
827
811
  }
828
812
  ]
829
813
  },
@@ -840,12 +824,10 @@ export const APPLICATIONS = {
840
824
  {
841
825
  "name": "topolo-mdm-api",
842
826
  "kind": "worker",
843
- "apiUrl": "https://mdm-api.topolo.app",
844
- "domains": [
845
- "mdm-api.topolo.app"
846
- ],
827
+ "apiUrl": null,
828
+ "domains": [],
847
829
  "routes": [],
848
- "serviceId": "mdm"
830
+ "serviceId": null
849
831
  },
850
832
  {
851
833
  "name": "topolo-mdm-console",
@@ -855,7 +837,7 @@ export const APPLICATIONS = {
855
837
  "mdm.topolo.app"
856
838
  ],
857
839
  "routes": [],
858
- "serviceId": null
840
+ "serviceId": "mdm"
859
841
  }
860
842
  ]
861
843
  },
@@ -1114,36 +1096,6 @@ export const APPLICATIONS = {
1114
1096
  }
1115
1097
  ]
1116
1098
  },
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
1099
  "quro": {
1148
1100
  "id": "quro",
1149
1101
  "appDir": "TopoloQuro",
@@ -1209,17 +1161,15 @@ export const APPLICATIONS = {
1209
1161
  "roadmapper.topolo.app"
1210
1162
  ],
1211
1163
  "routes": [],
1212
- "serviceId": null
1164
+ "serviceId": "roadmapper"
1213
1165
  },
1214
1166
  {
1215
1167
  "name": "topolo-roadmapper-api",
1216
1168
  "kind": "worker",
1217
- "apiUrl": "https://roadmapper-api.topolo.app",
1218
- "domains": [
1219
- "roadmapper-api.topolo.app"
1220
- ],
1169
+ "apiUrl": null,
1170
+ "domains": [],
1221
1171
  "routes": [],
1222
- "serviceId": "roadmapper"
1172
+ "serviceId": null
1223
1173
  }
1224
1174
  ]
1225
1175
  },
@@ -1271,17 +1221,15 @@ export const APPLICATIONS = {
1271
1221
  "studio.topolo.app"
1272
1222
  ],
1273
1223
  "routes": [],
1274
- "serviceId": null
1224
+ "serviceId": "social_studio"
1275
1225
  },
1276
1226
  {
1277
1227
  "name": "topolo-social-studio-api",
1278
1228
  "kind": "worker",
1279
- "apiUrl": "https://studio-api.topolo.app",
1280
- "domains": [
1281
- "studio-api.topolo.app"
1282
- ],
1229
+ "apiUrl": null,
1230
+ "domains": [],
1283
1231
  "routes": [],
1284
- "serviceId": "social_studio"
1232
+ "serviceId": null
1285
1233
  },
1286
1234
  {
1287
1235
  "name": "topolo-social-studio-queue",
@@ -1311,17 +1259,15 @@ export const APPLICATIONS = {
1311
1259
  "socialize.topolo.app"
1312
1260
  ],
1313
1261
  "routes": [],
1314
- "serviceId": null
1262
+ "serviceId": "socialize"
1315
1263
  },
1316
1264
  {
1317
1265
  "name": "topolo-socialize-api",
1318
1266
  "kind": "worker",
1319
- "apiUrl": "https://socialize-api.topolo.app",
1320
- "domains": [
1321
- "socialize-api.topolo.app"
1322
- ],
1267
+ "apiUrl": null,
1268
+ "domains": [],
1323
1269
  "routes": [],
1324
- "serviceId": "socialize"
1270
+ "serviceId": null
1325
1271
  },
1326
1272
  {
1327
1273
  "name": "topolo-socialize-scheduler",
@@ -1522,12 +1468,10 @@ export const APPLICATIONS = {
1522
1468
  {
1523
1469
  "name": "topolo-web-api",
1524
1470
  "kind": "worker",
1525
- "apiUrl": "https://web-api.topolo.app",
1526
- "domains": [
1527
- "web-api.topolo.app"
1528
- ],
1471
+ "apiUrl": null,
1472
+ "domains": [],
1529
1473
  "routes": [],
1530
- "serviceId": "web"
1474
+ "serviceId": null
1531
1475
  },
1532
1476
  {
1533
1477
  "name": "topolo-web-runtime",
@@ -1550,7 +1494,7 @@ export const APPLICATIONS = {
1550
1494
  "web.topolo.app"
1551
1495
  ],
1552
1496
  "routes": [],
1553
- "serviceId": null
1497
+ "serviceId": "web"
1554
1498
  }
1555
1499
  ]
1556
1500
  }
@@ -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({
@@ -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
  ],
@@ -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
  ]);
package/src/client.ts CHANGED
@@ -76,6 +76,11 @@ export interface RequestOptions {
76
76
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
77
77
  query?: Record<string, string | number | boolean | undefined>;
78
78
  body?: unknown;
79
+ /** Workspace-boundary resource context for resource-scoped API keys. */
80
+ resource?: {
81
+ resourceType: string;
82
+ resourceId: string;
83
+ };
79
84
  /** Explicit write-acknowledgement for mutating calls. */
80
85
  confirm?: boolean;
81
86
  /** Extra headers appended after auth/audit headers. */
@@ -86,6 +91,8 @@ export interface RequestOptions {
86
91
  const WRITE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
87
92
  const TOPOLO_ACTION_ID_HEADER = 'X-Topolo-Action-Id';
88
93
  const TOPOLO_ACTION_NAME_HEADER = 'X-Topolo-Action-Name';
94
+ const TOPOLO_RESOURCE_TYPE_HEADER = 'X-Topolo-Resource-Type';
95
+ const TOPOLO_RESOURCE_ID_HEADER = 'X-Topolo-Resource-ID';
89
96
 
90
97
  const POSITIVE_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
91
98
  const NEGATIVE_CACHE_TTL_MS = 60 * 1000; // 1 minute
@@ -112,6 +119,18 @@ export interface TopoloServiceCatalogEntry {
112
119
  surfaceKind: string | null;
113
120
  launchable: boolean;
114
121
  permissions: string[];
122
+ apiKeyResource: TopoloApiKeyResourceMetadata | null;
123
+ apiKeyResources: TopoloApiKeyResourceMetadata[];
124
+ }
125
+
126
+ export interface TopoloApiKeyResourceMetadata {
127
+ resourceType: string;
128
+ label: string;
129
+ emptyLabel: string;
130
+ resourceConcept: 'workspace';
131
+ canonicalResourceType: 'workspace';
132
+ defaultEntitlement: 'enterprise';
133
+ exceptionPolicy: 'app_or_organization';
115
134
  }
116
135
 
117
136
  export interface TopoloActionCatalogEntry {
@@ -222,6 +241,18 @@ export class TopoloClient {
222
241
  if (opts.headers) {
223
242
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
224
243
  }
244
+ if (opts.resource) {
245
+ const resourceType = opts.resource.resourceType.trim();
246
+ const resourceId = opts.resource.resourceId.trim();
247
+ if (!resourceType || !resourceId) {
248
+ throw new TopoloSdkError(
249
+ 'invalid_resource_context',
250
+ 'Both resource.resourceType and resource.resourceId are required when resource context is provided.',
251
+ );
252
+ }
253
+ headers.set(TOPOLO_RESOURCE_TYPE_HEADER, resourceType);
254
+ headers.set(TOPOLO_RESOURCE_ID_HEADER, resourceId);
255
+ }
225
256
 
226
257
  const controller = new AbortController();
227
258
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
@@ -447,6 +478,18 @@ export class TopoloClient {
447
478
  ? s.granted_permissions
448
479
  .filter((permission): permission is string => typeof permission === 'string' && permission.trim().length > 0)
449
480
  : [];
481
+ const catalogApiKeyResources = normalizeApiKeyResourceMetadataList(
482
+ s.api_key_resources ?? s.apiKeyResources,
483
+ );
484
+ const apiKeyResource =
485
+ normalizeApiKeyResourceMetadata(s.api_key_resource ?? s.apiKeyResource) ??
486
+ catalogApiKeyResources[0] ??
487
+ null;
488
+ const apiKeyResources = catalogApiKeyResources.length > 0
489
+ ? catalogApiKeyResources
490
+ : apiKeyResource
491
+ ? [apiKeyResource]
492
+ : [];
450
493
  entries.push({
451
494
  serviceId,
452
495
  slug: typeof s.app_slug === 'string' ? s.app_slug : null,
@@ -470,6 +513,8 @@ export class TopoloClient {
470
513
  typeof s.surface_kind === 'string' ? s.surface_kind : null,
471
514
  launchable: s.launchable === true,
472
515
  permissions,
516
+ apiKeyResource,
517
+ apiKeyResources,
473
518
  });
474
519
  }
475
520
  this.serviceCatalogCache = {
@@ -577,6 +622,8 @@ export class TopoloClient {
577
622
  surfaceKind: 'application',
578
623
  launchable: Boolean(app.productionUrl),
579
624
  permissions: [],
625
+ apiKeyResource: null,
626
+ apiKeyResources: [],
580
627
  }));
581
628
  }
582
629
 
@@ -640,6 +687,60 @@ function resolveConfiguredServiceId(
640
687
  return null;
641
688
  }
642
689
 
690
+ function normalizeApiKeyResourceMetadata(input: unknown): TopoloApiKeyResourceMetadata | null {
691
+ if (!isRecord(input)) return null;
692
+ const resourceType = readString(input.resourceType ?? input.resource_type);
693
+ const label = readString(input.label);
694
+ const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
695
+ const resourceConcept = readString(input.resourceConcept ?? input.resource_concept);
696
+ const canonicalResourceType = readString(input.canonicalResourceType ?? input.canonical_resource_type);
697
+ const defaultEntitlement = readString(input.defaultEntitlement ?? input.default_entitlement);
698
+ const exceptionPolicy = readString(input.exceptionPolicy ?? input.exception_policy);
699
+
700
+ if (
701
+ !resourceType ||
702
+ !label ||
703
+ !emptyLabel ||
704
+ resourceConcept !== 'workspace' ||
705
+ canonicalResourceType !== 'workspace' ||
706
+ defaultEntitlement !== 'enterprise' ||
707
+ exceptionPolicy !== 'app_or_organization'
708
+ ) {
709
+ return null;
710
+ }
711
+
712
+ return {
713
+ resourceType,
714
+ label,
715
+ emptyLabel,
716
+ resourceConcept,
717
+ canonicalResourceType,
718
+ defaultEntitlement,
719
+ exceptionPolicy,
720
+ };
721
+ }
722
+
723
+ function normalizeApiKeyResourceMetadataList(input: unknown): TopoloApiKeyResourceMetadata[] {
724
+ if (!Array.isArray(input)) return [];
725
+ const resources: TopoloApiKeyResourceMetadata[] = [];
726
+ const seenTypes = new Set<string>();
727
+ for (const item of input) {
728
+ const resource = normalizeApiKeyResourceMetadata(item);
729
+ if (!resource || seenTypes.has(resource.resourceType)) continue;
730
+ seenTypes.add(resource.resourceType);
731
+ resources.push(resource);
732
+ }
733
+ return resources;
734
+ }
735
+
736
+ function readString(value: unknown): string {
737
+ return typeof value === 'string' ? value.trim() : '';
738
+ }
739
+
740
+ function isRecord(value: unknown): value is Record<string, unknown> {
741
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
742
+ }
743
+
643
744
  function resolveConfiguredServiceUrl(
644
745
  slug: string,
645
746
  serviceUrls?: Record<string, string>,
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import {
3
+ createTopolo,
4
+ CrmModule,
5
+ IdentityModule,
6
+ TopoloClient,
7
+ TopoloAuthError,
8
+ TopoloHttpError,
9
+ TopoloPermissionError,
10
+ } from './index.js';
11
+
12
+ const AGENT = { clientName: 'topolo-sdk-test', clientVersion: '0.0.0' };
13
+
14
+ describe('createTopolo', () => {
15
+ it('returns a ready client plus identity + crm module surfaces', () => {
16
+ const topolo = createTopolo({
17
+ credential: { kind: 'access_token', accessToken: 'tok' },
18
+ agent: AGENT,
19
+ fetch: vi.fn() as unknown as typeof fetch,
20
+ });
21
+
22
+ expect(topolo.client).toBeInstanceOf(TopoloClient);
23
+ expect(topolo.identity).toBeInstanceOf(IdentityModule);
24
+ expect(topolo.crm).toBeInstanceOf(CrmModule);
25
+ });
26
+
27
+ it('wires every module to the same underlying client', async () => {
28
+ const calls: string[] = [];
29
+ const impl = vi.fn(async (url: string) => {
30
+ calls.push(String(url));
31
+ if (String(url).includes('/api/services/by-slug/')) {
32
+ return new Response(JSON.stringify({ success: true, data: { service_id: 'service_auth' } }), {
33
+ status: 200,
34
+ headers: { 'Content-Type': 'application/json' },
35
+ });
36
+ }
37
+ if (String(url).includes('/api/services/catalog')) {
38
+ return new Response(JSON.stringify({ data: { services: [] } }), {
39
+ status: 200,
40
+ headers: { 'Content-Type': 'application/json' },
41
+ });
42
+ }
43
+ return new Response(
44
+ JSON.stringify({ data: { user: { id: 'u_1', email: 'a@b.c' } } }),
45
+ { status: 200, headers: { 'Content-Type': 'application/json' } },
46
+ );
47
+ }) as unknown as typeof fetch;
48
+
49
+ TopoloClient.clearServiceIdCache();
50
+ const topolo = createTopolo({
51
+ credential: { kind: 'access_token', accessToken: 'tok' },
52
+ agent: AGENT,
53
+ fetch: impl,
54
+ serviceIds: { auth: 'service_auth' },
55
+ });
56
+
57
+ const me = await topolo.identity.whoami();
58
+ expect(me.user.id).toBe('u_1');
59
+ expect(calls.some((u) => u.includes('/api/auth/me'))).toBe(true);
60
+ });
61
+ });
62
+
63
+ describe('package surface re-exports', () => {
64
+ it('re-exports the error classes from the barrel', () => {
65
+ expect(new TopoloAuthError('x').name).toBe('TopoloAuthError');
66
+ expect(new TopoloHttpError('auth', '/p', 500).name).toBe('TopoloHttpError');
67
+ expect(new TopoloPermissionError('nope', []).name).toBe('TopoloPermissionError');
68
+ });
69
+ });
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export {
5
5
  type CredentialIntrospection,
6
6
  type TopoloDebugEvent,
7
7
  type TopoloServiceCatalogEntry,
8
+ type TopoloApiKeyResourceMetadata,
8
9
  type TopoloActionCatalogEntry,
9
10
  } from "./client.js";
10
11
  export type { TopoloCredential, AgentIdentity } from "./auth.js";