opinionated-machine 6.15.0 → 6.16.1

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
@@ -1,6 +1,7 @@
1
1
  export { AbstractController, type BuildRoutesReturnType } from './lib/AbstractController.js';
2
2
  export { AbstractModule, type InferModuleDependencies, type InferPublicModuleDependencies, type MandatoryNameAndRegistrationPair, type PublicDependencies, } from './lib/AbstractModule.js';
3
3
  export { AbstractTestContextFactory, type CreateTestContextParams, } from './lib/AbstractTestContextFactory.js';
4
+ export * from './lib/api-contracts/index.js';
4
5
  export type { NestedPartial } from './lib/configUtils.js';
5
6
  export { type DependencyInjectionOptions, DIContext, type RegisterDependenciesParams, } from './lib/DIContext.js';
6
7
  export { ENABLE_ALL, isAnyMessageQueueConsumerEnabled, isEnqueuedJobWorkersEnabled, isJobQueueEnabled, isMessageQueueConsumerEnabled, isPeriodicJobEnabled, resolveJobQueuesEnabled, } from './lib/diConfigUtils.js';
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { AbstractController } from './lib/AbstractController.js';
2
2
  export { AbstractModule, } from './lib/AbstractModule.js';
3
3
  export { AbstractTestContextFactory, } from './lib/AbstractTestContextFactory.js';
4
+ export * from './lib/api-contracts/index.js';
4
5
  export { DIContext, } from './lib/DIContext.js';
5
6
  export { ENABLE_ALL, isAnyMessageQueueConsumerEnabled, isEnqueuedJobWorkersEnabled, isJobQueueEnabled, isMessageQueueConsumerEnabled, isPeriodicJobEnabled, resolveJobQueuesEnabled, } from './lib/diConfigUtils.js';
6
7
  // Dual-mode (SSE + JSON)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAA8B,MAAM,6BAA6B,CAAA;AAC5F,OAAO,EACL,cAAc,GAKf,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,0BAA0B,GAE3B,MAAM,qCAAqC,CAAA;AAE5C,OAAO,EAEL,SAAS,GAEV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,UAAU,EACV,gCAAgC,EAChC,2BAA2B,EAC3B,iBAAiB,EACjB,6BAA6B,EAC7B,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,wBAAwB,CAAA;AAC/B,yBAAyB;AACzB,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,iCAAiC;AACjC,cAAc,uBAAuB,CAAA;AACrC,MAAM;AACN,cAAc,oBAAoB,CAAA;AAClC,wBAAwB;AACxB,cAAc,wBAAwB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAA8B,MAAM,6BAA6B,CAAA;AAC5F,OAAO,EACL,cAAc,GAKf,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,0BAA0B,GAE3B,MAAM,qCAAqC,CAAA;AAC5C,cAAc,8BAA8B,CAAA;AAE5C,OAAO,EAEL,SAAS,GAEV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,UAAU,EACV,gCAAgC,EAChC,2BAA2B,EAC3B,iBAAiB,EACjB,6BAA6B,EAC7B,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,wBAAwB,CAAA;AAC/B,yBAAyB;AACzB,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,iCAAiC;AACjC,cAAc,uBAAuB,CAAA;AACrC,MAAM;AACN,cAAc,oBAAoB,CAAA;AAClC,wBAAwB;AACxB,cAAc,wBAAwB,CAAA"}
@@ -31,8 +31,10 @@ export declare class DIContext<Dependencies extends object, Config extends objec
31
31
  private readonly controllerResolvers;
32
32
  private readonly sseControllerNames;
33
33
  private readonly dualModeControllerNames;
34
+ private readonly apiControllerNames;
34
35
  private readonly appConfig;
35
36
  constructor(diContainer: AwilixContainer<Dependencies>, options: DependencyInjectionOptions, appConfig: Config, awilixManager?: AwilixManager);
37
+ private registerControllers;
36
38
  private registerModule;
37
39
  registerDependencies(params: RegisterDependenciesParams<Dependencies, Config, ExternalDependencies>, externalDependencies: ExternalDependencies, resolveControllers?: boolean): void;
38
40
  registerRoutes(app: FastifyInstance<any, any, any, any>): void;
