@saasicat/nest 0.26.1 → 1.0.0-rc.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 (76) hide show
  1. package/LICENSE +90 -201
  2. package/dist/.build-stamp +1 -0
  3. package/dist/_entries.cjs +3175 -2756
  4. package/dist/admin/index.d.cts +2 -2
  5. package/dist/admin/index.d.ts +2 -2
  6. package/dist/admin/index.js +2 -2
  7. package/dist/{admin-stats.service-HUZIU448.d.cts → admin-stats.service-BroyovpL.d.cts} +1 -1
  8. package/dist/{admin-stats.service-Db2exT_d.d.ts → admin-stats.service-G53IEkkt.d.ts} +1 -1
  9. package/dist/billing/index.d.cts +7 -7
  10. package/dist/billing/index.d.ts +7 -7
  11. package/dist/billing/index.js +38 -36
  12. package/dist/catalog/index.d.cts +13 -1
  13. package/dist/catalog/index.d.ts +13 -1
  14. package/dist/catalog/index.js +10 -8
  15. package/dist/checkout-offer/index.js +2 -2
  16. package/dist/chunk-2FR6ZL7R.js +0 -0
  17. package/dist/chunk-2SLTRKXC.js +0 -0
  18. package/dist/{chunk-7ZPFQXHM.js → chunk-3UZW7TU4.js} +1 -1
  19. package/dist/{chunk-XTVAFJ3P.js → chunk-AEEK64XD.js} +3 -3
  20. package/dist/chunk-AOQJEYOL.js +20 -0
  21. package/dist/chunk-H3W6QDQH.js +85 -0
  22. package/dist/{chunk-YYUXKLXK.js → chunk-I7GTA3RX.js} +2 -2
  23. package/dist/{chunk-SN5UOC43.js → chunk-IPOLCDKN.js} +11 -11
  24. package/dist/{chunk-B7NRY2LV.js → chunk-IVEA6M3B.js} +2 -40
  25. package/dist/chunk-JCAZT727.js +1883 -0
  26. package/dist/chunk-JTLKWP3Q.js +792 -0
  27. package/dist/{chunk-E56W4U2P.js → chunk-O2J2HDXA.js} +4 -4
  28. package/dist/{chunk-WKVBNTYC.js → chunk-OUDSQ3PU.js} +6 -6
  29. package/dist/{chunk-MJ32VNZD.js → chunk-P5D5CYO2.js} +18 -18
  30. package/dist/{chunk-L4SUWH5B.js → chunk-QUZU2M33.js} +2 -2
  31. package/dist/{chunk-WPUVJRIN.js → chunk-S6BAD5XM.js} +10 -10
  32. package/dist/{chunk-7X5MSAIL.js → chunk-SH7J5IU7.js} +9 -9
  33. package/dist/{chunk-37HEKVDH.js → chunk-V4OJ43K2.js} +10 -10
  34. package/dist/{chunk-NYLON2VC.js → chunk-VBRYPU7V.js} +2 -2
  35. package/dist/{chunk-B6IWLV7R.js → chunk-VTMCMVYQ.js} +6 -6
  36. package/dist/{chunk-YWDLYOFN.js → chunk-WSPHIIGS.js} +429 -1205
  37. package/dist/{chunk-5ZXBXYXU.js → chunk-WXYJHZCN.js} +3 -3
  38. package/dist/{chunk-6WL6VQ7Z.js → chunk-XBYAFEOR.js} +2 -2
  39. package/dist/{chunk-NNNXXUPT.js → chunk-XEW6YXL5.js} +28 -93
  40. package/dist/chunk-Y43SGD5J.js +50 -0
  41. package/dist/{define-saasicat-BK6RRWWG.d.ts → define-saasicat-BFgeoVG9.d.ts} +28 -5
  42. package/dist/{define-saasicat-D30I-WET.d.cts → define-saasicat-CNUWjfNK.d.cts} +28 -5
  43. package/dist/discovery/index.js +4 -4
  44. package/dist/{enforce-quota.interceptor-B9QueAFf.d.ts → enforce-quota.interceptor-BYqmdFB5.d.ts} +3 -3
  45. package/dist/{enforce-quota.interceptor-DY6CLjoP.d.cts → enforce-quota.interceptor-BxIR63Wn.d.cts} +3 -3
  46. package/dist/entitlement/index.js +9 -6
  47. package/dist/index.d.cts +10 -10
  48. package/dist/index.d.ts +10 -10
  49. package/dist/index.js +61 -52
  50. package/dist/{saas-platform.module-D1mUZPrz.d.ts → module-options-BymIkAsh.d.ts} +68 -55
  51. package/dist/{saas-platform.module-CgalxzcT.d.cts → module-options-DgwBKVpC.d.cts} +68 -55
  52. package/dist/platform/index.d.cts +204 -22
  53. package/dist/platform/index.d.ts +204 -22
  54. package/dist/platform/index.js +49 -25
  55. package/dist/promo/index.d.cts +3 -3
  56. package/dist/promo/index.d.ts +3 -3
  57. package/dist/promo/index.js +3 -3
  58. package/dist/{module-BE17XFLg.d.ts → promo.module-CnzddtJ0.d.ts} +1 -1
  59. package/dist/{module-CHMIyWlf.d.cts → promo.module-DokmUGmm.d.cts} +1 -1
  60. package/dist/registration/index.d.cts +1 -1
  61. package/dist/registration/index.d.ts +1 -1
  62. package/dist/registration/index.js +1 -1
  63. package/dist/subscription-contract/index.js +6 -3
  64. package/dist/{tenant-billing.controller-BSlpFqVA.d.ts → tenant-billing.controller-BQPy5yXh.d.ts} +2 -2
  65. package/dist/{tenant-billing.controller-DHSUrT_N.d.cts → tenant-billing.controller-CODIYzlI.d.cts} +2 -2
  66. package/dist/{subscription-bundles.module-BDbODWvA.d.ts → tenant-billing.module-B0stLoJn.d.ts} +51 -51
  67. package/dist/{subscription-bundles.module-Cfi-lFWF.d.cts → tenant-billing.module-BxJS6fX4.d.cts} +51 -51
  68. package/dist/testing/index.d.cts +14 -14
  69. package/dist/testing/index.d.ts +14 -14
  70. package/dist/testing/index.js +31 -29
  71. package/package.json +6 -6
  72. package/dist/chunk-L4KTQ6CL.js +0 -20
  73. package/dist/chunk-TCBYOFAC.js +0 -1464
  74. /package/dist/{chunk-FY4EJTV4.js → chunk-OYNHOY45.js} +0 -0
  75. /package/dist/{service-D48clObR.d.cts → promo.service-D48clObR.d.cts} +0 -0
  76. /package/dist/{service-D48clObR.d.ts → promo.service-D48clObR.d.ts} +0 -0
