moost 0.6.31 → 0.6.32

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/dist/index.d.ts CHANGED
@@ -12,6 +12,43 @@ export { ProstoLogger, TConsoleBase } from '@prostojs/logger';
12
12
  import { Hookable } from 'hookable';
13
13
  export { clearGlobalWooks, getGlobalWooks } from 'wooks';
14
14
 
15
+ /**
16
+ * Duplicate-copy detection for moost
17
+ *
18
+ * Mate stores decorator metadata in WeakMaps keyed by constructor
19
+ * reference, and moost keeps module-scoped DI singletons
20
+ * (`sharedMoostInfact`, `moostMate`). If a bundler loads two copies of
21
+ * moost, decorators run against one copy while lookups hit the other,
22
+ * and DI fails with distant "Class is not Injectable" errors. The first
23
+ * loaded copy stamps `globalThis` under a global symbol; any subsequent
24
+ * copy finds the stamp and warns.
25
+ */
26
+ declare const MOOST_MODULE_IDENTITY_KEY: unique symbol;
27
+ interface TMoostModuleIdentity {
28
+ version?: string;
29
+ path?: string;
30
+ }
31
+ /**
32
+ * Stamps the global object with this module's identity and warns when
33
+ * another copy of moost was already loaded — decorator metadata and DI
34
+ * singletons do not interoperate between copies.
35
+ *
36
+ * Exported for testability; invoked once at module scope.
37
+ */
38
+ declare function stampModuleIdentity(identity: TMoostModuleIdentity, globalObject?: object): void;
39
+ /**
40
+ * One-shot wrapper around `stampModuleIdentity` for this module copy.
41
+ *
42
+ * Invoked at module scope AND from `getMoostMate()`: the package ships
43
+ * `"sideEffects": false`, so a tree-shaking bundler may drop the
44
+ * module-scope invocation when nothing imports from this file — the
45
+ * `getMoostMate()` call keeps the duplicate check reachable in bundled
46
+ * apps (it fires on first decorator use, exactly when per-copy metadata
47
+ * divergence begins), which are the environments that produce duplicate
48
+ * copies in the first place.
49
+ */
50
+ declare function stampOnce(): void;
51
+
15
52
  /** Escape-hatch type alias for `any`. */
16
53
  type TAny = any;
17
54
  /** Generic function signature accepting any arguments. */