@@ -12,6 +12,8 @@ export class DIContext {
12
12
  sseControllerNames;
13
13
  // Dual-mode controller dependency names (resolved from container to preserve singletons)
14
14
  dualModeControllerNames;
15
+ // ApiContract controller dependency names (resolved from container to preserve singletons)
16
+ apiControllerNames;
15
17
  appConfig;
16
18
  constructor(diContainer, options, appConfig, awilixManager) {
17
19
  this.options = options;
@@ -29,6 +31,31 @@ export class DIContext {
29
31
  this.controllerResolvers = [];
30
32
  this.sseControllerNames = [];
31
33
  this.dualModeControllerNames = [];
34
+ this.apiControllerNames = [];
35
+ }
36
+ registerControllers(
37
+ // biome-ignore lint/suspicious/noExplicitAny: controller resolver properties are duck-typed
38
+ controllers, targetDiConfig) {
39
+ for (const [name, resolver] of Object.entries(controllers)) {
40
+ if (resolver.isDualModeController) {
41
+ this.dualModeControllerNames.push(name);
42
+ // @ts-expect-error we can't really ensure type-safety here
43
+ targetDiConfig[name] = resolver;
44
+ }
45
+ else if (resolver.isSSEController) {
46
+ this.sseControllerNames.push(name);
47
+ // @ts-expect-error we can't really ensure type-safety here
48
+ targetDiConfig[name] = resolver;
49
+ }
50
+ else if (resolver.isApiController) {
51
+ this.apiControllerNames.push(name);
52
+ // @ts-expect-error we can't really ensure type-safety here
53
+ targetDiConfig[name] = resolver;
54
+ }
55
+ else {
56
+ this.controllerResolvers.push(resolver);
57
+ }
58
+ }
32
59
  }
33
60
  registerModule(module, targetDiConfig, externalDependencies, resolveControllers, isPrimaryModule) {
34
61
  const resolvedDIConfig = module.resolveDependencies(this.options, externalDependencies);
@@ -41,26 +68,7 @@ export class DIContext {
41
68
  }
42
69
  if (isPrimaryModule && resolveControllers) {
43
70
  const controllers = module.resolveControllers(this.options);
44
- for (const [name, resolver] of Object.entries(controllers)) {
45
- // @ts-expect-error isDualModeController is a custom property on the resolver
46
- if (resolver.isDualModeController) {
47
- // Dual-mode controller: register in DI container and track name for route registration
48
- this.dualModeControllerNames.push(name);
49
- // @ts-expect-error we can't really ensure type-safety here
50
- targetDiConfig[name] = resolver;
51
- // @ts-expect-error isSSEController is a custom property on the resolver
52
- }
53
- else if (resolver.isSSEController) {
54
- // SSE controller: register in DI container and track name for route registration
55
- this.sseControllerNames.push(name);
56
- // @ts-expect-error we can't really ensure type-safety here
57
- targetDiConfig[name] = resolver;
58
- }
59
- else {
60
- // REST controller: add resolver for route registration
61
- this.controllerResolvers.push(resolver);
62
- }
63
- }
71
+ this.registerControllers(controllers, targetDiConfig);
64
72
  }
65
73
  }
66
74
  registerDependencies(params, externalDependencies, resolveControllers = true) {
@@ -101,6 +109,12 @@ export class DIContext {
101
109
  app.route(route);
102
110
  }
103
111
  }
112
+ for (const controllerName of this.apiControllerNames) {
113
+ const controller = this.diContainer.resolve(controllerName);
114
+ for (const route of controller.routes) {
115
+ app.route(route);
116
+ }
117
+ }
104
118
  }
105
119
  /**
106
120
  * Check if any SSE controllers are registered.
@@ -1 +1 @@
1
- {"version":3,"file":"DIContext.js","sourceRoot":"","sources":["../../lib/DIContext.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AAGpC,OAAO,EAAE,iCAAiC,EAAsB,MAAM,kBAAkB,CAAA;AAGxF,OAAO,EACL,iBAAiB,GAGlB,MAAM,mBAAmB,CAAA;AAwB1B,MAAM,OAAO,SAAS;IAKH,OAAO,CAA4B;IACpC,aAAa,CAAe;IAC5B,WAAW,CAA+B;IAC1D,8EAA8E;IAC7D,mBAAmB,CAAiB;IACrD,mFAAmF;IAClE,kBAAkB,CAAU;IAC7C,yFAAyF;IACxE,uBAAuB,CAAU;IACjC,SAAS,CAAQ;IAElC,YACE,WAA0C,EAC1C,OAAmC,EACnC,SAAiB,EACjB,aAA6B;QAE7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,IAAI,CAAC,aAAa;YAChB,aAAa;gBACb,IAAI,aAAa,CAAC;oBAChB,YAAY,EAAE,IAAI;oBAClB,SAAS,EAAE,IAAI;oBACf,WAAW;oBACX,WAAW,EAAE,IAAI;oBACjB,qBAAqB,EAAE,IAAI;iBAC5B,CAAC,CAAA;QACJ,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAA;QAC7B,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAA;QAC5B,IAAI,CAAC,uBAAuB,GAAG,EAAE,CAAA;IACnC,CAAC;IAEO,cAAc,CACpB,MAAqD,EACrD,cAAqD,EACrD,oBAA0C,EAC1C,kBAA2B,EAC3B,eAAwB;QAExB,MAAM,gBAAgB,GAAG,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;QAEvF,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;YACnC,2DAA2D;YAC3D,IAAI,eAAe,IAAI,gBAAgB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;gBACpD,2DAA2D;gBAC3D,cAAc,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAA;YAC7C,CAAC;QACH,CAAC;QAED,IAAI,eAAe,IAAI,kBAAkB,EAAE,CAAC;YAC1C,MAAM,WAAW,GAAG,MAAM,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAE3D,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC3D,6EAA6E;gBAC7E,IAAI,QAAQ,CAAC,oBAAoB,EAAE,CAAC;oBAClC,uFAAuF;oBACvF,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;oBACvC,2DAA2D;oBAC3D,cAAc,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAA;oBAC/B,wEAAwE;gBAC1E,CAAC;qBAAM,IAAI,QAAQ,CAAC,eAAe,EAAE,CAAC;oBACpC,iFAAiF;oBACjF,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;oBAClC,2DAA2D;oBAC3D,cAAc,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAA;gBACjC,CAAC;qBAAM,CAAC;oBACN,uDAAuD;oBACvD,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAA6B,CAAC,CAAA;gBAC9D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,oBAAoB,CAClB,MAA8E,EAC9E,oBAA0C,EAC1C,kBAAkB,GAAG,IAAI;QAEzB,MAAM,eAAe,GAAG,iCAAiC,CACvD,IAAI,CAAC,SAAS,EACd,MAAM,CAAC,kBAAkB,IAAI,QAAQ,EACrC,MAAM,CAAC,eAAe,EACtB,MAAM,CAAC,mBAAmB,IAAI,EAAE,CACjC,CAAA;QACD,MAAM,cAAc,GAA0C,EAAE,CAAA;QAEhE,KAAK,MAAM,aAAa,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC3C,IAAI,CAAC,cAAc,CACjB,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,IAAI,CACL,CAAA;QACH,CAAC;QAED,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC5B,KAAK,MAAM,eAAe,IAAI,MAAM,CAAC,gBAAgB,EAAE,CAAC;gBACtD,IAAI,CAAC,cAAc,CACjB,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,KAAK,CACN,CAAA;YACH,CAAC;QACH,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAmD,CAAC,CAAA;QAE9E,8BAA8B;QAC9B,0CAA0C;QAC1C,KAAK,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;YAChF,MAAM,eAAe,GAAG,EAAE,GAAI,gBAAsC,EAAE,CAAA;YAEtE,2CAA2C;YAC3C,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,CAAC,eAAe,CAAC,aAAa,CAAC,CAAA;YACxE,mBAAmB;YACnB,IAAI,eAAe,CAAC,QAAQ,KAAK,gBAAgB,CAAC,QAAQ,EAAE,CAAC;gBAC3D,mBAAmB;gBACnB,eAAe,CAAC,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAA;YACtD,CAAC;YAED,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,aAAa,EAAE,eAAe,CAAC,CAAA;QAC3D,CAAC;IACH,CAAC;IAED,4FAA4F;IAC5F,cAAc,CAAC,GAAwC;QACrD,KAAK,MAAM,kBAAkB,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC1D,wEAAwE;YACxE,MAAM,UAAU,GAA4B,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACxF,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,EAAE,CAAA;YACvC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1C,mFAAmF;gBACnF,2EAA2E;gBAC3E,GAAG,CAAC,KAAK,CAAC,KAAkB,CAAC,CAAA;YAC/B,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,iBAAiB;QACf,OAAO,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAA;IAC3C,CAAC;IAED;;;OAGG;IACH,sBAAsB;QACpB,OAAO,IAAI,CAAC,uBAAuB,CAAC,MAAM,GAAG,CAAC,CAAA;IAChD,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,iBAAiB;IACf,iFAAiF;IACjF,GAAwC,EACxC,OAAkC;QAElC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;YAC9B,OAAM;QACR,CAAC;QAED,KAAK,MAAM,cAAc,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACrD,uDAAuD;YACvD,MAAM,aAAa,GACjB,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;YAC1C,MAAM,SAAS,GAAG,aAAa,CAAC,cAAc,EAAE,CAAA;YAEhD,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnD,MAAM,KAAK,GAAG,iBAAiB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;gBAC3D,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;gBACzC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,sBAAsB;IACpB,iFAAiF;IACjF,GAAwC,EACxC,OAAuC;QAEvC,IAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,EAAE,CAAC;YACnC,OAAM;QACR,CAAC;QAED,KAAK,MAAM,cAAc,IAAI,IAAI,CAAC,uBAAuB,EAAE,CAAC;YAC1D,uDAAuD;YACvD,MAAM,kBAAkB,GAEpB,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;YAC5C,MAAM,cAAc,GAAG,kBAAkB,CAAC,mBAAmB,EAAE,CAAA;YAE/D,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;gBACxD,MAAM,KAAK,GAAG,iBAAiB,CAAC,kBAAkB,EAAE,WAAW,CAAC,CAAA;gBAChE,IAAI,CAAC,yBAAyB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;gBAC9C,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAClB,CAAC;QACH,CAAC;IACH,CAAC;IAEO,yBAAyB,CAC/B,KAAmB,EACnB,OAAuC;QAEvC,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC;YACxB,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;QAClD,CAAC;QACD,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;QAC/C,CAAC;QACD,0EAA0E;QAC1E,IAAI,OAAO,EAAE,iBAAiB,KAAK,SAAS,IAAI,OAAO,EAAE,UAAU,KAAK,SAAS,EAAE,CAAC;YAClF,2EAA2E;YAC3E,MAAM,eAAe,GAAG,KAAwC,CAAA;YAChE,eAAe,CAAC,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,IAAI,EAAE,EAAE;gBAC3D,GAAG,EAAE;oBACH,GAAG,CAAC,OAAO,CAAC,iBAAiB,KAAK,SAAS,IAAI;wBAC7C,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;qBAC7C,CAAC;oBACF,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;iBAC5E;aACF,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAEO,oBAAoB,CAAC,KAAmB,EAAE,OAAkC;QAClF,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC;YACxB,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAA;QAClD,CAAC;QACD,IAAI,OAAO,EAAE,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;QAC/C,CAAC;QACD,6DAA6D;QAC7D,IAAI,OAAO,EAAE,iBAAiB,KAAK,SAAS,IAAI,OAAO,EAAE,UAAU,KAAK,SAAS,EAAE,CAAC;YAClF,2EAA2E;YAC3E,MAAM,eAAe,GAAG,KAAwC,CAAA;YAChE,eAAe,CAAC,MAAM,GAAG,KAAK,CAAC,eAAe,CAAC,MAAM,IAAI,EAAE,EAAE;gBAC3D,GAAG,EAAE;oBACH,GAAG,CAAC,OAAO,CAAC,iBAAiB,KAAK,SAAS,IAAI;wBAC7C,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;qBAC7C,CAAC;oBACF,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;iBAC5E;aACF,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAEO,gBAAgB,CACtB,KAAmB,EACnB,gBAA4C;QAE5C,MAAM,kBAAkB,GAAG,KAAK,CAAC,UAAU,CAAA;QAC3C,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,KAAK,CAAC,UAAU,GAAG,gBAAgB,CAAA;YACnC,OAAM;QACR,CAAC;QACD,2EAA2E;QAC3E,MAAM,QAAQ,GAAU,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC;YACvD,CAAC,CAAC,kBAAkB;YACpB,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAA;QACxB,2EAA2E;QAC3E,MAAM,cAAc,GAAU,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC;YAC3D,CAAC,CAAC,gBAAgB;YAClB,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAA;QACtB,KAAK,CAAC,UAAU,GAAG,CAAC,GAAG,cAAc,EAAE,GAAG,QAAQ,CAAC,CAAA;IACrD,CAAC;IAEO,cAAc,CACpB,KAAmB,EACnB,SAA6D;QAE7D,2EAA2E;QAC3E,MAAM,eAAe,GAAG,KAAwC,CAAA;QAChE,eAAe,CAAC,MAAM,GAAG;YACvB,GAAG,CAAC,eAAe,CAAC,MAAM,IAAI,EAAE,CAAC;YACjC,SAAS;SACV,CAAA;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,MAAM,IAAI,CAAC,aAAa,CAAC,cAAc,EAAE,CAAA;QACzC,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAA;IAClC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,aAAa,CAAC,WAAW,EAAE,CAAA;IACxC,CAAC;CACF"}
1
+ {"version":3,"file":"DIContext.js","sourceRoot":"","sources":["../../lib/DIContext.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { RouteOptions } from 'fastify';
2
+ /**
3
+ * Abstract base class for controllers that use the `ApiContract` API.
4
+ *
5
+ * Concrete controllers declare a `routes` property built with `buildApiRoute()`.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * class UserController extends AbstractApiController {
10
+ * readonly routes = [
11
+ * buildApiRoute(getUser, async (req) => ({ status: 200, body: { id: req.params.id } })),
12
+ * buildApiRoute(streamUpdates, async (_req, sse) => { sse.start('keepAlive') }),
13
+ * ]
14
+ * }
15
+ * ```
16
+ */
17
+ export declare abstract class AbstractApiController {
18
+ abstract readonly routes: RouteOptions[];
19
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Abstract base class for controllers that use the `ApiContract` API.
3
+ *
4
+ * Concrete controllers declare a `routes` property built with `buildApiRoute()`.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * class UserController extends AbstractApiController {
9
+ * readonly routes = [
10
+ * buildApiRoute(getUser, async (req) => ({ status: 200, body: { id: req.params.id } })),
11
+ * buildApiRoute(streamUpdates, async (_req, sse) => { sse.start('keepAlive') }),
12
+ * ]
13
+ * }
14
+ * ```
15
+ */
16
+ export class AbstractApiController {
17
+ }
18
+ //# sourceMappingURL=AbstractApiController.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbstractApiController.js","sourceRoot":"","sources":["../../../lib/api-contracts/AbstractApiController.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAgB,qBAAqB;CAE1C"}
@@ -0,0 +1,116 @@
1
+ import type { ApiContract, ContractNoBody, ContractResponseMode, InferSseSuccessResponses, PayloadApiContract } from '@lokalise/api-contracts';
2
+ import type { FastifyRequest, RouteOptions } from 'fastify';
3
+ import type { z } from 'zod/v4';
4
+ import type { DualModeType } from '../dualmode/dualModeTypes.ts';
5
+ import type { FastifySSERouteOptions, SSEContext, SSEHandlerResult, SyncModeReply } from '../routes/fastifyRouteTypes.ts';
6
+ type NonSseBodyEntry<T> = T extends undefined ? never : T extends {
7
+ _tag: 'SseResponse';
8
+ } ? never : T extends {
9
+ _tag: 'BlobResponse';
10
+ } ? Blob : T extends {
11
+ _tag: 'TextResponse';
12
+ } ? string : T extends {
13
+ _tag: 'AnyOfResponses';
14
+ responses: Array<infer R>;
15
+ } ? NonSseBodyEntry<R> : T extends z.ZodType ? z.output<T> : undefined;
16
+ /**
17
+ * Discriminated union of `{ status, body }` pairs for all non-SSE responses in a contract.
18
+ *
19
+ * Allows non-SSE handlers to return a specific status code and body together without
20
+ * calling `reply.code()` separately.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * async (request) => {
25
+ * if (!valid) return { status: 400, body: { error: 'Bad Request' } }
26
+ * return { id: request.params.id }
27
+ * }
28
+ * ```
29
+ */
30
+ export type InferApiStatusResponse<Contract extends ApiContract> = {
31
+ [K in keyof Contract['responsesByStatusCode']]: NonSseBodyEntry<Contract['responsesByStatusCode'][K]> extends never ? never : {
32
+ status: K;
33
+ body: NonSseBodyEntry<Contract['responsesByStatusCode'][K]>;
34
+ };
35
+ }[keyof Contract['responsesByStatusCode']];
36
+ type InferOptSchema<T, Fallback = unknown> = NonNullable<T> extends z.ZodType ? z.output<NonNullable<T>> : Fallback;
37
+ type InferApiBodyType<Contract extends ApiContract> = Contract extends PayloadApiContract ? Contract['requestBodySchema'] extends typeof ContractNoBody ? undefined : NonNullable<Contract['requestBodySchema']> extends z.ZodType ? z.output<NonNullable<Contract['requestBodySchema']>> : undefined : undefined;
38
+ /**
39
+ * Infer the FastifyRequest type from an ApiContract.
40
+ *
41
+ * Provides properly typed params, querystring, headers, and body.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const handler = async (request: InferApiRequest<typeof myContract>) => {
46
+ * request.params.userId // typed
47
+ * request.body.name // typed
48
+ * }
49
+ * ```
50
+ */
51
+ export type InferApiRequest<Contract extends ApiContract> = FastifyRequest<{
52
+ Params: InferOptSchema<Contract['requestPathParamsSchema']>;
53
+ Querystring: InferOptSchema<Contract['requestQuerySchema']>;
54
+ Headers: InferOptSchema<Contract['requestHeaderSchema']>;
55
+ Body: InferApiBodyType<Contract>;
56
+ }>;
57
+ /**
58
+ * Handler for non-SSE responses from an ApiContract.
59
+ *
60
+ * Always return `{ status, body }` — the framework validates the body against the
61
+ * contract's schema for that status code and sends it.
62
+ *
63
+ * Use `reply.header()` to set response headers when needed.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * async (request) => ({ status: 200, body: { id: request.params.userId } })
68
+ * ```
69
+ *
70
+ * @example With multiple status codes
71
+ * ```typescript
72
+ * async (request) => {
73
+ * if (!valid) return { status: 400, body: { error: 'Bad Request' } }
74
+ * return { status: 200, body: { id: request.params.userId } }
75
+ * }
76
+ * ```
77
+ */
78
+ export type ApiNonSseHandler<Contract extends ApiContract> = (request: InferApiRequest<Contract>, reply: SyncModeReply) => InferApiStatusResponse<Contract> | Promise<InferApiStatusResponse<Contract>>;
79
+ /**
80
+ * Handler for SSE responses from an ApiContract.
81
+ *
82
+ * Call `sse.start(mode)` to begin streaming or `sse.respond(code, body)` for
83
+ * early HTTP returns before streaming starts.
84
+ */
85
+ export type ApiSseHandler<Contract extends ApiContract> = (request: InferApiRequest<Contract>, sse: SSEContext<InferSseSuccessResponses<Contract['responsesByStatusCode']>>) => SSEHandlerResult | Promise<SSEHandlerResult>;
86
+ /**
87
+ * Infer the handler shape based on the contract's response mode:
88
+ * - `'non-sse'` — bare `ApiNonSseHandler` function
89
+ * - `'sse'` — bare `ApiSseHandler` function
90
+ * - `'dual'` — `{ nonSse, sse }` object, branched by `Accept` header
91
+ */
92
+ export type InferApiHandler<Contract extends ApiContract> = [
93
+ ContractResponseMode<Contract['responsesByStatusCode']>
94
+ ] extends ['dual'] ? {
95
+ nonSse: ApiNonSseHandler<Contract>;
96
+ sse: ApiSseHandler<Contract>;
97
+ } : [ContractResponseMode<Contract['responsesByStatusCode']>] extends ['sse'] ? ApiSseHandler<Contract> : ApiNonSseHandler<Contract>;
98
+ /**
99
+ * Options for configuring an ApiContract route.
100
+ *
101
+ * Extends Fastify's `RouteOptions` minus the fields the contract provides
102
+ * (`method`, `url`, `schema`, `handler`, `sse`), so any Fastify hook or config
103
+ * (`preHandler`, `onRequest`, `config`, `bodyLimit`, etc.) can be passed directly.
104
+ *
105
+ * SSE lifecycle options (`onConnect`, `onClose`, `onReconnect`) are only
106
+ * relevant for SSE and dual-mode contracts and are ignored for non-SSE routes.
107
+ */
108
+ export type ApiRouteOptions = Omit<RouteOptions, 'method' | 'url' | 'schema' | 'handler' | 'sse'> & Omit<FastifySSERouteOptions, 'preHandler'> & {
109
+ /**
110
+ * Default response mode for dual-mode routes when the `Accept` header
111
+ * does not express a preference.
112
+ * @default 'json'
113
+ */
114
+ defaultMode?: DualModeType;
115
+ };
116
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=apiHandlerTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiHandlerTypes.js","sourceRoot":"","sources":["../../../lib/api-contracts/apiHandlerTypes.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { type ApiContract } from '@lokalise/api-contracts';
2
+ import type { RouteOptions } from 'fastify';
3
+ import type { ApiRouteOptions, InferApiHandler } from './apiHandlerTypes.ts';
4
+ /**
5
+ * Build a Fastify `RouteOptions` object from an `ApiContract` + handler.
6
+ *
7
+ * @returns Fastify `RouteOptions` ready to pass to `app.route()`
8
+ */
9
+ export declare function buildApiRoute<Contract extends ApiContract>(contract: Contract, handler: InferApiHandler<Contract>, options?: ApiRouteOptions): RouteOptions;
@@ -0,0 +1,352 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { ContractNoBody, getSseSchemaByEventName, hasAnySuccessSseResponse, isAnyOfResponses, isBlobResponse, isSseResponse, isTextResponse, mapApiContractToPath, SUCCESSFUL_HTTP_STATUS_CODES, } from '@lokalise/api-contracts';
3
+ import { InternalError } from '@lokalise/node-core';
4
+ import { determineMode, hasHttpStatusCode, isErrorLike } from "../routes/fastifyRouteUtils.js";
5
+ function isSuccessResponseDual(value) {
6
+ if (value === ContractNoBody || isTextResponse(value) || isBlobResponse(value))
7
+ return true;
8
+ if (!isSseResponse(value) && !isAnyOfResponses(value))
9
+ return true;
10
+ if (isAnyOfResponses(value)) {
11
+ return value.responses.some((response) => !isSseResponse(response));
12
+ }
13
+ return false;
14
+ }
15
+ function getContractResponseMode(contract) {
16
+ if (!hasAnySuccessSseResponse(contract))
17
+ return 'non-sse';
18
+ for (const code of SUCCESSFUL_HTTP_STATUS_CODES) {
19
+ const value = contract.responsesByStatusCode[code];
20
+ if (value && isSuccessResponseDual(value))
21
+ return 'dual';
22
+ }
23
+ return 'sse';
24
+ }
25
+ function buildSSERouteConfig(options) {
26
+ if (!options?.serializer && options?.heartbeatInterval === undefined)
27
+ return true;
28
+ const sseConfig = {};
29
+ if (options.serializer)
30
+ sseConfig.serializer = options.serializer;
31
+ if (options.heartbeatInterval !== undefined)
32
+ sseConfig.heartbeatInterval = options.heartbeatInterval;
33
+ return sseConfig;
34
+ }
35
+ // ============================================================================
36
+ // Internal Helpers — Sync Route
37
+ // ============================================================================
38
+ function getSchemaForStatusCode(contract, status) {
39
+ const entry = contract.responsesByStatusCode[status];
40
+ if (!entry ||
41
+ entry === ContractNoBody ||
42
+ isSseResponse(entry) ||
43
+ isTextResponse(entry) ||
44
+ isBlobResponse(entry)) {
45
+ return null;
46
+ }
47
+ if (isAnyOfResponses(entry)) {
48
+ for (const anyResponse of entry.responses) {
49
+ if (isSseResponse(anyResponse) ||
50
+ isTextResponse(anyResponse) ||
51
+ isBlobResponse(anyResponse)) {
52
+ continue;
53
+ }
54
+ return anyResponse;
55
+ }
56
+ return null;
57
+ }
58
+ else {
59
+ return entry;
60
+ }
61
+ }
62
+ function validateApiResponseHeaders(contract, reply) {
63
+ const schema = contract.responseHeaderSchema;
64
+ if (!schema) {
65
+ return;
66
+ }
67
+ const result = schema.safeParse(reply.getHeaders());
68
+ if (!result.success) {
69
+ throw new InternalError({
70
+ message: 'Internal Server Error',
71
+ errorCode: 'RESPONSE_HEADERS_VALIDATION_FAILED',
72
+ details: { validationError: result.error.message },
73
+ });
74
+ }
75
+ }
76
+ async function handleApiSyncRoute(contract,
77
+ // biome-ignore lint/suspicious/noExplicitAny: Handler types are validated by InferApiHandler at the call site
78
+ handler,
79
+ // biome-ignore lint/suspicious/noExplicitAny: Request types are validated by Fastify schema
80
+ request, reply) {
81
+ const { status, body } = await handler(request, reply);
82
+ if (reply.sent) {
83
+ request.log.warn({
84
+ msg: 'Sync handler sent response directly, bypassing response validation',
85
+ tag: 'response_sent_directly',
86
+ method: request.method,
87
+ url: request.url,
88
+ });
89
+ return;
90
+ }
91
+ try {
92
+ const schema = getSchemaForStatusCode(contract, status);
93
+ if (schema) {
94
+ const result = schema.safeParse(body);
95
+ if (!result.success) {
96
+ throw new InternalError({
97
+ message: 'Internal Server Error',
98
+ errorCode: 'RESPONSE_VALIDATION_FAILED',
99
+ details: { validationError: result.error.message },
100
+ });
101
+ }
102
+ }
103
+ }
104
+ catch (err) {
105
+ reply.code(500);
106
+ throw err;
107
+ }
108
+ validateApiResponseHeaders(contract, reply);
109
+ if (!reply.hasHeader('content-type')) {
110
+ reply.type('application/json');
111
+ }
112
+ return reply.code(status).send(body);
113
+ }
114
+ // ============================================================================
115
+ // Internal Helpers — SSE Route (no controller, uses reply.sse directly)
116
+ // ============================================================================
117
+ function buildApiSSEContext(
118
+ // biome-ignore lint/suspicious/noExplicitAny: Request types are validated by Fastify schema
119
+ request, reply, eventSchemas, options) {
120
+ let started = false;
121
+ let responseData;
122
+ const sseReply = reply;
123
+ const sseContext = {
124
+ start: (mode, startOptions) => {
125
+ started = true;
126
+ if (mode === 'keepAlive') {
127
+ sseReply.sse.keepAlive();
128
+ }
129
+ // sendHeaders() calls writeHead(200) but only queues headers in the buffer.
130
+ // flushHeaders() forces them onto the wire so the client's fetch() returns.
131
+ sseReply.sse.sendHeaders();
132
+ reply.raw.flushHeaders();
133
+ const connectionId = randomUUID();
134
+ const send = async (eventName, data, sendOptions) => {
135
+ const schema = eventSchemas[eventName];
136
+ if (schema) {
137
+ const result = schema.safeParse(data);
138
+ if (!result.success) {
139
+ throw new InternalError({
140
+ message: `SSE event validation failed for event "${eventName}": ${result.error.message}`,
141
+ errorCode: 'RESPONSE_VALIDATION_FAILED',
142
+ });
143
+ }
144
+ }
145
+ try {
146
+ await sseReply.sse.send({
147
+ event: eventName,
148
+ data,
149
+ id: sendOptions?.id,
150
+ retry: sendOptions?.retry,
151
+ });
152
+ return true;
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ };
158
+ const session = {
159
+ id: connectionId,
160
+ request,
161
+ reply,
162
+ context: (startOptions?.context ?? {}),
163
+ connectedAt: new Date(),
164
+ // biome-ignore lint/suspicious/noExplicitAny: SSEEventSender generic is satisfied at handler call site
165
+ send: send,
166
+ isConnected: () => sseReply.sse.isConnected,
167
+ getStream: () => sseReply.sse.stream(),
168
+ sendStream: async (messages) => {
169
+ for await (const message of messages) {
170
+ await send(message.event, message.data, { id: message.id, retry: message.retry });
171
+ }
172
+ },
173
+ rooms: { join: () => { }, leave: () => { } },
174
+ eventSchemas,
175
+ };
176
+ if (options?.onConnect) {
177
+ void Promise.resolve(options.onConnect(session)).catch(() => { });
178
+ }
179
+ if (options?.onClose) {
180
+ const onClose = options.onClose;
181
+ sseReply.sse.onClose(() => {
182
+ void Promise.resolve(onClose(session, 'client')).catch(() => { });
183
+ });
184
+ }
185
+ if (options?.onReconnect && sseReply.sse.lastEventId) {
186
+ const onReconnect = options.onReconnect;
187
+ const lastEventId = sseReply.sse.lastEventId;
188
+ void sseReply.sse.replay(async () => {
189
+ const replay = await onReconnect(session, lastEventId);
190
+ if (replay) {
191
+ for await (const msg of replay) {
192
+ await sseReply.sse.send(msg);
193
+ }
194
+ }
195
+ });
196
+ }
197
+ return session;
198
+ },
199
+ respond: ((code, body) => {
200
+ if (started) {
201
+ throw new Error('Cannot call sse.respond() after sse.start() — the SSE stream is already open.');
202
+ }
203
+ responseData = { code, body };
204
+ return { _type: 'respond', code, body };
205
+ // biome-ignore lint/suspicious/noExplicitAny: respond typing is enforced by contract at call site
206
+ }),
207
+ sendHeaders: () => {
208
+ sseReply.sse.sendHeaders();
209
+ },
210
+ reply,
211
+ };
212
+ return {
213
+ sseContext,
214
+ isStarted: () => started,
215
+ hasResponse: () => responseData !== undefined,
216
+ getResponseData: () => responseData,
217
+ };
218
+ }
219
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Core SSE handler coordinates context, error handling, and lifecycle
220
+ async function handleApiSseRoute(
221
+ // biome-ignore lint/suspicious/noExplicitAny: SSE handler types are validated by InferApiHandler at call site
222
+ sseHandler, eventSchemas, options,
223
+ // biome-ignore lint/suspicious/noExplicitAny: Request types are validated by Fastify schema
224
+ request, reply) {
225
+ const { sseContext, isStarted, hasResponse, getResponseData } = buildApiSSEContext(request, reply, eventSchemas, options);
226
+ try {
227
+ await sseHandler(request, sseContext);
228
+ if (!isStarted() && !hasResponse()) {
229
+ throw new Error('SSE handler must either send a response (sse.respond()) ' +
230
+ 'or start streaming (sse.start()). Handler returned without doing either.');
231
+ }
232
+ const responseData = getResponseData();
233
+ if (responseData) {
234
+ // Early HTTP response (sse.respond() was called before streaming)
235
+ reply.removeHeader('cache-control');
236
+ reply.removeHeader('x-accel-buffering');
237
+ reply.type('application/json').code(responseData.code).send(responseData.body);
238
+ }
239
+ // If started, @fastify/sse manages the rest of the connection lifecycle
240
+ }
241
+ catch (err) {
242
+ if (isStarted()) {
243
+ // Headers already sent — can't change status code; try to send error event
244
+ const sseReply = reply;
245
+ if (sseReply.sse.isConnected) {
246
+ try {
247
+ await sseReply.sse.send({
248
+ event: 'error',
249
+ data: { message: isErrorLike(err) ? err.message : 'Internal Server Error' },
250
+ });
251
+ }
252
+ catch {
253
+ // Ignore send failures during error handling
254
+ }
255
+ }
256
+ throw err;
257
+ }
258
+ // Streaming not started — send HTTP error response
259
+ const message = isErrorLike(err) ? err.message : 'Internal Server Error';
260
+ const statusCode = hasHttpStatusCode(err) ? err.httpStatusCode : 500;
261
+ const statusText = statusCode >= 500 ? 'Internal Server Error' : 'Error';
262
+ reply.code(statusCode).type('application/json').send({ statusCode, error: statusText, message });
263
+ }
264
+ }
265
+ // ============================================================================
266
+ // Internal Helpers — Schema
267
+ // ============================================================================
268
+ function buildResponseSchemas(contract) {
269
+ return Object.keys(contract.responsesByStatusCode).reduce((acc, statusCode) => {
270
+ const schema = getSchemaForStatusCode(contract, Number(statusCode));
271
+ if (schema) {
272
+ acc[Number(statusCode)] = schema;
273
+ }
274
+ return acc;
275
+ }, {});
276
+ }
277
+ function buildBaseSchema(contract) {
278
+ const schema = {};
279
+ if (contract.requestPathParamsSchema)
280
+ schema.params = contract.requestPathParamsSchema;
281
+ if (contract.requestQuerySchema)
282
+ schema.querystring = contract.requestQuerySchema;
283
+ if (contract.requestHeaderSchema)
284
+ schema.headers = contract.requestHeaderSchema;
285
+ if (contract.requestBodySchema !== undefined && contract.requestBodySchema !== ContractNoBody) {
286
+ schema.body = contract.requestBodySchema;
287
+ }
288
+ schema.response = buildResponseSchemas(contract);
289
+ return schema;
290
+ }
291
+ // ============================================================================
292
+ // Public API
293
+ // ============================================================================
294
+ /**
295
+ * Build a Fastify `RouteOptions` object from an `ApiContract` + handler.
296
+ *
297
+ * @returns Fastify `RouteOptions` ready to pass to `app.route()`
298
+ */
299
+ export function buildApiRoute(contract, handler, options) {
300
+ // Separate SSE-specific options (not in Fastify RouteOptions) from passthrough options
301
+ const { defaultMode, contractMetadataToRouteMapper, serializer: _serializer, heartbeatInterval: _heartbeatInterval, onConnect: _onConnect, onClose: _onClose, onReconnect: _onReconnect, logger: _logger, ...fastifyOptions } = options ?? {};
302
+ const url = mapApiContractToPath(contract);
303
+ const mode = getContractResponseMode(contract);
304
+ const eventSchemas = getSseSchemaByEventName(contract) ?? {};
305
+ const baseSchema = buildBaseSchema(contract);
306
+ const contractMetadata = contractMetadataToRouteMapper?.(contract.metadata) ?? {};
307
+ if (mode === 'non-sse') {
308
+ // biome-ignore lint/suspicious/noExplicitAny: handler shape validated by InferApiHandler at call site
309
+ const syncHandler = handler;
310
+ return {
311
+ ...fastifyOptions,
312
+ ...contractMetadata,
313
+ method: contract.method,
314
+ url,
315
+ schema: baseSchema,
316
+ handler: async (request, reply) => handleApiSyncRoute(contract, syncHandler, request, reply),
317
+ };
318
+ }
319
+ if (mode === 'dual') {
320
+ const resolvedDefaultMode = defaultMode ?? 'json';
321
+ // biome-ignore lint/suspicious/noExplicitAny: handler shape validated by InferApiHandler at call site
322
+ const dualHandlers = handler;
323
+ return {
324
+ ...fastifyOptions,
325
+ ...contractMetadata,
326
+ method: contract.method,
327
+ url,
328
+ sse: buildSSERouteConfig(options),
329
+ schema: baseSchema,
330
+ handler: (request, reply) => {
331
+ const responseMode = determineMode(request.headers.accept, resolvedDefaultMode);
332
+ if (responseMode === 'json') {
333
+ return handleApiSyncRoute(contract, dualHandlers.nonSse, request, reply);
334
+ }
335
+ return handleApiSseRoute(dualHandlers.sse, eventSchemas, options, request, reply);
336
+ },
337
+ };
338
+ }
339
+ // SSE-only
340
+ // biome-ignore lint/suspicious/noExplicitAny: handler shape validated by InferApiHandler at call site
341
+ const sseHandler = handler;
342
+ return {
343
+ ...fastifyOptions,
344
+ ...contractMetadata,
345
+ method: contract.method,
346
+ url,
347
+ sse: buildSSERouteConfig(options),
348
+ schema: baseSchema,
349
+ handler: async (request, reply) => handleApiSseRoute(sseHandler, eventSchemas, options, request, reply),
350
+ };
351
+ }
352
+ //# sourceMappingURL=apiRouteBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiRouteBuilder.js","sourceRoot":"","sources":["../../../lib/api-contracts/apiRouteBuilder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { BuildResolver, BuildResolverOptions, DisposableResolver } from 'awilix';
2
+ declare module 'awilix' {
3
+ interface ResolverOptions<T> {
4
+ /** Marks a resolver as an api controller (new ApiContract-based). */
5
+ isApiController?: boolean;
6
+ }
7
+ }
8
+ /**
9
+ * Register an `AbstractApiController` subclass with the awilix DI container.
10
+ *
11
+ * The returned resolver does **not** set `isSSEController` or `isDualModeController`,
12
+ * so `DIContext` reads its `routes` property automatically during `registerRoutes()`.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // In a module's resolveControllers():
17
+ * return {
18
+ * userController: asApiControllerClass(UserController),
19
+ * }
20
+ * ```
21
+ */
22
+ export declare function asApiControllerClass<T = object>(Type: {
23
+ prototype: T;
24
+ }, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
@@ -0,0 +1,27 @@
1
+ import { asFunction } from 'awilix';
2
+ /**
3
+ * Register an `AbstractApiController` subclass with the awilix DI container.
4
+ *
5
+ * The returned resolver does **not** set `isSSEController` or `isDualModeController`,
6
+ * so `DIContext` reads its `routes` property automatically during `registerRoutes()`.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * // In a module's resolveControllers():
11
+ * return {
12
+ * userController: asApiControllerClass(UserController),
13
+ * }
14
+ * ```
15
+ */
16
+ export function asApiControllerClass(Type, opts) {
17
+ const Ctor = Type;
18
+ return asFunction(
19
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic constructor invocation with cradle proxy
20
+ (cradle) => new Ctor(cradle), {
21
+ public: false,
22
+ isApiController: true,
23
+ ...opts,
24
+ lifetime: 'SINGLETON',
25
+ });
26
+ }
27
+ //# sourceMappingURL=asApiControllerClass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asApiControllerClass.js","sourceRoot":"","sources":["../../../lib/api-contracts/asApiControllerClass.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AASnC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAsB,EACtB,IAA8B;IAE9B,MAAM,IAAI,GAAG,IAAiC,CAAA;IAC9C,OAAO,UAAU;IACf,+FAA+F;IAC/F,CAAC,MAAW,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,EACjC;QACE,MAAM,EAAE,KAAK;QACb,eAAe,EAAE,IAAI;QACrB,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { AbstractApiController } from './AbstractApiController.ts';
2
+ export type { ApiNonSseHandler, ApiRouteOptions, ApiSseHandler, InferApiHandler, InferApiRequest, InferApiStatusResponse, } from './apiHandlerTypes.ts';
3
+ export { buildApiRoute } from './apiRouteBuilder.ts';
4
+ export { asApiControllerClass } from './asApiControllerClass.ts';
@@ -0,0 +1,4 @@
1
+ export { AbstractApiController } from "./AbstractApiController.js";
2
+ export { buildApiRoute } from "./apiRouteBuilder.js";
3
+ export { asApiControllerClass } from "./asApiControllerClass.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/api-contracts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;AASlE,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opinionated-machine",
3
- "version": "6.15.0",
3
+ "version": "6.16.1",
4
4
  "description": "Very opinionated DI framework for fastify, built on top of awilix ",
5
5
  "type": "module",
6
6
  "license": "MIT",