@vritti/api-sdk 0.3.13 → 0.3.15

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.
@@ -1,10 +1,28 @@
1
- import { V as VersionSnapshot, S as SiteFeatureLocks, P as PlatformBucket, a as SiteType, b as ScopeType, c as ServiceCode, F as FeatureCatalogEntry, R as RoleItem, d as SnapshotFeature, e as PlatformCodes, f as FeatureUnlocks, g as PlatformDenyCodes, L as LockReason } from './types-CG-Ynwyf.cjs';
2
- export { B as BusinessVocabulary, C as CatalogPermission, h as FeatureLocks, i as PLATFORMS, j as SERVICE_CODES, k as SITE_TYPES, l as SNAPSHOT_SCHEMA_VERSION, m as SnapshotApp, n as SnapshotAppFeatureRef, o as SnapshotBusiness, p as SnapshotMicrofrontendMobile, r as SnapshotMicrofrontendWeb, q as SnapshotMicrofrontends, s as SnapshotPermission, t as SnapshotPlan, u as SnapshotRoleTemplate, w as VocabularyEntry, v as snapshotFeatureKey } from './types-CG-Ynwyf.cjs';
1
+ import { V as VersionSnapshot, S as SiteFeatureLocks, P as PlatformBucket, a as SiteType, b as ScopeType, c as ServiceCode, F as FeatureCatalogEntry, R as RoleItem, d as SnapshotFeature, e as PlatformCodes, f as PlatformDenyCodes, A as ApiSurface, g as FeatureUnlocks, L as LockReason } from './types-B7ArIjwo.cjs';
2
+ export { h as API_BUCKETS, i as API_SURFACES, j as ApiBucket, B as BUCKET_BY_SURFACE, k as BusinessVocabulary, C as CatalogPermission, l as FeatureLocks, n as PLATFORMS, o as SERVICE_CODES, p as SITE_TYPES, q as SNAPSHOT_SCHEMA_VERSION, D as SURFACE_BY_BUCKET, r as SnapshotApp, s as SnapshotAppFeatureRef, t as SnapshotBusiness, u as SnapshotMicrofrontendMobile, w as SnapshotMicrofrontendWeb, v as SnapshotMicrofrontends, x as SnapshotPermission, y as SnapshotPlan, z as SnapshotRoleTemplate, U as UI_PLATFORMS, G as UiPlatformBucket, H as VocabularyEntry, m as isApiBucket, E as snapshotFeatureKey } from './types-B7ArIjwo.cjs';
3
3
 
4
+ /**
5
+ * Copies a legacy `app` bucket into whichever of `graphql`/`http` an entry lacks.
6
+ *
7
+ * Documents written before the API surfaces were entitled separately — old snapshots, old
8
+ * credential grants, old deny-lists — carry one `app` bucket that meant "any API surface".
9
+ * Normalising at read time is what lets those documents keep working without a re-key: an
10
+ * `app` grant admits both surfaces, and an `app` lock fails closed on both. Entries already
11
+ * carrying a surface bucket keep it — the legacy value only fills gaps.
12
+ */
13
+ declare function normalizeApiBuckets<T extends PlatformCodes | PlatformDenyCodes>(doc: Record<string, T> | undefined): Record<string, T> | undefined;
4
14
  declare function featureAppliesAtNode(applicableSiteTypes: SiteType[], siteType: SiteType): boolean;
5
15
  declare function findFeatureByCode(snapshot: VersionSnapshot, code: string): SnapshotFeature | undefined;
6
16
  declare function buildSiteCatalog(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks: SiteFeatureLocks | undefined, bucket: PlatformBucket, siteType?: SiteType, scope?: ScopeType, availableServices?: ServiceCode[]): FeatureCatalogEntry[];
7
17
  declare function isPlanMember(entry: PlatformCodes | undefined): boolean;
