@saasicat/nest 0.2.1 → 0.4.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 (43) hide show
  1. package/dist/admin/index.d.cts +2 -2
  2. package/dist/admin/index.d.ts +2 -2
  3. package/dist/{admin-manifest.config-DyrQNT7M.d.cts → admin-manifest.config-DxTfZ8-L.d.cts} +1 -1
  4. package/dist/{admin-manifest.config-DyrQNT7M.d.ts → admin-manifest.config-DxTfZ8-L.d.ts} +1 -1
  5. package/dist/{aggregation-CJ3qQf92.d.cts → aggregation-Bt1i_whS.d.cts} +1 -8
  6. package/dist/{aggregation-Dvz9e8X2.d.ts → aggregation-DGCyoN5d.d.ts} +1 -8
  7. package/dist/billing/index.cjs +13 -2
  8. package/dist/billing/index.d.cts +5 -5
  9. package/dist/billing/index.d.ts +5 -5
  10. package/dist/billing/index.js +12 -12
  11. package/dist/catalog/index.d.cts +1 -1
  12. package/dist/catalog/index.d.ts +1 -1
  13. package/dist/catalog/index.js +3 -3
  14. package/dist/{chunk-YXGHPILG.js → chunk-K42EQPBB.js} +14 -7
  15. package/dist/{chunk-QJVPRD3R.js → chunk-SG34XT7J.js} +61 -27
  16. package/dist/{chunk-RIGLQ4BV.js → chunk-VHEHZMHB.js} +21 -16
  17. package/dist/discovery/index.d.cts +2 -2
  18. package/dist/discovery/index.d.ts +2 -2
  19. package/dist/discovery/index.js +4 -4
  20. package/dist/{discovery.scanner-CUYLKlYT.d.cts → discovery.scanner-DLgEvuoy.d.cts} +1 -1
  21. package/dist/{discovery.scanner-CUYLKlYT.d.ts → discovery.scanner-DLgEvuoy.d.ts} +1 -1
  22. package/dist/{saas-platform.module-DT9TnCzk.d.ts → enforce-quota.interceptor-3HmbeHTM.d.ts} +93 -15
  23. package/dist/{saas-platform.module-CgZ2omgg.d.cts → enforce-quota.interceptor-HmdZFg41.d.cts} +93 -15
  24. package/dist/entitlement/index.cjs +8 -0
  25. package/dist/entitlement/index.d.cts +18 -2
  26. package/dist/entitlement/index.d.ts +18 -2
  27. package/dist/entitlement/index.js +5 -3
  28. package/dist/index.cjs +29 -16
  29. package/dist/index.d.cts +6 -6
  30. package/dist/index.d.ts +6 -6
  31. package/dist/index.js +81 -79
  32. package/dist/platform/index.cjs +59 -22
  33. package/dist/platform/index.d.cts +9 -62
  34. package/dist/platform/index.d.ts +9 -62
  35. package/dist/platform/index.js +4 -4
  36. package/dist/registration/index.d.cts +3 -6
  37. package/dist/registration/index.d.ts +3 -6
  38. package/dist/testing/index.cjs +69 -22
  39. package/dist/testing/index.d.cts +6 -8
  40. package/dist/testing/index.d.ts +6 -8
  41. package/dist/testing/index.js +15 -5
  42. package/package.json +5 -3
  43. package/dist/{chunk-V7HUZ6UQ.js → chunk-FN65XZKH.js} +6 -6
