@saasicat/nest 0.7.0 → 0.9.0

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.
Files changed (59) hide show
  1. package/dist/admin/index.cjs +262 -7
  2. package/dist/admin/index.d.cts +6 -4
  3. package/dist/admin/index.d.ts +6 -4
  4. package/dist/admin/index.js +12 -4
  5. package/dist/{admin-manifest.service-Cp6uwvmt.d.cts → admin-manifest.service-BMeSLRW4.d.ts} +1 -1
  6. package/dist/{admin-manifest.service-UGgzR1x-.d.ts → admin-manifest.service-StAQe6QO.d.cts} +1 -1
  7. package/dist/admin-resources.module-BybEjZs-.d.cts +40 -0
  8. package/dist/admin-resources.module-CUCLlUAi.d.ts +40 -0
  9. package/dist/billing/index.cjs +3 -1
  10. package/dist/billing/index.d.cts +5 -287
  11. package/dist/billing/index.d.ts +5 -287
  12. package/dist/billing/index.js +5 -8
  13. package/dist/catalog/index.d.cts +4 -101
  14. package/dist/catalog/index.d.ts +4 -101
  15. package/dist/catalog/index.js +12 -10
  16. package/dist/catalog.module-BKObceo0.d.ts +103 -0
  17. package/dist/catalog.module-Dw2Hrbyu.d.cts +103 -0
  18. package/dist/{chunk-U6HT27J7.js → chunk-3M6VLT5N.js} +257 -6
  19. package/dist/chunk-7K5PT2LX.js +211 -0
  20. package/dist/{chunk-RZU4HIAN.js → chunk-GW2I3JZD.js} +25 -22
  21. package/dist/{chunk-6LP7CDS2.js → chunk-HXAWPPFW.js} +10 -5
  22. package/dist/chunk-OFBDEO45.js +755 -0
  23. package/dist/{chunk-5DDCSGZC.js → chunk-PBQRPDO2.js} +142 -354
  24. package/dist/{chunk-SD3TT2BR.js → chunk-WGVTXSYW.js} +1 -1
  25. package/dist/{chunk-A2QBXDJC.js → chunk-ZZMAHX22.js} +343 -16
  26. package/dist/{enforce-quota.interceptor-Dj1Kusr5.d.ts → enforce-quota.interceptor-B3CL5MSw.d.ts} +94 -12
  27. package/dist/{enforce-quota.interceptor-cv7r80VM.d.cts → enforce-quota.interceptor-SqrQVF91.d.cts} +94 -12
  28. package/dist/entitlement/index.cjs +1 -0
  29. package/dist/entitlement/index.d.cts +3 -1
  30. package/dist/entitlement/index.d.ts +3 -1
  31. package/dist/entitlement/index.js +1 -2
  32. package/dist/index.cjs +3503 -2811
  33. package/dist/index.d.cts +9 -6
  34. package/dist/index.d.ts +9 -6
  35. package/dist/index.js +75 -61
  36. package/dist/module-B5m080F0.d.ts +39 -0
  37. package/dist/module-DeH_Jkcq.d.cts +39 -0
  38. package/dist/platform/index.cjs +11113 -971
  39. package/dist/platform/index.d.cts +41 -7
  40. package/dist/platform/index.d.ts +41 -7
  41. package/dist/platform/index.js +26 -6
  42. package/dist/promo/index.cjs +530 -5
  43. package/dist/promo/index.d.cts +47 -30
  44. package/dist/promo/index.d.ts +47 -30
  45. package/dist/promo/index.js +9 -1
  46. package/dist/{service-DX8KbGXl.d.cts → service-DlhMnYjW.d.cts} +1 -1
  47. package/dist/{service-DX8KbGXl.d.ts → service-DlhMnYjW.d.ts} +1 -1
  48. package/dist/subscription-bundles.module-BQmoozm5.d.ts +291 -0
  49. package/dist/subscription-bundles.module-DCpV-Pb5.d.cts +291 -0
  50. package/dist/testing/index.cjs +11101 -973
  51. package/dist/testing/index.d.cts +9 -4
  52. package/dist/testing/index.d.ts +9 -4
  53. package/dist/testing/index.js +10 -4
  54. package/package.json +4 -4
  55. package/dist/admin-manifest.config-DyrQNT7M.d.cts +0 -10
  56. package/dist/admin-manifest.config-DyrQNT7M.d.ts +0 -10
  57. package/dist/chunk-52UTQRD3.js +0 -326
  58. package/dist/chunk-Q53N43LQ.js +0 -13
  59. /package/dist/{chunk-2FR6ZL7R.js → chunk-HWGIMNWR.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SuperAdminGuard
3
- } from "./chunk-U6HT27J7.js";
3
+ } from "./chunk-3M6VLT5N.js";
4
4
  import {
5
5
  asProvider
6
6
  } from "./chunk-SABTXESR.js";
