@topolo/sdk 0.4.6 → 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 CHANGED
@@ -113,54 +113,53 @@ var TopoloHttpError = class extends TopoloSdkError {
113
113
 
114
114
  // src/services.generated.ts
115
115
  var SERVICES = {
116
- agent: "https://agent-api.topolo.app",
116
+ agent: "https://agent.topolo.app",
117
117
  auth: "https://auth.topolo.app",
118
118
  backup: "https://backup.topolo.app",
119
119
  books: "https://books.topolo.app",
120
120
  bugfix: "https://bugfix.topolo.app",
121
- bytes: "https://bytes-api.topolo.app",
121
+ bytes: "https://bytes.topolo.app",
122
122
  calendar: "https://calendar.topolo.app",
123
123
  capacity: "https://capacity.topolo.app",
124
124
  chat: "https://chat.topolo.app",
125
- commerce: "https://commerce-api.topolo.app",
125
+ commerce: "https://commerce.topolo.app",
126
126
  compose: "https://compose.topolo.app",
127
127
  consent: "https://consent.topolo.app",
128
- crm: "https://crm-api.topolo.app",
128
+ crm: "https://crm.topolo.app",
129
129
  developers: "https://developers.topolo.app",
130
130
  director: "https://director.topolo.app",
131
- feed: "https://feed-api.topolo.app",
131
+ feed: "https://feed.topolo.app",
132
132
  flow: "https://flow.topolo.app",
133
- forecast: "https://forecast-api.topolo.app",
133
+ forecast: "https://forecast.topolo.app",
134
134
  forms: "https://forms.topolo.app",
135
135
  home: "https://home.topolo.app",
136
136
  insights: "https://insights.topolo.app",
137
137
  inventory: "https://inventory.topolo.app",
138
- learn: "https://learn-api.topolo.app",
138
+ learn: "https://learn.topolo.app",
139
139
  localize: "https://localize.topolo.app",
140
- mail: "https://mail-api.topolo.app",
141
- mdm: "https://mdm-api.topolo.app",
140
+ mail: "https://mail.topolo.app",
141
+ mdm: "https://mdm.topolo.app",
142
142
  messages: "https://messages.topolo.app",
143
143
  nexus: "https://nexus-api.topolo.app",
144
144
  notify: "https://notify.topolo.app",
145
145
  observability_watch: "https://observability-watch.topolo.app",
146
- one: "https://one.topolo.app",
146
+ one: "https://www.topolo.app",
147
147
  p2p: "https://p2p.topolo.app",
148
148
  pay: "https://pay.topolo.app",
149
149
  people: "https://people.topolo.app",
150
- pulse: "https://pulse.topolo.app",
151
150
  "quro.api": "https://api.ol0.me",
152
151
  "quro.redirect": "https://ol0.me",
153
- roadmapper: "https://roadmapper-api.topolo.app",
152
+ roadmapper: "https://roadmapper.topolo.app",
154
153
  sign: "https://sign.topolo.app",
155
- social_studio: "https://studio-api.topolo.app",
156
- socialize: "https://socialize-api.topolo.app",
154
+ social_studio: "https://studio.topolo.app",
155
+ socialize: "https://socialize.topolo.app",
157
156
  spaces: "https://spaces.topolo.app",
158
157
  status: "https://status.topolo.app",
159
158
  success: "https://success.topolo.app",
160
159
  support: "https://support.topolo.app",
161
160
  survey: "https://survey.topolo.app",
162
161
  voice: "https://voice.topolo.app",
163
- web: "https://web-api.topolo.app"
162
+ web: "https://web.topolo.app"
164
163
  };
165
164
 
166
165
  // src/applications.generated.ts
@@ -211,17 +210,15 @@ var APPLICATIONS = {
211
210
  "agent.topolo.app"
212
211
  ],
213
212
  "routes": [],
214
- "serviceId": null
213
+ "serviceId": "agent"
215
214
  },
216
215
  {
217
216
  "name": "topolo-agent-api",
218
217
  "kind": "worker",
219
- "apiUrl": "https://agent-api.topolo.app",
220
- "domains": [
221
- "agent-api.topolo.app"
222
- ],
218
+ "apiUrl": null,
219
+ "domains": [],
223
220
  "routes": [],
224
- "serviceId": "agent"
221
+ "serviceId": null
225
222
  }
