@saasicat/nest 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +53 -0
  3. package/dist/admin/index.cjs +1283 -0
  4. package/dist/admin/index.d.cts +225 -0
  5. package/dist/admin/index.d.ts +225 -0
  6. package/dist/admin/index.js +58 -0
  7. package/dist/admin-audit.service-9IqXMlZm.d.cts +30 -0
  8. package/dist/admin-audit.service-9IqXMlZm.d.ts +30 -0
  9. package/dist/admin-manifest.config-DyrQNT7M.d.cts +10 -0
  10. package/dist/admin-manifest.config-DyrQNT7M.d.ts +10 -0
  11. package/dist/aggregation-CJ3qQf92.d.cts +288 -0
  12. package/dist/aggregation-Dvz9e8X2.d.ts +288 -0
  13. package/dist/billing/index.cjs +5205 -0
  14. package/dist/billing/index.d.cts +1326 -0
  15. package/dist/billing/index.d.ts +1326 -0
  16. package/dist/billing/index.js +197 -0
  17. package/dist/catalog/index.cjs +6449 -0
  18. package/dist/catalog/index.d.cts +1073 -0
  19. package/dist/catalog/index.d.ts +1073 -0
  20. package/dist/catalog/index.js +158 -0
  21. package/dist/checkout-offer/index.cjs +807 -0
  22. package/dist/checkout-offer/index.d.cts +120 -0
  23. package/dist/checkout-offer/index.d.ts +120 -0
  24. package/dist/checkout-offer/index.js +20 -0
  25. package/dist/chunk-2FR6ZL7R.js +0 -0
  26. package/dist/chunk-2PNX2QL2.js +347 -0
  27. package/dist/chunk-2VNZYRDV.js +326 -0
  28. package/dist/chunk-7MLGHWKO.js +12 -0
  29. package/dist/chunk-ABB2CRAS.js +31 -0
  30. package/dist/chunk-AJ3EPELS.js +157 -0
  31. package/dist/chunk-E56W4U2P.js +90 -0
  32. package/dist/chunk-HRU3CP57.js +71 -0
  33. package/dist/chunk-L4SUWH5B.js +14 -0
  34. package/dist/chunk-M6CZQYPY.js +314 -0
  35. package/dist/chunk-MDIZUVIK.js +87 -0
  36. package/dist/chunk-MV6LW5UB.js +217 -0
  37. package/dist/chunk-NYLON2VC.js +6 -0
  38. package/dist/chunk-P6MYZMXQ.js +951 -0
  39. package/dist/chunk-Q53N43LQ.js +13 -0
  40. package/dist/chunk-QJVPRD3R.js +573 -0
  41. package/dist/chunk-RIGLQ4BV.js +3367 -0
  42. package/dist/chunk-SABTXESR.js +24 -0
  43. package/dist/chunk-SHUYVCID.js +6 -0
  44. package/dist/chunk-SYEVGPFC.js +686 -0
  45. package/dist/chunk-V7HUZ6UQ.js +5923 -0
  46. package/dist/chunk-WPUVJRIN.js +29 -0
  47. package/dist/chunk-XCZ3TTFK.js +22 -0
  48. package/dist/chunk-XSGU2Y2O.js +1611 -0
  49. package/dist/chunk-YNEW3L63.js +190 -0
  50. package/dist/chunk-YXGHPILG.js +614 -0
  51. package/dist/chunk-ZL4LLE2Z.js +465 -0
  52. package/dist/di-CcNeq9v-.d.cts +16 -0
  53. package/dist/di-CcNeq9v-.d.ts +16 -0
  54. package/dist/discovery/index.cjs +588 -0
  55. package/dist/discovery/index.d.cts +299 -0
  56. package/dist/discovery/index.d.ts +299 -0
  57. package/dist/discovery/index.js +49 -0
  58. package/dist/discovery.scanner-CUYLKlYT.d.cts +59 -0
  59. package/dist/discovery.scanner-CUYLKlYT.d.ts +59 -0
  60. package/dist/entitlement/index.cjs +805 -0
  61. package/dist/entitlement/index.d.cts +60 -0
  62. package/dist/entitlement/index.d.ts +60 -0
  63. package/dist/entitlement/index.js +57 -0
  64. package/dist/index.cjs +15918 -0
  65. package/dist/index.d.cts +85 -0
  66. package/dist/index.d.ts +85 -0
  67. package/dist/index.js +956 -0
  68. package/dist/plan-resolution-CFCoUkrE.d.cts +41 -0
  69. package/dist/plan-resolution-CFCoUkrE.d.ts +41 -0
  70. package/dist/platform/index.cjs +2772 -0
  71. package/dist/platform/index.d.cts +118 -0
  72. package/dist/platform/index.d.ts +118 -0
  73. package/dist/platform/index.js +37 -0
  74. package/dist/promo/index.cjs +995 -0
  75. package/dist/promo/index.d.cts +121 -0
  76. package/dist/promo/index.d.ts +121 -0
  77. package/dist/promo/index.js +58 -0
  78. package/dist/registration/index.cjs +1687 -0
  79. package/dist/registration/index.d.cts +444 -0
  80. package/dist/registration/index.d.ts +444 -0
  81. package/dist/registration/index.js +82 -0
  82. package/dist/saas-platform.module-CgZ2omgg.d.cts +180 -0
  83. package/dist/saas-platform.module-DT9TnCzk.d.ts +180 -0
  84. package/dist/service-DX8KbGXl.d.cts +109 -0
  85. package/dist/service-DX8KbGXl.d.ts +109 -0
  86. package/dist/subscription-contract/index.cjs +471 -0
  87. package/dist/subscription-contract/index.d.cts +17 -0
  88. package/dist/subscription-contract/index.d.ts +17 -0
  89. package/dist/subscription-contract/index.js +21 -0
  90. package/dist/subscription-contract.service--cm47ZJJ.d.cts +39 -0
  91. package/dist/subscription-contract.service--cm47ZJJ.d.ts +39 -0
  92. package/dist/testing/index.cjs +3942 -0
  93. package/dist/testing/index.d.cts +279 -0
  94. package/dist/testing/index.d.ts +279 -0
  95. package/dist/testing/index.js +1195 -0
  96. package/package.json +185 -0
