@saasicat/nest 0.26.0 → 0.27.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.
- package/LICENSE +90 -201
- package/dist/_entries.cjs +3265 -2846
- package/dist/billing/index.d.cts +4 -4
- package/dist/billing/index.d.ts +4 -4
- package/dist/billing/index.js +33 -31
- package/dist/catalog/index.js +5 -5
- package/dist/checkout-offer/index.js +2 -2
- package/dist/chunk-2FR6ZL7R.js +0 -0
- package/dist/chunk-2JTISFUZ.js +792 -0
- package/dist/chunk-2SLTRKXC.js +0 -0
- package/dist/chunk-3E6ONOMG.js +50 -0
- package/dist/{chunk-UOWK2HYG.js → chunk-7ZPFQXHM.js} +1 -1
- package/dist/{chunk-B7NRY2LV.js → chunk-CJFAOD7K.js} +1 -39
- package/dist/{chunk-J62YI25C.js → chunk-EHKMPP6J.js} +1194 -775
- package/dist/{chunk-RPO4IVUY.js → chunk-F4B2NJUZ.js} +26 -91
- package/dist/chunk-H4DOTO7S.js +85 -0
- package/dist/{chunk-V3MRZ7SH.js → chunk-UHD6FJBQ.js} +421 -1197
- package/dist/{define-saasicat-BK6RRWWG.d.ts → define-saasicat-3ois8t8s.d.ts} +26 -3
- package/dist/{define-saasicat-D30I-WET.d.cts → define-saasicat-CFxut1VR.d.cts} +26 -3
- package/dist/{enforce-quota.interceptor-B9QueAFf.d.ts → enforce-quota.interceptor-CYDNvAQz.d.ts} +3 -3
- package/dist/{enforce-quota.interceptor-DY6CLjoP.d.cts → enforce-quota.interceptor-N80KC34N.d.cts} +3 -3
- package/dist/entitlement/index.js +7 -4
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +87 -79
- package/dist/{saas-platform.module-D1mUZPrz.d.ts → module-options-ByJL7FZm.d.ts} +40 -27
- package/dist/{saas-platform.module-CgalxzcT.d.cts → module-options-klrPnWGF.d.cts} +40 -27
- package/dist/platform/index.d.cts +201 -19
- package/dist/platform/index.d.ts +201 -19
- package/dist/platform/index.js +58 -33
- package/dist/promo/index.js +2 -2
- package/dist/subscription-contract/index.js +5 -2
- package/dist/{tenant-billing.controller-DHSUrT_N.d.cts → tenant-billing.controller-CAMMdfn1.d.cts} +1 -1
- package/dist/{tenant-billing.controller-BSlpFqVA.d.ts → tenant-billing.controller-CYYK0CpT.d.ts} +1 -1
- package/dist/{subscription-bundles.module-BDbODWvA.d.ts → tenant-billing.module-B0stLoJn.d.ts} +51 -51
- package/dist/{subscription-bundles.module-Cfi-lFWF.d.cts → tenant-billing.module-BxJS6fX4.d.cts} +51 -51
- package/dist/testing/index.d.cts +8 -8
- package/dist/testing/index.d.ts +8 -8
- package/dist/testing/index.js +13 -11
- package/package.json +4 -4
- package/dist/{chunk-QB7MICKQ.js → chunk-7X5MSAIL.js} +9 -9
- package/dist/{chunk-FY4EJTV4.js → chunk-OYNHOY45.js} +0 -0
- package/dist/{chunk-I4XD43AN.js → chunk-XTVAFJ3P.js} +3 -3
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PromoCodesModule
|
|
3
3
|
} from "./chunk-B6IWLV7R.js";
|
|
4
|
+
import {
|
|
5
|
+
SubscriptionContractModule
|
|
6
|
+
} from "./chunk-3E6ONOMG.js";
|
|
7
|
+
import {
|
|
8
|
+
AdminManifestModule,
|
|
9
|
+
AdminManifestService,
|
|
10
|
+
AdminModule,
|
|
11
|
+
AdminResourcesModule,
|
|
12
|
+
AdminStatsModule,
|
|
13
|
+
MfaService,
|
|
14
|
+
SuperAdminGuard
|
|
15
|
+
} from "./chunk-SN5UOC43.js";
|
|
4
16
|
import {
|
|
5
17
|
FEATURE_GUARD_MARKER,
|
|
6
18
|
PublicCatalogModule,
|
|
@@ -8,48 +20,38 @@ import {
|
|
|
8
20
|
SubscriptionBundleModule,
|
|
9
21
|
TenantBillingModule,
|
|
10
22
|
isPlatformFeatureGuard
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import {
|
|
13
|
-
SubscriptionContractModule
|
|
14
|
-
} from "./chunk-B7NRY2LV.js";
|
|
23
|
+
} from "./chunk-UHD6FJBQ.js";
|
|
15
24
|
import {
|
|
16
25
|
CatalogModule
|
|
17
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-7X5MSAIL.js";
|
|
18
27
|
import {
|
|
19
28
|
CheckoutOfferModule
|
|
20
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-XTVAFJ3P.js";
|
|
30
|
+
import {
|
|
31
|
+
SaaSiCatPublicRoute
|
|
32
|
+
} from "./chunk-YSC46BGM.js";
|
|
21
33
|
import {
|
|
22
34
|
DiscoveryModule
|
|
23
35
|
} from "./chunk-6WL6VQ7Z.js";
|
|
24
36
|
import {
|
|
25
|
-
EntitlementModule
|
|
37
|
+
EntitlementModule
|
|
38
|
+
} from "./chunk-H4DOTO7S.js";
|
|
39
|
+
import {
|
|
26
40
|
LimitExceededError
|
|
27
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-F4B2NJUZ.js";
|
|
28
42
|
import {
|
|
29
43
|
PLAN_CATALOG_TOKEN,
|
|
30
44
|
PlanCatalogModule
|
|
31
45
|
} from "./chunk-DQKCK7DX.js";
|
|
32
|
-
import {
|
|
33
|
-
ENFORCE_QUOTA_KEY
|
|
34
|
-
} from "./chunk-L4SUWH5B.js";
|
|
35
|
-
import {
|
|
36
|
-
AdminManifestModule,
|
|
37
|
-
AdminManifestService,
|
|
38
|
-
AdminModule,
|
|
39
|
-
AdminResourcesModule,
|
|
40
|
-
AdminStatsModule,
|
|
41
|
-
MfaService,
|
|
42
|
-
SuperAdminGuard
|
|
43
|
-
} from "./chunk-SN5UOC43.js";
|
|
44
|
-
import {
|
|
45
|
-
SaaSiCatPublicRoute
|
|
46
|
-
} from "./chunk-YSC46BGM.js";
|
|
47
46
|
import {
|
|
48
47
|
codedError
|
|
49
48
|
} from "./chunk-EBJJNTW6.js";
|
|
50
49
|
import {
|
|
51
50
|
asProvider
|
|
52
51
|
} from "./chunk-SABTXESR.js";
|
|
52
|
+
import {
|
|
53
|
+
ENFORCE_QUOTA_KEY
|
|
54
|
+
} from "./chunk-L4SUWH5B.js";
|
|
53
55
|
import {
|
|
54
56
|
__name
|
|
55
57
|
} from "./chunk-SHUYVCID.js";
|
|
@@ -350,23 +352,9 @@ SetupModule = _ts_decorate3([
|
|
|
350
352
|
Module({})
|
|
351
353
|
], SetupModule);
|
|
352
354
|
|
|
353
|
-
// src/platform/
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
static {
|
|
357
|
-
__name(this, "StaticPlanResolver");
|
|
358
|
-
}
|
|
359
|
-
planId;
|
|
360
|
-
constructor(planId) {
|
|
361
|
-
this.planId = planId;
|
|
362
|
-
}
|
|
363
|
-
async getPlanIdForTenant() {
|
|
364
|
-
return this.planId;
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
// src/platform/static-entitlement.service.ts
|
|
369
|
-
import { Inject as Inject2, Injectable as Injectable2 } from "@nestjs/common";
|
|
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";
|
|
370
358
|
function _ts_decorate4(decorators, target, key, desc) {
|
|
371
359
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
372
360
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -384,121 +372,63 @@ function _ts_param3(paramIndex, decorator) {
|
|
|
384
372
|
};
|
|
385
373
|
}
|
|
386
374
|
__name(_ts_param3, "_ts_param");
|
|
387
|
-
var StaticEntitlementService = class {
|
|
388
|
-
static {
|
|
389
|
-
__name(this, "StaticEntitlementService");
|
|
390
|
-
}
|
|
391
|
-
catalog;
|
|
392
|
-
resolver;
|
|
393
|
-
constructor(catalog, resolver) {
|
|
394
|
-
this.catalog = catalog;
|
|
395
|
-
this.resolver = resolver;
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Returns the effective features + quotas of a tenant from the
|
|
399
|
-
* plan catalog. When the plan is missing: an empty snapshot (no feature,
|
|
400
|
-
* all quotas at 0).
|
|
401
|
-
*/
|
|
402
|
-
async snapshot(tenantId) {
|
|
403
|
-
const planId = await this.resolver.getPlanIdForTenant(tenantId);
|
|
404
|
-
if (!planId) {
|
|
405
|
-
return {
|
|
406
|
-
planId: null,
|
|
407
|
-
features: [],
|
|
408
|
-
quotas: {}
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
const plan = this.findPlan(planId);
|
|
412
|
-
if (!plan) {
|
|
413
|
-
return {
|
|
414
|
-
planId,
|
|
415
|
-
features: [],
|
|
416
|
-
quotas: {}
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
return {
|
|
420
|
-
planId,
|
|
421
|
-
features: plan.features ?? [],
|
|
422
|
-
quotas: plan.quotas ?? {}
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
/** Convenience helper for tests / synchronous checks in the UI manifest. */
|
|
426
|
-
async hasFeature(tenantId, featureKey) {
|
|
427
|
-
const snap = await this.snapshot(tenantId);
|
|
428
|
-
return snap.features.includes(featureKey);
|
|
429
|
-
}
|
|
430
|
-
async quotaLimit(tenantId, quotaKey) {
|
|
431
|
-
const snap = await this.snapshot(tenantId);
|
|
432
|
-
const value = snap.quotas[quotaKey];
|
|
433
|
-
return value === void 0 ? null : value;
|
|
434
|
-
}
|
|
435
|
-
findPlan(planId) {
|
|
436
|
-
return (this.catalog.plans ?? []).find((p) => p.id === planId);
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
StaticEntitlementService = _ts_decorate4([
|
|
440
|
-
Injectable2(),
|
|
441
|
-
_ts_param3(0, Inject2(PLAN_CATALOG_TOKEN)),
|
|
442
|
-
_ts_param3(1, Inject2(PLAN_RESOLVER_PORT_TOKEN)),
|
|
443
|
-
_ts_metadata3("design:type", Function),
|
|
444
|
-
_ts_metadata3("design:paramtypes", [
|
|
445
|
-
typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
|
|
446
|
-
typeof PlanResolverPort === "undefined" ? Object : PlanResolverPort
|
|
447
|
-
])
|
|
448
|
-
], StaticEntitlementService);
|
|
449
|
-
|
|
450
|
-
// src/platform/feature-guard-coverage.check.ts
|
|
451
|
-
import { Inject as Inject3, Injectable as Injectable3, Logger } from "@nestjs/common";
|
|
452
|
-
import { DiscoveryService, MetadataScanner } from "@nestjs/core";
|
|
453
|
-
function _ts_decorate5(decorators, target, key, desc) {
|
|
454
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
455
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
456
|
-
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;
|
|
457
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
458
|
-
}
|
|
459
|
-
__name(_ts_decorate5, "_ts_decorate");
|
|
460
|
-
function _ts_metadata4(k, v) {
|
|
461
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
462
|
-
}
|
|
463
|
-
__name(_ts_metadata4, "_ts_metadata");
|
|
464
|
-
function _ts_param4(paramIndex, decorator) {
|
|
465
|
-
return function(target, key) {
|
|
466
|
-
decorator(target, key, paramIndex);
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
__name(_ts_param4, "_ts_param");
|
|
470
375
|
var GUARDS_METADATA = "__guards__";
|
|
471
376
|
var METHOD_METADATA = "method";
|
|
472
|
-
var
|
|
377
|
+
var ENFORCEMENT_CHAIN_STATE_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/EnforcementChainState");
|
|
378
|
+
var EnforcementChainCheck = class {
|
|
473
379
|
static {
|
|
474
|
-
__name(this, "
|
|
380
|
+
__name(this, "EnforcementChainCheck");
|
|
475
381
|
}
|
|
476
382
|
discoveryService;
|
|
477
383
|
metadataScanner;
|
|
478
|
-
|
|
479
|
-
constructor(discoveryService, metadataScanner
|
|
384
|
+
state;
|
|
385
|
+
constructor(discoveryService, metadataScanner, state = {
|
|
386
|
+
featureEnforcementActive: true,
|
|
387
|
+
quotaEnforcementActive: true,
|
|
388
|
+
globalGuardBound: true,
|
|
389
|
+
globalGuardOptedOut: false
|
|
390
|
+
}) {
|
|
480
391
|
this.discoveryService = discoveryService;
|
|
481
392
|
this.metadataScanner = metadataScanner;
|
|
393
|
+
this.state = state;
|
|
482
394
|
}
|
|
483
395
|
onApplicationBootstrap() {
|
|
484
|
-
const
|
|
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);
|
|
485
414
|
if (uncovered.length === 0) return;
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
${
|
|
489
|
-
Bind one behind your auth guard \u2014 @UseGuards(JwtAuthGuard, StaticFeatureGuard), or FeatureGuard from @saasicat/nest/billing on the V3 entitlement path.
|
|
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.`);
|
|
490
420
|
}
|
|
491
421
|
/**
|
|
492
|
-
*
|
|
493
|
-
*
|
|
422
|
+
* Every route carrying an enforcement annotation, with whether a
|
|
423
|
+
* recognisable feature guard covers it.
|
|
494
424
|
*
|
|
495
425
|
* Deliberately conservative in one direction only: an unrecognised wrapper
|
|
496
426
|
* is reported (and the message says so) rather than assumed safe, because
|
|
497
427
|
* the named route is cheap to check. The reverse — staying silent about a
|
|
498
428
|
* route with no guard at all — is the failure this exists to prevent.
|
|
499
429
|
*/
|
|
500
|
-
|
|
501
|
-
const
|
|
430
|
+
findAnnotatedRoutes() {
|
|
431
|
+
const annotated = [];
|
|
502
432
|
for (const wrapper of this.discoveryService.getControllers()) {
|
|
503
433
|
const instance = wrapper.instance;
|
|
504
434
|
if (!instance || typeof instance !== "object") continue;
|
|
@@ -510,39 +440,58 @@ Bind one behind your auth guard \u2014 @UseGuards(JwtAuthGuard, StaticFeatureGua
|
|
|
510
440
|
const handler = instance[method];
|
|
511
441
|
if (typeof handler !== "function") continue;
|
|
512
442
|
if (Reflect.getMetadata(METHOD_METADATA, handler) === void 0) continue;
|
|
513
|
-
const features = readFeatures(handler) ?? readFeatures(ctor) ?? /*
|
|
514
|
-
|
|
515
|
-
|
|
443
|
+
const features = readFeatures(handler) ?? readFeatures(ctor) ?? /* none */
|
|
444
|
+
[];
|
|
445
|
+
const quota = readQuota(handler) ?? readQuota(ctor);
|
|
446
|
+
if (features.length === 0 && quota === null) continue;
|
|
516
447
|
const guards = [
|
|
517
448
|
...classGuards,
|
|
518
449
|
...readGuards(handler)
|
|
519
450
|
];
|
|
520
|
-
|
|
521
|
-
uncovered.push({
|
|
451
|
+
annotated.push({
|
|
522
452
|
controller: ctor.name,
|
|
523
453
|
handler: method,
|
|
524
|
-
features
|
|
454
|
+
features,
|
|
455
|
+
quota,
|
|
456
|
+
guarded: guards.some(isPlatformFeatureGuard)
|
|
525
457
|
});
|
|
526
458
|
}
|
|
527
459
|
}
|
|
528
|
-
return
|
|
460
|
+
return annotated;
|
|
529
461
|
}
|
|
530
462
|
};
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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", [
|
|
537
471
|
typeof DiscoveryService === "undefined" ? Object : DiscoveryService,
|
|
538
|
-
typeof MetadataScanner === "undefined" ? Object : MetadataScanner
|
|
472
|
+
typeof MetadataScanner === "undefined" ? Object : MetadataScanner,
|
|
473
|
+
typeof EnforcementChainState === "undefined" ? Object : EnforcementChainState
|
|
539
474
|
])
|
|
540
|
-
],
|
|
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");
|
|
541
483
|
function readFeatures(target) {
|
|
542
484
|
const value = Reflect.getMetadata(REQUIRE_FEATURE_KEY, target);
|
|
543
485
|
return Array.isArray(value) ? value : null;
|
|
544
486
|
}
|
|
545
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");
|
|
546
495
|
function readGuards(target) {
|
|
547
496
|
const value = Reflect.getMetadata(GUARDS_METADATA, target);
|
|
548
497
|
if (!Array.isArray(value)) return [];
|
|
@@ -555,179 +504,217 @@ function readGuards(target) {
|
|
|
555
504
|
}
|
|
556
505
|
__name(readGuards, "readGuards");
|
|
557
506
|
|
|
558
|
-
// src/platform/
|
|
559
|
-
import {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
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;
|
|
566
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
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;
|
|
567
514
|
}
|
|
568
|
-
__name(
|
|
569
|
-
|
|
570
|
-
|
|
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]);
|
|
571
521
|
}
|
|
572
|
-
__name(
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
+
}, "SaasPlatformModule 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
|
|
576
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;
|
|
577
680
|
}
|
|
578
|
-
__name(
|
|
579
|
-
var
|
|
580
|
-
var StaticFeatureGuard = class {
|
|
681
|
+
__name(findViolations, "findViolations");
|
|
682
|
+
var SaaSiCatConfigurationError = class extends Error {
|
|
581
683
|
static {
|
|
582
|
-
__name(this, "
|
|
583
|
-
}
|
|
584
|
-
reflector;
|
|
585
|
-
entitlements;
|
|
586
|
-
config;
|
|
587
|
-
/** Lets `FeatureGuardCoverageCheck` recognise this guard across CJS entries. */
|
|
588
|
-
static [FEATURE_GUARD_MARKER] = true;
|
|
589
|
-
constructor(reflector, entitlements, config = null) {
|
|
590
|
-
this.reflector = reflector;
|
|
591
|
-
this.entitlements = entitlements;
|
|
592
|
-
this.config = config;
|
|
684
|
+
__name(this, "SaaSiCatConfigurationError");
|
|
593
685
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
]);
|
|
599
|
-
if (!required || required.length === 0) return true;
|
|
600
|
-
const request = context.switchToHttp().getRequest();
|
|
601
|
-
const user = request.user;
|
|
602
|
-
if (!user) {
|
|
603
|
-
throw new ForbiddenException2({
|
|
604
|
-
code: AUTH_ERROR_CODES.NOT_AUTHENTICATED,
|
|
605
|
-
message: "Not authenticated"
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
const role = this.config?.userRoleResolver ? this.config.userRoleResolver(user) : user.role ?? user.platformRole;
|
|
609
|
-
if (role === "SUPER_ADMIN") return true;
|
|
610
|
-
const tenantId = this.config?.tenantIdResolver ? this.config.tenantIdResolver(request) : request.tenantId ?? user.tenantId;
|
|
611
|
-
if (!tenantId) {
|
|
612
|
-
throw new ForbiddenException2({
|
|
613
|
-
code: AUTH_ERROR_CODES.NO_TENANT_ASSIGNED,
|
|
614
|
-
message: "No tenant assigned"
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
const snap = await this.entitlements.snapshot(tenantId);
|
|
618
|
-
const allowed = required.some((f) => snap.features.includes(f));
|
|
619
|
-
if (!allowed) {
|
|
620
|
-
const body = {
|
|
621
|
-
code: FEATURE_NOT_LICENSED,
|
|
622
|
-
featureKey: required[0],
|
|
623
|
-
featureKeys: required,
|
|
624
|
-
offers: [],
|
|
625
|
-
message: `Feature ${required.join(" / ")} is not included in the current plan.`
|
|
626
|
-
};
|
|
627
|
-
throw new ForbiddenException2(body);
|
|
628
|
-
}
|
|
629
|
-
return true;
|
|
686
|
+
violations;
|
|
687
|
+
constructor(violations) {
|
|
688
|
+
super(formatViolations(violations)), this.violations = violations;
|
|
689
|
+
this.name = "SaaSiCatConfigurationError";
|
|
630
690
|
}
|
|
631
691
|
};
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
_ts_metadata5("design:type", Function),
|
|
639
|
-
_ts_metadata5("design:paramtypes", [
|
|
640
|
-
typeof Reflector === "undefined" ? Object : Reflector,
|
|
641
|
-
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
|
|
642
|
-
Object
|
|
643
|
-
])
|
|
644
|
-
], StaticFeatureGuard);
|
|
645
|
-
|
|
646
|
-
// src/platform/enforce-quota.interceptor.ts
|
|
647
|
-
import { Inject as Inject5, Injectable as Injectable5, Optional as Optional2 } from "@nestjs/common";
|
|
648
|
-
import { Reflector as Reflector2 } from "@nestjs/core";
|
|
649
|
-
import { from } from "rxjs";
|
|
650
|
-
import { switchMap } from "rxjs/operators";
|
|
651
|
-
function _ts_decorate7(decorators, target, key, desc) {
|
|
652
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
653
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
654
|
-
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;
|
|
655
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
656
|
-
}
|
|
657
|
-
__name(_ts_decorate7, "_ts_decorate");
|
|
658
|
-
function _ts_metadata6(k, v) {
|
|
659
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
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}`;
|
|
660
698
|
}
|
|
661
|
-
__name(
|
|
662
|
-
function
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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);
|
|
666
708
|
}
|
|
667
|
-
__name(
|
|
668
|
-
|
|
669
|
-
|
|
709
|
+
__name(assertConfiguration, "assertConfiguration");
|
|
710
|
+
|
|
711
|
+
// src/platform/quota-providers-usage-snapshot.ts
|
|
712
|
+
var QuotaProvidersUsageSnapshot = class {
|
|
670
713
|
static {
|
|
671
|
-
__name(this, "
|
|
714
|
+
__name(this, "QuotaProvidersUsageSnapshot");
|
|
672
715
|
}
|
|
673
|
-
reflector;
|
|
674
|
-
entitlements;
|
|
675
716
|
providers;
|
|
676
|
-
constructor(
|
|
677
|
-
this.reflector = reflector;
|
|
678
|
-
this.entitlements = entitlements;
|
|
679
|
-
this.providers = providers;
|
|
680
|
-
}
|
|
681
|
-
intercept(context, next) {
|
|
682
|
-
const meta = this.reflector.getAllAndOverride(ENFORCE_QUOTA_KEY, [
|
|
683
|
-
context.getHandler(),
|
|
684
|
-
context.getClass()
|
|
685
|
-
]);
|
|
686
|
-
if (!meta?.quotaKey) return next.handle();
|
|
687
|
-
return from(this.check(context, meta)).pipe(switchMap(() => next.handle()));
|
|
688
|
-
}
|
|
689
|
-
async check(context, meta) {
|
|
690
|
-
const request = context.switchToHttp().getRequest();
|
|
691
|
-
const user = request.user;
|
|
692
|
-
if (!user) return;
|
|
693
|
-
const role = user.role ?? user.platformRole;
|
|
694
|
-
if (role === "SUPER_ADMIN") return;
|
|
695
|
-
const tenantId = request.tenantId ?? user.tenantId;
|
|
696
|
-
if (!tenantId) return;
|
|
697
|
-
const provider = (this.providers ?? []).find((p) => p.key === meta.quotaKey);
|
|
698
|
-
if (!provider) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
const limit = await this.entitlements.quotaLimit(tenantId, meta.quotaKey);
|
|
702
|
-
if (limit === null || limit === -1) return;
|
|
703
|
-
const current = await provider.count(tenantId);
|
|
704
|
-
const delta = meta.incrementBy ?? 1;
|
|
705
|
-
if (current + delta > limit) {
|
|
706
|
-
throw new LimitExceededError(meta.quotaKey, limit, current);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
EnforceQuotaInterceptor = _ts_decorate7([
|
|
711
|
-
Injectable5(),
|
|
712
|
-
_ts_param6(0, Inject5(Reflector2)),
|
|
713
|
-
_ts_param6(1, Inject5(StaticEntitlementService)),
|
|
714
|
-
_ts_param6(2, Optional2()),
|
|
715
|
-
_ts_param6(2, Inject5(QUOTA_PROVIDERS_TOKEN)),
|
|
716
|
-
_ts_metadata6("design:type", Function),
|
|
717
|
-
_ts_metadata6("design:paramtypes", [
|
|
718
|
-
typeof Reflector2 === "undefined" ? Object : Reflector2,
|
|
719
|
-
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
|
|
720
|
-
Object
|
|
721
|
-
])
|
|
722
|
-
], EnforceQuotaInterceptor);
|
|
723
|
-
|
|
724
|
-
// src/platform/quota-providers-usage-snapshot.ts
|
|
725
|
-
var QuotaProvidersUsageSnapshot = class {
|
|
726
|
-
static {
|
|
727
|
-
__name(this, "QuotaProvidersUsageSnapshot");
|
|
728
|
-
}
|
|
729
|
-
providers;
|
|
730
|
-
constructor(providers) {
|
|
717
|
+
constructor(providers) {
|
|
731
718
|
this.providers = providers;
|
|
732
719
|
}
|
|
733
720
|
async snapshot(tenantId) {
|
|
@@ -739,41 +726,443 @@ var QuotaProvidersUsageSnapshot = class {
|
|
|
739
726
|
}
|
|
740
727
|
};
|
|
741
728
|
|
|
742
|
-
// src/platform/
|
|
743
|
-
|
|
744
|
-
"
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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("saas-platform-nest/PlanResolverPort");
|
|
1058
|
+
var StaticPlanResolver = class {
|
|
748
1059
|
static {
|
|
749
|
-
__name(this, "
|
|
1060
|
+
__name(this, "StaticPlanResolver");
|
|
750
1061
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
this.subscriptions = subscriptions;
|
|
755
|
-
this.activeStatuses = activeStatuses;
|
|
1062
|
+
planId;
|
|
1063
|
+
constructor(planId) {
|
|
1064
|
+
this.planId = planId;
|
|
756
1065
|
}
|
|
757
|
-
async getPlanIdForTenant(
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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);
|
|
761
1140
|
}
|
|
762
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);
|
|
763
1152
|
|
|
764
1153
|
// src/platform/tenant-manifest.service.ts
|
|
765
|
-
import { Injectable as
|
|
766
|
-
function
|
|
1154
|
+
import { Injectable as Injectable4 } from "@nestjs/common";
|
|
1155
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
767
1156
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
768
1157
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
769
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;
|
|
770
1159
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
771
1160
|
}
|
|
772
|
-
__name(
|
|
773
|
-
function
|
|
1161
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
1162
|
+
function _ts_metadata5(k, v) {
|
|
774
1163
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
775
1164
|
}
|
|
776
|
-
__name(
|
|
1165
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
777
1166
|
var TenantManifestService = class {
|
|
778
1167
|
static {
|
|
779
1168
|
__name(this, "TenantManifestService");
|
|
@@ -819,10 +1208,10 @@ var TenantManifestService = class {
|
|
|
819
1208
|
};
|
|
820
1209
|
}
|
|
821
1210
|
};
|
|
822
|
-
TenantManifestService =
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1211
|
+
TenantManifestService = _ts_decorate6([
|
|
1212
|
+
Injectable4(),
|
|
1213
|
+
_ts_metadata5("design:type", Function),
|
|
1214
|
+
_ts_metadata5("design:paramtypes", [
|
|
826
1215
|
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService
|
|
827
1216
|
])
|
|
828
1217
|
], TenantManifestService);
|
|
@@ -836,7 +1225,146 @@ function isVisible(item, features) {
|
|
|
836
1225
|
__name(isVisible, "isVisible");
|
|
837
1226
|
|
|
838
1227
|
// src/platform/tenant-manifest.controller.ts
|
|
839
|
-
import { Controller as Controller2, Get as Get2, Inject as
|
|
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("saas-platform-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";
|
|
840
1368
|
function _ts_decorate9(decorators, target, key, desc) {
|
|
841
1369
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
842
1370
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -854,59 +1382,100 @@ function _ts_param7(paramIndex, decorator) {
|
|
|
854
1382
|
};
|
|
855
1383
|
}
|
|
856
1384
|
__name(_ts_param7, "_ts_param");
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1385
|
+
var STATIC_FEATURE_GUARD_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-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
|
+
});
|
|
862
1413
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
+
});
|
|
866
1422
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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);
|
|
873
1434
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
+
};
|
|
896
1473
|
|
|
897
1474
|
// src/platform/saas-platform.module.ts
|
|
898
|
-
import { Logger
|
|
899
|
-
import { APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core";
|
|
900
|
-
import { assertPersistenceCapabilities } from "@saasicat/types";
|
|
1475
|
+
import { Logger, Module as Module2 } from "@nestjs/common";
|
|
901
1476
|
import { DiscoveryModule as NestDiscoveryModule } from "@nestjs/core";
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
905
|
-
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;
|
|
906
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
907
|
-
}
|
|
908
|
-
__name(_ts_decorate10, "_ts_decorate");
|
|
909
|
-
var PLATFORM_LOGGER = new Logger2("SaaSiCat");
|
|
1477
|
+
|
|
1478
|
+
// src/platform/compose/manifest.ts
|
|
910
1479
|
var PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/PlatformSubscriptionRepository");
|
|
911
1480
|
var STANDARD_MANIFEST_REGISTRATION_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/StandardManifestRegistration");
|
|
912
1481
|
function buildStandardManifestContribution(catalog, adminResources, promoCodes) {
|
|
@@ -961,21 +1530,6 @@ function buildStandardManifestContribution(catalog, adminResources, promoCodes)
|
|
|
961
1530
|
};
|
|
962
1531
|
}
|
|
963
1532
|
__name(buildStandardManifestContribution, "buildStandardManifestContribution");
|
|
964
|
-
function normalizeTenantAuthGuards(guards) {
|
|
965
|
-
if (!Array.isArray(guards)) return guards;
|
|
966
|
-
return {
|
|
967
|
-
useFactory: /* @__PURE__ */ __name((...instances) => instances, "useFactory"),
|
|
968
|
-
inject: guards
|
|
969
|
-
};
|
|
970
|
-
}
|
|
971
|
-
__name(normalizeTenantAuthGuards, "normalizeTenantAuthGuards");
|
|
972
|
-
function quotaUsageSnapshotProvider(quotaProviders) {
|
|
973
|
-
return {
|
|
974
|
-
useFactory: /* @__PURE__ */ __name((...providers) => new QuotaProvidersUsageSnapshot(providers), "useFactory"),
|
|
975
|
-
inject: quotaProviders
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
__name(quotaUsageSnapshotProvider, "quotaUsageSnapshotProvider");
|
|
979
1533
|
function buildMinimalManifestConfig() {
|
|
980
1534
|
return {
|
|
981
1535
|
useFactory: /* @__PURE__ */ __name((catalog) => ({
|
|
@@ -1008,6 +1562,189 @@ function buildMinimalManifestConfig() {
|
|
|
1008
1562
|
};
|
|
1009
1563
|
}
|
|
1010
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/saas-platform.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");
|
|
1011
1748
|
var SaasPlatformModule = class _SaasPlatformModule {
|
|
1012
1749
|
static {
|
|
1013
1750
|
__name(this, "SaasPlatformModule");
|
|
@@ -1020,11 +1757,7 @@ var SaasPlatformModule = class _SaasPlatformModule {
|
|
|
1020
1757
|
const subscriptionBundlesConfig = options.subscriptionBundles || null;
|
|
1021
1758
|
const adminResourcesConfig = options.adminResources || null;
|
|
1022
1759
|
const promoCodesConfig = options.promoCodes || null;
|
|
1023
|
-
const
|
|
1024
|
-
const adminStatsConfig = options.adminStats || null;
|
|
1025
|
-
const checkoutOfferConfig = options.checkoutOffer || null;
|
|
1026
|
-
const subscriptionContractConfig = options.subscriptionContract || null;
|
|
1027
|
-
const requiresFullEntitlement = Boolean(options.entitlement || tenantBillingConfig || subscriptionBundlesConfig);
|
|
1760
|
+
const requiresFullEntitlement2 = Boolean(options.entitlement || tenantBillingConfig || subscriptionBundlesConfig);
|
|
1028
1761
|
const adapters = {
|
|
1029
1762
|
mfa: explicit.mfa ?? persistence?.core.mfa,
|
|
1030
1763
|
audit: explicit.audit ?? persistence?.core.audit,
|
|
@@ -1035,296 +1768,36 @@ var SaasPlatformModule = class _SaasPlatformModule {
|
|
|
1035
1768
|
planVersionRepository: explicit.planVersionRepository ?? persistence?.entitlement?.planVersionRepository,
|
|
1036
1769
|
transactionRunner: explicit.transactionRunner ?? persistence?.core.transactionRunner
|
|
1037
1770
|
};
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
].filter((key) => adapters[key] === void 0);
|
|
1043
|
-
if (missingCore.length) {
|
|
1044
|
-
throw new Error(`SaasPlatformModule.forRoot: adapters missing (provide them via \`adapters\` or a \`persistence\` bundle): ${missingCore.join(", ")}`);
|
|
1045
|
-
}
|
|
1771
|
+
assertConfiguration({
|
|
1772
|
+
options,
|
|
1773
|
+
adapters
|
|
1774
|
+
});
|
|
1046
1775
|
const mfaPort = adapters.mfa;
|
|
1047
1776
|
const auditPort = adapters.audit;
|
|
1048
1777
|
const rlsBypassPort = adapters.rlsBypass;
|
|
1049
|
-
|
|
1050
|
-
throw new Error("SaasPlatformModule.forRoot: either set `planCatalog` (quickstart YAML path) or, for DB hydration, BOTH a planCatalogReadSink (`adapters`/`persistence`) AND `dbCatalog` ({ projectKey, currency, vatRate }).");
|
|
1051
|
-
}
|
|
1052
|
-
if (catalogConfig && !persistence?.catalog) {
|
|
1053
|
-
throw new Error("SaasPlatformModule.forRoot: catalog is enabled, but `persistence.catalog` is missing. Use a complete persistence bundle or wire CatalogModule directly.");
|
|
1054
|
-
}
|
|
1055
|
-
if (adminResourcesConfig && adminResourcesConfig !== true && !adminResourcesConfig.resources && !persistence?.adminResources) {
|
|
1056
|
-
throw new Error("SaasPlatformModule.forRoot: adminResources is enabled, but `persistence.adminResources` is missing.");
|
|
1057
|
-
}
|
|
1058
|
-
if (promoCodesConfig && (!persistence?.promo || !adapters.transactionRunner)) {
|
|
1059
|
-
throw new Error("SaasPlatformModule.forRoot: promoCodes requires `persistence.promo` and a transactionRunner.");
|
|
1060
|
-
}
|
|
1061
|
-
if (setupConfig && !((setupConfig === true ? void 0 : setupConfig.provisioningPort) ?? persistence?.core.superAdminProvisioning)) {
|
|
1062
|
-
throw new Error("SaasPlatformModule.forRoot: setup is enabled, but no provisioningPort is available (set `setup.provisioningPort` or `persistence.core.superAdminProvisioning`).");
|
|
1063
|
-
}
|
|
1064
|
-
if (adminStatsConfig && !adminStatsConfig.auditStatsPort && !persistence?.core.auditStats) {
|
|
1065
|
-
throw new Error("SaasPlatformModule.forRoot: adminStats is enabled, but no auditStatsPort is available (set `adminStats.auditStatsPort` or `persistence.core.auditStats`).");
|
|
1066
|
-
}
|
|
1067
|
-
if (subscriptionContractConfig) {
|
|
1068
|
-
const explicitRepository = subscriptionContractConfig === true ? void 0 : subscriptionContractConfig.subscriptionContractRepository;
|
|
1069
|
-
if (!explicitRepository && !persistence?.entitlement?.subscriptionContractRepository) {
|
|
1070
|
-
throw new Error("SaasPlatformModule.forRoot: subscriptionContract is enabled, but no repository is available (set `subscriptionContract.subscriptionContractRepository` or use a compatible persistence bundle).");
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
if (promoCodesConfig && promoCodesConfig !== true && promoCodesConfig.includePublicController !== false && !promoCodesConfig.firstTimeCustomerCheck) {
|
|
1074
|
-
throw new Error("SaasPlatformModule.forRoot: public promo preview requires `firstTimeCustomerCheck`.");
|
|
1075
|
-
}
|
|
1076
|
-
if (adminResourcesConfig === true && !persistence?.adminResources) {
|
|
1077
|
-
throw new Error("SaasPlatformModule.forRoot: adminResources is enabled, but `persistence.adminResources` is missing.");
|
|
1078
|
-
}
|
|
1079
|
-
if (tenantBillingConfig && !persistence?.tenantBilling) {
|
|
1080
|
-
const missing = [
|
|
1081
|
-
!tenantBillingConfig.subscriptionUsagePort && "subscriptionUsagePort",
|
|
1082
|
-
!tenantBillingConfig.subscriptionWritePort && "subscriptionWritePort"
|
|
1083
|
-
].filter(Boolean);
|
|
1084
|
-
if (missing.length) {
|
|
1085
|
-
throw new Error(`SaasPlatformModule.forRoot: tenantBilling is enabled, but adapters are missing: ${missing.join(", ")}`);
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
if (subscriptionBundlesConfig && !tenantBillingConfig) {
|
|
1089
|
-
throw new Error("SaasPlatformModule.forRoot: subscriptionBundles requires tenantBilling so auth and subscription usage can be shared.");
|
|
1090
|
-
}
|
|
1091
|
-
if (subscriptionBundlesConfig) {
|
|
1092
|
-
const missing = [];
|
|
1093
|
-
if (!persistence?.entitlement?.subscriptionBundleRepository) {
|
|
1094
|
-
missing.push("subscriptionBundleRepository");
|
|
1095
|
-
}
|
|
1096
|
-
if (!persistence?.catalog?.bundleRepository) {
|
|
1097
|
-
missing.push("bundleRepository");
|
|
1098
|
-
}
|
|
1099
|
-
if (missing.length) {
|
|
1100
|
-
throw new Error(`SaasPlatformModule.forRoot: subscriptionBundles is enabled, but persistence adapters are missing: ${missing.join(", ")}`);
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
if (requiresFullEntitlement) {
|
|
1104
|
-
const missing = [];
|
|
1105
|
-
if (!adapters.subscriptionRepository) missing.push("subscriptionRepository");
|
|
1106
|
-
if (!adapters.planVersionRepository) missing.push("planVersionRepository");
|
|
1107
|
-
if (!adapters.transactionRunner) missing.push("transactionRunner");
|
|
1108
|
-
if (missing.length) {
|
|
1109
|
-
throw new Error(`SaasPlatformModule.forRoot: entitlement active or required by the standard stack, but adapters are missing: ${missing.join(", ")}`);
|
|
1110
|
-
}
|
|
1111
|
-
if (persistence) {
|
|
1112
|
-
assertPersistenceCapabilities(persistence.capabilities, {
|
|
1113
|
-
transactions: true,
|
|
1114
|
-
pessimisticLocking: true
|
|
1115
|
-
}, "SaasPlatformModule entitlement (transactional enforceLimit)");
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
const dbCatalog = options.dbCatalog;
|
|
1119
|
-
const planCatalogModule = options.planCatalog ? PlanCatalogModule.forRootWithCatalog(options.planCatalog, {
|
|
1120
|
-
global: true
|
|
1121
|
-
}) : PlanCatalogModule.forRoot({
|
|
1122
|
-
projectKey: dbCatalog.projectKey,
|
|
1123
|
-
app: dbCatalog.app,
|
|
1124
|
-
currency: dbCatalog.currency,
|
|
1125
|
-
vatRate: dbCatalog.vatRate,
|
|
1126
|
-
marketing: dbCatalog.marketing,
|
|
1127
|
-
sink: adapters.planCatalogReadSink,
|
|
1128
|
-
imports: options.imports
|
|
1129
|
-
});
|
|
1130
|
-
const appInfo = options.app ?? {
|
|
1131
|
-
key: options.planCatalog?.projectKey ?? options.dbCatalog?.projectKey ?? "app",
|
|
1132
|
-
version: options.planCatalog?.app?.version ?? options.dbCatalog?.app?.version ?? "0.0.0"
|
|
1133
|
-
};
|
|
1778
|
+
const appInfo = resolveAppInfo(options);
|
|
1134
1779
|
const imports = [
|
|
1135
1780
|
// Make injected client/auth providers visible to the high-level
|
|
1136
1781
|
// module's own quota providers and automatic plan resolver too.
|
|
1137
1782
|
...options.imports ?? [],
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
app: appInfo,
|
|
1141
|
-
controller: {
|
|
1142
|
-
guards: options.controller.guards
|
|
1143
|
-
},
|
|
1144
|
-
imports: options.imports,
|
|
1145
|
-
snapshotPath: options.discoverySnapshotPath === void 0 ? "var/discovery-snapshot.json" : options.discoverySnapshotPath
|
|
1146
|
-
}),
|
|
1147
|
-
AdminModule.forRoot({
|
|
1783
|
+
composePlanCatalog(options, adapters.planCatalogReadSink),
|
|
1784
|
+
...composeBaseModules(options, appInfo, {
|
|
1148
1785
|
mfaPort,
|
|
1149
1786
|
auditPort,
|
|
1150
|
-
rlsBypassPort
|
|
1151
|
-
imports: options.imports,
|
|
1152
|
-
global: true
|
|
1153
|
-
}),
|
|
1154
|
-
AdminManifestModule.forRoot({
|
|
1155
|
-
config: options.adminManifestConfig ?? buildMinimalManifestConfig(),
|
|
1156
|
-
extraProviders: options.adminManifestExtraProviders,
|
|
1157
|
-
includeManifestController: options.includeManifestController,
|
|
1158
|
-
guards: options.controller.guards,
|
|
1159
|
-
reloadGuards: options.reloadGuards,
|
|
1160
|
-
imports: options.imports,
|
|
1161
|
-
// Global like AdminModule above: apps register their manifest
|
|
1162
|
-
// contribution by injecting AdminManifestService into one of
|
|
1163
|
-
// their own modules (handbook §6.6). Re-exporting the module
|
|
1164
|
-
// from here does not make that injection resolvable.
|
|
1165
|
-
global: true
|
|
1787
|
+
rlsBypassPort
|
|
1166
1788
|
})
|
|
1167
1789
|
];
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
bundleRepository: persistence?.entitlement?.bundleRepository,
|
|
1177
|
-
imports: options.imports,
|
|
1178
|
-
global: Boolean(tenantBillingConfig || subscriptionBundlesConfig)
|
|
1179
|
-
}));
|
|
1180
|
-
}
|
|
1181
|
-
if (catalogConfig) {
|
|
1182
|
-
const catalog = persistence?.catalog;
|
|
1183
|
-
imports.push(CatalogModule.forRoot({
|
|
1184
|
-
planRepository: catalog.planRepository,
|
|
1185
|
-
bundleRepository: catalog.bundleRepository,
|
|
1186
|
-
catalogEntryRepository: catalog.catalogEntryRepository,
|
|
1187
|
-
marketingProjectionRepository: catalog.marketingProjectionRepository,
|
|
1188
|
-
promotionRepository: catalog.promotionRepository,
|
|
1189
|
-
marketingSettingsRepository: catalog.marketingSettingsRepository,
|
|
1190
|
-
controller: catalogConfig.adminControllers === false ? void 0 : {
|
|
1191
|
-
guards: options.controller.guards
|
|
1192
|
-
},
|
|
1193
|
-
imports: catalogConfig.imports ?? options.imports,
|
|
1194
|
-
extraProviders: catalogConfig.extraProviders,
|
|
1195
|
-
strictModeCheckMode: catalogConfig.strictModeCheckMode,
|
|
1196
|
-
autoSyncDiscoveryAtBoot: catalogConfig.autoSyncDiscoveryAtBoot,
|
|
1197
|
-
marketedOnlyFeatures: catalogConfig.marketedOnlyFeatures,
|
|
1198
|
-
featureUiRegistry: catalogConfig.featureUiRegistry,
|
|
1199
|
-
publicMarketingCatalog: catalogConfig.publicMarketingCatalog
|
|
1200
|
-
}));
|
|
1201
|
-
if (catalogConfig.publicCatalog !== false) {
|
|
1202
|
-
imports.push(PublicCatalogModule.forRoot({
|
|
1203
|
-
featureUiRegistry: catalogConfig.featureUiRegistry,
|
|
1204
|
-
projectKey: appInfo.key,
|
|
1205
|
-
bundleRepository: catalog.bundleRepository,
|
|
1206
|
-
marketingRepository: catalog.marketingProjectionRepository,
|
|
1207
|
-
catalogEntryRepository: catalog.catalogEntryRepository,
|
|
1208
|
-
imports: catalogConfig.imports ?? options.imports
|
|
1209
|
-
}));
|
|
1790
|
+
const composition = {
|
|
1791
|
+
options,
|
|
1792
|
+
adapters,
|
|
1793
|
+
persistence,
|
|
1794
|
+
appInfo,
|
|
1795
|
+
requiresFullEntitlement: requiresFullEntitlement2,
|
|
1796
|
+
shared: {
|
|
1797
|
+
quotaProvidersHostedByTenantBilling: false
|
|
1210
1798
|
}
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
const config = adminResourcesConfig === true ? {} : adminResourcesConfig;
|
|
1214
|
-
imports.push(AdminResourcesModule.forRoot({
|
|
1215
|
-
resources: config.resources ?? persistence?.adminResources?.resources,
|
|
1216
|
-
guards: config.guards ?? [
|
|
1217
|
-
...options.controller.guards,
|
|
1218
|
-
SuperAdminGuard
|
|
1219
|
-
],
|
|
1220
|
-
imports: config.imports ?? options.imports
|
|
1221
|
-
}));
|
|
1222
|
-
}
|
|
1223
|
-
if (promoCodesConfig) {
|
|
1224
|
-
const config = promoCodesConfig === true ? {} : promoCodesConfig;
|
|
1225
|
-
const promo = persistence?.promo;
|
|
1226
|
-
const { firstTimeCustomerCheck, adminGuards, imports: promoImports, includePublicController, adminController, ...promoOptions } = config;
|
|
1227
|
-
imports.push(PromoCodesModule.forRoot({
|
|
1228
|
-
...promoOptions,
|
|
1229
|
-
promoCodeRepository: promo.promoCodeRepository,
|
|
1230
|
-
redemptionRepository: promo.redemptionRepository,
|
|
1231
|
-
validationLogRepository: promo.validationLogRepository,
|
|
1232
|
-
subscriptionLookup: promo.subscriptionLookup,
|
|
1233
|
-
revenueAggregator: promo.revenueAggregator,
|
|
1234
|
-
transactionRunner: adapters.transactionRunner,
|
|
1235
|
-
firstTimeCustomerCheck: firstTimeCustomerCheck ?? {
|
|
1236
|
-
hasExistingCustomerForEmail: /* @__PURE__ */ __name(async () => false, "hasExistingCustomerForEmail")
|
|
1237
|
-
},
|
|
1238
|
-
includePublicController: includePublicController ?? false,
|
|
1239
|
-
adminController: adminController === false ? false : {
|
|
1240
|
-
guards: adminGuards ?? [
|
|
1241
|
-
...options.controller.guards,
|
|
1242
|
-
SuperAdminGuard
|
|
1243
|
-
]
|
|
1244
|
-
},
|
|
1245
|
-
imports: promoImports ?? options.imports
|
|
1246
|
-
}));
|
|
1247
|
-
}
|
|
1248
|
-
let sharedTenantAuthGuards;
|
|
1249
|
-
let sharedSubscriptionUsagePort;
|
|
1250
|
-
let quotaProvidersHostedByTenantBilling = false;
|
|
1251
|
-
if (tenantBillingConfig) {
|
|
1252
|
-
const tenantSlice = persistence?.tenantBilling;
|
|
1253
|
-
const { authGuards, subscriptionUsagePort, usageSnapshotPort, subscriptionWritePort, imports: tenantImports, extraProviders, extraExports, ...tenantOptions } = tenantBillingConfig;
|
|
1254
|
-
const quotaProviders = options.quotaProviders ?? [];
|
|
1255
|
-
quotaProvidersHostedByTenantBilling = quotaProviders.length > 0;
|
|
1256
|
-
sharedTenantAuthGuards = normalizeTenantAuthGuards(authGuards);
|
|
1257
|
-
sharedSubscriptionUsagePort = subscriptionUsagePort ?? tenantSlice?.subscriptionUsagePort;
|
|
1258
|
-
const resolvedUsageSnapshotPort = usageSnapshotPort ?? tenantSlice?.usageSnapshotPort ?? quotaUsageSnapshotProvider(options.quotaProviders ?? []);
|
|
1259
|
-
imports.push(TenantBillingModule.forRoot({
|
|
1260
|
-
...tenantOptions,
|
|
1261
|
-
authGuards: sharedTenantAuthGuards,
|
|
1262
|
-
subscriptionUsagePort: sharedSubscriptionUsagePort,
|
|
1263
|
-
usageSnapshotPort: resolvedUsageSnapshotPort,
|
|
1264
|
-
subscriptionWritePort: subscriptionWritePort ?? tenantSlice?.subscriptionWritePort,
|
|
1265
|
-
imports: tenantImports ?? options.imports,
|
|
1266
|
-
extraProviders: [
|
|
1267
|
-
...quotaProviders,
|
|
1268
|
-
...extraProviders ?? []
|
|
1269
|
-
],
|
|
1270
|
-
extraExports: [
|
|
1271
|
-
...quotaProviders,
|
|
1272
|
-
...extraExports ?? []
|
|
1273
|
-
]
|
|
1274
|
-
}));
|
|
1275
|
-
}
|
|
1276
|
-
if (subscriptionBundlesConfig) {
|
|
1277
|
-
const config = subscriptionBundlesConfig === true ? {} : subscriptionBundlesConfig;
|
|
1278
|
-
const { controller, imports: bundleImports, extraProviders, ...bundleOptions } = config;
|
|
1279
|
-
imports.push(SubscriptionBundleModule.forRoot({
|
|
1280
|
-
...bundleOptions,
|
|
1281
|
-
subscriptionBundleRepository: persistence?.entitlement?.subscriptionBundleRepository,
|
|
1282
|
-
bundleRepository: persistence?.catalog?.bundleRepository,
|
|
1283
|
-
controller: controller === false ? void 0 : {
|
|
1284
|
-
...controller ?? {},
|
|
1285
|
-
authGuards: sharedTenantAuthGuards,
|
|
1286
|
-
subscriptionUsagePort: sharedSubscriptionUsagePort
|
|
1287
|
-
},
|
|
1288
|
-
imports: bundleImports ?? tenantBillingConfig?.imports ?? options.imports,
|
|
1289
|
-
extraProviders
|
|
1290
|
-
}));
|
|
1291
|
-
}
|
|
1292
|
-
if (setupConfig) {
|
|
1293
|
-
const config = setupConfig === true ? {} : setupConfig;
|
|
1294
|
-
imports.push(SetupModule.forRoot({
|
|
1295
|
-
...config,
|
|
1296
|
-
provisioningPort: config.provisioningPort ?? persistence?.core.superAdminProvisioning,
|
|
1297
|
-
imports: config.imports ?? options.imports
|
|
1298
|
-
}));
|
|
1299
|
-
}
|
|
1300
|
-
if (adminStatsConfig) {
|
|
1301
|
-
imports.push(AdminStatsModule.forRoot({
|
|
1302
|
-
...adminStatsConfig,
|
|
1303
|
-
guards: adminStatsConfig.guards ?? [
|
|
1304
|
-
...options.controller.guards,
|
|
1305
|
-
SuperAdminGuard
|
|
1306
|
-
],
|
|
1307
|
-
auditStatsPort: adminStatsConfig.auditStatsPort ?? persistence?.core.auditStats,
|
|
1308
|
-
imports: adminStatsConfig.imports ?? options.imports
|
|
1309
|
-
}));
|
|
1310
|
-
}
|
|
1311
|
-
if (checkoutOfferConfig) {
|
|
1312
|
-
imports.push(CheckoutOfferModule.forRoot({
|
|
1313
|
-
...checkoutOfferConfig,
|
|
1314
|
-
bundleRepository: checkoutOfferConfig.bundleRepository ?? persistence?.catalog?.bundleRepository,
|
|
1315
|
-
planRepository: checkoutOfferConfig.planRepository ?? persistence?.catalog?.planRepository,
|
|
1316
|
-
catalogEntryRepository: checkoutOfferConfig.catalogEntryRepository ?? persistence?.catalog?.catalogEntryRepository,
|
|
1317
|
-
imports: checkoutOfferConfig.imports ?? options.imports
|
|
1318
|
-
}));
|
|
1319
|
-
}
|
|
1320
|
-
if (subscriptionContractConfig) {
|
|
1321
|
-
const config = subscriptionContractConfig === true ? {} : subscriptionContractConfig;
|
|
1322
|
-
imports.push(SubscriptionContractModule.forRoot({
|
|
1323
|
-
...config,
|
|
1324
|
-
subscriptionContractRepository: config.subscriptionContractRepository ?? persistence?.entitlement?.subscriptionContractRepository,
|
|
1325
|
-
imports: config.imports ?? options.imports
|
|
1326
|
-
}));
|
|
1327
|
-
}
|
|
1799
|
+
};
|
|
1800
|
+
imports.push(...composeFeatures(composition));
|
|
1328
1801
|
const lightweightProviders = [];
|
|
1329
1802
|
const lightweightExports = [];
|
|
1330
1803
|
if (options.autoManifest !== false) {
|
|
@@ -1340,100 +1813,35 @@ var SaasPlatformModule = class _SaasPlatformModule {
|
|
|
1340
1813
|
]
|
|
1341
1814
|
});
|
|
1342
1815
|
}
|
|
1343
|
-
const
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
if (
|
|
1348
|
-
if (hasSubscriptionResolver && !hasExplicitResolver) {
|
|
1349
|
-
lightweightProviders.push(asProvider(PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN, adapters.subscriptionRepository));
|
|
1350
|
-
}
|
|
1351
|
-
lightweightProviders.push(hasExplicitResolver ? asProvider(PLAN_RESOLVER_PORT_TOKEN, adapters.planResolver) : hasSubscriptionResolver ? {
|
|
1352
|
-
provide: PLAN_RESOLVER_PORT_TOKEN,
|
|
1353
|
-
useFactory: /* @__PURE__ */ __name((subscriptions) => new SubscriptionPlanResolver(subscriptions), "useFactory"),
|
|
1354
|
-
inject: [
|
|
1355
|
-
PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN
|
|
1356
|
-
]
|
|
1357
|
-
} : {
|
|
1358
|
-
provide: PLAN_RESOLVER_PORT_TOKEN,
|
|
1359
|
-
useValue: new StaticPlanResolver(options.defaultPlanId)
|
|
1360
|
-
}, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, ...quotaProvidersHostedByTenantBilling ? [] : options.quotaProviders ?? [], {
|
|
1361
|
-
provide: QUOTA_PROVIDERS_TOKEN,
|
|
1362
|
-
useFactory: /* @__PURE__ */ __name((...providers) => providers, "useFactory"),
|
|
1363
|
-
inject: options.quotaProviders ?? []
|
|
1364
|
-
}, ...options.globalFeatureGuard === false ? [] : [
|
|
1365
|
-
{
|
|
1366
|
-
provide: APP_GUARD,
|
|
1367
|
-
useExisting: StaticFeatureGuard
|
|
1368
|
-
}
|
|
1369
|
-
], {
|
|
1370
|
-
provide: APP_INTERCEPTOR,
|
|
1371
|
-
useExisting: EnforceQuotaInterceptor
|
|
1372
|
-
});
|
|
1373
|
-
lightweightExports.push(PLAN_RESOLVER_PORT_TOKEN, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, QUOTA_PROVIDERS_TOKEN);
|
|
1374
|
-
if (options.globalFeatureGuard === false) {
|
|
1375
|
-
lightweightProviders.push(FeatureGuardCoverageCheck);
|
|
1376
|
-
imports.push(NestDiscoveryModule);
|
|
1377
|
-
}
|
|
1378
|
-
} else {
|
|
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) {
|
|
1379
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.");
|
|
1380
1822
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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
|
+
}));
|
|
1391
1834
|
}
|
|
1835
|
+
if (options.enforcementChainCheck !== false) imports.push(NestDiscoveryModule);
|
|
1836
|
+
const tenantManifest = composeTenantManifest(composition);
|
|
1837
|
+
lightweightProviders.push(...tenantManifest.providers);
|
|
1838
|
+
lightweightExports.push(...tenantManifest.exports);
|
|
1392
1839
|
return {
|
|
1393
1840
|
module: _SaasPlatformModule,
|
|
1394
1841
|
imports,
|
|
1395
1842
|
providers: lightweightProviders,
|
|
1396
|
-
controllers:
|
|
1397
|
-
exports:
|
|
1398
|
-
PlanCatalogModule,
|
|
1399
|
-
DiscoveryModule,
|
|
1400
|
-
AdminModule,
|
|
1401
|
-
// ADMIN_MANIFEST_CONFIG travels transitively: re-exporting an
|
|
1402
|
-
// imported module's token directly is an UnknownExportException
|
|
1403
|
-
// at boot — the module export below already carries it.
|
|
1404
|
-
AdminManifestModule,
|
|
1405
|
-
...requiresFullEntitlement ? [
|
|
1406
|
-
EntitlementModule
|
|
1407
|
-
] : [],
|
|
1408
|
-
...catalogConfig ? [
|
|
1409
|
-
CatalogModule
|
|
1410
|
-
] : [],
|
|
1411
|
-
...tenantBillingConfig ? [
|
|
1412
|
-
TenantBillingModule
|
|
1413
|
-
] : [],
|
|
1414
|
-
...subscriptionBundlesConfig ? [
|
|
1415
|
-
SubscriptionBundleModule
|
|
1416
|
-
] : [],
|
|
1417
|
-
...adminResourcesConfig ? [
|
|
1418
|
-
AdminResourcesModule
|
|
1419
|
-
] : [],
|
|
1420
|
-
...promoCodesConfig ? [
|
|
1421
|
-
PromoCodesModule
|
|
1422
|
-
] : [],
|
|
1423
|
-
...setupConfig ? [
|
|
1424
|
-
SetupModule
|
|
1425
|
-
] : [],
|
|
1426
|
-
...adminStatsConfig ? [
|
|
1427
|
-
AdminStatsModule
|
|
1428
|
-
] : [],
|
|
1429
|
-
...checkoutOfferConfig ? [
|
|
1430
|
-
CheckoutOfferModule
|
|
1431
|
-
] : [],
|
|
1432
|
-
...subscriptionContractConfig ? [
|
|
1433
|
-
SubscriptionContractModule
|
|
1434
|
-
] : [],
|
|
1435
|
-
...lightweightExports
|
|
1436
|
-
],
|
|
1843
|
+
controllers: tenantManifest.controllers,
|
|
1844
|
+
exports: composeModuleExports(composition, lightweightExports),
|
|
1437
1845
|
global: true
|
|
1438
1846
|
};
|
|
1439
1847
|
}
|
|
@@ -1448,17 +1856,28 @@ export {
|
|
|
1448
1856
|
SetupService,
|
|
1449
1857
|
SetupController,
|
|
1450
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,
|
|
1451
1872
|
PLAN_RESOLVER_PORT_TOKEN,
|
|
1452
1873
|
StaticPlanResolver,
|
|
1453
1874
|
StaticEntitlementService,
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
StaticFeatureGuard,
|
|
1875
|
+
TenantManifestService,
|
|
1876
|
+
buildTenantManifestController,
|
|
1457
1877
|
QUOTA_PROVIDERS_TOKEN,
|
|
1458
1878
|
EnforceQuotaInterceptor,
|
|
1459
|
-
|
|
1879
|
+
STATIC_FEATURE_GUARD_CONFIG_TOKEN,
|
|
1880
|
+
StaticFeatureGuard,
|
|
1460
1881
|
SubscriptionPlanResolver,
|
|
1461
|
-
TenantManifestService,
|
|
1462
|
-
buildTenantManifestController,
|
|
1463
1882
|
SaasPlatformModule
|
|
1464
1883
|
};
|