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