@@ -0,0 +1,805 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/entitlement/index.ts
22
+ var entitlement_exports = {};
23
+ __export(entitlement_exports, {
24
+ ENTITLEMENT_RESOLUTION_CONFIG_TOKEN: () => ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
25
+ EntitlementModule: () => EntitlementModule,
26
+ EntitlementService: () => EntitlementService,
27
+ LimitExceededError: () => LimitExceededError,
28
+ PLAN_VERSION_REPOSITORY_TOKEN: () => PLAN_VERSION_REPOSITORY_TOKEN,
29
+ SUBSCRIPTION_REPOSITORY_TOKEN: () => SUBSCRIPTION_REPOSITORY_TOKEN,
30
+ TRANSACTION_RUNNER_TOKEN: () => TRANSACTION_RUNNER_TOKEN,
31
+ aggregateBusinessTypeQuotas: () => aggregateBusinessTypeQuotas,
32
+ aggregateContractLineItemEntitlements: () => aggregateContractLineItemEntitlements,
33
+ aggregateLimits: () => aggregateLimits,
34
+ aggregateSubscriptionBundleQuotas: () => aggregateSubscriptionBundleQuotas,
35
+ applyCustomLimits: () => applyCustomLimits,
36
+ buildReplacedByIndex: () => buildReplacedByIndex,
37
+ collectBusinessTypeFeatures: () => collectBusinessTypeFeatures,
38
+ collectSubscriptionBundleFeatures: () => collectSubscriptionBundleFeatures,
39
+ expandReplacedFeatures: () => expandReplacedFeatures,
40
+ filterActiveSubscriptionBundles: () => filterActiveSubscriptionBundles,
41
+ filterPlannedOnlyFeatures: () => filterPlannedOnlyFeatures,
42
+ hasAnyFeature: () => hasAnyFeature,
43
+ hasFeature: () => hasFeature,
44
+ resolveEntitlementPlan: () => resolveEntitlementPlan,
45
+ toEffectiveLimitsSnapshot: () => toEffectiveLimitsSnapshot
46
+ });
47
+ module.exports = __toCommonJS(entitlement_exports);
48
+
49
+ // src/billing/plan-helpers.ts
50
+ function isFeaturePlannedOnly(catalog, featureKey) {
51
+ const def = (catalog.features ?? []).find((f) => f.key === featureKey);
52
+ return def?.plannedOnly === true;
53
+ }
54
+ __name(isFeaturePlannedOnly, "isFeaturePlannedOnly");
55
+
56
+ // src/entitlement/aggregation.ts
57
+ function aggregateBusinessTypeQuotas(snapshot) {
58
+ const sums = {};
59
+ for (const bundle of snapshot.bundles) {
60
+ for (const [key, value] of Object.entries(bundle.quotas)) {
61
+ if (sums[key] === -1 || value === -1) {
62
+ sums[key] = -1;
63
+ } else {
64
+ sums[key] = (sums[key] ?? 0) + value;
65
+ }
66
+ }
67
+ }
68
+ for (const [key, value] of Object.entries(snapshot.quotaOverrides)) {
69
+ if (value !== void 0) {
70
+ sums[key] = value;
71
+ }
72
+ }
73
+ return sums;
74
+ }
75
+ __name(aggregateBusinessTypeQuotas, "aggregateBusinessTypeQuotas");
76
+ function filterActiveSubscriptionBundles(bundles, now) {
77
+ return bundles.filter((b) => b.canceledEffectiveAt === null || b.canceledEffectiveAt > now);
78
+ }
79
+ __name(filterActiveSubscriptionBundles, "filterActiveSubscriptionBundles");
80
+ function aggregateSubscriptionBundleQuotas(bundles) {
81
+ const sums = {};
82
+ for (const bundle of bundles) {
83
+ for (const [key, value] of Object.entries(bundle.quotas)) {
84
+ if (sums[key] === -1 || value === -1) {
85
+ sums[key] = -1;
86
+ } else {
87
+ sums[key] = (sums[key] ?? 0) + value;
88
+ }
89
+ }
90
+ }
91
+ return sums;
92
+ }
93
+ __name(aggregateSubscriptionBundleQuotas, "aggregateSubscriptionBundleQuotas");
94
+ function collectSubscriptionBundleFeatures(bundles) {
95
+ const set = /* @__PURE__ */ new Set();
96
+ for (const bundle of bundles) {
97
+ for (const f of bundle.features) set.add(f);
98
+ }
99
+ return Array.from(set);
100
+ }
101
+ __name(collectSubscriptionBundleFeatures, "collectSubscriptionBundleFeatures");
102
+ function aggregateContractLineItemEntitlements(lineItems, fallbackPlan = "UNKNOWN") {
103
+ const planItem = lineItems.find((item) => item.kind === "plan");
104
+ const plan = planItem?.sourceKey ?? fallbackPlan;
105
+ const quotas = {};
106
+ const features = /* @__PURE__ */ new Set();
107
+ for (const item of lineItems) {
108
+ for (const feature of item.featuresSnapshot) {
109
+ features.add(feature);
110
+ }
111
+ for (const [key, value] of Object.entries(item.quotaEffectsSnapshot)) {
112
+ if (quotas[key] === -1 || value === -1) {
113
+ quotas[key] = -1;
114
+ } else {
115
+ quotas[key] = (quotas[key] ?? 0) + value;
116
+ }
117
+ }
118
+ }
119
+ return {
120
+ plan,
121
+ quotas,
122
+ features
123
+ };
124
+ }
125
+ __name(aggregateContractLineItemEntitlements, "aggregateContractLineItemEntitlements");
126
+ function collectBusinessTypeFeatures(snapshot) {
127
+ const set = /* @__PURE__ */ new Set();
128
+ for (const bundle of snapshot.bundles) {
129
+ for (const f of bundle.features) {
130
+ set.add(f);
131
+ }
132
+ }
133
+ return Array.from(set);
134
+ }
135
+ __name(collectBusinessTypeFeatures, "collectBusinessTypeFeatures");
136
+ function filterPlannedOnlyFeatures(features, catalog) {
137
+ const out = /* @__PURE__ */ new Set();
138
+ for (const f of features) {
139
+ if (!isFeaturePlannedOnly(catalog, f)) {
140
+ out.add(f);
141
+ }
142
+ }
143
+ return out;
144
+ }
145
+ __name(filterPlannedOnlyFeatures, "filterPlannedOnlyFeatures");
146
+ function applyCustomLimits(base, custom) {
147
+ if (!custom) return base;
148
+ const quotas = {
149
+ ...base.quotas
150
+ };
151
+ if (custom.quotas) {
152
+ for (const [k, v] of Object.entries(custom.quotas)) {
153
+ quotas[k] = v;
154
+ }
155
+ }
156
+ const features = new Set(base.features);
157
+ if (custom.features) {
158
+ for (const f of custom.features) features.add(f);
159
+ }
160
+ return {
161
+ plan: base.plan,
162
+ quotas,
163
+ features
164
+ };
165
+ }
166
+ __name(applyCustomLimits, "applyCustomLimits");
167
+ function aggregateLimits(input, catalog, now) {
168
+ const businessTypeQuotas = input.businessTypeVersion ? aggregateBusinessTypeQuotas(input.businessTypeVersion) : {};
169
+ const businessTypeFeatures = input.businessTypeVersion ? collectBusinessTypeFeatures(input.businessTypeVersion) : [];
170
+ const activeBundles = filterActiveSubscriptionBundles(input.subscriptionBundles ?? [], now);
171
+ const subBundleQuotas = aggregateSubscriptionBundleQuotas(activeBundles);
172
+ const subBundleFeatures = collectSubscriptionBundleFeatures(activeBundles);
173
+ const quotas = {};
174
+ for (const [k, v] of Object.entries(input.planVersion.quotas)) {
175
+ quotas[k] = v;
176
+ }
177
+ for (const [k, v] of Object.entries(businessTypeQuotas)) {
178
+ if (quotas[k] === -1 || v === -1) {
179
+ quotas[k] = -1;
180
+ } else {
181
+ quotas[k] = (quotas[k] ?? 0) + v;
182
+ }
183
+ }
184
+ for (const [k, v] of Object.entries(subBundleQuotas)) {
185
+ if (quotas[k] === -1 || v === -1) {
186
+ quotas[k] = -1;
187
+ } else {
188
+ quotas[k] = (quotas[k] ?? 0) + v;
189
+ }
190
+ }
191
+ const features = /* @__PURE__ */ new Set([
192
+ ...input.planVersion.features,
193
+ ...businessTypeFeatures,
194
+ ...subBundleFeatures
195
+ ]);
196
+ const withCustom = applyCustomLimits({
197
+ plan: input.plan,
198
+ quotas,
199
+ features
200
+ }, input.customLimits);
201
+ const filteredFeatures = filterPlannedOnlyFeatures(withCustom.features, catalog);
202
+ return {
203
+ plan: withCustom.plan,
204
+ quotas: withCustom.quotas,
205
+ features: filteredFeatures
206
+ };
207
+ }
208
+ __name(aggregateLimits, "aggregateLimits");
209
+ function hasFeature(limits, feature) {
210
+ return limits.features.has(feature);
211
+ }
212
+ __name(hasFeature, "hasFeature");
213
+ function hasAnyFeature(limits, features) {
214
+ return features.some((f) => limits.features.has(f));
215
+ }
216
+ __name(hasAnyFeature, "hasAnyFeature");
217
+ function toEffectiveLimitsSnapshot(limits) {
218
+ return {
219
+ plan: limits.plan,
220
+ quotas: {
221
+ ...limits.quotas
222
+ },
223
+ features: Array.from(limits.features).sort()
224
+ };
225
+ }
226
+ __name(toEffectiveLimitsSnapshot, "toEffectiveLimitsSnapshot");
227
+
228
+ // src/entitlement/feature-aliases.ts
229
+ function buildReplacedByIndex(features) {
230
+ const index = /* @__PURE__ */ new Map();
231
+ for (const feature of features) {
232
+ for (const oldKey of feature.replaces ?? []) {
233
+ if (oldKey === feature.featureKey) continue;
234
+ const list = index.get(oldKey) ?? [];
235
+ list.push(feature.featureKey);
236
+ index.set(oldKey, list);
237
+ }
238
+ }
239
+ for (const list of index.values()) list.sort((a, b) => a.localeCompare(b));
240
+ assertNoReplacesCycle(index);
241
+ return index;
242
+ }
243
+ __name(buildReplacedByIndex, "buildReplacedByIndex");
244
+ function expandReplacedFeatures(granted, index) {
245
+ const expanded = new Set(granted);
246
+ const worklist = [
247
+ ...granted
248
+ ];
249
+ while (worklist.length > 0) {
250
+ const key = worklist.pop();
251
+ for (const successor of index.get(key) ?? []) {
252
+ if (expanded.has(successor)) continue;
253
+ expanded.add(successor);
254
+ worklist.push(successor);
255
+ }
256
+ }
257
+ return expanded;
258
+ }
259
+ __name(expandReplacedFeatures, "expandReplacedFeatures");
260
+ function assertNoReplacesCycle(index) {
261
+ const DONE = 2;
262
+ const IN_PROGRESS = 1;
263
+ const state = /* @__PURE__ */ new Map();
264
+ const visit = /* @__PURE__ */ __name((key, path) => {
265
+ const s = state.get(key);
266
+ if (s === DONE) return;
267
+ if (s === IN_PROGRESS) {
268
+ const cycleStart = path.indexOf(key);
269
+ const cycle = [
270
+ ...path.slice(cycleStart),
271
+ key
272
+ ].join(" \u2192 ");
273
+ throw new Error(`replaces-Zyklus in den Discovery-Metadaten: ${cycle}. Feature-Ersetzungen m\xFCssen azyklisch sein \u2014 replaces-Deklarationen im Code korrigieren (#39).`);
274
+ }
275
+ state.set(key, IN_PROGRESS);
276
+ for (const successor of index.get(key) ?? []) {
277
+ visit(successor, [
278
+ ...path,
279
+ key
280
+ ]);
281
+ }
282
+ state.set(key, DONE);
283
+ }, "visit");
284
+ for (const key of index.keys()) visit(key, []);
285
+ }
286
+ __name(assertNoReplacesCycle, "assertNoReplacesCycle");
287
+
288
+ // src/entitlement/plan-resolution.ts
289
+ function resolveEntitlementPlan(input, config, now) {
290
+ if (input.isPilot && config.pilotEntitlementPlan !== void 0) {
291
+ return config.pilotEntitlementPlan;
292
+ }
293
+ if (input.status === "TRIAL") {
294
+ return input.trialEntitlementPlan ?? config.defaultTrialEntitlementPlan ?? input.plan;
295
+ }
296
+ if (input.status === "PENDING_SALES" && config.pendingSalesEntitlementPlan !== void 0) {
297
+ return config.pendingSalesEntitlementPlan;
298
+ }
299
+ if (input.pendingPlan && input.pendingEffectiveAt && input.pendingEffectiveAt <= now) {
300
+ return input.pendingPlan;
301
+ }
302
+ return input.plan;
303
+ }
304
+ __name(resolveEntitlementPlan, "resolveEntitlementPlan");
305
+
306
+ // src/entitlement/limit-exceeded-error.ts
307
+ var LimitExceededError = class extends Error {
308
+ static {
309
+ __name(this, "LimitExceededError");
310
+ }
311
+ dimension;
312
+ max;
313
+ used;
314
+ constructor(dimension, max, used) {
315
+ super(`Limit f\xFCr ${dimension} erreicht: ${used}/${max}.`), this.dimension = dimension, this.max = max, this.used = used;
316
+ this.name = "LimitExceededError";
317
+ }
318
+ };
319
+
320
+ // src/entitlement/service.ts
321
+ var import_common2 = require("@nestjs/common");
322
+
323
+ // src/catalog/tokens.ts
324
+ var BUNDLE_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol("BUNDLE_REPOSITORY");
325
+
326
+ // src/billing/plan-catalog.module.ts
327
+ var import_common = require("@nestjs/common");
328
+
329
+ // src/core/di.ts
330
+ function asProvider(token, impl) {
331
+ if (typeof impl === "object" && impl !== null && "useFactory" in impl && typeof impl.useFactory === "function") {
332
+ const factory = impl;
333
+ return {
334
+ provide: token,
335
+ useFactory: factory.useFactory,
336
+ inject: factory.inject
337
+ };
338
+ }
339
+ return {
340
+ provide: token,
341
+ useValue: impl
342
+ };
343
+ }
344
+ __name(asProvider, "asProvider");
345
+
346
+ // src/billing/plan-catalog-from-snapshot.ts
347
+ function buildPlanCatalogFromSnapshot(settings, snapshot) {
348
+ const liveByPlanKey = new Map(snapshot.livePlanVersions.map((v) => [
349
+ v.planId,
350
+ v
351
+ ]));
352
+ const plans = snapshot.plans.filter((p) => p.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder).map((stem) => {
353
+ const live = liveByPlanKey.get(stem.planKey);
354
+ if (!live) {
355
+ return {
356
+ id: stem.planKey,
357
+ name: stem.label,
358
+ tagline: stem.description ?? void 0,
359
+ marketed: false,
360
+ monthlyNet: null,
361
+ yearlyNet: null,
362
+ features: [],
363
+ quotas: {}
364
+ };
365
+ }
366
+ return {
367
+ id: stem.planKey,
368
+ name: stem.label,
369
+ tagline: stem.description ?? void 0,
370
+ marketed: live.marketed,
371
+ monthlyNet: parseFloat(live.monthlyNet),
372
+ yearlyNet: parseFloat(live.yearlyNet),
373
+ features: live.features,
374
+ quotas: live.quotas ?? {}
375
+ };
376
+ });
377
+ const features = snapshot.featureEntries.filter((f) => f.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder).map((row) => ({
378
+ key: row.featureKey,
379
+ label: row.label,
380
+ icon: row.icon ?? void 0,
381
+ tier: row.tier ?? void 0,
382
+ plannedOnly: row.plannedOnly
383
+ }));
384
+ return {
385
+ schemaVersion: 1,
386
+ projectKey: settings.projectKey,
387
+ ...settings.app ? {
388
+ app: settings.app
389
+ } : {},
390
+ currency: settings.currency,
391
+ vatRate: settings.vatRate,
392
+ ...settings.marketing ? {
393
+ marketing: settings.marketing
394
+ } : {},
395
+ features,
396
+ plans
397
+ };
398
+ }
399
+ __name(buildPlanCatalogFromSnapshot, "buildPlanCatalogFromSnapshot");
400
+
401
+ // src/billing/plan-catalog.module.ts
402
+ function _ts_decorate(decorators, target, key, desc) {
403
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
404
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
405
+ 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;
406
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
407
+ }
408
+ __name(_ts_decorate, "_ts_decorate");
409
+ var PLAN_CATALOG_TOKEN = /* @__PURE__ */ Symbol("PLAN_CATALOG");
410
+ var PLAN_CATALOG_READ_SINK_TOKEN = /* @__PURE__ */ Symbol("PLAN_CATALOG_READ_SINK");
411
+ var PlanCatalogModule = class _PlanCatalogModule {
412
+ static {
413
+ __name(this, "PlanCatalogModule");
414
+ }
415
+ static forRoot(options) {
416
+ return {
417
+ module: _PlanCatalogModule,
418
+ global: options.global ?? true,
419
+ imports: options.imports ?? [],
420
+ providers: [
421
+ ...options.extraProviders ?? [],
422
+ asProvider(PLAN_CATALOG_READ_SINK_TOKEN, options.sink),
423
+ {
424
+ provide: PLAN_CATALOG_TOKEN,
425
+ useFactory: /* @__PURE__ */ __name(async (sink) => {
426
+ const snapshot = await sink.loadSnapshot(options.projectKey);
427
+ return buildPlanCatalogFromSnapshot({
428
+ projectKey: options.projectKey,
429
+ app: options.app,
430
+ currency: options.currency,
431
+ vatRate: options.vatRate,
432
+ marketing: options.marketing
433
+ }, snapshot);
434
+ }, "useFactory"),
435
+ inject: [
436
+ PLAN_CATALOG_READ_SINK_TOKEN
437
+ ]
438
+ }
439
+ ],
440
+ exports: [
441
+ PLAN_CATALOG_TOKEN,
442
+ PLAN_CATALOG_READ_SINK_TOKEN
443
+ ]
444
+ };
445
+ }
446
+ /**
447
+ * Tests / in-memory setup: takes the catalog object directly, without a
448
+ * sink/DB lookup. For unit tests + test bootstraps.
449
+ */
450
+ static forRootWithCatalog(catalog, opts = {}) {
451
+ return {
452
+ module: _PlanCatalogModule,
453
+ providers: [
454
+ {
455
+ provide: PLAN_CATALOG_TOKEN,
456
+ useValue: catalog
457
+ }
458
+ ],
459
+ exports: [
460
+ PLAN_CATALOG_TOKEN
461
+ ],
462
+ global: opts.global ?? true
463
+ };
464
+ }
465
+ };
466
+ PlanCatalogModule = _ts_decorate([
467
+ (0, import_common.Module)({})
468
+ ], PlanCatalogModule);
469
+
470
+ // src/billing/subscription-bundles.tokens.ts
471
+ var SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionBundleRepository");
472
+
473
+ // src/subscription-contract/tokens.ts
474
+ var SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionContractRepository");
475
+
476
+ // src/discovery/tokens.ts
477
+ var DISCOVERY_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/DiscoverySnapshot");
478
+
479
+ // src/entitlement/tokens.ts
480
+ var SUBSCRIPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionRepository");
481
+ var PLAN_VERSION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/PlanVersionRepository");
482
+ var TRANSACTION_RUNNER_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/TransactionRunner");
483
+ var ENTITLEMENT_RESOLUTION_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/EntitlementResolutionConfig");
484
+
485
+ // src/entitlement/service.ts
486
+ function _ts_decorate2(decorators, target, key, desc) {
487
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
488
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
489
+ 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;
490
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
491
+ }
492
+ __name(_ts_decorate2, "_ts_decorate");
493
+ function _ts_metadata(k, v) {
494
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
495
+ }
496
+ __name(_ts_metadata, "_ts_metadata");
497
+ function _ts_param(paramIndex, decorator) {
498
+ return function(target, key) {
499
+ decorator(target, key, paramIndex);
500
+ };
501
+ }
502
+ __name(_ts_param, "_ts_param");
503
+ var CACHE_TTL_MS = 6e4;
504
+ var CACHE_MAX_ENTRIES = 1e3;
505
+ var EntitlementService = class {
506
+ static {
507
+ __name(this, "EntitlementService");
508
+ }
509
+ catalog;
510
+ subscriptions;
511
+ planVersions;
512
+ tx;
513
+ resolutionConfig;
514
+ subscriptionBundles;
515
+ bundles;
516
+ subscriptionContracts;
517
+ discoverySnapshot;
518
+ // Map preserves insert order — on a hit the entry is removed and
519
+ // re-inserted (LRU). The TTL is a safety net for forgotten
520
+ // invalidations — 60s is enough, because limits generally only change
521
+ // through mutations, not through time (a pending plan's effective date
522
+ // is day-granular).
523
+ cache = /* @__PURE__ */ new Map();
524
+ // #39 — replaces alias index, built lazily from the boot-static snapshot;
525
+ // throws on replaces cycles (a clear error instead of an infinite loop).
526
+ replacedByIndex = null;
527
+ constructor(catalog, subscriptions, planVersions, tx, resolutionConfig = null, subscriptionBundles = null, bundles = null, subscriptionContracts = null, discoverySnapshot = null) {
528
+ this.catalog = catalog;
529
+ this.subscriptions = subscriptions;
530
+ this.planVersions = planVersions;
531
+ this.tx = tx;
532
+ this.resolutionConfig = resolutionConfig;
533
+ this.subscriptionBundles = subscriptionBundles;
534
+ this.bundles = bundles;
535
+ this.subscriptionContracts = subscriptionContracts;
536
+ this.discoverySnapshot = discoverySnapshot;
537
+ }
538
+ // ---------------------------------------------------------------------
539
+ // Read path — for FeatureGuard, sidebar hooks, GET /billing/entitlement
540
+ // ---------------------------------------------------------------------
541
+ async computeLimits(tenantId, now = /* @__PURE__ */ new Date()) {
542
+ const cached = this.readCache(tenantId, now.getTime());
543
+ if (cached) return cached;
544
+ const sub = await this.subscriptions.findByTenantId(tenantId);
545
+ if (!sub) {
546
+ throw new import_common2.NotFoundException(`Keine Subscription f\xFCr Tenant ${tenantId}`);
547
+ }
548
+ const limits = await this.deriveLimits(sub, now);
549
+ this.writeCache(tenantId, limits, now.getTime());
550
+ return limits;
551
+ }
552
+ /**
553
+ * Clears a tenant's cache entry. MUST be called as soon as a mutation
554
+ * changes the subscription, a bundle booking, or a PlanVersion binding —
555
+ * otherwise FeatureGuard and the sidebar stay on stale state for up to
556
+ * 60s.
557
+ */
558
+ invalidateTenant(tenantId) {
559
+ this.cache.delete(tenantId);
560
+ }
561
+ /** Clears the entire cache. Only for tests / bootstrap. */
562
+ invalidateAll() {
563
+ this.cache.clear();
564
+ }
565
+ /**
566
+ * Aggregator path — also callable directly by consumers that already
567
+ * have a `SubscriptionRecord` (e.g. within a transaction).
568
+ */
569
+ async deriveLimits(sub, now, tx) {
570
+ const contractLimits = await this.deriveLimitsFromContract(sub.tenantId, now);
571
+ if (contractLimits) return this.withReplacedFeatureAliases(contractLimits);
572
+ const effectivePlan = resolveEntitlementPlan(sub, this.resolutionConfig ?? {}, now);
573
+ const planVersion = effectivePlan === sub.plan ? sub.planVersion : await this.findActivePlanVersionOrFallback(effectivePlan, now, tx);
574
+ const subscriptionBundles = await this.loadSubscriptionBundleSnapshots(sub.id, now);
575
+ return this.withReplacedFeatureAliases(aggregateLimits({
576
+ plan: effectivePlan,
577
+ planVersion,
578
+ subscriptionBundles,
579
+ customLimits: sub.customLimits ?? null
580
+ }, this.catalog, now));
581
+ }
582
+ /**
583
+ * Grandfathering (#39): granted old feature keys transitively grant
584
+ * their successors from the `replaces` chains of the discovery snapshot.
585
+ * A no-op without a snapshot or without replaces declarations.
586
+ */
587
+ withReplacedFeatureAliases(limits) {
588
+ if (!this.discoverySnapshot) return limits;
589
+ if (!this.replacedByIndex) {
590
+ this.replacedByIndex = buildReplacedByIndex(this.discoverySnapshot.features);
591
+ }
592
+ if (this.replacedByIndex.size === 0) return limits;
593
+ return {
594
+ ...limits,
595
+ features: expandReplacedFeatures(limits.features, this.replacedByIndex)
596
+ };
597
+ }
598
+ async deriveLimitsFromContract(tenantId, now) {
599
+ if (!this.subscriptionContracts) return null;
600
+ const contract = await this.subscriptionContracts.findActiveByTenantId(tenantId, now);
601
+ if (!contract) return null;
602
+ if (contract.entitlementSnapshot) {
603
+ return {
604
+ plan: contract.entitlementSnapshot.plan,
605
+ quotas: {
606
+ ...contract.entitlementSnapshot.quotas
607
+ },
608
+ features: new Set(contract.entitlementSnapshot.features)
609
+ };
610
+ }
611
+ return aggregateContractLineItemEntitlements(contract.lineItems);
612
+ }
613
+ /**
614
+ * Loads a subscription's active bundle bookings and resolves the
615
+ * `BundleVersion` features/quotas per entry. Without a registered
616
+ * `SubscriptionBundleRepository` or `BundleRepository` the method
617
+ * returns an empty list — apps without a bundle schema stay unchanged
618
+ * (plan-only aggregation).
619
+ */
620
+ async loadSubscriptionBundleSnapshots(subscriptionId, now) {
621
+ if (!this.subscriptionBundles || !this.bundles) return [];
622
+ const active = await this.subscriptionBundles.listActiveBySubscription(subscriptionId, now);
623
+ if (active.length === 0) return [];
624
+ return Promise.all(active.map(async (booking) => {
625
+ const bv = await this.bundles.findVersionById(booking.bundleVersionId);
626
+ if (!bv) {
627
+ throw new Error(`BundleVersion '${booking.bundleVersionId}' aus aktiver SubscriptionBundle nicht gefunden`);
628
+ }
629
+ return {
630
+ bundleKey: bv.bundleKey,
631
+ features: bv.features,
632
+ quotas: bv.quotas,
633
+ canceledEffectiveAt: booking.canceledEffectiveAt
634
+ };
635
+ }));
636
+ }
637
+ // ---------------------------------------------------------------------
638
+ // Write/consumption path — row lock + count + insert in the same
639
+ // transaction. Protects against race conditions on concurrent creations
640
+ // for the same tenant.
641
+ // ---------------------------------------------------------------------
642
+ async enforceLimit(input) {
643
+ const now = input.now ?? /* @__PURE__ */ new Date();
644
+ const delta = input.delta ?? 1;
645
+ return this.tx.run(async (tx) => {
646
+ const sub = await this.subscriptions.findByTenantIdLocked(input.tenantId, tx);
647
+ if (!sub) {
648
+ throw new import_common2.NotFoundException(`Keine Subscription f\xFCr Tenant ${input.tenantId}`);
649
+ }
650
+ const limits = await this.deriveLimits(sub, now, tx);
651
+ const max = limits.quotas[input.dimension];
652
+ if (max === void 0) {
653
+ throw new Error(`Quota-Dimension "${input.dimension}" nicht im PlanCatalog (${this.catalog.projectKey}).`);
654
+ }
655
+ const used = await input.currentUsage(tx, input.tenantId);
656
+ if (max !== -1 && used + delta > max) {
657
+ throw new LimitExceededError(input.dimension, max, used);
658
+ }
659
+ return input.insert(tx);
660
+ });
661
+ }
662
+ // ---------------------------------------------------------------------
663
+ // Helpers
664
+ // ---------------------------------------------------------------------
665
+ /**
666
+ * Plan fallback for TRIAL/PENDING_SALES: returns the PlanVersion active
667
+ * as of `asOf` (SPEC_V2 §4.2). If the repo does not implement
668
+ * `findActive`, we fall back to `findLatestLive` (backward compat for
669
+ * adapters without `validFrom` columns).
670
+ */
671
+ async findActivePlanVersionOrFallback(planId, asOf, tx) {
672
+ const v = this.planVersions.findActive ? await this.planVersions.findActive(planId, asOf, tx) : await this.planVersions.findLatestLive(planId, tx);
673
+ if (!v) {
674
+ throw new import_common2.NotFoundException(`Keine zu ${asOf.toISOString().slice(0, 10)} aktive PlanVersion f\xFCr ${planId} \u2014 weder validFrom-window erf\xFCllt noch latest-live verf\xFCgbar.`);
675
+ }
676
+ return v;
677
+ }
678
+ readCache(tenantId, nowMs) {
679
+ const entry = this.cache.get(tenantId);
680
+ if (!entry) return null;
681
+ if (entry.expiresAt < nowMs) {
682
+ this.cache.delete(tenantId);
683
+ return null;
684
+ }
685
+ this.cache.delete(tenantId);
686
+ this.cache.set(tenantId, entry);
687
+ return entry.value;
688
+ }
689
+ writeCache(tenantId, value, nowMs) {
690
+ if (this.cache.has(tenantId)) this.cache.delete(tenantId);
691
+ this.cache.set(tenantId, {
692
+ value,
693
+ expiresAt: nowMs + CACHE_TTL_MS
694
+ });
695
+ while (this.cache.size > CACHE_MAX_ENTRIES) {
696
+ const oldest = this.cache.keys().next().value;
697
+ if (oldest === void 0) break;
698
+ this.cache.delete(oldest);
699
+ }
700
+ }
701
+ };
702
+ EntitlementService = _ts_decorate2([
703
+ (0, import_common2.Injectable)(),
704
+ _ts_param(0, (0, import_common2.Inject)(PLAN_CATALOG_TOKEN)),
705
+ _ts_param(1, (0, import_common2.Inject)(SUBSCRIPTION_REPOSITORY_TOKEN)),
706
+ _ts_param(2, (0, import_common2.Inject)(PLAN_VERSION_REPOSITORY_TOKEN)),
707
+ _ts_param(3, (0, import_common2.Inject)(TRANSACTION_RUNNER_TOKEN)),
708
+ _ts_param(4, (0, import_common2.Optional)()),
709
+ _ts_param(4, (0, import_common2.Inject)(ENTITLEMENT_RESOLUTION_CONFIG_TOKEN)),
710
+ _ts_param(5, (0, import_common2.Optional)()),
711
+ _ts_param(5, (0, import_common2.Inject)(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN)),
712
+ _ts_param(6, (0, import_common2.Optional)()),
713
+ _ts_param(6, (0, import_common2.Inject)(BUNDLE_REPOSITORY_TOKEN)),
714
+ _ts_param(7, (0, import_common2.Optional)()),
715
+ _ts_param(7, (0, import_common2.Inject)(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN)),
716
+ _ts_param(8, (0, import_common2.Optional)()),
717
+ _ts_param(8, (0, import_common2.Inject)(DISCOVERY_SNAPSHOT_TOKEN)),
718
+ _ts_metadata("design:type", Function),
719
+ _ts_metadata("design:paramtypes", [
720
+ typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
721
+ typeof SubscriptionRepository === "undefined" ? Object : SubscriptionRepository,
722
+ typeof PlanVersionRepository === "undefined" ? Object : PlanVersionRepository,
723
+ typeof TransactionRunner === "undefined" ? Object : TransactionRunner,
724
+ Object,
725
+ Object,
726
+ Object,
727
+ Object,
728
+ Object
729
+ ])
730
+ ], EntitlementService);
731
+
732
+ // src/entitlement/module.ts
733
+ var import_common3 = require("@nestjs/common");
734
+ function _ts_decorate3(decorators, target, key, desc) {
735
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
736
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
737
+ 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;
738
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
739
+ }
740
+ __name(_ts_decorate3, "_ts_decorate");
741
+ var EntitlementModule = class _EntitlementModule {
742
+ static {
743
+ __name(this, "EntitlementModule");
744
+ }
745
+ static forRoot(options) {
746
+ const providers = [
747
+ asProvider(SUBSCRIPTION_REPOSITORY_TOKEN, options.subscriptionRepository),
748
+ asProvider(PLAN_VERSION_REPOSITORY_TOKEN, options.planVersionRepository),
749
+ asProvider(TRANSACTION_RUNNER_TOKEN, options.transactionRunner),
750
+ EntitlementService
751
+ ];
752
+ if (options.resolutionConfig) {
753
+ providers.push({
754
+ provide: ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
755
+ useValue: options.resolutionConfig
756
+ });
757
+ }
758
+ if (options.subscriptionContractRepository) {
759
+ providers.push(asProvider(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN, options.subscriptionContractRepository));
760
+ }
761
+ if (options.subscriptionBundleRepository) {
762
+ providers.push(asProvider(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN, options.subscriptionBundleRepository));
763
+ }
764
+ if (options.bundleRepository) {
765
+ providers.push(asProvider(BUNDLE_REPOSITORY_TOKEN, options.bundleRepository));
766
+ }
767
+ return {
768
+ module: _EntitlementModule,
769
+ global: options.global ?? false,
770
+ providers,
771
+ exports: [
772
+ EntitlementService,
773
+ SUBSCRIPTION_REPOSITORY_TOKEN
774
+ ]
775
+ };
776
+ }
777
+ };
778
+ EntitlementModule = _ts_decorate3([
779
+ (0, import_common3.Module)({})
780
+ ], EntitlementModule);
781
+ // Annotate the CommonJS export names for ESM import in node:
782
+ 0 && (module.exports = {
783
+ ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
784
+ EntitlementModule,
785
+ EntitlementService,
786
+ LimitExceededError,
787
+ PLAN_VERSION_REPOSITORY_TOKEN,
788
+ SUBSCRIPTION_REPOSITORY_TOKEN,
789
+ TRANSACTION_RUNNER_TOKEN,
790
+ aggregateBusinessTypeQuotas,
791
+ aggregateContractLineItemEntitlements,
792
+ aggregateLimits,
793
+ aggregateSubscriptionBundleQuotas,
794
+ applyCustomLimits,
795
+ buildReplacedByIndex,
796
+ collectBusinessTypeFeatures,
797
+ collectSubscriptionBundleFeatures,
798
+ expandReplacedFeatures,
799
+ filterActiveSubscriptionBundles,
800
+ filterPlannedOnlyFeatures,
801
+ hasAnyFeature,
802
+ hasFeature,
803
+ resolveEntitlementPlan,
804
+ toEffectiveLimitsSnapshot
805
+ });