@@ -172,6 +209,28 @@ interface TPipeData {
172
209
 
173
210
  declare const resolvePipe: TPipeFn<TEmpty>;
174
211
 
212
+ /** Summary of a registered controller, its metadata, prefix, and handlers. */
213
+ interface TControllerOverview {
214
+ meta: TMoostMetadata;
215
+ computedPrefix: string;
216
+ type: TFunction;
217
+ handlers: THandlerOverview[];
218
+ }
219
+ /** Summary of a registered handler method within a controller. */
220
+ interface THandlerOverview {
221
+ meta: TMoostMetadata;
222
+ path?: string;
223
+ type: string;
224
+ method: string;
225
+ handler: TMoostHandler<TEmpty>;
226
+ registeredAs: {
227
+ path: string;
228
+ args: string[];
229
+ }[];
230
+ }
231
+ /** Hook points in the event handler lifecycle where context injectors are invoked. */
232
+ type TContextInjectorHook = 'Event:start' | 'Interceptors:init' | 'Arguments:resolve' | 'Interceptors:before' | 'Handler' | 'Interceptors:after';
233
+
175
234
  type TInterceptorBeforeFn = (reply: (response: TAny) => void) => void | Promise<void>;
176
235
  type TInterceptorAfterFn = (response: TAny, reply: (response: TAny) => void) => void | Promise<void>;
177
236
  type TInterceptorErrorFn = (error: Error, reply: (response: TAny) => void) => void | Promise<void>;
@@ -328,6 +387,20 @@ declare function getInfactScopeVars<T extends object>(name: string | symbol): T
328
387
  */
329
388
  declare function getNewMoostInfact(): Infact<TMoostMetadata<TEmpty>, TMoostMetadata<TEmpty>, TMoostParamsMetadata, TCustom>;
330
389
 
390
+ /**
391
+ * `fatal-capable` findings may reject `init()` (only non-optional constructor
392
+ * params — they would fail at instantiation anyway); `warn-only` findings are
393
+ * always logged as warnings and never thrown (silent `undefined` is legal there).
394
+ */
395
+ type TParamAuditSeverity = 'fatal-capable' | 'warn-only';
396
+ /** A single diagnostic produced by {@link auditParams}. */
397
+ interface TParamAuditFinding {
398
+ message: string;
399
+ severity: TParamAuditSeverity;
400
+ }
401
+ /** Mode for the bind-time param-type audit (see `TMoostOptions['diagnostics']`). */
402
+ type TParamAuditMode = 'error' | 'warn' | 'off';
403
+
331
404
  /**
332
405
  * A `@MoostInit`-decorated controller method, collected at bind time and run
333
406
  * once by `Moost.init()` after all controllers are bound. Interceptors are not
@@ -341,34 +414,101 @@ interface TInitHook {
341
414
  resolveArgs?: () => unknown[] | Promise<unknown[]>;
342
415
  }
343
416
 
344
- /** Summary of a registered controller, its metadata, prefix, and handlers. */
345
- interface TControllerOverview {
346
- meta: TMoostMetadata;
347
- computedPrefix: string;
348
- type: TFunction;
349
- handlers: THandlerOverview[];
350
- }
351
- /** Summary of a registered handler method within a controller. */
352
- interface THandlerOverview {
353
- meta: TMoostMetadata;
354
- path?: string;
355
- type: string;
356
- method: string;
357
- handler: TMoostHandler<TEmpty>;
358
- registeredAs: {
359
- path: string;
360
- args: string[];
361
- }[];
362
- }
363
- /** Hook points in the event handler lifecycle where context injectors are invoked. */
364
- type TContextInjectorHook = 'Event:start' | 'Interceptors:init' | 'Arguments:resolve' | 'Interceptors:before' | 'Handler' | 'Interceptors:after';
417
+ /** Mode for the bind-time inheritance audit (see `TMoostOptions['diagnostics']`). */
418
+ type TInheritanceAuditMode = 'warn' | 'off';
365
419
 
366
420
  interface TMoostOptions {
367
421
  /**
368
- * Prefix that is used for each event path
422
+ * Global path prefix — mounts the whole app under one path segment.
423
+ *
424
+ * This is the first-class "serve everything under `/api`" option: every
425
+ * controller (registered via `registerControllers`, imported through
426
+ * `@ImportController`, or the Moost subclass itself) computes its mount
427
+ * path as `globalPrefix + '/' + <own prefix>`, so
428
+ * `new Moost({ globalPrefix: 'api' })` mounts a `@Controller('users')`
429
+ * class at `/api/users` without touching any registration call.
430
+ *
431
+ * All registration forms stay under `globalPrefix` — even the
432
+ * prefix-replacing ones (`[prefix, Ctrl]` tuple, `mode: 'replace'`) only
433
+ * replace the controller's own `@Controller` prefix, never the global one.
434
+ * Extra/duplicate slashes are normalized by the router.
369
435
  */
370
436
  globalPrefix?: string;
371
437
  logger?: TConsoleBase;
438
+ /**
439
+ * Bind-time DI diagnostics, evaluated during `init()`.
440
+ *
441
+ * `paramTypes` audits constructor and handler params whose emitted design
442
+ * type is unusable for DI — `Object` (class imported with `import type`, or
443
+ * an interface/union type) or `undefined` (circular import) — and that carry
444
+ * no explicit resolution (`@Inject`, `@Resolve`-based decorators, `@Circular`):
445
+ * - `'error'` — log every finding and reject `init()` when a non-optional
446
+ * constructor param is affected (it would fail at instantiation anyway);
447
+ * - `'warn'` — log findings, never throw;
448
+ * - `'off'` — skip the audit entirely.
449
+ *
450
+ * Defaults to `'error'` when `NODE_ENV !== 'production'`, `'warn'` otherwise.
451
+ * Optional/nullable constructor params and handler method params are always
452
+ * warn-only (`undefined` injection is legal there).
453
+ */
454
+ diagnostics?: {
455
+ paramTypes?: TParamAuditMode;
456
+ /**
457
+ * `inheritance` audits every registered controller for the two subclassing
458
+ * traps (warn-only, never rejects `init()`):
459
+ * - a class that registered 0 handlers while an ancestor defines some,
460
+ * without a deliberate `@Inherit(false)` opt-out — parent routes
461
+ * silently 404 (fires both when no `@Inherit` decision exists and when
462
+ * `@Inherit()` is present but an undecorated intermediate class breaks
463
+ * the chain — the warning names the broken link);
464
+ * - a DI-instantiated class with no effective constructor param metadata
465
+ * while a decorated ancestor declares constructor params — dependencies
466
+ * silently resolve to `undefined` (or the class is not seen as
467
+ * injectable at all when it carries no metadata).
468
+ *
469
+ * `'warn'` (default) logs a warning naming both classes and the fix
470
+ * (`@Inherit()` or re-declaring); `'off'` skips the audit.
471
+ */
472
+ inheritance?: TInheritanceAuditMode;
473
+ };
474
+ }
475
+ /**
476
+ * Object registration form for {@link Moost.registerControllers}: mounts a
477
+ * group of controllers under a shared path prefix with explicit composition
478
+ * semantics (see `mode`).
479
+ */
480
+ interface TControllersGroup {
481
+ /**
482
+ * Path segment applied to every controller of the group
483
+ * (always mounted under `globalPrefix` when one is set).
484
+ */
485
+ prefix: string;
486
+ /** Controllers (classes or instances) to register. */
487
+ controllers: (TObject | TFunction)[];
488
+ /**
489
+ * How `prefix` combines with each controller's own `@Controller(...)` prefix:
490
+ * - `'prepend'` (default) — `globalPrefix + '/' + prefix + '/' + own prefix`;
491
+ * - `'replace'` — `prefix` replaces the controller's own prefix
492
+ * (same semantics as the `[prefix, controller]` tuple form).
493
+ */
494
+ mode?: 'prepend' | 'replace';
495
+ }
496
+ /**
497
+ * Normalized internal shape of a pending controller registration —
498
+ * `registerControllers` reduces all of its accepted forms to this.
499
+ */
500
+ interface TControllerRegistration {
501
+ controller: TObject | TFunction;
502
+ /**
503
+ * Replaces the controller's own `@Controller` prefix
504
+ * (tuple form / object form with `mode: 'replace'`).
505
+ */
506
+ replaceOwnPrefix?: string;
507
+ /**
508
+ * Inserted between `globalPrefix` and the controller's own prefix
509
+ * (object form with `mode: 'prepend'`).
510
+ */
511
+ prependPrefix?: string;
372
512
  }
373
513
  /**
374
514
  * ## Moost
@@ -431,7 +571,15 @@ declare class Moost extends Hookable {
431
571
  protected initHooks: TInitHook[];
432
572
  protected provide: TProvideRegistry;
433
573
  protected replace: TReplaceRegistry;
434
- protected unregisteredControllers: (TObject | TFunction | [string, TObject | TFunction])[];
574
+ protected unregisteredControllers: TControllerRegistration[];
575
+ /** D1 param-audit findings collected while binding controllers, flushed by `init()`. */
576
+ protected paramAuditFindings: TParamAuditFinding[];
577
+ /** Effective `diagnostics.paramTypes` mode, resolved once (see {@link TMoostOptions}). */
578
+ protected readonly paramAuditMode: TParamAuditMode;
579
+ /** Effective `diagnostics.inheritance` mode (see {@link TMoostOptions}). */
580
+ protected readonly inheritanceAuditMode: TInheritanceAuditMode;
581
+ /** D5: set once `init()` completes — late provide/replace registrations then warn. */
582
+ protected initialized: boolean;
435
583
  constructor(options?: TMoostOptions | undefined);
436
584
  _fireEventStart(source: TMoostAdapter<unknown>): void;
437
585
  _fireEventEnd(source: TMoostAdapter<unknown>): void;
@@ -469,6 +617,22 @@ declare class Moost extends Hookable {
469
617
  * A throwing hook rejects `init()` (fail-fast).
470
618
  */
471
619
  protected runInitHooks(): Promise<void>;
620
+ /**
621
+ * D1 audit of a DI-instantiated controller class' constructor params.
622
+ * Findings are collected for `init()` to flush. Returns `true` when the
623
+ * bind-time SINGLETON instantiation must be skipped: in `'error'` mode a
624
+ * fatal-capable finding guarantees `init()` rejects with the aggregated
625
+ * audit error (naming class and param), which the generic infact
626
+ * instantiation error would otherwise preempt.
627
+ */
628
+ protected collectConstructorAudit(className: string, classMeta?: TMoostMetadata): boolean;
629
+ /**
630
+ * Flushes D1 findings collected during binding: every finding is logged as a
631
+ * warning; in `'error'` mode, fatal-capable findings are folded into one
632
+ * aggregate error message listing all of them, returned for `init()` to
633
+ * throw (`init()` owns the precedence between this and a bind error).
634
+ */
635
+ protected flushParamAudit(): string | undefined;
472
636
  protected bindControllers(): Promise<void>;
473
637
  protected bindController(controller: TFunction | TObject, provide: TProvideRegistry, replace: TReplaceRegistry, globalPrefix: string, replaceOwnPrefix?: string): Promise<void>;
474
638
  applyGlobalPipes(...items: (TPipeFn | TPipeData)[]): this;
@@ -483,22 +647,60 @@ declare class Moost extends Hookable {
483
647
  applyGlobalInterceptors(...items: (TClassConstructor | TInterceptorDef | TInterceptorData)[]): this;
484
648
  /**
485
649
  * Register new entries to provide as dependency injections
650
+ *
651
+ * Ordering rule: call this **before `init()`**. `init()` snapshots the
652
+ * provide registry once when binding controllers, so entries added later are
653
+ * never seen by already-bound controllers (sibling registration order does
654
+ * not matter — only the before/after-`init()` boundary does). To scope
655
+ * providers to part of the app, use class-level `@Provide` on a parent
656
+ * controller — it flows parent → child through `@ImportController`, never
657
+ * to siblings.
486
658
  * @param provide - Provide Registry (use createProvideRegistry from '\@prostojs/infact')
487
659
  * @returns
488
660
  */
489
661
  setProvideRegistry(provide: TProvideRegistry): this;
490
662
  /**
491
663
  * Register replace classes to provide as dependency injections
664
+ *
665
+ * Ordering rule: call this **before `init()`**. `init()` snapshots the
666
+ * replace registry once when binding controllers, so replacements added
667
+ * later are never seen by already-bound controllers.
492
668
  * @param replace - Replace Registry (use createReplaceRegistry from '\@prostojs/infact')
493
669
  * @returns
494
670
  */
495
671
  setReplaceRegistry(replace: TReplaceRegistry): this;
496
672
  /**
497
- * Register controllers (similar to @ImportController decorator)
498
- * @param controllers - list of target controllers (instances)
673
+ * Register controllers with the app (similar to the `@ImportController` decorator).
674
+ *
675
+ * Accepted forms (mixable in one call):
676
+ *
677
+ * 1. **Class or instance** — `registerControllers(UsersController)`.
678
+ * Mounted at `globalPrefix + '/' + own @Controller prefix`.
679
+ *
680
+ * 2. **Tuple `[prefix, controller]`** — `registerControllers(['api/users', UsersController])`.
681
+ * **IMPORTANT: the string REPLACES the controller's own `@Controller(...)` prefix — it does
682
+ * NOT prepend to it.** `['api', UsersController]` mounts a `@Controller('users')` class at
683
+ * `/api`, not `/api/users`, so every tuple registration must repeat the full path.
684
+ * To compose prefixes instead, use the object form below.
685
+ *
686
+ * 3. **Object group `{ prefix, controllers, mode? }`** —
687
+ * `registerControllers({ prefix: 'api', controllers: [UsersController] })`.
688
+ * Registers every entry of `controllers` under `prefix`:
689
+ * - `mode: 'prepend'` (default) composes the prefixes:
690
+ * `globalPrefix + '/' + prefix + '/' + own @Controller prefix`
691
+ * (a `@Controller('users')` class mounts at `/api/users`);
692
+ * - `mode: 'replace'` replaces each controller's own prefix with `prefix`
693
+ * (same semantics as the tuple form).
694
+ *
695
+ * The object form is detected only for plain objects with a `controllers` array, so
696
+ * controller classes, instances and tuples keep working unchanged. To mount the whole
697
+ * app under one segment, prefer the `globalPrefix` option (see {@link TMoostOptions}).
698
+ *
699
+ * @param controllers - controllers to register: classes, instances,
700
+ * `[prefix, controller]` tuples or `{ prefix, controllers, mode? }` groups
499
701
  * @returns
500
702
  */
501
- registerControllers(...controllers: (TObject | TFunction | [string, TObject | TFunction])[]): this;
703
+ registerControllers(...controllers: (TObject | TFunction | [string, TObject | TFunction] | TControllersGroup)[]): this;
502
704
  logMappedHandler(eventName: string, classConstructor: Function, method: string, stroke?: boolean, prefix?: string): void;
503
705
  }
504
706
  interface TMoostAdapterOptions<H, T> {
@@ -797,11 +999,20 @@ declare function Resolve<T extends TObject = TEmpty>(resolver: (metas: TPipeMeta
797
999
  */
798
1000
  declare function Param(name: string): MethodDecorator & ClassDecorator & ParameterDecorator & PropertyDecorator;
799
1001
  /**
800
- * Get Parsed Params from url parh
1002
+ * Get Parsed Params from url path
1003
+ *
1004
+ * Stamps `paramSource: 'ROUTE'` metadata (like `@Param(name)` does), so
1005
+ * source-aware pipes (e.g. coercion pipes that only coerce string-transport
1006
+ * input) recognize the whole params object as route input.
1007
+ *
1008
+ * Tip: type the argument with an interface-based DTO (e.g. an atscript `.as`
1009
+ * interface) — interfaces emit as `declare class`, so the design type survives
1010
+ * every metadata toolchain, unlike scalar type aliases which only survive
1011
+ * syntactic emitters.
801
1012
  * @decorator
802
1013
  * @paramType object
803
1014
  */
804
- declare function Params(): ParameterDecorator & PropertyDecorator;
1015
+ declare function Params(): MethodDecorator & ClassDecorator & ParameterDecorator & PropertyDecorator;
805
1016
  /**
806
1017
  * Provide Const Value
807
1018
  * @decorator
@@ -1048,5 +1259,5 @@ declare function createLogger(opts?: Partial<TProstoLoggerOptions>): ProstoLogge
1048
1259
  /** Default colored console transport used by Moost loggers. */
1049
1260
  declare const loggerConsoleTransport: _prostojs_logger.TProstoLoggerTransportFn<any>;
1050
1261
 
1051
- export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, LoggerTopic, Moost, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, createLogger, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, getHandlerPaths, getInfactScopeVars, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, globalKey, isThenable, loggerConsoleTransport, mergeSorted, registerEventScope, resolvePipe, setControllerContext, setInfactLoggingOptions, setInterceptResult, setOvertake, useControllerContext, useHandlerPaths, useInterceptResult, useOvertake, useScopeId };
1052
- export type { TAny, TAnyFn, TClassConstructor, TContextInjectorHook, TControllerOverview, TEmpty, TFunction, TGetHandlerPathsOptions, TInjectableScope, TInterceptorAfterFn, TInterceptorBeforeFn, TInterceptorData, TInterceptorDef, TInterceptorDefFactory, TInterceptorEntry, TInterceptorErrorFn, TLogger, TMoostAdapter, TMoostAdapterOptions, TMoostEventHandlerHookOptions, TMoostEventHandlerOptions, TMoostHandler, TMoostMetadata, TMoostOptions, TMoostParamsMetadata, TObject, TOvertakeFn, TPipeData, TPipeFn, TPipeMetas, TPrimitives };
1262
+ export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, LoggerTopic, MOOST_MODULE_IDENTITY_KEY, Moost, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, createLogger, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, getHandlerPaths, getInfactScopeVars, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, globalKey, isThenable, loggerConsoleTransport, mergeSorted, registerEventScope, resolvePipe, setControllerContext, setInfactLoggingOptions, setInterceptResult, setOvertake, stampModuleIdentity, stampOnce, useControllerContext, useHandlerPaths, useInterceptResult, useOvertake, useScopeId };
1263
+ export type { TAny, TAnyFn, TClassConstructor, TContextInjectorHook, TControllerOverview, TControllerRegistration, TControllersGroup, TEmpty, TFunction, TGetHandlerPathsOptions, TInjectableScope, TInterceptorAfterFn, TInterceptorBeforeFn, TInterceptorData, TInterceptorDef, TInterceptorDefFactory, TInterceptorEntry, TInterceptorErrorFn, TLogger, TMoostAdapter, TMoostAdapterOptions, TMoostEventHandlerHookOptions, TMoostEventHandlerOptions, TMoostHandler, TMoostMetadata, TMoostModuleIdentity, TMoostOptions, TMoostParamsMetadata, TObject, TOvertakeFn, TPipeData, TPipeFn, TPipeMetas, TPrimitives };