226
223
  ]
227
224
  },
@@ -341,17 +338,15 @@ var APPLICATIONS = {
341
338
  "bytes.topolo.app"
342
339
  ],
343
340
  "routes": [],
344
- "serviceId": null
341
+ "serviceId": "bytes"
345
342
  },
346
343
  {
347
344
  "name": "topolo-bytes-api",
348
345
  "kind": "worker",
349
- "apiUrl": "https://bytes-api.topolo.app",
350
- "domains": [
351
- "bytes-api.topolo.app"
352
- ],
346
+ "apiUrl": null,
347
+ "domains": [],
353
348
  "routes": [],
354
- "serviceId": "bytes"
349
+ "serviceId": null
355
350
  }
356
351
  ]
357
352
  },
@@ -450,12 +445,10 @@ var APPLICATIONS = {
450
445
  {
451
446
  "name": "topolo-commerce-api",
452
447
  "kind": "worker",
453
- "apiUrl": "https://commerce-api.topolo.app",
454
- "domains": [
455
- "commerce-api.topolo.app"
456
- ],
448
+ "apiUrl": null,
449
+ "domains": [],
457
450
  "routes": [],
458
- "serviceId": "commerce"
451
+ "serviceId": null
459
452
  },
460
453
  {
461
454
  "name": "topolo-commerce-guest-web",
@@ -475,7 +468,7 @@ var APPLICATIONS = {
475
468
  "commerce.topolo.app"
476
469
  ],
477
470
  "routes": [],
478
- "serviceId": null
471
+ "serviceId": "commerce"
479
472
  },
480
473
  {
481
474
  "name": "topolo-commerce-station-web",
@@ -562,12 +555,10 @@ var APPLICATIONS = {
562
555
  {
563
556
  "name": "topolo-crm-api",
564
557
  "kind": "worker",
565
- "apiUrl": "https://crm-api.topolo.app",
566
- "domains": [
567
- "crm-api.topolo.app"
568
- ],
558
+ "apiUrl": null,
559
+ "domains": [],
569
560
  "routes": [],
570
- "serviceId": "crm"
561
+ "serviceId": null
571
562
  },
572
563
  {
573
564
  "name": "topolo-crm-app",
@@ -577,7 +568,7 @@ var APPLICATIONS = {
577
568
  "crm.topolo.app"
578
569
  ],
579
570
  "routes": [],
580
- "serviceId": null
571
+ "serviceId": "crm"
581
572
  }
582
573
  ]
583
574
  },
@@ -700,12 +691,10 @@ var APPLICATIONS = {
700
691
  {
701
692
  "name": "topolo-feed-api",
702
693
  "kind": "worker",
703
- "apiUrl": "https://feed-api.topolo.app",
704
- "domains": [
705
- "feed-api.topolo.app"
706
- ],
694
+ "apiUrl": null,
695
+ "domains": [],
707
696
  "routes": [],
708
- "serviceId": "feed"
697
+ "serviceId": null
709
698
  },
710
699
  {
711
700
  "name": "topolo-feed-web",
@@ -715,7 +704,7 @@ var APPLICATIONS = {
715
704
  "feed.topolo.app"
716
705
  ],
717
706
  "routes": [],
718
- "serviceId": null
707
+ "serviceId": "feed"
719
708
  }
720
709
  ]
721
710
  },
@@ -767,17 +756,15 @@ var APPLICATIONS = {
767
756
  "forecast.topolo.app"
768
757
  ],
769
758
  "routes": [],
770
- "serviceId": null
759
+ "serviceId": "forecast"
771
760
  },
772
761
  {
773
762
  "name": "topolo-forecast-api",
774
763
  "kind": "worker",
775
- "apiUrl": "https://forecast-api.topolo.app",
776
- "domains": [
777
- "forecast-api.topolo.app"
778
- ],
764
+ "apiUrl": null,
765
+ "domains": [],
779
766
  "routes": [],
780
- "serviceId": "forecast"
767
+ "serviceId": null
781
768
  }