@@ -1,17 +1,29 @@
1
+ import {
2
+ PromoCodesModule
3
+ } from "./chunk-OFBDEO45.js";
1
4
  import {
2
5
  AdminManifestModule,
3
- AdminModule
4
- } from "./chunk-U6HT27J7.js";
6
+ AdminManifestService,
7
+ AdminModule,
8
+ AdminResourcesModule,
9
+ SuperAdminGuard
10
+ } from "./chunk-3M6VLT5N.js";
11
+ import {
12
+ PublicCatalogModule,
13
+ REQUIRE_FEATURE_KEY,
14
+ SubscriptionBundleModule,
15
+ TenantBillingModule
16
+ } from "./chunk-HXAWPPFW.js";
5
17
  import {
6
- REQUIRE_FEATURE_KEY
7
- } from "./chunk-Q53N43LQ.js";
18
+ CatalogModule
19
+ } from "./chunk-PBQRPDO2.js";
8
20
  import {
9
21
  DiscoveryModule
10
22
  } from "./chunk-AJ3EPELS.js";
11
23
  import {
12
24
  EntitlementModule,
13
25
  LimitExceededError
14
- } from "./chunk-RZU4HIAN.js";
26
+ } from "./chunk-GW2I3JZD.js";
15
27
  import {
16
28
  PLAN_CATALOG_TOKEN,
17
29
  PlanCatalogModule
@@ -269,6 +281,46 @@ EnforceQuotaInterceptor = _ts_decorate3([
269
281
  ])
270
282
  ], EnforceQuotaInterceptor);
271
283
 
284
+ // src/platform/quota-providers-usage-snapshot.ts
285
+ var QuotaProvidersUsageSnapshot = class {
286
+ static {
287
+ __name(this, "QuotaProvidersUsageSnapshot");
288
+ }
289
+ providers;
290
+ constructor(providers) {
291
+ this.providers = providers;
292
+ }
293
+ async snapshot(tenantId) {
294
+ const entries = await Promise.all(this.providers.map(async (provider) => [
295
+ provider.key,
296
+ await provider.count(tenantId)
297
+ ]));
298
+ return Object.fromEntries(entries);
299
+ }
300
+ };
301
+
302
+ // src/platform/subscription-plan-resolver.ts
303
+ var DEFAULT_ACTIVE_STATUSES = /* @__PURE__ */ new Set([
304
+ "ACTIVE",
305
+ "TRIAL"
306
+ ]);
307
+ var SubscriptionPlanResolver = class {
308
+ static {
309
+ __name(this, "SubscriptionPlanResolver");
310
+ }
311
+ subscriptions;
312
+ activeStatuses;
313
+ constructor(subscriptions, activeStatuses = DEFAULT_ACTIVE_STATUSES) {
314
+ this.subscriptions = subscriptions;
315
+ this.activeStatuses = activeStatuses;
316
+ }
317
+ async getPlanIdForTenant(tenantId) {
318
+ const subscription = await this.subscriptions.findByTenantId(tenantId);
319
+ if (!subscription || !this.activeStatuses.has(subscription.status)) return null;
320
+ return subscription.plan;
321
+ }
322
+ };
323
+
272
324
  // src/platform/tenant-manifest.service.ts
273
325
  import { Injectable as Injectable4 } from "@nestjs/common";