18
+ /**
19
+ * Whether a feature's declared API surfaces admit a caller's surface.
20
+ *
21
+ * Lenient on either side being unknown: a pre-flag snapshot declares nothing (`undefined`), and a
22
+ * caller resolving without a surface (cloud's matrix builders, UI buckets) filters nothing. Strictness
23
+ * comes from both sides being present — including a declared `[]`, which admits no surface at all.
24
+ */
25
+ declare function surfaceAllows(surfaces: ApiSurface[] | undefined, surface: ApiSurface | undefined): boolean;
8
26
  declare function isSiteLockedOnPlatform(entry: SiteFeatureLocks[string] | undefined, platform: PlatformBucket, code: string): boolean;
9
27
  declare function buildSiteRoles(snapshot: VersionSnapshot, businessCode: string | undefined): RoleItem[];
10
28
 
@@ -29,10 +47,10 @@ declare function filterGrantedByDeps(granted: Set<string>, deps: DependsMap): Se
29
47
  * The caller's surface, as the caller reports it.
30
48
  *
31
49
  * Finer than `PlatformBucket` on the mobile side — `ios` and `android` load different remote
32
- * entries but share one grant bucket. `app` is one-to-one with its bucket: an API client has no
33
- * variants because it has no UI.
50
+ * entries but share one grant bucket. The API platforms are one-to-one with their buckets: an
51
+ * API client has no variants because it has no UI.
34
52
  */