782
769
  ]
783
770
  },
@@ -919,17 +906,15 @@ var APPLICATIONS = {
919
906
  "learn.topolo.app"
920
907
  ],
921
908
  "routes": [],
922
- "serviceId": null
909
+ "serviceId": "learn"
923
910
  },
924
911
  {
925
912
  "name": "topolo-learn-api",
926
913
  "kind": "worker",
927
- "apiUrl": "https://learn-api.topolo.app",
928
- "domains": [
929
- "learn-api.topolo.app"
930
- ],
914
+ "apiUrl": null,
915
+ "domains": [],
931
916
  "routes": [],
932
- "serviceId": "learn"
917
+ "serviceId": null
933
918
  }
934
919
  ]
935
920
  },
@@ -975,17 +960,15 @@ var APPLICATIONS = {
975
960
  "mail.topolo.app"
976
961
  ],
977
962
  "routes": [],
978
- "serviceId": null
963
+ "serviceId": "mail"
979
964
  },
980
965
  {
981
966
  "name": "topolo-mail-api",
982
967
  "kind": "worker",
983
- "apiUrl": "https://mail-api.topolo.app",
984
- "domains": [
985
- "mail-api.topolo.app"
986
- ],
968
+ "apiUrl": null,
969
+ "domains": [],
987
970
  "routes": [],
988
- "serviceId": "mail"
971
+ "serviceId": null
989
972
  }
990
973
  ]
991
974
  },
@@ -1002,12 +985,10 @@ var APPLICATIONS = {
1002
985
  {
1003
986
  "name": "topolo-mdm-api",
1004
987
  "kind": "worker",
1005
- "apiUrl": "https://mdm-api.topolo.app",
1006
- "domains": [
1007
- "mdm-api.topolo.app"
1008
- ],
988
+ "apiUrl": null,
989
+ "domains": [],
1009
990
  "routes": [],
1010
- "serviceId": "mdm"
991
+ "serviceId": null
1011
992
  },
1012
993
  {
1013
994
  "name": "topolo-mdm-console",
@@ -1017,7 +998,7 @@ var APPLICATIONS = {
1017
998
  "mdm.topolo.app"
1018
999
  ],
1019
1000
  "routes": [],
1020
- "serviceId": null
1001
+ "serviceId": "mdm"
1021
1002
  }
1022
1003
  ]
1023
1004
  },
@@ -1150,20 +1131,23 @@ var APPLICATIONS = {
1150
1131
  {
1151
1132
  "name": "topolo-one",
1152
1133
  "kind": "worker",
1153
- "apiUrl": null,
1154
- "domains": [],
1155
- "routes": [],
1134
+ "apiUrl": "https://one.topolo.app",
1135
+ "domains": [
1136
+ "one.topolo.app"
1137
+ ],
1138
+ "routes": [
1139
+ "one.topolo.app/*"
1140
+ ],
1156
1141
  "serviceId": null
1157
1142
  },
1158
1143
  {
1159
1144
  "name": "topolo-one-api",
1160
1145
  "kind": "worker",
1161
- "apiUrl": "https://one.topolo.app",
1146
+ "apiUrl": "https://www.topolo.app",
1162
1147
  "domains": [
1163
- "one.topolo.app"
1148
+ "www.topolo.app"
1164
1149
  ],
1165
1150
  "routes": [
1166
- "one.topolo.app/*",
1167
1151
  "www.topolo.app/*"
1168
1152
  ],
1169
1153
  "serviceId": "one"
@@ -1273,36 +1257,6 @@ var APPLICATIONS = {
1273
1257
  }
1274
1258
  ]
1275
1259
  },