274
326
  function _ts_decorate4(decorators, target, key, desc) {
@@ -413,6 +465,75 @@ function _ts_decorate6(decorators, target, key, desc) {
413
465
  return c > 3 && r && Object.defineProperty(target, key, r), r;
414
466
  }
415
467
  __name(_ts_decorate6, "_ts_decorate");
468
+ var PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/PlatformSubscriptionRepository");
469
+ var STANDARD_MANIFEST_REGISTRATION_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/StandardManifestRegistration");
470
+ function buildStandardManifestContribution(catalog, adminResources, promoCodes) {
471
+ const capabilities = {
472
+ "discovery.read": true
473
+ };
474
+ if (catalog && catalog.adminControllers !== false) {
475
+ Object.assign(capabilities, {
476
+ "plans.read": true,
477
+ "plans.publish": true,
478
+ "bundles.read": true,
479
+ "bundles.write": true,
480
+ "bundles.publish": true,
481
+ "marketingProjections.read": true,
482
+ "marketingProjections.write": true
483
+ });
484
+ }
485
+ const navigation = {};
486
+ if (adminResources) {
487
+ Object.assign(capabilities, {
488
+ "tenants.read": true,
489
+ "tenants.suspend": true,
490
+ "tenants.reactivate": true,
491
+ "users.read": true,
492
+ "audit.read": true,
493
+ "subscriptions.read": true
494
+ });
495
+ navigation.standardPages = {
496
+ subscriptions: {
497
+ enabled: true,
498
+ requiredCapability: "subscriptions.read"
499
+ }
500
+ };
501
+ }
502
+ if (promoCodes) {
503
+ Object.assign(capabilities, {
504
+ "promoCodes.read": true,
505
+ "promoCodes.write": true,
506
+ "promoCodes.delete": true
507
+ });
508
+ navigation.standardPages = {
509
+ ...navigation.standardPages ?? {},
510
+ promoCodes: {
511
+ enabled: true,
512
+ requiredCapability: "promoCodes.read"
513
+ }
514
+ };
515
+ }
516
+ return {
517
+ capabilities,
518
+ navigation: navigation.standardPages ? navigation : void 0
519
+ };
520
+ }
521
+ __name(buildStandardManifestContribution, "buildStandardManifestContribution");
522
+ function normalizeTenantAuthGuards(guards) {
523
+ if (!Array.isArray(guards)) return guards;
524
+ return {
525
+ useFactory: /* @__PURE__ */ __name((...instances) => instances, "useFactory"),
526
+ inject: guards
527
+ };
528
+ }
529
+ __name(normalizeTenantAuthGuards, "normalizeTenantAuthGuards");
530
+ function quotaUsageSnapshotProvider(quotaProviders) {
531
+ return {
532
+ useFactory: /* @__PURE__ */ __name((...providers) => new QuotaProvidersUsageSnapshot(providers), "useFactory"),
533
+ inject: quotaProviders
534
+ };
535
+ }
536
+ __name(quotaUsageSnapshotProvider, "quotaUsageSnapshotProvider");
416
537
  function buildMinimalManifestConfig() {
417
538
  return {
418
539
  useFactory: /* @__PURE__ */ __name((catalog) => ({
@@ -452,6 +573,12 @@ var SaasPlatformModule = class _SaasPlatformModule {
452
573
  static forRoot(options) {
453
574
  const explicit = options.adapters ?? {};
454
575
  const persistence = options.persistence;
576
+ const catalogConfig = options.catalog || null;
577
+ const tenantBillingConfig = options.tenantBilling || null;
578
+ const subscriptionBundlesConfig = options.subscriptionBundles || null;
579
+ const adminResourcesConfig = options.adminResources || null;
580
+ const promoCodesConfig = options.promoCodes || null;
581
+ const requiresFullEntitlement = Boolean(options.entitlement || tenantBillingConfig || subscriptionBundlesConfig);
455
582
  const adapters = {
456
583
  mfa: explicit.mfa ?? persistence?.core.mfa,
457
584
  audit: explicit.audit ?? persistence?.core.audit,
@@ -476,13 +603,52 @@ var SaasPlatformModule = class _SaasPlatformModule {
476
603
  if (!options.planCatalog && (!adapters.planCatalogReadSink || !options.dbCatalog)) {
477
604
  throw new Error("SaasPlatformModule.forRoot: either set `planCatalog` (quickstart YAML path) or, for DB hydration, BOTH a planCatalogReadSink (`adapters`/`persistence`) AND `dbCatalog` ({ projectKey, currency, vatRate }).");
478
605
  }
479
- if (options.entitlement) {
606
+ if (catalogConfig && !persistence?.catalog) {
607
+ throw new Error("SaasPlatformModule.forRoot: catalog is enabled, but `persistence.catalog` is missing. Use a complete persistence bundle or wire CatalogModule directly.");
608
+ }
609
+ if (adminResourcesConfig && adminResourcesConfig !== true && !adminResourcesConfig.resources && !persistence?.adminResources) {
610
+ throw new Error("SaasPlatformModule.forRoot: adminResources is enabled, but `persistence.adminResources` is missing.");
611
+ }
612
+ if (promoCodesConfig && (!persistence?.promo || !adapters.transactionRunner)) {
613
+ throw new Error("SaasPlatformModule.forRoot: promoCodes requires `persistence.promo` and a transactionRunner.");
614
+ }
615
+ if (promoCodesConfig && promoCodesConfig !== true && promoCodesConfig.includePublicController !== false && !promoCodesConfig.firstTimeCustomerCheck) {
616
+ throw new Error("SaasPlatformModule.forRoot: public promo preview requires `firstTimeCustomerCheck`.");
617
+ }
618
+ if (adminResourcesConfig === true && !persistence?.adminResources) {
619
+ throw new Error("SaasPlatformModule.forRoot: adminResources is enabled, but `persistence.adminResources` is missing.");
620
+ }
621
+ if (tenantBillingConfig && !persistence?.tenantBilling) {
622
+ const missing = [
623
+ !tenantBillingConfig.subscriptionUsagePort && "subscriptionUsagePort",
624
+ !tenantBillingConfig.subscriptionWritePort && "subscriptionWritePort"
625
+ ].filter(Boolean);
626
+ if (missing.length) {
627
+ throw new Error(`SaasPlatformModule.forRoot: tenantBilling is enabled, but adapters are missing: ${missing.join(", ")}`);
628
+ }
629
+ }
630
+ if (subscriptionBundlesConfig && !tenantBillingConfig) {
631
+ throw new Error("SaasPlatformModule.forRoot: subscriptionBundles requires tenantBilling so auth and subscription usage can be shared.");
632
+ }
633
+ if (subscriptionBundlesConfig) {
634
+ const missing = [];
635
+ if (!persistence?.entitlement?.subscriptionBundleRepository) {
636
+ missing.push("subscriptionBundleRepository");
637
+ }
638
+ if (!persistence?.catalog?.bundleRepository) {
639
+ missing.push("bundleRepository");
640
+ }
641
+ if (missing.length) {
642
+ throw new Error(`SaasPlatformModule.forRoot: subscriptionBundles is enabled, but persistence adapters are missing: ${missing.join(", ")}`);
643
+ }
644
+ }
645
+ if (requiresFullEntitlement) {
480
646
  const missing = [];
481
647
  if (!adapters.subscriptionRepository) missing.push("subscriptionRepository");
482
648
  if (!adapters.planVersionRepository) missing.push("planVersionRepository");
483
649
  if (!adapters.transactionRunner) missing.push("transactionRunner");
484
650
  if (missing.length) {
485
- throw new Error(`SaasPlatformModule.forRoot: entitlement active, but adapters are missing: ${missing.join(", ")}`);
651
+ throw new Error(`SaasPlatformModule.forRoot: entitlement active or required by the standard stack, but adapters are missing: ${missing.join(", ")}`);
486
652
  }
487
653
  if (persistence) {
488
654
  assertPersistenceCapabilities(persistence.capabilities, {
@@ -508,6 +674,9 @@ var SaasPlatformModule = class _SaasPlatformModule {
508
674
  version: options.planCatalog?.app?.version ?? options.dbCatalog?.app?.version ?? "0.0.0"
509
675
  };
510
676
  const imports = [
677
+ // Make injected client/auth providers visible to the high-level
678
+ // module's own quota providers and automatic plan resolver too.
679
+ ...options.imports ?? [],
511
680
  planCatalogModule,
512
681
  DiscoveryModule.forRoot({
513
682
  app: appInfo,
@@ -521,12 +690,14 @@ var SaasPlatformModule = class _SaasPlatformModule {
521
690
  mfaPort,
522
691
  auditPort,
523
692
  rlsBypassPort,
693
+ imports: options.imports,
524
694
  global: true
525
695
  }),
526
696
  AdminManifestModule.forRoot({
527
697
  config: options.adminManifestConfig ?? buildMinimalManifestConfig(),
528
698
  guards: options.controller.guards,
529
699
  reloadGuards: options.reloadGuards,
700
+ imports: options.imports,
530
701
  // Global like AdminModule above: apps register their manifest
531
702
  // contribution by injecting AdminManifestService into one of
532
703
  // their own modules (handbook §6.6). Re-exporting the module
@@ -534,26 +705,163 @@ var SaasPlatformModule = class _SaasPlatformModule {
534
705
  global: true
535
706
  })
536
707
  ];
537
- if (options.entitlement) {
708
+ if (requiresFullEntitlement) {
538
709
  imports.push(EntitlementModule.forRoot({
539
710
  subscriptionRepository: adapters.subscriptionRepository,
540
711
  planVersionRepository: adapters.planVersionRepository,
541
712
  transactionRunner: adapters.transactionRunner,
542
- resolutionConfig: options.entitlement.resolutionConfig,
713
+ resolutionConfig: options.entitlement ? options.entitlement.resolutionConfig : void 0,
543
714
  subscriptionContractRepository: persistence?.entitlement?.subscriptionContractRepository,
544
715
  subscriptionBundleRepository: persistence?.entitlement?.subscriptionBundleRepository,
545
- bundleRepository: persistence?.entitlement?.bundleRepository
716
+ bundleRepository: persistence?.entitlement?.bundleRepository,
717
+ imports: options.imports,
718
+ global: Boolean(tenantBillingConfig || subscriptionBundlesConfig)
719
+ }));
720
+ }
721
+ if (catalogConfig) {
722
+ const catalog = persistence?.catalog;
723
+ imports.push(CatalogModule.forRoot({
724
+ planRepository: catalog.planRepository,
725
+ bundleRepository: catalog.bundleRepository,
726
+ catalogEntryRepository: catalog.catalogEntryRepository,
727
+ marketingProjectionRepository: catalog.marketingProjectionRepository,
728
+ promotionRepository: catalog.promotionRepository,
729
+ marketingSettingsRepository: catalog.marketingSettingsRepository,
730
+ controller: catalogConfig.adminControllers === false ? void 0 : {
731
+ guards: options.controller.guards
732
+ },
733
+ imports: catalogConfig.imports ?? options.imports,
734
+ extraProviders: catalogConfig.extraProviders,
735
+ strictModeCheckMode: catalogConfig.strictModeCheckMode,
736
+ autoSyncDiscoveryAtBoot: catalogConfig.autoSyncDiscoveryAtBoot,
737
+ marketedOnlyFeatures: catalogConfig.marketedOnlyFeatures,
738
+ featureUiRegistry: catalogConfig.featureUiRegistry,
739
+ publicMarketingCatalog: catalogConfig.publicMarketingCatalog
740
+ }));
741
+ if (catalogConfig.publicCatalog !== false) {
742
+ imports.push(PublicCatalogModule.forRoot({
743
+ featureUiRegistry: catalogConfig.featureUiRegistry,
744
+ projectKey: appInfo.key,
745
+ bundleRepository: catalog.bundleRepository,
746
+ marketingRepository: catalog.marketingProjectionRepository,
747
+ catalogEntryRepository: catalog.catalogEntryRepository,
748
+ imports: catalogConfig.imports ?? options.imports
749
+ }));
750
+ }
751
+ }
752
+ if (adminResourcesConfig) {
753
+ const config = adminResourcesConfig === true ? {} : adminResourcesConfig;
754
+ imports.push(AdminResourcesModule.forRoot({
755
+ resources: config.resources ?? persistence?.adminResources?.resources,
756
+ guards: config.guards ?? [
757
+ ...options.controller.guards,
758
+ SuperAdminGuard
759
+ ],
760
+ imports: config.imports ?? options.imports
761
+ }));
762
+ }
763
+ if (promoCodesConfig) {
764
+ const config = promoCodesConfig === true ? {} : promoCodesConfig;
765
+ const promo = persistence?.promo;
766
+ const { firstTimeCustomerCheck, adminGuards, imports: promoImports, includePublicController, ...promoOptions } = config;
767
+ imports.push(PromoCodesModule.forRoot({
768
+ ...promoOptions,
769
+ promoCodeRepository: promo.promoCodeRepository,
770
+ redemptionRepository: promo.redemptionRepository,
771
+ validationLogRepository: promo.validationLogRepository,
772
+ subscriptionLookup: promo.subscriptionLookup,
773
+ revenueAggregator: promo.revenueAggregator,
774
+ transactionRunner: adapters.transactionRunner,
775
+ firstTimeCustomerCheck: firstTimeCustomerCheck ?? {
776
+ hasExistingCustomerForEmail: /* @__PURE__ */ __name(async () => false, "hasExistingCustomerForEmail")
777
+ },
778
+ includePublicController: includePublicController ?? false,
779
+ adminController: {
780
+ guards: adminGuards ?? [
781
+ ...options.controller.guards,
782
+ SuperAdminGuard
783
+ ]
784
+ },
785
+ imports: promoImports ?? options.imports
786
+ }));
787
+ }
788
+ let sharedTenantAuthGuards;
789
+ let sharedSubscriptionUsagePort;
790
+ let quotaProvidersHostedByTenantBilling = false;
791
+ if (tenantBillingConfig) {
792
+ const tenantSlice = persistence?.tenantBilling;
793
+ const { authGuards, subscriptionUsagePort, usageSnapshotPort, subscriptionWritePort, imports: tenantImports, extraProviders, extraExports, ...tenantOptions } = tenantBillingConfig;
794
+ const quotaProviders = options.quotaProviders ?? [];
795
+ quotaProvidersHostedByTenantBilling = quotaProviders.length > 0;
796
+ sharedTenantAuthGuards = normalizeTenantAuthGuards(authGuards);
797
+ sharedSubscriptionUsagePort = subscriptionUsagePort ?? tenantSlice?.subscriptionUsagePort;
798
+ const resolvedUsageSnapshotPort = usageSnapshotPort ?? tenantSlice?.usageSnapshotPort ?? quotaUsageSnapshotProvider(options.quotaProviders ?? []);
799
+ imports.push(TenantBillingModule.forRoot({
800
+ ...tenantOptions,
801
+ authGuards: sharedTenantAuthGuards,
802
+ subscriptionUsagePort: sharedSubscriptionUsagePort,
803
+ usageSnapshotPort: resolvedUsageSnapshotPort,
804
+ subscriptionWritePort: subscriptionWritePort ?? tenantSlice?.subscriptionWritePort,
805
+ imports: tenantImports ?? options.imports,
806
+ extraProviders: [
807
+ ...quotaProviders,
808
+ ...extraProviders ?? []
809
+ ],
810
+ extraExports: [
811
+ ...quotaProviders,
812
+ ...extraExports ?? []
813
+ ]
814
+ }));
815
+ }
816
+ if (subscriptionBundlesConfig) {
817
+ const config = subscriptionBundlesConfig === true ? {} : subscriptionBundlesConfig;
818
+ const { controller, imports: bundleImports, extraProviders, ...bundleOptions } = config;
819
+ imports.push(SubscriptionBundleModule.forRoot({
820
+ ...bundleOptions,
821
+ subscriptionBundleRepository: persistence?.entitlement?.subscriptionBundleRepository,
822
+ bundleRepository: persistence?.catalog?.bundleRepository,
823
+ controller: controller === false ? void 0 : {
824
+ ...controller ?? {},
825
+ authGuards: sharedTenantAuthGuards,
826
+ subscriptionUsagePort: sharedSubscriptionUsagePort
827
+ },
828
+ imports: bundleImports ?? tenantBillingConfig?.imports ?? options.imports,
829
+ extraProviders
546
830
  }));
547
831
  }
548
832
  const lightweightProviders = [];
549
833
  const lightweightExports = [];
550
- const hasResolver = !!adapters.planResolver;
834
+ if (options.autoManifest !== false) {
835
+ const contribution = buildStandardManifestContribution(catalogConfig, adminResourcesConfig, promoCodesConfig);
836
+ lightweightProviders.push({
837
+ provide: STANDARD_MANIFEST_REGISTRATION_TOKEN,
838
+ useFactory: /* @__PURE__ */ __name((manifest) => {
839
+ manifest.register(contribution);
840
+ return contribution;
841
+ }, "useFactory"),
842
+ inject: [
843
+ AdminManifestService
844
+ ]
845
+ });
846
+ }
847
+ const hasExplicitResolver = !!adapters.planResolver;
848
+ const hasSubscriptionResolver = Boolean(tenantBillingConfig && adapters.subscriptionRepository);
849
+ const hasResolver = hasExplicitResolver || hasSubscriptionResolver;
551
850
  const hasFallback = !!options.defaultPlanId;
552
851
  if (hasResolver || hasFallback) {
553
- lightweightProviders.push(hasResolver ? asProvider(PLAN_RESOLVER_PORT_TOKEN, adapters.planResolver) : {
852
+ if (hasSubscriptionResolver && !hasExplicitResolver) {
853
+ lightweightProviders.push(asProvider(PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN, adapters.subscriptionRepository));
854
+ }
855
+ lightweightProviders.push(hasExplicitResolver ? asProvider(PLAN_RESOLVER_PORT_TOKEN, adapters.planResolver) : hasSubscriptionResolver ? {
856
+ provide: PLAN_RESOLVER_PORT_TOKEN,
857
+ useFactory: /* @__PURE__ */ __name((subscriptions) => new SubscriptionPlanResolver(subscriptions), "useFactory"),
858
+ inject: [
859
+ PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN
860
+ ]
861
+ } : {
554
862
  provide: PLAN_RESOLVER_PORT_TOKEN,
555
863
  useValue: new StaticPlanResolver(options.defaultPlanId)
556
- }, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, ...options.quotaProviders ?? [], {
864
+ }, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, ...quotaProvidersHostedByTenantBilling ? [] : options.quotaProviders ?? [], {
557
865
  provide: QUOTA_PROVIDERS_TOKEN,
558
866
  useFactory: /* @__PURE__ */ __name((...providers) => providers, "useFactory"),
559
867
  inject: options.quotaProviders ?? []
@@ -569,11 +877,13 @@ var SaasPlatformModule = class _SaasPlatformModule {
569
877
  const tenantControllers = [];
570
878
  if (options.tenantManifest) {
571
879
  if (!hasResolver && !hasFallback) {
572
- throw new Error("SaasPlatformModule.forRoot: tenantManifest erfordert defaultPlanId oder adapters.planResolver, sonst kann das Manifest keine Features aufl\xF6sen.");
880
+ throw new Error("SaasPlatformModule.forRoot: tenantManifest requires defaultPlanId, adapters.planResolver or tenantBilling so it can resolve features.");
573
881
  }
574
882
  lightweightProviders.push(TenantManifestService);
575
883
  lightweightExports.push(TenantManifestService);
576
- tenantControllers.push(buildTenantManifestController(options.tenantManifest));
884
+ tenantControllers.push(buildTenantManifestController(options.tenantManifest === true ? {
885
+ guards: Array.isArray(tenantBillingConfig?.authGuards) ? tenantBillingConfig.authGuards : options.controller.guards
886
+ } : options.tenantManifest));
577
887
  }
578
888
  return {
579
889
  module: _SaasPlatformModule,
@@ -588,9 +898,24 @@ var SaasPlatformModule = class _SaasPlatformModule {
588
898
  // imported module's token directly is an UnknownExportException
589
899
  // at boot — the module export below already carries it.
590
900
  AdminManifestModule,
591
- ...options.entitlement ? [
901
+ ...requiresFullEntitlement ? [
592
902
  EntitlementModule
593
903
  ] : [],
904
+ ...catalogConfig ? [
905
+ CatalogModule
906
+ ] : [],
907
+ ...tenantBillingConfig ? [
908
+ TenantBillingModule
909
+ ] : [],
910
+ ...subscriptionBundlesConfig ? [
911
+ SubscriptionBundleModule
912
+ ] : [],
913
+ ...adminResourcesConfig ? [
914
+ AdminResourcesModule
915
+ ] : [],
916
+ ...promoCodesConfig ? [
917
+ PromoCodesModule
918
+ ] : [],
594
919
  ...lightweightExports
595
920
  ],
596
921
  global: true
@@ -609,6 +934,8 @@ export {
609
934
  StaticFeatureGuard,
610
935
  QUOTA_PROVIDERS_TOKEN,
611
936
  EnforceQuotaInterceptor,
937
+ QuotaProvidersUsageSnapshot,
938
+ SubscriptionPlanResolver,
612
939
  TenantManifestService,
613
940
  buildTenantManifestController,
614
941
  SaasPlatformModule
@@ -1,9 +1,12 @@
1
- import { Type, CanActivate, DynamicModule, ForwardReference, FactoryProvider, ExecutionContext, NestInterceptor, CallHandler } from '@nestjs/common';
2
- import { MfaPort, AuditPort, RlsBypassPort, PlanCatalogReadSink, SubscriptionRepository, PlanVersionRepository, TransactionRunner, PlanCatalog, SaasicatPersistenceAdapter, QuotaProvider } from '@saasicat/types';
1
+ import { Type, CanActivate, DynamicModule, ForwardReference, FactoryProvider, Provider, ExecutionContext, NestInterceptor, CallHandler } from '@nestjs/common';
2
+ import { PlanCatalog, SaasicatPersistenceAdapter, MfaPort, AuditPort, RlsBypassPort, PlanCatalogReadSink, SubscriptionRepository, PlanVersionRepository, TransactionRunner, QuotaProvider, FeatureUiRegistry, SubscriptionUsagePort, UsageSnapshotPort, TenantSubscriptionWritePort, AdminResourcesPort, FirstTimeCustomerCheck } from '@saasicat/types';
3
3
  import { P as ProviderSpec } from './di-CcNeq9v-.js';
4
- import { A as AdminManifestConfig } from './admin-manifest.config-DyrQNT7M.js';
5
- import { b as DiscoveryAppInfo } from './discovery.scanner-DLgEvuoy.js';
4
+ import { A as AdminManifestConfig, d as AdminResourcesModuleOptions } from './admin-resources.module-CUCLlUAi.js';
6
5
  import { E as EntitlementResolutionConfig } from './plan-resolution-CFCoUkrE.js';
6
+ import { a as PromoCodesModuleOptions } from './module-B5m080F0.js';
7
+ import { b as DiscoveryAppInfo } from './discovery.scanner-DLgEvuoy.js';
8
+ import { b as CatalogModuleOptions } from './catalog.module-BKObceo0.js';
9
+ import { t as TenantBillingModuleOptions, p as SubscriptionBundleModuleOptions, n as SubscriptionBundleControllerOptions } from './subscription-bundles.module-BQmoozm5.js';
7
10
  import { Reflector } from '@nestjs/core';
8
11
  import { Observable } from 'rxjs';
9
12
 
@@ -67,8 +70,8 @@ interface SaasPlatformAdapters {
67
70
  * Optional — resolver `tenantId → planId`. The quickstart path uses this
68
71
  * together with the `StaticEntitlementService` to automatically check
69
72
  * `@RequireFeature` and `@EnforceQuota` against the plan catalog limit.
70
- * If not set, `defaultPlanId` **must** be provided a `StaticPlanResolver`
71
- * then returns the same plan for all tenants.
73
+ * If not set, `tenantBilling` derives the resolver from the subscription
74
+ * repository. Lightweight setups can instead set `defaultPlanId`.
72
75
  */
73
76
  planResolver?: ProviderSpec<PlanResolverPort>;
74
77
  /**
@@ -79,6 +82,50 @@ interface SaasPlatformAdapters {
79
82
  planVersionRepository?: ProviderSpec<PlanVersionRepository>;
80
83
  transactionRunner?: ProviderSpec<TransactionRunner>;
81
84
  }
85
+ type PlatformImports = Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
86
+ interface SaasPlatformCatalogOptions extends Pick<CatalogModuleOptions, 'strictModeCheckMode' | 'autoSyncDiscoveryAtBoot' | 'marketedOnlyFeatures' | 'publicMarketingCatalog' | 'extraProviders'> {
87
+ /** Labels/icons used by discovery review and the public catalog. */
88
+ featureUiRegistry: FeatureUiRegistry;
89
+ /** Mount the SuperAdmin catalog controllers. Default `true`. */
90
+ adminControllers?: boolean;
91
+ /** Mount `/billing/plans|bundles|feature-registry`. Default `true`. */
92
+ publicCatalog?: boolean;
93
+ /** Override the top-level module imports for catalog adapter factories. */
94
+ imports?: PlatformImports;
95
+ }
96
+ type SaasPlatformTenantAuthGuards = Array<Type<CanActivate>> | ProviderSpec<ReadonlyArray<CanActivate>>;
97
+ interface SaasPlatformTenantBillingOptions extends Omit<TenantBillingModuleOptions, 'authGuards' | 'subscriptionUsagePort' | 'usageSnapshotPort' | 'subscriptionWritePort' | 'global'> {
98
+ /**
99
+ * Tenant guards as classes (the common path) or as a custom provider
100
+ * factory. Guard classes are resolved from `imports`.
101
+ */
102
+ authGuards: SaasPlatformTenantAuthGuards;
103
+ /** Optional overrides; otherwise the persistence bundle supplies them. */
104
+ subscriptionUsagePort?: ProviderSpec<SubscriptionUsagePort>;
105
+ usageSnapshotPort?: ProviderSpec<UsageSnapshotPort>;
106
+ subscriptionWritePort?: ProviderSpec<TenantSubscriptionWritePort>;
107
+ }
108
+ interface SaasPlatformSubscriptionBundlesOptions extends Omit<SubscriptionBundleModuleOptions, 'subscriptionBundleRepository' | 'bundleRepository' | 'controller' | 'imports' | 'extraProviders' | 'global'> {
109
+ /** Default mounts the tenant controller and reuses tenant-billing auth. */
110
+ controller?: false | Omit<SubscriptionBundleControllerOptions, 'authGuards' | 'subscriptionUsagePort'>;
111
+ imports?: PlatformImports;
112
+ extraProviders?: Provider[];
113
+ }
114
+ interface SaasPlatformAdminResourcesOptions extends Omit<AdminResourcesModuleOptions, 'resources' | 'guards' | 'global'> {
115
+ /** Override the default controller guards + `SuperAdminGuard` chain. */
116
+ guards?: Array<Type<CanActivate>>;
117
+ /** Custom schema override; the persistence bundle supplies the default. */
118
+ resources?: ProviderSpec<AdminResourcesPort>;
119
+ }
120
+ interface SaasPlatformPromoCodesOptions extends Omit<PromoCodesModuleOptions, 'promoCodeRepository' | 'redemptionRepository' | 'validationLogRepository' | 'subscriptionLookup' | 'revenueAggregator' | 'transactionRunner' | 'firstTimeCustomerCheck' | 'adminController' | 'global'> {
121
+ /**
122
+ * Required when the public preview/redeem flow is enabled. Admin-only
123
+ * setups do not need an app-specific customer-history check.
124
+ */
125
+ firstTimeCustomerCheck?: ProviderSpec<FirstTimeCustomerCheck>;
126
+ /** Override the default controller guards + `SuperAdminGuard` chain. */
127
+ adminGuards?: Array<Type<CanActivate>>;
128
+ }
82
129
  interface SaasPlatformModuleOptions {
83
130
  /**
84
131
  * Plan catalog. Either as an already-loaded object (quickstart, comes
@@ -135,7 +182,7 @@ interface SaasPlatformModuleOptions {
135
182
  * Modules whose providers must be visible in the DI scope (typically:
136
183
  * `AuthModule` with the `JwtAuthGuard`).
137
184
  */
138
- imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
185
+ imports?: PlatformImports;
139
186
  /**
140
187
  * App identity for the DiscoveryScanner. If omitted, `planCatalog.app` is
141
188
  * used (recommendation: simply declare it in the YAML).
@@ -154,10 +201,17 @@ interface SaasPlatformModuleOptions {
154
201
  */
155
202
  adminManifestConfig?: AdminManifestConfig | Pick<FactoryProvider<AdminManifestConfig>, 'useFactory' | 'inject'>;
156
203
  /**
157
- * Default `false`. If `true`, `EntitlementModule.forRoot({...})` is called
204
+ * Infer manifest capabilities for platform endpoints mounted by this
205
+ * module. Default `true`; set `false` only when the app owns the complete
206
+ * manifest capability policy.
207
+ */
208
+ autoManifest?: boolean;
209
+ /**
210
+ * Default `false`. When set, `EntitlementModule.forRoot({...})` is called
158
211
  * with the repositories from `adapters` — only meaningful if the app
159
212
  * implements the V3 contract path (`subscriptionRepository` & co. must then
160
- * be set).
213
+ * be set). `tenantBilling` and `subscriptionBundles` enable it
214
+ * automatically.
161
215
  */
162
216
  entitlement?: false | {
163
217
  resolutionConfig?: EntitlementResolutionConfig;
@@ -177,12 +231,40 @@ interface SaasPlatformModuleOptions {
177
231
  * `QUOTA_PROVIDERS_TOKEN`.
178
232
  */
179
233
  quotaProviders?: Array<Type<QuotaProvider>>;
234
+ /**
235
+ * Standard DB-backed catalog stack. Repositories are taken from
236
+ * `persistence.catalog`; no consumer forwarding module is needed.
237
+ */
238
+ catalog?: false | SaasPlatformCatalogOptions;
239
+ /**
240
+ * Standard tenant self-service stack. Subscription read/write adapters
241
+ * come from `persistence.tenantBilling`; usage is derived from
242
+ * `quotaProviders` unless explicitly overridden.
243
+ */
244
+ tenantBilling?: false | SaasPlatformTenantBillingOptions;
245
+ /**
246
+ * Add-on bundle service and tenant controller. `true` uses defaults and
247
+ * reuses tenant-billing auth/usage; an object customizes policy.
248
+ */
249
+ subscriptionBundles?: false | true | SaasPlatformSubscriptionBundlesOptions;
250
+ /**
251
+ * Standard SuperAdmin Tenant/User/Audit/Subscription endpoints. `true`
252
+ * uses `persistence.adminResources` and the standard guard chain.
253
+ */
254
+ adminResources?: false | true | SaasPlatformAdminResourcesOptions;
255
+ /**
256
+ * Promo-code domain and SuperAdmin CRUD. `true` enables the admin API
257
+ * without the public preview endpoint; pass an object to enable preview.
258
+ */
259
+ promoCodes?: false | true | SaasPlatformPromoCodesOptions;
180
260
  /**
181
261
  * Enable the tenant manifest — the app UI gets a filtered manifest per
182
262
  * tenant with features, quotas and visible navigation. Requires that
183
- * `defaultPlanId` or `adapters.planResolver` is set.
263
+ * `defaultPlanId`, `adapters.planResolver` or `tenantBilling` is set.
264
+ * `true` reuses tenant-billing guard classes when available, otherwise
265
+ * `controller.guards`.
184
266
  */
185
- tenantManifest?: TenantManifestControllerOptions;
267
+ tenantManifest?: true | TenantManifestControllerOptions;
186
268
  }
187
269
  /**
188
270
  * Bundles PlanCatalog + Discovery + Admin + AdminManifest (+ optionally
@@ -260,4 +342,4 @@ declare class EnforceQuotaInterceptor implements NestInterceptor {
260
342
  private check;
261
343
  }
262
344
 
263
- export { EnforceQuotaInterceptor as E, PLAN_RESOLVER_PORT_TOKEN as P, QUOTA_PROVIDERS_TOKEN as Q, StaticEntitlementService as S, type TenantManifestControllerOptions as T, type PlanResolverPort as a, STATIC_FEATURE_GUARD_CONFIG_TOKEN as b, type SaasPlatformAdapters as c, SaasPlatformModule as d, type SaasPlatformModuleOptions as e, type StaticEntitlementSnapshot as f, StaticFeatureGuard as g, type StaticFeatureGuardConfig as h, StaticPlanResolver as i, buildTenantManifestController as j };
345
+ export { EnforceQuotaInterceptor as E, PLAN_RESOLVER_PORT_TOKEN as P, QUOTA_PROVIDERS_TOKEN as Q, type SaasPlatformAdapters as S, type TenantManifestControllerOptions as T, StaticEntitlementService as a, StaticFeatureGuard as b, StaticPlanResolver as c, type SaasPlatformModuleOptions as d, type PlanResolverPort as e, STATIC_FEATURE_GUARD_CONFIG_TOKEN as f, SaasPlatformModule as g, type SaasPlatformAdminResourcesOptions as h, type SaasPlatformCatalogOptions as i, type SaasPlatformPromoCodesOptions as j, type SaasPlatformSubscriptionBundlesOptions as k, type SaasPlatformTenantAuthGuards as l, type SaasPlatformTenantBillingOptions as m, type StaticEntitlementSnapshot as n, type StaticFeatureGuardConfig as o, buildTenantManifestController as p };