@vritti/api-sdk 0.4.1 → 0.4.2

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.
@@ -114,78 +114,40 @@ function snapshotFeatureKey(code, scope) {
114
114
  return `${scope}.${code}`;
115
115
  }
116
116
  __name(snapshotFeatureKey, "snapshotFeatureKey");
117
- var SNAPSHOT_SCHEMA_VERSION = 4;
117
+ var SNAPSHOT_SCHEMA_VERSION = 5;
118
118
 
119
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");
158
120
  function featureAppliesAtNode(applicableSiteTypes, siteType) {
159
121
  return applicableSiteTypes.includes(siteType);
160
122
  }
161
123
  __name(featureAppliesAtNode, "featureAppliesAtNode");
162
124
  function findFeatureByCode(snapshot, code) {
163
- for (const feature of Object.values(snapshot.features ?? {})) {
164
- if (feature?.code === code) return feature;
125
+ for (const feature of Object.values(snapshot.features)) {
126
+ if (feature.code === code) return feature;
165
127
  }
166
128
  return void 0;
167
129
  }
168
130
  __name(findFeatureByCode, "findFeatureByCode");
169
131
  function buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, siteType, scope, availableServices = []) {
170
132
  if (!businessCode) return [];
171
- const business = snapshot.businesses?.[businessCode];
133
+ const business = snapshot.businesses[businessCode];
172
134
  if (!business) return [];
173
- const plans = normalizePlans(business.plans ?? {});
135
+ const plans = business.plans;
174
136
  const plan = planCode ? plans[planCode] : void 0;
175
- const locks = normalizeApiBuckets(siteLocks);
137
+ const locks = siteLocks;
176
138
  const catalog = [];
177
139
  const sortedApps = [
178
140
  ...business.apps
179
141
  ].sort((a, b) => a.name.localeCompare(b.name));
180
142
  for (const app of sortedApps) {
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.
143
+ 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
144
  // An API bucket renders nothing — there a feature is admitted by the surfaces it declares
183
145
  // instead, so a GRAPHQL credential never resolves an HTTP-only feature. A surface-excluded
184
146
  // feature vanishes from the catalog entirely, which is what makes resolution fail closed.
185
147
  (isApiBucket(bucket) ? surfaceAllows(f.apiSurfaces, SURFACE_BY_BUCKET[bucket]) : !!(f.microfrontends?.web || f.microfrontends?.mobile)) && (siteType === void 0 || featureAppliesAtNode(f.applicableSiteTypes, siteType)));
186
148
  if (businessAppFeatures.length === 0) continue;
187
149
  for (const feature of businessAppFeatures) {
188
- const membership = plan?.unlockedPermissions?.[feature.code];
150
+ const membership = plan?.unlockedPermissions[feature.code];
189
151
  const web = feature.microfrontends?.web;
190
152
  const mobile = feature.microfrontends?.mobile;
191
153
  const memberOnBucket = membership?.[bucket] !== void 0;
@@ -229,11 +191,11 @@ function buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, s
229
191
  __name(buildSiteCatalog, "buildSiteCatalog");
230
192
  function isPlanMember(entry) {
231
193
  if (!entry) return false;
232
- return PLATFORMS.some((platform) => entry[platform] !== void 0) || entry.app !== void 0;
194
+ return PLATFORMS.some((platform) => entry[platform] !== void 0);
233
195
  }
234
196
  __name(isPlanMember, "isPlanMember");
235
197
  function surfaceAllows(surfaces, surface) {
236
- return surface === void 0 || surfaces === void 0 || surfaces.includes(surface);
198
+ return surface === void 0 || surfaces.includes(surface);
237
199
  }
238
200
  __name(surfaceAllows, "surfaceAllows");
239
201
  function resolveLockReason(planLocked, siteLocked, missingServices) {
@@ -244,7 +206,7 @@ function resolveLockReason(planLocked, siteLocked, missingServices) {
244
206
  }
245
207
  __name(resolveLockReason, "resolveLockReason");
246
208
  function unmetServices(feature, availableServices) {
247
- return (feature.requiredServices ?? []).filter((service) => !availableServices.includes(service));
209
+ return feature.requiredServices.filter((service) => !availableServices.includes(service));
248
210
  }
249
211
  __name(unmetServices, "unmetServices");
250
212
  function isSiteLockedOnPlatform(entry, platform, code) {
@@ -255,7 +217,7 @@ __name(isSiteLockedOnPlatform, "isSiteLockedOnPlatform");
255
217
  function buildPermissions(feature, businessCode, planMembership, siteLocks, plans, bucket, missingServices = []) {
256
218
  const planUnlocked = new Set(planMembership?.[bucket] ?? []);
257
219
  const lockEntry = siteLocks?.[feature.code];
258
- const perms = (feature.permissions ?? []).filter((p) => p.isGlobal || p.businesses.includes(businessCode));
220
+ const perms = feature.permissions.filter((p) => p.isGlobal || p.businesses.includes(businessCode)).filter((p) => p.platforms.includes(bucket));
259
221
  const deps = buildDependsMap(perms);
260
222
  const codes = perms.map((p) => p.code);
261
223
  const directlyPlanLocked = /* @__PURE__ */ new Set();
@@ -293,7 +255,7 @@ __name(buildPermissions, "buildPermissions");
293
255
  function plansUnlockingClosure(plans, featureCode, closure, bucket) {
294
256
  const result = [];
295
257
  for (const [code, plan] of Object.entries(plans)) {
296
- const unlocked = plan.unlockedPermissions?.[featureCode]?.[bucket];
258
+ const unlocked = plan.unlockedPermissions[featureCode]?.[bucket];
297
259
  if (unlocked && closure.every((c) => unlocked.includes(c))) result.push(code);
298
260
  }
299
261
  return result;
@@ -302,16 +264,16 @@ __name(plansUnlockingClosure, "plansUnlockingClosure");
302
264
  function plansIncludingFeature(plans, featureCode, bucket) {
303
265
  const result = [];
304
266
  for (const [code, plan] of Object.entries(plans)) {
305
- if (plan.unlockedPermissions?.[featureCode]?.[bucket] !== void 0) result.push(code);
267
+ if (plan.unlockedPermissions[featureCode]?.[bucket] !== void 0) result.push(code);
306
268
  }
307
269
  return result;
308
270
  }
309
271
  __name(plansIncludingFeature, "plansIncludingFeature");
310
272
  function buildSiteRoles(snapshot, businessCode) {
311
273
  if (!businessCode) return [];
312
- const business = snapshot.businesses?.[businessCode];
274
+ const business = snapshot.businesses[businessCode];
313
275
  if (!business) return [];
314
- return Object.values(business.roleTemplates ?? {});
276
+ return Object.values(business.roleTemplates);
315
277
  }
316
278
  __name(buildSiteRoles, "buildSiteRoles");
317
279
 
@@ -331,11 +293,11 @@ function composeRoleGrants(params) {
331
293
  const result = {};
332
294
  const featureCodes = /* @__PURE__ */ new Set([
333
295
  ...Object.keys(baseFeatures ?? {}),
334
- ...Object.keys(additions ?? {})
296
+ ...Object.keys(additions)
335
297
  ]);
336
298
  for (const code of featureCodes) {
337
299
  const base = baseFeatures?.[code] ?? {};
338
- const add = additions?.[code] ?? {};
300
+ const add = additions[code] ?? {};
339
301
  const revokes = revoked?.[code];
340
302
  const composed = {};
341
303
  for (const bucket of PLATFORMS) {
@@ -368,32 +330,26 @@ var EMPTY_ROUTE = {
368
330
  function resolveUserFeatures(params) {
369
331
  const { snapshot, businessCode, planCode, siteLocks, platform, siteType, scope, availableServices } = params;
370
332
  const bucket = BUCKET_BY_CLIENT[platform];
371
- const roleFeatures = normalizeApiBuckets(params.roleFeatures) ?? {};
372
- const featureByCode = /* @__PURE__ */ __name((code) => scope ? snapshot.features?.[snapshotFeatureKey(code, scope)] : findFeatureByCode(snapshot, code), "featureByCode");
333
+ const roleFeatures = params.roleFeatures;
334
+ const featureByCode = /* @__PURE__ */ __name((code) => scope ? snapshot.features[snapshotFeatureKey(code, scope)] : findFeatureByCode(snapshot, code), "featureByCode");
373
335
  const catalog = buildSiteCatalog(snapshot, businessCode, planCode, siteLocks, bucket, siteType, scope, availableServices);
374
336
  const catalogMap = new Map(catalog.map((f) => [
375
337
  f.code,
376
338
  f
377
339
  ]));
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
- ]));
340
+ const businessPlans = snapshot.businesses[businessCode]?.plans ?? {};
385
341
  const currentUnlockedCodes = /* @__PURE__ */ new Set();
386
342
  if (planCode && businessPlans[planCode]) {
387
- for (const [featureCode, platforms] of Object.entries(businessPlans[planCode].unlockedPermissions ?? {})) {
388
- if (platforms?.[bucket] !== void 0) currentUnlockedCodes.add(featureCode);
343
+ for (const [featureCode, platforms] of Object.entries(businessPlans[planCode].unlockedPermissions)) {
344
+ if (platforms[bucket] !== void 0) currentUnlockedCodes.add(featureCode);
389
345
  }
390
346
  }
391
347
  const planAdds = /* @__PURE__ */ new Map();
392
348
  for (const [planKey, plan] of Object.entries(businessPlans)) {
393
349
  if (planKey === planCode) continue;
394
350
  const adds = [];
395
- for (const [featureCode, platforms] of Object.entries(plan.unlockedPermissions ?? {})) {
396
- if (platforms?.[bucket] === void 0 || currentUnlockedCodes.has(featureCode)) continue;
351
+ for (const [featureCode, platforms] of Object.entries(plan.unlockedPermissions)) {
352
+ if (platforms[bucket] === void 0 || currentUnlockedCodes.has(featureCode)) continue;
397
353
  const name = featureByCode(featureCode)?.name;
398
354
  if (name) adds.push({
399
355
  code: featureCode,
@@ -403,8 +359,8 @@ function resolveUserFeatures(params) {
403
359
  planAdds.set(planKey, adds);
404
360
  }
405
361
  const grantedFeatures = /* @__PURE__ */ new Map();
406
- for (const [code, grant] of Object.entries(roleFeatures ?? {})) {
407
- const granted = grant?.[bucket];
362
+ for (const [code, grant] of Object.entries(roleFeatures)) {
363
+ const granted = grant[bucket];
408
364
  if (granted === void 0) continue;
409
365
  if (!grantedFeatures.has(code)) grantedFeatures.set(code, /* @__PURE__ */ new Set());
410
366
  for (const perm of granted) grantedFeatures.get(code)?.add(perm);
@@ -416,20 +372,20 @@ function resolveUserFeatures(params) {
416
372
  const route = isApiBucket(bucket) ? EMPTY_ROUTE : pickRouteForPlatform(catalogEntry, platform);
417
373
  if (!route) continue;
418
374
  const featureDeps = buildDependsMap(featureByCode(code)?.permissions ?? []);
419
- const permByCode = new Map((catalogEntry.permissions ?? []).map((p) => [
375
+ const permByCode = new Map(catalogEntry.permissions.map((p) => [
420
376
  p.code,
421
377
  p
422
378
  ]));
423
379
  const grantedPerms = [
424
380
  ...filterGrantedByDeps(permsSet, featureDeps)
425
- ];
381
+ ].filter((c) => permByCode.has(c));
426
382
  const lockedPermissions = grantedPerms.map((c) => permByCode.get(c)).filter((p) => !!p?.locked).map((p) => ({
427
383
  code: p.code,
428
384
  reason: p.lockReason ?? null,
429
- unlockPlans: p.unlockPlans ?? [],
430
- missingServices: p.missingServices ?? []
385
+ unlockPlans: p.unlockPlans,
386
+ missingServices: p.missingServices
431
387
  }));
432
- const upsell = catalogEntry.locked && catalogEntry.lockReason === "PLAN" ? (catalogEntry.unlockPlans ?? []).map((plan) => ({
388
+ const upsell = catalogEntry.locked && catalogEntry.lockReason === "PLAN" ? catalogEntry.unlockPlans.map((plan) => ({
433
389
  plan,
434
390
  features: (planAdds.get(plan) ?? []).filter((f) => f.code !== code).map((f) => f.name)
435
391
  })).filter((group) => group.features.length > 0) : [];
@@ -442,8 +398,8 @@ function resolveUserFeatures(params) {
442
398
  permissions: grantedPerms,
443
399
  locked: catalogEntry.locked ?? false,
444
400
  lockReason: catalogEntry.lockReason ?? null,
445
- unlockPlans: catalogEntry.unlockPlans ?? [],
446
- missingServices: catalogEntry.missingServices ?? [],
401
+ unlockPlans: catalogEntry.unlockPlans,
402
+ missingServices: catalogEntry.missingServices,
447
403
  lockedPermissions,
448
404
  upsell,
449
405
  route,
@@ -486,21 +442,14 @@ function buildPlanMatrix(snapshot, businessCode, planCode, siteLocks) {
486
442
  }
487
443
  __name(buildPlanMatrix, "buildPlanMatrix");
488
444
  function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, siteType) {
489
- const business = businessCode ? snapshot.businesses?.[businessCode] : void 0;
490
- const plans = Object.fromEntries(Object.entries(business?.plans ?? {}).map(([code, p]) => [
491
- code,
492
- {
493
- ...p,
494
- unlockedPermissions: normalizeApiBuckets(p.unlockedPermissions) ?? {}
495
- }
496
- ]));
445
+ const business = businessCode ? snapshot.businesses[businessCode] : void 0;
446
+ const plans = business?.plans ?? {};
497
447
  const plan = planCode ? plans[planCode] : void 0;
498
448
  const planMeta = {
499
449
  code: planCode ?? "",
500
450
  name: plan?.name ?? planCode ?? ""
501
451
  };
502
- const normalizedLocks = normalizeApiBuckets(siteLocks);
503
- const locks = normalizedLocks ?? {};
452
+ const locks = siteLocks ?? {};
504
453
  if (!business || !plan) return {
505
454
  plan: planMeta,
506
455
  apps: [],
@@ -508,31 +457,51 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
508
457
  };
509
458
  const apps = [];
510
459
  for (const app of business.apps) {
511
- let unlockedCount = 0;
512
- let totalCount = 0;
460
+ const counts = {
461
+ web: {
462
+ unlocked: 0,
463
+ total: 0
464
+ },
465
+ mobile: {
466
+ unlocked: 0,
467
+ total: 0
468
+ },
469
+ graphql: {
470
+ unlocked: 0,
471
+ total: 0
472
+ },
473
+ http: {
474
+ unlocked: 0,
475
+ total: 0
476
+ }
477
+ };
513
478
  const features = [];
514
479
  for (const ref of app.features) {
515
480
  if (!allScopes && ref.scope !== "SITE") continue;
516
481
  const code = ref.code;
517
- const feature = snapshot.features?.[snapshotFeatureKey(code, ref.scope)];
482
+ const feature = snapshot.features[snapshotFeatureKey(code, ref.scope)];
518
483
  if (!feature) continue;
519
484
  if (siteType !== void 0 && !featureAppliesAtNode(feature.applicableSiteTypes, siteType)) continue;
520
485
  const platforms = [
521
486
  ...UI_PLATFORMS.filter((p) => !!feature.microfrontends?.[p]),
522
- ...API_BUCKETS.filter((b) => feature.apiSurfaces === void 0 || feature.apiSurfaces.includes(SURFACE_BY_BUCKET[b]))
487
+ ...API_BUCKETS.filter((b) => feature.apiSurfaces.includes(SURFACE_BY_BUCKET[b]))
523
488
  ];
524
- const membership = plan.unlockedPermissions?.[code];
489
+ const groupByCode = new Map(feature.permissionGroups.map((g) => [
490
+ g.code,
491
+ g
492
+ ]));
493
+ const membership = plan.unlockedPermissions[code];
525
494
  const featureInPlan = isPlanMember(membership);
526
- const siteEntry = normalizedLocks?.[code];
527
- const permissions = (feature.permissions ?? []).filter((p) => p.isGlobal || p.businesses.includes(businessCode ?? "")).map((p) => {
495
+ const siteEntry = siteLocks?.[code];
496
+ const permissions = feature.permissions.filter((p) => p.isGlobal || p.businesses.includes(businessCode ?? "")).map((p) => {
528
497
  const cell = /* @__PURE__ */ __name((plat) => {
529
- if (!platforms.includes(plat)) return null;
498
+ if (!platforms.includes(plat) || !p.platforms.includes(plat)) return null;
530
499
  const planCodes = membership?.[plat];
531
500
  const inPlan = featureInPlan && planCodes !== void 0 && planCodes.includes(p.code);
532
501
  const selected = inPlan && !isSiteLockedOnPlatform(siteEntry, plat, p.code);
533
502
  const availableIn = inPlan ? [] : plansUnlockingPerm(plans, code, p.code, plat, planCode);
534
- totalCount += 1;
535
- if (inPlan) unlockedCount += 1;
503
+ counts[plat].total += 1;
504
+ if (inPlan) counts[plat].unlocked += 1;
536
505
  return {
537
506
  inPlan,
538
507
  selected,
@@ -542,7 +511,8 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
542
511
  return {
543
512
  code: p.code,
544
513
  label: p.label,
545
- dependsOn: p.dependsOn ?? [],
514
+ dependsOn: p.dependsOn,
515
+ group: p.group ? groupByCode.get(p.group) : void 0,
546
516
  web: cell("web"),
547
517
  mobile: cell("mobile"),
548
518
  graphql: cell("graphql"),
@@ -567,8 +537,7 @@ function buildMatrix(snapshot, businessCode, planCode, siteLocks, allScopes, sit
567
537
  code: app.code,
568
538
  name: app.name,
569
539
  icon: app.icon ?? null,
570
- unlockedCount,
571
- totalCount,
540
+ counts,
572
541
  features
573
542
  });
574
543
  }
@@ -584,7 +553,7 @@ function plansUnlockingPerm(plans, featureCode, permCode, platform, excludeCode)
584
553
  const names = [];
585
554
  for (const [code, p] of Object.entries(plans)) {
586
555
  if (code === excludeCode) continue;
587
- if ((p.unlockedPermissions?.[featureCode]?.[platform] ?? []).includes(permCode)) names.push(p.name);
556
+ if ((p.unlockedPermissions[featureCode]?.[platform] ?? []).includes(permCode)) names.push(p.name);
588
557
  }
589
558
  return names;
590
559
  }
@@ -593,7 +562,7 @@ function plansIncludingFeature2(plans, featureCode, excludeCode) {
593
562
  const names = [];
594
563
  for (const [code, p] of Object.entries(plans)) {
595
564
  if (code === excludeCode) continue;
596
- if (isPlanMember(p.unlockedPermissions?.[featureCode])) names.push(p.name);
565
+ if (isPlanMember(p.unlockedPermissions[featureCode])) names.push(p.name);
597
566
  }
598
567
  return names;
599
568
  }
@@ -621,7 +590,6 @@ export {
621
590
  isApiBucket,
622
591
  isPlanMember,
623
592
  isSiteLockedOnPlatform,
624
- normalizeApiBuckets,
625
593
  pickRouteForPlatform,
626
594
  prereqClosure,
627
595
  resolveUserFeatures,