1276
- "pulse": {
1277
- "id": "pulse",
1278
- "appDir": "TopoloPulse",
1279
- "name": "topolo-pulse",
1280
- "packageName": "topolo-pulse",
1281
- "productionUrl": "https://pulse.topolo.app",
1282
- "services": [
1283
- "pulse"
1284
- ],
1285
- "deployTargets": [
1286
- {
1287
- "name": "topolo-pulse",
1288
- "kind": "worker",
1289
- "apiUrl": "https://pulse.topolo.app",
1290
- "domains": [
1291
- "pulse.topolo.app"
1292
- ],
1293
- "routes": [],
1294
- "serviceId": "pulse"
1295
- },
1296
- {
1297
- "name": "topolo-pulse-api",
1298
- "kind": "worker",
1299
- "apiUrl": null,
1300
- "domains": [],
1301
- "routes": [],
1302
- "serviceId": null
1303
- }
1304
- ]
1305
- },
1306
1260
  "quro": {
1307
1261
  "id": "quro",
1308
1262
  "appDir": "TopoloQuro",
@@ -1368,17 +1322,15 @@ var APPLICATIONS = {
1368
1322
  "roadmapper.topolo.app"
1369
1323
  ],
1370
1324
  "routes": [],
1371
- "serviceId": null
1325
+ "serviceId": "roadmapper"
1372
1326
  },
1373
1327
  {
1374
1328
  "name": "topolo-roadmapper-api",
1375
1329
  "kind": "worker",
1376
- "apiUrl": "https://roadmapper-api.topolo.app",
1377
- "domains": [
1378
- "roadmapper-api.topolo.app"
1379
- ],
1330
+ "apiUrl": null,
1331
+ "domains": [],
1380
1332
  "routes": [],
1381
- "serviceId": "roadmapper"
1333
+ "serviceId": null
1382
1334
  }
1383
1335
  ]
1384
1336
  },
@@ -1430,17 +1382,15 @@ var APPLICATIONS = {
1430
1382
  "studio.topolo.app"
1431
1383
  ],
1432
1384
  "routes": [],
1433
- "serviceId": null
1385
+ "serviceId": "social_studio"
1434
1386
  },
1435
1387
  {
1436
1388
  "name": "topolo-social-studio-api",
1437
1389
  "kind": "worker",
1438
- "apiUrl": "https://studio-api.topolo.app",
1439
- "domains": [
1440
- "studio-api.topolo.app"
1441
- ],
1390
+ "apiUrl": null,
1391
+ "domains": [],
1442
1392
  "routes": [],
1443
- "serviceId": "social_studio"
1393
+ "serviceId": null
1444
1394
  },
1445
1395
  {
1446
1396
  "name": "topolo-social-studio-queue",
@@ -1470,17 +1420,15 @@ var APPLICATIONS = {
1470
1420
  "socialize.topolo.app"
1471
1421
  ],
1472
1422
  "routes": [],
1473
- "serviceId": null
1423
+ "serviceId": "socialize"
1474
1424
  },
1475
1425
  {
1476
1426
  "name": "topolo-socialize-api",
1477
1427
  "kind": "worker",
1478
- "apiUrl": "https://socialize-api.topolo.app",
1479
- "domains": [
1480
- "socialize-api.topolo.app"
1481
- ],
1428
+ "apiUrl": null,
1429
+ "domains": [],
1482
1430
  "routes": [],
1483
- "serviceId": "socialize"
1431
+ "serviceId": null
1484
1432
  },
1485
1433
  {
1486
1434
  "name": "topolo-socialize-scheduler",
@@ -1681,12 +1629,10 @@ var APPLICATIONS = {
1681
1629
  {
1682
1630
  "name": "topolo-web-api",
1683
1631
  "kind": "worker",
1684
- "apiUrl": "https://web-api.topolo.app",
1685
- "domains": [
1686
- "web-api.topolo.app"
1687
- ],
1632
+ "apiUrl": null,
1633
+ "domains": [],
1688
1634
  "routes": [],
1689
- "serviceId": "web"
1635
+ "serviceId": null
1690
1636
  },
1691
1637
  {
1692
1638
  "name": "topolo-web-runtime",
@@ -1709,14 +1655,14 @@ var APPLICATIONS = {
1709
1655
  "web.topolo.app"
1710
1656
  ],
1711
1657
  "routes": [],
1712
- "serviceId": null
1658
+ "serviceId": "web"
1713
1659
  }
1714
1660
  ]
1715
1661
  }
1716
1662
  };
1717
1663
 
1718
1664
  // src/application-requirements.ts