@@ -56,4 +56,4 @@ declare class DiscoveryScanner implements OnApplicationBootstrap {
56
56
  */
57
57
  declare function computeSnapshotHash(snapshot: Omit<DiscoverySnapshot, 'hash'>): string;
58
58
 
59
- export { type DiscoveryAppInfo as D, DISCOVERY_APP_INFO_TOKEN as a, DISCOVERY_SNAPSHOT_PATH_TOKEN as b, DiscoveryScanner as c, computeSnapshotHash as d };
59
+ export { DISCOVERY_APP_INFO_TOKEN as D, DISCOVERY_SNAPSHOT_PATH_TOKEN as a, type DiscoveryAppInfo as b, DiscoveryScanner as c, computeSnapshotHash as d };
@@ -56,4 +56,4 @@ declare class DiscoveryScanner implements OnApplicationBootstrap {
56
56
  */
57
57
  declare function computeSnapshotHash(snapshot: Omit<DiscoverySnapshot, 'hash'>): string;
58
58
 
59
- export { type DiscoveryAppInfo as D, DISCOVERY_APP_INFO_TOKEN as a, DISCOVERY_SNAPSHOT_PATH_TOKEN as b, DiscoveryScanner as c, computeSnapshotHash as d };
59
+ export { DISCOVERY_APP_INFO_TOKEN as D, DISCOVERY_SNAPSHOT_PATH_TOKEN as a, type DiscoveryAppInfo as b, DiscoveryScanner as c, computeSnapshotHash as d };
@@ -1,9 +1,11 @@
1
- import { Type, CanActivate, DynamicModule, ForwardReference, FactoryProvider } from '@nestjs/common';
2
- import { MfaPort, AuditPort, RlsBypassPort, PlanCatalogReadSink, SubscriptionRepository, PlanVersionRepository, TransactionRunner, PlanCatalog, QuotaProvider } from '@saasicat/types';
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';
3
3
  import { P as ProviderSpec } from './di-CcNeq9v-.js';
4
- import { A as AdminManifestConfig } from './admin-manifest.config-DyrQNT7M.js';
5
- import { D as DiscoveryAppInfo } from './discovery.scanner-CUYLKlYT.js';
4
+ import { a as AdminManifestConfig } from './admin-manifest.config-DxTfZ8-L.js';
5
+ import { b as DiscoveryAppInfo } from './discovery.scanner-DLgEvuoy.js';
6
6
  import { E as EntitlementResolutionConfig } from './plan-resolution-CFCoUkrE.js';
7
+ import { Reflector } from '@nestjs/core';
8
+ import { Observable } from 'rxjs';
7
9
 
8
10
  declare const PLAN_RESOLVER_PORT_TOKEN: unique symbol;
9
11
  interface PlanResolverPort {
@@ -75,14 +77,38 @@ interface SaasPlatformAdapters {
75
77
  interface SaasPlatformModuleOptions {
76
78
  /**
77
79
  * Plan catalog. Either as an already-loaded object (quickstart, comes
78
- * directly from `loadPlanCatalogFromFile('config/saas.yaml')`) or as a
79
- * sink reference in `adapters.planCatalogReadSink` for DB hydration.
80
+ * directly from `loadPlanCatalogFromFile('config/saas.yaml')`) or via DB
81
+ * hydration: a sink reference in `adapters.planCatalogReadSink` /
82
+ * `persistence.planCatalogReadSink` **plus** the `dbCatalog` identity.
80
83
  */
81
84
  planCatalog?: PlanCatalog;
82
85
  /**
83
- * Adapter bindings.
86
+ * App identity for the DB-hydration path — required when `planCatalog`
87
+ * is omitted. The read sink only loads plans/features (filtered by
88
+ * `projectKey`); branding, currency and VAT cannot come from the
89
+ * database and must be supplied here.
84
90
  */
85
- adapters: SaasPlatformAdapters;
91
+ dbCatalog?: {
92
+ projectKey: string;
93
+ currency: string;
94
+ vatRate: number;
95
+ app?: PlanCatalog['app'];
96
+ marketing?: PlanCatalog['marketing'];
97
+ };
98
+ /**
99
+ * Aggregate persistence bundle from an adapter package (e.g.
100
+ * `prismaPersistence({ client: PrismaService })` from
101
+ * `@saasicat/adapter-prisma`). Fills every port the bundle ships;
102
+ * individual `adapters` entries override bundle slices. The declared
103
+ * `capabilities` are validated fail-fast against the enabled feature set
104
+ * (e.g. `entitlement: true` requires transactions + pessimistic locking).
105
+ */
106
+ persistence?: SaasicatPersistenceAdapter;
107
+ /**
108
+ * Individual adapter bindings. Optional when `persistence` provides the
109
+ * respective port; explicit entries take precedence over the bundle.
110
+ */
111
+ adapters?: SaasPlatformAdapters;
86
112
  /**
87
113
  * Class-level guards for the platform controllers (`GET /admin/discovery`
88
114
  * and `GET /admin/manifest`). REQUIRED — otherwise the platform throws at
@@ -158,23 +184,75 @@ interface SaasPlatformModuleOptions {
158
184
  * Entitlement) into a single `forRoot({...})` call. Reduces AppModule
159
185
  * boilerplate and eliminates the ordering trap.
160
186
  *
161
- * Quickstart path:
187
+ * Quickstart path (Prisma + PostgreSQL on the canonical schema):
162
188
  *
163
189
  * ```ts
164
190
  * SaasPlatformModule.forRoot({
165
191
  * planCatalog: loadPlanCatalogFromFile({ path: 'config/saas.yaml' }),
166
192
  * controller: { guards: [JwtAuthGuard] },
167
193
  * imports: [AuthModule],
168
- * adapters: {
169
- * mfa: PrismaMfaAdapter, // from @saasicat/prisma
170
- * audit: PrismaAuditAdapter,
171
- * rlsBypass: AsyncLocalRlsBypassAdapter,
172
- * },
194
+ * persistence: prismaPersistence({ client: PrismaService }), // @saasicat/adapter-prisma
173
195
  * })
174
196
  * ```
197
+ *
198
+ * Individual `adapters` entries stay supported (custom schema / other ORM)
199
+ * and override bundle slices field by field.
175
200
  */
176
201
  declare class SaasPlatformModule {
177
202
  static forRoot(options: SaasPlatformModuleOptions): DynamicModule;
178
203
  }
179
204
 
180
- export { type PlanResolverPort as P, type SaasPlatformModuleOptions as S, type TenantManifestControllerOptions as T, PLAN_RESOLVER_PORT_TOKEN as a, type SaasPlatformAdapters as b, SaasPlatformModule as c, StaticPlanResolver as d, buildTenantManifestController as e };
205
+ interface StaticEntitlementSnapshot {
206
+ planId: string | null;
207
+ features: readonly string[];
208
+ /** quotaKey → limit (`-1` = unlimited). */
209
+ quotas: Readonly<Record<string, number>>;
210
+ }
211
+ declare class StaticEntitlementService {
212
+ private readonly catalog;
213
+ private readonly resolver;
214
+ constructor(catalog: PlanCatalog, resolver: PlanResolverPort);
215
+ /**
216
+ * Returns the effective features + quotas of a tenant from the
217
+ * plan catalog. When the plan is missing: an empty snapshot (no feature,
218
+ * all quotas at 0).
219
+ */
220
+ snapshot(tenantId: string): Promise<StaticEntitlementSnapshot>;
221
+ /** Convenience helper for tests / synchronous checks in the UI manifest. */
222
+ hasFeature(tenantId: string, featureKey: string): Promise<boolean>;
223
+ quotaLimit(tenantId: string, quotaKey: string): Promise<number | null>;
224
+ private findPlan;
225
+ }
226
+
227
+ declare const STATIC_FEATURE_GUARD_CONFIG_TOKEN: unique symbol;
228
+ interface StaticFeatureGuardConfig {
229
+ /**
230
+ * Resolver for the user role (e.g. `(user) => user.platformRole`).
231
+ * SUPER_ADMIN gets a bypass.
232
+ */
233
+ userRoleResolver?: (user: unknown) => string | undefined;
234
+ /**
235
+ * Resolver for the tenant ID from the request. Default:
236
+ * `request.tenantId ?? request.user?.tenantId`.
237
+ */
238
+ tenantIdResolver?: (request: unknown) => string | undefined;
239
+ }
240
+ declare class StaticFeatureGuard implements CanActivate {
241
+ private readonly reflector;
242
+ private readonly entitlements;
243
+ private readonly config;
244
+ constructor(reflector: Reflector, entitlements: StaticEntitlementService, config?: StaticFeatureGuardConfig | null);
245
+ canActivate(context: ExecutionContext): Promise<boolean>;
246
+ }
247
+
248
+ declare const QUOTA_PROVIDERS_TOKEN: unique symbol;
249
+ declare class EnforceQuotaInterceptor implements NestInterceptor {
250
+ private readonly reflector;
251
+ private readonly entitlements;
252
+ private readonly providers;
253
+ constructor(reflector: Reflector, entitlements: StaticEntitlementService, providers?: ReadonlyArray<QuotaProvider> | null);
254
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
255
+ private check;
256
+ }
257
+
258
+ 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 };
@@ -1,9 +1,11 @@
1
- import { Type, CanActivate, DynamicModule, ForwardReference, FactoryProvider } from '@nestjs/common';
2
- import { MfaPort, AuditPort, RlsBypassPort, PlanCatalogReadSink, SubscriptionRepository, PlanVersionRepository, TransactionRunner, PlanCatalog, QuotaProvider } from '@saasicat/types';
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';
3
3
  import { P as ProviderSpec } from './di-CcNeq9v-.cjs';
4
- import { A as AdminManifestConfig } from './admin-manifest.config-DyrQNT7M.cjs';
5
- import { D as DiscoveryAppInfo } from './discovery.scanner-CUYLKlYT.cjs';
4
+ import { a as AdminManifestConfig } from './admin-manifest.config-DxTfZ8-L.cjs';
5
+ import { b as DiscoveryAppInfo } from './discovery.scanner-DLgEvuoy.cjs';
6
6
  import { E as EntitlementResolutionConfig } from './plan-resolution-CFCoUkrE.cjs';
7
+ import { Reflector } from '@nestjs/core';
8
+ import { Observable } from 'rxjs';
7
9
 
8
10
  declare const PLAN_RESOLVER_PORT_TOKEN: unique symbol;
9
11
  interface PlanResolverPort {
@@ -75,14 +77,38 @@ interface SaasPlatformAdapters {
75
77
  interface SaasPlatformModuleOptions {
76
78
  /**
77
79
  * Plan catalog. Either as an already-loaded object (quickstart, comes
78
- * directly from `loadPlanCatalogFromFile('config/saas.yaml')`) or as a
79
- * sink reference in `adapters.planCatalogReadSink` for DB hydration.
80
+ * directly from `loadPlanCatalogFromFile('config/saas.yaml')`) or via DB
81
+ * hydration: a sink reference in `adapters.planCatalogReadSink` /
82
+ * `persistence.planCatalogReadSink` **plus** the `dbCatalog` identity.
80
83
  */
81
84
  planCatalog?: PlanCatalog;
82
85
  /**
83
- * Adapter bindings.
86
+ * App identity for the DB-hydration path — required when `planCatalog`
87
+ * is omitted. The read sink only loads plans/features (filtered by
88
+ * `projectKey`); branding, currency and VAT cannot come from the
89
+ * database and must be supplied here.
84
90
  */
85
- adapters: SaasPlatformAdapters;
91
+ dbCatalog?: {
92
+ projectKey: string;
93
+ currency: string;
94
+ vatRate: number;
95
+ app?: PlanCatalog['app'];
96
+ marketing?: PlanCatalog['marketing'];
97
+ };
98
+ /**
99
+ * Aggregate persistence bundle from an adapter package (e.g.
100
+ * `prismaPersistence({ client: PrismaService })` from
101
+ * `@saasicat/adapter-prisma`). Fills every port the bundle ships;
102
+ * individual `adapters` entries override bundle slices. The declared
103
+ * `capabilities` are validated fail-fast against the enabled feature set
104
+ * (e.g. `entitlement: true` requires transactions + pessimistic locking).
105
+ */
106
+ persistence?: SaasicatPersistenceAdapter;
107
+ /**
108
+ * Individual adapter bindings. Optional when `persistence` provides the
109
+ * respective port; explicit entries take precedence over the bundle.
110
+ */
111
+ adapters?: SaasPlatformAdapters;
86
112
  /**
87
113
  * Class-level guards for the platform controllers (`GET /admin/discovery`
88
114
  * and `GET /admin/manifest`). REQUIRED — otherwise the platform throws at
@@ -158,23 +184,75 @@ interface SaasPlatformModuleOptions {
158
184
  * Entitlement) into a single `forRoot({...})` call. Reduces AppModule
159
185
  * boilerplate and eliminates the ordering trap.
160
186
  *
161
- * Quickstart path:
187
+ * Quickstart path (Prisma + PostgreSQL on the canonical schema):
162
188
  *
163
189
  * ```ts
164
190
  * SaasPlatformModule.forRoot({
165
191
  * planCatalog: loadPlanCatalogFromFile({ path: 'config/saas.yaml' }),
166
192
  * controller: { guards: [JwtAuthGuard] },
167
193
  * imports: [AuthModule],
168
- * adapters: {
169
- * mfa: PrismaMfaAdapter, // from @saasicat/prisma
170
- * audit: PrismaAuditAdapter,
171
- * rlsBypass: AsyncLocalRlsBypassAdapter,
172
- * },
194
+ * persistence: prismaPersistence({ client: PrismaService }), // @saasicat/adapter-prisma
173
195
  * })
174
196
  * ```
197
+ *
198
+ * Individual `adapters` entries stay supported (custom schema / other ORM)
199
+ * and override bundle slices field by field.
175
200
  */
176
201
  declare class SaasPlatformModule {
177
202
  static forRoot(options: SaasPlatformModuleOptions): DynamicModule;
178
203
  }
179
204
 
180
- export { type PlanResolverPort as P, type SaasPlatformModuleOptions as S, type TenantManifestControllerOptions as T, PLAN_RESOLVER_PORT_TOKEN as a, type SaasPlatformAdapters as b, SaasPlatformModule as c, StaticPlanResolver as d, buildTenantManifestController as e };
205
+ interface StaticEntitlementSnapshot {
206
+ planId: string | null;
207
+ features: readonly string[];
208
+ /** quotaKey → limit (`-1` = unlimited). */
209
+ quotas: Readonly<Record<string, number>>;
210
+ }
211
+ declare class StaticEntitlementService {
212
+ private readonly catalog;
213
+ private readonly resolver;
214
+ constructor(catalog: PlanCatalog, resolver: PlanResolverPort);
215
+ /**
216
+ * Returns the effective features + quotas of a tenant from the
217
+ * plan catalog. When the plan is missing: an empty snapshot (no feature,
218
+ * all quotas at 0).
219
+ */
220
+ snapshot(tenantId: string): Promise<StaticEntitlementSnapshot>;
221
+ /** Convenience helper for tests / synchronous checks in the UI manifest. */
222
+ hasFeature(tenantId: string, featureKey: string): Promise<boolean>;
223
+ quotaLimit(tenantId: string, quotaKey: string): Promise<number | null>;
224
+ private findPlan;
225
+ }
226
+
227
+ declare const STATIC_FEATURE_GUARD_CONFIG_TOKEN: unique symbol;
228
+ interface StaticFeatureGuardConfig {
229
+ /**
230
+ * Resolver for the user role (e.g. `(user) => user.platformRole`).
231
+ * SUPER_ADMIN gets a bypass.
232
+ */
233
+ userRoleResolver?: (user: unknown) => string | undefined;
234
+ /**
235
+ * Resolver for the tenant ID from the request. Default:
236
+ * `request.tenantId ?? request.user?.tenantId`.
237
+ */
238
+ tenantIdResolver?: (request: unknown) => string | undefined;
239
+ }
240
+ declare class StaticFeatureGuard implements CanActivate {
241
+ private readonly reflector;
242
+ private readonly entitlements;
243
+ private readonly config;
244
+ constructor(reflector: Reflector, entitlements: StaticEntitlementService, config?: StaticFeatureGuardConfig | null);
245
+ canActivate(context: ExecutionContext): Promise<boolean>;
246
+ }
247
+
248
+ declare const QUOTA_PROVIDERS_TOKEN: unique symbol;
249
+ declare class EnforceQuotaInterceptor implements NestInterceptor {
250
+ private readonly reflector;
251
+ private readonly entitlements;
252
+ private readonly providers;
253
+ constructor(reflector: Reflector, entitlements: StaticEntitlementService, providers?: ReadonlyArray<QuotaProvider> | null);
254
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
255
+ private check;
256
+ }
257
+
258
+ 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 };
@@ -41,6 +41,7 @@ __export(entitlement_exports, {
41
41
  filterPlannedOnlyFeatures: () => filterPlannedOnlyFeatures,
42
42
  hasAnyFeature: () => hasAnyFeature,
43
43
  hasFeature: () => hasFeature,
44
+ isLimitExceededError: () => isLimitExceededError,
44
45
  resolveEntitlementPlan: () => resolveEntitlementPlan,
45
46
  toEffectiveLimitsSnapshot: () => toEffectiveLimitsSnapshot
46
47
  });
@@ -304,6 +305,7 @@ function resolveEntitlementPlan(input, config, now) {
304
305
  __name(resolveEntitlementPlan, "resolveEntitlementPlan");
305
306
 
306
307
  // src/entitlement/limit-exceeded-error.ts
308
+ var LIMIT_EXCEEDED = "LIMIT_EXCEEDED";
307
309
  var LimitExceededError = class extends Error {
308
310
  static {
309
311
  __name(this, "LimitExceededError");
@@ -311,11 +313,16 @@ var LimitExceededError = class extends Error {
311
313
  dimension;
312
314
  max;
313
315
  used;
316
+ code = LIMIT_EXCEEDED;
314
317
  constructor(dimension, max, used) {
315
318
  super(`Limit f\xFCr ${dimension} erreicht: ${used}/${max}.`), this.dimension = dimension, this.max = max, this.used = used;
316
319
  this.name = "LimitExceededError";
317
320
  }
318
321
  };
322
+ function isLimitExceededError(err) {
323
+ return err instanceof Error && err.code === LIMIT_EXCEEDED;
324
+ }
325
+ __name(isLimitExceededError, "isLimitExceededError");
319
326
 
320
327
  // src/entitlement/service.ts
321
328
  var import_common2 = require("@nestjs/common");
@@ -800,6 +807,7 @@ EntitlementModule = _ts_decorate3([
800
807
  filterPlannedOnlyFeatures,
801
808
  hasAnyFeature,
802
809
  hasFeature,
810
+ isLimitExceededError,
803
811
  resolveEntitlementPlan,
804
812
  toEffectiveLimitsSnapshot
805
813
  });
@@ -1,4 +1,4 @@
1
- export { B as BundleVersionSnapshot, a as BusinessTypeVersionSnapshot, C as CustomLimitsShape, E as EffectiveLimits, b as EffectiveLimitsSnapshot, c as EnforceLimitInput, d as EntitlementService, L as LimitExceededError, P as PlanVersionSnapshot, S as SubscriptionBundleSnapshot, e as SubscriptionLimitsInput, f as aggregateBusinessTypeQuotas, g as aggregateContractLineItemEntitlements, h as aggregateLimits, i as aggregateSubscriptionBundleQuotas, j as applyCustomLimits, k as collectBusinessTypeFeatures, l as collectSubscriptionBundleFeatures, m as filterActiveSubscriptionBundles, n as filterPlannedOnlyFeatures, o as hasAnyFeature, p as hasFeature, t as toEffectiveLimitsSnapshot } from '../aggregation-CJ3qQf92.cjs';
1
+ export { B as BundleVersionSnapshot, a as BusinessTypeVersionSnapshot, C as CustomLimitsShape, E as EffectiveLimits, b as EffectiveLimitsSnapshot, c as EnforceLimitInput, d as EntitlementService, P as PlanVersionSnapshot, S as SubscriptionBundleSnapshot, e as SubscriptionLimitsInput, f as aggregateBusinessTypeQuotas, g as aggregateContractLineItemEntitlements, h as aggregateLimits, i as aggregateSubscriptionBundleQuotas, j as applyCustomLimits, k as collectBusinessTypeFeatures, l as collectSubscriptionBundleFeatures, m as filterActiveSubscriptionBundles, n as filterPlannedOnlyFeatures, o as hasAnyFeature, p as hasFeature, t as toEffectiveLimitsSnapshot } from '../aggregation-Bt1i_whS.cjs';
2
2
  import { DiscoveredFeature, SubscriptionRepository, PlanVersionRepository, TransactionRunner, SubscriptionContractRepository, SubscriptionBundleRepository, BundleRepository } from '@saasicat/types';
3
3
  import { E as EntitlementResolutionConfig } from '../plan-resolution-CFCoUkrE.cjs';
4
4
  export { a as EntitlementResolutionInput, r as resolveEntitlementPlan } from '../plan-resolution-CFCoUkrE.cjs';
@@ -24,6 +24,22 @@ declare function buildReplacedByIndex(features: readonly FeatureReplacesSource[]
24
24
  */
25
25
  declare function expandReplacedFeatures(granted: ReadonlySet<string>, index: ReplacedByIndex): Set<string>;
26
26
 
27
+ declare class LimitExceededError extends Error {
28
+ readonly dimension: string;
29
+ readonly max: number;
30
+ readonly used: number;
31
+ readonly code = "LIMIT_EXCEEDED";
32
+ constructor(dimension: string, max: number, used: number);
33
+ }
34
+ /**
35
+ * Realm-safe type guard (checks `code` instead of `instanceof`). The tsup
36
+ * multi-entry build duplicates this class into every sub-bundle
37
+ * (platform/billing/entitlement), so an `instanceof` between thrower and
38
+ * catcher from different entries is false — same hazard the token registry
39
+ * solves with `Symbol.for` and `isPlatformUserExistsError` solves for types.
40
+ */
41
+ declare function isLimitExceededError(err: unknown): err is LimitExceededError;
42
+
27
43
  interface EntitlementModuleOptions {
28
44
  /** Consumer implementation of the `SubscriptionRepository` interface. */
29
45
  subscriptionRepository: ProviderSpec<SubscriptionRepository>;
@@ -57,4 +73,4 @@ declare const PLAN_VERSION_REPOSITORY_TOKEN: unique symbol;
57
73
  declare const TRANSACTION_RUNNER_TOKEN: unique symbol;
58
74
  declare const ENTITLEMENT_RESOLUTION_CONFIG_TOKEN: unique symbol;
59
75
 
60
- export { ENTITLEMENT_RESOLUTION_CONFIG_TOKEN, EntitlementModule, type EntitlementModuleOptions, EntitlementResolutionConfig, PLAN_VERSION_REPOSITORY_TOKEN, type ReplacedByIndex, SUBSCRIPTION_REPOSITORY_TOKEN, TRANSACTION_RUNNER_TOKEN, buildReplacedByIndex, expandReplacedFeatures };
76
+ export { ENTITLEMENT_RESOLUTION_CONFIG_TOKEN, EntitlementModule, type EntitlementModuleOptions, EntitlementResolutionConfig, LimitExceededError, PLAN_VERSION_REPOSITORY_TOKEN, type ReplacedByIndex, SUBSCRIPTION_REPOSITORY_TOKEN, TRANSACTION_RUNNER_TOKEN, buildReplacedByIndex, expandReplacedFeatures, isLimitExceededError };
@@ -1,4 +1,4 @@
1
- export { B as BundleVersionSnapshot, a as BusinessTypeVersionSnapshot, C as CustomLimitsShape, E as EffectiveLimits, b as EffectiveLimitsSnapshot, c as EnforceLimitInput, d as EntitlementService, L as LimitExceededError, P as PlanVersionSnapshot, S as SubscriptionBundleSnapshot, e as SubscriptionLimitsInput, f as aggregateBusinessTypeQuotas, g as aggregateContractLineItemEntitlements, h as aggregateLimits, i as aggregateSubscriptionBundleQuotas, j as applyCustomLimits, k as collectBusinessTypeFeatures, l as collectSubscriptionBundleFeatures, m as filterActiveSubscriptionBundles, n as filterPlannedOnlyFeatures, o as hasAnyFeature, p as hasFeature, t as toEffectiveLimitsSnapshot } from '../aggregation-Dvz9e8X2.js';
1
+ export { B as BundleVersionSnapshot, a as BusinessTypeVersionSnapshot, C as CustomLimitsShape, E as EffectiveLimits, b as EffectiveLimitsSnapshot, c as EnforceLimitInput, d as EntitlementService, P as PlanVersionSnapshot, S as SubscriptionBundleSnapshot, e as SubscriptionLimitsInput, f as aggregateBusinessTypeQuotas, g as aggregateContractLineItemEntitlements, h as aggregateLimits, i as aggregateSubscriptionBundleQuotas, j as applyCustomLimits, k as collectBusinessTypeFeatures, l as collectSubscriptionBundleFeatures, m as filterActiveSubscriptionBundles, n as filterPlannedOnlyFeatures, o as hasAnyFeature, p as hasFeature, t as toEffectiveLimitsSnapshot } from '../aggregation-DGCyoN5d.js';
2
2
  import { DiscoveredFeature, SubscriptionRepository, PlanVersionRepository, TransactionRunner, SubscriptionContractRepository, SubscriptionBundleRepository, BundleRepository } from '@saasicat/types';
3
3
  import { E as EntitlementResolutionConfig } from '../plan-resolution-CFCoUkrE.js';
4
4
  export { a as EntitlementResolutionInput, r as resolveEntitlementPlan } from '../plan-resolution-CFCoUkrE.js';
@@ -24,6 +24,22 @@ declare function buildReplacedByIndex(features: readonly FeatureReplacesSource[]
24
24
  */
25
25
  declare function expandReplacedFeatures(granted: ReadonlySet<string>, index: ReplacedByIndex): Set<string>;
26
26
 
27
+ declare class LimitExceededError extends Error {
28
+ readonly dimension: string;
29
+ readonly max: number;
30
+ readonly used: number;
31
+ readonly code = "LIMIT_EXCEEDED";
32
+ constructor(dimension: string, max: number, used: number);
33
+ }
34
+ /**
35
+ * Realm-safe type guard (checks `code` instead of `instanceof`). The tsup
36
+ * multi-entry build duplicates this class into every sub-bundle
37
+ * (platform/billing/entitlement), so an `instanceof` between thrower and
38
+ * catcher from different entries is false — same hazard the token registry
39
+ * solves with `Symbol.for` and `isPlatformUserExistsError` solves for types.
40
+ */
41
+ declare function isLimitExceededError(err: unknown): err is LimitExceededError;
42
+
27
43
  interface EntitlementModuleOptions {
28
44
  /** Consumer implementation of the `SubscriptionRepository` interface. */
29
45
  subscriptionRepository: ProviderSpec<SubscriptionRepository>;
@@ -57,4 +73,4 @@ declare const PLAN_VERSION_REPOSITORY_TOKEN: unique symbol;
57
73
  declare const TRANSACTION_RUNNER_TOKEN: unique symbol;
58
74
  declare const ENTITLEMENT_RESOLUTION_CONFIG_TOKEN: unique symbol;
59
75
 
60
- export { ENTITLEMENT_RESOLUTION_CONFIG_TOKEN, EntitlementModule, type EntitlementModuleOptions, EntitlementResolutionConfig, PLAN_VERSION_REPOSITORY_TOKEN, type ReplacedByIndex, SUBSCRIPTION_REPOSITORY_TOKEN, TRANSACTION_RUNNER_TOKEN, buildReplacedByIndex, expandReplacedFeatures };
76
+ export { ENTITLEMENT_RESOLUTION_CONFIG_TOKEN, EntitlementModule, type EntitlementModuleOptions, EntitlementResolutionConfig, LimitExceededError, PLAN_VERSION_REPOSITORY_TOKEN, type ReplacedByIndex, SUBSCRIPTION_REPOSITORY_TOKEN, TRANSACTION_RUNNER_TOKEN, buildReplacedByIndex, expandReplacedFeatures, isLimitExceededError };
@@ -16,10 +16,10 @@ import {
16
16
  filterPlannedOnlyFeatures,
17
17
  hasAnyFeature,
18
18
  hasFeature,
19
+ isLimitExceededError,
19
20
  resolveEntitlementPlan,
20
21
  toEffectiveLimitsSnapshot
21
- } from "../chunk-YXGHPILG.js";
22
- import "../chunk-MV6LW5UB.js";
22
+ } from "../chunk-K42EQPBB.js";
23
23
  import "../chunk-NYLON2VC.js";
24
24
  import {
25
25
  ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
@@ -28,8 +28,9 @@ import {
28
28
  TRANSACTION_RUNNER_TOKEN
29
29
  } from "../chunk-7MLGHWKO.js";
30
30
  import "../chunk-XCZ3TTFK.js";
31
- import "../chunk-SABTXESR.js";
32
31
  import "../chunk-L4SUWH5B.js";
32
+ import "../chunk-MV6LW5UB.js";
33
+ import "../chunk-SABTXESR.js";
33
34
  import "../chunk-SHUYVCID.js";
34
35
  export {
35
36
  ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
@@ -52,6 +53,7 @@ export {
52
53
  filterPlannedOnlyFeatures,
53
54
  hasAnyFeature,
54
55
  hasFeature,
56
+ isLimitExceededError,
55
57
  resolveEntitlementPlan,
56
58
  toEffectiveLimitsSnapshot
57
59
  };
package/dist/index.cjs CHANGED
@@ -308,6 +308,7 @@ __export(index_exports, {
308
308
  ipFingerprint: () => ipFingerprint,
309
309
  isFeatureInPlan: () => isFeatureInPlan,
310
310
  isFeaturePlannedOnly: () => isFeaturePlannedOnly,
311
+ isLimitExceededError: () => isLimitExceededError,
311
312
  loadApprovedCatalogKeys: () => loadApprovedCatalogKeys,
312
313
  loadDiscoverySnapshotFromFile: () => loadDiscoverySnapshotFromFile,
313
314
  loadPlanCatalogFromFile: () => loadPlanCatalogFromFile,
@@ -1683,6 +1684,7 @@ function assertNoReplacesCycle(index) {
1683
1684
  __name(assertNoReplacesCycle, "assertNoReplacesCycle");
1684
1685
 
1685
1686
  // src/entitlement/limit-exceeded-error.ts
1687
+ var LIMIT_EXCEEDED = "LIMIT_EXCEEDED";
1686
1688
  var LimitExceededError = class extends Error {
1687
1689
  static {
1688
1690
  __name(this, "LimitExceededError");
@@ -1690,11 +1692,16 @@ var LimitExceededError = class extends Error {
1690
1692
  dimension;
1691
1693
  max;
1692
1694
  used;
1695
+ code = LIMIT_EXCEEDED;
1693
1696
  constructor(dimension, max, used) {
1694
1697
  super(`Limit f\xFCr ${dimension} erreicht: ${used}/${max}.`), this.dimension = dimension, this.max = max, this.used = used;
1695
1698
  this.name = "LimitExceededError";
1696
1699
  }
1697
1700
  };
1701
+ function isLimitExceededError(err) {
1702
+ return err instanceof Error && err.code === LIMIT_EXCEEDED;
1703
+ }
1704
+ __name(isLimitExceededError, "isLimitExceededError");
1698
1705
 
1699
1706
  // src/entitlement/plan-resolution.ts
1700
1707
  function resolveEntitlementPlan(input, config, now) {
@@ -2209,6 +2216,7 @@ __name(compareRankedOffers, "compareRankedOffers");
2209
2216
 
2210
2217
  // src/billing/limit-exceeded.filter.ts
2211
2218
  var import_common11 = require("@nestjs/common");
2219
+ var import_core2 = require("@nestjs/core");
2212
2220
  function _ts_decorate11(decorators, target, key, desc) {
2213
2221
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2214
2222
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -2216,12 +2224,16 @@ function _ts_decorate11(decorators, target, key, desc) {
2216
2224
  return c > 3 && r && Object.defineProperty(target, key, r), r;
2217
2225
  }
2218
2226
  __name(_ts_decorate11, "_ts_decorate");
2219
- var LimitExceededFilter = class _LimitExceededFilter {
2227
+ var LimitExceededFilter = class _LimitExceededFilter extends import_core2.BaseExceptionFilter {
2220
2228
  static {
2221
2229
  __name(this, "LimitExceededFilter");
2222
2230
  }
2223
2231
  logger = new import_common11.Logger(_LimitExceededFilter.name);
2224
2232
  catch(exception, host) {
2233
+ if (!isLimitExceededError(exception)) {
2234
+ super.catch(exception, host);
2235
+ return;
2236
+ }
2225
2237
  const ctx = host.switchToHttp();
2226
2238
  const response = ctx.getResponse();
2227
2239
  const request = ctx.getRequest();
@@ -2238,7 +2250,7 @@ var LimitExceededFilter = class _LimitExceededFilter {
2238
2250
  }
2239
2251
  };
2240
2252
  LimitExceededFilter = _ts_decorate11([
2241
- (0, import_common11.Catch)(LimitExceededError)
2253
+ (0, import_common11.Catch)()
2242
2254
  ], LimitExceededFilter);
2243
2255
 
2244
2256
  // src/billing/plan-catalog-loader.ts
@@ -5681,7 +5693,7 @@ SuperAdminGuard = _ts_decorate36([
5681
5693
 
5682
5694
  // src/admin/mfa.guard.ts
5683
5695
  var import_common35 = require("@nestjs/common");
5684
- var import_core2 = require("@nestjs/core");
5696
+ var import_core3 = require("@nestjs/core");
5685
5697
 
5686
5698
  // src/admin/mfa.ts
5687
5699
  var import_common34 = require("@nestjs/common");
@@ -5860,10 +5872,10 @@ var MfaGuard = class _MfaGuard {
5860
5872
  };
5861
5873
  MfaGuard = _ts_decorate38([
5862
5874
  (0, import_common35.Injectable)(),
5863
- _ts_param21(0, (0, import_common35.Inject)(import_core2.Reflector)),
5875
+ _ts_param21(0, (0, import_common35.Inject)(import_core3.Reflector)),
5864
5876
  _ts_metadata25("design:type", Function),
5865
5877
  _ts_metadata25("design:paramtypes", [
5866
- typeof import_core2.Reflector === "undefined" ? Object : import_core2.Reflector,
5878
+ typeof import_core3.Reflector === "undefined" ? Object : import_core3.Reflector,
5867
5879
  typeof MfaService === "undefined" ? Object : MfaService
5868
5880
  ])
5869
5881
  ], MfaGuard);
@@ -8636,7 +8648,7 @@ var import_node_crypto4 = require("crypto");
8636
8648
  var import_node_fs2 = require("fs");
8637
8649
  var import_node_path2 = require("path");
8638
8650
  var import_common52 = require("@nestjs/common");
8639
- var import_core3 = require("@nestjs/core");
8651
+ var import_core4 = require("@nestjs/core");
8640
8652
  function _ts_decorate63(decorators, target, key, desc) {
8641
8653
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8642
8654
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -8821,18 +8833,18 @@ var DiscoveryScanner = class _DiscoveryScanner {
8821
8833
  };
8822
8834
  DiscoveryScanner = _ts_decorate63([
8823
8835
  (0, import_common52.Injectable)(),
8824
- _ts_param31(0, (0, import_common52.Inject)(import_core3.DiscoveryService)),
8825
- _ts_param31(1, (0, import_common52.Inject)(import_core3.MetadataScanner)),
8826
- _ts_param31(2, (0, import_common52.Inject)(import_core3.Reflector)),
8836
+ _ts_param31(0, (0, import_common52.Inject)(import_core4.DiscoveryService)),
8837
+ _ts_param31(1, (0, import_common52.Inject)(import_core4.MetadataScanner)),
8838
+ _ts_param31(2, (0, import_common52.Inject)(import_core4.Reflector)),
8827
8839
  _ts_param31(3, (0, import_common52.Optional)()),
8828
8840
  _ts_param31(3, (0, import_common52.Inject)(DISCOVERY_APP_INFO_TOKEN)),
8829
8841
  _ts_param31(4, (0, import_common52.Optional)()),
8830
8842
  _ts_param31(4, (0, import_common52.Inject)(DISCOVERY_SNAPSHOT_PATH_TOKEN)),
8831
8843
  _ts_metadata46("design:type", Function),
8832
8844
  _ts_metadata46("design:paramtypes", [
8833
- typeof import_core3.DiscoveryService === "undefined" ? Object : import_core3.DiscoveryService,
8834
- typeof import_core3.MetadataScanner === "undefined" ? Object : import_core3.MetadataScanner,
8835
- typeof import_core3.Reflector === "undefined" ? Object : import_core3.Reflector,
8845
+ typeof import_core4.DiscoveryService === "undefined" ? Object : import_core4.DiscoveryService,
8846
+ typeof import_core4.MetadataScanner === "undefined" ? Object : import_core4.MetadataScanner,
8847
+ typeof import_core4.Reflector === "undefined" ? Object : import_core4.Reflector,
8836
8848
  typeof DiscoveryAppInfo === "undefined" ? Object : DiscoveryAppInfo,
8837
8849
  Object
8838
8850
  ])
@@ -8932,7 +8944,7 @@ __name(canonicalStringify, "canonicalStringify");
8932
8944
 
8933
8945
  // src/discovery/discovery.module.ts
8934
8946
  var import_common54 = require("@nestjs/common");
8935
- var import_core4 = require("@nestjs/core");
8947
+ var import_core5 = require("@nestjs/core");
8936
8948
 
8937
8949
  // src/discovery/discovery.controller.ts
8938
8950
  var import_common53 = require("@nestjs/common");
@@ -9056,7 +9068,7 @@ var DiscoveryModule = class _DiscoveryModule {
9056
9068
  return {
9057
9069
  module: _DiscoveryModule,
9058
9070
  imports: [
9059
- import_core4.DiscoveryModule,
9071
+ import_core5.DiscoveryModule,
9060
9072
  ...options.imports ?? []
9061
9073
  ],
9062
9074
  controllers,
@@ -9097,11 +9109,11 @@ function loadDiscoverySnapshotFromFile(path) {
9097
9109
  __name(loadDiscoverySnapshotFromFile, "loadDiscoverySnapshotFromFile");
9098
9110
 
9099
9111
  // src/discovery/headless-scan.ts
9100
- var import_core5 = require("@nestjs/core");
9112
+ var import_core6 = require("@nestjs/core");
9101
9113
  async function runHeadlessDiscoveryScan(appModule, options = {}) {
9102
9114
  const exit = options.exit ?? ((code) => process.exit(code));
9103
9115
  try {
9104
- const app = await import_core5.NestFactory.createApplicationContext(appModule, {
9116
+ const app = await import_core6.NestFactory.createApplicationContext(appModule, {
9105
9117
  logger: options.logger ?? [
9106
9118
  "error",
9107
9119
  "warn",
@@ -15889,6 +15901,7 @@ CheckoutOfferModule = _ts_decorate96([
15889
15901
  ipFingerprint,
15890
15902
  isFeatureInPlan,
15891
15903
  isFeaturePlannedOnly,
15904
+ isLimitExceededError,
15892
15905
  loadApprovedCatalogKeys,
15893
15906
  loadDiscoverySnapshotFromFile,
15894
15907
  loadPlanCatalogFromFile,