stratal 0.0.18 → 0.0.19
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/README.md +8 -8
- package/dist/{base-email.provider-Cuw4OAB0.mjs → base-email.provider-mjynzewK.mjs} +1 -1
- package/dist/{base-email.provider-Cuw4OAB0.mjs.map → base-email.provider-mjynzewK.mjs.map} +1 -1
- package/dist/bin/cloudflare-workers-loader.mjs.map +1 -1
- package/dist/bin/quarry.mjs +21 -36
- package/dist/bin/quarry.mjs.map +1 -1
- package/dist/cache/index.d.mts +3 -2
- package/dist/cache/index.d.mts.map +1 -1
- package/dist/cache/index.mjs +3 -3
- package/dist/{colors-BTAnQRGU.mjs → colors-DJaRDXoS.mjs} +1 -1
- package/dist/{colors-BTAnQRGU.mjs.map → colors-DJaRDXoS.mjs.map} +1 -1
- package/dist/{command-DjGqCYHv.mjs → command-BgSlsS4M.mjs} +2 -2
- package/dist/{command-DjGqCYHv.mjs.map → command-BgSlsS4M.mjs.map} +1 -1
- package/dist/{command-B1YuV-UZ.d.mts → command-DsQq56Lp.d.mts} +2 -2
- package/dist/{command-B1YuV-UZ.d.mts.map → command-DsQq56Lp.d.mts.map} +1 -1
- package/dist/config/index.d.mts +81 -37
- package/dist/config/index.d.mts.map +1 -1
- package/dist/config/index.mjs +126 -45
- package/dist/config/index.mjs.map +1 -1
- package/dist/{consumer-registry-BkuHXR_u.d.mts → consumer-registry-Doom7BEh.d.mts} +1 -1
- package/dist/{consumer-registry-BkuHXR_u.d.mts.map → consumer-registry-Doom7BEh.d.mts.map} +1 -1
- package/dist/controller.decorator-LZY9aHYG.mjs +66 -0
- package/dist/controller.decorator-LZY9aHYG.mjs.map +1 -0
- package/dist/cron/index.d.mts +4 -3
- package/dist/cron/index.d.mts.map +1 -1
- package/dist/cron/index.mjs +1 -1
- package/dist/{cron-manager-1KnZvojs.mjs → cron-manager-C30t9UZM.mjs} +29 -19
- package/dist/cron-manager-C30t9UZM.mjs.map +1 -0
- package/dist/{cron-manager-BnEZquBL.d.mts → cron-manager-RuPtFVLy.d.mts} +27 -13
- package/dist/cron-manager-RuPtFVLy.d.mts.map +1 -0
- package/dist/di/index.d.mts +1 -1
- package/dist/di/index.mjs +2 -2
- package/dist/email/index.d.mts +3 -3
- package/dist/email/index.mjs +87 -10
- package/dist/email/index.mjs.map +1 -1
- package/dist/{en-3QnZwP-u.mjs → en-rHmW6vD9.mjs} +5 -31
- package/dist/en-rHmW6vD9.mjs.map +1 -0
- package/dist/env-CamWD-U1.d.mts +25 -0
- package/dist/env-CamWD-U1.d.mts.map +1 -0
- package/dist/errors/index.d.mts +1 -1
- package/dist/errors/index.mjs +1 -1
- package/dist/{errors--RBIvDXr.mjs → errors-B4pYgYON.mjs} +161 -7
- package/dist/errors-B4pYgYON.mjs.map +1 -0
- package/dist/{errors-B7hCnXgB.mjs → errors-BUyUfr2Z.mjs} +14 -7
- package/dist/errors-BUyUfr2Z.mjs.map +1 -0
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +1 -1
- package/dist/{events-UTJliZhl.mjs → events-COKixqnG.mjs} +2 -2
- package/dist/{events-UTJliZhl.mjs.map → events-COKixqnG.mjs.map} +1 -1
- package/dist/{gateway-context-BdBFoQd8.mjs → gateway-context-cqZ8wMoi.mjs} +4 -65
- package/dist/gateway-context-cqZ8wMoi.mjs.map +1 -0
- package/dist/guards/index.d.mts +14 -5
- package/dist/guards/index.d.mts.map +1 -1
- package/dist/guards/index.mjs +1 -1
- package/dist/{guards-MtDgcHnF.mjs → guards-DMbsAxSX.mjs} +1 -1
- package/dist/guards-DMbsAxSX.mjs.map +1 -0
- package/dist/http-method.decorator-BT3ufnz8.mjs +96 -0
- package/dist/http-method.decorator-BT3ufnz8.mjs.map +1 -0
- package/dist/i18n/index.d.mts +3 -3
- package/dist/i18n/index.mjs +2 -2
- package/dist/i18n/messages/en/index.d.mts +1 -1
- package/dist/i18n/messages/en/index.mjs +1 -1
- package/dist/i18n/utils/index.mjs +1 -1
- package/dist/i18n/validation/index.d.mts +1 -1
- package/dist/i18n/validation/index.mjs +1 -1
- package/dist/{i18n.module-BpLLLCTg.mjs → i18n.module-CI_prYFD.mjs} +74 -196
- package/dist/i18n.module-CI_prYFD.mjs.map +1 -0
- package/dist/{index-Dfpd_ypO.d.mts → index-B437eK7p.d.mts} +26 -12
- package/dist/index-B437eK7p.d.mts.map +1 -0
- package/dist/{index-BR23zDMy.d.mts → index-CWRS7Ri3.d.mts} +1 -1
- package/dist/{index-BR23zDMy.d.mts.map → index-CWRS7Ri3.d.mts.map} +1 -1
- package/dist/{index-BDh9J2KD.d.mts → index-DFhEeFfC.d.mts} +4 -30
- package/dist/{index-BDh9J2KD.d.mts.map → index-DFhEeFfC.d.mts.map} +1 -1
- package/dist/{index-BrmS34sa.d.mts → index-DPFqRs8L.d.mts} +70 -39
- package/dist/index-DPFqRs8L.d.mts.map +1 -0
- package/dist/{index-DPxmo6AY.d.mts → index-Dnqm9ZB6.d.mts} +5 -4
- package/dist/index-Dnqm9ZB6.d.mts.map +1 -0
- package/dist/index-SHx31sBJ.d.mts +101 -0
- package/dist/index-SHx31sBJ.d.mts.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{is-command-PvULqiTa.mjs → is-command-C6a7WTPw.mjs} +2 -2
- package/dist/{is-command-PvULqiTa.mjs.map → is-command-C6a7WTPw.mjs.map} +1 -1
- package/dist/{is-seeder-BN9Ej1r7.mjs → is-seeder-CebjZCDn.mjs} +1 -1
- package/dist/{is-seeder-BN9Ej1r7.mjs.map → is-seeder-CebjZCDn.mjs.map} +1 -1
- package/dist/logger/index.d.mts +1 -1
- package/dist/logger/index.mjs +1 -1
- package/dist/{logger-c0ftIK4G.mjs → logger-V6Ms3QnQ.mjs} +38 -20
- package/dist/{logger-c0ftIK4G.mjs.map → logger-V6Ms3QnQ.mjs.map} +1 -1
- package/dist/macroable/index.d.mts +2 -0
- package/dist/macroable/index.mjs +2 -0
- package/dist/macroable-BmufBshB.mjs +122 -0
- package/dist/macroable-BmufBshB.mjs.map +1 -0
- package/dist/module/index.d.mts +2 -2
- package/dist/module/index.mjs +1 -1
- package/dist/{module-C3YZ-kZN.mjs → module-qGE_1duv.mjs} +31 -18
- package/dist/module-qGE_1duv.mjs.map +1 -0
- package/dist/openapi/index.d.mts +3 -3
- package/dist/openapi/index.mjs +2 -2
- package/dist/{openapi-tools.service-B77QXD56.mjs → openapi-tools.service-CYWGuhue.mjs} +4 -1
- package/dist/{openapi-tools.service-B77QXD56.mjs.map → openapi-tools.service-CYWGuhue.mjs.map} +1 -1
- package/dist/{openapi.service-6yj0BUY4.d.mts → openapi.service-Bv_NioM9.d.mts} +3 -3
- package/dist/{openapi.service-6yj0BUY4.d.mts.map → openapi.service-Bv_NioM9.d.mts.map} +1 -1
- package/dist/quarry/index.d.mts +7 -7
- package/dist/quarry/index.d.mts.map +1 -1
- package/dist/quarry/index.mjs +4 -4
- package/dist/{quarry-registry-CQCIlYTO.mjs → quarry-registry-DFfRRkA7.mjs} +17 -15
- package/dist/quarry-registry-DFfRRkA7.mjs.map +1 -0
- package/dist/queue/index.d.mts +2 -2
- package/dist/queue/index.mjs +2 -2
- package/dist/{queue.module-DIjD6nr-.mjs → queue.module-P-G-nCYz.mjs} +4 -4
- package/dist/{queue.module-DIjD6nr-.mjs.map → queue.module-P-G-nCYz.mjs.map} +1 -1
- package/dist/r2-storage.provider-LdzK9tfG.mjs +244 -0
- package/dist/r2-storage.provider-LdzK9tfG.mjs.map +1 -0
- package/dist/{resend.provider-Bvw36rQy.mjs → resend.provider-bwILp0WI.mjs} +2 -2
- package/dist/{resend.provider-Bvw36rQy.mjs.map → resend.provider-bwILp0WI.mjs.map} +1 -1
- package/dist/router/index.d.mts +2 -2
- package/dist/router/index.mjs +7 -5
- package/dist/seeder/index.d.mts +3 -3
- package/dist/seeder/index.mjs +2 -2
- package/dist/{seeder-D7VXULXB.mjs → seeder-BcqIFa2X.mjs} +5 -5
- package/dist/{seeder-D7VXULXB.mjs.map → seeder-BcqIFa2X.mjs.map} +1 -1
- package/dist/{setup-BRIN-iYT.mjs → setup-CtekcwuO.mjs} +1 -1
- package/dist/{setup-BRIN-iYT.mjs.map → setup-CtekcwuO.mjs.map} +1 -1
- package/dist/signed-url-COX7cCWR.mjs +74 -0
- package/dist/signed-url-COX7cCWR.mjs.map +1 -0
- package/dist/{smtp.provider-CAwpvzvD.mjs → smtp.provider-B07yuARi.mjs} +2 -2
- package/dist/{smtp.provider-CAwpvzvD.mjs.map → smtp.provider-B07yuARi.mjs.map} +1 -1
- package/dist/storage/index.d.mts +39 -17
- package/dist/storage/index.d.mts.map +1 -1
- package/dist/storage/index.mjs +3 -3
- package/dist/storage/providers/index.d.mts +30 -70
- package/dist/storage/providers/index.d.mts.map +1 -1
- package/dist/storage/providers/index.mjs +2 -2
- package/dist/{storage-CJ-QOwNv.mjs → storage-P6X4h9So.mjs} +101 -27
- package/dist/storage-P6X4h9So.mjs.map +1 -0
- package/dist/{storage-provider.interface-YRtyYBxV.d.mts → storage-provider.interface-CC1nniHk.d.mts} +20 -21
- package/dist/storage-provider.interface-CC1nniHk.d.mts.map +1 -0
- package/dist/{stratal-B7G4i9-N.mjs → stratal-BCiwCFN9.mjs} +57 -26
- package/dist/stratal-BCiwCFN9.mjs.map +1 -0
- package/dist/{types-CN0zONAZ.d.mts → types-DIWemRad.d.mts} +1 -1
- package/dist/types-DIWemRad.d.mts.map +1 -0
- package/dist/{usage-generator-Cl1HPlUp.mjs → usage-generator-MBcRo0Q2.mjs} +2 -2
- package/dist/{usage-generator-Cl1HPlUp.mjs.map → usage-generator-MBcRo0Q2.mjs.map} +1 -1
- package/dist/{validation-B4bePOa_.mjs → validation-Dbg3ehdP.mjs} +1 -1
- package/dist/{validation-B4bePOa_.mjs.map → validation-Dbg3ehdP.mjs.map} +1 -1
- package/dist/websocket/index.d.mts +3 -3
- package/dist/websocket/index.mjs +1 -1
- package/dist/workers/index.d.mts +2 -1
- package/dist/workers/index.d.mts.map +1 -1
- package/dist/workers/index.mjs +2 -2
- package/package.json +27 -39
- package/dist/cron-manager-1KnZvojs.mjs.map +0 -1
- package/dist/cron-manager-BnEZquBL.d.mts.map +0 -1
- package/dist/en-3QnZwP-u.mjs.map +0 -1
- package/dist/errors--RBIvDXr.mjs.map +0 -1
- package/dist/errors-B7hCnXgB.mjs.map +0 -1
- package/dist/gateway-context-BdBFoQd8.mjs.map +0 -1
- package/dist/guards-MtDgcHnF.mjs.map +0 -1
- package/dist/i18n.module-BpLLLCTg.mjs.map +0 -1
- package/dist/index-BrmS34sa.d.mts.map +0 -1
- package/dist/index-DPxmo6AY.d.mts.map +0 -1
- package/dist/index-Dfpd_ypO.d.mts.map +0 -1
- package/dist/module-C3YZ-kZN.mjs.map +0 -1
- package/dist/quarry-registry-CQCIlYTO.mjs.map +0 -1
- package/dist/s3-storage.provider-BAhHDMI3.mjs +0 -343
- package/dist/s3-storage.provider-BAhHDMI3.mjs.map +0 -1
- package/dist/storage-CJ-QOwNv.mjs.map +0 -1
- package/dist/storage-provider.interface-YRtyYBxV.d.mts.map +0 -1
- package/dist/stratal-B7G4i9-N.mjs.map +0 -1
- package/dist/types-CN0zONAZ.d.mts.map +0 -1
package/dist/config/index.d.mts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Cn as OnInitialize, _n as FactoryProvider, bn as ModuleContext, hn as DynamicModule } from "../index-DPFqRs8L.mjs";
|
|
2
|
+
import { t as Macroable } from "../index-SHx31sBJ.mjs";
|
|
3
|
+
import { o as z } from "../index-B437eK7p.mjs";
|
|
3
4
|
import { InjectionToken } from "tsyringe";
|
|
4
5
|
|
|
5
6
|
//#region src/config/config.tokens.d.ts
|
|
6
7
|
declare const CONFIG_TOKENS: {
|
|
7
8
|
readonly ConfigService: symbol;
|
|
9
|
+
readonly ConfigStore: symbol;
|
|
8
10
|
};
|
|
9
11
|
//#endregion
|
|
10
12
|
//#region src/config/register-as.d.ts
|
|
@@ -177,14 +179,13 @@ type ConfigPath<T> = { [K in keyof T & string]: T[K] extends Record<string, unkn
|
|
|
177
179
|
*/
|
|
178
180
|
type ConfigPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? T[K] extends Record<string, unknown> ? ConfigPathValue<T[K], Rest> : never : never : P extends keyof T ? T[P] : never;
|
|
179
181
|
/**
|
|
180
|
-
* ConfigService interface with dot notation support
|
|
182
|
+
* ConfigService interface with dot notation support.
|
|
183
|
+
*
|
|
184
|
+
* Values are initialized on the underlying {@link ConfigStore} via
|
|
185
|
+
* {@link ConfigModule}; the service itself exposes request-scoped
|
|
186
|
+
* reads and per-request overrides.
|
|
181
187
|
*/
|
|
182
188
|
interface IConfigService<T extends object = ModuleConfig> {
|
|
183
|
-
/**
|
|
184
|
-
* Initialize the config service with validated configuration
|
|
185
|
-
* Should be called once during application startup
|
|
186
|
-
*/
|
|
187
|
-
initialize(config: T): void;
|
|
188
189
|
/**
|
|
189
190
|
* Get config value using dot notation
|
|
190
191
|
* @example config.get('database.url')
|
|
@@ -210,64 +211,107 @@ interface IConfigService<T extends object = ModuleConfig> {
|
|
|
210
211
|
has(path: ConfigPath<T>): boolean;
|
|
211
212
|
}
|
|
212
213
|
//#endregion
|
|
214
|
+
//#region src/config/services/config.store.d.ts
|
|
215
|
+
/**
|
|
216
|
+
* ConfigStore
|
|
217
|
+
*
|
|
218
|
+
* Singleton-scoped holder of validated, merged configuration.
|
|
219
|
+
*
|
|
220
|
+
* ConfigStore is the source of truth for configuration values. It is
|
|
221
|
+
* initialized once during application startup by {@link ConfigModule}
|
|
222
|
+
* and never mutated afterwards.
|
|
223
|
+
*
|
|
224
|
+
* Per-request overrides live on {@link ConfigService}, which reads
|
|
225
|
+
* through to this store for any key not explicitly overridden.
|
|
226
|
+
*/
|
|
227
|
+
declare class ConfigStore<T extends object = ModuleConfig> {
|
|
228
|
+
private data;
|
|
229
|
+
/**
|
|
230
|
+
* Initialize the store with validated configuration.
|
|
231
|
+
* Called by {@link ConfigModule} during initialization.
|
|
232
|
+
*/
|
|
233
|
+
initialize(config: T): void;
|
|
234
|
+
/**
|
|
235
|
+
* Get config value using dot notation.
|
|
236
|
+
*/
|
|
237
|
+
get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>;
|
|
238
|
+
/**
|
|
239
|
+
* Check if a config path exists.
|
|
240
|
+
*/
|
|
241
|
+
has(path: ConfigPath<T>): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Get the entire config object (readonly snapshot).
|
|
244
|
+
*/
|
|
245
|
+
all(): Readonly<T>;
|
|
246
|
+
/**
|
|
247
|
+
* True once {@link initialize} has been called.
|
|
248
|
+
*/
|
|
249
|
+
isInitialized(): boolean;
|
|
250
|
+
private getByPath;
|
|
251
|
+
private isDangerousKey;
|
|
252
|
+
private ensureInitialized;
|
|
253
|
+
private deepClone;
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
213
256
|
//#region src/config/services/config.service.d.ts
|
|
214
257
|
/**
|
|
215
|
-
* ConfigService with dot notation support
|
|
258
|
+
* ConfigService with dot notation support and per-request overrides.
|
|
259
|
+
*
|
|
260
|
+
* ConfigService is **request-scoped**: each request gets its own
|
|
261
|
+
* instance with a private `overrides` map layered over the shared
|
|
262
|
+
* {@link ConfigStore}. Calls to {@link set} mutate only the current
|
|
263
|
+
* request's overrides, which makes it safe to mutate config from
|
|
264
|
+
* middleware (e.g. to pin `environment.appUrl` to the request host).
|
|
216
265
|
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
266
|
+
* Extends {@link Macroable} so apps can add domain-specific getters
|
|
267
|
+
* and methods via `ConfigService.getter()` / `ConfigService.macro()`.
|
|
219
268
|
*
|
|
220
269
|
* @example
|
|
221
270
|
* ```typescript
|
|
222
|
-
* //
|
|
271
|
+
* // Read with dot notation
|
|
223
272
|
* const url = config.get('database.url')
|
|
224
273
|
* const fromName = config.get('email.from.name')
|
|
225
274
|
*
|
|
226
|
-
* //
|
|
227
|
-
* config.set('
|
|
275
|
+
* // Per-request override (e.g. in middleware)
|
|
276
|
+
* config.set('environment.appUrl', `${proto}://${host}`)
|
|
228
277
|
*
|
|
229
|
-
* // Reset
|
|
230
|
-
* config.reset('
|
|
231
|
-
* config.reset() // reset entire config
|
|
278
|
+
* // Reset the override for the current request
|
|
279
|
+
* config.reset('environment.appUrl')
|
|
232
280
|
* ```
|
|
233
281
|
*/
|
|
234
|
-
declare class ConfigService<T extends object = ModuleConfig> implements IConfigService<T> {
|
|
235
|
-
private
|
|
236
|
-
private
|
|
282
|
+
declare class ConfigService<T extends object = ModuleConfig> extends Macroable implements IConfigService<T> {
|
|
283
|
+
private readonly store;
|
|
284
|
+
private overrides;
|
|
285
|
+
constructor(store: ConfigStore<T>);
|
|
237
286
|
/**
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*/
|
|
241
|
-
initialize(config: T): void;
|
|
242
|
-
/**
|
|
243
|
-
* Get config value using dot notation
|
|
244
|
-
* @example config.get('database.url')
|
|
287
|
+
* Get config value using dot notation. Request overrides take
|
|
288
|
+
* precedence over the shared store.
|
|
245
289
|
*/
|
|
246
290
|
get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>;
|
|
247
291
|
/**
|
|
248
|
-
* Set config value
|
|
249
|
-
*
|
|
292
|
+
* Set a config value for the lifetime of the current request.
|
|
293
|
+
* Does not mutate the shared store.
|
|
250
294
|
*/
|
|
251
295
|
set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void;
|
|
252
296
|
/**
|
|
253
|
-
*
|
|
254
|
-
* @param path - Optional path to reset (resets entire config if omitted)
|
|
297
|
+
* Clear a single override, or all overrides for this request.
|
|
255
298
|
*/
|
|
256
299
|
reset(path?: ConfigPath<T>): void;
|
|
257
300
|
/**
|
|
258
|
-
* Get
|
|
301
|
+
* Get the full config object, with request overrides merged in.
|
|
259
302
|
*/
|
|
260
303
|
all(): Readonly<T>;
|
|
261
304
|
/**
|
|
262
|
-
* Check if a config path exists
|
|
305
|
+
* Check if a config path exists (in overrides or the store).
|
|
263
306
|
*/
|
|
264
307
|
has(path: ConfigPath<T>): boolean;
|
|
265
|
-
private
|
|
266
|
-
private
|
|
308
|
+
private readOverride;
|
|
309
|
+
private walk;
|
|
310
|
+
private writeByPath;
|
|
311
|
+
private hasDangerousSegment;
|
|
267
312
|
private isDangerousKey;
|
|
268
|
-
private ensureInitialized;
|
|
269
313
|
private deepClone;
|
|
270
314
|
}
|
|
271
315
|
//#endregion
|
|
272
|
-
export { CONFIG_TOKENS, ConfigModule, type ConfigModuleOptions, type ConfigNamespace, type ConfigPath, type ConfigPathValue, ConfigService, ConfigValidationError, type IConfigService, type InferConfigType, type ModuleConfig, registerAs };
|
|
316
|
+
export { CONFIG_TOKENS, ConfigModule, type ConfigModuleOptions, type ConfigNamespace, type ConfigPath, type ConfigPathValue, ConfigService, ConfigStore, ConfigValidationError, type IConfigService, type InferConfigType, type ModuleConfig, registerAs };
|
|
273
317
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/config/config.tokens.ts","../../src/config/register-as.ts","../../src/config/config.module.ts","../../src/config/config.types.ts","../../src/config/services/config.service.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/config/config.tokens.ts","../../src/config/register-as.ts","../../src/config/config.module.ts","../../src/config/config.types.ts","../../src/config/services/config.store.ts","../../src/config/services/config.service.ts"],"mappings":"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"}
|
package/dist/config/index.mjs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { A as Scope, H as ApplicationError, k as ERROR_CODES } from "../errors
|
|
2
|
-
import { a as __decorate, f as DI_TOKENS, p as Transient } from "../logger-
|
|
3
|
-
import {
|
|
1
|
+
import { A as Scope, H as ApplicationError, k as ERROR_CODES } from "../errors-B4pYgYON.mjs";
|
|
2
|
+
import { a as __decorate, f as DI_TOKENS, o as __decorateParam, p as Transient, s as __decorateMetadata } from "../logger-V6Ms3QnQ.mjs";
|
|
3
|
+
import { t as Macroable } from "../macroable-BmufBshB.mjs";
|
|
4
|
+
import { C as Module } from "../module-qGE_1duv.mjs";
|
|
5
|
+
import { inject } from "tsyringe";
|
|
4
6
|
//#region src/config/config.tokens.ts
|
|
5
|
-
const CONFIG_TOKENS = {
|
|
7
|
+
const CONFIG_TOKENS = {
|
|
8
|
+
ConfigService: Symbol.for("stratal:config:service"),
|
|
9
|
+
ConfigStore: Symbol.for("stratal:config:store")
|
|
10
|
+
};
|
|
6
11
|
//#endregion
|
|
7
12
|
//#region src/config/config.types.ts
|
|
8
13
|
var ConfigValidationError = class extends Error {
|
|
@@ -40,69 +45,77 @@ var ConfigNotInitializedError = class extends ApplicationError {
|
|
|
40
45
|
};
|
|
41
46
|
//#endregion
|
|
42
47
|
//#region src/config/services/config.service.ts
|
|
43
|
-
let ConfigService = class ConfigService {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* Called by ConfigModule during initialization
|
|
49
|
-
*/
|
|
50
|
-
initialize(config) {
|
|
51
|
-
this.originalConfig = this.deepClone(config);
|
|
52
|
-
this.currentConfig = this.deepClone(config);
|
|
48
|
+
let ConfigService = class ConfigService extends Macroable {
|
|
49
|
+
overrides = /* @__PURE__ */ new Map();
|
|
50
|
+
constructor(store) {
|
|
51
|
+
super();
|
|
52
|
+
this.store = store;
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
* Get config value using dot notation
|
|
56
|
-
*
|
|
55
|
+
* Get config value using dot notation. Request overrides take
|
|
56
|
+
* precedence over the shared store.
|
|
57
57
|
*/
|
|
58
58
|
get(path) {
|
|
59
|
-
this.
|
|
60
|
-
|
|
59
|
+
const override = this.readOverride(path);
|
|
60
|
+
if (override !== void 0) return override;
|
|
61
|
+
return this.store.get(path);
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
|
-
* Set config value
|
|
64
|
-
*
|
|
64
|
+
* Set a config value for the lifetime of the current request.
|
|
65
|
+
* Does not mutate the shared store.
|
|
65
66
|
*/
|
|
66
67
|
set(path, value) {
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
68
|
+
if (this.hasDangerousSegment(path)) return;
|
|
69
|
+
this.overrides.set(path, value);
|
|
69
70
|
}
|
|
70
71
|
/**
|
|
71
|
-
*
|
|
72
|
-
* @param path - Optional path to reset (resets entire config if omitted)
|
|
72
|
+
* Clear a single override, or all overrides for this request.
|
|
73
73
|
*/
|
|
74
74
|
reset(path) {
|
|
75
|
-
this.ensureInitialized();
|
|
76
75
|
if (path) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
76
|
+
this.overrides.delete(path);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.overrides.clear();
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
-
* Get
|
|
82
|
+
* Get the full config object, with request overrides merged in.
|
|
83
83
|
*/
|
|
84
84
|
all() {
|
|
85
|
-
this.
|
|
86
|
-
|
|
85
|
+
const base = this.store.all();
|
|
86
|
+
if (this.overrides.size === 0) return base;
|
|
87
|
+
const merged = this.deepClone(base);
|
|
88
|
+
for (const [path, value] of this.overrides) this.writeByPath(merged, path, value);
|
|
89
|
+
return merged;
|
|
87
90
|
}
|
|
88
91
|
/**
|
|
89
|
-
* Check if a config path exists
|
|
92
|
+
* Check if a config path exists (in overrides or the store).
|
|
90
93
|
*/
|
|
91
94
|
has(path) {
|
|
92
|
-
this.
|
|
93
|
-
return this.
|
|
95
|
+
if (this.readOverride(path) !== void 0) return true;
|
|
96
|
+
return this.store.has(path);
|
|
94
97
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
readOverride(path) {
|
|
99
|
+
if (this.hasDangerousSegment(path)) return void 0;
|
|
100
|
+
if (this.overrides.has(path)) return this.overrides.get(path);
|
|
101
|
+
const segments = path.split(".");
|
|
102
|
+
for (let i = segments.length - 1; i > 0; i--) {
|
|
103
|
+
const parent = segments.slice(0, i).join(".");
|
|
104
|
+
if (this.overrides.has(parent)) {
|
|
105
|
+
const parentValue = this.overrides.get(parent);
|
|
106
|
+
return this.walk(parentValue, segments.slice(i));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
walk(value, keys) {
|
|
111
|
+
let current = value;
|
|
98
112
|
for (const key of keys) {
|
|
99
|
-
if (this.isDangerousKey(key)) return void 0;
|
|
100
113
|
if (current === null || current === void 0) return void 0;
|
|
101
114
|
current = current[key];
|
|
102
115
|
}
|
|
103
116
|
return current;
|
|
104
117
|
}
|
|
105
|
-
|
|
118
|
+
writeByPath(obj, path, value) {
|
|
106
119
|
const keys = path.split(".");
|
|
107
120
|
if (keys.some((key) => this.isDangerousKey(key))) return;
|
|
108
121
|
let current = obj;
|
|
@@ -123,18 +136,82 @@ let ConfigService = class ConfigService {
|
|
|
123
136
|
configurable: true
|
|
124
137
|
});
|
|
125
138
|
}
|
|
139
|
+
hasDangerousSegment(path) {
|
|
140
|
+
return path.split(".").some((key) => this.isDangerousKey(key));
|
|
141
|
+
}
|
|
142
|
+
isDangerousKey(key) {
|
|
143
|
+
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
144
|
+
}
|
|
145
|
+
deepClone(obj) {
|
|
146
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
147
|
+
return JSON.parse(JSON.stringify(obj));
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
ConfigService = __decorate([
|
|
151
|
+
Transient(CONFIG_TOKENS.ConfigService),
|
|
152
|
+
__decorateParam(0, inject(CONFIG_TOKENS.ConfigStore)),
|
|
153
|
+
__decorateMetadata("design:paramtypes", [Object])
|
|
154
|
+
], ConfigService);
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/config/services/config.store.ts
|
|
157
|
+
let ConfigStore = class ConfigStore {
|
|
158
|
+
data;
|
|
159
|
+
/**
|
|
160
|
+
* Initialize the store with validated configuration.
|
|
161
|
+
* Called by {@link ConfigModule} during initialization.
|
|
162
|
+
*/
|
|
163
|
+
initialize(config) {
|
|
164
|
+
this.data = this.deepClone(config);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get config value using dot notation.
|
|
168
|
+
*/
|
|
169
|
+
get(path) {
|
|
170
|
+
this.ensureInitialized();
|
|
171
|
+
return this.getByPath(this.data, path);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if a config path exists.
|
|
175
|
+
*/
|
|
176
|
+
has(path) {
|
|
177
|
+
this.ensureInitialized();
|
|
178
|
+
return this.getByPath(this.data, path) !== void 0;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get the entire config object (readonly snapshot).
|
|
182
|
+
*/
|
|
183
|
+
all() {
|
|
184
|
+
this.ensureInitialized();
|
|
185
|
+
return this.data;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* True once {@link initialize} has been called.
|
|
189
|
+
*/
|
|
190
|
+
isInitialized() {
|
|
191
|
+
return this.data !== void 0;
|
|
192
|
+
}
|
|
193
|
+
getByPath(obj, path) {
|
|
194
|
+
const keys = path.split(".");
|
|
195
|
+
let current = obj;
|
|
196
|
+
for (const key of keys) {
|
|
197
|
+
if (this.isDangerousKey(key)) return void 0;
|
|
198
|
+
if (current === null || current === void 0) return void 0;
|
|
199
|
+
current = current[key];
|
|
200
|
+
}
|
|
201
|
+
return current;
|
|
202
|
+
}
|
|
126
203
|
isDangerousKey(key) {
|
|
127
204
|
return key === "__proto__" || key === "constructor" || key === "prototype";
|
|
128
205
|
}
|
|
129
206
|
ensureInitialized() {
|
|
130
|
-
if (
|
|
207
|
+
if (this.data === void 0) throw new ConfigNotInitializedError();
|
|
131
208
|
}
|
|
132
209
|
deepClone(obj) {
|
|
133
210
|
if (obj === null || typeof obj !== "object") return obj;
|
|
134
211
|
return JSON.parse(JSON.stringify(obj));
|
|
135
212
|
}
|
|
136
213
|
};
|
|
137
|
-
|
|
214
|
+
ConfigStore = __decorate([Transient(CONFIG_TOKENS.ConfigStore)], ConfigStore);
|
|
138
215
|
//#endregion
|
|
139
216
|
//#region src/config/config.module.ts
|
|
140
217
|
var _ConfigModule;
|
|
@@ -162,21 +239,25 @@ let ConfigModule = _ConfigModule = class ConfigModule {
|
|
|
162
239
|
onInitialize(context) {
|
|
163
240
|
if (!moduleOptions) throw new ConfigModuleNotInitializedError();
|
|
164
241
|
const env = context.container.resolve(DI_TOKENS.CloudflareEnv);
|
|
165
|
-
const
|
|
242
|
+
const configStore = context.container.resolve(CONFIG_TOKENS.ConfigStore);
|
|
166
243
|
const mergedConfig = {};
|
|
167
244
|
for (const namespace of moduleOptions.load) mergedConfig[namespace.namespace] = namespace.factory(env);
|
|
168
245
|
if (moduleOptions.validateSchema) {
|
|
169
246
|
const result = moduleOptions.validateSchema.safeParse(mergedConfig);
|
|
170
247
|
if (!result.success) throw new ConfigValidationError("Configuration validation failed", result.error);
|
|
171
248
|
}
|
|
172
|
-
|
|
249
|
+
configStore.initialize(mergedConfig);
|
|
173
250
|
context.logger.debug("ConfigModule initialized", { namespaces: moduleOptions.load.map((n) => n.namespace) });
|
|
174
251
|
}
|
|
175
252
|
};
|
|
176
253
|
ConfigModule = _ConfigModule = __decorate([Module({ providers: [{
|
|
254
|
+
provide: CONFIG_TOKENS.ConfigStore,
|
|
255
|
+
useClass: ConfigStore,
|
|
256
|
+
scope: Scope.Singleton
|
|
257
|
+
}, {
|
|
177
258
|
provide: CONFIG_TOKENS.ConfigService,
|
|
178
259
|
useClass: ConfigService,
|
|
179
|
-
scope: Scope.
|
|
260
|
+
scope: Scope.Request
|
|
180
261
|
}] })], ConfigModule);
|
|
181
262
|
//#endregion
|
|
182
263
|
//#region src/config/register-as.ts
|
|
@@ -227,6 +308,6 @@ function registerAs(namespace, factory) {
|
|
|
227
308
|
};
|
|
228
309
|
}
|
|
229
310
|
//#endregion
|
|
230
|
-
export { CONFIG_TOKENS, ConfigModule, ConfigService, ConfigValidationError, registerAs };
|
|
311
|
+
export { CONFIG_TOKENS, ConfigModule, ConfigService, ConfigStore, ConfigValidationError, registerAs };
|
|
231
312
|
|
|
232
313
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/config/config.tokens.ts","../../src/config/config.types.ts","../../src/config/errors/config-module-not-initialized.error.ts","../../src/config/errors/config-not-initialized.error.ts","../../src/config/services/config.service.ts","../../src/config/config.module.ts","../../src/config/register-as.ts"],"sourcesContent":["export const CONFIG_TOKENS = {\n\tConfigService: Symbol.for('stratal:config:service'),\n} as const\n","import type { z } from '../i18n/validation'\n\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly errors: z.ZodError\n ) {\n super(message)\n this.name = 'ConfigValidationError'\n }\n}\n\n/**\n * Configuration that can be augmented by applications\n * Apps should augment this interface with their AppConfig type using module augmentation\n *\n * @example\n * ```typescript\n * // In your app (e.g., apps/backend/src/config/types.ts)\n * declare module 'stratal' {\n * interface ModuleConfig {\n * database: { url: string; maxConnections: number }\n * email: { provider: string; from: { name: string; email: string } }\n * }\n * }\n * ```\n */\nexport interface ModuleConfig { }\n\n/**\n * Generate all valid dot-notation paths from a config object type\n * @example ConfigPath<{ database: { url: string } }> = 'database' | 'database.url'\n */\nexport type ConfigPath<T> = {\n [K in keyof T & string]: T[K] extends Record<string, unknown>\n ? K | `${K}.${ConfigPath<T[K]>}`\n : K\n}[keyof T & string]\n\n/**\n * Get the value type at a dot-notation path\n * @example ConfigPathValue<{ database: { url: string } }, 'database.url'> = string\n */\nexport type ConfigPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? T[K] extends Record<string, unknown>\n ? ConfigPathValue<T[K], Rest>\n : never\n : never\n : P extends keyof T\n ? T[P]\n : never\n\n/**\n * ConfigService interface with dot notation support\n */\nexport interface IConfigService<T extends object = ModuleConfig> {\n /**\n * Initialize the config service with validated configuration\n * Should be called once during application startup\n */\n initialize(config: T): void\n\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void\n\n /**\n * Get entire config object\n */\n all(): Readonly<T>\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean\n}\n","import { ApplicationError, ERROR_CODES } from '../../errors'\n\n/**\n * Error thrown when ConfigModule's onInitialize runs but forRoot() was never called.\n *\n * This means the module was imported without calling ConfigModule.forRoot({ load: [...] }).\n */\nexport class ConfigModuleNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configModuleNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_MODULE_NOT_INITIALIZED\n )\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\n/**\n * ConfigNotInitializedError\n *\n * Thrown when attempting to access ConfigService before it has been initialized.\n * This typically indicates that ConfigModule's onInitialize hook hasn't run yet,\n * or the module wasn't registered properly.\n */\nexport class ConfigNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_NOT_INITIALIZED\n )\n }\n}\n","import { Transient } from '../../di/decorators'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, IConfigService, ModuleConfig } from '../config.types'\nimport { ConfigNotInitializedError } from '../errors'\n\n/**\n * ConfigService with dot notation support for get/set operations\n *\n * Supports runtime overrides via set() - useful for request-specific config overrides.\n * Use reset() to restore original values.\n *\n * @example\n * ```typescript\n * // Get with dot notation\n * const url = config.get('database.url')\n * const fromName = config.get('email.from.name')\n *\n * // Set at runtime (e.g., in middleware for runtime override)\n * config.set('email.from.name', 'Custom Name')\n *\n * // Reset to original\n * config.reset('email.from.name') // reset specific path\n * config.reset() // reset entire config\n * ```\n */\n@Transient(CONFIG_TOKENS.ConfigService)\nexport class ConfigService<T extends object = ModuleConfig> implements IConfigService<T> {\n private originalConfig: T | undefined\n private currentConfig: T | undefined\n\n /**\n * Initialize the config service with validated configuration\n * Called by ConfigModule during initialization\n */\n initialize(config: T): void {\n this.originalConfig = this.deepClone(config)\n this.currentConfig = this.deepClone(config)\n }\n\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n this.ensureInitialized()\n return this.getByPath(this.currentConfig, path) as ConfigPathValue<T, P>\n }\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void {\n this.ensureInitialized()\n this.setByPath(this.currentConfig, path, value)\n }\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void {\n this.ensureInitialized()\n if (path) {\n const originalValue = this.getByPath(this.originalConfig, path)\n this.setByPath(this.currentConfig, path, this.deepClone(originalValue))\n } else {\n this.currentConfig = this.deepClone(this.originalConfig)\n }\n }\n\n /**\n * Get entire config object\n */\n all(): Readonly<T> {\n this.ensureInitialized()\n return this.currentConfig as Readonly<T>\n }\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean {\n this.ensureInitialized()\n return this.getByPath(this.currentConfig, path) !== undefined\n }\n\n private getByPath(obj: unknown, path: string): unknown {\n const keys = path.split('.')\n let current = obj\n for (const key of keys) {\n if (this.isDangerousKey(key)) return undefined\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private setByPath(obj: unknown, path: string, value: unknown): void {\n const keys = path.split('.')\n if (keys.some((key) => this.isDangerousKey(key))) return\n let current = obj as Record<string, unknown>\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]\n if (!Object.hasOwn(current, key) || typeof current[key] !== 'object' || current[key] === null) {\n Object.defineProperty(current, key, {\n value: {},\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n current = current[key] as Record<string, unknown>\n }\n Object.defineProperty(current, keys[keys.length - 1], {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private ensureInitialized(): void {\n if (!this.currentConfig) {\n throw new ConfigNotInitializedError()\n }\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import { DI_TOKENS } from '../di/tokens'\nimport { Scope } from '../di/types'\nimport type { z } from '../i18n/validation'\nimport { Module } from '../module'\nimport type { DynamicModule, ModuleContext, OnInitialize, Provider } from '../module/types'\nimport { CONFIG_TOKENS } from './config.tokens'\nimport { ConfigValidationError, type ModuleConfig } from './config.types'\nimport { ConfigModuleNotInitializedError } from './errors'\nimport type { ConfigNamespace } from './register-as'\nimport { ConfigService } from './services/config.service'\n\n/**\n * Any config namespace - uses structural typing for flexibility\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyConfigNamespace = ConfigNamespace<string, any, object>\n\n/**\n * Options for ConfigModule.forRoot\n */\nexport interface ConfigModuleOptions {\n /**\n * Array of config namespaces created via registerAs()\n */\n load: AnyConfigNamespace[]\n\n /**\n * Optional Zod schema for validating the merged config\n * Validates the entire config object after all namespaces are merged\n */\n validateSchema?: z.ZodType<object>\n}\n\n// Store options for use in onInitialize\nlet moduleOptions: ConfigModuleOptions | null = null\n\n/**\n * ConfigModule\n *\n * Provides configuration management with namespace support.\n * Uses registerAs() to create typed config namespaces that can be injected.\n *\n * @example\n * ```typescript\n * // Define config namespaces\n * const databaseConfig = registerAs('database', (env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: 10\n * }))\n *\n * const emailConfig = registerAs('email', (env) => ({\n * provider: env.EMAIL_PROVIDER,\n * from: { name: 'App', email: 'noreply@example.com' }\n * }))\n *\n * // Register in module\n * @Module({\n * imports: [\n * ConfigModule.forRoot({\n * load: [databaseConfig, emailConfig],\n * validateSchema: AppConfigSchema\n * })\n * ]\n * })\n * export class AppModule {}\n *\n * // Inject config\n * constructor(\n * @inject(CONFIG_TOKENS.ConfigService) private config: IConfigService,\n * @inject(databaseConfig.KEY) private dbConfig: DatabaseConfig\n * ) {\n * // Use dot notation\n * const url = this.config.get('database.url')\n *\n * // Or inject namespace directly\n * const url = this.dbConfig.url\n * }\n * ```\n */\n@Module({\n providers: [\n // Register the main ConfigService as Singleton so initialization persists\n {\n provide: CONFIG_TOKENS.ConfigService,\n useClass: ConfigService,\n scope: Scope.Singleton,\n },\n ],\n})\nexport class ConfigModule implements OnInitialize {\n /**\n * Configure ConfigModule with namespace loaders\n *\n * @param options - Configuration options\n * @returns Dynamic module with config infrastructure\n */\n static forRoot(options: ConfigModuleOptions): DynamicModule {\n moduleOptions = options\n\n const providers: Provider[] = []\n\n // Register each namespace config using asProvider()\n for (const namespace of options.load) {\n providers.push(namespace.asProvider())\n }\n\n return {\n module: ConfigModule,\n providers,\n }\n }\n\n /**\n * Initialize config service with merged namespaces\n * Called after all providers are registered\n */\n onInitialize(context: ModuleContext): void {\n if (!moduleOptions) {\n throw new ConfigModuleNotInitializedError()\n }\n\n const env = context.container.resolve<unknown>(DI_TOKENS.CloudflareEnv)\n const configService = context.container.resolve<ConfigService>(CONFIG_TOKENS.ConfigService)\n\n // Build merged config from all namespaces\n const mergedConfig: Record<string, unknown> = {}\n\n for (const namespace of moduleOptions.load) {\n mergedConfig[namespace.namespace] = namespace.factory(env)\n }\n\n // Validate if schema provided\n if (moduleOptions.validateSchema) {\n const result = moduleOptions.validateSchema.safeParse(mergedConfig)\n if (!result.success) {\n throw new ConfigValidationError(\n 'Configuration validation failed',\n result.error\n )\n }\n }\n\n // Initialize ConfigService with merged config\n configService.initialize(mergedConfig as ModuleConfig)\n\n context.logger.debug('ConfigModule initialized', {\n namespaces: moduleOptions.load.map((n) => n.namespace),\n })\n }\n}\n","import type { InjectionToken } from 'tsyringe'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { FactoryProvider } from '../module/types'\n\n/**\n * Configuration namespace registration result\n */\nexport interface ConfigNamespace<TKey extends string, TEnv, TConfig extends object> {\n /** Auto-derived injection token (e.g., 'database' -> Symbol('stratal:config:database')) */\n readonly KEY: InjectionToken<TConfig>\n /** The namespace key */\n readonly namespace: TKey\n /** The factory function that receives env and returns config */\n readonly factory: (env: TEnv) => TConfig\n /**\n * Returns a provider configuration for use in module registration\n * Automatically injects DI_TOKENS.CloudflareEnv\n */\n asProvider(): FactoryProvider<TConfig>\n}\n\n/**\n * Create a namespaced configuration factory\n * Similar to NestJS registerAs\n *\n * @param namespace - Configuration namespace (e.g., 'database', 'email')\n * @param factory - Factory function receiving env and returning config object\n * @returns ConfigNamespace with token, factory, and asProvider() method\n *\n * @example\n * ```typescript\n * // apps/backend/src/config/database.config.ts\n * export const databaseConfig = registerAs('database', (env: Env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: parseInt(env.DATABASE_MAX_CONNECTIONS || '10'),\n * }))\n *\n * // Auto-generates: databaseConfig.KEY = Symbol('stratal:config:database')\n *\n * // Usage in module:\n * // Option 1: Manual provider\n * {\n * provide: databaseConfig.KEY,\n * useFactory: databaseConfig.factory,\n * inject: [DI_TOKENS.CloudflareEnv]\n * }\n *\n * // Option 2: asProvider() helper\n * databaseConfig.asProvider()\n * // Returns: { provide: databaseConfig.KEY, useFactory: ..., inject: [DI_TOKENS.CloudflareEnv] }\n * ```\n */\nexport function registerAs<TKey extends string, TEnv, TConfig extends object>(\n namespace: TKey,\n factory: (env: TEnv) => TConfig\n): ConfigNamespace<TKey, TEnv, TConfig> {\n const KEY = Symbol.for(`stratal:config:${namespace}`) as InjectionToken<TConfig>\n\n return {\n KEY,\n namespace,\n factory,\n asProvider(): FactoryProvider<TConfig> {\n return {\n provide: KEY,\n useFactory: factory,\n inject: [DI_TOKENS.CloudflareEnv],\n }\n },\n }\n}\n\n/**\n * Helper to derive config type from registerAs result\n *\n * @example\n * ```typescript\n * const databaseConfig = registerAs('database', (env) => ({ url: env.DATABASE_URL }))\n * type DatabaseConfig = InferConfigType<typeof databaseConfig>\n * // { url: string }\n * ```\n */\nexport type InferConfigType<T> = T extends ConfigNamespace<string, unknown, infer C> ? C : never\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/config/config.tokens.ts","../../src/config/config.types.ts","../../src/config/errors/config-module-not-initialized.error.ts","../../src/config/errors/config-not-initialized.error.ts","../../src/config/services/config.service.ts","../../src/config/services/config.store.ts","../../src/config/config.module.ts","../../src/config/register-as.ts"],"sourcesContent":["export const CONFIG_TOKENS = {\n\tConfigService: Symbol.for('stratal:config:service'),\n\tConfigStore: Symbol.for('stratal:config:store'),\n} as const\n","import type { z } from '../i18n/validation'\n\nexport class ConfigValidationError extends Error {\n constructor(\n message: string,\n public readonly errors: z.ZodError\n ) {\n super(message)\n this.name = 'ConfigValidationError'\n }\n}\n\n/**\n * Configuration that can be augmented by applications\n * Apps should augment this interface with their AppConfig type using module augmentation\n *\n * @example\n * ```typescript\n * // In your app (e.g., apps/backend/src/config/types.ts)\n * declare module 'stratal' {\n * interface ModuleConfig {\n * database: { url: string; maxConnections: number }\n * email: { provider: string; from: { name: string; email: string } }\n * }\n * }\n * ```\n */\nexport interface ModuleConfig { }\n\n/**\n * Generate all valid dot-notation paths from a config object type\n * @example ConfigPath<{ database: { url: string } }> = 'database' | 'database.url'\n */\nexport type ConfigPath<T> = {\n [K in keyof T & string]: T[K] extends Record<string, unknown>\n ? K | `${K}.${ConfigPath<T[K]>}`\n : K\n}[keyof T & string]\n\n/**\n * Get the value type at a dot-notation path\n * @example ConfigPathValue<{ database: { url: string } }, 'database.url'> = string\n */\nexport type ConfigPathValue<T, P extends string> = P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? T[K] extends Record<string, unknown>\n ? ConfigPathValue<T[K], Rest>\n : never\n : never\n : P extends keyof T\n ? T[P]\n : never\n\n/**\n * ConfigService interface with dot notation support.\n *\n * Values are initialized on the underlying {@link ConfigStore} via\n * {@link ConfigModule}; the service itself exposes request-scoped\n * reads and per-request overrides.\n */\nexport interface IConfigService<T extends object = ModuleConfig> {\n /**\n * Get config value using dot notation\n * @example config.get('database.url')\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P>\n\n /**\n * Set config value at runtime (for runtime overrides)\n * @example config.set('email.from.name', 'Custom Name')\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void\n\n /**\n * Reset config to original value\n * @param path - Optional path to reset (resets entire config if omitted)\n */\n reset(path?: ConfigPath<T>): void\n\n /**\n * Get entire config object\n */\n all(): Readonly<T>\n\n /**\n * Check if a config path exists\n */\n has(path: ConfigPath<T>): boolean\n}\n","import { ApplicationError, ERROR_CODES } from '../../errors'\n\n/**\n * Error thrown when ConfigModule's onInitialize runs but forRoot() was never called.\n *\n * This means the module was imported without calling ConfigModule.forRoot({ load: [...] }).\n */\nexport class ConfigModuleNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configModuleNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_MODULE_NOT_INITIALIZED\n )\n }\n}\n","import { ERROR_CODES } from '../../errors'\nimport { ApplicationError } from '../../errors'\n\n/**\n * ConfigNotInitializedError\n *\n * Thrown when attempting to access ConfigService before it has been initialized.\n * This typically indicates that ConfigModule's onInitialize hook hasn't run yet,\n * or the module wasn't registered properly.\n */\nexport class ConfigNotInitializedError extends ApplicationError {\n constructor() {\n super(\n 'errors.configNotInitialized',\n ERROR_CODES.SYSTEM.CONFIG_NOT_INITIALIZED\n )\n }\n}\n","import { inject } from 'tsyringe'\nimport { Transient } from '../../di/decorators'\nimport { Macroable } from '../../macroable/macroable'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, IConfigService, ModuleConfig } from '../config.types'\nimport { type ConfigStore } from './config.store'\n\n/**\n * ConfigService with dot notation support and per-request overrides.\n *\n * ConfigService is **request-scoped**: each request gets its own\n * instance with a private `overrides` map layered over the shared\n * {@link ConfigStore}. Calls to {@link set} mutate only the current\n * request's overrides, which makes it safe to mutate config from\n * middleware (e.g. to pin `environment.appUrl` to the request host).\n *\n * Extends {@link Macroable} so apps can add domain-specific getters\n * and methods via `ConfigService.getter()` / `ConfigService.macro()`.\n *\n * @example\n * ```typescript\n * // Read with dot notation\n * const url = config.get('database.url')\n * const fromName = config.get('email.from.name')\n *\n * // Per-request override (e.g. in middleware)\n * config.set('environment.appUrl', `${proto}://${host}`)\n *\n * // Reset the override for the current request\n * config.reset('environment.appUrl')\n * ```\n */\n@Transient(CONFIG_TOKENS.ConfigService)\nexport class ConfigService<T extends object = ModuleConfig> extends Macroable implements IConfigService<T> {\n private overrides = new Map<string, unknown>()\n\n constructor(\n @inject(CONFIG_TOKENS.ConfigStore) private readonly store: ConfigStore<T>,\n ) {\n super()\n }\n\n /**\n * Get config value using dot notation. Request overrides take\n * precedence over the shared store.\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n const override = this.readOverride(path)\n if (override !== undefined) {\n return override as ConfigPathValue<T, P>\n }\n return this.store.get(path)\n }\n\n /**\n * Set a config value for the lifetime of the current request.\n * Does not mutate the shared store.\n */\n set<P extends ConfigPath<T>>(path: P, value: ConfigPathValue<T, P>): void {\n if (this.hasDangerousSegment(path)) return\n this.overrides.set(path, value)\n }\n\n /**\n * Clear a single override, or all overrides for this request.\n */\n reset(path?: ConfigPath<T>): void {\n if (path) {\n this.overrides.delete(path)\n return\n }\n this.overrides.clear()\n }\n\n /**\n * Get the full config object, with request overrides merged in.\n */\n all(): Readonly<T> {\n const base = this.store.all() as T\n if (this.overrides.size === 0) {\n return base as Readonly<T>\n }\n const merged = this.deepClone(base)\n for (const [path, value] of this.overrides) {\n this.writeByPath(merged, path, value)\n }\n return merged as Readonly<T>\n }\n\n /**\n * Check if a config path exists (in overrides or the store).\n */\n has(path: ConfigPath<T>): boolean {\n if (this.readOverride(path) !== undefined) return true\n return this.store.has(path)\n }\n\n private readOverride(path: string): unknown {\n if (this.hasDangerousSegment(path)) return undefined\n if (this.overrides.has(path)) {\n return this.overrides.get(path)\n }\n // Support partial-path reads: if an ancestor was overridden, walk into it.\n const segments = path.split('.')\n for (let i = segments.length - 1; i > 0; i--) {\n const parent = segments.slice(0, i).join('.')\n if (this.overrides.has(parent)) {\n const parentValue = this.overrides.get(parent)\n return this.walk(parentValue, segments.slice(i))\n }\n }\n return undefined\n }\n\n private walk(value: unknown, keys: string[]): unknown {\n let current = value\n for (const key of keys) {\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private writeByPath(obj: unknown, path: string, value: unknown): void {\n const keys = path.split('.')\n if (keys.some((key) => this.isDangerousKey(key))) return\n let current = obj as Record<string, unknown>\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i]\n if (!Object.hasOwn(current, key) || typeof current[key] !== 'object' || current[key] === null) {\n Object.defineProperty(current, key, {\n value: {},\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n current = current[key] as Record<string, unknown>\n }\n Object.defineProperty(current, keys[keys.length - 1], {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n })\n }\n\n private hasDangerousSegment(path: string): boolean {\n return path.split('.').some((key) => this.isDangerousKey(key))\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import { Transient } from '../../di/decorators'\nimport { CONFIG_TOKENS } from '../config.tokens'\nimport type { ConfigPath, ConfigPathValue, ModuleConfig } from '../config.types'\nimport { ConfigNotInitializedError } from '../errors'\n\n/**\n * ConfigStore\n *\n * Singleton-scoped holder of validated, merged configuration.\n *\n * ConfigStore is the source of truth for configuration values. It is\n * initialized once during application startup by {@link ConfigModule}\n * and never mutated afterwards.\n *\n * Per-request overrides live on {@link ConfigService}, which reads\n * through to this store for any key not explicitly overridden.\n */\n@Transient(CONFIG_TOKENS.ConfigStore)\nexport class ConfigStore<T extends object = ModuleConfig> {\n private data: T | undefined\n\n /**\n * Initialize the store with validated configuration.\n * Called by {@link ConfigModule} during initialization.\n */\n initialize(config: T): void {\n this.data = this.deepClone(config)\n }\n\n /**\n * Get config value using dot notation.\n */\n get<P extends ConfigPath<T>>(path: P): ConfigPathValue<T, P> {\n this.ensureInitialized()\n return this.getByPath(this.data, path) as ConfigPathValue<T, P>\n }\n\n /**\n * Check if a config path exists.\n */\n has(path: ConfigPath<T>): boolean {\n this.ensureInitialized()\n return this.getByPath(this.data, path) !== undefined\n }\n\n /**\n * Get the entire config object (readonly snapshot).\n */\n all(): Readonly<T> {\n this.ensureInitialized()\n return this.data as Readonly<T>\n }\n\n /**\n * True once {@link initialize} has been called.\n */\n isInitialized(): boolean {\n return this.data !== undefined\n }\n\n private getByPath(obj: unknown, path: string): unknown {\n const keys = path.split('.')\n let current = obj\n for (const key of keys) {\n if (this.isDangerousKey(key)) return undefined\n if (current === null || current === undefined) return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n }\n\n private isDangerousKey(key: string): boolean {\n return key === '__proto__' || key === 'constructor' || key === 'prototype'\n }\n\n private ensureInitialized(): void {\n if (this.data === undefined) {\n throw new ConfigNotInitializedError()\n }\n }\n\n private deepClone<V>(obj: V): V {\n if (obj === null || typeof obj !== 'object') {\n return obj\n }\n return JSON.parse(JSON.stringify(obj)) as V\n }\n}\n","import { DI_TOKENS } from '../di/tokens'\nimport { Scope } from '../di/types'\nimport type { z } from '../i18n/validation'\nimport { Module } from '../module'\nimport type { DynamicModule, ModuleContext, OnInitialize, Provider } from '../module/types'\nimport { CONFIG_TOKENS } from './config.tokens'\nimport { ConfigValidationError, type ModuleConfig } from './config.types'\nimport { ConfigModuleNotInitializedError } from './errors'\nimport type { ConfigNamespace } from './register-as'\nimport { ConfigService } from './services/config.service'\nimport { ConfigStore } from './services/config.store'\n\n/**\n * Any config namespace - uses structural typing for flexibility\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyConfigNamespace = ConfigNamespace<string, any, object>\n\n/**\n * Options for ConfigModule.forRoot\n */\nexport interface ConfigModuleOptions {\n /**\n * Array of config namespaces created via registerAs()\n */\n load: AnyConfigNamespace[]\n\n /**\n * Optional Zod schema for validating the merged config\n * Validates the entire config object after all namespaces are merged\n */\n validateSchema?: z.ZodType<object>\n}\n\n// Store options for use in onInitialize\nlet moduleOptions: ConfigModuleOptions | null = null\n\n/**\n * ConfigModule\n *\n * Provides configuration management with namespace support.\n * Uses registerAs() to create typed config namespaces that can be injected.\n *\n * @example\n * ```typescript\n * // Define config namespaces\n * const databaseConfig = registerAs('database', (env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: 10\n * }))\n *\n * const emailConfig = registerAs('email', (env) => ({\n * provider: env.EMAIL_PROVIDER,\n * from: { name: 'App', email: 'noreply@example.com' }\n * }))\n *\n * // Register in module\n * @Module({\n * imports: [\n * ConfigModule.forRoot({\n * load: [databaseConfig, emailConfig],\n * validateSchema: AppConfigSchema\n * })\n * ]\n * })\n * export class AppModule {}\n *\n * // Inject config\n * constructor(\n * @inject(CONFIG_TOKENS.ConfigService) private config: IConfigService,\n * @inject(databaseConfig.KEY) private dbConfig: DatabaseConfig\n * ) {\n * // Use dot notation\n * const url = this.config.get('database.url')\n *\n * // Or inject namespace directly\n * const url = this.dbConfig.url\n * }\n * ```\n */\n@Module({\n providers: [\n // ConfigStore is the singleton source of truth for validated config.\n {\n provide: CONFIG_TOKENS.ConfigStore,\n useClass: ConfigStore,\n scope: Scope.Singleton,\n },\n // ConfigService is request-scoped: each request gets its own\n // overrides map layered over the shared ConfigStore.\n {\n provide: CONFIG_TOKENS.ConfigService,\n useClass: ConfigService,\n scope: Scope.Request,\n },\n ],\n})\nexport class ConfigModule implements OnInitialize {\n /**\n * Configure ConfigModule with namespace loaders\n *\n * @param options - Configuration options\n * @returns Dynamic module with config infrastructure\n */\n static forRoot(options: ConfigModuleOptions): DynamicModule {\n moduleOptions = options\n\n const providers: Provider[] = []\n\n // Register each namespace config using asProvider()\n for (const namespace of options.load) {\n providers.push(namespace.asProvider())\n }\n\n return {\n module: ConfigModule,\n providers,\n }\n }\n\n /**\n * Initialize config service with merged namespaces\n * Called after all providers are registered\n */\n onInitialize(context: ModuleContext): void {\n if (!moduleOptions) {\n throw new ConfigModuleNotInitializedError()\n }\n\n const env = context.container.resolve<unknown>(DI_TOKENS.CloudflareEnv)\n const configStore = context.container.resolve<ConfigStore>(CONFIG_TOKENS.ConfigStore)\n\n // Build merged config from all namespaces\n const mergedConfig: Record<string, unknown> = {}\n\n for (const namespace of moduleOptions.load) {\n mergedConfig[namespace.namespace] = namespace.factory(env)\n }\n\n // Validate if schema provided\n if (moduleOptions.validateSchema) {\n const result = moduleOptions.validateSchema.safeParse(mergedConfig)\n if (!result.success) {\n throw new ConfigValidationError(\n 'Configuration validation failed',\n result.error\n )\n }\n }\n\n // Initialize the shared ConfigStore with merged config.\n configStore.initialize(mergedConfig as ModuleConfig)\n\n context.logger.debug('ConfigModule initialized', {\n namespaces: moduleOptions.load.map((n) => n.namespace),\n })\n }\n}\n","import type { InjectionToken } from 'tsyringe'\nimport { DI_TOKENS } from '../di/tokens'\nimport type { FactoryProvider } from '../module/types'\n\n/**\n * Configuration namespace registration result\n */\nexport interface ConfigNamespace<TKey extends string, TEnv, TConfig extends object> {\n /** Auto-derived injection token (e.g., 'database' -> Symbol('stratal:config:database')) */\n readonly KEY: InjectionToken<TConfig>\n /** The namespace key */\n readonly namespace: TKey\n /** The factory function that receives env and returns config */\n readonly factory: (env: TEnv) => TConfig\n /**\n * Returns a provider configuration for use in module registration\n * Automatically injects DI_TOKENS.CloudflareEnv\n */\n asProvider(): FactoryProvider<TConfig>\n}\n\n/**\n * Create a namespaced configuration factory\n * Similar to NestJS registerAs\n *\n * @param namespace - Configuration namespace (e.g., 'database', 'email')\n * @param factory - Factory function receiving env and returning config object\n * @returns ConfigNamespace with token, factory, and asProvider() method\n *\n * @example\n * ```typescript\n * // apps/backend/src/config/database.config.ts\n * export const databaseConfig = registerAs('database', (env: Env) => ({\n * url: env.DATABASE_URL,\n * maxConnections: parseInt(env.DATABASE_MAX_CONNECTIONS || '10'),\n * }))\n *\n * // Auto-generates: databaseConfig.KEY = Symbol('stratal:config:database')\n *\n * // Usage in module:\n * // Option 1: Manual provider\n * {\n * provide: databaseConfig.KEY,\n * useFactory: databaseConfig.factory,\n * inject: [DI_TOKENS.CloudflareEnv]\n * }\n *\n * // Option 2: asProvider() helper\n * databaseConfig.asProvider()\n * // Returns: { provide: databaseConfig.KEY, useFactory: ..., inject: [DI_TOKENS.CloudflareEnv] }\n * ```\n */\nexport function registerAs<TKey extends string, TEnv, TConfig extends object>(\n namespace: TKey,\n factory: (env: TEnv) => TConfig\n): ConfigNamespace<TKey, TEnv, TConfig> {\n const KEY = Symbol.for(`stratal:config:${namespace}`) as InjectionToken<TConfig>\n\n return {\n KEY,\n namespace,\n factory,\n asProvider(): FactoryProvider<TConfig> {\n return {\n provide: KEY,\n useFactory: factory,\n inject: [DI_TOKENS.CloudflareEnv],\n }\n },\n }\n}\n\n/**\n * Helper to derive config type from registerAs result\n *\n * @example\n * ```typescript\n * const databaseConfig = registerAs('database', (env) => ({ url: env.DATABASE_URL }))\n * type DatabaseConfig = InferConfigType<typeof databaseConfig>\n * // { url: string }\n * ```\n */\nexport type InferConfigType<T> = T extends ConfigNamespace<string, unknown, infer C> ? C : never\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consumer-registry-
|
|
1
|
+
{"version":3,"file":"consumer-registry-Doom7BEh.d.mts","names":[],"sources":["../src/queue/queue-consumer.ts","../src/queue/consumer-registry.ts"],"mappings":";;AAMA;;;;;UAAiB,YAAA;EAIf;EAFA,EAAA;EAMA;EAJA,SAAA;EAMA;EAJA,IAAA;EAMG;EAJH,OAAA,EAAS,CAAA;EAIK;EAFd,QAAA;IACE,MAAA;IAAA,CACC,GAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;UAsBY,cAAA;EAsBQ;;;;;;EAAA,SAfd,YAAA;ECXE;;;;;EDkBX,MAAA,CAAO,OAAA,EAAS,YAAA,CAAa,CAAA,IAAK,OAAA;ECqDD;;;;;;ED7CjC,OAAA,EAAS,KAAA,EAAO,KAAA,EAAO,OAAA,EAAS,YAAA,CAAa,CAAA,IAAK,OAAA;AAAA;;;AAxDpD;;;;;;;;;;;;;;;AAkCA;;;;;;;;;;;;;;;;AAlCA,cC8Ba,gBAAA;EDkBJ;EAAA,QChBC,eAAA;EDwBR;EAAA,QCrBQ,YAAA;EDqBC;;;;;;;ECZT,QAAA,CAAS,QAAA,EAAU,cAAA;;;AAfrB;;;;;;;;EAuCE,YAAA,CAAa,WAAA,WAAsB,cAAA;EAjC3B;;;;;;EAgDR,YAAA,CAAa,WAAA;EAAb;;;;;EASA,eAAA,CAAA;EASiC;;;;;EAAjC,eAAA,CAAA,GAAmB,cAAA;AAAA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { f as ROUTE_METADATA_KEYS } from "./errors-B4pYgYON.mjs";
|
|
2
|
+
import { p as Transient } from "./logger-V6Ms3QnQ.mjs";
|
|
3
|
+
//#region src/router/decorators/controller.decorator.ts
|
|
4
|
+
const CONTROLLER_ROUTE_KEY = ROUTE_METADATA_KEYS.CONTROLLER_ROUTE;
|
|
5
|
+
/**
|
|
6
|
+
* Base controller decorator for route registration
|
|
7
|
+
*
|
|
8
|
+
* This is the core controller decorator that handles:
|
|
9
|
+
* - Transient scope registration (request-scoped)
|
|
10
|
+
* - Route metadata storage
|
|
11
|
+
* - Controller options (tags, security schemes, hideFromDocs)
|
|
12
|
+
*
|
|
13
|
+
* @param route - Base route for this controller (e.g., '/api/v1/users')
|
|
14
|
+
* @param options - Optional configuration (tags, security schemes, hideFromDocs)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { Controller } from 'stratal/router'
|
|
19
|
+
*
|
|
20
|
+
* @Controller('/api/v1/users', { tags: ['Users'] })
|
|
21
|
+
* export class UsersController implements IController {
|
|
22
|
+
* // All routes accessible
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function Controller(route, options) {
|
|
27
|
+
return function(target) {
|
|
28
|
+
Transient()(target);
|
|
29
|
+
Reflect.defineMetadata(CONTROLLER_ROUTE_KEY, route, target);
|
|
30
|
+
if (options) Reflect.defineMetadata(ROUTE_METADATA_KEYS.CONTROLLER_OPTIONS, options, target);
|
|
31
|
+
return target;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the route from controller class metadata
|
|
36
|
+
*
|
|
37
|
+
* @param target - Controller class or instance
|
|
38
|
+
* @returns Route string or undefined if not set
|
|
39
|
+
*/
|
|
40
|
+
function getControllerRoute(target) {
|
|
41
|
+
const metadataTarget = typeof target === "function" ? target : target.constructor;
|
|
42
|
+
return Reflect.getMetadata(CONTROLLER_ROUTE_KEY, metadataTarget);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the options from controller class metadata
|
|
46
|
+
*
|
|
47
|
+
* @param target - Controller class or instance
|
|
48
|
+
* @returns Controller options or undefined if not set
|
|
49
|
+
*/
|
|
50
|
+
function getControllerOptions(target) {
|
|
51
|
+
const metadataTarget = typeof target === "function" ? target : target.constructor;
|
|
52
|
+
return Reflect.getMetadata(ROUTE_METADATA_KEYS.CONTROLLER_OPTIONS, metadataTarget);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the version from controller class metadata
|
|
56
|
+
*
|
|
57
|
+
* @param target - Controller class or instance
|
|
58
|
+
* @returns Version string, array, VERSION_NEUTRAL symbol, or undefined if not set
|
|
59
|
+
*/
|
|
60
|
+
function getControllerVersion(target) {
|
|
61
|
+
return getControllerOptions(target)?.version;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { getControllerVersion as i, getControllerOptions as n, getControllerRoute as r, Controller as t };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=controller.decorator-LZY9aHYG.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.decorator-LZY9aHYG.mjs","names":[],"sources":["../src/router/decorators/controller.decorator.ts"],"sourcesContent":["import { Transient } from '../../di/decorators'\nimport { type Constructor } from '../../types'\nimport { ROUTE_METADATA_KEYS } from '../constants'\nimport { type ControllerOptions } from '../types'\n\nconst CONTROLLER_ROUTE_KEY = ROUTE_METADATA_KEYS.CONTROLLER_ROUTE\n\n/**\n * Base controller decorator for route registration\n *\n * This is the core controller decorator that handles:\n * - Transient scope registration (request-scoped)\n * - Route metadata storage\n * - Controller options (tags, security schemes, hideFromDocs)\n *\n * @param route - Base route for this controller (e.g., '/api/v1/users')\n * @param options - Optional configuration (tags, security schemes, hideFromDocs)\n *\n * @example\n * ```typescript\n * import { Controller } from 'stratal/router'\n *\n * @Controller('/api/v1/users', { tags: ['Users'] })\n * export class UsersController implements IController {\n * // All routes accessible\n * }\n * ```\n */\nexport function Controller(route: string, options?: ControllerOptions) {\n return function <T extends Constructor>(target: T) {\n // Wrap @Transient (handles @injectable and scope metadata)\n Transient()(target)\n\n // Store route metadata on the class\n Reflect.defineMetadata(CONTROLLER_ROUTE_KEY, route, target)\n\n // Store options metadata if provided\n if (options) {\n Reflect.defineMetadata(ROUTE_METADATA_KEYS.CONTROLLER_OPTIONS, options, target)\n }\n\n return target\n }\n}\n\n/**\n * Get the route from controller class metadata\n *\n * @param target - Controller class or instance\n * @returns Route string or undefined if not set\n */\nexport function getControllerRoute(target: object): string | undefined {\n // Check if target is a class constructor (function) or an instance\n // If class, get metadata from it directly; if instance, get from constructor\n const metadataTarget = typeof target === 'function' ? target : (target as { constructor: object }).constructor\n return Reflect.getMetadata(CONTROLLER_ROUTE_KEY, metadataTarget) as string | undefined\n}\n\n/**\n * Get the options from controller class metadata\n *\n * @param target - Controller class or instance\n * @returns Controller options or undefined if not set\n */\nexport function getControllerOptions(target: object): ControllerOptions | undefined {\n const metadataTarget = typeof target === 'function' ? target : (target as { constructor: object }).constructor\n return Reflect.getMetadata(ROUTE_METADATA_KEYS.CONTROLLER_OPTIONS, metadataTarget) as ControllerOptions | undefined\n}\n\n/**\n * Get the version from controller class metadata\n *\n * @param target - Controller class or instance\n * @returns Version string, array, VERSION_NEUTRAL symbol, or undefined if not set\n */\nexport function getControllerVersion(target: object): ControllerOptions['version'] {\n const options = getControllerOptions(target)\n return options?.version\n}\n"],"mappings":";;;AAKA,MAAM,uBAAuB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;AAuBjD,SAAgB,WAAW,OAAe,SAA6B;AACrE,QAAO,SAAiC,QAAW;AAEjD,aAAW,CAAC,OAAO;AAGnB,UAAQ,eAAe,sBAAsB,OAAO,OAAO;AAG3D,MAAI,QACF,SAAQ,eAAe,oBAAoB,oBAAoB,SAAS,OAAO;AAGjF,SAAO;;;;;;;;;AAUX,SAAgB,mBAAmB,QAAoC;CAGrE,MAAM,iBAAiB,OAAO,WAAW,aAAa,SAAU,OAAmC;AACnG,QAAO,QAAQ,YAAY,sBAAsB,eAAe;;;;;;;;AASlE,SAAgB,qBAAqB,QAA+C;CAClF,MAAM,iBAAiB,OAAO,WAAW,aAAa,SAAU,OAAmC;AACnG,QAAO,QAAQ,YAAY,oBAAoB,oBAAoB,eAAe;;;;;;;;AASpF,SAAgB,qBAAqB,QAA8C;AAEjF,QADgB,qBAAqB,OACvB,EAAE"}
|