@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.
- package/dist/index.cjs +163 -137
- package/dist/index.d.cts +83 -91
- package/dist/index.d.ts +83 -91
- package/dist/index.js +163 -137
- package/package.json +1 -1
- package/src/application-requirements.ts +9 -5
- package/src/applications.generated.ts +50 -102
- package/src/client.test.ts +108 -19
- package/src/client.ts +137 -4
- package/src/index.test.ts +69 -0
- package/src/index.ts +1 -0
- package/src/modules/crm.test.ts +208 -0
- package/src/modules/identity.test.ts +165 -0
- package/src/modules/p2p.test.ts +286 -0
- package/src/oauth.test.ts +103 -0
- package/src/services.generated.ts +14 -15
- package/src/services.test.ts +18 -7
- package/src/widgets.test.ts +216 -0
|
@@ -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/
|
|
129
|
-
'
|
|
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":
|
|
52
|
+
"serviceId": "agent"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"name": "topolo-agent-api",
|
|
56
56
|
"kind": "worker",
|
|
57
|
-
"apiUrl":
|
|
58
|
-
"domains": [
|
|
59
|
-
"agent-api.topolo.app"
|
|
60
|
-
],
|
|
57
|
+
"apiUrl": null,
|
|
58
|
+
"domains": [],
|
|
61
59
|
"routes": [],
|
|
62
|
-
"serviceId":
|
|
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":
|
|
180
|
+
"serviceId": "bytes"
|
|
183
181
|
},
|
|
184
182
|
{
|
|
185
183
|
"name": "topolo-bytes-api",
|
|
186
184
|
"kind": "worker",
|
|
187
|
-
"apiUrl":
|
|
188
|
-
"domains": [
|
|
189
|
-
"bytes-api.topolo.app"
|
|
190
|
-
],
|
|
185
|
+
"apiUrl": null,
|
|
186
|
+
"domains": [],
|
|
191
187
|
"routes": [],
|
|
192
|
-
"serviceId":
|
|
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":
|
|
292
|
-
"domains": [
|
|
293
|
-
"commerce-api.topolo.app"
|
|
294
|
-
],
|
|
287
|
+
"apiUrl": null,
|
|
288
|
+
"domains": [],
|
|
295
289
|
"routes": [],
|
|
296
|
-
"serviceId":
|
|
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":
|
|
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":
|
|
404
|
-
"domains": [
|
|
405
|
-
"crm-api.topolo.app"
|
|
406
|
-
],
|
|
397
|
+
"apiUrl": null,
|
|
398
|
+
"domains": [],
|
|
407
399
|
"routes": [],
|
|
408
|
-
"serviceId":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
600
|
+
"serviceId": "forecast"
|
|
609
601
|
},
|
|
610
602
|
{
|
|
611
603
|
"name": "topolo-forecast-api",
|
|
612
604
|
"kind": "worker",
|
|
613
|
-
"apiUrl":
|
|
614
|
-
"domains": [
|
|
615
|
-
"forecast-api.topolo.app"
|
|
616
|
-
],
|
|
605
|
+
"apiUrl": null,
|
|
606
|
+
"domains": [],
|
|
617
607
|
"routes": [],
|
|
618
|
-
"serviceId":
|
|
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":
|
|
750
|
+
"serviceId": "learn"
|
|
761
751
|
},
|
|
762
752
|
{
|
|
763
753
|
"name": "topolo-learn-api",
|
|
764
754
|
"kind": "worker",
|
|
765
|
-
"apiUrl":
|
|
766
|
-
"domains": [
|
|
767
|
-
"learn-api.topolo.app"
|
|
768
|
-
],
|
|
755
|
+
"apiUrl": null,
|
|
756
|
+
"domains": [],
|
|
769
757
|
"routes": [],
|
|
770
|
-
"serviceId":
|
|
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":
|
|
804
|
+
"serviceId": "mail"
|
|
817
805
|
},
|
|
818
806
|
{
|
|
819
807
|
"name": "topolo-mail-api",
|
|
820
808
|
"kind": "worker",
|
|
821
|
-
"apiUrl":
|
|
822
|
-
"domains": [
|
|
823
|
-
"mail-api.topolo.app"
|
|
824
|
-
],
|
|
809
|
+
"apiUrl": null,
|
|
810
|
+
"domains": [],
|
|
825
811
|
"routes": [],
|
|
826
|
-
"serviceId":
|
|
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":
|
|
844
|
-
"domains": [
|
|
845
|
-
"mdm-api.topolo.app"
|
|
846
|
-
],
|
|
829
|
+
"apiUrl": null,
|
|
830
|
+
"domains": [],
|
|
847
831
|
"routes": [],
|
|
848
|
-
"serviceId":
|
|
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":
|
|
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
|
|
907
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
924
908
|
"domains": [
|
|
925
|
-
"nexus
|
|
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":
|
|
1166
|
+
"serviceId": "roadmapper"
|
|
1213
1167
|
},
|
|
1214
1168
|
{
|
|
1215
1169
|
"name": "topolo-roadmapper-api",
|
|
1216
1170
|
"kind": "worker",
|
|
1217
|
-
"apiUrl":
|
|
1218
|
-
"domains": [
|
|
1219
|
-
"roadmapper-api.topolo.app"
|
|
1220
|
-
],
|
|
1171
|
+
"apiUrl": null,
|
|
1172
|
+
"domains": [],
|
|
1221
1173
|
"routes": [],
|
|
1222
|
-
"serviceId":
|
|
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":
|
|
1226
|
+
"serviceId": "social_studio"
|
|
1275
1227
|
},
|
|
1276
1228
|
{
|
|
1277
1229
|
"name": "topolo-social-studio-api",
|
|
1278
1230
|
"kind": "worker",
|
|
1279
|
-
"apiUrl":
|
|
1280
|
-
"domains": [
|
|
1281
|
-
"studio-api.topolo.app"
|
|
1282
|
-
],
|
|
1231
|
+
"apiUrl": null,
|
|
1232
|
+
"domains": [],
|
|
1283
1233
|
"routes": [],
|
|
1284
|
-
"serviceId":
|
|
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":
|
|
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":
|
|
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":
|
|
1526
|
-
"domains": [
|
|
1527
|
-
"web-api.topolo.app"
|
|
1528
|
-
],
|
|
1475
|
+
"apiUrl": null,
|
|
1476
|
+
"domains": [],
|
|
1529
1477
|
"routes": [],
|
|
1530
|
-
"serviceId":
|
|
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":
|
|
1501
|
+
"serviceId": "web"
|
|
1554
1502
|
}
|
|
1555
1503
|
]
|
|
1556
1504
|
}
|
package/src/client.test.ts
CHANGED
|
@@ -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]!;
|