1719
- var APPLICATION_REQUIREMENTS_VERSION = "2026-05-01.1";
1665
+ var APPLICATION_REQUIREMENTS_VERSION = "2026-06-07.1";
1720
1666
  var APPLICATION_REQUIREMENTS = [
1721
1667
  {
1722
1668
  id: "platform-metadata",
@@ -1772,16 +1718,20 @@ var APPLICATION_REQUIREMENTS = [
1772
1718
  summary: "Browser applications should feel like one platform and should not create isolated navigation or account surfaces.",
1773
1719
  appliesTo: ["browser"],
1774
1720
  evidence: [
1775
- "@topolo/ui-kit is used for the platform shell where the app has authenticated UI",
1776
- "The shared launcher/app switcher is reachable from authenticated app chrome",
1777
- "Account menu and sign-out behavior use TopoloAppShell or TopoloAccountMenu",
1721
+ "@topolo-io/app-shell is used for the platform shell where the app has authenticated UI",
1722
+ "@topolo-io/app-launcher keeps the shared launcher/app switcher reachable from authenticated app chrome",
1723
+ "Account menu and sign-out behavior use TopoloAppShell or the ui-kit TopoloAccountMenu primitive",
1724
+ "Repeated app body surfaces use shared TopoloAppLayout primitives instead of local stat cards, empty states, or status badges",
1725
+ "Local preview supports deterministic visual QA fixtures for overview, list, detail, settings, and empty/loading states",
1778
1726
  "Authenticated app body content stays inside the viewport on mobile and desktop widths"
1779
1727
  ],
1780
1728
  implementation: [
1781
- "Use TopoloAppShell for authenticated app layouts",
1782
- "Use TopoloAccountMenu when a partial-shell surface still needs account chrome",
1729
+ "Use @topolo-io/app-shell TopoloAppShell for authenticated app layouts",
1730
+ "Use the @topolo-io/ui-kit TopoloAccountMenu primitive when a partial-shell surface still needs account chrome",
1783
1731
  "Pass app-specific account actions as additive menu items instead of forking dropdowns",
1784
1732
  "Use shared design tokens and platform UI primitives before creating local equivalents",
1733
+ "Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
1734
+ "Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
1785
1735
  "Keep mobile and desktop shell behavior aligned with the platform UI kit",
1786
1736
  "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"
1787
1737
  ]
@@ -1860,11 +1810,13 @@ var APPLICATION_REQUIREMENTS = [
1860
1810
  evidence: [
1861
1811
  "The app-owned API exposes GET /api/widget",
1862
1812
  "The endpoint returns TopoloWidgetApiResponse from @topolo/sdk",
1813
+ "Every returned widget includes snapshot.summary with app-owned operational prose",
1863
1814
  "The endpoint derives organization/user context from the app auth boundary"
1864
1815
  ],
1865
1816
  implementation: [
1866
1817
  "Import createTopoloWidgetResponse from @topolo/sdk in the app backend",
1867
1818
  "Return native app metrics or action widgets instead of generic launch-card fallbacks",
1819
+ "Populate snapshot.summary in the producing app for every widget instead of synthesizing summaries in TopoloOne",
1868
1820
  "Add a route-level test that validates the response with validateTopoloWidgetResponse"
1869
1821
  ]
1870
1822
  },
@@ -2161,6 +2113,8 @@ function firstString(...values) {
2161
2113
  var WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
2162
2114
  var TOPOLO_ACTION_ID_HEADER = "X-Topolo-Action-Id";
2163
2115
  var TOPOLO_ACTION_NAME_HEADER = "X-Topolo-Action-Name";
2116
+ var TOPOLO_RESOURCE_TYPE_HEADER = "X-Topolo-Resource-Type";
2117
+ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
2164
2118
  var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
2165
2119
  var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
2166
2120
  var CATALOG_CACHE_TTL_MS = 60 * 1e3;
@@ -2239,6 +2193,18 @@ var TopoloClient = class _TopoloClient {
2239
2193
  if (opts.headers) {
2240
2194
  for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
2241
2195
  }
2196
+ if (opts.resource) {
2197
+ const resourceType = opts.resource.resourceType.trim();
2198
+ const resourceId = opts.resource.resourceId.trim();
2199
+ if (!resourceType || !resourceId) {
2200
+ throw new TopoloSdkError(
2201
+ "invalid_resource_context",
2202
+ "Both resource.resourceType and resource.resourceId are required when resource context is provided."
2203
+ );
2204
+ }
2205
+ headers.set(TOPOLO_RESOURCE_TYPE_HEADER, resourceType);
2206
+ headers.set(TOPOLO_RESOURCE_ID_HEADER, resourceId);
2207
+ }
2242
2208
  const controller = new AbortController();
2243
2209
  const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
2244
2210
  const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
@@ -2421,6 +2387,11 @@ var TopoloClient = class _TopoloClient {
2421
2387
  const serviceId = typeof s.service_id === "string" ? s.service_id : typeof s.id === "string" ? s.id : null;
2422
2388
  if (!serviceId) continue;
2423
2389
  const permissions = Array.isArray(s.granted_permissions) ? s.granted_permissions.filter((permission) => typeof permission === "string" && permission.trim().length > 0) : [];
2390
+ const catalogApiKeyResources = normalizeApiKeyResourceMetadataList(
2391
+ s.api_key_resources ?? s.apiKeyResources
2392
+ );
2393
+ const apiKeyResource = normalizeApiKeyResourceMetadata(s.api_key_resource ?? s.apiKeyResource) ?? catalogApiKeyResources[0] ?? null;
2394
+ const apiKeyResources = catalogApiKeyResources.length > 0 ? catalogApiKeyResources : apiKeyResource ? [apiKeyResource] : [];
2424
2395
  entries.push({
2425
2396
  serviceId,
2426
2397
  slug: typeof s.app_slug === "string" ? s.app_slug : null,
@@ -2431,7 +2402,9 @@ var TopoloClient = class _TopoloClient {
2431
2402
  status: typeof s.status === "string" ? s.status : null,
2432
2403
  surfaceKind: typeof s.surface_kind === "string" ? s.surface_kind : null,
2433
2404
  launchable: s.launchable === true,
2434
- permissions
2405
+ permissions,
2406
+ apiKeyResource,
2407
+ apiKeyResources
2435
2408
  });
2436
2409
  }
2437
2410
  this.serviceCatalogCache = {
@@ -2517,7 +2490,9 @@ var TopoloClient = class _TopoloClient {
2517
2490
  status: "bundled",
2518
2491
  surfaceKind: "application",
2519
2492
  launchable: Boolean(app.productionUrl),
2520
- permissions: []
2493
+ permissions: [],
2494
+ apiKeyResource: null,
2495
+ apiKeyResources: []
2521
2496
  }));
2522
2497
  }
2523
2498
  /**
@@ -2566,6 +2541,46 @@ function resolveConfiguredServiceId(slug, serviceIds) {
2566
2541
  }
2567
2542
  return null;
2568
2543
  }
2544
+ function normalizeApiKeyResourceMetadata(input) {
2545
+ if (!isRecord(input)) return null;
2546
+ const resourceType = readString(input.resourceType ?? input.resource_type);
2547
+ const label = readString(input.label);
2548
+ const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
2549
+ const resourceConcept = readString(input.resourceConcept ?? input.resource_concept);
2550
+ const canonicalResourceType = readString(input.canonicalResourceType ?? input.canonical_resource_type);
2551
+ const defaultEntitlement = readString(input.defaultEntitlement ?? input.default_entitlement);
2552
+ const exceptionPolicy = readString(input.exceptionPolicy ?? input.exception_policy);
2553
+ if (!resourceType || !label || !emptyLabel || resourceConcept !== "workspace" || canonicalResourceType !== "workspace" || defaultEntitlement !== "enterprise" || exceptionPolicy !== "app_or_organization") {
2554
+ return null;
2555
+ }
2556
+ return {
2557
+ resourceType,
2558
+ label,
2559
+ emptyLabel,
2560
+ resourceConcept,
2561
+ canonicalResourceType,
2562
+ defaultEntitlement,
2563
+ exceptionPolicy
2564
+ };
2565
+ }
2566
+ function normalizeApiKeyResourceMetadataList(input) {
2567
+ if (!Array.isArray(input)) return [];
2568
+ const resources = [];
2569
+ const seenTypes = /* @__PURE__ */ new Set();
2570
+ for (const item of input) {
2571
+ const resource = normalizeApiKeyResourceMetadata(item);
2572
+ if (!resource || seenTypes.has(resource.resourceType)) continue;
2573
+ seenTypes.add(resource.resourceType);
2574
+ resources.push(resource);
2575
+ }
2576
+ return resources;
2577
+ }
2578
+ function readString(value) {
2579
+ return typeof value === "string" ? value.trim() : "";
2580
+ }
2581
+ function isRecord(value) {
2582
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2583
+ }
2569
2584
  function resolveConfiguredServiceUrl(slug, serviceUrls) {
2570
2585
  for (const serviceKey of serviceKeyCandidates2(slug)) {
2571
2586
  const configured = serviceUrls?.[serviceKey];
@@ -3133,7 +3148,7 @@ function createTopoloWidgetError(error) {
3133
3148
  }
3134
3149
  function validateTopoloWidgetResponse(response) {
3135
3150
  const errors = [];
3136
- if (!isRecord(response)) {
3151
+ if (!isRecord2(response)) {
3137
3152
  return { ok: false, errors: ["response must be an object"] };
3138
3153
  }
3139
3154
  if (typeof response.success !== "boolean") {
@@ -3152,7 +3167,7 @@ function validateTopoloWidgetResponse(response) {
3152
3167
  return { ok: errors.length === 0, errors };
3153
3168
  }
3154
3169
  function validateWidget(widget, path, errors) {
3155
- if (!isRecord(widget)) {
3170
+ if (!isRecord2(widget)) {
3156
3171
  errors.push(`${path} must be an object`);
3157
3172
  return;
3158
3173
  }
@@ -3179,7 +3194,7 @@ function validateWidget(widget, path, errors) {
3179
3194
  case "chart":
3180
3195
  validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
3181
3196
  validateNonEmptyString(widget.title, `${path}.title`, errors);
3182
- if (!isRecord(widget.data)) {
3197
+ if (!isRecord2(widget.data)) {
3183
3198
  errors.push(`${path}.data must be an object`);
3184
3199
  } else {
3185
3200
  validateArray(widget.data.labels, `${path}.data.labels`, errors);
@@ -3192,12 +3207,14 @@ function validateWidget(widget, path, errors) {
3192
3207
  }
3193
3208
  function validateSnapshotMetadata(snapshot, path, errors) {
3194
3209
  if (snapshot === void 0) {
3210
+ errors.push(`${path} must be an object`);
3195
3211
  return;
3196
3212
  }
3197
- if (!isRecord(snapshot)) {
3198
- errors.push(`${path} must be an object when provided`);
3213
+ if (!isRecord2(snapshot)) {
3214
+ errors.push(`${path} must be an object`);
3199
3215
  return;
3200
3216
  }
3217
+ validateNonEmptyString(snapshot.summary, `${path}.summary`, errors);
3201
3218
  if (snapshot.defaultMetricKeys !== void 0 && !Array.isArray(snapshot.defaultMetricKeys)) {
3202
3219
  errors.push(`${path}.defaultMetricKeys must be an array when provided`);
3203
3220
  }
@@ -3213,7 +3230,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
3213
3230
  }
3214
3231
  snapshot.availableMetrics.forEach((metric, index) => {
3215
3232
  const metricPath = `${path}.availableMetrics[${index}]`;
3216
- if (!isRecord(metric)) {
3233
+ if (!isRecord2(metric)) {
3217
3234
  errors.push(`${metricPath} must be an object`);
3218
3235
  return;
3219
3236
  }
@@ -3234,7 +3251,7 @@ function validateArray(value, path, errors) {
3234
3251
  errors.push(`${path} must be an array`);
3235
3252
  }
3236
3253
  }
3237
- function isRecord(value) {
3254
+ function isRecord2(value) {
3238
3255
  return typeof value === "object" && value !== null && !Array.isArray(value);
3239
3256
  }
3240
3257
  function toIsoTimestamp(input) {