@saasicat/nest 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +53 -0
- package/dist/admin/index.cjs +1283 -0
- package/dist/admin/index.d.cts +225 -0
- package/dist/admin/index.d.ts +225 -0
- package/dist/admin/index.js +58 -0
- package/dist/admin-audit.service-9IqXMlZm.d.cts +30 -0
- package/dist/admin-audit.service-9IqXMlZm.d.ts +30 -0
- package/dist/admin-manifest.config-DyrQNT7M.d.cts +10 -0
- package/dist/admin-manifest.config-DyrQNT7M.d.ts +10 -0
- package/dist/aggregation-CJ3qQf92.d.cts +288 -0
- package/dist/aggregation-Dvz9e8X2.d.ts +288 -0
- package/dist/billing/index.cjs +5205 -0
- package/dist/billing/index.d.cts +1326 -0
- package/dist/billing/index.d.ts +1326 -0
- package/dist/billing/index.js +197 -0
- package/dist/catalog/index.cjs +6449 -0
- package/dist/catalog/index.d.cts +1073 -0
- package/dist/catalog/index.d.ts +1073 -0
- package/dist/catalog/index.js +158 -0
- package/dist/checkout-offer/index.cjs +807 -0
- package/dist/checkout-offer/index.d.cts +120 -0
- package/dist/checkout-offer/index.d.ts +120 -0
- package/dist/checkout-offer/index.js +20 -0
- package/dist/chunk-2FR6ZL7R.js +0 -0
- package/dist/chunk-2PNX2QL2.js +347 -0
- package/dist/chunk-2VNZYRDV.js +326 -0
- package/dist/chunk-7MLGHWKO.js +12 -0
- package/dist/chunk-ABB2CRAS.js +31 -0
- package/dist/chunk-AJ3EPELS.js +157 -0
- package/dist/chunk-E56W4U2P.js +90 -0
- package/dist/chunk-HRU3CP57.js +71 -0
- package/dist/chunk-L4SUWH5B.js +14 -0
- package/dist/chunk-M6CZQYPY.js +314 -0
- package/dist/chunk-MDIZUVIK.js +87 -0
- package/dist/chunk-MV6LW5UB.js +217 -0
- package/dist/chunk-NYLON2VC.js +6 -0
- package/dist/chunk-P6MYZMXQ.js +951 -0
- package/dist/chunk-Q53N43LQ.js +13 -0
- package/dist/chunk-QJVPRD3R.js +573 -0
- package/dist/chunk-RIGLQ4BV.js +3367 -0
- package/dist/chunk-SABTXESR.js +24 -0
- package/dist/chunk-SHUYVCID.js +6 -0
- package/dist/chunk-SYEVGPFC.js +686 -0
- package/dist/chunk-V7HUZ6UQ.js +5923 -0
- package/dist/chunk-WPUVJRIN.js +29 -0
- package/dist/chunk-XCZ3TTFK.js +22 -0
- package/dist/chunk-XSGU2Y2O.js +1611 -0
- package/dist/chunk-YNEW3L63.js +190 -0
- package/dist/chunk-YXGHPILG.js +614 -0
- package/dist/chunk-ZL4LLE2Z.js +465 -0
- package/dist/di-CcNeq9v-.d.cts +16 -0
- package/dist/di-CcNeq9v-.d.ts +16 -0
- package/dist/discovery/index.cjs +588 -0
- package/dist/discovery/index.d.cts +299 -0
- package/dist/discovery/index.d.ts +299 -0
- package/dist/discovery/index.js +49 -0
- package/dist/discovery.scanner-CUYLKlYT.d.cts +59 -0
- package/dist/discovery.scanner-CUYLKlYT.d.ts +59 -0
- package/dist/entitlement/index.cjs +805 -0
- package/dist/entitlement/index.d.cts +60 -0
- package/dist/entitlement/index.d.ts +60 -0
- package/dist/entitlement/index.js +57 -0
- package/dist/index.cjs +15918 -0
- package/dist/index.d.cts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +956 -0
- package/dist/plan-resolution-CFCoUkrE.d.cts +41 -0
- package/dist/plan-resolution-CFCoUkrE.d.ts +41 -0
- package/dist/platform/index.cjs +2772 -0
- package/dist/platform/index.d.cts +118 -0
- package/dist/platform/index.d.ts +118 -0
- package/dist/platform/index.js +37 -0
- package/dist/promo/index.cjs +995 -0
- package/dist/promo/index.d.cts +121 -0
- package/dist/promo/index.d.ts +121 -0
- package/dist/promo/index.js +58 -0
- package/dist/registration/index.cjs +1687 -0
- package/dist/registration/index.d.cts +444 -0
- package/dist/registration/index.d.ts +444 -0
- package/dist/registration/index.js +82 -0
- package/dist/saas-platform.module-CgZ2omgg.d.cts +180 -0
- package/dist/saas-platform.module-DT9TnCzk.d.ts +180 -0
- package/dist/service-DX8KbGXl.d.cts +109 -0
- package/dist/service-DX8KbGXl.d.ts +109 -0
- package/dist/subscription-contract/index.cjs +471 -0
- package/dist/subscription-contract/index.d.cts +17 -0
- package/dist/subscription-contract/index.d.ts +17 -0
- package/dist/subscription-contract/index.js +21 -0
- package/dist/subscription-contract.service--cm47ZJJ.d.cts +39 -0
- package/dist/subscription-contract.service--cm47ZJJ.d.ts +39 -0
- package/dist/testing/index.cjs +3942 -0
- package/dist/testing/index.d.cts +279 -0
- package/dist/testing/index.d.ts +279 -0
- package/dist/testing/index.js +1195 -0
- package/package.json +185 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
+
import { Type, CanActivate, DynamicModule, ForwardReference, LogLevel } from '@nestjs/common';
|
|
3
|
+
import { DiscoveredQuotaPolicy, DiscoveryCodeStatus, CapabilityKind, DiscoverySnapshot } from '@saasicat/types';
|
|
4
|
+
export { DiscoveredCapability, DiscoveredFeature, DiscoveredQuota, DiscoverySnapshot } from '@saasicat/types';
|
|
5
|
+
import { D as DiscoveryAppInfo } from '../discovery.scanner-CUYLKlYT.cjs';
|
|
6
|
+
export { a as DISCOVERY_APP_INFO_TOKEN, b as DISCOVERY_SNAPSHOT_PATH_TOKEN, c as DiscoveryScanner, d as computeSnapshotHash } from '../discovery.scanner-CUYLKlYT.cjs';
|
|
7
|
+
import '@nestjs/core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for `@ImplementsCapability(key, options)`. `key` itself comes as a
|
|
11
|
+
* separate argument; everything else is optional to keep boilerplate low.
|
|
12
|
+
*/
|
|
13
|
+
interface ImplementsCapabilityOptions {
|
|
14
|
+
/** Human-readable label (for the discovery UI). */
|
|
15
|
+
label?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Aggregation wrapper: the feature key this capability belongs to.
|
|
18
|
+
* Bundles are planned exclusively in the SuperAdmin UI and are
|
|
19
|
+
* **deliberately not** part of the code decorator (SPEC_V2 §3.1).
|
|
20
|
+
*/
|
|
21
|
+
feature?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Code status. Default is `active` (visible in discovery, can be
|
|
24
|
+
* referenced in plans). `deprecated` recommends a replacement;
|
|
25
|
+
* `experimental` marks WIP capabilities that the SuperAdmin sees with a
|
|
26
|
+
* warning in the UI; `internal` does not appear in the UI, but does in the
|
|
27
|
+
* snapshot hash.
|
|
28
|
+
*/
|
|
29
|
+
status?: DiscoveryCodeStatus;
|
|
30
|
+
/**
|
|
31
|
+
* Implementation kind. Not detected by the scanner itself — the user
|
|
32
|
+
* declares it explicitly (Endpoint, Service, Job, Event).
|
|
33
|
+
*/
|
|
34
|
+
kind?: CapabilityKind;
|
|
35
|
+
/** Code-owner tag (e.g. 'accounting', 'membership'). Audit-relevant. */
|
|
36
|
+
owner?: string;
|
|
37
|
+
/** Recommended when `status: 'deprecated'`. */
|
|
38
|
+
replacementKey?: string;
|
|
39
|
+
/** Recommended when `status: 'deprecated'`. ISO date. */
|
|
40
|
+
removalPlannedAt?: string;
|
|
41
|
+
/** Free-text reason for deprecation/internal. */
|
|
42
|
+
reason?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Feature keys that this capability's feature requires at runtime (#35)
|
|
45
|
+
* — e.g. `TRAINING_PLANNER` ⟹ `RESOURCE_MANAGEMENT`.
|
|
46
|
+
* The scanner aggregates the union of all capability requires per feature
|
|
47
|
+
* (minus its own featureKey); the strict-mode check and the configurator
|
|
48
|
+
* use it to surface unmet dependencies.
|
|
49
|
+
*/
|
|
50
|
+
requires?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Old feature keys that this capability's feature replaces (#39).
|
|
53
|
+
* Hard path of replacement: the declaration lives on the NEW feature, the
|
|
54
|
+
* old code can be deleted in the same commit. (`replacementKey` remains
|
|
55
|
+
* the soft path on the still-existing, deprecated old code.)
|
|
56
|
+
*/
|
|
57
|
+
replaces?: string[];
|
|
58
|
+
}
|
|
59
|
+
/** Options for `@RequiresCapability(...keys)` — currently no options, just the key list. */
|
|
60
|
+
type RequiresCapabilityKeys = readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* Options for `@DefinesQuota(options)`. `key` is the QuotaKey that
|
|
63
|
+
* `@EnforceQuota(...)` checks against at runtime.
|
|
64
|
+
*/
|
|
65
|
+
interface DefinesQuotaOptions {
|
|
66
|
+
key: string;
|
|
67
|
+
label: string;
|
|
68
|
+
/** Unit for the UI (e.g. 'invoices', 'GB', 'requests'). */
|
|
69
|
+
unit: string;
|
|
70
|
+
policy: DiscoveredQuotaPolicy;
|
|
71
|
+
/** Aggregation wrapper: which feature the quota is bound to. */
|
|
72
|
+
feature?: string;
|
|
73
|
+
/** Old QuotaKeys that this quota replaces (#39, analogous to Capability `replaces`). */
|
|
74
|
+
replaces?: string[];
|
|
75
|
+
}
|
|
76
|
+
/** Options for `@EnforceQuota(key, options)`. */
|
|
77
|
+
interface EnforceQuotaOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Increment step per call (default 1). Negative values allow a decrement
|
|
80
|
+
* (e.g. reversal of an invoice).
|
|
81
|
+
*/
|
|
82
|
+
incrementBy?: number;
|
|
83
|
+
/**
|
|
84
|
+
* When the quota is checked:
|
|
85
|
+
* - `before` — before the handler runs (default; prevents the write)
|
|
86
|
+
* - `after` — after successful execution (for counters that need the
|
|
87
|
+
* result — e.g. actual storage size)
|
|
88
|
+
*/
|
|
89
|
+
timing?: 'before' | 'after';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Internal metadata shape under which `@ImplementsCapability` stores its data.
|
|
94
|
+
* The scanner reads it back out.
|
|
95
|
+
*/
|
|
96
|
+
interface ImplementsCapabilityMetadata extends ImplementsCapabilityOptions {
|
|
97
|
+
capabilityKey: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Marks a method as the implementation of a technical capability.
|
|
101
|
+
*
|
|
102
|
+
* The capability is the smallest checkable unit (e.g. `invoice.create`).
|
|
103
|
+
* Multiple methods may declare the same capability — the
|
|
104
|
+
* Discovery scanner deduplicates by `capabilityKey`.
|
|
105
|
+
*
|
|
106
|
+
* Optional fields:
|
|
107
|
+
* - `feature` aggregates capabilities into feature wrappers that the
|
|
108
|
+
* SuperAdmin can reference in plans.
|
|
109
|
+
* - `status` controls visibility/lifecycle (default `active`).
|
|
110
|
+
* - `kind` declares the implementation kind (default `endpoint`).
|
|
111
|
+
*
|
|
112
|
+
* **Bundles are deliberately NOT declared in the decorator** (SPEC_V2 §3.1).
|
|
113
|
+
* They are created exclusively in the SuperAdmin UI (DB table `bundles`),
|
|
114
|
+
* by the admin grouping features there.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* @Post()
|
|
119
|
+
* @ImplementsCapability('invoice.create', {
|
|
120
|
+
* label: 'Rechnung erstellen',
|
|
121
|
+
* feature: 'INVOICE_MANAGEMENT',
|
|
122
|
+
* kind: 'endpoint',
|
|
123
|
+
* owner: 'accounting',
|
|
124
|
+
* })
|
|
125
|
+
* createInvoice() { … }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare const ImplementsCapability: (capabilityKey: string, options?: ImplementsCapabilityOptions) => _nestjs_common.CustomDecorator<string>;
|
|
129
|
+
/**
|
|
130
|
+
* Runtime guard: the tenant must have **all** specified capabilities.
|
|
131
|
+
* Multiple calls (or multiple keys) are evaluated as a logical AND.
|
|
132
|
+
*
|
|
133
|
+
* In contrast to `@RequireFeature(...)` (defined in
|
|
134
|
+
* `billing/require-feature.decorator.ts`, logical OR), the capability guard
|
|
135
|
+
* checks at the capability level — finer-grained, because a feature has
|
|
136
|
+
* multiple capabilities.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* @Post()
|
|
141
|
+
* @ImplementsCapability('invoice.create', { … })
|
|
142
|
+
* @RequiresCapability('invoice.create')
|
|
143
|
+
* createInvoice() { … }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare const RequiresCapability: (...capabilityKeys: string[]) => _nestjs_common.CustomDecorator<string>;
|
|
147
|
+
/**
|
|
148
|
+
* Marks a class as a QuotaProvider for a QuotaKey. The
|
|
149
|
+
* Discovery scanner aggregates from it the list of all quotas defined in
|
|
150
|
+
* code; the SuperAdmin can reference these as limits in plans/bundles.
|
|
151
|
+
*
|
|
152
|
+
* A class may have multiple `@DefinesQuota` decorators (e.g. a
|
|
153
|
+
* provider that holds counters for multiple QuotaKeys).
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* @Injectable()
|
|
158
|
+
* @DefinesQuota({
|
|
159
|
+
* key: 'invoicesPerMonth',
|
|
160
|
+
* label: 'Rechnungen pro Monat',
|
|
161
|
+
* unit: 'invoices',
|
|
162
|
+
* policy: 'monthlyReset',
|
|
163
|
+
* feature: 'INVOICE_MANAGEMENT',
|
|
164
|
+
* })
|
|
165
|
+
* class InvoiceQuotaProvider implements QuotaProvider { … }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare const DefinesQuota: (options: DefinesQuotaOptions) => _nestjs_common.CustomDecorator<string>;
|
|
169
|
+
/**
|
|
170
|
+
* Internal metadata shape under which `@EnforceQuota` stores its data.
|
|
171
|
+
*/
|
|
172
|
+
interface EnforceQuotaMetadata extends EnforceQuotaOptions {
|
|
173
|
+
quotaKey: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Runtime enforcement: checks + increments a quota counter per call.
|
|
177
|
+
* The corresponding `@DefinesQuota` provider must be registered in at least
|
|
178
|
+
* one class (the strict-mode check verifies this).
|
|
179
|
+
*
|
|
180
|
+
* Default `incrementBy: 1`, `timing: 'before'`. On quota overrun the
|
|
181
|
+
* QuotaGuard throws a `LimitExceededError` (see
|
|
182
|
+
* `entitlement/limit-exceeded-error.ts`).
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* @Post()
|
|
187
|
+
* @ImplementsCapability('invoice.create', { … })
|
|
188
|
+
* @EnforceQuota('invoicesPerMonth', { incrementBy: 1 })
|
|
189
|
+
* createInvoice() { … }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare const EnforceQuota: (quotaKey: string, options?: EnforceQuotaOptions) => _nestjs_common.CustomDecorator<string>;
|
|
193
|
+
|
|
194
|
+
interface DiscoveryControllerConfig {
|
|
195
|
+
/**
|
|
196
|
+
* Class-level guards for `GET /admin/discovery`. MANDATORY — `forRoot()`
|
|
197
|
+
* throws at boot otherwise. Pass `[]` explicitly if the endpoint should
|
|
198
|
+
* deliberately be auth-free (e.g. CI/smoke test).
|
|
199
|
+
*/
|
|
200
|
+
guards: Array<Type<CanActivate>>;
|
|
201
|
+
}
|
|
202
|
+
interface DiscoveryModuleOptions {
|
|
203
|
+
/** App identity that is adopted into the snapshot (`app.key`/`app.version`). */
|
|
204
|
+
app: DiscoveryAppInfo;
|
|
205
|
+
/**
|
|
206
|
+
* Controller mount for `GET /admin/discovery`. If omitted, the endpoint
|
|
207
|
+
* is not registered — consumers can then build it themselves (e.g. with
|
|
208
|
+
* a different path or additional guards) and inject the scanner via
|
|
209
|
+
* `DISCOVERY_SNAPSHOT_TOKEN`.
|
|
210
|
+
*/
|
|
211
|
+
controller?: DiscoveryControllerConfig;
|
|
212
|
+
/**
|
|
213
|
+
* Modules whose providers must be visible in the DI scope of this
|
|
214
|
+
* DynamicModule — typically: `AuthModule`, which exports the `JwtAuthGuard`
|
|
215
|
+
* from `controller.guards`. Without this entry, NestJS fails with an
|
|
216
|
+
* `UnknownDependenciesException` for the guard if the AuthModule is not
|
|
217
|
+
* global.
|
|
218
|
+
*/
|
|
219
|
+
imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
|
|
220
|
+
/**
|
|
221
|
+
* Optional: register the module globally — `DISCOVERY_SNAPSHOT_TOKEN` and
|
|
222
|
+
* `DiscoveryScanner` are then available without another `imports` entry.
|
|
223
|
+
* Default: `true`.
|
|
224
|
+
*/
|
|
225
|
+
isGlobal?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* Optional: path into which the DiscoveryScanner writes the snapshot as
|
|
228
|
+
* JSON on `OnApplicationBootstrap`. Consumers (CI gates, preflight CLIs)
|
|
229
|
+
* can read the file with `loadDiscoverySnapshotFromFile(path)` without
|
|
230
|
+
* booting all app modules themselves. If omitted, nothing is written.
|
|
231
|
+
*/
|
|
232
|
+
snapshotPath?: string | null;
|
|
233
|
+
}
|
|
234
|
+
declare class DiscoveryModule {
|
|
235
|
+
static forRoot(options: DiscoveryModuleOptions): DynamicModule;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
declare class DiscoverySnapshotNotFoundError extends Error {
|
|
239
|
+
readonly path: string;
|
|
240
|
+
constructor(path: string);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Reads a snapshot JSON file persisted by the DiscoveryScanner and returns
|
|
244
|
+
* it as a `DiscoverySnapshot`. Throws `DiscoverySnapshotNotFoundError`
|
|
245
|
+
* when the file is missing — that is a hard boot condition for CI gates
|
|
246
|
+
* (a Preflight without a current snapshot would be worthless).
|
|
247
|
+
*/
|
|
248
|
+
declare function loadDiscoverySnapshotFromFile(path: string): DiscoverySnapshot;
|
|
249
|
+
|
|
250
|
+
interface HeadlessScanOptions {
|
|
251
|
+
/** Log level of the boot context. Default `['error', 'warn', 'log']`. */
|
|
252
|
+
logger?: LogLevel[] | false;
|
|
253
|
+
/**
|
|
254
|
+
* Exit function on errors — default `process.exit(4)` (analogous to
|
|
255
|
+
* Seed-Gate/Preflight). Injectable in tests.
|
|
256
|
+
*/
|
|
257
|
+
exit?: (code: number) => never;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Runs the headless scan and returns the snapshot. On boot/scan
|
|
261
|
+
* errors the error is logged and the process exits with code 4 —
|
|
262
|
+
* the staged rollout (non-fatal) is controlled by the consumer's
|
|
263
|
+
* migrate path (`… || echo WARN`), not by this runner.
|
|
264
|
+
*/
|
|
265
|
+
declare function runHeadlessDiscoveryScan(appModule: Type, options?: HeadlessScanOptions): Promise<DiscoverySnapshot>;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Builds a controller class at boot time with the guards configured by the
|
|
269
|
+
* consumer. Called by DiscoveryModule.forRoot() when the `controller`
|
|
270
|
+
* option is set.
|
|
271
|
+
*/
|
|
272
|
+
declare function buildDiscoveryController(guards: Array<Type<CanActivate>>): Type;
|
|
273
|
+
|
|
274
|
+
/** Method level: marks a capability implementation. */
|
|
275
|
+
declare const IMPLEMENTS_CAPABILITY_KEY = "discovery:implements-capability";
|
|
276
|
+
/** Method/class level: runtime guard, tenant must have the capability. */
|
|
277
|
+
declare const REQUIRES_CAPABILITY_KEY = "discovery:requires-capability";
|
|
278
|
+
/** Class level: marks a class as a QuotaProvider for a QuotaKey. */
|
|
279
|
+
declare const DEFINES_QUOTA_KEY = "discovery:defines-quota";
|
|
280
|
+
/** Method level: runtime enforcement (increment/check of a quota counter). */
|
|
281
|
+
declare const ENFORCE_QUOTA_KEY = "discovery:enforce-quota";
|
|
282
|
+
/**
|
|
283
|
+
* Provider token for the discovery snapshot — provided by the DiscoveryModule
|
|
284
|
+
* and consumed via `@Inject(DISCOVERY_SNAPSHOT_TOKEN)`
|
|
285
|
+
* (e.g. AdminController `/admin/discovery`, CatalogModule auto-sync).
|
|
286
|
+
*
|
|
287
|
+
* `Symbol.for` (NOT `Symbol`) is mandatory here: this package is built with tsup/
|
|
288
|
+
* esbuild, which cannot code-split CJS — each entry point
|
|
289
|
+
* (`./discovery`, `./catalog`, …) therefore inlines its OWN copy of this
|
|
290
|
+
* file. A plain `Symbol()` would be a different symbol per copy; a consumer
|
|
291
|
+
* that imports DiscoveryModule from `./discovery` and CatalogModule from `./catalog`
|
|
292
|
+
* would get two different tokens → the DI match
|
|
293
|
+
* fails → the snapshot does not reach the CatalogModule (prod incident
|
|
294
|
+
* 2026-06-09). `Symbol.for` uses the process-wide registry → identical across
|
|
295
|
+
* all bundle copies. (#25)
|
|
296
|
+
*/
|
|
297
|
+
declare const DISCOVERY_SNAPSHOT_TOKEN: unique symbol;
|
|
298
|
+
|
|
299
|
+
export { DEFINES_QUOTA_KEY, DISCOVERY_SNAPSHOT_TOKEN, DefinesQuota, type DefinesQuotaOptions, DiscoveryAppInfo, type DiscoveryControllerConfig, DiscoveryModule, type DiscoveryModuleOptions, DiscoverySnapshotNotFoundError, ENFORCE_QUOTA_KEY, EnforceQuota, type EnforceQuotaMetadata, type EnforceQuotaOptions, type HeadlessScanOptions, IMPLEMENTS_CAPABILITY_KEY, ImplementsCapability, type ImplementsCapabilityMetadata, type ImplementsCapabilityOptions, REQUIRES_CAPABILITY_KEY, RequiresCapability, type RequiresCapabilityKeys, buildDiscoveryController, loadDiscoverySnapshotFromFile, runHeadlessDiscoveryScan };
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import * as _nestjs_common from '@nestjs/common';
|
|
2
|
+
import { Type, CanActivate, DynamicModule, ForwardReference, LogLevel } from '@nestjs/common';
|
|
3
|
+
import { DiscoveredQuotaPolicy, DiscoveryCodeStatus, CapabilityKind, DiscoverySnapshot } from '@saasicat/types';
|
|
4
|
+
export { DiscoveredCapability, DiscoveredFeature, DiscoveredQuota, DiscoverySnapshot } from '@saasicat/types';
|
|
5
|
+
import { D as DiscoveryAppInfo } from '../discovery.scanner-CUYLKlYT.js';
|
|
6
|
+
export { a as DISCOVERY_APP_INFO_TOKEN, b as DISCOVERY_SNAPSHOT_PATH_TOKEN, c as DiscoveryScanner, d as computeSnapshotHash } from '../discovery.scanner-CUYLKlYT.js';
|
|
7
|
+
import '@nestjs/core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Options for `@ImplementsCapability(key, options)`. `key` itself comes as a
|
|
11
|
+
* separate argument; everything else is optional to keep boilerplate low.
|
|
12
|
+
*/
|
|
13
|
+
interface ImplementsCapabilityOptions {
|
|
14
|
+
/** Human-readable label (for the discovery UI). */
|
|
15
|
+
label?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Aggregation wrapper: the feature key this capability belongs to.
|
|
18
|
+
* Bundles are planned exclusively in the SuperAdmin UI and are
|
|
19
|
+
* **deliberately not** part of the code decorator (SPEC_V2 §3.1).
|
|
20
|
+
*/
|
|
21
|
+
feature?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Code status. Default is `active` (visible in discovery, can be
|
|
24
|
+
* referenced in plans). `deprecated` recommends a replacement;
|
|
25
|
+
* `experimental` marks WIP capabilities that the SuperAdmin sees with a
|
|
26
|
+
* warning in the UI; `internal` does not appear in the UI, but does in the
|
|
27
|
+
* snapshot hash.
|
|
28
|
+
*/
|
|
29
|
+
status?: DiscoveryCodeStatus;
|
|
30
|
+
/**
|
|
31
|
+
* Implementation kind. Not detected by the scanner itself — the user
|
|
32
|
+
* declares it explicitly (Endpoint, Service, Job, Event).
|
|
33
|
+
*/
|
|
34
|
+
kind?: CapabilityKind;
|
|
35
|
+
/** Code-owner tag (e.g. 'accounting', 'membership'). Audit-relevant. */
|
|
36
|
+
owner?: string;
|
|
37
|
+
/** Recommended when `status: 'deprecated'`. */
|
|
38
|
+
replacementKey?: string;
|
|
39
|
+
/** Recommended when `status: 'deprecated'`. ISO date. */
|
|
40
|
+
removalPlannedAt?: string;
|
|
41
|
+
/** Free-text reason for deprecation/internal. */
|
|
42
|
+
reason?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Feature keys that this capability's feature requires at runtime (#35)
|
|
45
|
+
* — e.g. `TRAINING_PLANNER` ⟹ `RESOURCE_MANAGEMENT`.
|
|
46
|
+
* The scanner aggregates the union of all capability requires per feature
|
|
47
|
+
* (minus its own featureKey); the strict-mode check and the configurator
|
|
48
|
+
* use it to surface unmet dependencies.
|
|
49
|
+
*/
|
|
50
|
+
requires?: string[];
|
|
51
|
+
/**
|
|
52
|
+
* Old feature keys that this capability's feature replaces (#39).
|
|
53
|
+
* Hard path of replacement: the declaration lives on the NEW feature, the
|
|
54
|
+
* old code can be deleted in the same commit. (`replacementKey` remains
|
|
55
|
+
* the soft path on the still-existing, deprecated old code.)
|
|
56
|
+
*/
|
|
57
|
+
replaces?: string[];
|
|
58
|
+
}
|
|
59
|
+
/** Options for `@RequiresCapability(...keys)` — currently no options, just the key list. */
|
|
60
|
+
type RequiresCapabilityKeys = readonly string[];
|
|
61
|
+
/**
|
|
62
|
+
* Options for `@DefinesQuota(options)`. `key` is the QuotaKey that
|
|
63
|
+
* `@EnforceQuota(...)` checks against at runtime.
|
|
64
|
+
*/
|
|
65
|
+
interface DefinesQuotaOptions {
|
|
66
|
+
key: string;
|
|
67
|
+
label: string;
|
|
68
|
+
/** Unit for the UI (e.g. 'invoices', 'GB', 'requests'). */
|
|
69
|
+
unit: string;
|
|
70
|
+
policy: DiscoveredQuotaPolicy;
|
|
71
|
+
/** Aggregation wrapper: which feature the quota is bound to. */
|
|
72
|
+
feature?: string;
|
|
73
|
+
/** Old QuotaKeys that this quota replaces (#39, analogous to Capability `replaces`). */
|
|
74
|
+
replaces?: string[];
|
|
75
|
+
}
|
|
76
|
+
/** Options for `@EnforceQuota(key, options)`. */
|
|
77
|
+
interface EnforceQuotaOptions {
|
|
78
|
+
/**
|
|
79
|
+
* Increment step per call (default 1). Negative values allow a decrement
|
|
80
|
+
* (e.g. reversal of an invoice).
|
|
81
|
+
*/
|
|
82
|
+
incrementBy?: number;
|
|
83
|
+
/**
|
|
84
|
+
* When the quota is checked:
|
|
85
|
+
* - `before` — before the handler runs (default; prevents the write)
|
|
86
|
+
* - `after` — after successful execution (for counters that need the
|
|
87
|
+
* result — e.g. actual storage size)
|
|
88
|
+
*/
|
|
89
|
+
timing?: 'before' | 'after';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Internal metadata shape under which `@ImplementsCapability` stores its data.
|
|
94
|
+
* The scanner reads it back out.
|
|
95
|
+
*/
|
|
96
|
+
interface ImplementsCapabilityMetadata extends ImplementsCapabilityOptions {
|
|
97
|
+
capabilityKey: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Marks a method as the implementation of a technical capability.
|
|
101
|
+
*
|
|
102
|
+
* The capability is the smallest checkable unit (e.g. `invoice.create`).
|
|
103
|
+
* Multiple methods may declare the same capability — the
|
|
104
|
+
* Discovery scanner deduplicates by `capabilityKey`.
|
|
105
|
+
*
|
|
106
|
+
* Optional fields:
|
|
107
|
+
* - `feature` aggregates capabilities into feature wrappers that the
|
|
108
|
+
* SuperAdmin can reference in plans.
|
|
109
|
+
* - `status` controls visibility/lifecycle (default `active`).
|
|
110
|
+
* - `kind` declares the implementation kind (default `endpoint`).
|
|
111
|
+
*
|
|
112
|
+
* **Bundles are deliberately NOT declared in the decorator** (SPEC_V2 §3.1).
|
|
113
|
+
* They are created exclusively in the SuperAdmin UI (DB table `bundles`),
|
|
114
|
+
* by the admin grouping features there.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* @Post()
|
|
119
|
+
* @ImplementsCapability('invoice.create', {
|
|
120
|
+
* label: 'Rechnung erstellen',
|
|
121
|
+
* feature: 'INVOICE_MANAGEMENT',
|
|
122
|
+
* kind: 'endpoint',
|
|
123
|
+
* owner: 'accounting',
|
|
124
|
+
* })
|
|
125
|
+
* createInvoice() { … }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare const ImplementsCapability: (capabilityKey: string, options?: ImplementsCapabilityOptions) => _nestjs_common.CustomDecorator<string>;
|
|
129
|
+
/**
|
|
130
|
+
* Runtime guard: the tenant must have **all** specified capabilities.
|
|
131
|
+
* Multiple calls (or multiple keys) are evaluated as a logical AND.
|
|
132
|
+
*
|
|
133
|
+
* In contrast to `@RequireFeature(...)` (defined in
|
|
134
|
+
* `billing/require-feature.decorator.ts`, logical OR), the capability guard
|
|
135
|
+
* checks at the capability level — finer-grained, because a feature has
|
|
136
|
+
* multiple capabilities.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* @Post()
|
|
141
|
+
* @ImplementsCapability('invoice.create', { … })
|
|
142
|
+
* @RequiresCapability('invoice.create')
|
|
143
|
+
* createInvoice() { … }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare const RequiresCapability: (...capabilityKeys: string[]) => _nestjs_common.CustomDecorator<string>;
|
|
147
|
+
/**
|
|
148
|
+
* Marks a class as a QuotaProvider for a QuotaKey. The
|
|
149
|
+
* Discovery scanner aggregates from it the list of all quotas defined in
|
|
150
|
+
* code; the SuperAdmin can reference these as limits in plans/bundles.
|
|
151
|
+
*
|
|
152
|
+
* A class may have multiple `@DefinesQuota` decorators (e.g. a
|
|
153
|
+
* provider that holds counters for multiple QuotaKeys).
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* @Injectable()
|
|
158
|
+
* @DefinesQuota({
|
|
159
|
+
* key: 'invoicesPerMonth',
|
|
160
|
+
* label: 'Rechnungen pro Monat',
|
|
161
|
+
* unit: 'invoices',
|
|
162
|
+
* policy: 'monthlyReset',
|
|
163
|
+
* feature: 'INVOICE_MANAGEMENT',
|
|
164
|
+
* })
|
|
165
|
+
* class InvoiceQuotaProvider implements QuotaProvider { … }
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
declare const DefinesQuota: (options: DefinesQuotaOptions) => _nestjs_common.CustomDecorator<string>;
|
|
169
|
+
/**
|
|
170
|
+
* Internal metadata shape under which `@EnforceQuota` stores its data.
|
|
171
|
+
*/
|
|
172
|
+
interface EnforceQuotaMetadata extends EnforceQuotaOptions {
|
|
173
|
+
quotaKey: string;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Runtime enforcement: checks + increments a quota counter per call.
|
|
177
|
+
* The corresponding `@DefinesQuota` provider must be registered in at least
|
|
178
|
+
* one class (the strict-mode check verifies this).
|
|
179
|
+
*
|
|
180
|
+
* Default `incrementBy: 1`, `timing: 'before'`. On quota overrun the
|
|
181
|
+
* QuotaGuard throws a `LimitExceededError` (see
|
|
182
|
+
* `entitlement/limit-exceeded-error.ts`).
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* @Post()
|
|
187
|
+
* @ImplementsCapability('invoice.create', { … })
|
|
188
|
+
* @EnforceQuota('invoicesPerMonth', { incrementBy: 1 })
|
|
189
|
+
* createInvoice() { … }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare const EnforceQuota: (quotaKey: string, options?: EnforceQuotaOptions) => _nestjs_common.CustomDecorator<string>;
|
|
193
|
+
|
|
194
|
+
interface DiscoveryControllerConfig {
|
|
195
|
+
/**
|
|
196
|
+
* Class-level guards for `GET /admin/discovery`. MANDATORY — `forRoot()`
|
|
197
|
+
* throws at boot otherwise. Pass `[]` explicitly if the endpoint should
|
|
198
|
+
* deliberately be auth-free (e.g. CI/smoke test).
|
|
199
|
+
*/
|
|
200
|
+
guards: Array<Type<CanActivate>>;
|
|
201
|
+
}
|
|
202
|
+
interface DiscoveryModuleOptions {
|
|
203
|
+
/** App identity that is adopted into the snapshot (`app.key`/`app.version`). */
|
|
204
|
+
app: DiscoveryAppInfo;
|
|
205
|
+
/**
|
|
206
|
+
* Controller mount for `GET /admin/discovery`. If omitted, the endpoint
|
|
207
|
+
* is not registered — consumers can then build it themselves (e.g. with
|
|
208
|
+
* a different path or additional guards) and inject the scanner via
|
|
209
|
+
* `DISCOVERY_SNAPSHOT_TOKEN`.
|
|
210
|
+
*/
|
|
211
|
+
controller?: DiscoveryControllerConfig;
|
|
212
|
+
/**
|
|
213
|
+
* Modules whose providers must be visible in the DI scope of this
|
|
214
|
+
* DynamicModule — typically: `AuthModule`, which exports the `JwtAuthGuard`
|
|
215
|
+
* from `controller.guards`. Without this entry, NestJS fails with an
|
|
216
|
+
* `UnknownDependenciesException` for the guard if the AuthModule is not
|
|
217
|
+
* global.
|
|
218
|
+
*/
|
|
219
|
+
imports?: Array<Type<unknown> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
|
|
220
|
+
/**
|
|
221
|
+
* Optional: register the module globally — `DISCOVERY_SNAPSHOT_TOKEN` and
|
|
222
|
+
* `DiscoveryScanner` are then available without another `imports` entry.
|
|
223
|
+
* Default: `true`.
|
|
224
|
+
*/
|
|
225
|
+
isGlobal?: boolean;
|
|
226
|
+
/**
|
|
227
|
+
* Optional: path into which the DiscoveryScanner writes the snapshot as
|
|
228
|
+
* JSON on `OnApplicationBootstrap`. Consumers (CI gates, preflight CLIs)
|
|
229
|
+
* can read the file with `loadDiscoverySnapshotFromFile(path)` without
|
|
230
|
+
* booting all app modules themselves. If omitted, nothing is written.
|
|
231
|
+
*/
|
|
232
|
+
snapshotPath?: string | null;
|
|
233
|
+
}
|
|
234
|
+
declare class DiscoveryModule {
|
|
235
|
+
static forRoot(options: DiscoveryModuleOptions): DynamicModule;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
declare class DiscoverySnapshotNotFoundError extends Error {
|
|
239
|
+
readonly path: string;
|
|
240
|
+
constructor(path: string);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Reads a snapshot JSON file persisted by the DiscoveryScanner and returns
|
|
244
|
+
* it as a `DiscoverySnapshot`. Throws `DiscoverySnapshotNotFoundError`
|
|
245
|
+
* when the file is missing — that is a hard boot condition for CI gates
|
|
246
|
+
* (a Preflight without a current snapshot would be worthless).
|
|
247
|
+
*/
|
|
248
|
+
declare function loadDiscoverySnapshotFromFile(path: string): DiscoverySnapshot;
|
|
249
|
+
|
|
250
|
+
interface HeadlessScanOptions {
|
|
251
|
+
/** Log level of the boot context. Default `['error', 'warn', 'log']`. */
|
|
252
|
+
logger?: LogLevel[] | false;
|
|
253
|
+
/**
|
|
254
|
+
* Exit function on errors — default `process.exit(4)` (analogous to
|
|
255
|
+
* Seed-Gate/Preflight). Injectable in tests.
|
|
256
|
+
*/
|
|
257
|
+
exit?: (code: number) => never;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Runs the headless scan and returns the snapshot. On boot/scan
|
|
261
|
+
* errors the error is logged and the process exits with code 4 —
|
|
262
|
+
* the staged rollout (non-fatal) is controlled by the consumer's
|
|
263
|
+
* migrate path (`… || echo WARN`), not by this runner.
|
|
264
|
+
*/
|
|
265
|
+
declare function runHeadlessDiscoveryScan(appModule: Type, options?: HeadlessScanOptions): Promise<DiscoverySnapshot>;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Builds a controller class at boot time with the guards configured by the
|
|
269
|
+
* consumer. Called by DiscoveryModule.forRoot() when the `controller`
|
|
270
|
+
* option is set.
|
|
271
|
+
*/
|
|
272
|
+
declare function buildDiscoveryController(guards: Array<Type<CanActivate>>): Type;
|
|
273
|
+
|
|
274
|
+
/** Method level: marks a capability implementation. */
|
|
275
|
+
declare const IMPLEMENTS_CAPABILITY_KEY = "discovery:implements-capability";
|
|
276
|
+
/** Method/class level: runtime guard, tenant must have the capability. */
|
|
277
|
+
declare const REQUIRES_CAPABILITY_KEY = "discovery:requires-capability";
|
|
278
|
+
/** Class level: marks a class as a QuotaProvider for a QuotaKey. */
|
|
279
|
+
declare const DEFINES_QUOTA_KEY = "discovery:defines-quota";
|
|
280
|
+
/** Method level: runtime enforcement (increment/check of a quota counter). */
|
|
281
|
+
declare const ENFORCE_QUOTA_KEY = "discovery:enforce-quota";
|
|
282
|
+
/**
|
|
283
|
+
* Provider token for the discovery snapshot — provided by the DiscoveryModule
|
|
284
|
+
* and consumed via `@Inject(DISCOVERY_SNAPSHOT_TOKEN)`
|
|
285
|
+
* (e.g. AdminController `/admin/discovery`, CatalogModule auto-sync).
|
|
286
|
+
*
|
|
287
|
+
* `Symbol.for` (NOT `Symbol`) is mandatory here: this package is built with tsup/
|
|
288
|
+
* esbuild, which cannot code-split CJS — each entry point
|
|
289
|
+
* (`./discovery`, `./catalog`, …) therefore inlines its OWN copy of this
|
|
290
|
+
* file. A plain `Symbol()` would be a different symbol per copy; a consumer
|
|
291
|
+
* that imports DiscoveryModule from `./discovery` and CatalogModule from `./catalog`
|
|
292
|
+
* would get two different tokens → the DI match
|
|
293
|
+
* fails → the snapshot does not reach the CatalogModule (prod incident
|
|
294
|
+
* 2026-06-09). `Symbol.for` uses the process-wide registry → identical across
|
|
295
|
+
* all bundle copies. (#25)
|
|
296
|
+
*/
|
|
297
|
+
declare const DISCOVERY_SNAPSHOT_TOKEN: unique symbol;
|
|
298
|
+
|
|
299
|
+
export { DEFINES_QUOTA_KEY, DISCOVERY_SNAPSHOT_TOKEN, DefinesQuota, type DefinesQuotaOptions, DiscoveryAppInfo, type DiscoveryControllerConfig, DiscoveryModule, type DiscoveryModuleOptions, DiscoverySnapshotNotFoundError, ENFORCE_QUOTA_KEY, EnforceQuota, type EnforceQuotaMetadata, type EnforceQuotaOptions, type HeadlessScanOptions, IMPLEMENTS_CAPABILITY_KEY, ImplementsCapability, type ImplementsCapabilityMetadata, type ImplementsCapabilityOptions, REQUIRES_CAPABILITY_KEY, RequiresCapability, type RequiresCapabilityKeys, buildDiscoveryController, loadDiscoverySnapshotFromFile, runHeadlessDiscoveryScan };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DefinesQuota,
|
|
3
|
+
EnforceQuota,
|
|
4
|
+
ImplementsCapability,
|
|
5
|
+
RequiresCapability,
|
|
6
|
+
runHeadlessDiscoveryScan
|
|
7
|
+
} from "../chunk-HRU3CP57.js";
|
|
8
|
+
import {
|
|
9
|
+
DiscoverySnapshotNotFoundError,
|
|
10
|
+
loadDiscoverySnapshotFromFile
|
|
11
|
+
} from "../chunk-ABB2CRAS.js";
|
|
12
|
+
import {
|
|
13
|
+
DiscoveryModule,
|
|
14
|
+
buildDiscoveryController
|
|
15
|
+
} from "../chunk-AJ3EPELS.js";
|
|
16
|
+
import {
|
|
17
|
+
DISCOVERY_APP_INFO_TOKEN,
|
|
18
|
+
DISCOVERY_SNAPSHOT_PATH_TOKEN,
|
|
19
|
+
DiscoveryScanner,
|
|
20
|
+
computeSnapshotHash
|
|
21
|
+
} from "../chunk-M6CZQYPY.js";
|
|
22
|
+
import {
|
|
23
|
+
DEFINES_QUOTA_KEY,
|
|
24
|
+
DISCOVERY_SNAPSHOT_TOKEN,
|
|
25
|
+
ENFORCE_QUOTA_KEY,
|
|
26
|
+
IMPLEMENTS_CAPABILITY_KEY,
|
|
27
|
+
REQUIRES_CAPABILITY_KEY
|
|
28
|
+
} from "../chunk-L4SUWH5B.js";
|
|
29
|
+
import "../chunk-SHUYVCID.js";
|
|
30
|
+
export {
|
|
31
|
+
DEFINES_QUOTA_KEY,
|
|
32
|
+
DISCOVERY_APP_INFO_TOKEN,
|
|
33
|
+
DISCOVERY_SNAPSHOT_PATH_TOKEN,
|
|
34
|
+
DISCOVERY_SNAPSHOT_TOKEN,
|
|
35
|
+
DefinesQuota,
|
|
36
|
+
DiscoveryModule,
|
|
37
|
+
DiscoveryScanner,
|
|
38
|
+
DiscoverySnapshotNotFoundError,
|
|
39
|
+
ENFORCE_QUOTA_KEY,
|
|
40
|
+
EnforceQuota,
|
|
41
|
+
IMPLEMENTS_CAPABILITY_KEY,
|
|
42
|
+
ImplementsCapability,
|
|
43
|
+
REQUIRES_CAPABILITY_KEY,
|
|
44
|
+
RequiresCapability,
|
|
45
|
+
buildDiscoveryController,
|
|
46
|
+
computeSnapshotHash,
|
|
47
|
+
loadDiscoverySnapshotFromFile,
|
|
48
|
+
runHeadlessDiscoveryScan
|
|
49
|
+
};
|