@@ -0,0 +1,1883 @@
1
+ import {
2
+ PromoCodesModule
3
+ } from "./chunk-VTMCMVYQ.js";
4
+ import {
5
+ SubscriptionContractModule
6
+ } from "./chunk-Y43SGD5J.js";
7
+ import {
8
+ AdminManifestModule,
9
+ AdminManifestService,
10
+ AdminModule,
11
+ AdminResourcesModule,
12
+ AdminStatsModule,
13
+ MfaService,
14
+ SuperAdminGuard
15
+ } from "./chunk-IPOLCDKN.js";
16
+ import {
17
+ FEATURE_GUARD_MARKER,
18
+ PublicCatalogModule,
19
+ REQUIRE_FEATURE_KEY,
20
+ SubscriptionBundleModule,
21
+ TenantBillingModule,
22
+ isPlatformFeatureGuard
23
+ } from "./chunk-WSPHIIGS.js";
24
+ import {
25
+ CatalogModule
26
+ } from "./chunk-SH7J5IU7.js";
27
+ import {
28
+ CheckoutOfferModule
29
+ } from "./chunk-AEEK64XD.js";
30
+ import {
31
+ SaaSiCatPublicRoute
32
+ } from "./chunk-YSC46BGM.js";
33
+ import {
34
+ DiscoveryModule
35
+ } from "./chunk-XBYAFEOR.js";
36
+ import {
37
+ EntitlementModule
38
+ } from "./chunk-H3W6QDQH.js";
39
+ import {
40
+ LimitExceededError
41
+ } from "./chunk-XEW6YXL5.js";
42
+ import {
43
+ PLAN_CATALOG_TOKEN,
44
+ PlanCatalogModule
45
+ } from "./chunk-DQKCK7DX.js";
46
+ import {
47
+ codedError
48
+ } from "./chunk-EBJJNTW6.js";
49
+ import {
50
+ asProvider
51
+ } from "./chunk-SABTXESR.js";
52
+ import {
53
+ ENFORCE_QUOTA_KEY
54
+ } from "./chunk-QUZU2M33.js";
55
+ import {
56
+ __name
57
+ } from "./chunk-SHUYVCID.js";
58
+
59
+ // src/setup/setup.tokens.ts
60
+ var SETUP_PROVISIONING_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SetupProvisioningPort");
61
+ var SETUP_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SetupConfig");
62
+
63
+ // src/setup/setup.service.ts
64
+ import { BadRequestException, ConflictException, ForbiddenException, Inject, Injectable, UnauthorizedException } from "@nestjs/common";
65
+ import { isPlatformUserExistsError, SETUP_ERROR_CODES } from "@saasicat/types";
66
+ import { randomBytes, timingSafeEqual } from "crypto";
67
+ import QRCode from "qrcode";
68
+ function _ts_decorate(decorators, target, key, desc) {
69
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
70
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
71
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
72
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
73
+ }
74
+ __name(_ts_decorate, "_ts_decorate");
75
+ function _ts_metadata(k, v) {
76
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
77
+ }
78
+ __name(_ts_metadata, "_ts_metadata");
79
+ function _ts_param(paramIndex, decorator) {
80
+ return function(target, key) {
81
+ decorator(target, key, paramIndex);
82
+ };
83
+ }
84
+ __name(_ts_param, "_ts_param");
85
+ var GENERATED_PASSWORD_BYTES = 12;
86
+ function timingSafeEqualStr(a, b) {
87
+ const ab = Buffer.from(a, "utf8");
88
+ const bb = Buffer.from(b, "utf8");
89
+ if (ab.length !== bb.length) return false;
90
+ return timingSafeEqual(ab, bb);
91
+ }
92
+ __name(timingSafeEqualStr, "timingSafeEqualStr");
93
+ var SetupService = class {
94
+ static {
95
+ __name(this, "SetupService");
96
+ }
97
+ users;
98
+ config;
99
+ mfa;
100
+ constructor(users, config, mfa) {
101
+ this.users = users;
102
+ this.config = config;
103
+ this.mfa = mfa;
104
+ }
105
+ async status() {
106
+ return {
107
+ needsSetup: await this.users.countSuperAdmins() === 0
108
+ };
109
+ }
110
+ async setup(req) {
111
+ this.assertToken(req.token);
112
+ await this.assertNoSuperAdmin();
113
+ const email = req.email.trim().toLowerCase();
114
+ if (!email.includes("@")) {
115
+ throw new BadRequestException(codedError(SETUP_ERROR_CODES.INVALID_EMAIL));
116
+ }
117
+ const generated = !req.password;
118
+ const password = req.password ?? randomBytes(GENERATED_PASSWORD_BYTES).toString("base64url");
119
+ let user;
120
+ try {
121
+ user = await this.users.createSuperAdmin({
122
+ email,
123
+ password
124
+ });
125
+ } catch (err) {
126
+ if (isPlatformUserExistsError(err)) {
127
+ throw new ConflictException({
128
+ code: SETUP_ERROR_CODES.EMAIL_EXISTS,
129
+ message: err.message
130
+ });
131
+ }
132
+ throw err;
133
+ }
134
+ const totp = await this.mfa.setup(user.id, user.email, this.config.mfaIssuer);
135
+ const qrDataUrl = await QRCode.toDataURL(totp.otpauthUri, {
136
+ width: 220,
137
+ margin: 1
138
+ });
139
+ return {
140
+ userId: user.id,
141
+ email: user.email,
142
+ otpauthUri: totp.otpauthUri,
143
+ qrDataUrl,
144
+ secret: totp.secret,
145
+ ...generated ? {
146
+ generatedPassword: password
147
+ } : {}
148
+ };
149
+ }
150
+ async confirmMfa(req) {
151
+ this.assertToken(req.token);
152
+ const ok = await this.mfa.verify({
153
+ userId: req.userId,
154
+ code: req.code
155
+ });
156
+ return {
157
+ ok
158
+ };
159
+ }
160
+ assertToken(token) {
161
+ const expected = process.env[this.config.setupTokenEnvVar]?.trim();
162
+ if (!expected) {
163
+ throw new ForbiddenException({
164
+ code: SETUP_ERROR_CODES.SETUP_DISABLED,
165
+ message: `${this.config.setupTokenEnvVar} is not set \u2014 setup is disabled.`,
166
+ params: {
167
+ envVar: this.config.setupTokenEnvVar
168
+ }
169
+ });
170
+ }
171
+ if (!token || !timingSafeEqualStr(token, expected)) {
172
+ throw new UnauthorizedException(codedError(SETUP_ERROR_CODES.INVALID_SETUP_TOKEN));
173
+ }
174
+ }
175
+ async assertNoSuperAdmin() {
176
+ if (await this.users.countSuperAdmins() > 0) {
177
+ throw new ConflictException(codedError(SETUP_ERROR_CODES.SETUP_ALREADY_DONE));
178
+ }
179
+ }
180
+ };
181
+ SetupService = _ts_decorate([
182
+ Injectable(),
183
+ _ts_param(0, Inject(SETUP_PROVISIONING_PORT_TOKEN)),
184
+ _ts_param(1, Inject(SETUP_CONFIG_TOKEN)),
185
+ _ts_metadata("design:type", Function),
186
+ _ts_metadata("design:paramtypes", [
187
+ typeof SuperAdminProvisioningPort === "undefined" ? Object : SuperAdminProvisioningPort,
188
+ typeof SetupConfig === "undefined" ? Object : SetupConfig,
189
+ typeof MfaService === "undefined" ? Object : MfaService
190
+ ])
191
+ ], SetupService);
192
+
193
+ // src/setup/setup.controller.ts
194
+ import { Body, Controller, Get, Post } from "@nestjs/common";
195
+ import { IsEmail, IsNotEmpty, IsOptional, IsString, MinLength } from "class-validator";
196
+ function _ts_decorate2(decorators, target, key, desc) {
197
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
198
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
199
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
200
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
201
+ }
202
+ __name(_ts_decorate2, "_ts_decorate");
203
+ function _ts_metadata2(k, v) {
204
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
205
+ }
206
+ __name(_ts_metadata2, "_ts_metadata");
207
+ function _ts_param2(paramIndex, decorator) {
208
+ return function(target, key) {
209
+ decorator(target, key, paramIndex);
210
+ };
211
+ }
212
+ __name(_ts_param2, "_ts_param");
213
+ var SetupDto = class SetupDto2 {
214
+ static {
215
+ __name(this, "SetupDto");
216
+ }
217
+ token;
218
+ email;
219
+ password;
220
+ };
221
+ _ts_decorate2([
222
+ IsString(),
223
+ IsNotEmpty(),
224
+ _ts_metadata2("design:type", String)
225
+ ], SetupDto.prototype, "token", void 0);
226
+ _ts_decorate2([
227
+ IsEmail(),
228
+ _ts_metadata2("design:type", String)
229
+ ], SetupDto.prototype, "email", void 0);
230
+ _ts_decorate2([
231
+ IsOptional(),
232
+ IsString(),
233
+ MinLength(8),
234
+ _ts_metadata2("design:type", String)
235
+ ], SetupDto.prototype, "password", void 0);
236
+ var SetupConfirmMfaDto = class SetupConfirmMfaDto2 {
237
+ static {
238
+ __name(this, "SetupConfirmMfaDto");
239
+ }
240
+ token;
241
+ userId;
242
+ code;
243
+ };
244
+ _ts_decorate2([
245
+ IsString(),
246
+ IsNotEmpty(),
247
+ _ts_metadata2("design:type", String)
248
+ ], SetupConfirmMfaDto.prototype, "token", void 0);
249
+ _ts_decorate2([
250
+ IsString(),
251
+ IsNotEmpty(),
252
+ _ts_metadata2("design:type", String)
253
+ ], SetupConfirmMfaDto.prototype, "userId", void 0);
254
+ _ts_decorate2([
255
+ IsString(),
256
+ IsNotEmpty(),
257
+ _ts_metadata2("design:type", String)
258
+ ], SetupConfirmMfaDto.prototype, "code", void 0);
259
+ var SetupController = class {
260
+ static {
261
+ __name(this, "SetupController");
262
+ }
263
+ setup;
264
+ constructor(setup) {
265
+ this.setup = setup;
266
+ }
267
+ status() {
268
+ return this.setup.status();
269
+ }
270
+ run(dto) {
271
+ return this.setup.setup(dto);
272
+ }
273
+ confirm(dto) {
274
+ return this.setup.confirmMfa(dto);
275
+ }
276
+ };
277
+ _ts_decorate2([
278
+ Get("status"),
279
+ _ts_metadata2("design:type", Function),
280
+ _ts_metadata2("design:paramtypes", []),
281
+ _ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
282
+ ], SetupController.prototype, "status", null);
283
+ _ts_decorate2([
284
+ Post(),
285
+ _ts_param2(0, Body()),
286
+ _ts_metadata2("design:type", Function),
287
+ _ts_metadata2("design:paramtypes", [
288
+ typeof SetupDto === "undefined" ? Object : SetupDto
289
+ ]),
290
+ _ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
291
+ ], SetupController.prototype, "run", null);
292
+ _ts_decorate2([
293
+ Post("confirm-mfa"),
294
+ _ts_param2(0, Body()),
295
+ _ts_metadata2("design:type", Function),
296
+ _ts_metadata2("design:paramtypes", [
297
+ typeof SetupConfirmMfaDto === "undefined" ? Object : SetupConfirmMfaDto
298
+ ]),
299
+ _ts_metadata2("design:returntype", typeof Promise === "undefined" ? Object : Promise)
300
+ ], SetupController.prototype, "confirm", null);
301
+ SetupController = _ts_decorate2([
302
+ Controller("admin/setup"),
303
+ SaaSiCatPublicRoute(),
304
+ _ts_metadata2("design:type", Function),
305
+ _ts_metadata2("design:paramtypes", [
306
+ typeof SetupService === "undefined" ? Object : SetupService
307
+ ])
308
+ ], SetupController);
309
+
310
+ // src/setup/setup.module.ts
311
+ import { Module } from "@nestjs/common";
312
+ function _ts_decorate3(decorators, target, key, desc) {
313
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
314
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
315
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
316
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
317
+ }
318
+ __name(_ts_decorate3, "_ts_decorate");
319
+ var DEFAULT_SETUP_TOKEN_ENV_VAR = "SETUP_TOKEN";
320
+ var DEFAULT_MFA_ISSUER = "SuperAdmin";
321
+ var SetupModule = class _SetupModule {
322
+ static {
323
+ __name(this, "SetupModule");
324
+ }
325
+ static forRoot(options) {
326
+ return {
327
+ module: _SetupModule,
328
+ global: options.global ?? false,
329
+ imports: options.imports ?? [],
330
+ controllers: [
331
+ SetupController
332
+ ],
333
+ providers: [
334
+ asProvider(SETUP_PROVISIONING_PORT_TOKEN, options.provisioningPort),
335
+ {
336
+ provide: SETUP_CONFIG_TOKEN,
337
+ useValue: {
338
+ setupTokenEnvVar: options.setupTokenEnvVar ?? DEFAULT_SETUP_TOKEN_ENV_VAR,
339
+ mfaIssuer: options.mfaIssuer ?? DEFAULT_MFA_ISSUER
340
+ }
341
+ },
342
+ SetupService,
343
+ ...options.extraProviders ?? []
344
+ ],
345
+ exports: [
346
+ SetupService
347
+ ]
348
+ };
349
+ }
350
+ };
351
+ SetupModule = _ts_decorate3([
352
+ Module({})
353
+ ], SetupModule);
354
+
355
+ // src/platform/enforcement-chain.check.ts
356
+ import { Inject as Inject2, Injectable as Injectable2, Optional } from "@nestjs/common";
357
+ import { DiscoveryService, MetadataScanner } from "@nestjs/core";
358
+ function _ts_decorate4(decorators, target, key, desc) {
359
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
360
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
361
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
362
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
363
+ }
364
+ __name(_ts_decorate4, "_ts_decorate");
365
+ function _ts_metadata3(k, v) {
366
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
367
+ }
368
+ __name(_ts_metadata3, "_ts_metadata");
369
+ function _ts_param3(paramIndex, decorator) {
370
+ return function(target, key) {
371
+ decorator(target, key, paramIndex);
372
+ };
373
+ }
374
+ __name(_ts_param3, "_ts_param");
375
+ var GUARDS_METADATA = "__guards__";
376
+ var METHOD_METADATA = "method";
377
+ var ENFORCEMENT_CHAIN_STATE_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/EnforcementChainState");
378
+ var EnforcementChainCheck = class {
379
+ static {
380
+ __name(this, "EnforcementChainCheck");
381
+ }
382
+ discoveryService;
383
+ metadataScanner;
384
+ state;
385
+ constructor(discoveryService, metadataScanner, state = {
386
+ featureEnforcementActive: true,
387
+ quotaEnforcementActive: true,
388
+ globalGuardBound: true,
389
+ globalGuardOptedOut: false
390
+ }) {
391
+ this.discoveryService = discoveryService;
392
+ this.metadataScanner = metadataScanner;
393
+ this.state = state;
394
+ }
395
+ onApplicationBootstrap() {
396
+ const annotated = this.findAnnotatedRoutes();
397
+ if (!this.state.featureEnforcementActive) {
398
+ if (annotated.length === 0) return;
399
+ throw new Error(`${annotated.length} route(s) are annotated for licence enforcement, but nothing can resolve a tenant to a plan, so @RequireFeature and @EnforceQuota do nothing at all: every annotated route serves unlicensed traffic and every quota reads as unlimited.
400
+ ${describe(annotated)}
401
+ Set one of: adapters.planResolver, defaultPlanId, or tenantBilling. If this application deliberately publishes a catalogue without enforcing it, remove the annotations \u2014 a decorator that cannot act is not documentation, it is a claim the code does not keep.`);
402
+ }
403
+ if (!this.state.quotaEnforcementActive) {
404
+ const unenforced = annotated.filter((r) => r.quota !== null);
405
+ if (unenforced.length > 0) {
406
+ throw new Error(`${unenforced.length} route(s) enforce a quota, but nothing registers EnforceQuotaInterceptor, so every @EnforceQuota reads as unlimited and the limit a customer paid for is not applied:
407
+ ${describe(unenforced)}
408
+ The V3 entitlement stack resolves features, not quotas: the interceptor is registered only where a plan can be resolved. Set one of adapters.planResolver, defaultPlanId, or tenantBilling with a subscription repository \u2014 or remove the annotations, because a decorator that cannot act is not documentation.
409
+ If this application enforces these quotas itself, set \`enforcementChainCheck: false\` \u2014 it turns this check off and nothing else.`);
410
+ }
411
+ }
412
+ if (this.state.globalGuardBound) return;
413
+ const uncovered = annotated.filter((r) => r.features.length > 0 && !r.guarded);
414
+ if (uncovered.length === 0) return;
415
+ const why = this.state.globalGuardOptedOut ? "globalFeatureGuard is off" : "the platform binds no global feature guard on this entitlement path";
416
+ throw new Error(`${why} and ${uncovered.length} annotated route(s) have no feature guard in front of them, so they serve unlicensed traffic:
417
+ ${describe(uncovered)}
418
+ Bind one behind your auth guard \u2014 @UseGuards(JwtAuthGuard, StaticFeatureGuard), or FeatureGuard from @saasicat/nest/billing on the V3 entitlement path.
419
+ Two things this cannot see, and the way out of each: a guard of your own that wraps ours (put FEATURE_GUARD_MARKER on the wrapper class \u2014 the symbol StaticFeatureGuard and FeatureGuard both carry), and a feature guard you bind globally as an APP_GUARD rather than per controller. If either is what these routes use, set \`enforcementChainCheck: false\` \u2014 it turns this check off and nothing else.`);
420
+ }
421
+ /**
422
+ * Every route carrying an enforcement annotation, with whether a
423
+ * recognisable feature guard covers it.
424
+ *
425
+ * Deliberately conservative in one direction only: an unrecognised wrapper
426
+ * is reported (and the message says so) rather than assumed safe, because
427
+ * the named route is cheap to check. The reverse — staying silent about a
428
+ * route with no guard at all — is the failure this exists to prevent.
429
+ */
430
+ findAnnotatedRoutes() {
431
+ const annotated = [];
432
+ for (const wrapper of this.discoveryService.getControllers()) {
433
+ const instance = wrapper.instance;
434
+ if (!instance || typeof instance !== "object") continue;
435
+ const ctor = instance.constructor;
436
+ if (!ctor) continue;
437
+ const classGuards = readGuards(ctor);
438
+ const prototype = Object.getPrototypeOf(instance);
439
+ for (const method of this.metadataScanner.getAllMethodNames(prototype)) {
440
+ const handler = instance[method];
441
+ if (typeof handler !== "function") continue;
442
+ if (Reflect.getMetadata(METHOD_METADATA, handler) === void 0) continue;
443
+ const features = readFeatures(handler) ?? readFeatures(ctor) ?? /* none */
444
+ [];
445
+ const quota = readQuota(handler) ?? readQuota(ctor);
446
+ if (features.length === 0 && quota === null) continue;
447
+ const guards = [
448
+ ...classGuards,
449
+ ...readGuards(handler)
450
+ ];
451
+ annotated.push({
452
+ controller: ctor.name,
453
+ handler: method,
454
+ features,
455
+ quota,
456
+ guarded: guards.some(isPlatformFeatureGuard)
457
+ });
458
+ }
459
+ }
460
+ return annotated;
461
+ }
462
+ };
463
+ EnforcementChainCheck = _ts_decorate4([
464
+ Injectable2(),
465
+ _ts_param3(0, Inject2(DiscoveryService)),
466
+ _ts_param3(1, Inject2(MetadataScanner)),
467
+ _ts_param3(2, Optional()),
468
+ _ts_param3(2, Inject2(ENFORCEMENT_CHAIN_STATE_TOKEN)),
469
+ _ts_metadata3("design:type", Function),
470
+ _ts_metadata3("design:paramtypes", [
471
+ typeof DiscoveryService === "undefined" ? Object : DiscoveryService,
472
+ typeof MetadataScanner === "undefined" ? Object : MetadataScanner,
473
+ typeof EnforcementChainState === "undefined" ? Object : EnforcementChainState
474
+ ])
475
+ ], EnforcementChainCheck);
476
+ function describe(routes) {
477
+ return routes.map((r) => {
478
+ const requirement = r.features.length > 0 ? `requires ${r.features.join(" or ")}` : `enforces quota ${r.quota}`;
479
+ return ` ${r.controller}.${r.handler} \u2014 ${requirement}`;
480
+ }).join("\n");
481
+ }
482
+ __name(describe, "describe");
483
+ function readFeatures(target) {
484
+ const value = Reflect.getMetadata(REQUIRE_FEATURE_KEY, target);
485
+ return Array.isArray(value) ? value : null;
486
+ }
487
+ __name(readFeatures, "readFeatures");
488
+ function readQuota(target) {
489
+ const value = Reflect.getMetadata(ENFORCE_QUOTA_KEY, target);
490
+ if (typeof value !== "object" || value === null) return null;
491
+ const quotaKey = value.quotaKey;
492
+ return typeof quotaKey === "string" ? quotaKey : null;
493
+ }
494
+ __name(readQuota, "readQuota");
495
+ function readGuards(target) {
496
+ const value = Reflect.getMetadata(GUARDS_METADATA, target);
497
+ if (!Array.isArray(value)) return [];
498
+ return value.flatMap((guard) => typeof guard === "function" ? [
499
+ guard
500
+ ] : [
501
+ guard,
502
+ guard?.constructor
503
+ ]);
504
+ }
505
+ __name(readGuards, "readGuards");
506
+
507
+ // src/platform/validation/rules.ts
508
+ import { assertPersistenceCapabilities } from "@saasicat/types";
509
+ var DOCS_BASE = "https://github.com/uelker70/saasicat/blob/main/docs/reference/options.md";
510
+ var headingSlug = /* @__PURE__ */ __name((text) => text.trim().toLowerCase().replace(/[^a-z0-9 -]/g, "").replace(/ /g, "-"), "headingSlug");
511
+ var docsUrlFor = /* @__PURE__ */ __name((id) => `${DOCS_BASE}#${headingSlug(id)}`, "docsUrlFor");
512
+ function optionsOf(slot) {
513
+ return typeof slot === "object" ? slot : void 0;
514
+ }
515
+ __name(optionsOf, "optionsOf");
516
+ var bundle = /* @__PURE__ */ __name((c) => c.options.persistence, "bundle");
517
+ var requiresFullEntitlement = /* @__PURE__ */ __name((c) => Boolean(c.options.entitlement || c.options.tenantBilling || c.options.subscriptionBundles), "requiresFullEntitlement");
518
+ var resolvesPlans = /* @__PURE__ */ __name((c) => Boolean(c.adapters.planResolver || c.options.defaultPlanId || c.options.tenantBilling && c.adapters.subscriptionRepository), "resolvesPlans");
519
+ function absent(required) {
520
+ return Object.keys(required).filter((key) => !required[key]);
521
+ }
522
+ __name(absent, "absent");
523
+ var list = /* @__PURE__ */ __name((names) => names.join(", "), "list");
524
+ var RULE_SPECS = [
525
+ {
526
+ id: "core.adapters-bound",
527
+ when: /* @__PURE__ */ __name(() => true, "when"),
528
+ assert: /* @__PURE__ */ __name((c) => absent(coreAdapters(c)).length === 0, "assert"),
529
+ message: /* @__PURE__ */ __name((c) => `the core adapters are missing: ${list(absent(coreAdapters(c)))}. Bind them through \`persistence: prismaPersistence({ client })\` or one by one through \`adapters\`.`, "message")
530
+ },
531
+ {
532
+ id: "catalog.identity-or-sink",
533
+ when: /* @__PURE__ */ __name((c) => !c.options.planCatalog, "when"),
534
+ assert: /* @__PURE__ */ __name((c) => Boolean(c.adapters.planCatalogReadSink && c.options.dbCatalog), "assert"),
535
+ message: "no plan catalogue is reachable. Either set `planCatalog` (the quickstart YAML path) or, for DB hydration, BOTH a `planCatalogReadSink` (via `adapters` or `persistence`) AND `dbCatalog` ({ projectKey, currency, vatRate }). Without the identity the sink loads with projectKey '' and the app boots with a silently empty catalogue."
536
+ },
537
+ {
538
+ id: "catalog.requires-persistence",
539
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.catalog), "when"),
540
+ assert: /* @__PURE__ */ __name((c) => Boolean(bundle(c)?.catalog), "assert"),
541
+ message: "catalog is enabled, but `persistence.catalog` is missing. Use a complete persistence bundle, or wire `CatalogModule` directly."
542
+ },
543
+ {
544
+ id: "admin-resources.requires-persistence",
545
+ when: /* @__PURE__ */ __name((c) => {
546
+ if (!c.options.adminResources) return false;
547
+ return !optionsOf(c.options.adminResources)?.resources;
548
+ }, "when"),
549
+ assert: /* @__PURE__ */ __name((c) => Boolean(bundle(c)?.adminResources), "assert"),
550
+ message: "adminResources is enabled, but `persistence.adminResources` is missing. Use a complete persistence bundle, or pass your own `adminResources.resources`."
551
+ },
552
+ {
553
+ id: "promo-codes.requires-persistence-and-transactions",
554
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.promoCodes), "when"),
555
+ assert: /* @__PURE__ */ __name((c) => Boolean(bundle(c)?.promo && c.adapters.transactionRunner), "assert"),
556
+ message: /* @__PURE__ */ __name((c) => `promoCodes requires \`persistence.promo\` and a transactionRunner; missing: ${list(absent({
557
+ "persistence.promo": bundle(c)?.promo,
558
+ transactionRunner: c.adapters.transactionRunner
559
+ }))}. Redeeming a code writes two rows and must not half-apply.`, "message")
560
+ },
561
+ {
562
+ id: "setup.requires-provisioning-port",
563
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.setup), "when"),
564
+ assert: /* @__PURE__ */ __name((c) => Boolean(optionsOf(c.options.setup)?.provisioningPort ?? bundle(c)?.core.superAdminProvisioning), "assert"),
565
+ message: "setup is enabled, but no provisioningPort is available. Set `setup.provisioningPort` or use a bundle providing `persistence.core.superAdminProvisioning`."
566
+ },
567
+ {
568
+ id: "admin-stats.requires-audit-stats-port",
569
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.adminStats), "when"),
570
+ assert: /* @__PURE__ */ __name((c) => Boolean(optionsOf(c.options.adminStats)?.auditStatsPort ?? bundle(c)?.core.auditStats), "assert"),
571
+ message: "adminStats is enabled, but no auditStatsPort is available. Set `adminStats.auditStatsPort` or use a bundle providing `persistence.core.auditStats`."
572
+ },
573
+ {
574
+ id: "subscription-contract.requires-repository",
575
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.subscriptionContract), "when"),
576
+ assert: /* @__PURE__ */ __name((c) => Boolean(optionsOf(c.options.subscriptionContract)?.subscriptionContractRepository ?? bundle(c)?.entitlement?.subscriptionContractRepository), "assert"),
577
+ message: "subscriptionContract is enabled, but no repository is available. Set `subscriptionContract.subscriptionContractRepository` or use a compatible persistence bundle."
578
+ },
579
+ {
580
+ id: "promo-codes.public-preview-requires-first-time-check",
581
+ when: /* @__PURE__ */ __name((c) => {
582
+ const config = optionsOf(c.options.promoCodes);
583
+ return config !== void 0 && config.includePublicController !== false;
584
+ }, "when"),
585
+ assert: /* @__PURE__ */ __name((c) => Boolean(optionsOf(c.options.promoCodes)?.firstTimeCustomerCheck), "assert"),
586
+ message: "the public promo preview is mounted but has no `firstTimeCustomerCheck`, so every first-time-only code would preview as unavailable. Pass one, or set `promoCodes.includePublicController: false`."
587
+ },
588
+ {
589
+ id: "tenant-billing.requires-ports",
590
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.tenantBilling) && !bundle(c)?.tenantBilling, "when"),
591
+ assert: /* @__PURE__ */ __name((c) => absent(tenantBillingPorts(c)).length === 0, "assert"),
592
+ message: /* @__PURE__ */ __name((c) => `tenantBilling is enabled without a \`persistence.tenantBilling\` slice, and these adapters are missing: ${list(absent(tenantBillingPorts(c)))}.`, "message")
593
+ },
594
+ {
595
+ id: "subscription-bundles.requires-tenant-billing",
596
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.subscriptionBundles), "when"),
597
+ assert: /* @__PURE__ */ __name((c) => Boolean(c.options.tenantBilling), "assert"),
598
+ message: "subscriptionBundles requires tenantBilling, so the two share authentication and subscription usage rather than resolving them twice."
599
+ },
600
+ {
601
+ id: "subscription-bundles.requires-repositories",
602
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.subscriptionBundles), "when"),
603
+ assert: /* @__PURE__ */ __name((c) => absent(bundleRepositories(c)).length === 0, "assert"),
604
+ message: /* @__PURE__ */ __name((c) => `subscriptionBundles is enabled, but these persistence adapters are missing: ${list(absent(bundleRepositories(c)))}.`, "message")
605
+ },
606
+ {
607
+ id: "entitlement.requires-adapters",
608
+ when: requiresFullEntitlement,
609
+ assert: /* @__PURE__ */ __name((c) => absent(entitlementAdapters(c)).length === 0, "assert"),
610
+ message: /* @__PURE__ */ __name((c) => `entitlement active or required by the standard stack, but adapters are missing: ${list(absent(entitlementAdapters(c)))}.`, "message")
611
+ },
612
+ {
613
+ id: "entitlement.requires-transactional-persistence",
614
+ when: /* @__PURE__ */ __name((c) => requiresFullEntitlement(c) && Boolean(bundle(c)), "when"),
615
+ assert: /* @__PURE__ */ __name((c) => absent(entitlementCapabilities(c)).length === 0, "assert"),
616
+ message: /* @__PURE__ */ __name((c) => `the entitlement stack needs a store that can do ${list(absent(entitlementCapabilities(c)))}, and the persistence bundle declares it cannot. \`enforceLimit()\` reads a counter and writes it back; without a transaction and a row lock two concurrent requests both pass the limit.`, "message"),
617
+ // `PersistenceCapabilityError` carries `code:
618
+ // 'PERSISTENCE_CAPABILITY_MISSING'`, which is a machine-readable part
619
+ // of the published contract rather than prose. Raising it through the
620
+ // shared helper keeps the class, the code and the wording identical to
621
+ // what a consumer catches today.
622
+ error: /* @__PURE__ */ __name((c) => {
623
+ assertPersistenceCapabilities(bundle(c).capabilities, {
624
+ transactions: true,
625
+ pessimisticLocking: true
626
+ }, "SaaSiCatModule entitlement (transactional enforceLimit)");
627
+ throw new Error("persistence capabilities check did not fail as expected");
628
+ }, "error")
629
+ },
630
+ {
631
+ id: "tenant-manifest.requires-plan-resolution",
632
+ when: /* @__PURE__ */ __name((c) => Boolean(c.options.tenantManifest), "when"),
633
+ assert: resolvesPlans,
634
+ message: "tenantManifest is enabled but nothing can resolve a tenant to a plan, so it could not filter features. Set `defaultPlanId`, `adapters.planResolver`, or enable `tenantBilling`."
635
+ }
636
+ ];
637
+ var PLATFORM_RULES = RULE_SPECS.map((spec) => ({
638
+ ...spec,
639
+ docs: docsUrlFor(spec.id)
640
+ }));
641
+ var coreAdapters = /* @__PURE__ */ __name((c) => ({
642
+ mfa: c.adapters.mfa,
643
+ audit: c.adapters.audit,
644
+ rlsBypass: c.adapters.rlsBypass
645
+ }), "coreAdapters");
646
+ var tenantBillingPorts = /* @__PURE__ */ __name((c) => {
647
+ const config = optionsOf(c.options.tenantBilling);
648
+ return {
649
+ subscriptionUsagePort: config?.subscriptionUsagePort,
650
+ subscriptionWritePort: config?.subscriptionWritePort
651
+ };
652
+ }, "tenantBillingPorts");
653
+ var bundleRepositories = /* @__PURE__ */ __name((c) => ({
654
+ subscriptionBundleRepository: bundle(c)?.entitlement?.subscriptionBundleRepository,
655
+ bundleRepository: bundle(c)?.catalog?.bundleRepository
656
+ }), "bundleRepositories");
657
+ var entitlementAdapters = /* @__PURE__ */ __name((c) => ({
658
+ subscriptionRepository: c.adapters.subscriptionRepository,
659
+ planVersionRepository: c.adapters.planVersionRepository,
660
+ transactionRunner: c.adapters.transactionRunner
661
+ }), "entitlementAdapters");
662
+ var entitlementCapabilities = /* @__PURE__ */ __name((c) => ({
663
+ transactions: bundle(c)?.capabilities.transactions,
664
+ "pessimistic locking": bundle(c)?.capabilities.pessimisticLocking
665
+ }), "entitlementCapabilities");
666
+
667
+ // src/platform/validation/validate.ts
668
+ function findViolations(configuration, rules = PLATFORM_RULES) {
669
+ const violations = [];
670
+ for (const rule of rules) {
671
+ if (!rule.when(configuration)) continue;
672
+ if (rule.assert(configuration)) continue;
673
+ violations.push({
674
+ id: rule.id,
675
+ message: typeof rule.message === "string" ? rule.message : rule.message(configuration),
676
+ docs: rule.docs
677
+ });
678
+ }
679
+ return violations;
680
+ }
681
+ __name(findViolations, "findViolations");
682
+ var SaaSiCatConfigurationError = class extends Error {
683
+ static {
684
+ __name(this, "SaaSiCatConfigurationError");
685
+ }
686
+ violations;
687
+ constructor(violations) {
688
+ super(formatViolations(violations)), this.violations = violations;
689
+ this.name = "SaaSiCatConfigurationError";
690
+ }
691
+ };
692
+ function formatViolations(violations) {
693
+ const heading = violations.length === 1 ? "SaaSiCatModule.forRoot: 1 configuration problem:" : `SaaSiCatModule.forRoot: ${violations.length} configuration problems:`;
694
+ const body = violations.map((v, index) => ` ${index + 1}. [${v.id}] ${v.message}
695
+ \u2192 ${v.docs}`).join("\n");
696
+ return `${heading}
697
+ ${body}`;
698
+ }
699
+ __name(formatViolations, "formatViolations");
700
+ function assertConfiguration(configuration, rules = PLATFORM_RULES) {
701
+ const violations = findViolations(configuration, rules);
702
+ if (violations.length === 0) return;
703
+ if (violations.length === 1) {
704
+ const rule = rules.find((r) => r.id === violations[0].id);
705
+ if (rule?.error) throw rule.error(configuration);
706
+ }
707
+ throw new SaaSiCatConfigurationError(violations);
708
+ }
709
+ __name(assertConfiguration, "assertConfiguration");
710
+
711
+ // src/platform/quota-providers-usage-snapshot.ts
712
+ var QuotaProvidersUsageSnapshot = class {
713
+ static {
714
+ __name(this, "QuotaProvidersUsageSnapshot");
715
+ }
716
+ providers;
717
+ constructor(providers) {
718
+ this.providers = providers;
719
+ }
720
+ async snapshot(tenantId) {
721
+ const entries = await Promise.all(this.providers.map(async (provider) => [
722
+ provider.key,
723
+ await provider.count(tenantId)
724
+ ]));
725
+ return Object.fromEntries(entries);
726
+ }
727
+ };
728
+
729
+ // src/platform/compose/context.ts
730
+ function optionsOf2(slot) {
731
+ return typeof slot === "object" ? slot : {};
732
+ }
733
+ __name(optionsOf2, "optionsOf");
734
+
735
+ // src/platform/compose/admin-resources.ts
736
+ function composeAdminResources({ options, persistence }) {
737
+ if (!options.adminResources) return [];
738
+ const config = optionsOf2(options.adminResources);
739
+ return [
740
+ AdminResourcesModule.forRoot({
741
+ resources: config.resources ?? persistence?.adminResources?.resources,
742
+ guards: config.guards ?? [
743
+ ...options.controller.guards,
744
+ SuperAdminGuard
745
+ ],
746
+ imports: config.imports ?? options.imports
747
+ })
748
+ ];
749
+ }
750
+ __name(composeAdminResources, "composeAdminResources");
751
+
752
+ // src/platform/compose/catalog.ts
753
+ function composeCatalog({ options, persistence, appInfo }) {
754
+ const config = options.catalog;
755
+ if (!config) return [];
756
+ const catalog = persistence?.catalog;
757
+ const modules = [
758
+ CatalogModule.forRoot({
759
+ planRepository: catalog.planRepository,
760
+ bundleRepository: catalog.bundleRepository,
761
+ catalogEntryRepository: catalog.catalogEntryRepository,
762
+ marketingProjectionRepository: catalog.marketingProjectionRepository,
763
+ promotionRepository: catalog.promotionRepository,
764
+ marketingSettingsRepository: catalog.marketingSettingsRepository,
765
+ controller: config.adminControllers === false ? void 0 : {
766
+ guards: options.controller.guards
767
+ },
768
+ imports: config.imports ?? options.imports,
769
+ extraProviders: config.extraProviders,
770
+ strictModeCheckMode: config.strictModeCheckMode,
771
+ autoSyncDiscoveryAtBoot: config.autoSyncDiscoveryAtBoot,
772
+ marketedOnlyFeatures: config.marketedOnlyFeatures,
773
+ featureUiRegistry: config.featureUiRegistry,
774
+ publicMarketingCatalog: config.publicMarketingCatalog
775
+ })
776
+ ];
777
+ if (config.publicCatalog !== false) {
778
+ modules.push(PublicCatalogModule.forRoot({
779
+ featureUiRegistry: config.featureUiRegistry,
780
+ projectKey: appInfo.key,
781
+ bundleRepository: catalog.bundleRepository,
782
+ marketingRepository: catalog.marketingProjectionRepository,
783
+ catalogEntryRepository: catalog.catalogEntryRepository,
784
+ imports: config.imports ?? options.imports
785
+ }));
786
+ }
787
+ return modules;
788
+ }
789
+ __name(composeCatalog, "composeCatalog");
790
+
791
+ // src/platform/compose/entitlement.ts
792
+ function composeEntitlement({ options, adapters, persistence, requiresFullEntitlement: requiresFullEntitlement2 }) {
793
+ if (!requiresFullEntitlement2) return [];
794
+ return [
795
+ EntitlementModule.forRoot({
796
+ subscriptionRepository: adapters.subscriptionRepository,
797
+ planVersionRepository: adapters.planVersionRepository,
798
+ transactionRunner: adapters.transactionRunner,
799
+ resolutionConfig: options.entitlement ? options.entitlement.resolutionConfig : void 0,
800
+ subscriptionContractRepository: persistence?.entitlement?.subscriptionContractRepository,
801
+ subscriptionBundleRepository: persistence?.entitlement?.subscriptionBundleRepository,
802
+ bundleRepository: persistence?.entitlement?.bundleRepository,
803
+ imports: options.imports,
804
+ global: Boolean(options.tenantBilling || options.subscriptionBundles)
805
+ })
806
+ ];
807
+ }
808
+ __name(composeEntitlement, "composeEntitlement");
809
+
810
+ // src/platform/compose/optional-modules.ts
811
+ function composeSetup({ options, persistence }) {
812
+ if (!options.setup) return [];
813
+ const config = optionsOf2(options.setup);
814
+ return [
815
+ SetupModule.forRoot({
816
+ ...config,
817
+ provisioningPort: config.provisioningPort ?? persistence?.core.superAdminProvisioning,
818
+ imports: config.imports ?? options.imports
819
+ })
820
+ ];
821
+ }
822
+ __name(composeSetup, "composeSetup");
823
+ function composeAdminStats({ options, persistence }) {
824
+ const config = options.adminStats;
825
+ if (!config) return [];
826
+ return [
827
+ AdminStatsModule.forRoot({
828
+ ...config,
829
+ guards: config.guards ?? [
830
+ ...options.controller.guards,
831
+ SuperAdminGuard
832
+ ],
833
+ auditStatsPort: config.auditStatsPort ?? persistence?.core.auditStats,
834
+ imports: config.imports ?? options.imports
835
+ })
836
+ ];
837
+ }
838
+ __name(composeAdminStats, "composeAdminStats");
839
+ function composeCheckoutOffer({ options, persistence }) {
840
+ const config = options.checkoutOffer;
841
+ if (!config) return [];
842
+ return [
843
+ CheckoutOfferModule.forRoot({
844
+ ...config,
845
+ bundleRepository: config.bundleRepository ?? persistence?.catalog?.bundleRepository,
846
+ planRepository: config.planRepository ?? persistence?.catalog?.planRepository,
847
+ catalogEntryRepository: config.catalogEntryRepository ?? persistence?.catalog?.catalogEntryRepository,
848
+ imports: config.imports ?? options.imports
849
+ })
850
+ ];
851
+ }
852
+ __name(composeCheckoutOffer, "composeCheckoutOffer");
853
+ function composeSubscriptionContract({ options, persistence }) {
854
+ if (!options.subscriptionContract) return [];
855
+ const config = optionsOf2(options.subscriptionContract);
856
+ return [
857
+ SubscriptionContractModule.forRoot({
858
+ ...config,
859
+ subscriptionContractRepository: config.subscriptionContractRepository ?? persistence?.entitlement?.subscriptionContractRepository,
860
+ imports: config.imports ?? options.imports
861
+ })
862
+ ];
863
+ }
864
+ __name(composeSubscriptionContract, "composeSubscriptionContract");
865
+
866
+ // src/platform/compose/promo-codes.ts
867
+ function composePromoCodes({ options, adapters, persistence }) {
868
+ if (!options.promoCodes) return [];
869
+ const config = optionsOf2(options.promoCodes);
870
+ const promo = persistence?.promo;
871
+ const { firstTimeCustomerCheck, adminGuards, imports: promoImports, includePublicController, adminController, ...promoOptions } = config;
872
+ return [
873
+ PromoCodesModule.forRoot({
874
+ ...promoOptions,
875
+ promoCodeRepository: promo.promoCodeRepository,
876
+ redemptionRepository: promo.redemptionRepository,
877
+ validationLogRepository: promo.validationLogRepository,
878
+ subscriptionLookup: promo.subscriptionLookup,
879
+ revenueAggregator: promo.revenueAggregator,
880
+ transactionRunner: adapters.transactionRunner,
881
+ firstTimeCustomerCheck: firstTimeCustomerCheck ?? {
882
+ hasExistingCustomerForEmail: /* @__PURE__ */ __name(async () => false, "hasExistingCustomerForEmail")
883
+ },
884
+ includePublicController: includePublicController ?? false,
885
+ adminController: adminController === false ? false : {
886
+ guards: adminGuards ?? [
887
+ ...options.controller.guards,
888
+ SuperAdminGuard
889
+ ]
890
+ },
891
+ imports: promoImports ?? options.imports
892
+ })
893
+ ];
894
+ }
895
+ __name(composePromoCodes, "composePromoCodes");
896
+
897
+ // src/platform/compose/tenant-bindings.ts
898
+ function normalizeTenantAuthGuards(guards) {
899
+ if (!Array.isArray(guards)) return guards;
900
+ return {
901
+ useFactory: /* @__PURE__ */ __name((...instances) => instances, "useFactory"),
902
+ inject: guards
903
+ };
904
+ }
905
+ __name(normalizeTenantAuthGuards, "normalizeTenantAuthGuards");
906
+ function quotaUsageSnapshotProvider(quotaProviders) {
907
+ return {
908
+ useFactory: /* @__PURE__ */ __name((...providers) => new QuotaProvidersUsageSnapshot(providers), "useFactory"),
909
+ inject: [
910
+ ...quotaProviders
911
+ ]
912
+ };
913
+ }
914
+ __name(quotaUsageSnapshotProvider, "quotaUsageSnapshotProvider");
915
+
916
+ // src/platform/compose/tenant-billing.ts
917
+ function composeTenantBilling(ctx) {
918
+ const config = ctx.options.tenantBilling;
919
+ if (!config) return [];
920
+ const tenantSlice = ctx.persistence?.tenantBilling;
921
+ const { authGuards, subscriptionUsagePort, usageSnapshotPort, subscriptionWritePort, imports: tenantImports, extraProviders, extraExports, ...tenantOptions } = config;
922
+ const quotaProviders = ctx.options.quotaProviders ?? [];
923
+ ctx.shared.quotaProvidersHostedByTenantBilling = quotaProviders.length > 0;
924
+ ctx.shared.authGuards = normalizeTenantAuthGuards(authGuards);
925
+ ctx.shared.subscriptionUsagePort = subscriptionUsagePort ?? tenantSlice?.subscriptionUsagePort;
926
+ return [
927
+ TenantBillingModule.forRoot({
928
+ ...tenantOptions,
929
+ authGuards: ctx.shared.authGuards,
930
+ subscriptionUsagePort: ctx.shared.subscriptionUsagePort,
931
+ usageSnapshotPort: usageSnapshotPort ?? tenantSlice?.usageSnapshotPort ?? quotaUsageSnapshotProvider(quotaProviders),
932
+ subscriptionWritePort: subscriptionWritePort ?? tenantSlice?.subscriptionWritePort,
933
+ imports: tenantImports ?? ctx.options.imports,
934
+ extraProviders: [
935
+ ...quotaProviders,
936
+ ...extraProviders ?? []
937
+ ],
938
+ extraExports: [
939
+ ...quotaProviders,
940
+ ...extraExports ?? []
941
+ ]
942
+ })
943
+ ];
944
+ }
945
+ __name(composeTenantBilling, "composeTenantBilling");
946
+ function composeSubscriptionBundles(ctx) {
947
+ if (!ctx.options.subscriptionBundles) return [];
948
+ const config = optionsOf2(ctx.options.subscriptionBundles);
949
+ const { controller, imports: bundleImports, extraProviders, ...bundleOptions } = config;
950
+ return [
951
+ SubscriptionBundleModule.forRoot({
952
+ ...bundleOptions,
953
+ subscriptionBundleRepository: ctx.persistence?.entitlement?.subscriptionBundleRepository,
954
+ bundleRepository: ctx.persistence?.catalog?.bundleRepository,
955
+ controller: controller === false ? void 0 : {
956
+ ...controller ?? {},
957
+ authGuards: ctx.shared.authGuards,
958
+ subscriptionUsagePort: ctx.shared.subscriptionUsagePort
959
+ },
960
+ // Falls back to the tenant-billing imports before the shared ones,
961
+ // because the two controllers usually resolve the same app
962
+ // providers and an app that scoped them for one meant both.
963
+ imports: bundleImports ?? optionsOf2(ctx.options.tenantBilling).imports ?? ctx.options.imports,
964
+ extraProviders
965
+ })
966
+ ];
967
+ }
968
+ __name(composeSubscriptionBundles, "composeSubscriptionBundles");
969
+
970
+ // src/platform/compose/index.ts
971
+ var FEATURE_COMPOSERS = [
972
+ composeEntitlement,
973
+ composeCatalog,
974
+ composeAdminResources,
975
+ composePromoCodes,
976
+ // Before the bundles — see the note above.
977
+ composeTenantBilling,
978
+ composeSubscriptionBundles,
979
+ composeSetup,
980
+ composeAdminStats,
981
+ composeCheckoutOffer,
982
+ composeSubscriptionContract
983
+ ];
984
+ function composeFeatures(ctx) {
985
+ return FEATURE_COMPOSERS.flatMap((compose) => compose(ctx));
986
+ }
987
+ __name(composeFeatures, "composeFeatures");
988
+
989
+ // src/platform/compose/module-exports.ts
990
+ var ALWAYS = [
991
+ PlanCatalogModule,
992
+ DiscoveryModule,
993
+ AdminModule,
994
+ // ADMIN_MANIFEST_CONFIG travels transitively: re-exporting an imported
995
+ // module's token directly is an UnknownExportException at boot — the module
996
+ // export here already carries it.
997
+ AdminManifestModule
998
+ ];
999
+ function optionalExports({ options, requiresFullEntitlement: requiresFullEntitlement2 }) {
1000
+ return [
1001
+ [
1002
+ requiresFullEntitlement2,
1003
+ EntitlementModule
1004
+ ],
1005
+ [
1006
+ options.catalog,
1007
+ CatalogModule
1008
+ ],
1009
+ [
1010
+ options.tenantBilling,
1011
+ TenantBillingModule
1012
+ ],
1013
+ [
1014
+ options.subscriptionBundles,
1015
+ SubscriptionBundleModule
1016
+ ],
1017
+ [
1018
+ options.adminResources,
1019
+ AdminResourcesModule
1020
+ ],
1021
+ [
1022
+ options.promoCodes,
1023
+ PromoCodesModule
1024
+ ],
1025
+ [
1026
+ options.setup,
1027
+ SetupModule
1028
+ ],
1029
+ [
1030
+ options.adminStats,
1031
+ AdminStatsModule
1032
+ ],
1033
+ [
1034
+ options.checkoutOffer,
1035
+ CheckoutOfferModule
1036
+ ],
1037
+ [
1038
+ options.subscriptionContract,
1039
+ SubscriptionContractModule
1040
+ ]
1041
+ ];
1042
+ }
1043
+ __name(optionalExports, "optionalExports");
1044
+ var MOUNTED_BUT_NOT_EXPORTED = [
1045
+ "PublicCatalogModule"
1046
+ ];
1047
+ function composeModuleExports(ctx, ownExports) {
1048
+ return [
1049
+ ...ALWAYS,
1050
+ ...optionalExports(ctx).filter(([enabled]) => Boolean(enabled)).map(([, module]) => module),
1051
+ ...ownExports
1052
+ ];
1053
+ }
1054
+ __name(composeModuleExports, "composeModuleExports");
1055
+
1056
+ // src/platform/plan-resolver.port.ts
1057
+ var PLAN_RESOLVER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PlanResolverPort");
1058
+ var StaticPlanResolver = class {
1059
+ static {
1060
+ __name(this, "StaticPlanResolver");
1061
+ }
1062
+ planId;
1063
+ constructor(planId) {
1064
+ this.planId = planId;
1065
+ }
1066
+ async getPlanIdForTenant() {
1067
+ return this.planId;
1068
+ }
1069
+ };
1070
+
1071
+ // src/platform/static-entitlement.service.ts
1072
+ import { Inject as Inject3, Injectable as Injectable3 } from "@nestjs/common";
1073
+ function _ts_decorate5(decorators, target, key, desc) {
1074
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1075
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1076
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1077
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1078
+ }
1079
+ __name(_ts_decorate5, "_ts_decorate");
1080
+ function _ts_metadata4(k, v) {
1081
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1082
+ }
1083
+ __name(_ts_metadata4, "_ts_metadata");
1084
+ function _ts_param4(paramIndex, decorator) {
1085
+ return function(target, key) {
1086
+ decorator(target, key, paramIndex);
1087
+ };
1088
+ }
1089
+ __name(_ts_param4, "_ts_param");
1090
+ var StaticEntitlementService = class {
1091
+ static {
1092
+ __name(this, "StaticEntitlementService");
1093
+ }
1094
+ catalog;
1095
+ resolver;
1096
+ constructor(catalog, resolver) {
1097
+ this.catalog = catalog;
1098
+ this.resolver = resolver;
1099
+ }
1100
+ /**
1101
+ * Returns the effective features + quotas of a tenant from the
1102
+ * plan catalog. When the plan is missing: an empty snapshot (no feature,
1103
+ * all quotas at 0).
1104
+ */
1105
+ async snapshot(tenantId) {
1106
+ const planId = await this.resolver.getPlanIdForTenant(tenantId);
1107
+ if (!planId) {
1108
+ return {
1109
+ planId: null,
1110
+ features: [],
1111
+ quotas: {}
1112
+ };
1113
+ }
1114
+ const plan = this.findPlan(planId);
1115
+ if (!plan) {
1116
+ return {
1117
+ planId,
1118
+ features: [],
1119
+ quotas: {}
1120
+ };
1121
+ }
1122
+ return {
1123
+ planId,
1124
+ features: plan.features ?? [],
1125
+ quotas: plan.quotas ?? {}
1126
+ };
1127
+ }
1128
+ /** Convenience helper for tests / synchronous checks in the UI manifest. */
1129
+ async hasFeature(tenantId, featureKey) {
1130
+ const snap = await this.snapshot(tenantId);
1131
+ return snap.features.includes(featureKey);
1132
+ }
1133
+ async quotaLimit(tenantId, quotaKey) {
1134
+ const snap = await this.snapshot(tenantId);
1135
+ const value = snap.quotas[quotaKey];
1136
+ return value === void 0 ? null : value;
1137
+ }
1138
+ findPlan(planId) {
1139
+ return (this.catalog.plans ?? []).find((p) => p.id === planId);
1140
+ }
1141
+ };
1142
+ StaticEntitlementService = _ts_decorate5([
1143
+ Injectable3(),
1144
+ _ts_param4(0, Inject3(PLAN_CATALOG_TOKEN)),
1145
+ _ts_param4(1, Inject3(PLAN_RESOLVER_PORT_TOKEN)),
1146
+ _ts_metadata4("design:type", Function),
1147
+ _ts_metadata4("design:paramtypes", [
1148
+ typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
1149
+ typeof PlanResolverPort === "undefined" ? Object : PlanResolverPort
1150
+ ])
1151
+ ], StaticEntitlementService);
1152
+
1153
+ // src/platform/tenant-manifest.service.ts
1154
+ import { Injectable as Injectable4 } from "@nestjs/common";
1155
+ function _ts_decorate6(decorators, target, key, desc) {
1156
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1157
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1158
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1159
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1160
+ }
1161
+ __name(_ts_decorate6, "_ts_decorate");
1162
+ function _ts_metadata5(k, v) {
1163
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1164
+ }
1165
+ __name(_ts_metadata5, "_ts_metadata");
1166
+ var TenantManifestService = class {
1167
+ static {
1168
+ __name(this, "TenantManifestService");
1169
+ }
1170
+ entitlements;
1171
+ navItems = [];
1172
+ constructor(entitlements) {
1173
+ this.entitlements = entitlements;
1174
+ }
1175
+ /**
1176
+ * Call in the app's `OnModuleInit`: registers app navigation for the
1177
+ * Tenant UI. Idempotent: the same `id` overwrites.
1178
+ */
1179
+ registerNavItem(item) {
1180
+ const existing = this.navItems.findIndex((i) => i.id === item.id);
1181
+ if (existing >= 0) this.navItems[existing] = item;
1182
+ else this.navItems.push(item);
1183
+ }
1184
+ /** Bulk variant. */
1185
+ registerNavItems(items) {
1186
+ for (const item of items) this.registerNavItem(item);
1187
+ }
1188
+ /**
1189
+ * Returns the filtered manifest for a Tenant. NavItems with
1190
+ * `requiresFeature` are filtered out server-side as soon as none of the
1191
+ * required features are present in the entitlement snapshot.
1192
+ */
1193
+ async getManifest(tenantId) {
1194
+ const snap = await this.entitlements.snapshot(tenantId);
1195
+ const featureSet = new Set(snap.features);
1196
+ const visible = this.navItems.filter((item) => isVisible(item, featureSet)).map((item) => ({
1197
+ ...item
1198
+ })).sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
1199
+ return {
1200
+ schemaVersion: 1,
1201
+ tenant: {
1202
+ id: tenantId
1203
+ },
1204
+ planId: snap.planId,
1205
+ features: snap.features,
1206
+ quotas: snap.quotas,
1207
+ navigation: visible
1208
+ };
1209
+ }
1210
+ };
1211
+ TenantManifestService = _ts_decorate6([
1212
+ Injectable4(),
1213
+ _ts_metadata5("design:type", Function),
1214
+ _ts_metadata5("design:paramtypes", [
1215
+ typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService
1216
+ ])
1217
+ ], TenantManifestService);
1218
+ function isVisible(item, features) {
1219
+ if (!item.requiresFeature) return true;
1220
+ const required = Array.isArray(item.requiresFeature) ? item.requiresFeature : [
1221
+ item.requiresFeature
1222
+ ];
1223
+ return required.some((f) => features.has(f));
1224
+ }
1225
+ __name(isVisible, "isVisible");
1226
+
1227
+ // src/platform/tenant-manifest.controller.ts
1228
+ import { Controller as Controller2, Get as Get2, Inject as Inject4, Req, UseGuards } from "@nestjs/common";
1229
+ function _ts_decorate7(decorators, target, key, desc) {
1230
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1231
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1232
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1233
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1234
+ }
1235
+ __name(_ts_decorate7, "_ts_decorate");
1236
+ function _ts_metadata6(k, v) {
1237
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1238
+ }
1239
+ __name(_ts_metadata6, "_ts_metadata");
1240
+ function _ts_param5(paramIndex, decorator) {
1241
+ return function(target, key) {
1242
+ decorator(target, key, paramIndex);
1243
+ };
1244
+ }
1245
+ __name(_ts_param5, "_ts_param");
1246
+ function buildTenantManifestController(options) {
1247
+ const path = options.path ?? "tenant/manifest";
1248
+ let GeneratedTenantManifestController = class GeneratedTenantManifestController {
1249
+ static {
1250
+ __name(this, "GeneratedTenantManifestController");
1251
+ }
1252
+ svc;
1253
+ constructor(svc) {
1254
+ this.svc = svc;
1255
+ }
1256
+ async get(req) {
1257
+ const tenantId = req.user?.tenantId ?? req.tenantId;
1258
+ if (!tenantId) {
1259
+ throw new Error("No tenant on the request \u2014 TenantManifest requires user.tenantId.");
1260
+ }
1261
+ return this.svc.getManifest(tenantId);
1262
+ }
1263
+ };
1264
+ _ts_decorate7([
1265
+ Get2(),
1266
+ _ts_param5(0, Req()),
1267
+ _ts_metadata6("design:type", Function),
1268
+ _ts_metadata6("design:paramtypes", [
1269
+ typeof RequestWithUser === "undefined" ? Object : RequestWithUser
1270
+ ]),
1271
+ _ts_metadata6("design:returntype", Promise)
1272
+ ], GeneratedTenantManifestController.prototype, "get", null);
1273
+ GeneratedTenantManifestController = _ts_decorate7([
1274
+ Controller2(path),
1275
+ UseGuards(...options.guards),
1276
+ _ts_param5(0, Inject4(TenantManifestService)),
1277
+ _ts_metadata6("design:type", Function),
1278
+ _ts_metadata6("design:paramtypes", [
1279
+ typeof TenantManifestService === "undefined" ? Object : TenantManifestService
1280
+ ])
1281
+ ], GeneratedTenantManifestController);
1282
+ return GeneratedTenantManifestController;
1283
+ }
1284
+ __name(buildTenantManifestController, "buildTenantManifestController");
1285
+
1286
+ // src/platform/enforce-quota.interceptor.ts
1287
+ import { Inject as Inject5, Injectable as Injectable5, Optional as Optional2 } from "@nestjs/common";
1288
+ import { Reflector } from "@nestjs/core";
1289
+ import { from } from "rxjs";
1290
+ import { switchMap } from "rxjs/operators";
1291
+ function _ts_decorate8(decorators, target, key, desc) {
1292
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1293
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1294
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1295
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1296
+ }
1297
+ __name(_ts_decorate8, "_ts_decorate");
1298
+ function _ts_metadata7(k, v) {
1299
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1300
+ }
1301
+ __name(_ts_metadata7, "_ts_metadata");
1302
+ function _ts_param6(paramIndex, decorator) {
1303
+ return function(target, key) {
1304
+ decorator(target, key, paramIndex);
1305
+ };
1306
+ }
1307
+ __name(_ts_param6, "_ts_param");
1308
+ var QUOTA_PROVIDERS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/QuotaProviders");
1309
+ var EnforceQuotaInterceptor = class {
1310
+ static {
1311
+ __name(this, "EnforceQuotaInterceptor");
1312
+ }
1313
+ reflector;
1314
+ entitlements;
1315
+ providers;
1316
+ constructor(reflector, entitlements, providers = null) {
1317
+ this.reflector = reflector;
1318
+ this.entitlements = entitlements;
1319
+ this.providers = providers;
1320
+ }
1321
+ intercept(context, next) {
1322
+ const meta = this.reflector.getAllAndOverride(ENFORCE_QUOTA_KEY, [
1323
+ context.getHandler(),
1324
+ context.getClass()
1325
+ ]);
1326
+ if (!meta?.quotaKey) return next.handle();
1327
+ return from(this.check(context, meta)).pipe(switchMap(() => next.handle()));
1328
+ }
1329
+ async check(context, meta) {
1330
+ const request = context.switchToHttp().getRequest();
1331
+ const user = request.user;
1332
+ if (!user) return;
1333
+ const role = user.role ?? user.platformRole;
1334
+ if (role === "SUPER_ADMIN") return;
1335
+ const tenantId = request.tenantId ?? user.tenantId;
1336
+ if (!tenantId) return;
1337
+ const provider = (this.providers ?? []).find((p) => p.key === meta.quotaKey);
1338
+ if (!provider) {
1339
+ return;
1340
+ }
1341
+ const limit = await this.entitlements.quotaLimit(tenantId, meta.quotaKey);
1342
+ if (limit === null || limit === -1) return;
1343
+ const current = await provider.count(tenantId);
1344
+ const delta = meta.incrementBy ?? 1;
1345
+ if (current + delta > limit) {
1346
+ throw new LimitExceededError(meta.quotaKey, limit, current);
1347
+ }
1348
+ }
1349
+ };
1350
+ EnforceQuotaInterceptor = _ts_decorate8([
1351
+ Injectable5(),
1352
+ _ts_param6(0, Inject5(Reflector)),
1353
+ _ts_param6(1, Inject5(StaticEntitlementService)),
1354
+ _ts_param6(2, Optional2()),
1355
+ _ts_param6(2, Inject5(QUOTA_PROVIDERS_TOKEN)),
1356
+ _ts_metadata7("design:type", Function),
1357
+ _ts_metadata7("design:paramtypes", [
1358
+ typeof Reflector === "undefined" ? Object : Reflector,
1359
+ typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
1360
+ Object
1361
+ ])
1362
+ ], EnforceQuotaInterceptor);
1363
+
1364
+ // src/platform/static-feature.guard.ts
1365
+ import { ForbiddenException as ForbiddenException2, Inject as Inject6, Injectable as Injectable6, Optional as Optional3 } from "@nestjs/common";
1366
+ import { Reflector as Reflector2 } from "@nestjs/core";
1367
+ import { AUTH_ERROR_CODES, FEATURE_NOT_LICENSED } from "@saasicat/types";
1368
+ function _ts_decorate9(decorators, target, key, desc) {
1369
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1370
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1371
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1372
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1373
+ }
1374
+ __name(_ts_decorate9, "_ts_decorate");
1375
+ function _ts_metadata8(k, v) {
1376
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1377
+ }
1378
+ __name(_ts_metadata8, "_ts_metadata");
1379
+ function _ts_param7(paramIndex, decorator) {
1380
+ return function(target, key) {
1381
+ decorator(target, key, paramIndex);
1382
+ };
1383
+ }
1384
+ __name(_ts_param7, "_ts_param");
1385
+ var STATIC_FEATURE_GUARD_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/StaticFeatureGuardConfig");
1386
+ var StaticFeatureGuard = class {
1387
+ static {
1388
+ __name(this, "StaticFeatureGuard");
1389
+ }
1390
+ reflector;
1391
+ entitlements;
1392
+ config;
1393
+ /** Lets `EnforcementChainCheck` recognise this guard across CJS entries. */
1394
+ static [FEATURE_GUARD_MARKER] = true;
1395
+ constructor(reflector, entitlements, config = null) {
1396
+ this.reflector = reflector;
1397
+ this.entitlements = entitlements;
1398
+ this.config = config;
1399
+ }
1400
+ async canActivate(context) {
1401
+ const required = this.reflector.getAllAndOverride(REQUIRE_FEATURE_KEY, [
1402
+ context.getHandler(),
1403
+ context.getClass()
1404
+ ]);
1405
+ if (!required || required.length === 0) return true;
1406
+ const request = context.switchToHttp().getRequest();
1407
+ const user = request.user;
1408
+ if (!user) {
1409
+ throw new ForbiddenException2({
1410
+ code: AUTH_ERROR_CODES.NOT_AUTHENTICATED,
1411
+ message: "Not authenticated"
1412
+ });
1413
+ }
1414
+ const role = this.config?.userRoleResolver ? this.config.userRoleResolver(user) : user.role ?? user.platformRole;
1415
+ if (role === "SUPER_ADMIN") return true;
1416
+ const tenantId = this.config?.tenantIdResolver ? this.config.tenantIdResolver(request) : request.tenantId ?? user.tenantId;
1417
+ if (!tenantId) {
1418
+ throw new ForbiddenException2({
1419
+ code: AUTH_ERROR_CODES.NO_TENANT_ASSIGNED,
1420
+ message: "No tenant assigned"
1421
+ });
1422
+ }
1423
+ const snap = await this.entitlements.snapshot(tenantId);
1424
+ const allowed = required.some((f) => snap.features.includes(f));
1425
+ if (!allowed) {
1426
+ const body = {
1427
+ code: FEATURE_NOT_LICENSED,
1428
+ featureKey: required[0],
1429
+ featureKeys: required,
1430
+ offers: [],
1431
+ message: `Feature ${required.join(" / ")} is not included in the current plan.`
1432
+ };
1433
+ throw new ForbiddenException2(body);
1434
+ }
1435
+ return true;
1436
+ }
1437
+ };
1438
+ StaticFeatureGuard = _ts_decorate9([
1439
+ Injectable6(),
1440
+ _ts_param7(0, Inject6(Reflector2)),
1441
+ _ts_param7(1, Inject6(StaticEntitlementService)),
1442
+ _ts_param7(2, Optional3()),
1443
+ _ts_param7(2, Inject6(STATIC_FEATURE_GUARD_CONFIG_TOKEN)),
1444
+ _ts_metadata8("design:type", Function),
1445
+ _ts_metadata8("design:paramtypes", [
1446
+ typeof Reflector2 === "undefined" ? Object : Reflector2,
1447
+ typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
1448
+ Object
1449
+ ])
1450
+ ], StaticFeatureGuard);
1451
+
1452
+ // src/platform/subscription-plan-resolver.ts
1453
+ var DEFAULT_ACTIVE_STATUSES = /* @__PURE__ */ new Set([
1454
+ "ACTIVE",
1455
+ "TRIAL"
1456
+ ]);
1457
+ var SubscriptionPlanResolver = class {
1458
+ static {
1459
+ __name(this, "SubscriptionPlanResolver");
1460
+ }
1461
+ subscriptions;
1462
+ activeStatuses;
1463
+ constructor(subscriptions, activeStatuses = DEFAULT_ACTIVE_STATUSES) {
1464
+ this.subscriptions = subscriptions;
1465
+ this.activeStatuses = activeStatuses;
1466
+ }
1467
+ async getPlanIdForTenant(tenantId) {
1468
+ const subscription = await this.subscriptions.findByTenantId(tenantId);
1469
+ if (!subscription || !this.activeStatuses.has(subscription.status)) return null;
1470
+ return subscription.plan;
1471
+ }
1472
+ };
1473
+
1474
+ // src/platform/saasicat.module.ts
1475
+ import { Logger, Module as Module2 } from "@nestjs/common";
1476
+ import { DiscoveryModule as NestDiscoveryModule } from "@nestjs/core";
1477
+
1478
+ // src/platform/compose/manifest.ts
1479
+ var PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PlatformSubscriptionRepository");
1480
+ var STANDARD_MANIFEST_REGISTRATION_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/StandardManifestRegistration");
1481
+ function buildStandardManifestContribution(catalog, adminResources, promoCodes) {
1482
+ const capabilities = {
1483
+ "discovery.read": true
1484
+ };
1485
+ if (catalog && catalog.adminControllers !== false) {
1486
+ Object.assign(capabilities, {
1487
+ "plans.read": true,
1488
+ "plans.publish": true,
1489
+ "bundles.read": true,
1490
+ "bundles.write": true,
1491
+ "bundles.publish": true,
1492
+ "marketingProjections.read": true,
1493
+ "marketingProjections.write": true
1494
+ });
1495
+ }
1496
+ const navigation = {};
1497
+ if (adminResources) {
1498
+ Object.assign(capabilities, {
1499
+ "tenants.read": true,
1500
+ "tenants.suspend": true,
1501
+ "tenants.reactivate": true,
1502
+ "users.read": true,
1503
+ "audit.read": true,
1504
+ "subscriptions.read": true
1505
+ });
1506
+ navigation.standardPages = {
1507
+ subscriptions: {
1508
+ enabled: true,
1509
+ requiredCapability: "subscriptions.read"
1510
+ }
1511
+ };
1512
+ }
1513
+ if (promoCodes) {
1514
+ Object.assign(capabilities, {
1515
+ "promoCodes.read": true,
1516
+ "promoCodes.write": true,
1517
+ "promoCodes.delete": true
1518
+ });
1519
+ navigation.standardPages = {
1520
+ ...navigation.standardPages ?? {},
1521
+ promoCodes: {
1522
+ enabled: true,
1523
+ requiredCapability: "promoCodes.read"
1524
+ }
1525
+ };
1526
+ }
1527
+ return {
1528
+ capabilities,
1529
+ navigation: navigation.standardPages ? navigation : void 0
1530
+ };
1531
+ }
1532
+ __name(buildStandardManifestContribution, "buildStandardManifestContribution");
1533
+ function buildMinimalManifestConfig() {
1534
+ return {
1535
+ useFactory: /* @__PURE__ */ __name((catalog) => ({
1536
+ project: {
1537
+ key: catalog.projectKey,
1538
+ displayName: catalog.app?.name ?? catalog.projectKey,
1539
+ label: catalog.app?.label,
1540
+ icon: catalog.app?.icon,
1541
+ logoUrl: catalog.app?.logoUrl,
1542
+ environment: process.env.NODE_ENV === "production" ? "production" : "development",
1543
+ availableLocales: catalog.marketing?.availableLocales,
1544
+ defaultLocale: catalog.marketing?.availableLocales?.[0]
1545
+ },
1546
+ build: {
1547
+ platformPackageVersion: "0.0.0",
1548
+ appVersion: "0.0.0"
1549
+ },
1550
+ planCatalogSnapshot: {
1551
+ source: "saasicat-module",
1552
+ hash: "sha256-quickstart",
1553
+ currency: catalog.currency,
1554
+ vatRate: catalog.vatRate,
1555
+ plans: catalog.plans ?? [],
1556
+ features: catalog.features ?? []
1557
+ }
1558
+ }), "useFactory"),
1559
+ inject: [
1560
+ PLAN_CATALOG_TOKEN
1561
+ ]
1562
+ };
1563
+ }
1564
+ __name(buildMinimalManifestConfig, "buildMinimalManifestConfig");
1565
+
1566
+ // src/platform/compose/base.ts
1567
+ var DEFAULT_SNAPSHOT_PATH = "var/discovery-snapshot.json";
1568
+ function resolveAppInfo(options) {
1569
+ return options.app ?? {
1570
+ key: options.planCatalog?.projectKey ?? options.dbCatalog?.projectKey ?? "app",
1571
+ version: options.planCatalog?.app?.version ?? options.dbCatalog?.app?.version ?? "0.0.0"
1572
+ };
1573
+ }
1574
+ __name(resolveAppInfo, "resolveAppInfo");
1575
+ function composePlanCatalog(options, sink) {
1576
+ if (options.planCatalog) {
1577
+ return PlanCatalogModule.forRootWithCatalog(options.planCatalog, {
1578
+ global: true
1579
+ });
1580
+ }
1581
+ const dbCatalog = options.dbCatalog;
1582
+ return PlanCatalogModule.forRoot({
1583
+ projectKey: dbCatalog.projectKey,
1584
+ app: dbCatalog.app,
1585
+ currency: dbCatalog.currency,
1586
+ vatRate: dbCatalog.vatRate,
1587
+ marketing: dbCatalog.marketing,
1588
+ sink,
1589
+ imports: options.imports
1590
+ });
1591
+ }
1592
+ __name(composePlanCatalog, "composePlanCatalog");
1593
+ function composeBaseModules(options, appInfo, ports) {
1594
+ return [
1595
+ DiscoveryModule.forRoot({
1596
+ app: appInfo,
1597
+ controller: {
1598
+ guards: options.controller.guards
1599
+ },
1600
+ imports: options.imports,
1601
+ snapshotPath: options.discoverySnapshotPath === void 0 ? DEFAULT_SNAPSHOT_PATH : options.discoverySnapshotPath
1602
+ }),
1603
+ AdminModule.forRoot({
1604
+ ...ports,
1605
+ imports: options.imports,
1606
+ global: true
1607
+ }),
1608
+ AdminManifestModule.forRoot({
1609
+ config: options.adminManifestConfig ?? buildMinimalManifestConfig(),
1610
+ extraProviders: options.adminManifestExtraProviders,
1611
+ includeManifestController: options.includeManifestController,
1612
+ guards: options.controller.guards,
1613
+ reloadGuards: options.reloadGuards,
1614
+ imports: options.imports,
1615
+ // Global like AdminModule above: apps register their manifest
1616
+ // contribution by injecting AdminManifestService into one of their
1617
+ // own modules (handbook §6.6). Re-exporting the module from here
1618
+ // does not make that injection resolvable.
1619
+ global: true
1620
+ })
1621
+ ];
1622
+ }
1623
+ __name(composeBaseModules, "composeBaseModules");
1624
+
1625
+ // src/platform/compose/tenant-manifest.ts
1626
+ var NONE = {
1627
+ providers: [],
1628
+ exports: [],
1629
+ controllers: []
1630
+ };
1631
+ function composeTenantManifest(ctx) {
1632
+ const { options } = ctx;
1633
+ if (!options.tenantManifest) return NONE;
1634
+ const tenantAuthGuards = optionsOf2(options.tenantBilling).authGuards;
1635
+ return {
1636
+ providers: [
1637
+ TenantManifestService
1638
+ ],
1639
+ exports: [
1640
+ TenantManifestService
1641
+ ],
1642
+ controllers: [
1643
+ buildTenantManifestController(options.tenantManifest === true ? {
1644
+ guards: Array.isArray(tenantAuthGuards) ? tenantAuthGuards : options.controller.guards
1645
+ } : options.tenantManifest)
1646
+ ]
1647
+ };
1648
+ }
1649
+ __name(composeTenantManifest, "composeTenantManifest");
1650
+
1651
+ // src/platform/compose/enforcement-runtime.ts
1652
+ import { APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core";
1653
+ function resolvePlanResolution(ctx) {
1654
+ const explicit = !!ctx.adapters.planResolver;
1655
+ const viaSubscriptions = Boolean(ctx.options.tenantBilling && ctx.adapters.subscriptionRepository);
1656
+ const fallback = !!ctx.options.defaultPlanId;
1657
+ return {
1658
+ explicit,
1659
+ viaSubscriptions,
1660
+ fallback,
1661
+ available: explicit || viaSubscriptions || fallback
1662
+ };
1663
+ }
1664
+ __name(resolvePlanResolution, "resolvePlanResolution");
1665
+ function composeEnforcementRuntime(ctx, resolution) {
1666
+ if (!resolution.available) return {
1667
+ providers: [],
1668
+ exports: []
1669
+ };
1670
+ const { options, adapters, shared } = ctx;
1671
+ const providers = [];
1672
+ if (resolution.viaSubscriptions && !resolution.explicit) {
1673
+ providers.push(asProvider(PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN, adapters.subscriptionRepository));
1674
+ }
1675
+ providers.push(
1676
+ planResolverProvider(ctx, resolution),
1677
+ StaticEntitlementService,
1678
+ StaticFeatureGuard,
1679
+ EnforceQuotaInterceptor,
1680
+ ...shared.quotaProvidersHostedByTenantBilling ? [] : options.quotaProviders ?? [],
1681
+ {
1682
+ provide: QUOTA_PROVIDERS_TOKEN,
1683
+ useFactory: /* @__PURE__ */ __name((...quotas) => quotas, "useFactory"),
1684
+ inject: options.quotaProviders ?? []
1685
+ },
1686
+ ...options.globalFeatureGuard === false ? [] : [
1687
+ {
1688
+ provide: APP_GUARD,
1689
+ useExisting: StaticFeatureGuard
1690
+ }
1691
+ ],
1692
+ // The interceptor stays global either way: interceptors run after all
1693
+ // guards, so it sees the authenticated request even when the app binds
1694
+ // its own guard chain.
1695
+ {
1696
+ provide: APP_INTERCEPTOR,
1697
+ useExisting: EnforceQuotaInterceptor
1698
+ }
1699
+ );
1700
+ return {
1701
+ providers,
1702
+ exports: [
1703
+ PLAN_RESOLVER_PORT_TOKEN,
1704
+ StaticEntitlementService,
1705
+ StaticFeatureGuard,
1706
+ EnforceQuotaInterceptor,
1707
+ QUOTA_PROVIDERS_TOKEN
1708
+ ]
1709
+ };
1710
+ }
1711
+ __name(composeEnforcementRuntime, "composeEnforcementRuntime");
1712
+ function planResolverProvider(ctx, resolution) {
1713
+ if (resolution.explicit) {
1714
+ return asProvider(PLAN_RESOLVER_PORT_TOKEN, ctx.adapters.planResolver);
1715
+ }
1716
+ if (resolution.viaSubscriptions) {
1717
+ return {
1718
+ provide: PLAN_RESOLVER_PORT_TOKEN,
1719
+ useFactory: /* @__PURE__ */ __name((subscriptions) => new SubscriptionPlanResolver(subscriptions), "useFactory"),
1720
+ inject: [
1721
+ PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN
1722
+ ]
1723
+ };
1724
+ }
1725
+ return {
1726
+ provide: PLAN_RESOLVER_PORT_TOKEN,
1727
+ useValue: new StaticPlanResolver(ctx.options.defaultPlanId)
1728
+ };
1729
+ }
1730
+ __name(planResolverProvider, "planResolverProvider");
1731
+
1732
+ // src/platform/saasicat.module.ts
1733
+ function _ts_decorate10(decorators, target, key, desc) {
1734
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1735
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1736
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1737
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1738
+ }
1739
+ __name(_ts_decorate10, "_ts_decorate");
1740
+ var PLATFORM_LOGGER = new Logger("SaaSiCat");
1741
+ function enforcementChainState(state) {
1742
+ return {
1743
+ provide: ENFORCEMENT_CHAIN_STATE_TOKEN,
1744
+ useValue: state
1745
+ };
1746
+ }
1747
+ __name(enforcementChainState, "enforcementChainState");
1748
+ var SaaSiCatModule = class _SaaSiCatModule {
1749
+ static {
1750
+ __name(this, "SaaSiCatModule");
1751
+ }
1752
+ static forRoot(options) {
1753
+ const explicit = options.adapters ?? {};
1754
+ const persistence = options.persistence;
1755
+ const catalogConfig = options.catalog || null;
1756
+ const tenantBillingConfig = options.tenantBilling || null;
1757
+ const subscriptionBundlesConfig = options.subscriptionBundles || null;
1758
+ const adminResourcesConfig = options.adminResources || null;
1759
+ const promoCodesConfig = options.promoCodes || null;
1760
+ const requiresFullEntitlement2 = Boolean(options.entitlement || tenantBillingConfig || subscriptionBundlesConfig);
1761
+ const adapters = {
1762
+ mfa: explicit.mfa ?? persistence?.core.mfa,
1763
+ audit: explicit.audit ?? persistence?.core.audit,
1764
+ rlsBypass: explicit.rlsBypass ?? persistence?.core.rlsBypass,
1765
+ planCatalogReadSink: explicit.planCatalogReadSink ?? persistence?.planCatalogReadSink,
1766
+ planResolver: explicit.planResolver,
1767
+ subscriptionRepository: explicit.subscriptionRepository ?? persistence?.entitlement?.subscriptionRepository,
1768
+ planVersionRepository: explicit.planVersionRepository ?? persistence?.entitlement?.planVersionRepository,
1769
+ transactionRunner: explicit.transactionRunner ?? persistence?.core.transactionRunner
1770
+ };
1771
+ assertConfiguration({
1772
+ options,
1773
+ adapters
1774
+ });
1775
+ const mfaPort = adapters.mfa;
1776
+ const auditPort = adapters.audit;
1777
+ const rlsBypassPort = adapters.rlsBypass;
1778
+ const appInfo = resolveAppInfo(options);
1779
+ const imports = [
1780
+ // Make injected client/auth providers visible to the high-level
1781
+ // module's own quota providers and automatic plan resolver too.
1782
+ ...options.imports ?? [],
1783
+ composePlanCatalog(options, adapters.planCatalogReadSink),
1784
+ ...composeBaseModules(options, appInfo, {
1785
+ mfaPort,
1786
+ auditPort,
1787
+ rlsBypassPort
1788
+ })
1789
+ ];
1790
+ const composition = {
1791
+ options,
1792
+ adapters,
1793
+ persistence,
1794
+ appInfo,
1795
+ requiresFullEntitlement: requiresFullEntitlement2,
1796
+ shared: {
1797
+ quotaProvidersHostedByTenantBilling: false
1798
+ }
1799
+ };
1800
+ imports.push(...composeFeatures(composition));
1801
+ const lightweightProviders = [];
1802
+ const lightweightExports = [];
1803
+ if (options.autoManifest !== false) {
1804
+ const contribution = buildStandardManifestContribution(catalogConfig, adminResourcesConfig, promoCodesConfig);
1805
+ lightweightProviders.push({
1806
+ provide: STANDARD_MANIFEST_REGISTRATION_TOKEN,
1807
+ useFactory: /* @__PURE__ */ __name((manifest) => {
1808
+ manifest.register(contribution);
1809
+ return contribution;
1810
+ }, "useFactory"),
1811
+ inject: [
1812
+ AdminManifestService
1813
+ ]
1814
+ });
1815
+ }
1816
+ const resolution = resolvePlanResolution(composition);
1817
+ const runtime = composeEnforcementRuntime(composition, resolution);
1818
+ lightweightProviders.push(...runtime.providers);
1819
+ lightweightExports.push(...runtime.exports);
1820
+ if (!resolution.available && !requiresFullEntitlement2) {
1821
+ PLATFORM_LOGGER.warn("No plan resolver configured \u2014 @RequireFeature and @EnforceQuota are INERT. The platform registered neither StaticFeatureGuard nor EnforceQuotaInterceptor, so every annotated route serves unlicensed traffic and every quota is unlimited. Set one of: adapters.planResolver, defaultPlanId, or tenantBilling.");
1822
+ }
1823
+ if (options.enforcementChainCheck !== false) {
1824
+ lightweightProviders.push(EnforcementChainCheck, enforcementChainState({
1825
+ featureEnforcementActive: resolution.available || requiresFullEntitlement2,
1826
+ quotaEnforcementActive: resolution.available,
1827
+ globalGuardBound: resolution.available && options.globalFeatureGuard !== false,
1828
+ // Whether the OPTION unbound it, as opposed to this simply
1829
+ // being a path where the platform binds none. The message
1830
+ // names the true cause, and an application that never set
1831
+ // the option is not told to look for it.
1832
+ globalGuardOptedOut: options.globalFeatureGuard === false
1833
+ }));
1834
+ }
1835
+ if (options.enforcementChainCheck !== false) imports.push(NestDiscoveryModule);
1836
+ const tenantManifest = composeTenantManifest(composition);
1837
+ lightweightProviders.push(...tenantManifest.providers);
1838
+ lightweightExports.push(...tenantManifest.exports);
1839
+ return {
1840
+ module: _SaaSiCatModule,
1841
+ imports,
1842
+ providers: lightweightProviders,
1843
+ controllers: tenantManifest.controllers,
1844
+ exports: composeModuleExports(composition, lightweightExports),
1845
+ global: true
1846
+ };
1847
+ }
1848
+ };
1849
+ SaaSiCatModule = _ts_decorate10([
1850
+ Module2({})
1851
+ ], SaaSiCatModule);
1852
+
1853
+ export {
1854
+ SETUP_PROVISIONING_PORT_TOKEN,
1855
+ SETUP_CONFIG_TOKEN,
1856
+ SetupService,
1857
+ SetupController,
1858
+ SetupModule,
1859
+ ENFORCEMENT_CHAIN_STATE_TOKEN,
1860
+ EnforcementChainCheck,
1861
+ headingSlug,
1862
+ docsUrlFor,
1863
+ PLATFORM_RULES,
1864
+ findViolations,
1865
+ SaaSiCatConfigurationError,
1866
+ assertConfiguration,
1867
+ QuotaProvidersUsageSnapshot,
1868
+ FEATURE_COMPOSERS,
1869
+ composeFeatures,
1870
+ MOUNTED_BUT_NOT_EXPORTED,
1871
+ composeModuleExports,
1872
+ PLAN_RESOLVER_PORT_TOKEN,
1873
+ StaticPlanResolver,
1874
+ StaticEntitlementService,
1875
+ TenantManifestService,
1876
+ buildTenantManifestController,
1877
+ QUOTA_PROVIDERS_TOKEN,
1878
+ EnforceQuotaInterceptor,
1879
+ STATIC_FEATURE_GUARD_CONFIG_TOKEN,
1880
+ StaticFeatureGuard,
1881
+ SubscriptionPlanResolver,
1882
+ SaaSiCatModule
1883
+ };