@saasicat/nest 0.8.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 (95) hide show
  1. package/dist/_entries.cjs +17678 -0
  2. package/dist/admin/index.cjs +4 -1274
  3. package/dist/admin/index.d.cts +10 -171
  4. package/dist/admin/index.d.ts +10 -171
  5. package/dist/admin/index.js +13 -4
  6. package/dist/admin-resources.module-BZN_yBFj.d.cts +40 -0
  7. package/dist/admin-resources.module-Cms8MY6h.d.ts +40 -0
  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 -5128
  11. package/dist/billing/index.d.cts +8 -719
  12. package/dist/billing/index.d.ts +8 -719
  13. package/dist/billing/index.js +7 -10
  14. package/dist/catalog/index.cjs +4 -5624
  15. package/dist/catalog/index.d.cts +7 -427
  16. package/dist/catalog/index.d.ts +7 -427
  17. package/dist/catalog/index.js +14 -12
  18. package/dist/catalog.module-BKObceo0.d.ts +103 -0
  19. package/dist/catalog.module-Dw2Hrbyu.d.cts +103 -0
  20. package/dist/checkout-offer/index.cjs +4 -807
  21. package/dist/checkout-offer/index.d.cts +5 -84
  22. package/dist/checkout-offer/index.d.ts +5 -84
  23. package/dist/checkout-offer/index.js +3 -2
  24. package/dist/checkout-offer.module-Y0hDOVrq.d.ts +85 -0
  25. package/dist/checkout-offer.module-ZYF5IsDY.d.cts +85 -0
  26. package/dist/{chunk-5DDCSGZC.js → chunk-33TP26MR.js} +144 -356
  27. package/dist/{chunk-CK54YKVK.js → chunk-75JABGZK.js} +16 -16
  28. package/dist/{chunk-HRU3CP57.js → chunk-BOADBIGB.js} +1 -1
  29. package/dist/{chunk-ZLUGQE5D.js → chunk-E7AJ42AB.js} +8 -8
  30. package/dist/{chunk-6LP7CDS2.js → chunk-F3VV6EXG.js} +18 -13
  31. package/dist/{chunk-PLDHI6NA.js → chunk-FVT7KTUI.js} +258 -3
  32. package/dist/{chunk-RZU4HIAN.js → chunk-GBDUH6DO.js} +26 -23
  33. package/dist/{chunk-K3OPNVFR.js → chunk-HBFU5AN3.js} +5 -9
  34. package/dist/chunk-HWGIMNWR.js +0 -0
  35. package/dist/chunk-IRWZC3GC.js +755 -0
  36. package/dist/chunk-L4KTQ6CL.js +20 -0
  37. package/dist/{chunk-T5SXXAKG.js → chunk-LZHLGUQI.js} +1 -1
  38. package/dist/{chunk-YHHE4KDA.js → chunk-PUXLPUVH.js} +351 -21
  39. package/dist/chunk-TXDH4FUA.js +211 -0
  40. package/dist/chunk-UF53DWWR.js +0 -0
  41. package/dist/{chunk-AJ3EPELS.js → chunk-V34AA7C7.js} +1 -1
  42. package/dist/chunk-WJ6BMDL3.js +313 -0
  43. package/dist/{chunk-M6CZQYPY.js → chunk-Z3FWN3HJ.js} +2 -2
  44. package/dist/discovery/index.cjs +4 -588
  45. package/dist/discovery/index.js +3 -3
  46. package/dist/{enforce-quota.interceptor-cv7r80VM.d.cts → enforce-quota.interceptor-BhkX5nq2.d.cts} +127 -12
  47. package/dist/{enforce-quota.interceptor-Dj1Kusr5.d.ts → enforce-quota.interceptor-CcqZc0Yu.d.ts} +127 -12
  48. package/dist/entitlement/index.cjs +4 -780
  49. package/dist/entitlement/index.d.cts +3 -1
  50. package/dist/entitlement/index.d.ts +3 -1
  51. package/dist/entitlement/index.js +2 -3
  52. package/dist/index.cjs +4 -14975
  53. package/dist/index.d.cts +22 -54
  54. package/dist/index.d.ts +22 -54
  55. package/dist/index.js +88 -369
  56. package/dist/module-CSN8j8Tp.d.cts +39 -0
  57. package/dist/module-DgYJur6f.d.ts +39 -0
  58. package/dist/plan-versions.service-BB36Q1bg.d.ts +329 -0
  59. package/dist/plan-versions.service-CUR2Whyr.d.cts +329 -0
  60. package/dist/platform/index.cjs +4 -2779
  61. package/dist/platform/index.d.cts +50 -8
  62. package/dist/platform/index.d.ts +50 -8
  63. package/dist/platform/index.js +116 -12
  64. package/dist/promo/index.cjs +4 -995
  65. package/dist/promo/index.d.cts +48 -46
  66. package/dist/promo/index.d.ts +48 -46
  67. package/dist/promo/index.js +10 -2
  68. package/dist/rate-limit.guard-dntqAlfZ.d.cts +19 -0
  69. package/dist/rate-limit.guard-dntqAlfZ.d.ts +19 -0
  70. package/dist/registration/index.cjs +4 -1655
  71. package/dist/registration/index.js +1 -1
  72. package/dist/setup.service-BPwJ7My_.d.cts +45 -0
  73. package/dist/setup.service-D2mrdDDB.d.ts +45 -0
  74. package/dist/subscription-bundle-preview.service-BkJXHAeP.d.cts +438 -0
  75. package/dist/subscription-bundle-preview.service-DBbrE_cw.d.ts +438 -0
  76. package/dist/subscription-bundles.module-BQmoozm5.d.ts +291 -0
  77. package/dist/subscription-bundles.module-DCpV-Pb5.d.cts +291 -0
  78. package/dist/subscription-contract/index.cjs +4 -471
  79. package/dist/subscription-contract/index.d.cts +5 -13
  80. package/dist/subscription-contract/index.d.ts +5 -13
  81. package/dist/subscription-contract.module-CG9fPKAr.d.cts +14 -0
  82. package/dist/subscription-contract.module-CVebZzHR.d.ts +14 -0
  83. package/dist/testing/index.cjs +4 -3756
  84. package/dist/testing/index.d.cts +9 -4
  85. package/dist/testing/index.d.ts +9 -4
  86. package/dist/testing/index.js +13 -7
  87. package/package.json +5 -5
  88. package/dist/admin-manifest.config-DyrQNT7M.d.cts +0 -10
  89. package/dist/admin-manifest.config-DyrQNT7M.d.ts +0 -10
  90. package/dist/admin-manifest.service-Cp6uwvmt.d.cts +0 -33
  91. package/dist/admin-manifest.service-UGgzR1x-.d.ts +0 -33
  92. package/dist/chunk-52UTQRD3.js +0 -326
  93. package/dist/chunk-N3WANKSA.js +0 -20
  94. package/dist/chunk-Q53N43LQ.js +0 -13
  95. /package/dist/{chunk-2FR6ZL7R.js → chunk-DBZGV3HC.js} +0 -0
