@saasicat/nest 1.0.0-rc.2 → 1.0.0-rc.21
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/README.md +44 -8
- package/dist/.build-stamp +1 -1
- package/dist/_entries.cjs +10214 -4796
- package/dist/admin/index.d.cts +5 -4
- package/dist/admin/index.d.ts +5 -4
- package/dist/admin/index.js +13 -9
- package/dist/{admin-resources.module-CpvzcfCc.d.cts → admin-resources.module-B4XNvdH-.d.cts} +1 -2
- package/dist/{admin-resources.module-DF5_Q7RD.d.ts → admin-resources.module-CWqdI9BV.d.ts} +1 -2
- package/dist/{admin-stats.service-TYiSUwIR.d.ts → admin-stats.service-CsLNt19Z.d.ts} +11 -7
- package/dist/{admin-stats.service-DfKupywR.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +11 -7
- package/dist/{aggregation-B3CD0v_d.d.cts → aggregation-ChpZDh0g.d.ts} +39 -6
- package/dist/{aggregation-BFzW07DE.d.ts → aggregation-OpnhwzF5.d.cts} +39 -6
- package/dist/billing/index.d.cts +448 -108
- package/dist/billing/index.d.ts +448 -108
- package/dist/billing/index.js +108 -41
- package/dist/catalog/index.d.cts +74 -45
- package/dist/catalog/index.d.ts +74 -45
- package/dist/catalog/index.js +21 -13
- package/dist/catalog.module-C9O7BxRq.d.ts +180 -0
- package/dist/catalog.module-T-I5Ricz.d.cts +180 -0
- package/dist/checkout-offer/index.d.cts +17 -22
- package/dist/checkout-offer/index.d.ts +17 -22
- package/dist/checkout-offer/index.js +21 -7
- package/dist/checkout-offer.module-DFhGy6JO.d.cts +58 -0
- package/dist/checkout-offer.module-x7SP_dX4.d.ts +58 -0
- package/dist/checkout-offer.service-BeHgYikI.d.ts +242 -0
- package/dist/checkout-offer.service-CCVxG9b2.d.cts +242 -0
- package/dist/chunk-34XVPR6P.js +222 -0
- package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
- package/dist/chunk-3RLWML2S.js +896 -0
- package/dist/{chunk-TXC3LHHB.js → chunk-4SDSYV4A.js} +21 -13
- package/dist/chunk-55QEDAMX.js +946 -0
- package/dist/chunk-5CKC7O52.js +140 -0
- package/dist/chunk-6PJE7EWO.js +185 -0
- package/dist/chunk-7NJKVC5X.js +353 -0
- package/dist/{chunk-RUNZ3X4C.js → chunk-AOS2JPDD.js} +1263 -185
- package/dist/{chunk-WOVQPXV4.js → chunk-CQ2ZZMTD.js} +702 -725
- package/dist/{chunk-NA6O63B7.js → chunk-F5X66HF5.js} +5 -5
- package/dist/chunk-FEQZCWUL.js +174 -0
- package/dist/{chunk-VIJ5NJWC.js → chunk-G6EZWECL.js} +107 -31
- package/dist/chunk-G7RQO2XO.js +0 -0
- package/dist/{chunk-N3L3AICU.js → chunk-GPQWGA6B.js} +5 -6
- package/dist/chunk-GZTL64QK.js +7 -0
- package/dist/{chunk-SABTXESR.js → chunk-J7NJRK3K.js} +17 -1
- package/dist/{chunk-SEPN52AD.js → chunk-JBCC6C3M.js} +566 -307
- package/dist/{chunk-I7GTA3RX.js → chunk-JQRA724W.js} +1 -1
- package/dist/{chunk-WXYJHZCN.js → chunk-JVAEKTJ4.js} +2 -14
- package/dist/chunk-KPEMTBOP.js +26 -0
- package/dist/chunk-LF6J4YYN.js +301 -0
- package/dist/{chunk-LTT736P3.js → chunk-LLYVYRGJ.js} +228 -387
- package/dist/{chunk-6Z7JR4EW.js → chunk-M47BNEY2.js} +1897 -633
- package/dist/{chunk-KFT5AIIH.js → chunk-NDLC5GYK.js} +206 -24
- package/dist/{chunk-O2J2HDXA.js → chunk-OOTEXP47.js} +3 -8
- package/dist/chunk-QPVDCKYS.js +702 -0
- package/dist/{chunk-NHVDCYK5.js → chunk-R5YCJBHY.js} +71 -106
- package/dist/{chunk-XBYAFEOR.js → chunk-S33SO5XX.js} +1 -1
- package/dist/chunk-SZ7RFPXA.js +10 -0
- package/dist/chunk-TBBZWZQT.js +98 -0
- package/dist/chunk-TOEFN7DN.js +295 -0
- package/dist/{chunk-7ZEGFL42.js → chunk-WHJSYKNC.js} +6 -6
- package/dist/chunk-WOEJ6K7M.js +55 -0
- package/dist/{chunk-AU3OOREM.js → chunk-XSSWYPP5.js} +16 -3
- package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
- package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
- package/dist/discovery/index.d.cts +2 -2
- package/dist/discovery/index.d.ts +2 -2
- package/dist/discovery/index.js +6 -6
- package/dist/{discovery.scanner-9cMqF95o.d.cts → discovery.scanner-DXKc6JkV.d.cts} +1 -1
- package/dist/{discovery.scanner-9cMqF95o.d.ts → discovery.scanner-DXKc6JkV.d.ts} +1 -1
- package/dist/{enforce-quota.interceptor-Df0e3OWe.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
- package/dist/{enforce-quota.interceptor-DW3etVyI.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
- package/dist/entitlement/index.d.cts +27 -4
- package/dist/entitlement/index.d.ts +27 -4
- package/dist/entitlement/index.js +13 -8
- package/dist/index.d.cts +209 -28
- package/dist/index.d.ts +209 -28
- package/dist/index.js +285 -163
- package/dist/issuer-identity.check-CBO0vcsq.d.cts +197 -0
- package/dist/issuer-identity.check-Cz9CEMkZ.d.ts +197 -0
- package/dist/{module-options-COSEb0VW.d.ts → module-options-DmQ3G4sZ.d.ts} +135 -41
- package/dist/{module-options-BG3MzQva.d.cts → module-options-qAOwinHR.d.cts} +135 -41
- package/dist/payment-callback.service-CR84Xw1Z.d.cts +125 -0
- package/dist/payment-callback.service-CR84Xw1Z.d.ts +125 -0
- package/dist/payments/index.cjs +4 -0
- package/dist/payments/index.d.cts +187 -0
- package/dist/payments/index.d.ts +187 -0
- package/dist/payments/index.js +141 -0
- package/dist/payments.module-BdUq9ot_.d.cts +57 -0
- package/dist/payments.module-CQGv_VG7.d.ts +57 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.cts → plan-resolution-CwUy_OqC.d.cts} +14 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.ts → plan-resolution-CwUy_OqC.d.ts} +14 -0
- package/dist/{plan-versions.service-DChhbK6h.d.ts → plan-versions.service-C0N-Em2W.d.ts} +38 -64
- package/dist/{plan-versions.service-TPEtbN0r.d.cts → plan-versions.service-DARLh-qI.d.cts} +38 -64
- package/dist/platform/index.d.cts +52 -23
- package/dist/platform/index.d.ts +52 -23
- package/dist/platform/index.js +57 -36
- package/dist/promo/index.d.cts +23 -8
- package/dist/promo/index.d.ts +23 -8
- package/dist/promo/index.js +15 -7
- package/dist/{promo.module-DtvIycpt.d.cts → promo.module-CKkECmU8.d.cts} +9 -3
- package/dist/{promo.module-Z8h1PNX-.d.ts → promo.module-DuIJdK3U.d.ts} +9 -3
- package/dist/promo.service-BYBu6dy3.d.cts +215 -0
- package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
- package/dist/registration/index.d.cts +104 -47
- package/dist/registration/index.d.ts +104 -47
- package/dist/registration/index.js +15 -9
- package/dist/subscriber/index.cjs +4 -0
- package/dist/subscriber/index.d.cts +28 -0
- package/dist/subscriber/index.d.ts +28 -0
- package/dist/subscriber/index.js +16 -0
- package/dist/subscriber.service-5W6Gi-1B.d.cts +68 -0
- package/dist/subscriber.service-5W6Gi-1B.d.ts +68 -0
- package/dist/subscription-contract/index.d.cts +4 -2
- package/dist/subscription-contract/index.d.ts +4 -2
- package/dist/subscription-contract/index.js +13 -5
- package/dist/{subscription-contract.module-fDFdQ0jm.d.ts → subscription-contract.module-BfIRpc1o.d.ts} +8 -2
- package/dist/{subscription-contract.module-CEK8HS5-.d.cts → subscription-contract.module-DTMuUJYd.d.cts} +8 -2
- package/dist/{subscription-contract.service-hD87MKyg.d.cts → subscription-contract.service-CPZrTM9C.d.ts} +32 -3
- package/dist/{subscription-contract.service-hD87MKyg.d.ts → subscription-contract.service-CfNApLuP.d.cts} +32 -3
- package/dist/tenant-billing.controller-BLqbsONe.d.ts +812 -0
- package/dist/tenant-billing.controller-DKbAqTbM.d.cts +812 -0
- package/dist/tenant-billing.module-ClbjzocC.d.cts +364 -0
- package/dist/tenant-billing.module-DohgJJ2m.d.ts +364 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.cts +129 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.ts +129 -0
- package/dist/testing/index.d.cts +44 -17
- package/dist/testing/index.d.ts +44 -17
- package/dist/testing/index.js +189 -40
- package/package.json +24 -4
- package/dist/catalog.module-BDDO6iwq.d.cts +0 -103
- package/dist/catalog.module-BTnMsE6u.d.ts +0 -103
- package/dist/checkout-offer.module-CMtiQJTT.d.ts +0 -39
- package/dist/checkout-offer.module-CVpWrzbe.d.cts +0 -39
- package/dist/checkout-offer.service-BLOv2HOo.d.cts +0 -49
- package/dist/checkout-offer.service-BLOv2HOo.d.ts +0 -49
- package/dist/chunk-57V6ZTI6.js +0 -22
- package/dist/chunk-BSK6YBLI.js +0 -87
- package/dist/chunk-DQKCK7DX.js +0 -217
- package/dist/chunk-VXEYLNIB.js +0 -632
- package/dist/chunk-WUDYIPYH.js +0 -715
- package/dist/define-saasicat-DROBe-b1.d.ts +0 -69
- package/dist/define-saasicat-vGBCkFys.d.cts +0 -69
- package/dist/promo.service-BJbKAmw3.d.cts +0 -112
- package/dist/promo.service-BJbKAmw3.d.ts +0 -112
- package/dist/tenant-billing.controller-BQ67mlmI.d.ts +0 -438
- package/dist/tenant-billing.controller-DS8kB8a7.d.cts +0 -438
- package/dist/tenant-billing.module-BznC3XtW.d.cts +0 -299
- package/dist/tenant-billing.module-wUbsQSFG.d.ts +0 -299
- /package/dist/{chunk-2FR6ZL7R.js → chunk-BHZOH2DY.js} +0 -0
- /package/dist/{chunk-2SLTRKXC.js → chunk-DFOW3JVO.js} +0 -0
- /package/dist/{chunk-DBZGV3HC.js → chunk-DU56UKEH.js} +0 -0
- /package/dist/{chunk-OYNHOY45.js → chunk-GAUEVIAE.js} +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DiscoverySnapshotNotFoundError,
|
|
3
|
-
loadDiscoverySnapshotFromFile
|
|
4
|
-
} from "./chunk-74SCJ4HF.js";
|
|
5
1
|
import {
|
|
6
2
|
ADVISORY_STRICT_MODE_CODES,
|
|
7
3
|
validateBundleDraft,
|
|
8
4
|
validatePlanDraft
|
|
9
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CQ2ZZMTD.js";
|
|
6
|
+
import {
|
|
7
|
+
DiscoverySnapshotNotFoundError,
|
|
8
|
+
loadDiscoverySnapshotFromFile
|
|
9
|
+
} from "./chunk-74SCJ4HF.js";
|
|
10
10
|
import {
|
|
11
11
|
__name
|
|
12
12
|
} from "./chunk-SHUYVCID.js";
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {
|
|
2
|
+
asProvider
|
|
3
|
+
} from "./chunk-J7NJRK3K.js";
|
|
4
|
+
import {
|
|
5
|
+
__name
|
|
6
|
+
} from "./chunk-SHUYVCID.js";
|
|
7
|
+
|
|
8
|
+
// src/billing/plan-catalog-from-snapshot.ts
|
|
9
|
+
function buildPlanCatalogFromSnapshot(settings, snapshot) {
|
|
10
|
+
const liveByPlanKey = new Map(snapshot.livePlanVersions.map((v) => [
|
|
11
|
+
v.planId,
|
|
12
|
+
v
|
|
13
|
+
]));
|
|
14
|
+
const plans = snapshot.plans.filter((p) => p.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder || byKey(a.planKey, b.planKey)).map((stem) => {
|
|
15
|
+
const live = liveByPlanKey.get(stem.planKey);
|
|
16
|
+
if (!live) {
|
|
17
|
+
return {
|
|
18
|
+
id: stem.planKey,
|
|
19
|
+
name: stem.label,
|
|
20
|
+
tagline: stem.description ?? void 0,
|
|
21
|
+
marketed: false,
|
|
22
|
+
monthlyNet: null,
|
|
23
|
+
yearlyNet: null,
|
|
24
|
+
features: [],
|
|
25
|
+
quotas: {}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return planDefFromVersion({
|
|
29
|
+
id: stem.planKey,
|
|
30
|
+
name: stem.label,
|
|
31
|
+
tagline: stem.description ?? void 0
|
|
32
|
+
}, live);
|
|
33
|
+
});
|
|
34
|
+
const features = snapshot.featureEntries.filter((f) => f.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder || byKey(a.featureKey, b.featureKey)).map((row) => ({
|
|
35
|
+
key: row.featureKey,
|
|
36
|
+
label: row.label,
|
|
37
|
+
icon: row.icon ?? void 0,
|
|
38
|
+
tier: row.tier ?? void 0,
|
|
39
|
+
plannedOnly: row.plannedOnly
|
|
40
|
+
}));
|
|
41
|
+
const given = Object.fromEntries(Object.entries(settings).filter(([, value]) => value !== void 0));
|
|
42
|
+
return {
|
|
43
|
+
schemaVersion: 1,
|
|
44
|
+
...given,
|
|
45
|
+
features,
|
|
46
|
+
plans
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
__name(buildPlanCatalogFromSnapshot, "buildPlanCatalogFromSnapshot");
|
|
50
|
+
function planDefFromVersion(identity, version) {
|
|
51
|
+
return {
|
|
52
|
+
...identity,
|
|
53
|
+
marketed: version.marketed,
|
|
54
|
+
monthlyNet: priceOf(version.monthlyNet),
|
|
55
|
+
yearlyNet: priceOf(version.yearlyNet),
|
|
56
|
+
features: version.features,
|
|
57
|
+
quotas: version.quotas ?? {}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
__name(planDefFromVersion, "planDefFromVersion");
|
|
61
|
+
function priceOf(value) {
|
|
62
|
+
const price = Number.parseFloat(String(value));
|
|
63
|
+
return Number.isFinite(price) ? price : null;
|
|
64
|
+
}
|
|
65
|
+
__name(priceOf, "priceOf");
|
|
66
|
+
function byKey(a, b) {
|
|
67
|
+
if (a === b) return 0;
|
|
68
|
+
return a < b ? -1 : 1;
|
|
69
|
+
}
|
|
70
|
+
__name(byKey, "byKey");
|
|
71
|
+
|
|
72
|
+
// src/billing/plan-catalog-source.ts
|
|
73
|
+
function databasePlanCatalogSource(settings, sink) {
|
|
74
|
+
return {
|
|
75
|
+
origin: "database",
|
|
76
|
+
current: /* @__PURE__ */ __name(async () => buildPlanCatalogFromSnapshot(settings, await sink.loadSnapshot()), "current")
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
__name(databasePlanCatalogSource, "databasePlanCatalogSource");
|
|
80
|
+
function givenPlanCatalogSource(catalog) {
|
|
81
|
+
return {
|
|
82
|
+
origin: "given",
|
|
83
|
+
current: /* @__PURE__ */ __name(() => Promise.resolve(catalog), "current")
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
__name(givenPlanCatalogSource, "givenPlanCatalogSource");
|
|
87
|
+
|
|
88
|
+
// src/billing/plan-catalog.module.ts
|
|
89
|
+
import { Module } from "@nestjs/common";
|
|
90
|
+
import { planCatalogSettingsOf } from "@saasicat/core";
|
|
91
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
92
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
93
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
94
|
+
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;
|
|
95
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
96
|
+
}
|
|
97
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
98
|
+
var PLAN_CATALOG_SETTINGS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PlanCatalogSettings");
|
|
99
|
+
var PLAN_CATALOG_SOURCE_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PlanCatalogSource");
|
|
100
|
+
var PLAN_CATALOG_READ_SINK_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PLAN_CATALOG_READ_SINK");
|
|
101
|
+
var PlanCatalogModule = class _PlanCatalogModule {
|
|
102
|
+
static {
|
|
103
|
+
__name(this, "PlanCatalogModule");
|
|
104
|
+
}
|
|
105
|
+
static forRoot(options) {
|
|
106
|
+
const { sink: sinkSpec, imports, extraProviders, global, ...settings } = options;
|
|
107
|
+
return {
|
|
108
|
+
module: _PlanCatalogModule,
|
|
109
|
+
global: global ?? true,
|
|
110
|
+
imports: imports ?? [],
|
|
111
|
+
providers: [
|
|
112
|
+
...extraProviders ?? [],
|
|
113
|
+
asProvider(PLAN_CATALOG_READ_SINK_TOKEN, sinkSpec),
|
|
114
|
+
{
|
|
115
|
+
provide: PLAN_CATALOG_SETTINGS_TOKEN,
|
|
116
|
+
useValue: settings
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
provide: PLAN_CATALOG_SOURCE_TOKEN,
|
|
120
|
+
useFactory: /* @__PURE__ */ __name(async (sink) => {
|
|
121
|
+
const source = databasePlanCatalogSource(settings, sink);
|
|
122
|
+
await source.current();
|
|
123
|
+
return source;
|
|
124
|
+
}, "useFactory"),
|
|
125
|
+
inject: [
|
|
126
|
+
PLAN_CATALOG_READ_SINK_TOKEN
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
],
|
|
130
|
+
exports: [
|
|
131
|
+
PLAN_CATALOG_SETTINGS_TOKEN,
|
|
132
|
+
PLAN_CATALOG_SOURCE_TOKEN,
|
|
133
|
+
PLAN_CATALOG_READ_SINK_TOKEN
|
|
134
|
+
]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Tests / in-memory setup: takes the catalog object directly, without a
|
|
139
|
+
* sink/DB lookup. For unit tests + test bootstraps.
|
|
140
|
+
*/
|
|
141
|
+
static forRootWithCatalog(catalog, opts = {}) {
|
|
142
|
+
return {
|
|
143
|
+
module: _PlanCatalogModule,
|
|
144
|
+
providers: [
|
|
145
|
+
{
|
|
146
|
+
provide: PLAN_CATALOG_SETTINGS_TOKEN,
|
|
147
|
+
useValue: planCatalogSettingsOf(catalog)
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
provide: PLAN_CATALOG_SOURCE_TOKEN,
|
|
151
|
+
useValue: givenPlanCatalogSource(catalog)
|
|
152
|
+
}
|
|
153
|
+
],
|
|
154
|
+
exports: [
|
|
155
|
+
PLAN_CATALOG_SETTINGS_TOKEN,
|
|
156
|
+
PLAN_CATALOG_SOURCE_TOKEN
|
|
157
|
+
],
|
|
158
|
+
global: opts.global ?? true
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
PlanCatalogModule = _ts_decorate([
|
|
163
|
+
Module({})
|
|
164
|
+
], PlanCatalogModule);
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
buildPlanCatalogFromSnapshot,
|
|
168
|
+
planDefFromVersion,
|
|
169
|
+
givenPlanCatalogSource,
|
|
170
|
+
PLAN_CATALOG_SETTINGS_TOKEN,
|
|
171
|
+
PLAN_CATALOG_SOURCE_TOKEN,
|
|
172
|
+
PLAN_CATALOG_READ_SINK_TOKEN,
|
|
173
|
+
PlanCatalogModule
|
|
174
|
+
};
|
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PLAN_CATALOG_TOKEN,
|
|
3
|
-
isFeaturePlannedOnly
|
|
4
|
-
} from "./chunk-DQKCK7DX.js";
|
|
5
|
-
import {
|
|
6
|
-
SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
|
|
7
|
-
} from "./chunk-VBRYPU7V.js";
|
|
8
|
-
import {
|
|
9
|
-
codedError
|
|
10
|
-
} from "./chunk-57V6ZTI6.js";
|
|
11
1
|
import {
|
|
12
2
|
ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
|
|
13
3
|
PLAN_VERSION_REPOSITORY_TOKEN,
|
|
14
4
|
SUBSCRIPTION_REPOSITORY_TOKEN,
|
|
15
5
|
TRANSACTION_RUNNER_TOKEN
|
|
16
6
|
} from "./chunk-OUDSQ3PU.js";
|
|
7
|
+
import {
|
|
8
|
+
isFeaturePlannedOnly
|
|
9
|
+
} from "./chunk-TBBZWZQT.js";
|
|
10
|
+
import {
|
|
11
|
+
SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
|
|
12
|
+
} from "./chunk-VBRYPU7V.js";
|
|
17
13
|
import {
|
|
18
14
|
BUNDLE_REPOSITORY_TOKEN
|
|
19
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-3QVGA6JX.js";
|
|
20
16
|
import {
|
|
21
17
|
DISCOVERY_SNAPSHOT_TOKEN
|
|
22
18
|
} from "./chunk-QUZU2M33.js";
|
|
19
|
+
import {
|
|
20
|
+
PLAN_CATALOG_SOURCE_TOKEN
|
|
21
|
+
} from "./chunk-FEQZCWUL.js";
|
|
22
|
+
import {
|
|
23
|
+
codedError
|
|
24
|
+
} from "./chunk-J7NJRK3K.js";
|
|
23
25
|
import {
|
|
24
26
|
__name
|
|
25
27
|
} from "./chunk-SHUYVCID.js";
|
|
@@ -28,6 +30,17 @@ import {
|
|
|
28
30
|
var SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SubscriptionBundleRepository");
|
|
29
31
|
var SUBSCRIPTION_BUNDLE_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SubscriptionBundleConfig");
|
|
30
32
|
|
|
33
|
+
// src/entitlement/landed-cancellation.ts
|
|
34
|
+
function cancellationHasLanded(sub, now) {
|
|
35
|
+
const landsAt = cancellationLandsAt(sub);
|
|
36
|
+
return landsAt !== null && landsAt <= now;
|
|
37
|
+
}
|
|
38
|
+
__name(cancellationHasLanded, "cancellationHasLanded");
|
|
39
|
+
function cancellationLandsAt(sub) {
|
|
40
|
+
return sub.canceledEffectiveAt ?? sub.canceledAt;
|
|
41
|
+
}
|
|
42
|
+
__name(cancellationLandsAt, "cancellationLandsAt");
|
|
43
|
+
|
|
31
44
|
// src/entitlement/aggregation.ts
|
|
32
45
|
function filterActiveSubscriptionBundles(bundles, now) {
|
|
33
46
|
return bundles.filter((b) => b.canceledEffectiveAt === null || b.canceledEffectiveAt > now);
|
|
@@ -332,7 +345,7 @@ var EntitlementService = class {
|
|
|
332
345
|
static {
|
|
333
346
|
__name(this, "EntitlementService");
|
|
334
347
|
}
|
|
335
|
-
|
|
348
|
+
catalogs;
|
|
336
349
|
subscriptions;
|
|
337
350
|
planVersions;
|
|
338
351
|
tx;
|
|
@@ -350,8 +363,8 @@ var EntitlementService = class {
|
|
|
350
363
|
// #39 — replaces alias index, built lazily from the boot-static snapshot;
|
|
351
364
|
// throws on replaces cycles (a clear error instead of an infinite loop).
|
|
352
365
|
replacedByIndex = null;
|
|
353
|
-
constructor(
|
|
354
|
-
this.
|
|
366
|
+
constructor(catalogs, subscriptions, planVersions, tx, resolutionConfig = null, subscriptionBundles = null, bundles = null, subscriptionContracts = null, discoverySnapshot = null) {
|
|
367
|
+
this.catalogs = catalogs;
|
|
355
368
|
this.subscriptions = subscriptions;
|
|
356
369
|
this.planVersions = planVersions;
|
|
357
370
|
this.tx = tx;
|
|
@@ -364,9 +377,19 @@ var EntitlementService = class {
|
|
|
364
377
|
// ---------------------------------------------------------------------
|
|
365
378
|
// Read path — for FeatureGuard, sidebar hooks, GET /billing/entitlement
|
|
366
379
|
// ---------------------------------------------------------------------
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
380
|
+
/**
|
|
381
|
+
* The tenant's effective limits, answered from the cache for up to
|
|
382
|
+
* `CACHE_TTL_MS`. A caller that has already read the catalogue for its own
|
|
383
|
+
* operation passes it as `catalog`: the answer is then computed against
|
|
384
|
+
* that reading and kept out of the cache in both directions — not taken
|
|
385
|
+
* from it, and not written to it, since a reading the caller holds may be
|
|
386
|
+
* older than the one everybody else should be answered from.
|
|
387
|
+
*/
|
|
388
|
+
async computeLimits(tenantId, now = /* @__PURE__ */ new Date(), catalog) {
|
|
389
|
+
if (!catalog) {
|
|
390
|
+
const cached = this.readCache(tenantId, now.getTime());
|
|
391
|
+
if (cached) return cached;
|
|
392
|
+
}
|
|
370
393
|
const sub = await this.subscriptions.findByTenantId(tenantId);
|
|
371
394
|
if (!sub) {
|
|
372
395
|
throw new NotFoundException({
|
|
@@ -377,8 +400,9 @@ var EntitlementService = class {
|
|
|
377
400
|
}
|
|
378
401
|
});
|
|
379
402
|
}
|
|
380
|
-
|
|
381
|
-
this.
|
|
403
|
+
if (catalog) return this.limitsFor(sub, now, catalog);
|
|
404
|
+
const limits = await this.limitsFor(sub, now, await this.catalogs.current());
|
|
405
|
+
this.writeCache(tenantId, limits, now.getTime(), cancellationLandsAt(sub));
|
|
382
406
|
return limits;
|
|
383
407
|
}
|
|
384
408
|
/**
|
|
@@ -399,27 +423,75 @@ var EntitlementService = class {
|
|
|
399
423
|
* have a `SubscriptionRecord` (e.g. within a transaction).
|
|
400
424
|
*/
|
|
401
425
|
async deriveLimits(sub, now, tx) {
|
|
426
|
+
return this.limitsFor(sub, now, await this.catalogs.current(), tx);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* `deriveLimits` against a catalogue already read. The catalogue decides
|
|
430
|
+
* which features are `plannedOnly`, and it is read outside any transaction
|
|
431
|
+
* a caller holds, so that checking a limit does not wait for a second
|
|
432
|
+
* connection while the subscription row is locked.
|
|
433
|
+
*/
|
|
434
|
+
async limitsFor(sub, now, catalog, tx) {
|
|
435
|
+
if (cancellationHasLanded(sub, now)) {
|
|
436
|
+
const floor = this.resolutionConfig?.canceledEntitlementPlan;
|
|
437
|
+
if (floor === void 0) {
|
|
438
|
+
return {
|
|
439
|
+
plan: sub.plan,
|
|
440
|
+
quotas: {},
|
|
441
|
+
features: /* @__PURE__ */ new Set()
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
const floorVersion = await this.findActivePlanVersionOrFallback(floor, now, tx);
|
|
445
|
+
return this.asGrantable(catalog, aggregateLimits({
|
|
446
|
+
plan: floor,
|
|
447
|
+
planVersion: floorVersion,
|
|
448
|
+
subscriptionBundles: [],
|
|
449
|
+
customLimits: null
|
|
450
|
+
}, catalog, now));
|
|
451
|
+
}
|
|
402
452
|
const contract = await this.findActiveContract(sub.tenantId, now, tx);
|
|
403
453
|
if (contract) {
|
|
404
454
|
const bundles = await this.loadSubscriptionBundleSnapshots(sub.id, now, tx);
|
|
405
|
-
return this.
|
|
455
|
+
return this.asGrantable(catalog, mergeSubscriptionBundlesIntoLimits(contractLimits(contract), bundles, contractBundleVersionIds(contract), catalog, now));
|
|
406
456
|
}
|
|
407
457
|
const effectivePlan = resolveEntitlementPlan(sub, this.resolutionConfig ?? {}, now);
|
|
408
458
|
const planVersion = effectivePlan === sub.plan ? sub.planVersion : await this.findActivePlanVersionOrFallback(effectivePlan, now, tx);
|
|
409
459
|
const subscriptionBundles = await this.loadSubscriptionBundleSnapshots(sub.id, now, tx);
|
|
410
|
-
return this.
|
|
460
|
+
return this.asGrantable(catalog, aggregateLimits({
|
|
411
461
|
plan: effectivePlan,
|
|
412
462
|
planVersion,
|
|
413
463
|
subscriptionBundles,
|
|
414
464
|
customLimits: sub.customLimits ?? null
|
|
415
|
-
},
|
|
465
|
+
}, catalog, now));
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* The last thing every path does, so that no path can skip it.
|
|
469
|
+
*
|
|
470
|
+
* Two steps, in this order. Grandfathering (#39): granted old feature keys
|
|
471
|
+
* transitively grant their successors from the `replaces` chains of the
|
|
472
|
+
* discovery snapshot — a no-op without a snapshot or without replaces
|
|
473
|
+
* declarations. Then `plannedOnly` features come out.
|
|
474
|
+
*
|
|
475
|
+
* The filter sits here rather than in each branch because it used to sit
|
|
476
|
+
* in each branch and one of them did not have it: limits read from a
|
|
477
|
+
* frozen contract were handed over as they stood, so a feature the
|
|
478
|
+
* catalogue says has no code behind it was granted to whoever had signed
|
|
479
|
+
* before it was marked. A successor pulled in by an alias could arrive the
|
|
480
|
+
* same way. `SC-ENTL-003` says "never … wherever it comes from", and one
|
|
481
|
+
* place is what makes that answerable.
|
|
482
|
+
*/
|
|
483
|
+
asGrantable(catalog, limits) {
|
|
484
|
+
return {
|
|
485
|
+
...limits,
|
|
486
|
+
features: filterPlannedOnlyFeatures(this.replaceFeatureAliases(limits).features, catalog)
|
|
487
|
+
};
|
|
416
488
|
}
|
|
417
489
|
/**
|
|
418
490
|
* Grandfathering (#39): granted old feature keys transitively grant
|
|
419
491
|
* their successors from the `replaces` chains of the discovery snapshot.
|
|
420
492
|
* A no-op without a snapshot or without replaces declarations.
|
|
421
493
|
*/
|
|
422
|
-
|
|
494
|
+
replaceFeatureAliases(limits) {
|
|
423
495
|
if (!this.discoverySnapshot) return limits;
|
|
424
496
|
if (!this.replacedByIndex) {
|
|
425
497
|
this.replacedByIndex = buildReplacedByIndex(this.discoverySnapshot.features);
|
|
@@ -467,6 +539,7 @@ var EntitlementService = class {
|
|
|
467
539
|
async enforceLimit(input) {
|
|
468
540
|
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
469
541
|
const delta = input.delta ?? 1;
|
|
542
|
+
const catalog = await this.catalogs.current();
|
|
470
543
|
return this.tx.run(async (tx) => {
|
|
471
544
|
const sub = await this.subscriptions.findByTenantIdLocked(input.tenantId, tx);
|
|
472
545
|
if (!sub) {
|
|
@@ -478,12 +551,11 @@ var EntitlementService = class {
|
|
|
478
551
|
}
|
|
479
552
|
});
|
|
480
553
|
}
|
|
481
|
-
const limits = await this.
|
|
554
|
+
const limits = await this.limitsFor(sub, now, catalog, tx);
|
|
482
555
|
const max = limits.quotas[input.dimension];
|
|
483
556
|
if (max === void 0) {
|
|
484
557
|
throw new InternalServerErrorException(codedError(BILLING_ERROR_CODES.QUOTA_DIMENSION_UNKNOWN, {
|
|
485
|
-
dimension: input.dimension
|
|
486
|
-
projectKey: this.catalog.projectKey
|
|
558
|
+
dimension: input.dimension
|
|
487
559
|
}));
|
|
488
560
|
}
|
|
489
561
|
const used = await input.currentUsage(tx, input.tenantId);
|
|
@@ -520,7 +592,7 @@ var EntitlementService = class {
|
|
|
520
592
|
readCache(tenantId, nowMs) {
|
|
521
593
|
const entry = this.cache.get(tenantId);
|
|
522
594
|
if (!entry) return null;
|
|
523
|
-
if (entry.expiresAt
|
|
595
|
+
if (entry.expiresAt <= nowMs) {
|
|
524
596
|
this.cache.delete(tenantId);
|
|
525
597
|
return null;
|
|
526
598
|
}
|
|
@@ -528,11 +600,13 @@ var EntitlementService = class {
|
|
|
528
600
|
this.cache.set(tenantId, entry);
|
|
529
601
|
return entry.value;
|
|
530
602
|
}
|
|
531
|
-
writeCache(tenantId, value, nowMs) {
|
|
603
|
+
writeCache(tenantId, value, nowMs, expiresNotAfter = null) {
|
|
532
604
|
if (this.cache.has(tenantId)) this.cache.delete(tenantId);
|
|
605
|
+
const ttlExpiry = nowMs + CACHE_TTL_MS;
|
|
606
|
+
const boundary = expiresNotAfter?.getTime() ?? Infinity;
|
|
533
607
|
this.cache.set(tenantId, {
|
|
534
608
|
value,
|
|
535
|
-
expiresAt: nowMs
|
|
609
|
+
expiresAt: boundary > nowMs ? Math.min(ttlExpiry, boundary) : ttlExpiry
|
|
536
610
|
});
|
|
537
611
|
while (this.cache.size > CACHE_MAX_ENTRIES) {
|
|
538
612
|
const oldest = this.cache.keys().next().value;
|
|
@@ -543,7 +617,7 @@ var EntitlementService = class {
|
|
|
543
617
|
};
|
|
544
618
|
EntitlementService = _ts_decorate([
|
|
545
619
|
Injectable(),
|
|
546
|
-
_ts_param(0, Inject(
|
|
620
|
+
_ts_param(0, Inject(PLAN_CATALOG_SOURCE_TOKEN)),
|
|
547
621
|
_ts_param(1, Inject(SUBSCRIPTION_REPOSITORY_TOKEN)),
|
|
548
622
|
_ts_param(2, Inject(PLAN_VERSION_REPOSITORY_TOKEN)),
|
|
549
623
|
_ts_param(3, Inject(TRANSACTION_RUNNER_TOKEN)),
|
|
@@ -559,7 +633,7 @@ EntitlementService = _ts_decorate([
|
|
|
559
633
|
_ts_param(8, Inject(DISCOVERY_SNAPSHOT_TOKEN)),
|
|
560
634
|
_ts_metadata("design:type", Function),
|
|
561
635
|
_ts_metadata("design:paramtypes", [
|
|
562
|
-
typeof
|
|
636
|
+
typeof PlanCatalogSource === "undefined" ? Object : PlanCatalogSource,
|
|
563
637
|
typeof SubscriptionRepository === "undefined" ? Object : SubscriptionRepository,
|
|
564
638
|
typeof PlanVersionRepository === "undefined" ? Object : PlanVersionRepository,
|
|
565
639
|
typeof TransactionRunner === "undefined" ? Object : TransactionRunner,
|
|
@@ -574,6 +648,8 @@ EntitlementService = _ts_decorate([
|
|
|
574
648
|
export {
|
|
575
649
|
SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN,
|
|
576
650
|
SUBSCRIPTION_BUNDLE_CONFIG_TOKEN,
|
|
651
|
+
cancellationHasLanded,
|
|
652
|
+
cancellationLandsAt,
|
|
577
653
|
filterActiveSubscriptionBundles,
|
|
578
654
|
aggregateSubscriptionBundleQuotas,
|
|
579
655
|
collectSubscriptionBundleFeatures,
|
|
File without changes
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
// src/billing/version-diff.ts
|
|
2
|
-
import { classifyBundleVersionDiff, classifyPlanDiff } from "@saasicat/core";
|
|
3
|
-
|
|
4
1
|
// src/billing/tenant-billing.tokens.ts
|
|
5
2
|
var TENANT_AUTH_GUARDS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/TenantAuthGuards");
|
|
3
|
+
var BILLING_PERMISSION_GUARDS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/BillingPermissionGuards");
|
|
6
4
|
var TENANT_ID_RESOLVER_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/TenantIdResolver");
|
|
7
5
|
var USER_ID_RESOLVER_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/UserIdResolver");
|
|
8
6
|
var USER_EMAIL_RESOLVER_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/UserEmailResolver");
|
|
@@ -12,11 +10,11 @@ var USAGE_SNAPSHOT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/UsageS
|
|
|
12
10
|
var SUBSCRIPTION_WRITE_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/TenantSubscriptionWritePort");
|
|
13
11
|
var TRIAL_PROJECTION_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/TrialProjectionPort");
|
|
14
12
|
var PENDING_PLAN_QUERY_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PendingPlanQueryPort");
|
|
13
|
+
var CANCELLATION_NOTICE_DAYS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/CancellationNoticeDays");
|
|
15
14
|
|
|
16
15
|
export {
|
|
17
|
-
classifyBundleVersionDiff,
|
|
18
|
-
classifyPlanDiff,
|
|
19
16
|
TENANT_AUTH_GUARDS_TOKEN,
|
|
17
|
+
BILLING_PERMISSION_GUARDS_TOKEN,
|
|
20
18
|
TENANT_ID_RESOLVER_TOKEN,
|
|
21
19
|
USER_ID_RESOLVER_TOKEN,
|
|
22
20
|
USER_EMAIL_RESOLVER_TOKEN,
|
|
@@ -25,5 +23,6 @@ export {
|
|
|
25
23
|
USAGE_SNAPSHOT_PORT_TOKEN,
|
|
26
24
|
SUBSCRIPTION_WRITE_PORT_TOKEN,
|
|
27
25
|
TRIAL_PROJECTION_PORT_TOKEN,
|
|
28
|
-
PENDING_PLAN_QUERY_PORT_TOKEN
|
|
26
|
+
PENDING_PLAN_QUERY_PORT_TOKEN,
|
|
27
|
+
CANCELLATION_NOTICE_DAYS_TOKEN
|
|
29
28
|
};
|
|
@@ -19,6 +19,22 @@ function asProvider(token, impl) {
|
|
|
19
19
|
}
|
|
20
20
|
__name(asProvider, "asProvider");
|
|
21
21
|
|
|
22
|
+
// src/errors/coded-error.ts
|
|
23
|
+
import { ERROR_MESSAGES_EN, formatErrorMessage } from "@saasicat/core";
|
|
24
|
+
function codedError(code, params) {
|
|
25
|
+
const message = formatErrorMessage(ERROR_MESSAGES_EN[code], params ?? {});
|
|
26
|
+
return params ? {
|
|
27
|
+
code,
|
|
28
|
+
message,
|
|
29
|
+
params
|
|
30
|
+
} : {
|
|
31
|
+
code,
|
|
32
|
+
message
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
__name(codedError, "codedError");
|
|
36
|
+
|
|
22
37
|
export {
|
|
23
|
-
asProvider
|
|
38
|
+
asProvider,
|
|
39
|
+
codedError
|
|
24
40
|
};
|