@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.
- package/dist/index.cjs +145 -135
- package/dist/index.d.cts +84 -92
- package/dist/index.d.ts +84 -92
- package/dist/index.js +145 -135
- package/package.json +1 -1
- package/src/application-requirements.ts +9 -5
- package/src/applications.generated.ts +52 -108
- package/src/client.test.ts +74 -0
- package/src/client.ts +101 -0
- 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 +13 -14
- package/src/services.test.ts +18 -7
- package/src/widgets.test.ts +216 -0
|
@@ -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
|
},
|
|
@@ -538,12 +530,10 @@ export const APPLICATIONS = {
|
|
|
538
530
|
{
|
|
539
531
|
"name": "topolo-feed-api",
|
|
540
532
|
"kind": "worker",
|
|
541
|
-
"apiUrl":
|
|
542
|
-
"domains": [
|
|
543
|
-
"feed-api.topolo.app"
|
|
544
|
-
],
|
|
533
|
+
"apiUrl": null,
|
|
534
|
+
"domains": [],
|
|
545
535
|
"routes": [],
|
|
546
|
-
"serviceId":
|
|
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":
|
|
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":
|
|
598
|
+
"serviceId": "forecast"
|
|
609
599
|
},
|
|
610
600
|
{
|
|
611
601
|
"name": "topolo-forecast-api",
|
|
612
602
|
"kind": "worker",
|
|
613
|
-
"apiUrl":
|
|
614
|
-
"domains": [
|
|
615
|
-
"forecast-api.topolo.app"
|
|
616
|
-
],
|
|
603
|
+
"apiUrl": null,
|
|
604
|
+
"domains": [],
|
|
617
605
|
"routes": [],
|
|
618
|
-
"serviceId":
|
|
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":
|
|
748
|
+
"serviceId": "learn"
|
|
761
749
|
},
|
|
762
750
|
{
|
|
763
751
|
"name": "topolo-learn-api",
|
|
764
752
|
"kind": "worker",
|
|
765
|
-
"apiUrl":
|
|
766
|
-
"domains": [
|
|
767
|
-
"learn-api.topolo.app"
|
|
768
|
-
],
|
|
753
|
+
"apiUrl": null,
|
|
754
|
+
"domains": [],
|
|
769
755
|
"routes": [],
|
|
770
|
-
"serviceId":
|
|
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":
|
|
802
|
+
"serviceId": "mail"
|
|
817
803
|
},
|
|
818
804
|
{
|
|
819
805
|
"name": "topolo-mail-api",
|
|
820
806
|
"kind": "worker",
|
|
821
|
-
"apiUrl":
|
|
822
|
-
"domains": [
|
|
823
|
-
"mail-api.topolo.app"
|
|
824
|
-
],
|
|
807
|
+
"apiUrl": null,
|
|
808
|
+
"domains": [],
|
|
825
809
|
"routes": [],
|
|
826
|
-
"serviceId":
|
|
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":
|
|
844
|
-
"domains": [
|
|
845
|
-
"mdm-api.topolo.app"
|
|
846
|
-
],
|
|
827
|
+
"apiUrl": null,
|
|
828
|
+
"domains": [],
|
|
847
829
|
"routes": [],
|
|
848
|
-
"serviceId":
|
|
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":
|
|
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":
|
|
1164
|
+
"serviceId": "roadmapper"
|
|
1213
1165
|
},
|
|
1214
1166
|
{
|
|
1215
1167
|
"name": "topolo-roadmapper-api",
|
|
1216
1168
|
"kind": "worker",
|
|
1217
|
-
"apiUrl":
|
|
1218
|
-
"domains": [
|
|
1219
|
-
"roadmapper-api.topolo.app"
|
|
1220
|
-
],
|
|
1169
|
+
"apiUrl": null,
|
|
1170
|
+
"domains": [],
|
|
1221
1171
|
"routes": [],
|
|
1222
|
-
"serviceId":
|
|
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":
|
|
1224
|
+
"serviceId": "social_studio"
|
|
1275
1225
|
},
|
|
1276
1226
|
{
|
|
1277
1227
|
"name": "topolo-social-studio-api",
|
|
1278
1228
|
"kind": "worker",
|
|
1279
|
-
"apiUrl":
|
|
1280
|
-
"domains": [
|
|
1281
|
-
"studio-api.topolo.app"
|
|
1282
|
-
],
|
|
1229
|
+
"apiUrl": null,
|
|
1230
|
+
"domains": [],
|
|
1283
1231
|
"routes": [],
|
|
1284
|
-
"serviceId":
|
|
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":
|
|
1262
|
+
"serviceId": "socialize"
|
|
1315
1263
|
},
|
|
1316
1264
|
{
|
|
1317
1265
|
"name": "topolo-socialize-api",
|
|
1318
1266
|
"kind": "worker",
|
|
1319
|
-
"apiUrl":
|
|
1320
|
-
"domains": [
|
|
1321
|
-
"socialize-api.topolo.app"
|
|
1322
|
-
],
|
|
1267
|
+
"apiUrl": null,
|
|
1268
|
+
"domains": [],
|
|
1323
1269
|
"routes": [],
|
|
1324
|
-
"serviceId":
|
|
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":
|
|
1526
|
-
"domains": [
|
|
1527
|
-
"web-api.topolo.app"
|
|
1528
|
-
],
|
|
1471
|
+
"apiUrl": null,
|
|
1472
|
+
"domains": [],
|
|
1529
1473
|
"routes": [],
|
|
1530
|
-
"serviceId":
|
|
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":
|
|
1497
|
+
"serviceId": "web"
|
|
1554
1498
|
}
|
|
1555
1499
|
]
|
|
1556
1500
|
}
|
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({
|
|
@@ -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