@vritti/api-sdk 0.3.12 → 0.3.14

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-BBK6MG-U.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-BBK6MG-U.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
 
@@ -25,7 +43,14 @@ declare function prereqClosure(code: string, deps: DependsMap): string[];
25
43
  declare function cascadeLocked(codes: string[], directlyLocked: Set<string>, deps: DependsMap): Set<string>;
26
44
  declare function filterGrantedByDeps(granted: Set<string>, deps: DependsMap): Set<string>;
27
45
 
28
- type ClientPlatform = 'web' | 'ios' | 'android';
46
+ /**
47
+ * The caller's surface, as the caller reports it.
48
+ *
49
+ * Finer than `PlatformBucket` on the mobile side — `ios` and `android` load different remote
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.
52
+ */
53
+ type ClientPlatform = 'web' | 'ios' | 'android' | 'graphql' | 'http';
29
54
  interface LockedPermission {
30
55
  code: string;
31
56
  reason: LockReason | null;
@@ -100,6 +125,8 @@ interface SiteMatrixPermission {
100
125
  dependsOn: string[];
101
126
  web: SiteMatrixCell | null;
102
127
  mobile: SiteMatrixCell | null;
128
+ graphql: SiteMatrixCell | null;
129
+ http: SiteMatrixCell | null;
103
130
  }
104
131
  interface SiteMatrixFeature {
105
132
  code: string;
@@ -110,6 +137,7 @@ interface SiteMatrixFeature {
110
137
  platforms: PlatformBucket[];
111
138
  inPlan: boolean;
112
139
  availableIn: string[];
140
+ apiSurfaces?: ApiSurface[];
113
141
  permissions: SiteMatrixPermission[];
114
142
  }
115
143
  interface SiteMatrixApp {
@@ -131,4 +159,4 @@ interface SiteMatrix {
131
159
  declare function buildSiteMatrix(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks: SiteFeatureLocks | undefined, siteType?: SiteType): SiteMatrix;
132
160
  declare function buildPlanMatrix(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks?: SiteFeatureLocks): SiteMatrix;
133
161
 
134
- 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-BBK6MG-U.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-BBK6MG-U.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
 
@@ -25,7 +43,14 @@ declare function prereqClosure(code: string, deps: DependsMap): string[];
25
43
  declare function cascadeLocked(codes: string[], directlyLocked: Set<string>, deps: DependsMap): Set<string>;
26
44
  declare function filterGrantedByDeps(granted: Set<string>, deps: DependsMap): Set<string>;
27
45
 
28
- type ClientPlatform = 'web' | 'ios' | 'android';
46
+ /**
47
+ * The caller's surface, as the caller reports it.
48
+ *
49
+ * Finer than `PlatformBucket` on the mobile side — `ios` and `android` load different remote
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.
52
+ */
53
+ type ClientPlatform = 'web' | 'ios' | 'android' | 'graphql' | 'http';
29
54
  interface LockedPermission {
30
55
  code: string;
31
56
  reason: LockReason | null;
@@ -100,6 +125,8 @@ interface SiteMatrixPermission {
100
125
  dependsOn: string[];
101
126
  web: SiteMatrixCell | null;
102
127
  mobile: SiteMatrixCell | null;
128
+ graphql: SiteMatrixCell | null;
129
+ http: SiteMatrixCell | null;
103
130
  }
104
131
  interface SiteMatrixFeature {
105
132
  code: string;
@@ -110,6 +137,7 @@ interface SiteMatrixFeature {
110
137
  platforms: PlatformBucket[];
111
138
  inPlan: boolean;
112
139
  availableIn: string[];
140
+ apiSurfaces?: ApiSurface[];
113
141
  permissions: SiteMatrixPermission[];
114
142
  }
115
143
  interface SiteMatrixApp {
@@ -131,4 +159,4 @@ interface SiteMatrix {
131
159
  declare function buildSiteMatrix(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks: SiteFeatureLocks | undefined, siteType?: SiteType): SiteMatrix;
132
160
  declare function buildPlanMatrix(snapshot: VersionSnapshot, businessCode: string | undefined, planCode: string | undefined, siteLocks?: SiteFeatureLocks): SiteMatrix;
133
161
 
134
- 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 };
@@ -73,9 +73,35 @@ __name(filterGrantedByDeps, "filterGrantedByDeps");
73
73
 
74
74
  // src/catalog-resolver/types.ts
75
75
  var PLATFORMS = [
76
+ "web",
77
+ "mobile",
78
+ "graphql",
79
+ "http"
80
+ ];
81
+ var UI_PLATFORMS = [
76
82
  "web",
77
83
  "mobile"
78
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");
79
105
  var SITE_TYPES = [
80
106
  "OUTLET",
81
107
  "WAREHOUSE",
@@ -88,9 +114,47 @@ function snapshotFeatureKey(code, scope) {
88
114
  return `${scope}.${code}`;
89
115
  }
90
116
  __name(snapshotFeatureKey, "snapshotFeatureKey");
91
- var SNAPSHOT_SCHEMA_VERSION = 2;
117
+ var SNAPSHOT_SCHEMA_VERSION = 4;
92
118
 
93
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");
94
158
  function featureAppliesAtNode(applicableSiteTypes, siteType) {
95
159
  return applicableSiteTypes.includes(siteType);
96
160
  }
@@ -106,23 +170,28 @@ function buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, s
106
170
  if (!businessCode) return [];
107
171
  const business = snapshot.businesses?.[businessCode];
108
172
  if (!business) return [];
109
- const plan = planCode ? business.plans?.[planCode] : void 0;
110
- const plans = business.plans ?? {};
173
+ const plans = normalizePlans(business.plans ?? {});
174
+ const plan = planCode ? plans[planCode] : void 0;
175
+ const locks = normalizeApiBuckets(siteLocks);
111
176
  const catalog = [];
112
177
  const sortedApps = [
113
178
  ...business.apps
114
179
  ].sort((a, b) => a.name.localeCompare(b.name));
115
180
  for (const app of sortedApps) {
116
- const businessAppFeatures = app.features.filter((ref) => scope === void 0 || ref.scope === scope).map((ref) => snapshot.features?.[snapshotFeatureKey(ref.code, ref.scope)]).filter((f) => !!f && !!(f.microfrontends?.web || f.microfrontends?.mobile) && (siteType === void 0 || featureAppliesAtNode(f.applicableSiteTypes, siteType)));
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.
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)));
117
186
  if (businessAppFeatures.length === 0) continue;
118
187
  for (const feature of businessAppFeatures) {
119
188
  const membership = plan?.unlockedPermissions?.[feature.code];
120
189
  const web = feature.microfrontends?.web;
121
190
  const mobile = feature.microfrontends?.mobile;
122
191
  const memberOnBucket = membership?.[bucket] !== void 0;
123
- const sitePlatformLocked = siteLocks?.[feature.code]?.[bucket] === null;
192
+ const sitePlatformLocked = locks?.[feature.code]?.[bucket] === null;
124
193
  const missingServices = unmetServices(feature, availableServices);
125
- const permissions = buildPermissions(feature, businessCode, membership, siteLocks, plans, bucket, missingServices);
194
+ const permissions = buildPermissions(feature, businessCode, membership, locks, plans, bucket, missingServices);
126
195
  const lockReason = resolveLockReason(!memberOnBucket, sitePlatformLocked, missingServices);
127
196
  const locked = lockReason !== null;
128
197
  const unlockPlans = lockReason === "PLAN" ? plansIncludingFeature(plans, feature.code, bucket) : [];
@@ -159,9 +228,14 @@ function buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, s
159
228
  }
160
229
  __name(buildSiteCatalog, "buildSiteCatalog");
161
230
  function isPlanMember(entry) {
162
- 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;
163
233
  }
164
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");
165
239
  function resolveLockReason(planLocked, siteLocked, missingServices) {
166
240
  if (planLocked) return "PLAN";
167
241
  if (siteLocked) return "SITE";
@@ -271,7 +345,7 @@ function composeRoleGrants(params) {
271
345
  if (revoke === null) continue;
272
346
  composed[bucket] = revoke === void 0 ? merged : merged.filter((c) => !revoke.includes(c));
273
347
  }
274
- if (composed.web === void 0 && composed.mobile === void 0) continue;
348
+ if (PLATFORMS.every((bucket) => composed[bucket] === void 0)) continue;
275
349
  result[code] = composed;
276
350
  }
277
351
  return result;
@@ -279,16 +353,35 @@ function composeRoleGrants(params) {
279
353
  __name(composeRoleGrants, "composeRoleGrants");
280
354
 
281
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
+ };
363
+ var EMPTY_ROUTE = {
364
+ remoteEntry: "",
365
+ exposedModule: "",
366
+ routePrefix: ""
367
+ };
282
368
  function resolveUserFeatures(params) {
283
- const { snapshot, businessCode, planCode, siteLocks, roleFeatures, platform, siteType, scope, availableServices } = params;
284
- const bucket = platform === "web" ? "web" : "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) ?? {};
285
372
  const featureByCode = /* @__PURE__ */ __name((code) => scope ? snapshot.features?.[snapshotFeatureKey(code, scope)] : findFeatureByCode(snapshot, code), "featureByCode");
286
373
  const catalog = buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, siteType, scope, availableServices);
287
374
  const catalogMap = new Map(catalog.map((f) => [
288
375
  f.code,
289
376
  f
290
377
  ]));
291
- 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
+ ]));
292
385
  const currentUnlockedCodes = /* @__PURE__ */ new Set();
293
386
  if (planCode && businessPlans[planCode]) {
294
387
  for (const [featureCode, platforms] of Object.entries(businessPlans[planCode].unlockedPermissions ?? {})) {
@@ -320,7 +413,7 @@ function resolveUserFeatures(params) {
320
413
  for (const [code, permsSet] of grantedFeatures) {
321
414
  const catalogEntry = catalogMap.get(code);
322
415
  if (!catalogEntry) continue;
323
- const route = pickRouteForPlatform(catalogEntry, platform);
416
+ const route = isApiBucket(bucket) ? EMPTY_ROUTE : pickRouteForPlatform(catalogEntry, platform);
324
417
  if (!route) continue;
325
418
  const featureDeps = buildDependsMap(featureByCode(code)?.permissions ?? []);
326
419
  const permByCode = new Map((catalogEntry.permissions ?? []).map((p) => [
@@ -365,6 +458,7 @@ function resolveUserFeatures(params) {
365
458
  }
366
459
  __name(resolveUserFeatures, "resolveUserFeatures");
367
460
  function pickRouteForPlatform(entry, platform) {
461
+ if (platform === "graphql" || platform === "http") return null;
368
462
  if (platform === "ios" || platform === "android") {
369
463
  if (!entry.mobile) return null;
370
464
  return {
@@ -393,13 +487,20 @@ function buildPlanMatrix(snapshot, businessCode, planCode, siteLocks) {
393
487
  __name(buildPlanMatrix, "buildPlanMatrix");
394
488
  function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, siteType) {
395
489
  const business = businessCode ? snapshot.businesses?.[businessCode] : void 0;
396
- 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
+ ]));
397
497
  const plan = planCode ? plans[planCode] : void 0;
398
498
  const planMeta = {
399
499
  code: planCode ?? "",
400
500
  name: plan?.name ?? planCode ?? ""
401
501
  };
402
- const locks = siteLocks ?? {};
502
+ const normalizedLocks = normalizeApiBuckets(siteLocks);
503
+ const locks = normalizedLocks ?? {};
403
504
  if (!business || !plan) return {
404
505
  plan: planMeta,
405
506
  apps: [],
@@ -416,11 +517,13 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
416
517
  const feature = snapshot.features?.[snapshotFeatureKey(code, ref.scope)];
417
518
  if (!feature) continue;
418
519
  if (siteType !== void 0 && !featureAppliesAtNode(feature.applicableSiteTypes, siteType)) continue;
419
- const platforms = PLATFORMS.filter((p) => !!feature.microfrontends?.[p]);
420
- if (platforms.length === 0) continue;
520
+ const platforms = [
521
+ ...UI_PLATFORMS.filter((p) => !!feature.microfrontends?.[p]),
522
+ ...API_BUCKETS.filter((b) => feature.apiSurfaces === void 0 || feature.apiSurfaces.includes(SURFACE_BY_BUCKET[b]))
523
+ ];
421
524
  const membership = plan.unlockedPermissions?.[code];
422
525
  const featureInPlan = isPlanMember(membership);
423
- const siteEntry = siteLocks?.[code];
526
+ const siteEntry = normalizedLocks?.[code];
424
527
  const permissions = (feature.permissions ?? []).filter((p) => p.isGlobal || p.businesses.includes(businessCode ?? "")).map((p) => {
425
528
  const cell = /* @__PURE__ */ __name((plat) => {
426
529
  if (!platforms.includes(plat)) return null;
@@ -441,7 +544,9 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
441
544
  label: p.label,
442
545
  dependsOn: p.dependsOn ?? [],
443
546
  web: cell("web"),
444
- mobile: cell("mobile")
547
+ mobile: cell("mobile"),
548
+ graphql: cell("graphql"),
549
+ http: cell("http")
445
550
  };
446
551
  });
447
552
  features.push({
@@ -453,6 +558,7 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
453
558
  platforms,
454
559
  inPlan: featureInPlan,
455
560
  availableIn: featureInPlan ? [] : plansIncludingFeature2(plans, code, planCode),
561
+ apiSurfaces: feature.apiSurfaces,
456
562
  permissions
457
563
  });
458
564
  }
@@ -493,10 +599,15 @@ function plansIncludingFeature2(plans, featureCode, excludeCode) {
493
599
  }
494
600
  __name(plansIncludingFeature2, "plansIncludingFeature");
495
601
  export {
602
+ API_BUCKETS,
603
+ API_SURFACES,
604
+ BUCKET_BY_SURFACE,
496
605
  PLATFORMS,
497
606
  SERVICE_CODES,
498
607
  SITE_TYPES,
499
608
  SNAPSHOT_SCHEMA_VERSION,
609
+ SURFACE_BY_BUCKET,
610
+ UI_PLATFORMS,
500
611
  buildDependsMap,
501
612
  buildPlanMatrix,
502
613
  buildSiteCatalog,
@@ -507,11 +618,14 @@ export {
507
618
  featureAppliesAtNode,
508
619
  filterGrantedByDeps,
509
620
  findFeatureByCode,
621
+ isApiBucket,
510
622
  isPlanMember,
511
623
  isSiteLockedOnPlatform,
624
+ normalizeApiBuckets,
512
625
  pickRouteForPlatform,
513
626
  prereqClosure,
514
627
  resolveUserFeatures,
515
- snapshotFeatureKey
628
+ snapshotFeatureKey,
629
+ surfaceAllows
516
630
  };
517
631
  //# sourceMappingURL=catalog-resolver.js.map