35
- type ClientPlatform = 'web' | 'ios' | 'android' | 'app';
53
+ type ClientPlatform = 'web' | 'ios' | 'android' | 'graphql' | 'http';
36
54
  interface LockedPermission {
37
55
  code: string;
38
56
  reason: LockReason | null;
@@ -107,7 +125,8 @@ interface SiteMatrixPermission {
107
125
  dependsOn: string[];
108
126
  web: SiteMatrixCell | null;
109
127
  mobile: SiteMatrixCell | null;
110
- app: SiteMatrixCell | null;
128
+ graphql: SiteMatrixCell | null;
129
+ http: SiteMatrixCell | null;
111
130
  }
112
131
  interface SiteMatrixFeature {
113
132
  code: string;
@@ -118,6 +137,7 @@ interface SiteMatrixFeature {
118
137
  platforms: PlatformBucket[];
119
138
  inPlan: boolean;
120
139
  availableIn: string[];
140
+ apiSurfaces?: ApiSurface[];
121
141
  permissions: SiteMatrixPermission[];
122
142
  }
123
143
  interface SiteMatrixApp {
@@ -139,4 +159,4 @@ interface SiteMatrix {
139
159
  declare function buildSiteMatrix(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks: SiteFeatureLocks | undefined, siteType?: SiteType): SiteMatrix;
140
160
  declare function buildPlanMatrix(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks?: SiteFeatureLocks): SiteMatrix;
141
161
 
142
- export { type ClientPlatform, type ComposeRoleGrantsParams, type DependsMap, FeatureCatalogEntry, FeatureUnlocks, LockReason, type LockedPermission, type PermissionFeature, PlatformBucket, PlatformCodes, PlatformDenyCodes, type ResolveUserFeaturesParams, type RevokedGrants, RoleItem, ScopeType, ServiceCode, SiteFeatureLocks, type SiteMatrix, type SiteMatrixApp, type SiteMatrixCell, type SiteMatrixFeature, type SiteMatrixPermission, SiteType, SnapshotFeature, VersionSnapshot, buildDependsMap, buildPlanMatrix, buildSiteCatalog, buildSiteMatrix, buildSiteRoles, cascadeLocked, composeRoleGrants, featureAppliesAtNode, filterGrantedByDeps, findFeatureByCode, isPlanMember, isSiteLockedOnPlatform, pickRouteForPlatform, prereqClosure, resolveUserFeatures };
162
+ export { ApiSurface, type ClientPlatform, type ComposeRoleGrantsParams, type DependsMap, FeatureCatalogEntry, FeatureUnlocks, LockReason, type LockedPermission, type PermissionFeature, PlatformBucket, PlatformCodes, PlatformDenyCodes, type ResolveUserFeaturesParams, type RevokedGrants, RoleItem, ScopeType, ServiceCode, SiteFeatureLocks, type SiteMatrix, type SiteMatrixApp, type SiteMatrixCell, type SiteMatrixFeature, type SiteMatrixPermission, SiteType, SnapshotFeature, VersionSnapshot, buildDependsMap, buildPlanMatrix, buildSiteCatalog, buildSiteMatrix, buildSiteRoles, cascadeLocked, composeRoleGrants, featureAppliesAtNode, filterGrantedByDeps, findFeatureByCode, isPlanMember, isSiteLockedOnPlatform, normalizeApiBuckets, pickRouteForPlatform, prereqClosure, resolveUserFeatures, surfaceAllows };
@@ -1,10 +1,28 @@
1
- import { V as VersionSnapshot, S as SiteFeatureLocks, P as PlatformBucket, a as SiteType, b as ScopeType, c as ServiceCode, F as FeatureCatalogEntry, R as RoleItem, d as SnapshotFeature, e as PlatformCodes, f as FeatureUnlocks, g as PlatformDenyCodes, L as LockReason } from './types-CG-Ynwyf.js';
2
- export { B as BusinessVocabulary, C as CatalogPermission, h as FeatureLocks, i as PLATFORMS, j as SERVICE_CODES, k as SITE_TYPES, l as SNAPSHOT_SCHEMA_VERSION, m as SnapshotApp, n as SnapshotAppFeatureRef, o as SnapshotBusiness, p as SnapshotMicrofrontendMobile, r as SnapshotMicrofrontendWeb, q as SnapshotMicrofrontends, s as SnapshotPermission, t as SnapshotPlan, u as SnapshotRoleTemplate, w as VocabularyEntry, v as snapshotFeatureKey } from './types-CG-Ynwyf.js';
1
+ import { V as VersionSnapshot, S as SiteFeatureLocks, P as PlatformBucket, a as SiteType, b as ScopeType, c as ServiceCode, F as FeatureCatalogEntry, R as RoleItem, d as SnapshotFeature, e as PlatformCodes, f as PlatformDenyCodes, A as ApiSurface, g as FeatureUnlocks, L as LockReason } from './types-B7ArIjwo.js';
2
+ export { h as API_BUCKETS, i as API_SURFACES, j as ApiBucket, B as BUCKET_BY_SURFACE, k as BusinessVocabulary, C as CatalogPermission, l as FeatureLocks, n as PLATFORMS, o as SERVICE_CODES, p as SITE_TYPES, q as SNAPSHOT_SCHEMA_VERSION, D as SURFACE_BY_BUCKET, r as SnapshotApp, s as SnapshotAppFeatureRef, t as SnapshotBusiness, u as SnapshotMicrofrontendMobile, w as SnapshotMicrofrontendWeb, v as SnapshotMicrofrontends, x as SnapshotPermission, y as SnapshotPlan, z as SnapshotRoleTemplate, U as UI_PLATFORMS, G as UiPlatformBucket, H as VocabularyEntry, m as isApiBucket, E as snapshotFeatureKey } from './types-B7ArIjwo.js';
3
3
 
4
+ /**
5
+ * Copies a legacy `app` bucket into whichever of `graphql`/`http` an entry lacks.
6
+ *
7
+ * Documents written before the API surfaces were entitled separately — old snapshots, old
8
+ * credential grants, old deny-lists — carry one `app` bucket that meant "any API surface".
9
+ * Normalising at read time is what lets those documents keep working without a re-key: an
10
+ * `app` grant admits both surfaces, and an `app` lock fails closed on both. Entries already
11
+ * carrying a surface bucket keep it — the legacy value only fills gaps.
12
+ */
13
+ declare function normalizeApiBuckets<T extends PlatformCodes | PlatformDenyCodes>(doc: Record<string, T> | undefined): Record<string, T> | undefined;
4
14
  declare function featureAppliesAtNode(applicableSiteTypes: SiteType[], siteType: SiteType): boolean;
5
15
  declare function findFeatureByCode(snapshot: VersionSnapshot, code: string): SnapshotFeature | undefined;
6
16
  declare function buildSiteCatalog(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks: SiteFeatureLocks | undefined, bucket: PlatformBucket, siteType?: SiteType, scope?: ScopeType, availableServices?: ServiceCode[]): FeatureCatalogEntry[];
7
17
  declare function isPlanMember(entry: PlatformCodes | undefined): boolean;
18
+ /**
19
+ * Whether a feature's declared API surfaces admit a caller's surface.
20
+ *
21
+ * Lenient on either side being unknown: a pre-flag snapshot declares nothing (`undefined`), and a
22
+ * caller resolving without a surface (cloud's matrix builders, UI buckets) filters nothing. Strictness
23
+ * comes from both sides being present — including a declared `[]`, which admits no surface at all.
24
+ */
25
+ declare function surfaceAllows(surfaces: ApiSurface[] | undefined, surface: ApiSurface | undefined): boolean;
8
26
  declare function isSiteLockedOnPlatform(entry: SiteFeatureLocks[string] | undefined, platform: PlatformBucket, code: string): boolean;
9
27
  declare function buildSiteRoles(snapshot: VersionSnapshot, businessCode: string | undefined): RoleItem[];
10
28
 
@@ -29,10 +47,10 @@ declare function filterGrantedByDeps(granted: Set<string>, deps: DependsMap): Se
29
47
  * The caller's surface, as the caller reports it.
30
48
  *
31
49
  * Finer than `PlatformBucket` on the mobile side — `ios` and `android` load different remote
32
- * entries but share one grant bucket. `app` is one-to-one with its bucket: an API client has no
33
- * variants because it has no UI.
50
+ * entries but share one grant bucket. The API platforms are one-to-one with their buckets: an
51
+ * API client has no variants because it has no UI.
34
52
  */
35
- type ClientPlatform = 'web' | 'ios' | 'android' | 'app';
53
+ type ClientPlatform = 'web' | 'ios' | 'android' | 'graphql' | 'http';
36
54
  interface LockedPermission {
37
55
  code: string;
38
56
  reason: LockReason | null;
@@ -107,7 +125,8 @@ interface SiteMatrixPermission {
107
125
  dependsOn: string[];
108
126
  web: SiteMatrixCell | null;
109
127
  mobile: SiteMatrixCell | null;
110
- app: SiteMatrixCell | null;
128
+ graphql: SiteMatrixCell | null;
129
+ http: SiteMatrixCell | null;
111
130
  }
112
131
  interface SiteMatrixFeature {
113
132
  code: string;
@@ -118,6 +137,7 @@ interface SiteMatrixFeature {
118
137
  platforms: PlatformBucket[];
119
138
  inPlan: boolean;
120
139
  availableIn: string[];
140
+ apiSurfaces?: ApiSurface[];
121
141
  permissions: SiteMatrixPermission[];
122
142
  }
123
143
  interface SiteMatrixApp {
@@ -139,4 +159,4 @@ interface SiteMatrix {
139
159
  declare function buildSiteMatrix(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks: SiteFeatureLocks | undefined, siteType?: SiteType): SiteMatrix;
140
160
  declare function buildPlanMatrix(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks?: SiteFeatureLocks): SiteMatrix;
141
161
 
142
- export { type ClientPlatform, type ComposeRoleGrantsParams, type DependsMap, FeatureCatalogEntry, FeatureUnlocks, LockReason, type LockedPermission, type PermissionFeature, PlatformBucket, PlatformCodes, PlatformDenyCodes, type ResolveUserFeaturesParams, type RevokedGrants, RoleItem, ScopeType, ServiceCode, SiteFeatureLocks, type SiteMatrix, type SiteMatrixApp, type SiteMatrixCell, type SiteMatrixFeature, type SiteMatrixPermission, SiteType, SnapshotFeature, VersionSnapshot, buildDependsMap, buildPlanMatrix, buildSiteCatalog, buildSiteMatrix, buildSiteRoles, cascadeLocked, composeRoleGrants, featureAppliesAtNode, filterGrantedByDeps, findFeatureByCode, isPlanMember, isSiteLockedOnPlatform, pickRouteForPlatform, prereqClosure, resolveUserFeatures };
162
+ export { ApiSurface, type ClientPlatform, type ComposeRoleGrantsParams, type DependsMap, FeatureCatalogEntry, FeatureUnlocks, LockReason, type LockedPermission, type PermissionFeature, PlatformBucket, PlatformCodes, PlatformDenyCodes, type ResolveUserFeaturesParams, type RevokedGrants, RoleItem, ScopeType, ServiceCode, SiteFeatureLocks, type SiteMatrix, type SiteMatrixApp, type SiteMatrixCell, type SiteMatrixFeature, type SiteMatrixPermission, SiteType, SnapshotFeature, VersionSnapshot, buildDependsMap, buildPlanMatrix, buildSiteCatalog, buildSiteMatrix, buildSiteRoles, cascadeLocked, composeRoleGrants, featureAppliesAtNode, filterGrantedByDeps, findFeatureByCode, isPlanMember, isSiteLockedOnPlatform, normalizeApiBuckets, pickRouteForPlatform, prereqClosure, resolveUserFeatures, surfaceAllows };
@@ -75,12 +75,33 @@ __name(filterGrantedByDeps, "filterGrantedByDeps");
75
75
  var PLATFORMS = [
76
76
  "web",
77
77
  "mobile",
78
- "app"
78
+ "graphql",
79
+ "http"
79
80
  ];
80
81
  var UI_PLATFORMS = [
81
82
  "web",
82
83
  "mobile"
83
84
  ];
85
+ var API_SURFACES = [
86
+ "GRAPHQL",
87
+ "HTTP"
88
+ ];
89
+ var API_BUCKETS = [
90
+ "graphql",
91
+ "http"
92
+ ];
93
+ var SURFACE_BY_BUCKET = {
94
+ graphql: "GRAPHQL",
95
+ http: "HTTP"
96
+ };
97
+ var BUCKET_BY_SURFACE = {
98
+ GRAPHQL: "graphql",
99
+ HTTP: "http"
100
+ };
101
+ function isApiBucket(bucket) {
102
+ return bucket === "graphql" || bucket === "http";
103
+ }
104
+ __name(isApiBucket, "isApiBucket");
84
105
  var SITE_TYPES = [
85
106
  "OUTLET",
86
107
  "WAREHOUSE",
@@ -93,9 +114,47 @@ function snapshotFeatureKey(code, scope) {
93
114
  return `${scope}.${code}`;
94
115
  }
95
116
  __name(snapshotFeatureKey, "snapshotFeatureKey");
96
- var SNAPSHOT_SCHEMA_VERSION = 2;
117
+ var SNAPSHOT_SCHEMA_VERSION = 4;
97
118
 
98
119
  // src/catalog-resolver/catalog.builder.ts
120
+ function normalizeApiBuckets(doc) {
121
+ if (!doc) return doc;
122
+ let changed = false;
123
+ const next = {};
124
+ for (const [code, entry] of Object.entries(doc)) {
125
+ const legacy = entry.app;
126
+ if (legacy === void 0 || entry.graphql !== void 0 && entry.http !== void 0) {
127
+ next[code] = entry;
128
+ continue;
129
+ }
130
+ changed = true;
131
+ next[code] = {
132
+ ...entry,
133
+ ...entry.graphql === void 0 ? {
134
+ graphql: legacy
135
+ } : {},
136
+ ...entry.http === void 0 ? {
137
+ http: legacy
138
+ } : {}
139
+ };
140
+ }
141
+ return changed ? next : doc;
142
+ }
143
+ __name(normalizeApiBuckets, "normalizeApiBuckets");
144
+ function normalizePlans(plans) {
145
+ let changed = false;
146
+ const next = {};
147
+ for (const [code, plan] of Object.entries(plans)) {
148
+ const unlockedPermissions = normalizeApiBuckets(plan.unlockedPermissions) ?? {};
149
+ if (unlockedPermissions !== plan.unlockedPermissions) changed = true;
150
+ next[code] = unlockedPermissions === plan.unlockedPermissions ? plan : {
151
+ ...plan,
152
+ unlockedPermissions
153
+ };
154
+ }
155
+ return changed ? next : plans;
156
+ }
157
+ __name(normalizePlans, "normalizePlans");
99
158
  function featureAppliesAtNode(applicableSiteTypes, siteType) {
100
159
  return applicableSiteTypes.includes(siteType);
101
160
  }
@@ -111,26 +170,28 @@ function buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, s
111
170
  if (!businessCode) return [];
112
171
  const business = snapshot.businesses?.[businessCode];
113
172
  if (!business) return [];
114
- const plan = planCode ? business.plans?.[planCode] : void 0;
115
- const plans = business.plans ?? {};
173
+ const plans = normalizePlans(business.plans ?? {});
174
+ const plan = planCode ? plans[planCode] : void 0;
175
+ const locks = normalizeApiBuckets(siteLocks);
116
176
  const catalog = [];
117
177
  const sortedApps = [
118
178
  ...business.apps
119
179
  ].sort((a, b) => a.name.localeCompare(b.name));
120
180
  for (const app of sortedApps) {
121
181
  const businessAppFeatures = app.features.filter((ref) => scope === void 0 || ref.scope === scope).map((ref) => snapshot.features?.[snapshotFeatureKey(ref.code, ref.scope)]).filter((f) => !!f && // A UI bucket needs something to render, so a feature shipping no microfrontend is dropped.
122
- // The `app` bucket renders nothing — dropping headless features there would make them
123
- // permanently ungrantable to an API client.
124
- (bucket === "app" || !!(f.microfrontends?.web || f.microfrontends?.mobile)) && (siteType === void 0 || featureAppliesAtNode(f.applicableSiteTypes, siteType)));
182
+ // An API bucket renders nothing — there a feature is admitted by the surfaces it declares
183
+ // instead, so a GRAPHQL credential never resolves an HTTP-only feature. A surface-excluded
184
+ // feature vanishes from the catalog entirely, which is what makes resolution fail closed.
185
+ (isApiBucket(bucket) ? surfaceAllows(f.apiSurfaces, SURFACE_BY_BUCKET[bucket]) : !!(f.microfrontends?.web || f.microfrontends?.mobile)) && (siteType === void 0 || featureAppliesAtNode(f.applicableSiteTypes, siteType)));
125
186
  if (businessAppFeatures.length === 0) continue;
126
187
  for (const feature of businessAppFeatures) {
127
188
  const membership = plan?.unlockedPermissions?.[feature.code];
128
189
  const web = feature.microfrontends?.web;
129
190
  const mobile = feature.microfrontends?.mobile;
130
191
  const memberOnBucket = membership?.[bucket] !== void 0;
131
- const sitePlatformLocked = siteLocks?.[feature.code]?.[bucket] === null;
192
+ const sitePlatformLocked = locks?.[feature.code]?.[bucket] === null;
132
193
  const missingServices = unmetServices(feature, availableServices);
133
- const permissions = buildPermissions(feature, businessCode, membership, siteLocks, plans, bucket, missingServices);
194
+ const permissions = buildPermissions(feature, businessCode, membership, locks, plans, bucket, missingServices);
134
195
  const lockReason = resolveLockReason(!memberOnBucket, sitePlatformLocked, missingServices);
135
196
  const locked = lockReason !== null;
136
197
  const unlockPlans = lockReason === "PLAN" ? plansIncludingFeature(plans, feature.code, bucket) : [];
@@ -167,9 +228,14 @@ function buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, s
167
228
  }
168
229
  __name(buildSiteCatalog, "buildSiteCatalog");
169
230
  function isPlanMember(entry) {
170
- return !!entry && (entry.web !== void 0 || entry.mobile !== void 0);
231
+ if (!entry) return false;
232
+ return PLATFORMS.some((platform) => entry[platform] !== void 0) || entry.app !== void 0;
171
233
  }
172
234
  __name(isPlanMember, "isPlanMember");
235
+ function surfaceAllows(surfaces, surface) {
236
+ return surface === void 0 || surfaces === void 0 || surfaces.includes(surface);
237
+ }
238
+ __name(surfaceAllows, "surfaceAllows");
173
239
  function resolveLockReason(planLocked, siteLocked, missingServices) {
174
240
  if (planLocked) return "PLAN";
175
241
  if (siteLocked) return "SITE";
@@ -279,7 +345,7 @@ function composeRoleGrants(params) {
279
345
  if (revoke === null) continue;
280
346
  composed[bucket] = revoke === void 0 ? merged : merged.filter((c) => !revoke.includes(c));
281
347
  }
282
- if (composed.web === void 0 && composed.mobile === void 0) continue;
348
+ if (PLATFORMS.every((bucket) => composed[bucket] === void 0)) continue;
283
349
  result[code] = composed;
284
350
  }
285
351
  return result;
@@ -287,21 +353,35 @@ function composeRoleGrants(params) {
287
353
  __name(composeRoleGrants, "composeRoleGrants");
288
354
 
289
355
  // src/catalog-resolver/resolve-user-features.ts
356
+ var BUCKET_BY_CLIENT = {
357
+ web: "web",
358
+ ios: "mobile",
359
+ android: "mobile",
360
+ graphql: "graphql",
361
+ http: "http"
362
+ };
290
363
  var EMPTY_ROUTE = {
291
364
  remoteEntry: "",
292
365
  exposedModule: "",
293
366
  routePrefix: ""
294
367
  };
295
368
  function resolveUserFeatures(params) {
296
- const { snapshot, businessCode, planCode, siteLocks, roleFeatures, platform, siteType, scope, availableServices } = params;
297
- const bucket = platform === "web" ? "web" : platform === "app" ? "app" : "mobile";
369
+ const { snapshot, businessCode, planCode, siteLocks, platform, siteType, scope, availableServices } = params;
370
+ const bucket = BUCKET_BY_CLIENT[platform];
371
+ const roleFeatures = normalizeApiBuckets(params.roleFeatures) ?? {};
298
372
  const featureByCode = /* @__PURE__ */ __name((code) => scope ? snapshot.features?.[snapshotFeatureKey(code, scope)] : findFeatureByCode(snapshot, code), "featureByCode");
299
373
  const catalog = buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, siteType, scope, availableServices);
300
374
  const catalogMap = new Map(catalog.map((f) => [
301
375
  f.code,
302
376
  f
303
377
  ]));
304
- const businessPlans = snapshot.businesses[businessCode]?.plans ?? {};
378
+ const businessPlans = Object.fromEntries(Object.entries(snapshot.businesses[businessCode]?.plans ?? {}).map(([code, plan]) => [
379
+ code,
380
+ {
381
+ ...plan,
382
+ unlockedPermissions: normalizeApiBuckets(plan.unlockedPermissions) ?? {}
383
+ }
384
+ ]));
305
385
  const currentUnlockedCodes = /* @__PURE__ */ new Set();
306
386
  if (planCode && businessPlans[planCode]) {
307
387
  for (const [featureCode, platforms] of Object.entries(businessPlans[planCode].unlockedPermissions ?? {})) {
@@ -333,7 +413,7 @@ function resolveUserFeatures(params) {
333
413
  for (const [code, permsSet] of grantedFeatures) {
334
414
  const catalogEntry = catalogMap.get(code);
335
415
  if (!catalogEntry) continue;
336
- const route = bucket === "app" ? EMPTY_ROUTE : pickRouteForPlatform(catalogEntry, platform);
416
+ const route = isApiBucket(bucket) ? EMPTY_ROUTE : pickRouteForPlatform(catalogEntry, platform);
337
417
  if (!route) continue;
338
418
  const featureDeps = buildDependsMap(featureByCode(code)?.permissions ?? []);
339
419
  const permByCode = new Map((catalogEntry.permissions ?? []).map((p) => [
@@ -378,6 +458,7 @@ function resolveUserFeatures(params) {
378
458
  }
379
459
  __name(resolveUserFeatures, "resolveUserFeatures");
380
460
  function pickRouteForPlatform(entry, platform) {
461
+ if (platform === "graphql" || platform === "http") return null;
381
462
  if (platform === "ios" || platform === "android") {
382
463
  if (!entry.mobile) return null;
383
464
  return {
@@ -406,13 +487,20 @@ function buildPlanMatrix(snapshot, businessCode, planCode, siteLocks) {
406
487
  __name(buildPlanMatrix, "buildPlanMatrix");
407
488
  function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, siteType) {
408
489
  const business = businessCode ? snapshot.businesses?.[businessCode] : void 0;
409
- const plans = business?.plans ?? {};
490
+ const plans = Object.fromEntries(Object.entries(business?.plans ?? {}).map(([code, p]) => [
491
+ code,
492
+ {
493
+ ...p,
494
+ unlockedPermissions: normalizeApiBuckets(p.unlockedPermissions) ?? {}
495
+ }
496
+ ]));
410
497
  const plan = planCode ? plans[planCode] : void 0;
411
498
  const planMeta = {
412
499
  code: planCode ?? "",
413
500
  name: plan?.name ?? planCode ?? ""
414
501
  };
415
- const locks = siteLocks ?? {};
502
+ const normalizedLocks = normalizeApiBuckets(siteLocks);
503
+ const locks = normalizedLocks ?? {};
416
504
  if (!business || !plan) return {
417
505
  plan: planMeta,
418
506
  apps: [],
@@ -431,11 +519,11 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
431
519
  if (siteType !== void 0 && !featureAppliesAtNode(feature.applicableSiteTypes, siteType)) continue;
432
520
  const platforms = [
433
521
  ...UI_PLATFORMS.filter((p) => !!feature.microfrontends?.[p]),
434
- "app"
522
+ ...API_BUCKETS.filter((b) => feature.apiSurfaces === void 0 || feature.apiSurfaces.includes(SURFACE_BY_BUCKET[b]))
435
523
  ];
436
524
  const membership = plan.unlockedPermissions?.[code];
437
525
  const featureInPlan = isPlanMember(membership);
438
- const siteEntry = siteLocks?.[code];
526
+ const siteEntry = normalizedLocks?.[code];
439
527
  const permissions = (feature.permissions ?? []).filter((p) => p.isGlobal || p.businesses.includes(businessCode ?? "")).map((p) => {
440
528
  const cell = /* @__PURE__ */ __name((plat) => {
441
529
  if (!platforms.includes(plat)) return null;
@@ -457,7 +545,8 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
457
545
  dependsOn: p.dependsOn ?? [],
458
546
  web: cell("web"),
459
547
  mobile: cell("mobile"),
460
- app: cell("app")
548
+ graphql: cell("graphql"),
549
+ http: cell("http")
461
550
  };
462
551
  });
463
552
  features.push({
@@ -469,6 +558,7 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
469
558
  platforms,
470
559
  inPlan: featureInPlan,
471
560
  availableIn: featureInPlan ? [] : plansIncludingFeature2(plans, code, planCode),
561
+ apiSurfaces: feature.apiSurfaces,
472
562
  permissions
473
563
  });
474
564
  }
@@ -509,10 +599,15 @@ function plansIncludingFeature2(plans, featureCode, excludeCode) {
509
599
  }
510
600
  __name(plansIncludingFeature2, "plansIncludingFeature");
511
601
  export {
602
+ API_BUCKETS,
603
+ API_SURFACES,
604
+ BUCKET_BY_SURFACE,
512
605
  PLATFORMS,
513
606
  SERVICE_CODES,
514
607
  SITE_TYPES,
515
608
  SNAPSHOT_SCHEMA_VERSION,
609
+ SURFACE_BY_BUCKET,
610
+ UI_PLATFORMS,
516
611
  buildDependsMap,
517
612
  buildPlanMatrix,
518
613
  buildSiteCatalog,
@@ -523,11 +618,14 @@ export {
523
618
  featureAppliesAtNode,
524
619
  filterGrantedByDeps,
525
620
  findFeatureByCode,
621
+ isApiBucket,
526
622
  isPlanMember,
527
623
  isSiteLockedOnPlatform,
624
+ normalizeApiBuckets,
528
625
  pickRouteForPlatform,
529
626
  prereqClosure,
530
627
  resolveUserFeatures,
531
- snapshotFeatureKey
628
+ snapshotFeatureKey,
629
+ surfaceAllows
532
630
  };
533
631
  //# sourceMappingURL=catalog-resolver.js.map