@@ -1,3756 +1,4 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
- var __export = (target, all) => {
10
- for (var name in all)
11
- __defProp(target, name, { get: all[name], enumerable: true });
12
- };
13
- var __copyProps = (to, from3, except, desc) => {
14
- if (from3 && typeof from3 === "object" || typeof from3 === "function") {
15
- for (let key of __getOwnPropNames(from3))
16
- if (!__hasOwnProp.call(to, key) && key !== except)
17
- __defProp(to, key, { get: () => from3[key], enumerable: !(desc = __getOwnPropDesc(from3, key)) || desc.enumerable });
18
- }
19
- return to;
20
- };
21
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
- // If the importer is in node compatibility mode or this is not an ESM
23
- // file that has been converted to a CommonJS file using a Babel-
24
- // compatible transform (i.e. "__esModule" has not been set), then set
25
- // "default" to the CommonJS "module.exports" for node compatibility.
26
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
- mod
28
- ));
29
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
-
31
- // src/testing/index.ts
32
- var testing_exports = {};
33
- __export(testing_exports, {
34
- EnforceQuotaInterceptor: () => EnforceQuotaInterceptor,
35
- FakeBundleRepository: () => FakeBundleRepository,
36
- FakeMarketingProjectionRepository: () => FakeMarketingProjectionRepository,
37
- FakePlanRepository: () => FakePlanRepository,
38
- FakePlanVersionRepository: () => FakePlanVersionRepository,
39
- FakeSubscriptionBundleRepository: () => FakeSubscriptionBundleRepository,
40
- FakeSubscriptionContractRepository: () => FakeSubscriptionContractRepository,
41
- FakeSubscriptionRepository: () => FakeSubscriptionRepository,
42
- FakeTransactionRunner: () => FakeTransactionRunner,
43
- PLAN_RESOLVER_PORT_TOKEN: () => PLAN_RESOLVER_PORT_TOKEN,
44
- StaticEntitlementService: () => StaticEntitlementService,
45
- StaticFeatureGuard: () => StaticFeatureGuard,
46
- StaticPlanResolver: () => StaticPlanResolver,
47
- StubAuditPort: () => StubAuditPort,
48
- StubMfaPort: () => StubMfaPort,
49
- StubRlsBypassPort: () => StubRlsBypassPort,
50
- createSaasPlatformTestModule: () => createSaasPlatformTestModule
51
- });
52
- module.exports = __toCommonJS(testing_exports);
53
-
54
- // src/testing/fake-repositories.ts
55
- var import_types = require("@saasicat/types");
56
- var FakeSubscriptionRepository = class {
57
- static {
58
- __name(this, "FakeSubscriptionRepository");
59
- }
60
- byTenantId = /* @__PURE__ */ new Map();
61
- /**
62
- * Test stubs for the `countBy*` editability methods. Tests set them
63
- * directly; default 0 (= "no subscription binds the version" →
64
- * published-but-future versions are editable when they are latest-in-chain
65
- * and validFrom lies in the future).
66
- */
67
- planVersionCounts = /* @__PURE__ */ new Map();
68
- bundleVersionCounts = /* @__PURE__ */ new Map();
69
- set(record) {
70
- this.byTenantId.set(record.tenantId, record);
71
- }
72
- delete(tenantId) {
73
- this.byTenantId.delete(tenantId);
74
- }
75
- clear() {
76
- this.byTenantId.clear();
77
- this.planVersionCounts.clear();
78
- this.bundleVersionCounts.clear();
79
- }
80
- /** Test helper: force the number of subscriptions for a PlanVersion. */
81
- setPlanVersionCount(planVersionId, count) {
82
- this.planVersionCounts.set(planVersionId, count);
83
- }
84
- /** Test helper: force the number of subscriptions for a BundleVersion. */
85
- setBundleVersionCount(bundleVersionId, count) {
86
- this.bundleVersionCounts.set(bundleVersionId, count);
87
- }
88
- async findByTenantId(tenantId) {
89
- return this.byTenantId.get(tenantId) ?? null;
90
- }
91
- async findByTenantIdLocked(tenantId, _tx) {
92
- return this.findByTenantId(tenantId);
93
- }
94
- async countByPlanVersionId(planVersionId) {
95
- return this.planVersionCounts.get(planVersionId) ?? 0;
96
- }
97
- async countByBundleVersionId(bundleVersionId) {
98
- return this.bundleVersionCounts.get(bundleVersionId) ?? 0;
99
- }
100
- };
101
- var FakeSubscriptionBundleRepository = class {
102
- static {
103
- __name(this, "FakeSubscriptionBundleRepository");
104
- }
105
- byId = /* @__PURE__ */ new Map();
106
- nextId = 1;
107
- now() {
108
- return /* @__PURE__ */ new Date("2026-01-01T00:00:00.000Z");
109
- }
110
- clear() {
111
- this.byId.clear();
112
- }
113
- async listBySubscription(subscriptionId) {
114
- return [
115
- ...this.byId.values()
116
- ].filter((r) => r.subscriptionId === subscriptionId).sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime());
117
- }
118
- async findById(subscriptionBundleId) {
119
- return this.byId.get(subscriptionBundleId) ?? null;
120
- }
121
- async listActiveBySubscription(subscriptionId, asOf = /* @__PURE__ */ new Date()) {
122
- return [
123
- ...this.byId.values()
124
- ].filter((r) => r.subscriptionId === subscriptionId && (r.canceledAt === null || r.canceledEffectiveAt !== null && r.canceledEffectiveAt.getTime() > asOf.getTime()));
125
- }
126
- async add(data) {
127
- const id = `sub-bundle-${this.nextId++}`;
128
- const createdAt = this.now();
129
- const row = {
130
- id,
131
- subscriptionId: data.subscriptionId,
132
- bundleVersionId: data.bundleVersionId,
133
- startedAt: data.startedAt,
134
- minimumTermEndsAt: data.minimumTermEndsAt ?? null,
135
- canceledAt: null,
136
- canceledEffectiveAt: null,
137
- createdAt,
138
- updatedAt: createdAt
139
- };
140
- this.byId.set(id, row);
141
- return row;
142
- }
143
- async cancel(subscriptionBundleId, data) {
144
- const existing = this.byId.get(subscriptionBundleId);
145
- if (!existing) {
146
- throw new Error(`SubscriptionBundle '${subscriptionBundleId}' nicht gefunden`);
147
- }
148
- if (existing.canceledAt !== null) {
149
- throw new Error(`SubscriptionBundle '${subscriptionBundleId}' ist bereits gek\xFCndigt`);
150
- }
151
- const updated = {
152
- ...existing,
153
- canceledAt: data.canceledAt,
154
- canceledEffectiveAt: data.canceledEffectiveAt,
155
- updatedAt: this.now()
156
- };
157
- this.byId.set(subscriptionBundleId, updated);
158
- return updated;
159
- }
160
- async reactivate(subscriptionBundleId) {
161
- const existing = this.byId.get(subscriptionBundleId);
162
- if (!existing) {
163
- throw new Error(`SubscriptionBundle '${subscriptionBundleId}' nicht gefunden`);
164
- }
165
- const updated = {
166
- ...existing,
167
- canceledAt: null,
168
- canceledEffectiveAt: null,
169
- updatedAt: this.now()
170
- };
171
- this.byId.set(subscriptionBundleId, updated);
172
- return updated;
173
- }
174
- async countActiveByBundleVersionId(bundleVersionId, asOf = /* @__PURE__ */ new Date()) {
175
- let n = 0;
176
- for (const r of this.byId.values()) {
177
- if (r.bundleVersionId !== bundleVersionId) continue;
178
- if (r.canceledAt === null) n += 1;
179
- else if (r.canceledEffectiveAt !== null && r.canceledEffectiveAt.getTime() > asOf.getTime()) {
180
- n += 1;
181
- }
182
- }
183
- return n;
184
- }
185
- };
186
- var FakeSubscriptionContractRepository = class {
187
- static {
188
- __name(this, "FakeSubscriptionContractRepository");
189
- }
190
- byId = /* @__PURE__ */ new Map();
191
- nextContractId = 1;
192
- nextLineItemId = 1;
193
- clear() {
194
- this.byId.clear();
195
- this.nextContractId = 1;
196
- this.nextLineItemId = 1;
197
- }
198
- async list(filter) {
199
- return [
200
- ...this.byId.values()
201
- ].filter((row) => !filter.projectKey || row.projectKey === filter.projectKey).filter((row) => !filter.tenantId || row.tenantId === filter.tenantId).filter((row) => !filter.status || row.status === filter.status).filter((row) => !filter.asOf || this.isActiveAt(row, filter.asOf)).sort((a, b) => b.effectiveFrom.getTime() - a.effectiveFrom.getTime()).map((row) => this.cloneRecord(row));
202
- }
203
- async findById(contractId) {
204
- const row = this.byId.get(contractId);
205
- return row ? this.cloneRecord(row) : null;
206
- }
207
- async findActiveByTenantId(tenantId, asOf = /* @__PURE__ */ new Date()) {
208
- const active = [
209
- ...this.byId.values()
210
- ].filter((row) => row.tenantId === tenantId).filter((row) => this.isActiveAt(row, asOf)).sort((a, b) => b.effectiveFrom.getTime() - a.effectiveFrom.getTime())[0];
211
- return active ? this.cloneRecord(active) : null;
212
- }
213
- async create(data) {
214
- const now = /* @__PURE__ */ new Date();
215
- const id = `contract-${this.nextContractId++}`;
216
- const row = {
217
- id,
218
- projectKey: data.projectKey,
219
- tenantId: data.tenantId,
220
- status: data.status ?? "active",
221
- effectiveFrom: new Date(data.effectiveFrom),
222
- effectiveUntil: data.effectiveUntil ? new Date(data.effectiveUntil) : null,
223
- originalOfferId: data.originalOfferId ?? null,
224
- originalPlanVersionId: data.originalPlanVersionId ?? null,
225
- originalBundleVersionIds: [
226
- ...data.originalBundleVersionIds ?? []
227
- ],
228
- entitlementSnapshot: data.entitlementSnapshot ? {
229
- plan: data.entitlementSnapshot.plan,
230
- quotas: {
231
- ...data.entitlementSnapshot.quotas
232
- },
233
- features: [
234
- ...data.entitlementSnapshot.features
235
- ]
236
- } : null,
237
- priceSnapshot: {
238
- ...data.priceSnapshot
239
- },
240
- promotionSnapshots: [
241
- ...data.promotionSnapshots ?? []
242
- ],
243
- promoCodeSnapshots: [
244
- ...data.promoCodeSnapshots ?? []
245
- ],
246
- termsSnapshot: data.termsSnapshot ? {
247
- ...data.termsSnapshot
248
- } : null,
249
- lineItems: data.lineItems.map((item) => ({
250
- ...item,
251
- id: `contract-line-${this.nextLineItemId++}`,
252
- contractId: id,
253
- minimumTermUntil: item.minimumTermUntil ? new Date(item.minimumTermUntil) : null,
254
- featuresSnapshot: [
255
- ...item.featuresSnapshot
256
- ],
257
- quotaEffectsSnapshot: {
258
- ...item.quotaEffectsSnapshot
259
- },
260
- metadata: item.metadata ? {
261
- ...item.metadata
262
- } : null,
263
- createdAt: now
264
- })),
265
- createdAt: now,
266
- updatedAt: now
267
- };
268
- this.byId.set(id, row);
269
- return this.cloneRecord(row);
270
- }
271
- async terminate(contractId, data) {
272
- const existing = this.byId.get(contractId);
273
- if (!existing) throw new Error(`SubscriptionContract '${contractId}' nicht gefunden`);
274
- const updated = {
275
- ...existing,
276
- status: data.status,
277
- effectiveUntil: new Date(data.effectiveUntil),
278
- updatedAt: /* @__PURE__ */ new Date()
279
- };
280
- this.byId.set(contractId, updated);
281
- return this.cloneRecord(updated);
282
- }
283
- isActiveAt(row, asOf) {
284
- if (row.status !== "active" && row.status !== "scheduled") return false;
285
- return row.effectiveFrom.getTime() <= asOf.getTime() && (row.effectiveUntil === null || row.effectiveUntil.getTime() > asOf.getTime());
286
- }
287
- cloneRecord(row) {
288
- return {
289
- ...row,
290
- effectiveFrom: new Date(row.effectiveFrom),
291
- effectiveUntil: row.effectiveUntil ? new Date(row.effectiveUntil) : null,
292
- originalBundleVersionIds: [
293
- ...row.originalBundleVersionIds
294
- ],
295
- entitlementSnapshot: row.entitlementSnapshot ? {
296
- plan: row.entitlementSnapshot.plan,
297
- quotas: {
298
- ...row.entitlementSnapshot.quotas
299
- },
300
- features: [
301
- ...row.entitlementSnapshot.features
302
- ]
303
- } : null,
304
- priceSnapshot: {
305
- ...row.priceSnapshot
306
- },
307
- promotionSnapshots: [
308
- ...row.promotionSnapshots
309
- ],
310
- promoCodeSnapshots: [
311
- ...row.promoCodeSnapshots
312
- ],
313
- termsSnapshot: row.termsSnapshot ? {
314
- ...row.termsSnapshot
315
- } : null,
316
- lineItems: row.lineItems.map((item) => ({
317
- ...item,
318
- minimumTermUntil: item.minimumTermUntil ? new Date(item.minimumTermUntil) : null,
319
- featuresSnapshot: [
320
- ...item.featuresSnapshot
321
- ],
322
- quotaEffectsSnapshot: {
323
- ...item.quotaEffectsSnapshot
324
- },
325
- metadata: item.metadata ? {
326
- ...item.metadata
327
- } : null,
328
- createdAt: new Date(item.createdAt)
329
- })),
330
- createdAt: new Date(row.createdAt),
331
- updatedAt: new Date(row.updatedAt)
332
- };
333
- }
334
- };
335
- var FakePlanVersionRepository = class {
336
- static {
337
- __name(this, "FakePlanVersionRepository");
338
- }
339
- byPlanId = /* @__PURE__ */ new Map();
340
- set(record) {
341
- this.byPlanId.set(record.planId, record);
342
- }
343
- clear() {
344
- this.byPlanId.clear();
345
- }
346
- async findLatestLive(planId, _tx) {
347
- return this.byPlanId.get(planId) ?? null;
348
- }
349
- // The fake keeps only one record per planId — `findActive` returns the
350
- // same as `findLatestLive`. Consumers test real time-window semantics
351
- // against the Prisma implementation or against `FakePlanRepository`
352
- // (which supports multiple versions per planKey).
353
- async findActive(planId, _asOf, _tx) {
354
- return this.byPlanId.get(planId) ?? null;
355
- }
356
- };
357
- var FakeTransactionRunner = class _FakeTransactionRunner {
358
- static {
359
- __name(this, "FakeTransactionRunner");
360
- }
361
- /** Sentinel value passed through as the TransactionContext. */
362
- static TX_SENTINEL = /* @__PURE__ */ Symbol.for("FakeTransactionRunner.tx");
363
- runCount = 0;
364
- async run(fn) {
365
- this.runCount += 1;
366
- return fn(_FakeTransactionRunner.TX_SENTINEL);
367
- }
368
- };
369
- var FakeBundleRepository = class {
370
- static {
371
- __name(this, "FakeBundleRepository");
372
- }
373
- bundles = /* @__PURE__ */ new Map();
374
- versions = /* @__PURE__ */ new Map();
375
- nextId = 1;
376
- genId(prefix) {
377
- const n = this.nextId++;
378
- const suffix = n.toString(16).padStart(12, "0");
379
- return `${prefix}aaaa-aaaa-aaaa-aaaa-${suffix}`;
380
- }
381
- nowIso() {
382
- return (/* @__PURE__ */ new Date()).toISOString();
383
- }
384
- // Test helpers
385
- seedBundle(row) {
386
- this.bundles.set(row.id, row);
387
- }
388
- seedVersion(row) {
389
- this.versions.set(row.id, row);
390
- }
391
- clear() {
392
- this.bundles.clear();
393
- this.versions.clear();
394
- }
395
- // ─── Master operations ───
396
- async list(filter) {
397
- const excludeDeleted = filter.excludeDeleted ?? true;
398
- return [
399
- ...this.bundles.values()
400
- ].filter((b) => b.projectKey === filter.projectKey && (!excludeDeleted || b.deletedAt === null));
401
- }
402
- async findById(bundleId) {
403
- return this.bundles.get(bundleId) ?? null;
404
- }
405
- async findByKey(projectKey, bundleKey) {
406
- for (const b of this.bundles.values()) {
407
- if (b.projectKey === projectKey && b.bundleKey === bundleKey && b.deletedAt === null) {
408
- return b;
409
- }
410
- }
411
- return null;
412
- }
413
- async create(data) {
414
- const now = this.nowIso();
415
- const row = {
416
- id: this.genId("bbbb"),
417
- projectKey: data.projectKey,
418
- bundleKey: data.bundleKey,
419
- label: data.label,
420
- description: data.description ?? null,
421
- icon: data.icon ?? null,
422
- sortOrder: data.sortOrder ?? 0,
423
- i18n: data.i18n ?? {},
424
- createdAt: now,
425
- updatedAt: now,
426
- deletedAt: null
427
- };
428
- this.bundles.set(row.id, row);
429
- return row;
430
- }
431
- async update(bundleId, data) {
432
- const existing = this.bundles.get(bundleId);
433
- if (!existing) throw new Error(`Bundle '${bundleId}' nicht gefunden`);
434
- const updated = {
435
- ...existing,
436
- label: data.label ?? existing.label,
437
- description: data.description !== void 0 ? data.description : existing.description,
438
- icon: data.icon !== void 0 ? data.icon : existing.icon,
439
- sortOrder: data.sortOrder ?? existing.sortOrder,
440
- i18n: data.i18n ?? existing.i18n,
441
- updatedAt: this.nowIso()
442
- };
443
- this.bundles.set(bundleId, updated);
444
- return updated;
445
- }
446
- async softDelete(bundleId) {
447
- const existing = this.bundles.get(bundleId);
448
- if (!existing) throw new Error(`Bundle '${bundleId}' nicht gefunden`);
449
- this.bundles.set(bundleId, {
450
- ...existing,
451
- deletedAt: this.nowIso()
452
- });
453
- }
454
- // ─── Version operations ───
455
- async listVersions(bundleId) {
456
- return [
457
- ...this.versions.values()
458
- ].filter((v) => v.bundleId === bundleId).sort((a, b) => a.version - b.version);
459
- }
460
- async findVersionById(versionId) {
461
- return this.versions.get(versionId) ?? null;
462
- }
463
- async findCurrentDraft(bundleId) {
464
- for (const v of this.versions.values()) {
465
- if (v.bundleId === bundleId && v.publishedAt === null) return v;
466
- }
467
- return null;
468
- }
469
- async findLatestLive(bundleId) {
470
- for (const v of this.versions.values()) {
471
- if (v.bundleId === bundleId && v.publishedAt !== null && v.supersededAt === null) {
472
- return v;
473
- }
474
- }
475
- return null;
476
- }
477
- async createDraft(data) {
478
- const draft = await this.findCurrentDraft(data.bundleId);
479
- if (draft) {
480
- throw new Error(`Bundle '${data.bundleId}' hat bereits eine Draft-Version v${draft.version}`);
481
- }
482
- const all = await this.listVersions(data.bundleId);
483
- const nextVersion = all.length === 0 ? 1 : Math.max(...all.map((v) => v.version)) + 1;
484
- const now = this.nowIso();
485
- const row = {
486
- id: this.genId("vvvv"),
487
- version: nextVersion,
488
- baseVersionId: data.baseVersionId ?? null,
489
- publishedAt: null,
490
- supersededAt: null,
491
- publishedChanges: null,
492
- changeNote: data.changeNote ?? "",
493
- nonRegressive: true,
494
- createdByUserId: data.createdByUserId ?? null,
495
- publishedByUserId: null,
496
- validFrom: data.validFrom ?? null,
497
- validUntil: data.validUntil ?? null,
498
- createdAt: now,
499
- updatedAt: now,
500
- bundleId: data.bundleId,
501
- bundleKey: this.bundles.get(data.bundleId)?.bundleKey ?? "",
502
- label: this.bundles.get(data.bundleId)?.label ?? "",
503
- features: data.features,
504
- quotas: data.quotas ?? {},
505
- compatibility: data.compatibility ?? {},
506
- pricingOverrides: data.pricingOverrides ?? [],
507
- monthlyNet: data.monthlyNet ?? null,
508
- yearlyNet: data.yearlyNet ?? null,
509
- marketed: data.marketed ?? true
510
- };
511
- this.versions.set(row.id, row);
512
- return row;
513
- }
514
- async updateDraft(versionId, data) {
515
- const existing = this.versions.get(versionId);
516
- if (!existing) throw new Error(`BundleVersion '${versionId}' nicht gefunden`);
517
- const updated = {
518
- ...existing,
519
- features: data.features ?? existing.features,
520
- quotas: data.quotas ?? existing.quotas,
521
- compatibility: data.compatibility ?? existing.compatibility,
522
- pricingOverrides: data.pricingOverrides ?? existing.pricingOverrides,
523
- monthlyNet: data.monthlyNet !== void 0 ? data.monthlyNet : existing.monthlyNet,
524
- yearlyNet: data.yearlyNet !== void 0 ? data.yearlyNet : existing.yearlyNet,
525
- marketed: data.marketed ?? existing.marketed,
526
- changeNote: data.changeNote ?? existing.changeNote,
527
- validFrom: data.validFrom !== void 0 ? data.validFrom : existing.validFrom,
528
- validUntil: data.validUntil !== void 0 ? data.validUntil : existing.validUntil,
529
- updatedAt: this.nowIso()
530
- };
531
- this.versions.set(versionId, updated);
532
- return updated;
533
- }
534
- async deleteDraft(versionId) {
535
- const existing = this.versions.get(versionId);
536
- if (!existing) return;
537
- if (existing.publishedAt !== null) {
538
- throw new Error(`BundleVersion '${versionId}' ist bereits published`);
539
- }
540
- this.versions.delete(versionId);
541
- }
542
- async publishDraft(versionId, publishMeta) {
543
- const draft = this.versions.get(versionId);
544
- if (!draft) throw new Error(`BundleVersion '${versionId}' nicht gefunden`);
545
- if (draft.publishedAt !== null) {
546
- throw new Error(`BundleVersion '${versionId}' ist bereits published`);
547
- }
548
- const now = this.nowIso();
549
- const validFromIso = publishMeta.validFrom.toISOString();
550
- const validUntilIso = publishMeta.validUntil ? publishMeta.validUntil.toISOString() : null;
551
- const previous = await this.findLatestLive(draft.bundleId);
552
- if (previous) {
553
- const dayMs = 24 * 60 * 60 * 1e3;
554
- const inheritedValidUntil = new Date(publishMeta.validFrom.getTime() - dayMs).toISOString();
555
- this.versions.set(previous.id, {
556
- ...previous,
557
- supersededAt: now,
558
- validUntil: inheritedValidUntil,
559
- updatedAt: now
560
- });
561
- }
562
- const published = {
563
- ...draft,
564
- publishedAt: now,
565
- publishedChanges: publishMeta.publishedChanges,
566
- nonRegressive: publishMeta.nonRegressive,
567
- publishedByUserId: publishMeta.publishedByUserId,
568
- validFrom: validFromIso,
569
- validUntil: validUntilIso,
570
- updatedAt: now
571
- };
572
- this.versions.set(versionId, published);
573
- return published;
574
- }
575
- };
576
- var FakeMarketingProjectionRepository = class {
577
- static {
578
- __name(this, "FakeMarketingProjectionRepository");
579
- }
580
- rows = /* @__PURE__ */ new Map();
581
- nextId = 1;
582
- genId() {
583
- const n = this.nextId++;
584
- const suffix = n.toString(16).padStart(12, "0");
585
- return `mmmmaaaa-aaaa-aaaa-aaaa-${suffix}`;
586
- }
587
- nowIso() {
588
- return (/* @__PURE__ */ new Date()).toISOString();
589
- }
590
- seed(row) {
591
- this.rows.set(row.id, row);
592
- }
593
- clear() {
594
- this.rows.clear();
595
- }
596
- async list(filter) {
597
- return [
598
- ...this.rows.values()
599
- ].filter((r) => {
600
- if (r.projectKey !== filter.projectKey) return false;
601
- if (filter.targetType && r.targetType !== filter.targetType) return false;
602
- if (filter.targetVersionId && r.targetVersionId !== filter.targetVersionId) return false;
603
- if (filter.locale && r.locale !== filter.locale) return false;
604
- return true;
605
- });
606
- }
607
- async findById(id) {
608
- return this.rows.get(id) ?? null;
609
- }
610
- async findByTarget(targetType, targetVersionId, locale) {
611
- for (const r of this.rows.values()) {
612
- if (r.targetType === targetType && r.targetVersionId === targetVersionId && r.locale === locale) {
613
- return r;
614
- }
615
- }
616
- return null;
617
- }
618
- async create(data) {
619
- const locale = data.locale ?? "de";
620
- const existing = await this.findByTarget(data.targetType, data.targetVersionId, locale);
621
- if (existing) {
622
- throw new Error(`MarketingProjection f\xFCr ${data.targetType}/${data.targetVersionId}/${locale} existiert bereits`);
623
- }
624
- const now = this.nowIso();
625
- const row = {
626
- id: this.genId(),
627
- projectKey: data.projectKey,
628
- targetType: data.targetType,
629
- targetVersionId: data.targetVersionId,
630
- locale,
631
- displayLabel: data.displayLabel,
632
- description: data.description,
633
- visible: data.visible ?? true,
634
- badge: data.badge ?? "",
635
- topFeatures: data.topFeatures ?? [],
636
- trialEnabled: data.trialEnabled ?? false,
637
- trialDays: data.trialDays ?? 30,
638
- priceTag: data.priceTag ?? null,
639
- ctaLabel: data.ctaLabel ?? null,
640
- priority: data.priority ?? 0,
641
- highlight: data.highlight ?? false,
642
- createdAt: now,
643
- updatedAt: now
644
- };
645
- this.rows.set(row.id, row);
646
- return row;
647
- }
648
- async update(id, data) {
649
- const existing = this.rows.get(id);
650
- if (!existing) throw new Error(`MarketingProjection '${id}' nicht gefunden`);
651
- const updated = {
652
- ...existing,
653
- displayLabel: data.displayLabel ?? existing.displayLabel,
654
- description: data.description ?? existing.description,
655
- visible: data.visible ?? existing.visible,
656
- badge: data.badge ?? existing.badge,
657
- topFeatures: data.topFeatures ?? existing.topFeatures,
658
- trialEnabled: data.trialEnabled ?? existing.trialEnabled,
659
- trialDays: data.trialDays ?? existing.trialDays,
660
- priceTag: data.priceTag !== void 0 ? data.priceTag : existing.priceTag,
661
- ctaLabel: data.ctaLabel !== void 0 ? data.ctaLabel : existing.ctaLabel,
662
- priority: data.priority ?? existing.priority,
663
- highlight: data.highlight ?? existing.highlight,
664
- updatedAt: this.nowIso()
665
- };
666
- this.rows.set(id, updated);
667
- return updated;
668
- }
669
- async delete(id) {
670
- if (!this.rows.has(id)) throw new Error(`MarketingProjection '${id}' nicht gefunden`);
671
- this.rows.delete(id);
672
- }
673
- };
674
- var FakePlanRepository = class {
675
- static {
676
- __name(this, "FakePlanRepository");
677
- }
678
- plans = /* @__PURE__ */ new Map();
679
- versions = /* @__PURE__ */ new Map();
680
- nextId = 1;
681
- nextVersionId = 1;
682
- genId() {
683
- const n = this.nextId++;
684
- const suffix = n.toString(16).padStart(12, "0");
685
- return `dddd-aaaa-aaaa-aaaa-aaaa-${suffix}`;
686
- }
687
- genVersionId() {
688
- const n = this.nextVersionId++;
689
- const suffix = n.toString(16).padStart(12, "0");
690
- return `eeee-aaaa-aaaa-aaaa-aaaa-${suffix}`;
691
- }
692
- nowIso() {
693
- return (/* @__PURE__ */ new Date()).toISOString();
694
- }
695
- seed(row) {
696
- this.plans.set(row.id, row);
697
- }
698
- seedVersion(row) {
699
- this.versions.set(row.id, row);
700
- }
701
- clear() {
702
- this.plans.clear();
703
- this.versions.clear();
704
- }
705
- async list(filter) {
706
- const excludeDeleted = filter.excludeDeleted ?? true;
707
- const liveKeys = filter.onlyPublished ? new Set([
708
- ...this.versions.values()
709
- ].filter((v) => v.publishedAt != null && v.supersededAt == null).map((v) => v.planId)) : null;
710
- return [
711
- ...this.plans.values()
712
- ].filter((p) => p.projectKey === filter.projectKey && (!excludeDeleted || p.deletedAt === null) && (!liveKeys || liveKeys.has(p.planKey)));
713
- }
714
- async findById(planId) {
715
- return this.plans.get(planId) ?? null;
716
- }
717
- async findByKey(projectKey, planKey) {
718
- for (const p of this.plans.values()) {
719
- if (p.projectKey === projectKey && p.planKey === planKey && p.deletedAt === null) {
720
- return p;
721
- }
722
- }
723
- return null;
724
- }
725
- async create(data) {
726
- const now = this.nowIso();
727
- const row = {
728
- id: this.genId(),
729
- projectKey: data.projectKey,
730
- planKey: data.planKey,
731
- label: data.label,
732
- description: data.description ?? null,
733
- icon: data.icon ?? null,
734
- sortOrder: data.sortOrder ?? 0,
735
- createdAt: now,
736
- updatedAt: now,
737
- deletedAt: null
738
- };
739
- this.plans.set(row.id, row);
740
- return row;
741
- }
742
- async update(planId, data) {
743
- const existing = this.plans.get(planId);
744
- if (!existing) throw new Error(`Plan '${planId}' nicht gefunden`);
745
- const updated = {
746
- ...existing,
747
- label: data.label ?? existing.label,
748
- description: data.description ?? existing.description,
749
- icon: data.icon ?? existing.icon,
750
- sortOrder: data.sortOrder ?? existing.sortOrder,
751
- updatedAt: this.nowIso()
752
- };
753
- this.plans.set(planId, updated);
754
- return updated;
755
- }
756
- async softDelete(planId) {
757
- const existing = this.plans.get(planId);
758
- if (!existing) throw new Error(`Plan '${planId}' nicht gefunden`);
759
- const updated = {
760
- ...existing,
761
- deletedAt: this.nowIso(),
762
- updatedAt: this.nowIso()
763
- };
764
- this.plans.set(planId, updated);
765
- }
766
- async hardDelete(planId) {
767
- const existing = this.plans.get(planId);
768
- if (!existing) return;
769
- this.plans.delete(planId);
770
- for (const [id, v] of this.versions.entries()) {
771
- if (v.planId === existing.planKey) this.versions.delete(id);
772
- }
773
- }
774
- // ─── Lifecycle (SPEC_V2 §11.1 M6 Pack 2a) ───
775
- // Binding Plan ↔ PlanVersion: PlanVersion.planId === Plan.planKey
776
- // (soft string reference). Lifecycle methods take planKey
777
- // as input — the service resolves the plan UUID beforehand.
778
- async listVersions(planKey) {
779
- return [
780
- ...this.versions.values()
781
- ].filter((v) => v.planId === planKey).sort((a, b) => a.version - b.version);
782
- }
783
- async findVersionById(versionId) {
784
- return this.versions.get(versionId) ?? null;
785
- }
786
- async findCurrentDraft(planKey) {
787
- for (const v of this.versions.values()) {
788
- if (v.planId === planKey && v.publishedAt === null) return v;
789
- }
790
- return null;
791
- }
792
- async findLatestLivePlanVersion(planKey) {
793
- const now = Date.now();
794
- const live = [
795
- ...this.versions.values()
796
- ].filter((v) => {
797
- if (v.planId !== planKey) return false;
798
- if (v.publishedAt === null) return false;
799
- if (v.supersededAt !== null) return false;
800
- if (v.endsAt) {
801
- const ends = new Date(v.endsAt).getTime();
802
- if (!Number.isNaN(ends) && ends <= now) return false;
803
- }
804
- return true;
805
- });
806
- live.sort((a, b) => b.version - a.version);
807
- return live[0] ?? null;
808
- }
809
- async findActivePlanVersion(planKey, asOf = /* @__PURE__ */ new Date()) {
810
- const t = asOf.getTime();
811
- const dayStart = (0, import_types.startOfUtcDay)(asOf).getTime();
812
- const matches = [
813
- ...this.versions.values()
814
- ].filter((v) => {
815
- if (v.planId !== planKey) return false;
816
- if (v.publishedAt === null) return false;
817
- if (v.validFrom) {
818
- const from3 = new Date(v.validFrom).getTime();
819
- if (Number.isNaN(from3) || from3 > t) return false;
820
- }
821
- if (v.validUntil) {
822
- const until = new Date(v.validUntil).getTime();
823
- if (!Number.isNaN(until) && until < dayStart) return false;
824
- }
825
- if (v.endsAt) {
826
- const ends = new Date(v.endsAt).getTime();
827
- if (!Number.isNaN(ends) && ends <= t) return false;
828
- }
829
- return true;
830
- });
831
- matches.sort((a, b) => {
832
- const fa = a.validFrom ? new Date(a.validFrom).getTime() : 0;
833
- const fb = b.validFrom ? new Date(b.validFrom).getTime() : 0;
834
- if (fb !== fa) return fb - fa;
835
- return b.version - a.version;
836
- });
837
- return matches[0] ?? null;
838
- }
839
- async createPlanVersionDraft(data) {
840
- const planKey = data.planId;
841
- const versions = [
842
- ...this.versions.values()
843
- ].filter((v) => v.planId === planKey);
844
- const nextVersion = versions.reduce((max, v) => Math.max(max, v.version), 0) + 1;
845
- const now = this.nowIso();
846
- const row = {
847
- id: this.genVersionId(),
848
- planId: planKey,
849
- version: nextVersion,
850
- baseVersionId: data.baseVersionId ?? null,
851
- features: data.features,
852
- bundles: data.bundles ?? [],
853
- quotas: data.quotas,
854
- monthlyNet: data.monthlyNet,
855
- yearlyNet: data.yearlyNet,
856
- marketed: data.marketed ?? true,
857
- publishedAt: null,
858
- supersededAt: null,
859
- publishedChanges: null,
860
- changeNote: data.changeNote ?? "",
861
- nonRegressive: true,
862
- validFrom: data.validFrom ?? null,
863
- validUntil: data.validUntil ?? null,
864
- endsAt: null,
865
- createdByUserId: data.createdByUserId ?? null,
866
- publishedByUserId: null,
867
- createdAt: now,
868
- updatedAt: now
869
- };
870
- this.versions.set(row.id, row);
871
- return row;
872
- }
873
- async updatePlanVersionDraft(versionId, data) {
874
- const existing = this.versions.get(versionId);
875
- if (!existing) throw new Error(`PlanVersion '${versionId}' nicht gefunden`);
876
- const updated = {
877
- ...existing,
878
- features: data.features ?? existing.features,
879
- bundles: data.bundles ?? existing.bundles ?? [],
880
- quotas: data.quotas ?? existing.quotas,
881
- monthlyNet: data.monthlyNet ?? existing.monthlyNet,
882
- yearlyNet: data.yearlyNet ?? existing.yearlyNet,
883
- marketed: data.marketed ?? existing.marketed,
884
- changeNote: data.changeNote ?? existing.changeNote,
885
- updatedAt: this.nowIso()
886
- };
887
- this.versions.set(versionId, updated);
888
- return updated;
889
- }
890
- async publishPlanVersionDraft(versionId, publishMeta) {
891
- const draft = this.versions.get(versionId);
892
- if (!draft) throw new Error(`PlanVersion '${versionId}' nicht gefunden`);
893
- const planKey = draft.planId;
894
- const now = this.nowIso();
895
- const dayMs = 24 * 60 * 60 * 1e3;
896
- const inheritedValidUntil = new Date(publishMeta.validFrom.getTime() - dayMs).toISOString();
897
- for (const v of this.versions.values()) {
898
- if (v.planId === planKey && v.publishedAt !== null && v.supersededAt === null && v.id !== versionId) {
899
- this.versions.set(v.id, {
900
- ...v,
901
- supersededAt: now,
902
- validUntil: v.validUntil ?? inheritedValidUntil,
903
- updatedAt: now
904
- });
905
- }
906
- }
907
- const published = {
908
- ...draft,
909
- publishedAt: now,
910
- publishedChanges: publishMeta.publishedChanges,
911
- nonRegressive: publishMeta.nonRegressive,
912
- publishedByUserId: publishMeta.publishedByUserId,
913
- validFrom: publishMeta.validFrom.toISOString(),
914
- validUntil: publishMeta.validUntil ? publishMeta.validUntil.toISOString() : null,
915
- updatedAt: now
916
- };
917
- this.versions.set(versionId, published);
918
- return published;
919
- }
920
- async deletePlanVersionDraft(versionId) {
921
- const existing = this.versions.get(versionId);
922
- if (!existing) return;
923
- if (existing.publishedAt !== null) {
924
- throw new Error(`FakePlanRepository: PlanVersion '${versionId}' ist bereits published \u2014 Discard nicht erlaubt`);
925
- }
926
- this.versions.delete(versionId);
927
- }
928
- async terminate(versionId, endsAt) {
929
- const existing = this.versions.get(versionId);
930
- if (!existing) {
931
- throw new Error(`FakePlanRepository: PlanVersion '${versionId}' nicht gefunden`);
932
- }
933
- const updated = {
934
- ...existing,
935
- endsAt: endsAt.toISOString(),
936
- updatedAt: this.nowIso()
937
- };
938
- this.versions.set(versionId, updated);
939
- return updated;
940
- }
941
- };
942
-
943
- // src/testing/create-saas-platform-test-module.ts
944
- var import_common23 = require("@nestjs/common");
945
-
946
- // src/platform/saas-platform.module.ts
947
- var import_common22 = require("@nestjs/common");
948
- var import_core6 = require("@nestjs/core");
949
- var import_types2 = require("@saasicat/types");
950
-
951
- // src/core/di.ts
952
- function asProvider(token, impl) {
953
- if (typeof impl === "object" && impl !== null && "useFactory" in impl && typeof impl.useFactory === "function") {
954
- const factory = impl;
955
- return {
956
- provide: token,
957
- useFactory: factory.useFactory,
958
- inject: factory.inject
959
- };
960
- }
961
- return {
962
- provide: token,
963
- useValue: impl
964
- };
965
- }
966
- __name(asProvider, "asProvider");
967
-
968
- // src/admin/module.ts
969
- var import_common6 = require("@nestjs/common");
970
-
971
- // src/admin/admin-audit.service.ts
972
- var os = __toESM(require("os"), 1);
973
- var import_common = require("@nestjs/common");
974
-
975
- // src/admin/tokens.ts
976
- var MFA_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/MfaPort");
977
- var AUDIT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/AuditPort");
978
- var RLS_BYPASS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/RlsBypassPort");
979
-
980
- // src/admin/admin-audit.service.ts
981
- function _ts_decorate(decorators, target, key, desc) {
982
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
983
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
984
- 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;
985
- return c > 3 && r && Object.defineProperty(target, key, r), r;
986
- }
987
- __name(_ts_decorate, "_ts_decorate");
988
- function _ts_metadata(k, v) {
989
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
990
- }
991
- __name(_ts_metadata, "_ts_metadata");
992
- function _ts_param(paramIndex, decorator) {
993
- return function(target, key) {
994
- decorator(target, key, paramIndex);
995
- };
996
- }
997
- __name(_ts_param, "_ts_param");
998
- var AdminAuditService = class {
999
- static {
1000
- __name(this, "AdminAuditService");
1001
- }
1002
- audit;
1003
- constructor(audit) {
1004
- this.audit = audit;
1005
- }
1006
- async log(input) {
1007
- await this.audit.write({
1008
- actor: input.actor,
1009
- entity: input.entity,
1010
- entityId: input.entityId,
1011
- action: input.action,
1012
- changes: {
1013
- ...input.changes ?? {},
1014
- actor: this.actorTag(input.actor)
1015
- }
1016
- });
1017
- }
1018
- /**
1019
- * Standardized actor tag for the `changes.actor` column:
1020
- * `<source>:<email>:<context>`.
1021
- */
1022
- actorTag(actor) {
1023
- return `${actor.source}:${actor.email}:${actor.context}`;
1024
- }
1025
- /** Helper: build an AdminActor from an authenticated web request. */
1026
- fromWebRequest(user, sessionId) {
1027
- return {
1028
- userId: user.id,
1029
- email: user.email,
1030
- source: "web",
1031
- context: sessionId ?? "unknown"
1032
- };
1033
- }
1034
- /** Helper: build an AdminActor from a CLI identity (hostname as context). */
1035
- fromCli(user) {
1036
- return {
1037
- userId: user.id,
1038
- email: user.email,
1039
- source: "cli",
1040
- context: os.hostname()
1041
- };
1042
- }
1043
- };
1044
- AdminAuditService = _ts_decorate([
1045
- (0, import_common.Injectable)(),
1046
- _ts_param(0, (0, import_common.Inject)(AUDIT_PORT_TOKEN)),
1047
- _ts_metadata("design:type", Function),
1048
- _ts_metadata("design:paramtypes", [
1049
- typeof AuditPort === "undefined" ? Object : AuditPort
1050
- ])
1051
- ], AdminAuditService);
1052
-
1053
- // src/admin/admin-bypass-rls.interceptor.ts
1054
- var import_common2 = require("@nestjs/common");
1055
- var import_rxjs = require("rxjs");
1056
- var import_operators = require("rxjs/operators");
1057
- function _ts_decorate2(decorators, target, key, desc) {
1058
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1059
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1060
- 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;
1061
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1062
- }
1063
- __name(_ts_decorate2, "_ts_decorate");
1064
- function _ts_metadata2(k, v) {
1065
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1066
- }
1067
- __name(_ts_metadata2, "_ts_metadata");
1068
- function _ts_param2(paramIndex, decorator) {
1069
- return function(target, key) {
1070
- decorator(target, key, paramIndex);
1071
- };
1072
- }
1073
- __name(_ts_param2, "_ts_param");
1074
- var AdminBypassRlsInterceptor = class {
1075
- static {
1076
- __name(this, "AdminBypassRlsInterceptor");
1077
- }
1078
- port;
1079
- constructor(port) {
1080
- this.port = port;
1081
- }
1082
- intercept(_context, next) {
1083
- return (0, import_rxjs.defer)(() => (0, import_rxjs.from)(this.port.runWithBypass(async () => {
1084
- return next.handle();
1085
- })).pipe((0, import_operators.switchAll)()));
1086
- }
1087
- };
1088
- AdminBypassRlsInterceptor = _ts_decorate2([
1089
- (0, import_common2.Injectable)(),
1090
- _ts_param2(0, (0, import_common2.Inject)(RLS_BYPASS_PORT_TOKEN)),
1091
- _ts_metadata2("design:type", Function),
1092
- _ts_metadata2("design:paramtypes", [
1093
- typeof RlsBypassPort === "undefined" ? Object : RlsBypassPort
1094
- ])
1095
- ], AdminBypassRlsInterceptor);
1096
-
1097
- // src/admin/mfa.ts
1098
- var import_common3 = require("@nestjs/common");
1099
- var import_otplib = require("otplib");
1100
- function _ts_decorate3(decorators, target, key, desc) {
1101
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1102
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1103
- 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;
1104
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1105
- }
1106
- __name(_ts_decorate3, "_ts_decorate");
1107
- function _ts_metadata3(k, v) {
1108
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1109
- }
1110
- __name(_ts_metadata3, "_ts_metadata");
1111
- function _ts_param3(paramIndex, decorator) {
1112
- return function(target, key) {
1113
- decorator(target, key, paramIndex);
1114
- };
1115
- }
1116
- __name(_ts_param3, "_ts_param");
1117
- var CLOCK_DRIFT_TOLERANCE_SECONDS = 30;
1118
- var OTPLIB_TOKEN_ERROR_NAMES = /* @__PURE__ */ new Set([
1119
- "TokenError",
1120
- "TokenFormatError",
1121
- "TokenLengthError"
1122
- ]);
1123
- function isOtplibTokenError(error) {
1124
- return error instanceof Error && OTPLIB_TOKEN_ERROR_NAMES.has(error.name);
1125
- }
1126
- __name(isOtplibTokenError, "isOtplibTokenError");
1127
- var MfaService = class _MfaService {
1128
- static {
1129
- __name(this, "MfaService");
1130
- }
1131
- mfa;
1132
- logger = new import_common3.Logger(_MfaService.name);
1133
- constructor(mfa) {
1134
- this.mfa = mfa;
1135
- }
1136
- /**
1137
- * Generates a new TOTP secret, persists it via `MfaPort.setSecret`
1138
- * and returns the secret + the otpauth URI for the QR code.
1139
- *
1140
- * `issuer` and `label` shape the display in the authenticator (e.g.
1141
- * "DemoApp:taci@example.com").
1142
- */
1143
- async setup(userId, label, issuer) {
1144
- const secret = (0, import_otplib.generateSecret)();
1145
- const otpauthUri = (0, import_otplib.generateURI)({
1146
- issuer,
1147
- label,
1148
- secret
1149
- });
1150
- await this.mfa.setSecret(userId, secret);
1151
- return {
1152
- secret,
1153
- otpauthUri
1154
- };
1155
- }
1156
- /**
1157
- * Verifies the entered code against the stored secret,
1158
- * clock-drift-tolerant by ±1 time step.
1159
- */
1160
- async verify(input) {
1161
- const secret = await this.mfa.getSecret(input.userId);
1162
- if (!secret) return false;
1163
- try {
1164
- const result = await (0, import_otplib.verify)({
1165
- secret,
1166
- token: input.code.trim(),
1167
- epochTolerance: CLOCK_DRIFT_TOLERANCE_SECONDS
1168
- });
1169
- return result.valid;
1170
- } catch (error) {
1171
- if (isOtplibTokenError(error)) {
1172
- return false;
1173
- }
1174
- const message = error instanceof Error ? error.message : String(error);
1175
- this.logger.warn(`TOTP-Verify f\xFCr User ${input.userId} unerwartet fehlgeschlagen: ${message}`);
1176
- return false;
1177
- }
1178
- }
1179
- /**
1180
- * Deletes a user's secret — used by `ahp admin mfa-reset`.
1181
- */
1182
- async disable(userId) {
1183
- await this.mfa.setSecret(userId, null);
1184
- }
1185
- /** Delegates to `MfaPort.isEnabled`. */
1186
- async isEnabled(userId) {
1187
- return this.mfa.isEnabled(userId);
1188
- }
1189
- };
1190
- MfaService = _ts_decorate3([
1191
- (0, import_common3.Injectable)(),
1192
- _ts_param3(0, (0, import_common3.Inject)(MFA_PORT_TOKEN)),
1193
- _ts_metadata3("design:type", Function),
1194
- _ts_metadata3("design:paramtypes", [
1195
- typeof MfaPort === "undefined" ? Object : MfaPort
1196
- ])
1197
- ], MfaService);
1198
-
1199
- // src/admin/mfa.guard.ts
1200
- var import_common4 = require("@nestjs/common");
1201
- var import_core = require("@nestjs/core");
1202
- function _ts_decorate4(decorators, target, key, desc) {
1203
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1204
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1205
- 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;
1206
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1207
- }
1208
- __name(_ts_decorate4, "_ts_decorate");
1209
- function _ts_metadata4(k, v) {
1210
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1211
- }
1212
- __name(_ts_metadata4, "_ts_metadata");
1213
- function _ts_param4(paramIndex, decorator) {
1214
- return function(target, key) {
1215
- decorator(target, key, paramIndex);
1216
- };
1217
- }
1218
- __name(_ts_param4, "_ts_param");
1219
- var REQUIRE_MFA_KEY = "saas-platform/require-mfa";
1220
- var MfaGuard = class _MfaGuard {
1221
- static {
1222
- __name(this, "MfaGuard");
1223
- }
1224
- reflector;
1225
- mfaService;
1226
- logger = new import_common4.Logger(_MfaGuard.name);
1227
- constructor(reflector, mfaService) {
1228
- this.reflector = reflector;
1229
- this.mfaService = mfaService;
1230
- }
1231
- async canActivate(context) {
1232
- const required = this.reflector.getAllAndOverride(REQUIRE_MFA_KEY, [
1233
- context.getHandler(),
1234
- context.getClass()
1235
- ]);
1236
- if (!required) return true;
1237
- if (process.env.SAAS_PLATFORM_SKIP_MFA === "1" && process.env.NODE_ENV !== "production") {
1238
- return true;
1239
- }
1240
- const request = context.switchToHttp().getRequest();
1241
- const user = request.user;
1242
- if (!user) throw new import_common4.UnauthorizedException({
1243
- reason: "NOT_AUTHENTICATED"
1244
- });
1245
- const enabled = await this.mfaService.isEnabled(user.id);
1246
- if (!enabled) {
1247
- throw new import_common4.UnauthorizedException({
1248
- reason: "MFA_NOT_SET_UP",
1249
- message: "Bitte zuerst MFA-Setup \xFCber das CLI ausf\xFChren."
1250
- });
1251
- }
1252
- const headerVal = request.headers?.["x-mfa-code"];
1253
- const code = Array.isArray(headerVal) ? headerVal[0] : headerVal;
1254
- if (!code) {
1255
- throw new import_common4.UnauthorizedException({
1256
- reason: "MFA_REQUIRED",
1257
- message: "TOTP-Code im Header X-Mfa-Code erforderlich."
1258
- });
1259
- }
1260
- const valid = await this.mfaService.verify({
1261
- userId: user.id,
1262
- code: String(code)
1263
- });
1264
- if (!valid) {
1265
- throw new import_common4.UnauthorizedException({
1266
- reason: "MFA_FAILED",
1267
- message: "TOTP-Code ung\xFCltig."
1268
- });
1269
- }
1270
- return true;
1271
- }
1272
- };
1273
- MfaGuard = _ts_decorate4([
1274
- (0, import_common4.Injectable)(),
1275
- _ts_param4(0, (0, import_common4.Inject)(import_core.Reflector)),
1276
- _ts_metadata4("design:type", Function),
1277
- _ts_metadata4("design:paramtypes", [
1278
- typeof import_core.Reflector === "undefined" ? Object : import_core.Reflector,
1279
- typeof MfaService === "undefined" ? Object : MfaService
1280
- ])
1281
- ], MfaGuard);
1282
-
1283
- // src/admin/super-admin.guard.ts
1284
- var import_common5 = require("@nestjs/common");
1285
- function _ts_decorate5(decorators, target, key, desc) {
1286
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1287
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1288
- 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;
1289
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1290
- }
1291
- __name(_ts_decorate5, "_ts_decorate");
1292
- var SuperAdminGuard = class {
1293
- static {
1294
- __name(this, "SuperAdminGuard");
1295
- }
1296
- canActivate(context) {
1297
- const request = context.switchToHttp().getRequest();
1298
- if (!request.user) throw new import_common5.ForbiddenException("Nicht authentifiziert");
1299
- const role = request.user.platformRole ?? request.user.role;
1300
- if (role !== "SUPER_ADMIN") {
1301
- throw new import_common5.ForbiddenException("Nur SUPER_ADMIN-Rolle erlaubt");
1302
- }
1303
- return true;
1304
- }
1305
- };
1306
- SuperAdminGuard = _ts_decorate5([
1307
- (0, import_common5.Injectable)()
1308
- ], SuperAdminGuard);
1309
-
1310
- // src/admin/module.ts
1311
- function _ts_decorate6(decorators, target, key, desc) {
1312
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1313
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1314
- 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;
1315
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1316
- }
1317
- __name(_ts_decorate6, "_ts_decorate");
1318
- var AdminModule = class _AdminModule {
1319
- static {
1320
- __name(this, "AdminModule");
1321
- }
1322
- static forRoot(options) {
1323
- const providers = [
1324
- asProvider(MFA_PORT_TOKEN, options.mfaPort),
1325
- asProvider(AUDIT_PORT_TOKEN, options.auditPort),
1326
- asProvider(RLS_BYPASS_PORT_TOKEN, options.rlsBypassPort),
1327
- MfaService,
1328
- AdminAuditService,
1329
- SuperAdminGuard,
1330
- MfaGuard,
1331
- AdminBypassRlsInterceptor
1332
- ];
1333
- return {
1334
- module: _AdminModule,
1335
- global: options.global ?? false,
1336
- providers,
1337
- exports: [
1338
- MFA_PORT_TOKEN,
1339
- AUDIT_PORT_TOKEN,
1340
- RLS_BYPASS_PORT_TOKEN,
1341
- MfaService,
1342
- AdminAuditService,
1343
- SuperAdminGuard,
1344
- MfaGuard,
1345
- AdminBypassRlsInterceptor
1346
- ]
1347
- };
1348
- }
1349
- };
1350
- AdminModule = _ts_decorate6([
1351
- (0, import_common6.Module)({})
1352
- ], AdminModule);
1353
-
1354
- // src/admin/admin-manifest.module.ts
1355
- var import_common9 = require("@nestjs/common");
1356
-
1357
- // src/admin/admin-manifest.config.ts
1358
- var ADMIN_MANIFEST_CONFIG = /* @__PURE__ */ Symbol("ADMIN_MANIFEST_CONFIG");
1359
-
1360
- // src/admin/admin-manifest.service.ts
1361
- var import_common7 = require("@nestjs/common");
1362
- var import_node_crypto = require("crypto");
1363
- function _ts_decorate7(decorators, target, key, desc) {
1364
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1365
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1366
- 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;
1367
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1368
- }
1369
- __name(_ts_decorate7, "_ts_decorate");
1370
- function _ts_metadata5(k, v) {
1371
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1372
- }
1373
- __name(_ts_metadata5, "_ts_metadata");
1374
- function _ts_param5(paramIndex, decorator) {
1375
- return function(target, key) {
1376
- decorator(target, key, paramIndex);
1377
- };
1378
- }
1379
- __name(_ts_param5, "_ts_param");
1380
- var PLATFORM_CORE_CONTRIBUTION_TOKEN = /* @__PURE__ */ Symbol("PLATFORM_CORE_CONTRIBUTION");
1381
- var AdminManifestService = class _AdminManifestService {
1382
- static {
1383
- __name(this, "AdminManifestService");
1384
- }
1385
- config;
1386
- logger = new import_common7.Logger(_AdminManifestService.name);
1387
- contributions = [];
1388
- cached = null;
1389
- constructor(config, platformCore = null) {
1390
- this.config = config;
1391
- if (platformCore) {
1392
- this.contributions.push(platformCore);
1393
- }
1394
- }
1395
- register(contribution) {
1396
- this.contributions.push(contribution);
1397
- this.cached = null;
1398
- }
1399
- getManifest() {
1400
- if (this.cached) return this.cached;
1401
- this.cached = this.build();
1402
- this.logger.log(`Manifest built: ${this.contributions.length} contribution(s), hash=${this.cached.build.manifestHash.slice(0, 24)}\u2026`);
1403
- return this.cached;
1404
- }
1405
- rebuild() {
1406
- this.cached = null;
1407
- return this.getManifest();
1408
- }
1409
- build() {
1410
- const merged = this.mergeContributions(this.contributions);
1411
- const draft = {
1412
- schemaVersion: 1,
1413
- project: this.config.project,
1414
- build: {
1415
- ...this.config.build,
1416
- manifestHash: "sha256-pending"
1417
- },
1418
- planCatalogSnapshot: this.config.planCatalogSnapshot,
1419
- capabilities: merged.capabilities,
1420
- navigation: merged.navigation,
1421
- dashboard: merged.dashboard,
1422
- tenants: merged.tenants,
1423
- audit: merged.audit
1424
- };
1425
- return {
1426
- ...draft,
1427
- build: {
1428
- ...draft.build,
1429
- manifestHash: this.computeHash(draft)
1430
- }
1431
- };
1432
- }
1433
- mergeContributions(contributions) {
1434
- const capabilities = {};
1435
- const standardPages = {};
1436
- const projectPages = [];
1437
- const kpiCards = [];
1438
- const tenantColumns = [];
1439
- const tenantActions = [];
1440
- const auditActions = [];
1441
- for (const c of contributions) {
1442
- if (c.capabilities) Object.assign(capabilities, c.capabilities);
1443
- if (c.navigation?.standardPages) Object.assign(standardPages, c.navigation.standardPages);
1444
- if (c.navigation?.projectPages) projectPages.push(...c.navigation.projectPages);
1445
- if (c.dashboard?.kpiCards) kpiCards.push(...c.dashboard.kpiCards);
1446
- if (c.tenants?.columns) tenantColumns.push(...c.tenants.columns);
1447
- if (c.tenants?.actions) tenantActions.push(...c.tenants.actions);
1448
- if (c.audit?.actions) auditActions.push(...c.audit.actions);
1449
- }
1450
- kpiCards.sort((a, b) => (b.slotPriority ?? 0) - (a.slotPriority ?? 0));
1451
- return {
1452
- capabilities,
1453
- navigation: {
1454
- standardPages,
1455
- projectPages
1456
- },
1457
- dashboard: kpiCards.length > 0 ? {
1458
- kpiCards
1459
- } : void 0,
1460
- tenants: tenantColumns.length > 0 || tenantActions.length > 0 ? {
1461
- columns: tenantColumns,
1462
- actions: tenantActions
1463
- } : void 0,
1464
- audit: auditActions.length > 0 ? {
1465
- actions: auditActions
1466
- } : void 0
1467
- };
1468
- }
1469
- /**
1470
- * Deterministic SHA-256 hash over the manifest. Uses the insertion
1471
- * order of the object keys — so that semantically meaningful reorders (e.g.
1472
- * the order of the sidebar items in `navigation.standardPages`) change the
1473
- * hash and invalidate the browser ETag cache. The manifest's own
1474
- * `manifestHash` field is hidden before serialization.
1475
- */
1476
- computeHash(manifest) {
1477
- const canonical = stableStringify({
1478
- ...manifest,
1479
- build: {
1480
- ...manifest.build,
1481
- manifestHash: void 0
1482
- }
1483
- });
1484
- const digest = (0, import_node_crypto.createHash)("sha256").update(canonical).digest("base64url");
1485
- return `sha256-${digest}`;
1486
- }
1487
- };
1488
- AdminManifestService = _ts_decorate7([
1489
- (0, import_common7.Injectable)(),
1490
- _ts_param5(0, (0, import_common7.Inject)(ADMIN_MANIFEST_CONFIG)),
1491
- _ts_param5(1, (0, import_common7.Optional)()),
1492
- _ts_param5(1, (0, import_common7.Inject)(PLATFORM_CORE_CONTRIBUTION_TOKEN)),
1493
- _ts_metadata5("design:type", Function),
1494
- _ts_metadata5("design:paramtypes", [
1495
- typeof AdminManifestConfig === "undefined" ? Object : AdminManifestConfig,
1496
- Object
1497
- ])
1498
- ], AdminManifestService);
1499
- function stableStringify(value) {
1500
- if (value === null) return "null";
1501
- if (value === void 0) return "null";
1502
- if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
1503
- if (typeof value === "boolean") return value ? "true" : "false";
1504
- if (typeof value === "string") return JSON.stringify(value);
1505
- if (Array.isArray(value)) {
1506
- return "[" + value.map(stableStringify).join(",") + "]";
1507
- }
1508
- if (typeof value === "object") {
1509
- const obj = value;
1510
- const keys = Object.keys(obj).filter((k) => obj[k] !== void 0);
1511
- return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
1512
- }
1513
- return JSON.stringify(value);
1514
- }
1515
- __name(stableStringify, "stableStringify");
1516
-
1517
- // src/admin/admin-public-boot.controller.ts
1518
- var import_common8 = require("@nestjs/common");
1519
- function _ts_decorate8(decorators, target, key, desc) {
1520
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1521
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1522
- 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;
1523
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1524
- }
1525
- __name(_ts_decorate8, "_ts_decorate");
1526
- function _ts_metadata6(k, v) {
1527
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1528
- }
1529
- __name(_ts_metadata6, "_ts_metadata");
1530
- function _ts_param6(paramIndex, decorator) {
1531
- return function(target, key) {
1532
- decorator(target, key, paramIndex);
1533
- };
1534
- }
1535
- __name(_ts_param6, "_ts_param");
1536
- var AdminPublicBootController = class {
1537
- static {
1538
- __name(this, "AdminPublicBootController");
1539
- }
1540
- config;
1541
- constructor(config) {
1542
- this.config = config;
1543
- }
1544
- getBoot() {
1545
- return {
1546
- project: {
1547
- key: this.config.project.key,
1548
- displayName: this.config.project.displayName,
1549
- label: this.config.project.label,
1550
- icon: this.config.project.icon,
1551
- logoUrl: this.config.project.logoUrl,
1552
- environment: this.config.project.environment
1553
- }
1554
- };
1555
- }
1556
- };
1557
- _ts_decorate8([
1558
- (0, import_common8.Get)("boot"),
1559
- _ts_metadata6("design:type", Function),
1560
- _ts_metadata6("design:paramtypes", []),
1561
- _ts_metadata6("design:returntype", typeof PublicBootResponse === "undefined" ? Object : PublicBootResponse)
1562
- ], AdminPublicBootController.prototype, "getBoot", null);
1563
- AdminPublicBootController = _ts_decorate8([
1564
- (0, import_common8.Controller)("admin"),
1565
- _ts_param6(0, (0, import_common8.Inject)(ADMIN_MANIFEST_CONFIG)),
1566
- _ts_metadata6("design:type", Function),
1567
- _ts_metadata6("design:paramtypes", [
1568
- typeof AdminManifestConfig === "undefined" ? Object : AdminManifestConfig
1569
- ])
1570
- ], AdminPublicBootController);
1571
-
1572
- // src/admin/manifest-core.ts
1573
- var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
1574
- navigation: {
1575
- // Key order is render order in the sidebar (within the
1576
- // section, see `DEFAULT_NAV_SECTIONS` in the nav-builder). The order per
1577
- // section is deliberately curated:
1578
- // Overview: dashboard
1579
- // Product catalog: discovery → bundles → plans →
1580
- // marketingCatalog → promoCodes
1581
- // (promoCodes is appended by the app contributions
1582
- // and therefore lands at the end of the section)
1583
- // Customers: tenants → users → pilots
1584
- // System: audit
1585
- standardPages: {
1586
- dashboard: {
1587
- enabled: true,
1588
- requiredCapability: "dashboard.read"
1589
- },
1590
- // SPEC_V2 §3.3 — discovery snapshot of the running backend.
1591
- // Apps must set `discovery.read` in their manifest capabilities
1592
- // to `true` so that the NavBuilder wires the page into the
1593
- // sidebar; otherwise it is filtered out automatically.
1594
- discovery: {
1595
- enabled: true,
1596
- requiredCapability: "discovery.read"
1597
- },
1598
- // SPEC_V2 §11.1 M3 — Bundle editor in SuperAdmin.
1599
- // Apps with a DB-driven catalog set `bundles.read = true`.
1600
- bundles: {
1601
- enabled: true,
1602
- requiredCapability: "bundles.read"
1603
- },
1604
- plans: {
1605
- enabled: true,
1606
- requiredCapability: "plans.read"
1607
- },
1608
- // SPEC_V2 §11.1 M3 — marketing catalog (locale pivot).
1609
- // Apps that market a public catalog set
1610
- // `marketingProjections.read = true`.
1611
- marketingCatalog: {
1612
- enabled: true,
1613
- requiredCapability: "marketingProjections.read"
1614
- },
1615
- tenants: {
1616
- enabled: true,
1617
- requiredCapability: "tenants.read"
1618
- },
1619
- users: {
1620
- enabled: true,
1621
- requiredCapability: "users.read"
1622
- },
1623
- pilots: {
1624
- enabled: true,
1625
- requiredCapability: "pilots.read"
1626
- },
1627
- // Audit log deliberately at the end — rarely needed, should
1628
- // not obscure the productive pages.
1629
- audit: {
1630
- enabled: true,
1631
- requiredCapability: "audit.read"
1632
- },
1633
- // Platform email sender (system/registration mails). Apps
1634
- // set `platformEmail.read = true` so that the NavBuilder wires the page
1635
- // in; otherwise it is filtered out automatically.
1636
- platformEmail: {
1637
- enabled: true,
1638
- requiredCapability: "platformEmail.read"
1639
- },
1640
- // Platform email history (audit of the sent system mails). Apps
1641
- // set `platformEmailHistory.read = true` so that the NavBuilder wires the
1642
- // page in; otherwise it is filtered out automatically.
1643
- platformEmailHistory: {
1644
- enabled: true,
1645
- requiredCapability: "platformEmailHistory.read"
1646
- }
1647
- }
1648
- },
1649
- tenants: {
1650
- actions: [
1651
- {
1652
- id: "platform.tenants.suspend",
1653
- label: "Mandant suspendieren",
1654
- actionKey: "tenants.suspend",
1655
- requiredCapability: "tenants.suspend",
1656
- requiresMfa: true,
1657
- confirmType: "typed-slug"
1658
- },
1659
- {
1660
- id: "platform.tenants.reactivate",
1661
- label: "Mandant reaktivieren",
1662
- actionKey: "tenants.reactivate",
1663
- requiredCapability: "tenants.reactivate",
1664
- requiresMfa: true,
1665
- confirmType: "simple"
1666
- },
1667
- {
1668
- id: "platform.tenants.impersonate",
1669
- label: "Impersonate",
1670
- actionKey: "tenants.impersonate",
1671
- requiredCapability: "tenants.impersonate",
1672
- requiresMfa: true,
1673
- confirmType: "simple"
1674
- },
1675
- {
1676
- id: "platform.tenants.export",
1677
- label: "DSGVO-Export",
1678
- actionKey: "tenants.export",
1679
- requiredCapability: "tenants.export",
1680
- requiresMfa: true,
1681
- confirmType: "simple"
1682
- },
1683
- {
1684
- id: "platform.subscriptions.cancel",
1685
- label: "Subscription k\xFCndigen",
1686
- actionKey: "subscriptions.cancel",
1687
- requiredCapability: "subscriptions.cancel",
1688
- requiresMfa: true,
1689
- confirmType: "typed-slug"
1690
- },
1691
- {
1692
- id: "platform.pilots.grant",
1693
- label: "Pilot-Status setzen",
1694
- actionKey: "pilots.grant",
1695
- requiredCapability: "pilots.grant",
1696
- requiresMfa: true,
1697
- confirmType: "simple"
1698
- },
1699
- {
1700
- id: "platform.pilots.revoke",
1701
- label: "Pilot-Status entziehen",
1702
- actionKey: "pilots.revoke",
1703
- requiredCapability: "pilots.revoke",
1704
- requiresMfa: true,
1705
- confirmType: "typed-slug"
1706
- },
1707
- {
1708
- id: "platform.pilots.extend",
1709
- label: "Pilot verl\xE4ngern",
1710
- actionKey: "pilots.extend",
1711
- requiredCapability: "pilots.extend",
1712
- requiresMfa: true,
1713
- confirmType: "date"
1714
- }
1715
- ]
1716
- },
1717
- audit: {
1718
- actions: [
1719
- {
1720
- key: "TENANT_SUSPEND",
1721
- label: "Mandant suspendiert",
1722
- severity: "high"
1723
- },
1724
- {
1725
- key: "TENANT_REACTIVATE",
1726
- label: "Mandant reaktiviert",
1727
- severity: "medium"
1728
- },
1729
- {
1730
- key: "TENANT_IMPERSONATE",
1731
- label: "Impersonation gestartet",
1732
- severity: "high"
1733
- },
1734
- {
1735
- key: "TENANT_EXPORT",
1736
- label: "DSGVO-Export ausgel\xF6st",
1737
- severity: "medium"
1738
- },
1739
- {
1740
- key: "PILOT_CREATE",
1741
- label: "Pilot-Tenant angelegt",
1742
- severity: "medium"
1743
- },
1744
- {
1745
- key: "PILOT_GRANT",
1746
- label: "Pilot-Status gesetzt",
1747
- severity: "medium"
1748
- },
1749
- {
1750
- key: "PILOT_REVOKE",
1751
- label: "Pilot-Status entzogen",
1752
- severity: "medium"
1753
- },
1754
- {
1755
- key: "PILOT_EXTEND",
1756
- label: "Pilot verl\xE4ngert",
1757
- severity: "low"
1758
- },
1759
- {
1760
- key: "PLAN_VERSION_PUBLISH",
1761
- label: "Plan-Version freigegeben",
1762
- severity: "high"
1763
- },
1764
- {
1765
- key: "PLAN_VERSION_TERMINATE",
1766
- label: "Plan-Version terminiert",
1767
- severity: "high"
1768
- },
1769
- {
1770
- key: "SUBSCRIPTION_CANCEL",
1771
- label: "Subscription gek\xFCndigt",
1772
- severity: "high"
1773
- },
1774
- {
1775
- key: "USER_RESET_PASSWORD",
1776
- label: "Passwort-Reset ausgel\xF6st",
1777
- severity: "medium"
1778
- },
1779
- {
1780
- key: "USER_DEACTIVATE",
1781
- label: "User deaktiviert",
1782
- severity: "high"
1783
- },
1784
- {
1785
- key: "USER_REASSIGN_ADMIN",
1786
- label: "Admin-Rolle \xFCbertragen",
1787
- severity: "high"
1788
- },
1789
- {
1790
- key: "PLATFORM_EMAIL_PROVIDER_CREATE",
1791
- label: "Plattform-Absender angelegt",
1792
- severity: "medium"
1793
- },
1794
- {
1795
- key: "PLATFORM_EMAIL_PROVIDER_UPDATE",
1796
- label: "Plattform-Absender ge\xE4ndert",
1797
- severity: "medium"
1798
- },
1799
- {
1800
- key: "PLATFORM_EMAIL_PROVIDER_DELETE",
1801
- label: "Plattform-Absender gel\xF6scht",
1802
- severity: "medium"
1803
- },
1804
- {
1805
- key: "PLATFORM_EMAIL_PROVIDER_TEST",
1806
- label: "Plattform-Absender Test-Versand",
1807
- severity: "low"
1808
- },
1809
- {
1810
- key: "EMAIL_HISTORY_DELETE",
1811
- label: "Plattform-E-Mail aus Verlauf entfernt",
1812
- severity: "medium"
1813
- },
1814
- {
1815
- key: "EMAIL_HISTORY_RESEND",
1816
- label: "Plattform-E-Mail erneut versendet",
1817
- severity: "low"
1818
- }
1819
- ]
1820
- }
1821
- };
1822
-
1823
- // src/admin/admin-manifest.module.ts
1824
- function _ts_decorate9(decorators, target, key, desc) {
1825
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1826
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1827
- 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;
1828
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1829
- }
1830
- __name(_ts_decorate9, "_ts_decorate");
1831
- function _ts_metadata7(k, v) {
1832
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
1833
- }
1834
- __name(_ts_metadata7, "_ts_metadata");
1835
- function _ts_param7(paramIndex, decorator) {
1836
- return function(target, key) {
1837
- decorator(target, key, paramIndex);
1838
- };
1839
- }
1840
- __name(_ts_param7, "_ts_param");
1841
- function asConfigProvider(impl) {
1842
- if (typeof impl === "object" && impl !== null && "useFactory" in impl && typeof impl.useFactory === "function") {
1843
- return {
1844
- provide: ADMIN_MANIFEST_CONFIG,
1845
- useFactory: impl.useFactory,
1846
- inject: impl.inject
1847
- };
1848
- }
1849
- return {
1850
- provide: ADMIN_MANIFEST_CONFIG,
1851
- useValue: impl
1852
- };
1853
- }
1854
- __name(asConfigProvider, "asConfigProvider");
1855
- function buildManifestController(guards, reloadGuards) {
1856
- const reloadChain = [
1857
- ...guards,
1858
- ...reloadGuards
1859
- ];
1860
- let GeneratedAdminManifestController = class GeneratedAdminManifestController {
1861
- static {
1862
- __name(this, "GeneratedAdminManifestController");
1863
- }
1864
- manifest;
1865
- // Explicit @Inject instead of type reflection: tsup/esbuild do not emit
1866
- // `design:paramtypes` metadata, so Nest cannot otherwise resolve the
1867
- // service type at the constructor.
1868
- constructor(manifest) {
1869
- this.manifest = manifest;
1870
- }
1871
- getManifest(ifNoneMatch, res) {
1872
- const m = this.manifest.getManifest();
1873
- const etag = `"${m.build.manifestHash}"`;
1874
- res.header("ETag", etag);
1875
- if (ifNoneMatch && ifNoneMatch === etag) {
1876
- res.status(import_common9.HttpStatus.NOT_MODIFIED);
1877
- return null;
1878
- }
1879
- return m;
1880
- }
1881
- reload() {
1882
- const m = this.manifest.rebuild();
1883
- return {
1884
- manifestHash: m.build.manifestHash,
1885
- reloadedAt: (/* @__PURE__ */ new Date()).toISOString()
1886
- };
1887
- }
1888
- };
1889
- _ts_decorate9([
1890
- (0, import_common9.Get)("manifest"),
1891
- (0, import_common9.Header)("Cache-Control", "private, max-age=60, must-revalidate"),
1892
- _ts_param7(0, (0, import_common9.Headers)("if-none-match")),
1893
- _ts_param7(1, (0, import_common9.Res)({
1894
- passthrough: true
1895
- })),
1896
- _ts_metadata7("design:type", Function),
1897
- _ts_metadata7("design:paramtypes", [
1898
- Object,
1899
- typeof HttpResponseLike === "undefined" ? Object : HttpResponseLike
1900
- ]),
1901
- _ts_metadata7("design:returntype", void 0)
1902
- ], GeneratedAdminManifestController.prototype, "getManifest", null);
1903
- _ts_decorate9([
1904
- (0, import_common9.Post)("manifest/reload"),
1905
- (0, import_common9.UseGuards)(...reloadChain),
1906
- (0, import_common9.HttpCode)(import_common9.HttpStatus.OK),
1907
- _ts_metadata7("design:type", Function),
1908
- _ts_metadata7("design:paramtypes", []),
1909
- _ts_metadata7("design:returntype", void 0)
1910
- ], GeneratedAdminManifestController.prototype, "reload", null);
1911
- GeneratedAdminManifestController = _ts_decorate9([
1912
- (0, import_common9.Controller)("admin"),
1913
- (0, import_common9.UseGuards)(...guards),
1914
- _ts_param7(0, (0, import_common9.Inject)(AdminManifestService)),
1915
- _ts_metadata7("design:type", Function),
1916
- _ts_metadata7("design:paramtypes", [
1917
- typeof AdminManifestService === "undefined" ? Object : AdminManifestService
1918
- ])
1919
- ], GeneratedAdminManifestController);
1920
- return GeneratedAdminManifestController;
1921
- }
1922
- __name(buildManifestController, "buildManifestController");
1923
- var AdminManifestModule = class _AdminManifestModule {
1924
- static {
1925
- __name(this, "AdminManifestModule");
1926
- }
1927
- static forRoot(options) {
1928
- const includeManifestController = options.includeManifestController ?? true;
1929
- const registerPlatformCore = options.registerPlatformCore ?? true;
1930
- const controllers = [
1931
- AdminPublicBootController
1932
- ];
1933
- if (includeManifestController) {
1934
- if (options.guards === void 0) {
1935
- throw new Error("AdminManifestModule.forRoot: `guards` ist Pflicht, wenn `includeManifestController` aktiv ist (Default `true`). Setze `guards: [JwtAuthGuard, SuperAdminGuard]` (oder analog) \u2014 oder `guards: []`, um den Endpoint absichtlich auth-frei zu betreiben. Alternative: `includeManifestController: false` und einen eigenen Wrapping-Controller registrieren.");
1936
- }
1937
- controllers.push(buildManifestController(options.guards, options.reloadGuards ?? []));
1938
- }
1939
- const providers = [
1940
- asConfigProvider(options.config),
1941
- AdminManifestService,
1942
- ...options.extraProviders ?? []
1943
- ];
1944
- if (registerPlatformCore) {
1945
- providers.push({
1946
- provide: PLATFORM_CORE_CONTRIBUTION_TOKEN,
1947
- useValue: PLATFORM_CORE_MANIFEST_CONTRIBUTION
1948
- });
1949
- }
1950
- return {
1951
- module: _AdminManifestModule,
1952
- global: options.global ?? false,
1953
- imports: options.imports ?? [],
1954
- controllers,
1955
- providers,
1956
- exports: [
1957
- ADMIN_MANIFEST_CONFIG,
1958
- AdminManifestService
1959
- ]
1960
- };
1961
- }
1962
- };
1963
- AdminManifestModule = _ts_decorate9([
1964
- (0, import_common9.Module)({})
1965
- ], AdminManifestModule);
1966
-
1967
- // src/billing/plan-catalog.module.ts
1968
- var import_common10 = require("@nestjs/common");
1969
-
1970
- // src/billing/plan-catalog-from-snapshot.ts
1971
- function buildPlanCatalogFromSnapshot(settings, snapshot) {
1972
- const liveByPlanKey = new Map(snapshot.livePlanVersions.map((v) => [
1973
- v.planId,
1974
- v
1975
- ]));
1976
- const plans = snapshot.plans.filter((p) => p.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder).map((stem) => {
1977
- const live = liveByPlanKey.get(stem.planKey);
1978
- if (!live) {
1979
- return {
1980
- id: stem.planKey,
1981
- name: stem.label,
1982
- tagline: stem.description ?? void 0,
1983
- marketed: false,
1984
- monthlyNet: null,
1985
- yearlyNet: null,
1986
- features: [],
1987
- quotas: {}
1988
- };
1989
- }
1990
- return {
1991
- id: stem.planKey,
1992
- name: stem.label,
1993
- tagline: stem.description ?? void 0,
1994
- marketed: live.marketed,
1995
- monthlyNet: parseFloat(live.monthlyNet),
1996
- yearlyNet: parseFloat(live.yearlyNet),
1997
- features: live.features,
1998
- quotas: live.quotas ?? {}
1999
- };
2000
- });
2001
- const features = snapshot.featureEntries.filter((f) => f.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder).map((row) => ({
2002
- key: row.featureKey,
2003
- label: row.label,
2004
- icon: row.icon ?? void 0,
2005
- tier: row.tier ?? void 0,
2006
- plannedOnly: row.plannedOnly
2007
- }));
2008
- return {
2009
- schemaVersion: 1,
2010
- projectKey: settings.projectKey,
2011
- ...settings.app ? {
2012
- app: settings.app
2013
- } : {},
2014
- currency: settings.currency,
2015
- vatRate: settings.vatRate,
2016
- ...settings.marketing ? {
2017
- marketing: settings.marketing
2018
- } : {},
2019
- features,
2020
- plans
2021
- };
2022
- }
2023
- __name(buildPlanCatalogFromSnapshot, "buildPlanCatalogFromSnapshot");
2024
-
2025
- // src/billing/plan-catalog.module.ts
2026
- function _ts_decorate10(decorators, target, key, desc) {
2027
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2028
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2029
- 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;
2030
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2031
- }
2032
- __name(_ts_decorate10, "_ts_decorate");
2033
- var PLAN_CATALOG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PLAN_CATALOG");
2034
- var PLAN_CATALOG_READ_SINK_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PLAN_CATALOG_READ_SINK");
2035
- var PlanCatalogModule = class _PlanCatalogModule {
2036
- static {
2037
- __name(this, "PlanCatalogModule");
2038
- }
2039
- static forRoot(options) {
2040
- return {
2041
- module: _PlanCatalogModule,
2042
- global: options.global ?? true,
2043
- imports: options.imports ?? [],
2044
- providers: [
2045
- ...options.extraProviders ?? [],
2046
- asProvider(PLAN_CATALOG_READ_SINK_TOKEN, options.sink),
2047
- {
2048
- provide: PLAN_CATALOG_TOKEN,
2049
- useFactory: /* @__PURE__ */ __name(async (sink) => {
2050
- const snapshot = await sink.loadSnapshot(options.projectKey);
2051
- return buildPlanCatalogFromSnapshot({
2052
- projectKey: options.projectKey,
2053
- app: options.app,
2054
- currency: options.currency,
2055
- vatRate: options.vatRate,
2056
- marketing: options.marketing
2057
- }, snapshot);
2058
- }, "useFactory"),
2059
- inject: [
2060
- PLAN_CATALOG_READ_SINK_TOKEN
2061
- ]
2062
- }
2063
- ],
2064
- exports: [
2065
- PLAN_CATALOG_TOKEN,
2066
- PLAN_CATALOG_READ_SINK_TOKEN
2067
- ]
2068
- };
2069
- }
2070
- /**
2071
- * Tests / in-memory setup: takes the catalog object directly, without a
2072
- * sink/DB lookup. For unit tests + test bootstraps.
2073
- */
2074
- static forRootWithCatalog(catalog, opts = {}) {
2075
- return {
2076
- module: _PlanCatalogModule,
2077
- providers: [
2078
- {
2079
- provide: PLAN_CATALOG_TOKEN,
2080
- useValue: catalog
2081
- }
2082
- ],
2083
- exports: [
2084
- PLAN_CATALOG_TOKEN
2085
- ],
2086
- global: opts.global ?? true
2087
- };
2088
- }
2089
- };
2090
- PlanCatalogModule = _ts_decorate10([
2091
- (0, import_common10.Module)({})
2092
- ], PlanCatalogModule);
2093
-
2094
- // src/discovery/discovery.module.ts
2095
- var import_common13 = require("@nestjs/common");
2096
- var import_core3 = require("@nestjs/core");
2097
-
2098
- // src/discovery/discovery.controller.ts
2099
- var import_common12 = require("@nestjs/common");
2100
-
2101
- // src/discovery/discovery.scanner.ts
2102
- var import_node_crypto2 = require("crypto");
2103
- var import_node_fs = require("fs");
2104
- var import_node_path = require("path");
2105
- var import_common11 = require("@nestjs/common");
2106
- var import_core2 = require("@nestjs/core");
2107
-
2108
- // src/discovery/tokens.ts
2109
- var IMPLEMENTS_CAPABILITY_KEY = "discovery:implements-capability";
2110
- var DEFINES_QUOTA_KEY = "discovery:defines-quota";
2111
- var ENFORCE_QUOTA_KEY = "discovery:enforce-quota";
2112
- var DISCOVERY_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/DiscoverySnapshot");
2113
-
2114
- // src/discovery/discovery.scanner.ts
2115
- function _ts_decorate11(decorators, target, key, desc) {
2116
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2117
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2118
- 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;
2119
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2120
- }
2121
- __name(_ts_decorate11, "_ts_decorate");
2122
- function _ts_metadata8(k, v) {
2123
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2124
- }
2125
- __name(_ts_metadata8, "_ts_metadata");
2126
- function _ts_param8(paramIndex, decorator) {
2127
- return function(target, key) {
2128
- decorator(target, key, paramIndex);
2129
- };
2130
- }
2131
- __name(_ts_param8, "_ts_param");
2132
- var DEFAULT_APP_INFO = {
2133
- key: "unknown",
2134
- version: "0.0.0"
2135
- };
2136
- var DISCOVERY_APP_INFO_TOKEN = /* @__PURE__ */ Symbol("DISCOVERY_APP_INFO");
2137
- var DISCOVERY_SNAPSHOT_PATH_TOKEN = /* @__PURE__ */ Symbol("DISCOVERY_SNAPSHOT_PATH");
2138
- var DiscoveryScanner = class _DiscoveryScanner {
2139
- static {
2140
- __name(this, "DiscoveryScanner");
2141
- }
2142
- discoveryService;
2143
- metadataScanner;
2144
- reflector;
2145
- appInfo;
2146
- snapshotPath;
2147
- logger = new import_common11.Logger(_DiscoveryScanner.name);
2148
- snapshot = null;
2149
- // Explicit @Inject instead of type reflection: in the platform package
2150
- // build tsup/esbuild do not emit `design:paramtypes` metadata, so Nest
2151
- // cannot otherwise resolve the DI targets at the constructor
2152
- // (UndefinedDependencyException at the consumer's boot). Pattern
2153
- // analogous to admin/admin-manifest.module.ts.
2154
- constructor(discoveryService, metadataScanner, reflector, appInfo = DEFAULT_APP_INFO, snapshotPath = null) {
2155
- this.discoveryService = discoveryService;
2156
- this.metadataScanner = metadataScanner;
2157
- this.reflector = reflector;
2158
- this.appInfo = appInfo;
2159
- this.snapshotPath = snapshotPath;
2160
- }
2161
- onApplicationBootstrap() {
2162
- this.snapshot = this.buildSnapshot();
2163
- 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)`);
2164
- if (this.snapshotPath) {
2165
- this.persistSnapshot(this.snapshotPath, this.snapshot);
2166
- }
2167
- }
2168
- persistSnapshot(path, snapshot) {
2169
- try {
2170
- (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), {
2171
- recursive: true
2172
- });
2173
- (0, import_node_fs.writeFileSync)(path, JSON.stringify(snapshot, null, 2) + "\n", "utf-8");
2174
- this.logger.log(`Discovery snapshot persisted to ${path}`);
2175
- } catch (err) {
2176
- this.logger.warn(`Discovery snapshot konnte nicht nach ${path} geschrieben werden: ${err.message}`);
2177
- }
2178
- }
2179
- /**
2180
- * Returns the boot-time snapshot. Before `onApplicationBootstrap` (e.g. in
2181
- * tests) the snapshot is built lazily.
2182
- */
2183
- getSnapshot() {
2184
- if (!this.snapshot) {
2185
- this.snapshot = this.buildSnapshot();
2186
- }
2187
- return this.snapshot;
2188
- }
2189
- /**
2190
- * Forces a re-scan (SuperAdmin "re-run discovery" or tests with
2191
- * dynamically registered providers). `scannedAt` is set anew and the
2192
- * snapshot — as at boot — is persisted to disk so that the last scan
2193
- * actually performed is preserved.
2194
- */
2195
- rebuildSnapshot() {
2196
- this.snapshot = this.buildSnapshot();
2197
- if (this.snapshotPath) {
2198
- this.persistSnapshot(this.snapshotPath, this.snapshot);
2199
- }
2200
- return this.snapshot;
2201
- }
2202
- buildSnapshot() {
2203
- const capabilitiesByKey = /* @__PURE__ */ new Map();
2204
- const quotasByKey = /* @__PURE__ */ new Map();
2205
- const enforceRefs = [];
2206
- const wrappers = [
2207
- ...this.discoveryService.getProviders(),
2208
- ...this.discoveryService.getControllers()
2209
- ];
2210
- for (const wrapper of wrappers) {
2211
- const instance = wrapper.instance;
2212
- if (!instance || typeof instance !== "object") continue;
2213
- const ctor = instance.constructor;
2214
- if (!ctor) continue;
2215
- const quotaOpts = this.reflector.get(DEFINES_QUOTA_KEY, ctor);
2216
- if (quotaOpts) {
2217
- this.registerQuota(quotasByKey, quotaOpts, ctor.name);
2218
- }
2219
- const prototype = Object.getPrototypeOf(instance);
2220
- const methodNames = this.metadataScanner.getAllMethodNames(prototype);
2221
- for (const methodName of methodNames) {
2222
- const methodRef = instance[methodName];
2223
- if (typeof methodRef !== "function") continue;
2224
- const capMeta = this.reflector.get(IMPLEMENTS_CAPABILITY_KEY, methodRef);
2225
- if (capMeta) {
2226
- this.registerCapability(capabilitiesByKey, capMeta, `${ctor.name}.${methodName}`);
2227
- }
2228
- const enforceMeta = this.reflector.get(ENFORCE_QUOTA_KEY, methodRef);
2229
- if (enforceMeta && capMeta) {
2230
- enforceRefs.push({
2231
- capabilityKey: capMeta.capabilityKey,
2232
- quotaKey: enforceMeta.quotaKey
2233
- });
2234
- }
2235
- }
2236
- }
2237
- const capabilities = sortByKey([
2238
- ...capabilitiesByKey.values()
2239
- ], (c) => c.capabilityKey);
2240
- const features = aggregateFeatures(capabilities);
2241
- const quotas = sortByKey(decorateQuotasWithEnforcers([
2242
- ...quotasByKey.values()
2243
- ], enforceRefs), (q) => q.quotaKey);
2244
- const snapshot = {
2245
- schemaVersion: 1,
2246
- scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
2247
- app: {
2248
- ...this.appInfo
2249
- },
2250
- capabilities,
2251
- features,
2252
- quotas,
2253
- hash: ""
2254
- };
2255
- snapshot.hash = computeSnapshotHash(snapshot);
2256
- return snapshot;
2257
- }
2258
- registerCapability(target, meta, declaredAt) {
2259
- const existing = target.get(meta.capabilityKey);
2260
- if (existing) {
2261
- this.logger.warn(`Capability '${meta.capabilityKey}' wird an mehreren Stellen deklariert (${existing.declaredAt} und ${declaredAt}); erste Deklaration gewinnt`);
2262
- return;
2263
- }
2264
- target.set(meta.capabilityKey, {
2265
- capabilityKey: meta.capabilityKey,
2266
- label: meta.label ?? null,
2267
- feature: meta.feature ?? null,
2268
- status: meta.status ?? "active",
2269
- kind: meta.kind ?? "endpoint",
2270
- owner: meta.owner ?? null,
2271
- replacementKey: meta.replacementKey ?? null,
2272
- removalPlannedAt: meta.removalPlannedAt ?? null,
2273
- reason: meta.reason ?? null,
2274
- requires: normalizeKeyList(meta.requires),
2275
- replaces: normalizeKeyList(meta.replaces),
2276
- declaredAt
2277
- });
2278
- }
2279
- registerQuota(target, opts, declaredAt) {
2280
- const existing = target.get(opts.key);
2281
- if (existing) {
2282
- this.logger.warn(`Quota '${opts.key}' wird an mehreren Stellen definiert (${existing.declaredAt} und ${declaredAt}); erste Definition gewinnt`);
2283
- return;
2284
- }
2285
- target.set(opts.key, {
2286
- quotaKey: opts.key,
2287
- label: opts.label,
2288
- unit: opts.unit,
2289
- policy: opts.policy,
2290
- feature: opts.feature ?? null,
2291
- replaces: normalizeKeyList(opts.replaces),
2292
- declaredAt,
2293
- enforcedBy: []
2294
- });
2295
- }
2296
- };
2297
- DiscoveryScanner = _ts_decorate11([
2298
- (0, import_common11.Injectable)(),
2299
- _ts_param8(0, (0, import_common11.Inject)(import_core2.DiscoveryService)),
2300
- _ts_param8(1, (0, import_common11.Inject)(import_core2.MetadataScanner)),
2301
- _ts_param8(2, (0, import_common11.Inject)(import_core2.Reflector)),
2302
- _ts_param8(3, (0, import_common11.Optional)()),
2303
- _ts_param8(3, (0, import_common11.Inject)(DISCOVERY_APP_INFO_TOKEN)),
2304
- _ts_param8(4, (0, import_common11.Optional)()),
2305
- _ts_param8(4, (0, import_common11.Inject)(DISCOVERY_SNAPSHOT_PATH_TOKEN)),
2306
- _ts_metadata8("design:type", Function),
2307
- _ts_metadata8("design:paramtypes", [
2308
- typeof import_core2.DiscoveryService === "undefined" ? Object : import_core2.DiscoveryService,
2309
- typeof import_core2.MetadataScanner === "undefined" ? Object : import_core2.MetadataScanner,
2310
- typeof import_core2.Reflector === "undefined" ? Object : import_core2.Reflector,
2311
- typeof DiscoveryAppInfo === "undefined" ? Object : DiscoveryAppInfo,
2312
- Object
2313
- ])
2314
- ], DiscoveryScanner);
2315
- function aggregateFeatures(capabilities) {
2316
- const byFeature = /* @__PURE__ */ new Map();
2317
- for (const cap of capabilities) {
2318
- if (!cap.feature) continue;
2319
- const acc = byFeature.get(cap.feature) ?? {
2320
- capabilityKeys: [],
2321
- requires: /* @__PURE__ */ new Set(),
2322
- replaces: /* @__PURE__ */ new Set()
2323
- };
2324
- acc.capabilityKeys.push(cap.capabilityKey);
2325
- for (const key of cap.requires ?? []) acc.requires.add(key);
2326
- for (const key of cap.replaces ?? []) acc.replaces.add(key);
2327
- byFeature.set(cap.feature, acc);
2328
- }
2329
- return sortByKey([
2330
- ...byFeature.entries()
2331
- ].map(([featureKey, acc]) => {
2332
- acc.requires.delete(featureKey);
2333
- acc.replaces.delete(featureKey);
2334
- return {
2335
- featureKey,
2336
- capabilityKeys: [
2337
- ...acc.capabilityKeys
2338
- ].sort((a, b) => a.localeCompare(b)),
2339
- requires: setToSortedListOrNull(acc.requires),
2340
- replaces: setToSortedListOrNull(acc.replaces)
2341
- };
2342
- }), (f) => f.featureKey);
2343
- }
2344
- __name(aggregateFeatures, "aggregateFeatures");
2345
- function normalizeKeyList(keys) {
2346
- if (!keys || keys.length === 0) return null;
2347
- return [
2348
- ...new Set(keys)
2349
- ].sort((a, b) => a.localeCompare(b));
2350
- }
2351
- __name(normalizeKeyList, "normalizeKeyList");
2352
- function setToSortedListOrNull(keys) {
2353
- if (keys.size === 0) return null;
2354
- return [
2355
- ...keys
2356
- ].sort((a, b) => a.localeCompare(b));
2357
- }
2358
- __name(setToSortedListOrNull, "setToSortedListOrNull");
2359
- function decorateQuotasWithEnforcers(quotas, enforceRefs) {
2360
- const enforcedByQuota = /* @__PURE__ */ new Map();
2361
- for (const ref of enforceRefs) {
2362
- const set = enforcedByQuota.get(ref.quotaKey) ?? /* @__PURE__ */ new Set();
2363
- set.add(ref.capabilityKey);
2364
- enforcedByQuota.set(ref.quotaKey, set);
2365
- }
2366
- return quotas.map((q) => ({
2367
- ...q,
2368
- enforcedBy: [
2369
- ...enforcedByQuota.get(q.quotaKey) ?? /* @__PURE__ */ new Set()
2370
- ].sort((a, b) => a.localeCompare(b))
2371
- }));
2372
- }
2373
- __name(decorateQuotasWithEnforcers, "decorateQuotasWithEnforcers");
2374
- function sortByKey(items, keyFn) {
2375
- return [
2376
- ...items
2377
- ].sort((a, b) => keyFn(a).localeCompare(keyFn(b)));
2378
- }
2379
- __name(sortByKey, "sortByKey");
2380
- function computeSnapshotHash(snapshot) {
2381
- const stableInput = {
2382
- schemaVersion: snapshot.schemaVersion,
2383
- appKey: snapshot.app.key,
2384
- capabilities: snapshot.capabilities.map((c) => ({
2385
- ...c
2386
- })),
2387
- features: snapshot.features,
2388
- quotas: snapshot.quotas
2389
- };
2390
- const json = canonicalStringify(stableInput);
2391
- return `sha256-${(0, import_node_crypto2.createHash)("sha256").update(json).digest("hex")}`;
2392
- }
2393
- __name(computeSnapshotHash, "computeSnapshotHash");
2394
- function canonicalStringify(value) {
2395
- return JSON.stringify(value, (_key, val) => {
2396
- if (val && typeof val === "object" && !Array.isArray(val)) {
2397
- const sorted = {};
2398
- for (const k of Object.keys(val).sort()) {
2399
- sorted[k] = val[k];
2400
- }
2401
- return sorted;
2402
- }
2403
- return val;
2404
- });
2405
- }
2406
- __name(canonicalStringify, "canonicalStringify");
2407
-
2408
- // src/discovery/discovery.controller.ts
2409
- function _ts_decorate12(decorators, target, key, desc) {
2410
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2411
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2412
- 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;
2413
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2414
- }
2415
- __name(_ts_decorate12, "_ts_decorate");
2416
- function _ts_metadata9(k, v) {
2417
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2418
- }
2419
- __name(_ts_metadata9, "_ts_metadata");
2420
- function _ts_param9(paramIndex, decorator) {
2421
- return function(target, key) {
2422
- decorator(target, key, paramIndex);
2423
- };
2424
- }
2425
- __name(_ts_param9, "_ts_param");
2426
- function buildDiscoveryController(guards) {
2427
- let GeneratedDiscoveryController = class GeneratedDiscoveryController {
2428
- static {
2429
- __name(this, "GeneratedDiscoveryController");
2430
- }
2431
- scanner;
2432
- // Explicit @Inject instead of type reflection: tsup/esbuild do not emit
2433
- // any `design:paramtypes` metadata, so otherwise Nest cannot resolve the
2434
- // service type on the constructor.
2435
- constructor(scanner) {
2436
- this.scanner = scanner;
2437
- }
2438
- getDiscovery(ifNoneMatch, res) {
2439
- const snapshot = this.scanner.getSnapshot();
2440
- const etag = `"${snapshot.hash}-${snapshot.scannedAt}"`;
2441
- res.header("ETag", etag);
2442
- if (ifNoneMatch && ifNoneMatch === etag) {
2443
- res.status(import_common12.HttpStatus.NOT_MODIFIED);
2444
- return null;
2445
- }
2446
- return snapshot;
2447
- }
2448
- /**
2449
- * Forces a fresh code scan and returns the new snapshot.
2450
- * `scannedAt` is updated; the Capability/Feature/Quota data
2451
- * are identical within the running process (code is stable at runtime).
2452
- */
2453
- rescanDiscovery() {
2454
- return this.scanner.rebuildSnapshot();
2455
- }
2456
- };
2457
- _ts_decorate12([
2458
- (0, import_common12.Get)("discovery"),
2459
- (0, import_common12.Header)("Cache-Control", "private, no-cache"),
2460
- _ts_param9(0, (0, import_common12.Headers)("if-none-match")),
2461
- _ts_param9(1, (0, import_common12.Res)({
2462
- passthrough: true
2463
- })),
2464
- _ts_metadata9("design:type", Function),
2465
- _ts_metadata9("design:paramtypes", [
2466
- Object,
2467
- typeof HttpResponseLike === "undefined" ? Object : HttpResponseLike
2468
- ]),
2469
- _ts_metadata9("design:returntype", Object)
2470
- ], GeneratedDiscoveryController.prototype, "getDiscovery", null);
2471
- _ts_decorate12([
2472
- (0, import_common12.Post)("discovery/rescan"),
2473
- _ts_metadata9("design:type", Function),
2474
- _ts_metadata9("design:paramtypes", []),
2475
- _ts_metadata9("design:returntype", typeof DiscoverySnapshot === "undefined" ? Object : DiscoverySnapshot)
2476
- ], GeneratedDiscoveryController.prototype, "rescanDiscovery", null);
2477
- GeneratedDiscoveryController = _ts_decorate12([
2478
- (0, import_common12.Controller)("admin"),
2479
- (0, import_common12.UseGuards)(...guards),
2480
- _ts_param9(0, (0, import_common12.Inject)(DiscoveryScanner)),
2481
- _ts_metadata9("design:type", Function),
2482
- _ts_metadata9("design:paramtypes", [
2483
- typeof DiscoveryScanner === "undefined" ? Object : DiscoveryScanner
2484
- ])
2485
- ], GeneratedDiscoveryController);
2486
- return GeneratedDiscoveryController;
2487
- }
2488
- __name(buildDiscoveryController, "buildDiscoveryController");
2489
-
2490
- // src/discovery/discovery.module.ts
2491
- function _ts_decorate13(decorators, target, key, desc) {
2492
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2493
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2494
- 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;
2495
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2496
- }
2497
- __name(_ts_decorate13, "_ts_decorate");
2498
- var DiscoveryModule = class _DiscoveryModule {
2499
- static {
2500
- __name(this, "DiscoveryModule");
2501
- }
2502
- static forRoot(options) {
2503
- const isGlobal = options.isGlobal ?? true;
2504
- const controllers = [];
2505
- if (options.controller) {
2506
- controllers.push(buildDiscoveryController(options.controller.guards));
2507
- }
2508
- const providers = [
2509
- {
2510
- provide: DISCOVERY_APP_INFO_TOKEN,
2511
- useValue: options.app
2512
- },
2513
- {
2514
- provide: DISCOVERY_SNAPSHOT_PATH_TOKEN,
2515
- useValue: options.snapshotPath ?? null
2516
- },
2517
- DiscoveryScanner,
2518
- {
2519
- provide: DISCOVERY_SNAPSHOT_TOKEN,
2520
- useFactory: /* @__PURE__ */ __name((scanner) => scanner.getSnapshot(), "useFactory"),
2521
- inject: [
2522
- DiscoveryScanner
2523
- ]
2524
- }
2525
- ];
2526
- return {
2527
- module: _DiscoveryModule,
2528
- imports: [
2529
- import_core3.DiscoveryModule,
2530
- ...options.imports ?? []
2531
- ],
2532
- controllers,
2533
- providers,
2534
- exports: [
2535
- DiscoveryScanner,
2536
- DISCOVERY_SNAPSHOT_TOKEN
2537
- ],
2538
- global: isGlobal
2539
- };
2540
- }
2541
- };
2542
- DiscoveryModule = _ts_decorate13([
2543
- (0, import_common13.Module)({})
2544
- ], DiscoveryModule);
2545
-
2546
- // src/entitlement/module.ts
2547
- var import_common15 = require("@nestjs/common");
2548
-
2549
- // src/entitlement/service.ts
2550
- var import_common14 = require("@nestjs/common");
2551
-
2552
- // src/catalog/tokens.ts
2553
- var BUNDLE_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol("BUNDLE_REPOSITORY");
2554
-
2555
- // src/billing/subscription-bundles.tokens.ts
2556
- var SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionBundleRepository");
2557
-
2558
- // src/subscription-contract/tokens.ts
2559
- var SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionContractRepository");
2560
-
2561
- // src/billing/plan-helpers.ts
2562
- function isFeaturePlannedOnly(catalog, featureKey) {
2563
- const def = (catalog.features ?? []).find((f) => f.key === featureKey);
2564
- return def?.plannedOnly === true;
2565
- }
2566
- __name(isFeaturePlannedOnly, "isFeaturePlannedOnly");
2567
-
2568
- // src/entitlement/aggregation.ts
2569
- function filterActiveSubscriptionBundles(bundles, now) {
2570
- return bundles.filter((b) => b.canceledEffectiveAt === null || b.canceledEffectiveAt > now);
2571
- }
2572
- __name(filterActiveSubscriptionBundles, "filterActiveSubscriptionBundles");
2573
- function aggregateSubscriptionBundleQuotas(bundles) {
2574
- const sums = {};
2575
- for (const bundle of bundles) {
2576
- for (const [key, value] of Object.entries(bundle.quotas)) {
2577
- if (sums[key] === -1 || value === -1) {
2578
- sums[key] = -1;
2579
- } else {
2580
- sums[key] = (sums[key] ?? 0) + value;
2581
- }
2582
- }
2583
- }
2584
- return sums;
2585
- }
2586
- __name(aggregateSubscriptionBundleQuotas, "aggregateSubscriptionBundleQuotas");
2587
- function collectSubscriptionBundleFeatures(bundles) {
2588
- const set = /* @__PURE__ */ new Set();
2589
- for (const bundle of bundles) {
2590
- for (const f of bundle.features) set.add(f);
2591
- }
2592
- return Array.from(set);
2593
- }
2594
- __name(collectSubscriptionBundleFeatures, "collectSubscriptionBundleFeatures");
2595
- function aggregateContractLineItemEntitlements(lineItems, fallbackPlan = "UNKNOWN") {
2596
- const planItem = lineItems.find((item) => item.kind === "plan");
2597
- const plan = planItem?.sourceKey ?? fallbackPlan;
2598
- const quotas = {};
2599
- const features = /* @__PURE__ */ new Set();
2600
- for (const item of lineItems) {
2601
- for (const feature of item.featuresSnapshot) {
2602
- features.add(feature);
2603
- }
2604
- for (const [key, value] of Object.entries(item.quotaEffectsSnapshot)) {
2605
- if (quotas[key] === -1 || value === -1) {
2606
- quotas[key] = -1;
2607
- } else {
2608
- quotas[key] = (quotas[key] ?? 0) + value;
2609
- }
2610
- }
2611
- }
2612
- return {
2613
- plan,
2614
- quotas,
2615
- features
2616
- };
2617
- }
2618
- __name(aggregateContractLineItemEntitlements, "aggregateContractLineItemEntitlements");
2619
- function filterPlannedOnlyFeatures(features, catalog) {
2620
- const out = /* @__PURE__ */ new Set();
2621
- for (const f of features) {
2622
- if (!isFeaturePlannedOnly(catalog, f)) {
2623
- out.add(f);
2624
- }
2625
- }
2626
- return out;
2627
- }
2628
- __name(filterPlannedOnlyFeatures, "filterPlannedOnlyFeatures");
2629
- function applyCustomLimits(base, custom) {
2630
- if (!custom) return base;
2631
- const quotas = {
2632
- ...base.quotas
2633
- };
2634
- if (custom.quotas) {
2635
- for (const [k, v] of Object.entries(custom.quotas)) {
2636
- quotas[k] = v;
2637
- }
2638
- }
2639
- const features = new Set(base.features);
2640
- if (custom.features) {
2641
- for (const f of custom.features) features.add(f);
2642
- }
2643
- return {
2644
- plan: base.plan,
2645
- quotas,
2646
- features
2647
- };
2648
- }
2649
- __name(applyCustomLimits, "applyCustomLimits");
2650
- function aggregateLimits(input, catalog, now) {
2651
- const activeBundles = filterActiveSubscriptionBundles(input.subscriptionBundles ?? [], now);
2652
- const subBundleQuotas = aggregateSubscriptionBundleQuotas(activeBundles);
2653
- const subBundleFeatures = collectSubscriptionBundleFeatures(activeBundles);
2654
- const quotas = {};
2655
- for (const [k, v] of Object.entries(input.planVersion.quotas)) {
2656
- quotas[k] = v;
2657
- }
2658
- for (const [k, v] of Object.entries(subBundleQuotas)) {
2659
- if (quotas[k] === -1 || v === -1) {
2660
- quotas[k] = -1;
2661
- } else {
2662
- quotas[k] = (quotas[k] ?? 0) + v;
2663
- }
2664
- }
2665
- const features = /* @__PURE__ */ new Set([
2666
- ...input.planVersion.features,
2667
- ...subBundleFeatures
2668
- ]);
2669
- const withCustom = applyCustomLimits({
2670
- plan: input.plan,
2671
- quotas,
2672
- features
2673
- }, input.customLimits);
2674
- const filteredFeatures = filterPlannedOnlyFeatures(withCustom.features, catalog);
2675
- return {
2676
- plan: withCustom.plan,
2677
- quotas: withCustom.quotas,
2678
- features: filteredFeatures
2679
- };
2680
- }
2681
- __name(aggregateLimits, "aggregateLimits");
2682
-
2683
- // src/entitlement/feature-aliases.ts
2684
- function buildReplacedByIndex(features) {
2685
- const index = /* @__PURE__ */ new Map();
2686
- for (const feature of features) {
2687
- for (const oldKey of feature.replaces ?? []) {
2688
- if (oldKey === feature.featureKey) continue;
2689
- const list = index.get(oldKey) ?? [];
2690
- list.push(feature.featureKey);
2691
- index.set(oldKey, list);
2692
- }
2693
- }
2694
- for (const list of index.values()) list.sort((a, b) => a.localeCompare(b));
2695
- assertNoReplacesCycle(index);
2696
- return index;
2697
- }
2698
- __name(buildReplacedByIndex, "buildReplacedByIndex");
2699
- function expandReplacedFeatures(granted, index) {
2700
- const expanded = new Set(granted);
2701
- const worklist = [
2702
- ...granted
2703
- ];
2704
- while (worklist.length > 0) {
2705
- const key = worklist.pop();
2706
- for (const successor of index.get(key) ?? []) {
2707
- if (expanded.has(successor)) continue;
2708
- expanded.add(successor);
2709
- worklist.push(successor);
2710
- }
2711
- }
2712
- return expanded;
2713
- }
2714
- __name(expandReplacedFeatures, "expandReplacedFeatures");
2715
- function assertNoReplacesCycle(index) {
2716
- const DONE = 2;
2717
- const IN_PROGRESS = 1;
2718
- const state = /* @__PURE__ */ new Map();
2719
- const visit = /* @__PURE__ */ __name((key, path) => {
2720
- const s = state.get(key);
2721
- if (s === DONE) return;
2722
- if (s === IN_PROGRESS) {
2723
- const cycleStart = path.indexOf(key);
2724
- const cycle = [
2725
- ...path.slice(cycleStart),
2726
- key
2727
- ].join(" \u2192 ");
2728
- throw new Error(`replaces-Zyklus in den Discovery-Metadaten: ${cycle}. Feature-Ersetzungen m\xFCssen azyklisch sein \u2014 replaces-Deklarationen im Code korrigieren (#39).`);
2729
- }
2730
- state.set(key, IN_PROGRESS);
2731
- for (const successor of index.get(key) ?? []) {
2732
- visit(successor, [
2733
- ...path,
2734
- key
2735
- ]);
2736
- }
2737
- state.set(key, DONE);
2738
- }, "visit");
2739
- for (const key of index.keys()) visit(key, []);
2740
- }
2741
- __name(assertNoReplacesCycle, "assertNoReplacesCycle");
2742
-
2743
- // src/entitlement/limit-exceeded-error.ts
2744
- var LIMIT_EXCEEDED = "LIMIT_EXCEEDED";
2745
- var LimitExceededError = class extends Error {
2746
- static {
2747
- __name(this, "LimitExceededError");
2748
- }
2749
- dimension;
2750
- max;
2751
- used;
2752
- code = LIMIT_EXCEEDED;
2753
- constructor(dimension, max, used) {
2754
- super(`Limit f\xFCr ${dimension} erreicht: ${used}/${max}.`), this.dimension = dimension, this.max = max, this.used = used;
2755
- this.name = "LimitExceededError";
2756
- }
2757
- };
2758
-
2759
- // src/entitlement/plan-resolution.ts
2760
- function resolveEntitlementPlan(input, config, now) {
2761
- if (input.isPilot && config.pilotEntitlementPlan !== void 0) {
2762
- return config.pilotEntitlementPlan;
2763
- }
2764
- if (input.status === "TRIAL") {
2765
- return input.trialEntitlementPlan ?? config.defaultTrialEntitlementPlan ?? input.plan;
2766
- }
2767
- if (input.status === "PENDING_SALES" && config.pendingSalesEntitlementPlan !== void 0) {
2768
- return config.pendingSalesEntitlementPlan;
2769
- }
2770
- if (input.pendingPlan && input.pendingEffectiveAt && input.pendingEffectiveAt <= now) {
2771
- return input.pendingPlan;
2772
- }
2773
- return input.plan;
2774
- }
2775
- __name(resolveEntitlementPlan, "resolveEntitlementPlan");
2776
-
2777
- // src/entitlement/tokens.ts
2778
- var ENTITLEMENT_SERVICE_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/EntitlementService");
2779
- var SUBSCRIPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionRepository");
2780
- var PLAN_VERSION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/PlanVersionRepository");
2781
- var TRANSACTION_RUNNER_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/TransactionRunner");
2782
- var ENTITLEMENT_RESOLUTION_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/EntitlementResolutionConfig");
2783
-
2784
- // src/entitlement/service.ts
2785
- function _ts_decorate14(decorators, target, key, desc) {
2786
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2787
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
2788
- 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;
2789
- return c > 3 && r && Object.defineProperty(target, key, r), r;
2790
- }
2791
- __name(_ts_decorate14, "_ts_decorate");
2792
- function _ts_metadata10(k, v) {
2793
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
2794
- }
2795
- __name(_ts_metadata10, "_ts_metadata");
2796
- function _ts_param10(paramIndex, decorator) {
2797
- return function(target, key) {
2798
- decorator(target, key, paramIndex);
2799
- };
2800
- }
2801
- __name(_ts_param10, "_ts_param");
2802
- var CACHE_TTL_MS = 6e4;
2803
- var CACHE_MAX_ENTRIES = 1e3;
2804
- var EntitlementService = class {
2805
- static {
2806
- __name(this, "EntitlementService");
2807
- }
2808
- catalog;
2809
- subscriptions;
2810
- planVersions;
2811
- tx;
2812
- resolutionConfig;
2813
- subscriptionBundles;
2814
- bundles;
2815
- subscriptionContracts;
2816
- discoverySnapshot;
2817
- // Map preserves insert order — on a hit the entry is removed and
2818
- // re-inserted (LRU). The TTL is a safety net for forgotten
2819
- // invalidations — 60s is enough, because limits generally only change
2820
- // through mutations, not through time (a pending plan's effective date
2821
- // is day-granular).
2822
- cache = /* @__PURE__ */ new Map();
2823
- // #39 — replaces alias index, built lazily from the boot-static snapshot;
2824
- // throws on replaces cycles (a clear error instead of an infinite loop).
2825
- replacedByIndex = null;
2826
- constructor(catalog, subscriptions, planVersions, tx, resolutionConfig = null, subscriptionBundles = null, bundles = null, subscriptionContracts = null, discoverySnapshot = null) {
2827
- this.catalog = catalog;
2828
- this.subscriptions = subscriptions;
2829
- this.planVersions = planVersions;
2830
- this.tx = tx;
2831
- this.resolutionConfig = resolutionConfig;
2832
- this.subscriptionBundles = subscriptionBundles;
2833
- this.bundles = bundles;
2834
- this.subscriptionContracts = subscriptionContracts;
2835
- this.discoverySnapshot = discoverySnapshot;
2836
- }
2837
- // ---------------------------------------------------------------------
2838
- // Read path — for FeatureGuard, sidebar hooks, GET /billing/entitlement
2839
- // ---------------------------------------------------------------------
2840
- async computeLimits(tenantId, now = /* @__PURE__ */ new Date()) {
2841
- const cached = this.readCache(tenantId, now.getTime());
2842
- if (cached) return cached;
2843
- const sub = await this.subscriptions.findByTenantId(tenantId);
2844
- if (!sub) {
2845
- throw new import_common14.NotFoundException(`Keine Subscription f\xFCr Tenant ${tenantId}`);
2846
- }
2847
- const limits = await this.deriveLimits(sub, now);
2848
- this.writeCache(tenantId, limits, now.getTime());
2849
- return limits;
2850
- }
2851
- /**
2852
- * Clears a tenant's cache entry. MUST be called as soon as a mutation
2853
- * changes the subscription, a bundle booking, or a PlanVersion binding —
2854
- * otherwise FeatureGuard and the sidebar stay on stale state for up to
2855
- * 60s.
2856
- */
2857
- invalidateTenant(tenantId) {
2858
- this.cache.delete(tenantId);
2859
- }
2860
- /** Clears the entire cache. Only for tests / bootstrap. */
2861
- invalidateAll() {
2862
- this.cache.clear();
2863
- }
2864
- /**
2865
- * Aggregator path — also callable directly by consumers that already
2866
- * have a `SubscriptionRecord` (e.g. within a transaction).
2867
- */
2868
- async deriveLimits(sub, now, tx) {
2869
- const contractLimits = await this.deriveLimitsFromContract(sub.tenantId, now);
2870
- if (contractLimits) return this.withReplacedFeatureAliases(contractLimits);
2871
- const effectivePlan = resolveEntitlementPlan(sub, this.resolutionConfig ?? {}, now);
2872
- const planVersion = effectivePlan === sub.plan ? sub.planVersion : await this.findActivePlanVersionOrFallback(effectivePlan, now, tx);
2873
- const subscriptionBundles = await this.loadSubscriptionBundleSnapshots(sub.id, now);
2874
- return this.withReplacedFeatureAliases(aggregateLimits({
2875
- plan: effectivePlan,
2876
- planVersion,
2877
- subscriptionBundles,
2878
- customLimits: sub.customLimits ?? null
2879
- }, this.catalog, now));
2880
- }
2881
- /**
2882
- * Grandfathering (#39): granted old feature keys transitively grant
2883
- * their successors from the `replaces` chains of the discovery snapshot.
2884
- * A no-op without a snapshot or without replaces declarations.
2885
- */
2886
- withReplacedFeatureAliases(limits) {
2887
- if (!this.discoverySnapshot) return limits;
2888
- if (!this.replacedByIndex) {
2889
- this.replacedByIndex = buildReplacedByIndex(this.discoverySnapshot.features);
2890
- }
2891
- if (this.replacedByIndex.size === 0) return limits;
2892
- return {
2893
- ...limits,
2894
- features: expandReplacedFeatures(limits.features, this.replacedByIndex)
2895
- };
2896
- }
2897
- async deriveLimitsFromContract(tenantId, now) {
2898
- if (!this.subscriptionContracts) return null;
2899
- const contract = await this.subscriptionContracts.findActiveByTenantId(tenantId, now);
2900
- if (!contract) return null;
2901
- if (contract.entitlementSnapshot) {
2902
- return {
2903
- plan: contract.entitlementSnapshot.plan,
2904
- quotas: {
2905
- ...contract.entitlementSnapshot.quotas
2906
- },
2907
- features: new Set(contract.entitlementSnapshot.features)
2908
- };
2909
- }
2910
- return aggregateContractLineItemEntitlements(contract.lineItems);
2911
- }
2912
- /**
2913
- * Loads a subscription's active bundle bookings and resolves the
2914
- * `BundleVersion` features/quotas per entry. Without a registered
2915
- * `SubscriptionBundleRepository` or `BundleRepository` the method
2916
- * returns an empty list — apps without a bundle schema stay unchanged
2917
- * (plan-only aggregation).
2918
- */
2919
- async loadSubscriptionBundleSnapshots(subscriptionId, now) {
2920
- if (!this.subscriptionBundles || !this.bundles) return [];
2921
- const active = await this.subscriptionBundles.listActiveBySubscription(subscriptionId, now);
2922
- if (active.length === 0) return [];
2923
- return Promise.all(active.map(async (booking) => {
2924
- const bv = await this.bundles.findVersionById(booking.bundleVersionId);
2925
- if (!bv) {
2926
- throw new Error(`BundleVersion '${booking.bundleVersionId}' aus aktiver SubscriptionBundle nicht gefunden`);
2927
- }
2928
- return {
2929
- bundleKey: bv.bundleKey,
2930
- features: bv.features,
2931
- quotas: bv.quotas,
2932
- canceledEffectiveAt: booking.canceledEffectiveAt
2933
- };
2934
- }));
2935
- }
2936
- // ---------------------------------------------------------------------
2937
- // Write/consumption path — row lock + count + insert in the same
2938
- // transaction. Protects against race conditions on concurrent creations
2939
- // for the same tenant.
2940
- // ---------------------------------------------------------------------
2941
- async enforceLimit(input) {
2942
- const now = input.now ?? /* @__PURE__ */ new Date();
2943
- const delta = input.delta ?? 1;
2944
- return this.tx.run(async (tx) => {
2945
- const sub = await this.subscriptions.findByTenantIdLocked(input.tenantId, tx);
2946
- if (!sub) {
2947
- throw new import_common14.NotFoundException(`Keine Subscription f\xFCr Tenant ${input.tenantId}`);
2948
- }
2949
- const limits = await this.deriveLimits(sub, now, tx);
2950
- const max = limits.quotas[input.dimension];
2951
- if (max === void 0) {
2952
- throw new Error(`Quota-Dimension "${input.dimension}" nicht im PlanCatalog (${this.catalog.projectKey}).`);
2953
- }
2954
- const used = await input.currentUsage(tx, input.tenantId);
2955
- if (max !== -1 && used + delta > max) {
2956
- throw new LimitExceededError(input.dimension, max, used);
2957
- }
2958
- return input.insert(tx);
2959
- });
2960
- }
2961
- // ---------------------------------------------------------------------
2962
- // Helpers
2963
- // ---------------------------------------------------------------------
2964
- /**
2965
- * Plan fallback for TRIAL/PENDING_SALES: returns the PlanVersion active
2966
- * as of `asOf` (SPEC_V2 §4.2). If the repo does not implement
2967
- * `findActive`, we fall back to `findLatestLive` (backward compat for
2968
- * adapters without `validFrom` columns).
2969
- */
2970
- async findActivePlanVersionOrFallback(planId, asOf, tx) {
2971
- const v = this.planVersions.findActive ? await this.planVersions.findActive(planId, asOf, tx) : await this.planVersions.findLatestLive(planId, tx);
2972
- if (!v) {
2973
- throw new import_common14.NotFoundException(`Keine zu ${asOf.toISOString().slice(0, 10)} aktive PlanVersion f\xFCr ${planId} \u2014 weder validFrom-window erf\xFCllt noch latest-live verf\xFCgbar.`);
2974
- }
2975
- return v;
2976
- }
2977
- readCache(tenantId, nowMs) {
2978
- const entry = this.cache.get(tenantId);
2979
- if (!entry) return null;
2980
- if (entry.expiresAt < nowMs) {
2981
- this.cache.delete(tenantId);
2982
- return null;
2983
- }
2984
- this.cache.delete(tenantId);
2985
- this.cache.set(tenantId, entry);
2986
- return entry.value;
2987
- }
2988
- writeCache(tenantId, value, nowMs) {
2989
- if (this.cache.has(tenantId)) this.cache.delete(tenantId);
2990
- this.cache.set(tenantId, {
2991
- value,
2992
- expiresAt: nowMs + CACHE_TTL_MS
2993
- });
2994
- while (this.cache.size > CACHE_MAX_ENTRIES) {
2995
- const oldest = this.cache.keys().next().value;
2996
- if (oldest === void 0) break;
2997
- this.cache.delete(oldest);
2998
- }
2999
- }
3000
- };
3001
- EntitlementService = _ts_decorate14([
3002
- (0, import_common14.Injectable)(),
3003
- _ts_param10(0, (0, import_common14.Inject)(PLAN_CATALOG_TOKEN)),
3004
- _ts_param10(1, (0, import_common14.Inject)(SUBSCRIPTION_REPOSITORY_TOKEN)),
3005
- _ts_param10(2, (0, import_common14.Inject)(PLAN_VERSION_REPOSITORY_TOKEN)),
3006
- _ts_param10(3, (0, import_common14.Inject)(TRANSACTION_RUNNER_TOKEN)),
3007
- _ts_param10(4, (0, import_common14.Optional)()),
3008
- _ts_param10(4, (0, import_common14.Inject)(ENTITLEMENT_RESOLUTION_CONFIG_TOKEN)),
3009
- _ts_param10(5, (0, import_common14.Optional)()),
3010
- _ts_param10(5, (0, import_common14.Inject)(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN)),
3011
- _ts_param10(6, (0, import_common14.Optional)()),
3012
- _ts_param10(6, (0, import_common14.Inject)(BUNDLE_REPOSITORY_TOKEN)),
3013
- _ts_param10(7, (0, import_common14.Optional)()),
3014
- _ts_param10(7, (0, import_common14.Inject)(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN)),
3015
- _ts_param10(8, (0, import_common14.Optional)()),
3016
- _ts_param10(8, (0, import_common14.Inject)(DISCOVERY_SNAPSHOT_TOKEN)),
3017
- _ts_metadata10("design:type", Function),
3018
- _ts_metadata10("design:paramtypes", [
3019
- typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
3020
- typeof SubscriptionRepository === "undefined" ? Object : SubscriptionRepository,
3021
- typeof PlanVersionRepository === "undefined" ? Object : PlanVersionRepository,
3022
- typeof TransactionRunner === "undefined" ? Object : TransactionRunner,
3023
- Object,
3024
- Object,
3025
- Object,
3026
- Object,
3027
- Object
3028
- ])
3029
- ], EntitlementService);
3030
-
3031
- // src/entitlement/module.ts
3032
- function _ts_decorate15(decorators, target, key, desc) {
3033
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3034
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3035
- 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;
3036
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3037
- }
3038
- __name(_ts_decorate15, "_ts_decorate");
3039
- var EntitlementModule = class _EntitlementModule {
3040
- static {
3041
- __name(this, "EntitlementModule");
3042
- }
3043
- static forRoot(options) {
3044
- const providers = [
3045
- asProvider(SUBSCRIPTION_REPOSITORY_TOKEN, options.subscriptionRepository),
3046
- asProvider(PLAN_VERSION_REPOSITORY_TOKEN, options.planVersionRepository),
3047
- asProvider(TRANSACTION_RUNNER_TOKEN, options.transactionRunner),
3048
- EntitlementService,
3049
- // Cross-bundle-stable alias so consumers in other subpath bundles
3050
- // (billing/TenantBillingModule) can inject the same instance.
3051
- {
3052
- provide: ENTITLEMENT_SERVICE_TOKEN,
3053
- useExisting: EntitlementService
3054
- }
3055
- ];
3056
- if (options.resolutionConfig) {
3057
- providers.push({
3058
- provide: ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
3059
- useValue: options.resolutionConfig
3060
- });
3061
- }
3062
- if (options.subscriptionContractRepository) {
3063
- providers.push(asProvider(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN, options.subscriptionContractRepository));
3064
- }
3065
- if (options.subscriptionBundleRepository) {
3066
- providers.push(asProvider(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN, options.subscriptionBundleRepository));
3067
- }
3068
- if (options.bundleRepository) {
3069
- providers.push(asProvider(BUNDLE_REPOSITORY_TOKEN, options.bundleRepository));
3070
- }
3071
- return {
3072
- module: _EntitlementModule,
3073
- global: options.global ?? false,
3074
- providers,
3075
- exports: [
3076
- EntitlementService,
3077
- ENTITLEMENT_SERVICE_TOKEN,
3078
- SUBSCRIPTION_REPOSITORY_TOKEN
3079
- ]
3080
- };
3081
- }
3082
- };
3083
- EntitlementModule = _ts_decorate15([
3084
- (0, import_common15.Module)({})
3085
- ], EntitlementModule);
3086
-
3087
- // src/platform/plan-resolver.port.ts
3088
- var PLAN_RESOLVER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/PlanResolverPort");
3089
- var StaticPlanResolver = class {
3090
- static {
3091
- __name(this, "StaticPlanResolver");
3092
- }
3093
- planId;
3094
- constructor(planId) {
3095
- this.planId = planId;
3096
- }
3097
- async getPlanIdForTenant() {
3098
- return this.planId;
3099
- }
3100
- };
3101
-
3102
- // src/platform/static-entitlement.service.ts
3103
- var import_common16 = require("@nestjs/common");
3104
- function _ts_decorate16(decorators, target, key, desc) {
3105
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3106
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3107
- 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;
3108
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3109
- }
3110
- __name(_ts_decorate16, "_ts_decorate");
3111
- function _ts_metadata11(k, v) {
3112
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3113
- }
3114
- __name(_ts_metadata11, "_ts_metadata");
3115
- function _ts_param11(paramIndex, decorator) {
3116
- return function(target, key) {
3117
- decorator(target, key, paramIndex);
3118
- };
3119
- }
3120
- __name(_ts_param11, "_ts_param");
3121
- var StaticEntitlementService = class {
3122
- static {
3123
- __name(this, "StaticEntitlementService");
3124
- }
3125
- catalog;
3126
- resolver;
3127
- constructor(catalog, resolver) {
3128
- this.catalog = catalog;
3129
- this.resolver = resolver;
3130
- }
3131
- /**
3132
- * Returns the effective features + quotas of a tenant from the
3133
- * plan catalog. When the plan is missing: an empty snapshot (no feature,
3134
- * all quotas at 0).
3135
- */
3136
- async snapshot(tenantId) {
3137
- const planId = await this.resolver.getPlanIdForTenant(tenantId);
3138
- if (!planId) {
3139
- return {
3140
- planId: null,
3141
- features: [],
3142
- quotas: {}
3143
- };
3144
- }
3145
- const plan = this.findPlan(planId);
3146
- if (!plan) {
3147
- return {
3148
- planId,
3149
- features: [],
3150
- quotas: {}
3151
- };
3152
- }
3153
- return {
3154
- planId,
3155
- features: plan.features ?? [],
3156
- quotas: plan.quotas ?? {}
3157
- };
3158
- }
3159
- /** Convenience helper for tests / synchronous checks in the UI manifest. */
3160
- async hasFeature(tenantId, featureKey) {
3161
- const snap = await this.snapshot(tenantId);
3162
- return snap.features.includes(featureKey);
3163
- }
3164
- async quotaLimit(tenantId, quotaKey) {
3165
- const snap = await this.snapshot(tenantId);
3166
- const value = snap.quotas[quotaKey];
3167
- return value === void 0 ? null : value;
3168
- }
3169
- findPlan(planId) {
3170
- return (this.catalog.plans ?? []).find((p) => p.id === planId);
3171
- }
3172
- };
3173
- StaticEntitlementService = _ts_decorate16([
3174
- (0, import_common16.Injectable)(),
3175
- _ts_param11(0, (0, import_common16.Inject)(PLAN_CATALOG_TOKEN)),
3176
- _ts_param11(1, (0, import_common16.Inject)(PLAN_RESOLVER_PORT_TOKEN)),
3177
- _ts_metadata11("design:type", Function),
3178
- _ts_metadata11("design:paramtypes", [
3179
- typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
3180
- typeof PlanResolverPort === "undefined" ? Object : PlanResolverPort
3181
- ])
3182
- ], StaticEntitlementService);
3183
-
3184
- // src/platform/static-feature.guard.ts
3185
- var import_common18 = require("@nestjs/common");
3186
- var import_core4 = require("@nestjs/core");
3187
-
3188
- // src/billing/require-feature.decorator.ts
3189
- var import_common17 = require("@nestjs/common");
3190
- var REQUIRE_FEATURE_KEY = "require-feature";
3191
-
3192
- // src/platform/static-feature.guard.ts
3193
- function _ts_decorate17(decorators, target, key, desc) {
3194
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3195
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3196
- 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;
3197
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3198
- }
3199
- __name(_ts_decorate17, "_ts_decorate");
3200
- function _ts_metadata12(k, v) {
3201
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3202
- }
3203
- __name(_ts_metadata12, "_ts_metadata");
3204
- function _ts_param12(paramIndex, decorator) {
3205
- return function(target, key) {
3206
- decorator(target, key, paramIndex);
3207
- };
3208
- }
3209
- __name(_ts_param12, "_ts_param");
3210
- var STATIC_FEATURE_GUARD_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/StaticFeatureGuardConfig");
3211
- var StaticFeatureGuard = class {
3212
- static {
3213
- __name(this, "StaticFeatureGuard");
3214
- }
3215
- reflector;
3216
- entitlements;
3217
- config;
3218
- constructor(reflector, entitlements, config = null) {
3219
- this.reflector = reflector;
3220
- this.entitlements = entitlements;
3221
- this.config = config;
3222
- }
3223
- async canActivate(context) {
3224
- const required = this.reflector.getAllAndOverride(REQUIRE_FEATURE_KEY, [
3225
- context.getHandler(),
3226
- context.getClass()
3227
- ]);
3228
- if (!required || required.length === 0) return true;
3229
- const request = context.switchToHttp().getRequest();
3230
- const user = request.user;
3231
- if (!user) throw new import_common18.ForbiddenException("Nicht authentifiziert");
3232
- const role = this.config?.userRoleResolver ? this.config.userRoleResolver(user) : user.role ?? user.platformRole;
3233
- if (role === "SUPER_ADMIN") return true;
3234
- const tenantId = this.config?.tenantIdResolver ? this.config.tenantIdResolver(request) : request.tenantId ?? user.tenantId;
3235
- if (!tenantId) throw new import_common18.ForbiddenException("Kein Mandant zugeordnet");
3236
- const snap = await this.entitlements.snapshot(tenantId);
3237
- const allowed = required.some((f) => snap.features.includes(f));
3238
- if (!allowed) {
3239
- throw new import_common18.ForbiddenException(`Feature ${required.join(" / ")} nicht im aktuellen Plan enthalten.`);
3240
- }
3241
- return true;
3242
- }
3243
- };
3244
- StaticFeatureGuard = _ts_decorate17([
3245
- (0, import_common18.Injectable)(),
3246
- _ts_param12(0, (0, import_common18.Inject)(import_core4.Reflector)),
3247
- _ts_param12(1, (0, import_common18.Inject)(StaticEntitlementService)),
3248
- _ts_param12(2, (0, import_common18.Optional)()),
3249
- _ts_param12(2, (0, import_common18.Inject)(STATIC_FEATURE_GUARD_CONFIG_TOKEN)),
3250
- _ts_metadata12("design:type", Function),
3251
- _ts_metadata12("design:paramtypes", [
3252
- typeof import_core4.Reflector === "undefined" ? Object : import_core4.Reflector,
3253
- typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
3254
- Object
3255
- ])
3256
- ], StaticFeatureGuard);
3257
-
3258
- // src/platform/enforce-quota.interceptor.ts
3259
- var import_common19 = require("@nestjs/common");
3260
- var import_core5 = require("@nestjs/core");
3261
- var import_rxjs2 = require("rxjs");
3262
- var import_operators2 = require("rxjs/operators");
3263
- function _ts_decorate18(decorators, target, key, desc) {
3264
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3265
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3266
- 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;
3267
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3268
- }
3269
- __name(_ts_decorate18, "_ts_decorate");
3270
- function _ts_metadata13(k, v) {
3271
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3272
- }
3273
- __name(_ts_metadata13, "_ts_metadata");
3274
- function _ts_param13(paramIndex, decorator) {
3275
- return function(target, key) {
3276
- decorator(target, key, paramIndex);
3277
- };
3278
- }
3279
- __name(_ts_param13, "_ts_param");
3280
- var QUOTA_PROVIDERS_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/QuotaProviders");
3281
- var EnforceQuotaInterceptor = class {
3282
- static {
3283
- __name(this, "EnforceQuotaInterceptor");
3284
- }
3285
- reflector;
3286
- entitlements;
3287
- providers;
3288
- constructor(reflector, entitlements, providers = null) {
3289
- this.reflector = reflector;
3290
- this.entitlements = entitlements;
3291
- this.providers = providers;
3292
- }
3293
- intercept(context, next) {
3294
- const meta = this.reflector.getAllAndOverride(ENFORCE_QUOTA_KEY, [
3295
- context.getHandler(),
3296
- context.getClass()
3297
- ]);
3298
- if (!meta?.quotaKey) return next.handle();
3299
- return (0, import_rxjs2.from)(this.check(context, meta)).pipe((0, import_operators2.switchMap)(() => next.handle()));
3300
- }
3301
- async check(context, meta) {
3302
- const request = context.switchToHttp().getRequest();
3303
- const user = request.user;
3304
- if (!user) return;
3305
- const role = user.role ?? user.platformRole;
3306
- if (role === "SUPER_ADMIN") return;
3307
- const tenantId = request.tenantId ?? user.tenantId;
3308
- if (!tenantId) return;
3309
- const provider = (this.providers ?? []).find((p) => p.key === meta.quotaKey);
3310
- if (!provider) {
3311
- return;
3312
- }
3313
- const limit = await this.entitlements.quotaLimit(tenantId, meta.quotaKey);
3314
- if (limit === null || limit === -1) return;
3315
- const current = await provider.count(tenantId);
3316
- const delta = meta.incrementBy ?? 1;
3317
- if (current + delta > limit) {
3318
- throw new LimitExceededError(meta.quotaKey, limit, current);
3319
- }
3320
- }
3321
- };
3322
- EnforceQuotaInterceptor = _ts_decorate18([
3323
- (0, import_common19.Injectable)(),
3324
- _ts_param13(0, (0, import_common19.Inject)(import_core5.Reflector)),
3325
- _ts_param13(1, (0, import_common19.Inject)(StaticEntitlementService)),
3326
- _ts_param13(2, (0, import_common19.Optional)()),
3327
- _ts_param13(2, (0, import_common19.Inject)(QUOTA_PROVIDERS_TOKEN)),
3328
- _ts_metadata13("design:type", Function),
3329
- _ts_metadata13("design:paramtypes", [
3330
- typeof import_core5.Reflector === "undefined" ? Object : import_core5.Reflector,
3331
- typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
3332
- Object
3333
- ])
3334
- ], EnforceQuotaInterceptor);
3335
-
3336
- // src/platform/tenant-manifest.service.ts
3337
- var import_common20 = require("@nestjs/common");
3338
- function _ts_decorate19(decorators, target, key, desc) {
3339
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3340
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3341
- 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;
3342
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3343
- }
3344
- __name(_ts_decorate19, "_ts_decorate");
3345
- function _ts_metadata14(k, v) {
3346
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3347
- }
3348
- __name(_ts_metadata14, "_ts_metadata");
3349
- var TenantManifestService = class {
3350
- static {
3351
- __name(this, "TenantManifestService");
3352
- }
3353
- entitlements;
3354
- navItems = [];
3355
- constructor(entitlements) {
3356
- this.entitlements = entitlements;
3357
- }
3358
- /**
3359
- * Call in the app's `OnModuleInit`: registers app navigation for the
3360
- * Tenant UI. Idempotent: the same `id` overwrites.
3361
- */
3362
- registerNavItem(item) {
3363
- const existing = this.navItems.findIndex((i) => i.id === item.id);
3364
- if (existing >= 0) this.navItems[existing] = item;
3365
- else this.navItems.push(item);
3366
- }
3367
- /** Bulk variant. */
3368
- registerNavItems(items) {
3369
- for (const item of items) this.registerNavItem(item);
3370
- }
3371
- /**
3372
- * Returns the filtered manifest for a Tenant. NavItems with
3373
- * `requiresFeature` are filtered out server-side as soon as none of the
3374
- * required features are present in the entitlement snapshot.
3375
- */
3376
- async getManifest(tenantId) {
3377
- const snap = await this.entitlements.snapshot(tenantId);
3378
- const featureSet = new Set(snap.features);
3379
- const visible = this.navItems.filter((item) => isVisible(item, featureSet)).map((item) => ({
3380
- ...item
3381
- })).sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
3382
- return {
3383
- schemaVersion: 1,
3384
- tenant: {
3385
- id: tenantId
3386
- },
3387
- planId: snap.planId,
3388
- features: snap.features,
3389
- quotas: snap.quotas,
3390
- navigation: visible
3391
- };
3392
- }
3393
- };
3394
- TenantManifestService = _ts_decorate19([
3395
- (0, import_common20.Injectable)(),
3396
- _ts_metadata14("design:type", Function),
3397
- _ts_metadata14("design:paramtypes", [
3398
- typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService
3399
- ])
3400
- ], TenantManifestService);
3401
- function isVisible(item, features) {
3402
- if (!item.requiresFeature) return true;
3403
- const required = Array.isArray(item.requiresFeature) ? item.requiresFeature : [
3404
- item.requiresFeature
3405
- ];
3406
- return required.some((f) => features.has(f));
3407
- }
3408
- __name(isVisible, "isVisible");
3409
-
3410
- // src/platform/tenant-manifest.controller.ts
3411
- var import_common21 = require("@nestjs/common");
3412
- function _ts_decorate20(decorators, target, key, desc) {
3413
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3414
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3415
- 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;
3416
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3417
- }
3418
- __name(_ts_decorate20, "_ts_decorate");
3419
- function _ts_metadata15(k, v) {
3420
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
3421
- }
3422
- __name(_ts_metadata15, "_ts_metadata");
3423
- function _ts_param14(paramIndex, decorator) {
3424
- return function(target, key) {
3425
- decorator(target, key, paramIndex);
3426
- };
3427
- }
3428
- __name(_ts_param14, "_ts_param");
3429
- function buildTenantManifestController(options) {
3430
- const path = options.path ?? "tenant/manifest";
3431
- let GeneratedTenantManifestController = class GeneratedTenantManifestController {
3432
- static {
3433
- __name(this, "GeneratedTenantManifestController");
3434
- }
3435
- svc;
3436
- constructor(svc) {
3437
- this.svc = svc;
3438
- }
3439
- async get(req) {
3440
- const tenantId = req.user?.tenantId ?? req.tenantId;
3441
- if (!tenantId) {
3442
- throw new Error("Kein Mandant im Request \u2014 TenantManifest braucht user.tenantId.");
3443
- }
3444
- return this.svc.getManifest(tenantId);
3445
- }
3446
- };
3447
- _ts_decorate20([
3448
- (0, import_common21.Get)(),
3449
- _ts_param14(0, (0, import_common21.Req)()),
3450
- _ts_metadata15("design:type", Function),
3451
- _ts_metadata15("design:paramtypes", [
3452
- typeof RequestWithUser === "undefined" ? Object : RequestWithUser
3453
- ]),
3454
- _ts_metadata15("design:returntype", Promise)
3455
- ], GeneratedTenantManifestController.prototype, "get", null);
3456
- GeneratedTenantManifestController = _ts_decorate20([
3457
- (0, import_common21.Controller)(path),
3458
- (0, import_common21.UseGuards)(...options.guards),
3459
- _ts_param14(0, (0, import_common21.Inject)(TenantManifestService)),
3460
- _ts_metadata15("design:type", Function),
3461
- _ts_metadata15("design:paramtypes", [
3462
- typeof TenantManifestService === "undefined" ? Object : TenantManifestService
3463
- ])
3464
- ], GeneratedTenantManifestController);
3465
- return GeneratedTenantManifestController;
3466
- }
3467
- __name(buildTenantManifestController, "buildTenantManifestController");
3468
-
3469
- // src/platform/saas-platform.module.ts
3470
- function _ts_decorate21(decorators, target, key, desc) {
3471
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3472
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3473
- 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;
3474
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3475
- }
3476
- __name(_ts_decorate21, "_ts_decorate");
3477
- function buildMinimalManifestConfig() {
3478
- return {
3479
- useFactory: /* @__PURE__ */ __name((catalog) => ({
3480
- project: {
3481
- key: catalog.projectKey,
3482
- displayName: catalog.app?.name ?? catalog.projectKey,
3483
- label: catalog.app?.label,
3484
- icon: catalog.app?.icon,
3485
- logoUrl: catalog.app?.logoUrl,
3486
- environment: process.env.NODE_ENV === "production" ? "production" : "development",
3487
- availableLocales: catalog.marketing?.availableLocales,
3488
- defaultLocale: catalog.marketing?.availableLocales?.[0]
3489
- },
3490
- build: {
3491
- platformPackageVersion: "0.0.0",
3492
- appVersion: "0.0.0"
3493
- },
3494
- planCatalogSnapshot: {
3495
- source: "saas-platform-module",
3496
- hash: "sha256-quickstart",
3497
- currency: catalog.currency,
3498
- vatRate: catalog.vatRate,
3499
- plans: catalog.plans ?? [],
3500
- features: catalog.features ?? []
3501
- }
3502
- }), "useFactory"),
3503
- inject: [
3504
- PLAN_CATALOG_TOKEN
3505
- ]
3506
- };
3507
- }
3508
- __name(buildMinimalManifestConfig, "buildMinimalManifestConfig");
3509
- var SaasPlatformModule = class _SaasPlatformModule {
3510
- static {
3511
- __name(this, "SaasPlatformModule");
3512
- }
3513
- static forRoot(options) {
3514
- const explicit = options.adapters ?? {};
3515
- const persistence = options.persistence;
3516
- const adapters = {
3517
- mfa: explicit.mfa ?? persistence?.core.mfa,
3518
- audit: explicit.audit ?? persistence?.core.audit,
3519
- rlsBypass: explicit.rlsBypass ?? persistence?.core.rlsBypass,
3520
- planCatalogReadSink: explicit.planCatalogReadSink ?? persistence?.planCatalogReadSink,
3521
- planResolver: explicit.planResolver,
3522
- subscriptionRepository: explicit.subscriptionRepository ?? persistence?.entitlement?.subscriptionRepository,
3523
- planVersionRepository: explicit.planVersionRepository ?? persistence?.entitlement?.planVersionRepository,
3524
- transactionRunner: explicit.transactionRunner ?? persistence?.core.transactionRunner
3525
- };
3526
- const missingCore = [
3527
- "mfa",
3528
- "audit",
3529
- "rlsBypass"
3530
- ].filter((key) => adapters[key] === void 0);
3531
- if (missingCore.length) {
3532
- throw new Error(`SaasPlatformModule.forRoot: adapters missing (provide them via \`adapters\` or a \`persistence\` bundle): ${missingCore.join(", ")}`);
3533
- }
3534
- const mfaPort = adapters.mfa;
3535
- const auditPort = adapters.audit;
3536
- const rlsBypassPort = adapters.rlsBypass;
3537
- if (!options.planCatalog && (!adapters.planCatalogReadSink || !options.dbCatalog)) {
3538
- throw new Error("SaasPlatformModule.forRoot: either set `planCatalog` (quickstart YAML path) or, for DB hydration, BOTH a planCatalogReadSink (`adapters`/`persistence`) AND `dbCatalog` ({ projectKey, currency, vatRate }).");
3539
- }
3540
- if (options.entitlement) {
3541
- const missing = [];
3542
- if (!adapters.subscriptionRepository) missing.push("subscriptionRepository");
3543
- if (!adapters.planVersionRepository) missing.push("planVersionRepository");
3544
- if (!adapters.transactionRunner) missing.push("transactionRunner");
3545
- if (missing.length) {
3546
- throw new Error(`SaasPlatformModule.forRoot: entitlement active, but adapters are missing: ${missing.join(", ")}`);
3547
- }
3548
- if (persistence) {
3549
- (0, import_types2.assertPersistenceCapabilities)(persistence.capabilities, {
3550
- transactions: true,
3551
- pessimisticLocking: true
3552
- }, "SaasPlatformModule entitlement (transactional enforceLimit)");
3553
- }
3554
- }
3555
- const dbCatalog = options.dbCatalog;
3556
- const planCatalogModule = options.planCatalog ? PlanCatalogModule.forRootWithCatalog(options.planCatalog, {
3557
- global: true
3558
- }) : PlanCatalogModule.forRoot({
3559
- projectKey: dbCatalog.projectKey,
3560
- app: dbCatalog.app,
3561
- currency: dbCatalog.currency,
3562
- vatRate: dbCatalog.vatRate,
3563
- marketing: dbCatalog.marketing,
3564
- sink: adapters.planCatalogReadSink,
3565
- imports: options.imports
3566
- });
3567
- const appInfo = options.app ?? {
3568
- key: options.planCatalog?.projectKey ?? options.dbCatalog?.projectKey ?? "app",
3569
- version: options.planCatalog?.app?.version ?? options.dbCatalog?.app?.version ?? "0.0.0"
3570
- };
3571
- const imports = [
3572
- planCatalogModule,
3573
- DiscoveryModule.forRoot({
3574
- app: appInfo,
3575
- controller: {
3576
- guards: options.controller.guards
3577
- },
3578
- imports: options.imports,
3579
- snapshotPath: options.discoverySnapshotPath === void 0 ? "var/discovery-snapshot.json" : options.discoverySnapshotPath
3580
- }),
3581
- AdminModule.forRoot({
3582
- mfaPort,
3583
- auditPort,
3584
- rlsBypassPort,
3585
- global: true
3586
- }),
3587
- AdminManifestModule.forRoot({
3588
- config: options.adminManifestConfig ?? buildMinimalManifestConfig(),
3589
- guards: options.controller.guards,
3590
- reloadGuards: options.reloadGuards,
3591
- // Global like AdminModule above: apps register their manifest
3592
- // contribution by injecting AdminManifestService into one of
3593
- // their own modules (handbook §6.6). Re-exporting the module
3594
- // from here does not make that injection resolvable.
3595
- global: true
3596
- })
3597
- ];
3598
- if (options.entitlement) {
3599
- imports.push(EntitlementModule.forRoot({
3600
- subscriptionRepository: adapters.subscriptionRepository,
3601
- planVersionRepository: adapters.planVersionRepository,
3602
- transactionRunner: adapters.transactionRunner,
3603
- resolutionConfig: options.entitlement.resolutionConfig,
3604
- subscriptionContractRepository: persistence?.entitlement?.subscriptionContractRepository,
3605
- subscriptionBundleRepository: persistence?.entitlement?.subscriptionBundleRepository,
3606
- bundleRepository: persistence?.entitlement?.bundleRepository
3607
- }));
3608
- }
3609
- const lightweightProviders = [];
3610
- const lightweightExports = [];
3611
- const hasResolver = !!adapters.planResolver;
3612
- const hasFallback = !!options.defaultPlanId;
3613
- if (hasResolver || hasFallback) {
3614
- lightweightProviders.push(hasResolver ? asProvider(PLAN_RESOLVER_PORT_TOKEN, adapters.planResolver) : {
3615
- provide: PLAN_RESOLVER_PORT_TOKEN,
3616
- useValue: new StaticPlanResolver(options.defaultPlanId)
3617
- }, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, ...options.quotaProviders ?? [], {
3618
- provide: QUOTA_PROVIDERS_TOKEN,
3619
- useFactory: /* @__PURE__ */ __name((...providers) => providers, "useFactory"),
3620
- inject: options.quotaProviders ?? []
3621
- }, {
3622
- provide: import_core6.APP_GUARD,
3623
- useExisting: StaticFeatureGuard
3624
- }, {
3625
- provide: import_core6.APP_INTERCEPTOR,
3626
- useExisting: EnforceQuotaInterceptor
3627
- });
3628
- lightweightExports.push(PLAN_RESOLVER_PORT_TOKEN, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, QUOTA_PROVIDERS_TOKEN);
3629
- }
3630
- const tenantControllers = [];
3631
- if (options.tenantManifest) {
3632
- if (!hasResolver && !hasFallback) {
3633
- throw new Error("SaasPlatformModule.forRoot: tenantManifest erfordert defaultPlanId oder adapters.planResolver, sonst kann das Manifest keine Features aufl\xF6sen.");
3634
- }
3635
- lightweightProviders.push(TenantManifestService);
3636
- lightweightExports.push(TenantManifestService);
3637
- tenantControllers.push(buildTenantManifestController(options.tenantManifest));
3638
- }
3639
- return {
3640
- module: _SaasPlatformModule,
3641
- imports,
3642
- providers: lightweightProviders,
3643
- controllers: tenantControllers,
3644
- exports: [
3645
- PlanCatalogModule,
3646
- DiscoveryModule,
3647
- AdminModule,
3648
- // ADMIN_MANIFEST_CONFIG travels transitively: re-exporting an
3649
- // imported module's token directly is an UnknownExportException
3650
- // at boot — the module export below already carries it.
3651
- AdminManifestModule,
3652
- ...options.entitlement ? [
3653
- EntitlementModule
3654
- ] : [],
3655
- ...lightweightExports
3656
- ],
3657
- global: true
3658
- };
3659
- }
3660
- };
3661
- SaasPlatformModule = _ts_decorate21([
3662
- (0, import_common22.Module)({})
3663
- ], SaasPlatformModule);
3664
-
3665
- // src/testing/create-saas-platform-test-module.ts
3666
- function _ts_decorate22(decorators, target, key, desc) {
3667
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3668
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
3669
- 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;
3670
- return c > 3 && r && Object.defineProperty(target, key, r), r;
3671
- }
3672
- __name(_ts_decorate22, "_ts_decorate");
3673
- var StubMfaPort = class {
3674
- static {
3675
- __name(this, "StubMfaPort");
3676
- }
3677
- secrets = /* @__PURE__ */ new Map();
3678
- async getSecret(userId) {
3679
- return this.secrets.get(userId) ?? null;
3680
- }
3681
- async setSecret(userId, secret) {
3682
- if (secret === null) this.secrets.delete(userId);
3683
- else this.secrets.set(userId, secret);
3684
- }
3685
- async isEnabled(userId) {
3686
- return this.secrets.has(userId);
3687
- }
3688
- };
3689
- var StubAuditPort = class {
3690
- static {
3691
- __name(this, "StubAuditPort");
3692
- }
3693
- calls = [];
3694
- async write(input) {
3695
- this.calls.push(input);
3696
- }
3697
- };
3698
- var StubRlsBypassPort = class {
3699
- static {
3700
- __name(this, "StubRlsBypassPort");
3701
- }
3702
- async runWithBypass(fn) {
3703
- return fn();
3704
- }
3705
- };
3706
- function createSaasPlatformTestModule(options) {
3707
- const defaultPlanId = options.defaultPlanId ?? options.planCatalog.plans?.[0]?.id;
3708
- let SaasPlatformTestHost = class SaasPlatformTestHost {
3709
- static {
3710
- __name(this, "SaasPlatformTestHost");
3711
- }
3712
- };
3713
- SaasPlatformTestHost = _ts_decorate22([
3714
- (0, import_common23.Module)({
3715
- imports: [
3716
- SaasPlatformModule.forRoot({
3717
- planCatalog: options.planCatalog,
3718
- controller: {
3719
- guards: []
3720
- },
3721
- adapters: {
3722
- mfa: options.overrides?.mfa ?? new StubMfaPort(),
3723
- audit: options.overrides?.audit ?? new StubAuditPort(),
3724
- rlsBypass: options.overrides?.rlsBypass ?? new StubRlsBypassPort()
3725
- },
3726
- defaultPlanId,
3727
- quotaProviders: options.quotaProviders
3728
- })
3729
- ]
3730
- })
3731
- ], SaasPlatformTestHost);
3732
- return {
3733
- module: SaasPlatformTestHost
3734
- };
3735
- }
3736
- __name(createSaasPlatformTestModule, "createSaasPlatformTestModule");
3737
- // Annotate the CommonJS export names for ESM import in node:
3738
- 0 && (module.exports = {
3739
- EnforceQuotaInterceptor,
3740
- FakeBundleRepository,
3741
- FakeMarketingProjectionRepository,
3742
- FakePlanRepository,
3743
- FakePlanVersionRepository,
3744
- FakeSubscriptionBundleRepository,
3745
- FakeSubscriptionContractRepository,
3746
- FakeSubscriptionRepository,
3747
- FakeTransactionRunner,
3748
- PLAN_RESOLVER_PORT_TOKEN,
3749
- StaticEntitlementService,
3750
- StaticFeatureGuard,
3751
- StaticPlanResolver,
3752
- StubAuditPort,
3753
- StubMfaPort,
3754
- StubRlsBypassPort,
3755
- createSaasPlatformTestModule
3756
- });
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').testingNs;