@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,14 @@
1
+ // src/discovery/tokens.ts
2
+ var IMPLEMENTS_CAPABILITY_KEY = "discovery:implements-capability";
3
+ var REQUIRES_CAPABILITY_KEY = "discovery:requires-capability";
4
+ var DEFINES_QUOTA_KEY = "discovery:defines-quota";
5
+ var ENFORCE_QUOTA_KEY = "discovery:enforce-quota";
6
+ var DISCOVERY_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/DiscoverySnapshot");
7
+
8
+ export {
9
+ IMPLEMENTS_CAPABILITY_KEY,
10
+ REQUIRES_CAPABILITY_KEY,
11
+ DEFINES_QUOTA_KEY,
12
+ ENFORCE_QUOTA_KEY,
13
+ DISCOVERY_SNAPSHOT_TOKEN
14
+ };
@@ -0,0 +1,314 @@
1
+ import {
2
+ DEFINES_QUOTA_KEY,
3
+ ENFORCE_QUOTA_KEY,
4
+ IMPLEMENTS_CAPABILITY_KEY
5
+ } from "./chunk-L4SUWH5B.js";
6
+ import {
7
+ __name
8
+ } from "./chunk-SHUYVCID.js";
9
+
10
+ // src/discovery/discovery.scanner.ts
11
+ import { createHash } from "crypto";
12
+ import { mkdirSync, writeFileSync } from "fs";
13
+ import { dirname } from "path";
14
+ import { Inject, Injectable, Logger, Optional } from "@nestjs/common";
15
+ import { DiscoveryService, MetadataScanner, Reflector } from "@nestjs/core";
16
+ function _ts_decorate(decorators, target, key, desc) {
17
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
18
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
19
+ 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;
20
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
21
+ }
22
+ __name(_ts_decorate, "_ts_decorate");
23
+ function _ts_metadata(k, v) {
24
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
25
+ }
26
+ __name(_ts_metadata, "_ts_metadata");
27
+ function _ts_param(paramIndex, decorator) {
28
+ return function(target, key) {
29
+ decorator(target, key, paramIndex);
30
+ };
31
+ }
32
+ __name(_ts_param, "_ts_param");
33
+ var DEFAULT_APP_INFO = {
34
+ key: "unknown",
35
+ version: "0.0.0"
36
+ };
37
+ var DISCOVERY_APP_INFO_TOKEN = /* @__PURE__ */ Symbol("DISCOVERY_APP_INFO");
38
+ var DISCOVERY_SNAPSHOT_PATH_TOKEN = /* @__PURE__ */ Symbol("DISCOVERY_SNAPSHOT_PATH");
39
+ var DiscoveryScanner = class _DiscoveryScanner {
40
+ static {
41
+ __name(this, "DiscoveryScanner");
42
+ }
43
+ discoveryService;
44
+ metadataScanner;
45
+ reflector;
46
+ appInfo;
47
+ snapshotPath;
48
+ logger = new Logger(_DiscoveryScanner.name);
49
+ snapshot = null;
50
+ // Explicit @Inject instead of type reflection: in the platform package
51
+ // build tsup/esbuild do not emit `design:paramtypes` metadata, so Nest
52
+ // cannot otherwise resolve the DI targets at the constructor
53
+ // (UndefinedDependencyException at the consumer's boot). Pattern
54
+ // analogous to admin/admin-manifest.module.ts.
55
+ constructor(discoveryService, metadataScanner, reflector, appInfo = DEFAULT_APP_INFO, snapshotPath = null) {
56
+ this.discoveryService = discoveryService;
57
+ this.metadataScanner = metadataScanner;
58
+ this.reflector = reflector;
59
+ this.appInfo = appInfo;
60
+ this.snapshotPath = snapshotPath;
61
+ }
62
+ onApplicationBootstrap() {
63
+ this.snapshot = this.buildSnapshot();
64
+ this.logger.log(`Discovery snapshot built: ${this.snapshot.capabilities.length} capabilities, ${this.snapshot.features.length} features, ${this.snapshot.quotas.length} quotas (hash=${this.snapshot.hash.slice(0, 12)}\u2026)`);
65
+ if (this.snapshotPath) {
66
+ this.persistSnapshot(this.snapshotPath, this.snapshot);
67
+ }
68
+ }
69
+ persistSnapshot(path, snapshot) {
70
+ try {
71
+ mkdirSync(dirname(path), {
72
+ recursive: true
73
+ });
74
+ writeFileSync(path, JSON.stringify(snapshot, null, 2) + "\n", "utf-8");
75
+ this.logger.log(`Discovery snapshot persisted to ${path}`);
76
+ } catch (err) {
77
+ this.logger.warn(`Discovery snapshot konnte nicht nach ${path} geschrieben werden: ${err.message}`);
78
+ }
79
+ }
80
+ /**
81
+ * Returns the boot-time snapshot. Before `onApplicationBootstrap` (e.g. in
82
+ * tests) the snapshot is built lazily.
83
+ */
84
+ getSnapshot() {
85
+ if (!this.snapshot) {
86
+ this.snapshot = this.buildSnapshot();
87
+ }
88
+ return this.snapshot;
89
+ }
90
+ /**
91
+ * Forces a re-scan (SuperAdmin "re-run discovery" or tests with
92
+ * dynamically registered providers). `scannedAt` is set anew and the
93
+ * snapshot — as at boot — is persisted to disk so that the last scan
94
+ * actually performed is preserved.
95
+ */
96
+ rebuildSnapshot() {
97
+ this.snapshot = this.buildSnapshot();
98
+ if (this.snapshotPath) {
99
+ this.persistSnapshot(this.snapshotPath, this.snapshot);
100
+ }
101
+ return this.snapshot;
102
+ }
103
+ buildSnapshot() {
104
+ const capabilitiesByKey = /* @__PURE__ */ new Map();
105
+ const quotasByKey = /* @__PURE__ */ new Map();
106
+ const enforceRefs = [];
107
+ const wrappers = [
108
+ ...this.discoveryService.getProviders(),
109
+ ...this.discoveryService.getControllers()
110
+ ];
111
+ for (const wrapper of wrappers) {
112
+ const instance = wrapper.instance;
113
+ if (!instance || typeof instance !== "object") continue;
114
+ const ctor = instance.constructor;
115
+ if (!ctor) continue;
116
+ const quotaOpts = this.reflector.get(DEFINES_QUOTA_KEY, ctor);
117
+ if (quotaOpts) {
118
+ this.registerQuota(quotasByKey, quotaOpts, ctor.name);
119
+ }
120
+ const prototype = Object.getPrototypeOf(instance);
121
+ const methodNames = this.metadataScanner.getAllMethodNames(prototype);
122
+ for (const methodName of methodNames) {
123
+ const methodRef = instance[methodName];
124
+ if (typeof methodRef !== "function") continue;
125
+ const capMeta = this.reflector.get(IMPLEMENTS_CAPABILITY_KEY, methodRef);
126
+ if (capMeta) {
127
+ this.registerCapability(capabilitiesByKey, capMeta, `${ctor.name}.${methodName}`);
128
+ }
129
+ const enforceMeta = this.reflector.get(ENFORCE_QUOTA_KEY, methodRef);
130
+ if (enforceMeta && capMeta) {
131
+ enforceRefs.push({
132
+ capabilityKey: capMeta.capabilityKey,
133
+ quotaKey: enforceMeta.quotaKey
134
+ });
135
+ }
136
+ }
137
+ }
138
+ const capabilities = sortByKey([
139
+ ...capabilitiesByKey.values()
140
+ ], (c) => c.capabilityKey);
141
+ const features = aggregateFeatures(capabilities);
142
+ const quotas = sortByKey(decorateQuotasWithEnforcers([
143
+ ...quotasByKey.values()
144
+ ], enforceRefs), (q) => q.quotaKey);
145
+ const snapshot = {
146
+ schemaVersion: 1,
147
+ scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
148
+ app: {
149
+ ...this.appInfo
150
+ },
151
+ capabilities,
152
+ features,
153
+ quotas,
154
+ hash: ""
155
+ };
156
+ snapshot.hash = computeSnapshotHash(snapshot);
157
+ return snapshot;
158
+ }
159
+ registerCapability(target, meta, declaredAt) {
160
+ const existing = target.get(meta.capabilityKey);
161
+ if (existing) {
162
+ this.logger.warn(`Capability '${meta.capabilityKey}' wird an mehreren Stellen deklariert (${existing.declaredAt} und ${declaredAt}); erste Deklaration gewinnt`);
163
+ return;
164
+ }
165
+ target.set(meta.capabilityKey, {
166
+ capabilityKey: meta.capabilityKey,
167
+ label: meta.label ?? null,
168
+ feature: meta.feature ?? null,
169
+ status: meta.status ?? "active",
170
+ kind: meta.kind ?? "endpoint",
171
+ owner: meta.owner ?? null,
172
+ replacementKey: meta.replacementKey ?? null,
173
+ removalPlannedAt: meta.removalPlannedAt ?? null,
174
+ reason: meta.reason ?? null,
175
+ requires: normalizeKeyList(meta.requires),
176
+ replaces: normalizeKeyList(meta.replaces),
177
+ declaredAt
178
+ });
179
+ }
180
+ registerQuota(target, opts, declaredAt) {
181
+ const existing = target.get(opts.key);
182
+ if (existing) {
183
+ this.logger.warn(`Quota '${opts.key}' wird an mehreren Stellen definiert (${existing.declaredAt} und ${declaredAt}); erste Definition gewinnt`);
184
+ return;
185
+ }
186
+ target.set(opts.key, {
187
+ quotaKey: opts.key,
188
+ label: opts.label,
189
+ unit: opts.unit,
190
+ policy: opts.policy,
191
+ feature: opts.feature ?? null,
192
+ replaces: normalizeKeyList(opts.replaces),
193
+ declaredAt,
194
+ enforcedBy: []
195
+ });
196
+ }
197
+ };
198
+ DiscoveryScanner = _ts_decorate([
199
+ Injectable(),
200
+ _ts_param(0, Inject(DiscoveryService)),
201
+ _ts_param(1, Inject(MetadataScanner)),
202
+ _ts_param(2, Inject(Reflector)),
203
+ _ts_param(3, Optional()),
204
+ _ts_param(3, Inject(DISCOVERY_APP_INFO_TOKEN)),
205
+ _ts_param(4, Optional()),
206
+ _ts_param(4, Inject(DISCOVERY_SNAPSHOT_PATH_TOKEN)),
207
+ _ts_metadata("design:type", Function),
208
+ _ts_metadata("design:paramtypes", [
209
+ typeof DiscoveryService === "undefined" ? Object : DiscoveryService,
210
+ typeof MetadataScanner === "undefined" ? Object : MetadataScanner,
211
+ typeof Reflector === "undefined" ? Object : Reflector,
212
+ typeof DiscoveryAppInfo === "undefined" ? Object : DiscoveryAppInfo,
213
+ Object
214
+ ])
215
+ ], DiscoveryScanner);
216
+ function aggregateFeatures(capabilities) {
217
+ const byFeature = /* @__PURE__ */ new Map();
218
+ for (const cap of capabilities) {
219
+ if (!cap.feature) continue;
220
+ const acc = byFeature.get(cap.feature) ?? {
221
+ capabilityKeys: [],
222
+ requires: /* @__PURE__ */ new Set(),
223
+ replaces: /* @__PURE__ */ new Set()
224
+ };
225
+ acc.capabilityKeys.push(cap.capabilityKey);
226
+ for (const key of cap.requires ?? []) acc.requires.add(key);
227
+ for (const key of cap.replaces ?? []) acc.replaces.add(key);
228
+ byFeature.set(cap.feature, acc);
229
+ }
230
+ return sortByKey([
231
+ ...byFeature.entries()
232
+ ].map(([featureKey, acc]) => {
233
+ acc.requires.delete(featureKey);
234
+ acc.replaces.delete(featureKey);
235
+ return {
236
+ featureKey,
237
+ capabilityKeys: [
238
+ ...acc.capabilityKeys
239
+ ].sort((a, b) => a.localeCompare(b)),
240
+ requires: setToSortedListOrNull(acc.requires),
241
+ replaces: setToSortedListOrNull(acc.replaces)
242
+ };
243
+ }), (f) => f.featureKey);
244
+ }
245
+ __name(aggregateFeatures, "aggregateFeatures");
246
+ function normalizeKeyList(keys) {
247
+ if (!keys || keys.length === 0) return null;
248
+ return [
249
+ ...new Set(keys)
250
+ ].sort((a, b) => a.localeCompare(b));
251
+ }
252
+ __name(normalizeKeyList, "normalizeKeyList");
253
+ function setToSortedListOrNull(keys) {
254
+ if (keys.size === 0) return null;
255
+ return [
256
+ ...keys
257
+ ].sort((a, b) => a.localeCompare(b));
258
+ }
259
+ __name(setToSortedListOrNull, "setToSortedListOrNull");
260
+ function decorateQuotasWithEnforcers(quotas, enforceRefs) {
261
+ const enforcedByQuota = /* @__PURE__ */ new Map();
262
+ for (const ref of enforceRefs) {
263
+ const set = enforcedByQuota.get(ref.quotaKey) ?? /* @__PURE__ */ new Set();
264
+ set.add(ref.capabilityKey);
265
+ enforcedByQuota.set(ref.quotaKey, set);
266
+ }
267
+ return quotas.map((q) => ({
268
+ ...q,
269
+ enforcedBy: [
270
+ ...enforcedByQuota.get(q.quotaKey) ?? /* @__PURE__ */ new Set()
271
+ ].sort((a, b) => a.localeCompare(b))
272
+ }));
273
+ }
274
+ __name(decorateQuotasWithEnforcers, "decorateQuotasWithEnforcers");
275
+ function sortByKey(items, keyFn) {
276
+ return [
277
+ ...items
278
+ ].sort((a, b) => keyFn(a).localeCompare(keyFn(b)));
279
+ }
280
+ __name(sortByKey, "sortByKey");
281
+ function computeSnapshotHash(snapshot) {
282
+ const stableInput = {
283
+ schemaVersion: snapshot.schemaVersion,
284
+ appKey: snapshot.app.key,
285
+ capabilities: snapshot.capabilities.map((c) => ({
286
+ ...c
287
+ })),
288
+ features: snapshot.features,
289
+ quotas: snapshot.quotas
290
+ };
291
+ const json = canonicalStringify(stableInput);
292
+ return `sha256-${createHash("sha256").update(json).digest("hex")}`;
293
+ }
294
+ __name(computeSnapshotHash, "computeSnapshotHash");
295
+ function canonicalStringify(value) {
296
+ return JSON.stringify(value, (_key, val) => {
297
+ if (val && typeof val === "object" && !Array.isArray(val)) {
298
+ const sorted = {};
299
+ for (const k of Object.keys(val).sort()) {
300
+ sorted[k] = val[k];
301
+ }
302
+ return sorted;
303
+ }
304
+ return val;
305
+ });
306
+ }
307
+ __name(canonicalStringify, "canonicalStringify");
308
+
309
+ export {
310
+ DISCOVERY_APP_INFO_TOKEN,
311
+ DISCOVERY_SNAPSHOT_PATH_TOKEN,
312
+ DiscoveryScanner,
313
+ computeSnapshotHash
314
+ };
@@ -0,0 +1,87 @@
1
+ import {
2
+ __name
3
+ } from "./chunk-SHUYVCID.js";
4
+
5
+ // src/checkout-offer/discount-line-items.ts
6
+ function appendImplicitDiscountLineItem(input) {
7
+ const lineItems = input.lineItems.map((item) => cloneCheckoutOfferLineItem(item));
8
+ const discountNet = roundMoney(input.priceBreakdown.regularNet - input.priceBreakdown.effectiveNet);
9
+ if (lineItems.some((item) => item.kind === "discount" && !isGeneratedDiscount(item))) {
10
+ return lineItems;
11
+ }
12
+ const lineItemsWithoutGeneratedDiscount = lineItems.filter((item) => !isGeneratedDiscount(item));
13
+ if (discountNet <= 0) {
14
+ return lineItemsWithoutGeneratedDiscount;
15
+ }
16
+ return [
17
+ ...lineItemsWithoutGeneratedDiscount,
18
+ createDiscountLineItem(input, discountNet)
19
+ ];
20
+ }
21
+ __name(appendImplicitDiscountLineItem, "appendImplicitDiscountLineItem");
22
+ function cloneCheckoutOfferLineItem(item) {
23
+ return {
24
+ ...item,
25
+ featuresSnapshot: [
26
+ ...item.featuresSnapshot ?? []
27
+ ],
28
+ quotaEffectsSnapshot: {
29
+ ...item.quotaEffectsSnapshot ?? {}
30
+ },
31
+ metadata: cloneRecord(item.metadata)
32
+ };
33
+ }
34
+ __name(cloneCheckoutOfferLineItem, "cloneCheckoutOfferLineItem");
35
+ function createDiscountLineItem(input, discountNet) {
36
+ const promoCode = input.promoCodeSnapshot ?? null;
37
+ const firstPromotion = input.promotionSnapshots?.[0] ?? null;
38
+ const discountGross = roundMoney(discountNet * (1 + input.priceBreakdown.vatRate));
39
+ return {
40
+ kind: "discount",
41
+ sourceKey: promoCode?.code ?? firstPromotion?.id ?? "price-discount",
42
+ sourceVersionId: null,
43
+ titleSnapshot: promoCode?.label ?? firstPromotion?.label ?? "Rabatt",
44
+ descriptionSnapshot: promoCode ? `Promo-Code ${promoCode.code}` : null,
45
+ quantity: 1,
46
+ unit: null,
47
+ priceNet: -discountNet,
48
+ priceGross: -discountGross,
49
+ billingCycle: input.billingCycle,
50
+ featuresSnapshot: [],
51
+ quotaEffectsSnapshot: {},
52
+ metadata: {
53
+ generated: true,
54
+ source: promoCode ? "promo_code" : firstPromotion ? "promotion" : "price_breakdown",
55
+ discountNet,
56
+ discountGross,
57
+ promotionSnapshots: cloneArray(input.promotionSnapshots ?? []),
58
+ promoCodeSnapshot: cloneJsonValue(promoCode)
59
+ }
60
+ };
61
+ }
62
+ __name(createDiscountLineItem, "createDiscountLineItem");
63
+ function roundMoney(value) {
64
+ return Math.round((value + Number.EPSILON) * 100) / 100;
65
+ }
66
+ __name(roundMoney, "roundMoney");
67
+ function isGeneratedDiscount(item) {
68
+ return item.kind === "discount" && item.metadata?.generated === true;
69
+ }
70
+ __name(isGeneratedDiscount, "isGeneratedDiscount");
71
+ function cloneRecord(value) {
72
+ if (!value) return null;
73
+ return cloneJsonValue(value);
74
+ }
75
+ __name(cloneRecord, "cloneRecord");
76
+ function cloneArray(value) {
77
+ return cloneJsonValue(value);
78
+ }
79
+ __name(cloneArray, "cloneArray");
80
+ function cloneJsonValue(value) {
81
+ return value == null ? value : JSON.parse(JSON.stringify(value));
82
+ }
83
+ __name(cloneJsonValue, "cloneJsonValue");
84
+
85
+ export {
86
+ appendImplicitDiscountLineItem
87
+ };
@@ -0,0 +1,217 @@
1
+ import {
2
+ asProvider
3
+ } from "./chunk-SABTXESR.js";
4
+ import {
5
+ __name
6
+ } from "./chunk-SHUYVCID.js";
7
+
8
+ // src/promo/math.ts
9
+ function round2(n) {
10
+ return Math.round(n * 100) / 100;
11
+ }
12
+ __name(round2, "round2");
13
+ function grossFromNet(net, vatRate) {
14
+ return round2(net * (1 + vatRate / 100));
15
+ }
16
+ __name(grossFromNet, "grossFromNet");
17
+ function computeIncludedVat(grossAmount, vatRate) {
18
+ return round2(grossAmount * vatRate / (100 + vatRate));
19
+ }
20
+ __name(computeIncludedVat, "computeIncludedVat");
21
+
22
+ // src/billing/plan-catalog-from-snapshot.ts
23
+ function buildPlanCatalogFromSnapshot(settings, snapshot) {
24
+ const liveByPlanKey = new Map(snapshot.livePlanVersions.map((v) => [
25
+ v.planId,
26
+ v
27
+ ]));
28
+ const plans = snapshot.plans.filter((p) => p.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder).map((stem) => {
29
+ const live = liveByPlanKey.get(stem.planKey);
30
+ if (!live) {
31
+ return {
32
+ id: stem.planKey,
33
+ name: stem.label,
34
+ tagline: stem.description ?? void 0,
35
+ marketed: false,
36
+ monthlyNet: null,
37
+ yearlyNet: null,
38
+ features: [],
39
+ quotas: {}
40
+ };
41
+ }
42
+ return {
43
+ id: stem.planKey,
44
+ name: stem.label,
45
+ tagline: stem.description ?? void 0,
46
+ marketed: live.marketed,
47
+ monthlyNet: parseFloat(live.monthlyNet),
48
+ yearlyNet: parseFloat(live.yearlyNet),
49
+ features: live.features,
50
+ quotas: live.quotas ?? {}
51
+ };
52
+ });
53
+ const features = snapshot.featureEntries.filter((f) => f.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder).map((row) => ({
54
+ key: row.featureKey,
55
+ label: row.label,
56
+ icon: row.icon ?? void 0,
57
+ tier: row.tier ?? void 0,
58
+ plannedOnly: row.plannedOnly
59
+ }));
60
+ return {
61
+ schemaVersion: 1,
62
+ projectKey: settings.projectKey,
63
+ ...settings.app ? {
64
+ app: settings.app
65
+ } : {},
66
+ currency: settings.currency,
67
+ vatRate: settings.vatRate,
68
+ ...settings.marketing ? {
69
+ marketing: settings.marketing
70
+ } : {},
71
+ features,
72
+ plans
73
+ };
74
+ }
75
+ __name(buildPlanCatalogFromSnapshot, "buildPlanCatalogFromSnapshot");
76
+
77
+ // src/billing/plan-catalog.module.ts
78
+ import { Module } from "@nestjs/common";
79
+ function _ts_decorate(decorators, target, key, desc) {
80
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
81
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
82
+ 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;
83
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
84
+ }
85
+ __name(_ts_decorate, "_ts_decorate");
86
+ var PLAN_CATALOG_TOKEN = /* @__PURE__ */ Symbol("PLAN_CATALOG");
87
+ var PLAN_CATALOG_READ_SINK_TOKEN = /* @__PURE__ */ Symbol("PLAN_CATALOG_READ_SINK");
88
+ var PlanCatalogModule = class _PlanCatalogModule {
89
+ static {
90
+ __name(this, "PlanCatalogModule");
91
+ }
92
+ static forRoot(options) {
93
+ return {
94
+ module: _PlanCatalogModule,
95
+ global: options.global ?? true,
96
+ imports: options.imports ?? [],
97
+ providers: [
98
+ ...options.extraProviders ?? [],
99
+ asProvider(PLAN_CATALOG_READ_SINK_TOKEN, options.sink),
100
+ {
101
+ provide: PLAN_CATALOG_TOKEN,
102
+ useFactory: /* @__PURE__ */ __name(async (sink) => {
103
+ const snapshot = await sink.loadSnapshot(options.projectKey);
104
+ return buildPlanCatalogFromSnapshot({
105
+ projectKey: options.projectKey,
106
+ app: options.app,
107
+ currency: options.currency,
108
+ vatRate: options.vatRate,
109
+ marketing: options.marketing
110
+ }, snapshot);
111
+ }, "useFactory"),
112
+ inject: [
113
+ PLAN_CATALOG_READ_SINK_TOKEN
114
+ ]
115
+ }
116
+ ],
117
+ exports: [
118
+ PLAN_CATALOG_TOKEN,
119
+ PLAN_CATALOG_READ_SINK_TOKEN
120
+ ]
121
+ };
122
+ }
123
+ /**
124
+ * Tests / in-memory setup: takes the catalog object directly, without a
125
+ * sink/DB lookup. For unit tests + test bootstraps.
126
+ */
127
+ static forRootWithCatalog(catalog, opts = {}) {
128
+ return {
129
+ module: _PlanCatalogModule,
130
+ providers: [
131
+ {
132
+ provide: PLAN_CATALOG_TOKEN,
133
+ useValue: catalog
134
+ }
135
+ ],
136
+ exports: [
137
+ PLAN_CATALOG_TOKEN
138
+ ],
139
+ global: opts.global ?? true
140
+ };
141
+ }
142
+ };
143
+ PlanCatalogModule = _ts_decorate([
144
+ Module({})
145
+ ], PlanCatalogModule);
146
+
147
+ // src/billing/plan-helpers.ts
148
+ function findPlan(catalog, planId) {
149
+ return (catalog.plans ?? []).find((p) => p.id === planId);
150
+ }
151
+ __name(findPlan, "findPlan");
152
+ function getPlanOrThrow(catalog, planId) {
153
+ const plan = findPlan(catalog, planId);
154
+ if (!plan) {
155
+ throw new Error(`Plan "${planId}" nicht im Catalog (${catalog.projectKey}) gefunden`);
156
+ }
157
+ return plan;
158
+ }
159
+ __name(getPlanOrThrow, "getPlanOrThrow");
160
+ function getMarketedPlans(catalog) {
161
+ return (catalog.plans ?? []).filter((p) => p.marketed !== false);
162
+ }
163
+ __name(getMarketedPlans, "getMarketedPlans");
164
+ function getPlanPriceNet(catalog, planId, cycle) {
165
+ const plan = findPlan(catalog, planId);
166
+ if (!plan) return null;
167
+ if (plan.marketed === false) return null;
168
+ const net = cycle === "YEARLY" ? plan.yearlyNet : plan.monthlyNet;
169
+ return net ?? null;
170
+ }
171
+ __name(getPlanPriceNet, "getPlanPriceNet");
172
+ function getPlanPriceGross(catalog, planId, cycle, vatRate) {
173
+ const net = getPlanPriceNet(catalog, planId, cycle);
174
+ if (net === null) return null;
175
+ const rate = vatRate ?? catalog.vatRate;
176
+ return round2(net * (1 + rate / 100));
177
+ }
178
+ __name(getPlanPriceGross, "getPlanPriceGross");
179
+ function isFeatureInPlan(catalog, planId, featureKey) {
180
+ const plan = findPlan(catalog, planId);
181
+ if (!plan) return false;
182
+ return plan.features.includes(featureKey);
183
+ }
184
+ __name(isFeatureInPlan, "isFeatureInPlan");
185
+ function getPlanQuota(catalog, planId, quotaKey) {
186
+ const plan = findPlan(catalog, planId);
187
+ return plan?.quotas[quotaKey];
188
+ }
189
+ __name(getPlanQuota, "getPlanQuota");
190
+ function getActiveFeatureKeys(catalog) {
191
+ return (catalog.features ?? []).filter((f) => !f.plannedOnly).map((f) => f.key);
192
+ }
193
+ __name(getActiveFeatureKeys, "getActiveFeatureKeys");
194
+ function isFeaturePlannedOnly(catalog, featureKey) {
195
+ const def = (catalog.features ?? []).find((f) => f.key === featureKey);
196
+ return def?.plannedOnly === true;
197
+ }
198
+ __name(isFeaturePlannedOnly, "isFeaturePlannedOnly");
199
+
200
+ export {
201
+ round2,
202
+ grossFromNet,
203
+ computeIncludedVat,
204
+ buildPlanCatalogFromSnapshot,
205
+ PLAN_CATALOG_TOKEN,
206
+ PLAN_CATALOG_READ_SINK_TOKEN,
207
+ PlanCatalogModule,
208
+ findPlan,
209
+ getPlanOrThrow,
210
+ getMarketedPlans,
211
+ getPlanPriceNet,
212
+ getPlanPriceGross,
213
+ isFeatureInPlan,
214
+ getPlanQuota,
215
+ getActiveFeatureKeys,
216
+ isFeaturePlannedOnly
217
+ };
@@ -0,0 +1,6 @@
1
+ // src/subscription-contract/tokens.ts
2
+ var SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionContractRepository");
3
+
4
+ export {
5
+ SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
6
+ };