opinionated-machine 5.1.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +966 -2
  2. package/dist/index.d.ts +3 -2
  3. package/dist/index.js +5 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/AbstractController.d.ts +3 -3
  6. package/dist/lib/AbstractController.js.map +1 -1
  7. package/dist/lib/AbstractModule.d.ts +23 -1
  8. package/dist/lib/AbstractModule.js +25 -0
  9. package/dist/lib/AbstractModule.js.map +1 -1
  10. package/dist/lib/DIContext.d.ts +35 -0
  11. package/dist/lib/DIContext.js +108 -1
  12. package/dist/lib/DIContext.js.map +1 -1
  13. package/dist/lib/resolverFunctions.d.ts +34 -0
  14. package/dist/lib/resolverFunctions.js +47 -0
  15. package/dist/lib/resolverFunctions.js.map +1 -1
  16. package/dist/lib/sse/AbstractSSEController.d.ts +163 -0
  17. package/dist/lib/sse/AbstractSSEController.js +228 -0
  18. package/dist/lib/sse/AbstractSSEController.js.map +1 -0
  19. package/dist/lib/sse/SSEConnectionSpy.d.ts +55 -0
  20. package/dist/lib/sse/SSEConnectionSpy.js +136 -0
  21. package/dist/lib/sse/SSEConnectionSpy.js.map +1 -0
  22. package/dist/lib/sse/index.d.ts +5 -0
  23. package/dist/lib/sse/index.js +6 -0
  24. package/dist/lib/sse/index.js.map +1 -0
  25. package/dist/lib/sse/sseContracts.d.ts +132 -0
  26. package/dist/lib/sse/sseContracts.js +102 -0
  27. package/dist/lib/sse/sseContracts.js.map +1 -0
  28. package/dist/lib/sse/sseParser.d.ts +167 -0
  29. package/dist/lib/sse/sseParser.js +225 -0
  30. package/dist/lib/sse/sseParser.js.map +1 -0
  31. package/dist/lib/sse/sseRouteBuilder.d.ts +47 -0
  32. package/dist/lib/sse/sseRouteBuilder.js +114 -0
  33. package/dist/lib/sse/sseRouteBuilder.js.map +1 -0
  34. package/dist/lib/sse/sseTypes.d.ts +164 -0
  35. package/dist/lib/sse/sseTypes.js +2 -0
  36. package/dist/lib/sse/sseTypes.js.map +1 -0
  37. package/dist/lib/testing/index.d.ts +5 -0
  38. package/dist/lib/testing/index.js +5 -0
  39. package/dist/lib/testing/index.js.map +1 -0
  40. package/dist/lib/testing/sseHttpClient.d.ts +203 -0
  41. package/dist/lib/testing/sseHttpClient.js +262 -0
  42. package/dist/lib/testing/sseHttpClient.js.map +1 -0
  43. package/dist/lib/testing/sseInjectClient.d.ts +173 -0
  44. package/dist/lib/testing/sseInjectClient.js +234 -0
  45. package/dist/lib/testing/sseInjectClient.js.map +1 -0
  46. package/dist/lib/testing/sseInjectHelpers.d.ts +59 -0
  47. package/dist/lib/testing/sseInjectHelpers.js +117 -0
  48. package/dist/lib/testing/sseInjectHelpers.js.map +1 -0
  49. package/dist/lib/testing/sseTestServer.d.ts +93 -0
  50. package/dist/lib/testing/sseTestServer.js +108 -0
  51. package/dist/lib/testing/sseTestServer.js.map +1 -0
  52. package/dist/lib/testing/sseTestTypes.d.ts +106 -0
  53. package/dist/lib/testing/sseTestTypes.js +2 -0
  54. package/dist/lib/testing/sseTestTypes.js.map +1 -0
  55. package/package.json +13 -11
package/dist/index.d.ts CHANGED
@@ -4,5 +4,6 @@ export { AbstractTestContextFactory, type CreateTestContextParams, } from './lib
4
4
  export type { NestedPartial } from './lib/configUtils.js';
5
5
  export { type DependencyInjectionOptions, DIContext, type RegisterDependenciesParams, } from './lib/DIContext.js';
6
6
  export { ENABLE_ALL, isAnyMessageQueueConsumerEnabled, isEnqueuedJobWorkersEnabled, isJobQueueEnabled, isMessageQueueConsumerEnabled, isPeriodicJobEnabled, resolveJobQueuesEnabled, } from './lib/diConfigUtils.js';
7
- export type { EnqueuedJobWorkerModuleOptions, JobQueueModuleOptions, MessageQueueConsumerModuleOptions, PeriodicJobOptions, } from './lib/resolverFunctions.js';
8
- export { asControllerClass, asEnqueuedJobQueueManagerFunction, asEnqueuedJobWorkerClass, asJobQueueClass, asMessageQueueHandlerClass, asPeriodicJobClass, asPgBossProcessorClass, asRepositoryClass, asServiceClass, asSingletonClass, asSingletonFunction, asUseCaseClass, type EnqueuedJobQueueManager, } from './lib/resolverFunctions.js';
7
+ export * from './lib/resolverFunctions.js';
8
+ export * from './lib/sse/index.js';
9
+ export * from './lib/testing/index.js';
package/dist/index.js CHANGED
@@ -3,5 +3,9 @@ export { AbstractModule, } from './lib/AbstractModule.js';
3
3
  export { AbstractTestContextFactory, } from './lib/AbstractTestContextFactory.js';
4
4
  export { DIContext, } from './lib/DIContext.js';
5
5
  export { ENABLE_ALL, isAnyMessageQueueConsumerEnabled, isEnqueuedJobWorkersEnabled, isJobQueueEnabled, isMessageQueueConsumerEnabled, isPeriodicJobEnabled, resolveJobQueuesEnabled, } from './lib/diConfigUtils.js';
6
- export { asControllerClass, asEnqueuedJobQueueManagerFunction, asEnqueuedJobWorkerClass, asJobQueueClass, asMessageQueueHandlerClass, asPeriodicJobClass, asPgBossProcessorClass, asRepositoryClass, asServiceClass, asSingletonClass, asSingletonFunction, asUseCaseClass, } from './lib/resolverFunctions.js';
6
+ export * from './lib/resolverFunctions.js';
7
+ // SSE
8
+ export * from './lib/sse/index.js';
9
+ // SSE testing utilities
10
+ export * from './lib/testing/index.js';
7
11
  //# sourceMappingURL=index.js.map
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,GAGf,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;AAO/B,OAAO,EACL,iBAAiB,EACjB,iCAAiC,EACjC,wBAAwB,EACxB,eAAe,EACf,0BAA0B,EAC1B,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,GAEf,MAAM,4BAA4B,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,GAGf,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,cAAc,4BAA4B,CAAA;AAC1C,MAAM;AACN,cAAc,oBAAoB,CAAA;AAClC,wBAAwB;AACxB,cAAc,wBAAwB,CAAA"}
@@ -3,10 +3,10 @@ import type { buildFastifyNoPayloadRoute, buildFastifyPayloadRoute } from '@loka
3
3
  import type { z } from 'zod/v4';
4
4
  type AnyCommonRouteDefinition = CommonRouteDefinition<any, any, any, any, any, any, any, any>;
5
5
  type OptionalZodSchema = z.Schema | undefined;
6
- type FastifyPayloadRouteReturnType<RequestBody extends OptionalZodSchema, ResponseBody extends OptionalZodSchema, Path extends OptionalZodSchema, Query extends OptionalZodSchema, Headers extends OptionalZodSchema, ResponseHeaders extends OptionalZodSchema, IsNonJSONResponseExpected extends boolean, IsEmptyResponseExpected extends boolean> = ReturnType<typeof buildFastifyPayloadRoute<RequestBody, ResponseBody, Path, Query, Headers, ResponseHeaders, IsNonJSONResponseExpected, IsEmptyResponseExpected>>;
7
- type FastifyNoPayloadRouteReturnType<RequestBody extends OptionalZodSchema, Path extends OptionalZodSchema, Query extends OptionalZodSchema, Headers extends OptionalZodSchema, ResponseHeaders extends OptionalZodSchema> = ReturnType<typeof buildFastifyNoPayloadRoute<RequestBody, Path, Query, Headers, ResponseHeaders>>;
6
+ type FastifyPayloadRouteReturnType<RequestBody extends OptionalZodSchema, ResponseBody extends OptionalZodSchema, Path extends OptionalZodSchema, Query extends OptionalZodSchema, Headers extends OptionalZodSchema, ResponseHeaders extends OptionalZodSchema, IsNonJSONResponseExpected extends boolean, IsEmptyResponseExpected extends boolean, ResponseSchemasByStatusCode extends Record<number, any> | undefined> = ReturnType<typeof buildFastifyPayloadRoute<RequestBody, ResponseBody, Path, Query, Headers, ResponseHeaders, IsNonJSONResponseExpected, IsEmptyResponseExpected, ResponseSchemasByStatusCode>>;
7
+ type FastifyNoPayloadRouteReturnType<RequestBody extends OptionalZodSchema, Path extends OptionalZodSchema, Query extends OptionalZodSchema, Headers extends OptionalZodSchema, ResponseHeaders extends OptionalZodSchema, ResponseSchemasByStatusCode extends Record<number, any> | undefined> = ReturnType<typeof buildFastifyNoPayloadRoute<RequestBody, Path, Query, Headers, ResponseHeaders, ResponseSchemasByStatusCode>>;
8
8
  export type BuildRoutesReturnType<APIContracts extends Record<string, AnyCommonRouteDefinition>> = {
9
- [K in keyof APIContracts]: APIContracts[K] extends PayloadRouteDefinition<infer RequestBody, infer ResponseBody, infer Path, infer Query, infer Headers, infer ResponseHeaders, infer IsNonJSONResponseExpected, infer IsEmptyResponseExpected, infer _ResponseSchemasByStatusCode> ? FastifyPayloadRouteReturnType<RequestBody, ResponseBody, Path, Query, Headers, ResponseHeaders, IsNonJSONResponseExpected, IsEmptyResponseExpected> : APIContracts[K] extends GetRouteDefinition<infer GetResponseBody, infer GetPath, infer GetQuery, infer GetHeaders, infer GetResponseHeaders, infer _GetIsNonJSONResponseExpected, infer _GetIsEmptyResponseExpected, infer _GetResponseSchemasByStatusCode> | DeleteRouteDefinition<infer DeleteResponseBody, infer DeletePath, infer DeleteQuery, infer DeleteHeaders, infer DeleteResponseHeaders, infer _DeleteIsNonJSONResponseExpected, infer _DeleteIsEmptyResponseExpected, infer _DeleteResponseSchemasByStatusCode> ? FastifyNoPayloadRouteReturnType<GetResponseBody | DeleteResponseBody, GetPath | DeletePath, GetQuery | DeleteQuery, GetHeaders | DeleteHeaders, GetResponseHeaders | DeleteResponseHeaders> : never;
9
+ [K in keyof APIContracts]: APIContracts[K] extends PayloadRouteDefinition<infer RequestBody, infer ResponseBody, infer Path, infer Query, infer Headers, infer ResponseHeaders, infer IsNonJSONResponseExpected, infer IsEmptyResponseExpected, infer ResponseSchemasByStatusCode> ? FastifyPayloadRouteReturnType<RequestBody, ResponseBody, Path, Query, Headers, ResponseHeaders, IsNonJSONResponseExpected, IsEmptyResponseExpected, ResponseSchemasByStatusCode> : APIContracts[K] extends GetRouteDefinition<infer GetResponseBody, infer GetPath, infer GetQuery, infer GetHeaders, infer GetResponseHeaders, infer _GetIsNonJSONResponseExpected, infer _GetIsEmptyResponseExpected, infer GetResponseSchemasByStatusCode> | DeleteRouteDefinition<infer DeleteResponseBody, infer DeletePath, infer DeleteQuery, infer DeleteHeaders, infer DeleteResponseHeaders, infer _DeleteIsNonJSONResponseExpected, infer _DeleteIsEmptyResponseExpected, infer DeleteResponseSchemasByStatusCode> ? FastifyNoPayloadRouteReturnType<GetResponseBody | DeleteResponseBody, GetPath | DeletePath, GetQuery | DeleteQuery, GetHeaders | DeleteHeaders, GetResponseHeaders | DeleteResponseHeaders, GetResponseSchemasByStatusCode | DeleteResponseSchemasByStatusCode> : never;
10
10
  };
11
11
  export declare abstract class AbstractController<APIContracts extends Record<string, AnyCommonRouteDefinition>> {
12
12
  abstract buildRoutes(): BuildRoutesReturnType<APIContracts>;
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractController.js","sourceRoot":"","sources":["../../lib/AbstractController.ts"],"names":[],"mappings":"AAoGA,MAAM,OAAgB,kBAAkB;CAIvC"}
1
+ {"version":3,"file":"AbstractController.js","sourceRoot":"","sources":["../../lib/AbstractController.ts"],"names":[],"mappings":"AAkHA,MAAM,OAAgB,kBAAkB;CAIvC"}
@@ -9,5 +9,27 @@ export type MandatoryNameAndRegistrationPair<T> = {
9
9
  export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
10
10
  export declare abstract class AbstractModule<ModuleDependencies, ExternalDependencies = never> {
11
11
  abstract resolveDependencies(diOptions: DependencyInjectionOptions, externalDependencies: ExternalDependencies): MandatoryNameAndRegistrationPair<ModuleDependencies>;
12
- abstract resolveControllers(): MandatoryNameAndRegistrationPair<unknown>;
12
+ /**
13
+ * Override to register REST and SSE controllers.
14
+ * Returns empty object by default - no changes needed for modules without controllers.
15
+ *
16
+ * Controllers registered here are automatically added to the DI container.
17
+ * SSE controllers (created with asSSEControllerClass) are automatically detected
18
+ * and registered for SSE route handling.
19
+ *
20
+ * @param diOptions - DI options (use for test mode detection with asSSEControllerClass)
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * public resolveControllers(diOptions: DependencyInjectionOptions) {
25
+ * return {
26
+ * // REST controller
27
+ * usersController: asControllerClass(UsersController),
28
+ * // SSE controller (automatically detected via isSSEController flag)
29
+ * notificationsSSEController: asSSEControllerClass(NotificationsSSEController, { diOptions }),
30
+ * }
31
+ * }
32
+ * ```
33
+ */
34
+ resolveControllers(_diOptions: DependencyInjectionOptions): MandatoryNameAndRegistrationPair<unknown>;
13
35
  }
@@ -1,3 +1,28 @@
1
1
  export class AbstractModule {
2
+ /**
3
+ * Override to register REST and SSE controllers.
4
+ * Returns empty object by default - no changes needed for modules without controllers.
5
+ *
6
+ * Controllers registered here are automatically added to the DI container.
7
+ * SSE controllers (created with asSSEControllerClass) are automatically detected
8
+ * and registered for SSE route handling.
9
+ *
10
+ * @param diOptions - DI options (use for test mode detection with asSSEControllerClass)
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * public resolveControllers(diOptions: DependencyInjectionOptions) {
15
+ * return {
16
+ * // REST controller
17
+ * usersController: asControllerClass(UsersController),
18
+ * // SSE controller (automatically detected via isSSEController flag)
19
+ * notificationsSSEController: asSSEControllerClass(NotificationsSSEController, { diOptions }),
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ resolveControllers(_diOptions) {
25
+ return {};
26
+ }
2
27
  }
3
28
  //# sourceMappingURL=AbstractModule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractModule.js","sourceRoot":"","sources":["../../lib/AbstractModule.ts"],"names":[],"mappings":"AAcA,MAAM,OAAgB,cAAc;CAOnC"}
1
+ {"version":3,"file":"AbstractModule.js","sourceRoot":"","sources":["../../lib/AbstractModule.ts"],"names":[],"mappings":"AAcA,MAAM,OAAgB,cAAc;IAMlC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACI,kBAAkB,CACvB,UAAsC;QAEtC,OAAO,EAAE,CAAA;IACX,CAAC;CACF"}
@@ -4,6 +4,7 @@ import type { FastifyInstance } from 'fastify';
4
4
  import type { AbstractModule } from './AbstractModule.js';
5
5
  import { type NestedPartial } from './configUtils.js';
6
6
  import type { ENABLE_ALL } from './diConfigUtils.js';
7
+ import { type RegisterSSERoutesOptions } from './sse/sseRouteBuilder.js';
7
8
  export type RegisterDependenciesParams<Dependencies, Config, ExternalDependencies> = {
8
9
  modules: readonly AbstractModule<unknown, ExternalDependencies>[];
9
10
  secondaryModules?: readonly AbstractModule<unknown, ExternalDependencies>[];
@@ -16,17 +17,51 @@ export type DependencyInjectionOptions = {
16
17
  enqueuedJobWorkersEnabled?: false | typeof ENABLE_ALL | string[];
17
18
  messageQueueConsumersEnabled?: false | typeof ENABLE_ALL | string[];
18
19
  periodicJobsEnabled?: false | typeof ENABLE_ALL | string[];
20
+ /**
21
+ * Enable SSE test mode features like connection spying.
22
+ * Only relevant for SSE controllers. Set to true in test environments.
23
+ * @default false
24
+ */
25
+ isTestMode?: boolean;
19
26
  };
20
27
  export declare class DIContext<Dependencies extends object, Config extends object, ExternalDependencies = undefined> {
21
28
  private readonly options;
22
29
  readonly awilixManager: AwilixManager;
23
30
  readonly diContainer: AwilixContainer<Dependencies>;
24
31
  private readonly controllerResolvers;
32
+ private readonly sseControllerNames;
25
33
  private readonly appConfig;
26
34
  constructor(diContainer: AwilixContainer, options: DependencyInjectionOptions, appConfig: Config, awilixManager?: AwilixManager);
27
35
  private registerModule;
28
36
  registerDependencies(params: RegisterDependenciesParams<Dependencies, Config, ExternalDependencies>, externalDependencies: ExternalDependencies, resolveControllers?: boolean): void;
29
37
  registerRoutes(app: FastifyInstance<any, any, any, any>): void;
38
+ /**
39
+ * Check if any SSE controllers are registered.
40
+ * Use this to conditionally call registerSSERoutes().
41
+ */
42
+ hasSSEControllers(): boolean;
43
+ /**
44
+ * Register SSE routes with the Fastify app.
45
+ *
46
+ * Must be called separately from registerRoutes().
47
+ * Requires @fastify/sse plugin to be registered on the app.
48
+ *
49
+ * @param app - Fastify instance with @fastify/sse registered
50
+ * @param options - Optional configuration for SSE routes
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * // Register @fastify/sse plugin first
55
+ * await app.register(fastifySSE, { heartbeatInterval: 30000 })
56
+ *
57
+ * // Then register SSE routes
58
+ * context.registerSSERoutes(app)
59
+ * ```
60
+ */
61
+ registerSSERoutes(app: FastifyInstance<any, any, any, any>, options?: RegisterSSERoutesOptions): void;
62
+ private applySSERouteOptions;
63
+ private applyPreHandlers;
64
+ private applyRateLimit;
30
65
  destroy(): Promise<void>;
31
66
  init(): Promise<void>;
32
67
  }
@@ -1,11 +1,15 @@
1
1
  import { AwilixManager } from 'awilix-manager';
2
+ import { merge } from 'ts-deepmerge';
2
3
  import { mergeConfigAndDependencyOverrides } from './configUtils.js';
4
+ import { buildFastifySSERoute } from './sse/sseRouteBuilder.js';
3
5
  export class DIContext {
4
6
  options;
5
7
  awilixManager;
6
8
  diContainer;
7
9
  // biome-ignore lint/suspicious/noExplicitAny: all controllers are controllers
8
10
  controllerResolvers;
11
+ // SSE controller dependency names (resolved from container to preserve singletons)
12
+ sseControllerNames;
9
13
  appConfig;
10
14
  constructor(diContainer, options, appConfig, awilixManager) {
11
15
  this.options = options;
@@ -21,6 +25,7 @@ export class DIContext {
21
25
  strictBooleanEnforced: true,
22
26
  });
23
27
  this.controllerResolvers = [];
28
+ this.sseControllerNames = [];
24
29
  }
25
30
  registerModule(module, targetDiConfig, externalDependencies, resolveControllers, isPrimaryModule) {
26
31
  const resolvedDIConfig = module.resolveDependencies(this.options, externalDependencies);
@@ -32,7 +37,20 @@ export class DIContext {
32
37
  }
33
38
  }
34
39
  if (isPrimaryModule && resolveControllers) {
35
- this.controllerResolvers.push(...Object.values(module.resolveControllers()));
40
+ const controllers = module.resolveControllers(this.options);
41
+ for (const [name, resolver] of Object.entries(controllers)) {
42
+ // @ts-expect-error isSSEController is a custom property on the resolver
43
+ if (resolver.isSSEController) {
44
+ // SSE controller: register in DI container and track name for route registration
45
+ this.sseControllerNames.push(name);
46
+ // @ts-expect-error we can't really ensure type-safety here
47
+ targetDiConfig[name] = resolver;
48
+ }
49
+ else {
50
+ // REST controller: add resolver for route registration
51
+ this.controllerResolvers.push(resolver);
52
+ }
53
+ }
36
54
  }
37
55
  }
38
56
  registerDependencies(params, externalDependencies, resolveControllers = true) {
@@ -68,10 +86,99 @@ export class DIContext {
68
86
  const controller = controllerResolver.resolve(this.diContainer);
69
87
  const routes = controller.buildRoutes();
70
88
  for (const route of Object.values(routes)) {
89
+ // Cast needed: GET/DELETE routes have body:undefined, POST/PATCH have body:unknown
90
+ // The union is incompatible with app.route() due to handler contravariance
91
+ app.route(route);
92
+ }
93
+ }
94
+ }
95
+ /**
96
+ * Check if any SSE controllers are registered.
97
+ * Use this to conditionally call registerSSERoutes().
98
+ */
99
+ hasSSEControllers() {
100
+ return this.sseControllerNames.length > 0;
101
+ }
102
+ /**
103
+ * Register SSE routes with the Fastify app.
104
+ *
105
+ * Must be called separately from registerRoutes().
106
+ * Requires @fastify/sse plugin to be registered on the app.
107
+ *
108
+ * @param app - Fastify instance with @fastify/sse registered
109
+ * @param options - Optional configuration for SSE routes
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * // Register @fastify/sse plugin first
114
+ * await app.register(fastifySSE, { heartbeatInterval: 30000 })
115
+ *
116
+ * // Then register SSE routes
117
+ * context.registerSSERoutes(app)
118
+ * ```
119
+ */
120
+ registerSSERoutes(
121
+ // biome-ignore lint/suspicious/noExplicitAny: Fastify instance types are complex
122
+ app, options) {
123
+ if (!this.hasSSEControllers()) {
124
+ return;
125
+ }
126
+ for (const controllerName of this.sseControllerNames) {
127
+ // Resolve from container to use the singleton instance
128
+ const sseController = this.diContainer.resolve(controllerName);
129
+ const sseRoutes = sseController.buildSSERoutes();
130
+ for (const routeConfig of Object.values(sseRoutes)) {
131
+ const route = buildFastifySSERoute(sseController, routeConfig);
132
+ this.applySSERouteOptions(route, options);
71
133
  app.route(route);
72
134
  }
73
135
  }
74
136
  }
137
+ applySSERouteOptions(route, options) {
138
+ if (options?.preHandler) {
139
+ this.applyPreHandlers(route, options.preHandler);
140
+ }
141
+ if (options?.rateLimit) {
142
+ this.applyRateLimit(route, options.rateLimit);
143
+ }
144
+ // Apply SSE-specific options (heartbeatInterval, serializer)
145
+ if (options?.heartbeatInterval !== undefined || options?.serializer !== undefined) {
146
+ // biome-ignore lint/suspicious/noExplicitAny: config types vary by plugins
147
+ const routeWithConfig = route;
148
+ routeWithConfig.config = merge(routeWithConfig.config || {}, {
149
+ sse: {
150
+ ...(options.heartbeatInterval !== undefined && {
151
+ heartbeatInterval: options.heartbeatInterval,
152
+ }),
153
+ ...(options.serializer !== undefined && { serializer: options.serializer }),
154
+ },
155
+ });
156
+ }
157
+ }
158
+ applyPreHandlers(route, globalPreHandler) {
159
+ const existingPreHandler = route.preHandler;
160
+ if (!existingPreHandler) {
161
+ route.preHandler = globalPreHandler;
162
+ return;
163
+ }
164
+ // biome-ignore lint/suspicious/noExplicitAny: preHandler types are complex
165
+ const handlers = Array.isArray(existingPreHandler)
166
+ ? existingPreHandler
167
+ : [existingPreHandler];
168
+ // biome-ignore lint/suspicious/noExplicitAny: preHandler types are complex
169
+ const globalHandlers = Array.isArray(globalPreHandler)
170
+ ? globalPreHandler
171
+ : [globalPreHandler];
172
+ route.preHandler = [...globalHandlers, ...handlers];
173
+ }
174
+ applyRateLimit(route, rateLimit) {
175
+ // biome-ignore lint/suspicious/noExplicitAny: config types vary by plugins
176
+ const routeWithConfig = route;
177
+ routeWithConfig.config = {
178
+ ...(routeWithConfig.config || {}),
179
+ rateLimit,
180
+ };
181
+ }
75
182
  async destroy() {
76
183
  await this.awilixManager.executeDispose();
77
184
  await this.diContainer.dispose();
@@ -1 +1 @@
1
- {"version":3,"file":"DIContext.js","sourceRoot":"","sources":["../../lib/DIContext.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DIContext.js","sourceRoot":"","sources":["../../lib/DIContext.ts"],"names":[],"mappings":"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"}
@@ -4,17 +4,51 @@ import type { DependencyInjectionOptions } from './DIContext.js';
4
4
  declare module 'awilix' {
5
5
  interface ResolverOptions<T> {
6
6
  public?: boolean;
7
+ isSSEController?: boolean;
7
8
  }
8
9
  }
9
10
  export interface EnqueuedJobQueueManager {
10
11
  start(enabled?: string[] | boolean): Promise<void>;
11
12
  }
12
13
  export declare function asSingletonClass<T = object>(Type: Constructor<T>, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
14
+ /**
15
+ * Register a class with an additional config parameter passed to the constructor.
16
+ * Uses asFunction wrapper internally to pass the config as a second parameter.
17
+ * Requires PROXY injection mode.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * myService: asClassWithConfig(MyService, { enableFeature: true }),
22
+ * ```
23
+ */
24
+ export declare function asClassWithConfig<T = object, Config = unknown>(Type: Constructor<T>, config: Config, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
13
25
  export declare function asSingletonFunction<T>(fn: FunctionReturning<T>, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
14
26
  export declare function asServiceClass<T = object>(Type: Constructor<T>, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
15
27
  export declare function asUseCaseClass<T = object>(Type: Constructor<T>, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
16
28
  export declare function asRepositoryClass<T = object>(Type: Constructor<T>, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
17
29
  export declare function asControllerClass<T = object>(Type: Constructor<T>, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
30
+ export type SSEControllerModuleOptions = {
31
+ diOptions: DependencyInjectionOptions;
32
+ };
33
+ /**
34
+ * Register an SSE controller class with the DI container.
35
+ *
36
+ * SSE controllers handle Server-Sent Events connections and require
37
+ * graceful shutdown to close all active connections.
38
+ *
39
+ * When `diOptions.isTestMode` is true, connection spying is enabled
40
+ * allowing tests to await connections via `controller.connectionSpy`.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // Without test mode
45
+ * notificationsSSEController: asSSEControllerClass(NotificationsSSEController),
46
+ *
47
+ * // With test mode (enables connection spy)
48
+ * notificationsSSEController: asSSEControllerClass(NotificationsSSEController, { diOptions }),
49
+ * ```
50
+ */
51
+ export declare function asSSEControllerClass<T = object>(Type: Constructor<T>, sseOptions?: SSEControllerModuleOptions, opts?: BuildResolverOptions<T>): BuildResolver<T> & DisposableResolver<T>;
18
52
  export type MessageQueueConsumerModuleOptions = {
19
53
  queueName: string;
20
54
  diOptions: DependencyInjectionOptions;
@@ -6,6 +6,23 @@ export function asSingletonClass(Type, opts) {
6
6
  lifetime: 'SINGLETON',
7
7
  });
8
8
  }
9
+ /**
10
+ * Register a class with an additional config parameter passed to the constructor.
11
+ * Uses asFunction wrapper internally to pass the config as a second parameter.
12
+ * Requires PROXY injection mode.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * myService: asClassWithConfig(MyService, { enableFeature: true }),
17
+ * ```
18
+ */
19
+ export function asClassWithConfig(Type, config, opts) {
20
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic constructor invocation with cradle proxy
21
+ return asFunction((cradle) => new Type(cradle, config), {
22
+ ...opts,
23
+ lifetime: opts?.lifetime ?? 'SINGLETON',
24
+ });
25
+ }
9
26
  export function asSingletonFunction(fn, opts) {
10
27
  return asFunction(fn, {
11
28
  ...opts,
@@ -40,6 +57,36 @@ export function asControllerClass(Type, opts) {
40
57
  lifetime: 'SINGLETON',
41
58
  });
42
59
  }
60
+ /**
61
+ * Register an SSE controller class with the DI container.
62
+ *
63
+ * SSE controllers handle Server-Sent Events connections and require
64
+ * graceful shutdown to close all active connections.
65
+ *
66
+ * When `diOptions.isTestMode` is true, connection spying is enabled
67
+ * allowing tests to await connections via `controller.connectionSpy`.
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * // Without test mode
72
+ * notificationsSSEController: asSSEControllerClass(NotificationsSSEController),
73
+ *
74
+ * // With test mode (enables connection spy)
75
+ * notificationsSSEController: asSSEControllerClass(NotificationsSSEController, { diOptions }),
76
+ * ```
77
+ */
78
+ export function asSSEControllerClass(Type, sseOptions, opts) {
79
+ const enableConnectionSpy = sseOptions?.diOptions.isTestMode ?? false;
80
+ const sseConfig = enableConnectionSpy ? { enableConnectionSpy: true } : undefined;
81
+ return asClassWithConfig(Type, sseConfig, {
82
+ public: false,
83
+ isSSEController: true,
84
+ asyncDispose: 'closeAllConnections',
85
+ asyncDisposePriority: 5, // Close SSE connections early in shutdown
86
+ ...opts,
87
+ lifetime: 'SINGLETON',
88
+ });
89
+ }
43
90
  export function asMessageQueueHandlerClass(Type, mqOptions, opts) {
44
91
  return asClass(Type, {
45
92
  // these follow message-queue-toolkit conventions
@@ -1 +1 @@
1
- {"version":3,"file":"resolverFunctions.js","sourceRoot":"","sources":["../../lib/resolverFunctions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolverFunctions.js","sourceRoot":"","sources":["../../lib/resolverFunctions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAG5C,OAAO,EACL,2BAA2B,EAC3B,iBAAiB,EACjB,6BAA6B,EAC7B,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,oBAAoB,CAAA;AAc3B,MAAM,UAAU,gBAAgB,CAC9B,IAAoB,EACpB,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAoB,EACpB,MAAc,EACd,IAA8B;IAE9B,+FAA+F;IAC/F,OAAO,UAAU,CAAC,CAAC,MAAW,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;QAC3D,GAAG,IAAI;QACP,QAAQ,EAAE,IAAI,EAAE,QAAQ,IAAI,WAAW;KACxC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,EAAwB,EACxB,IAA8B;IAE9B,OAAO,UAAU,CAAC,EAAE,EAAE;QACpB,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,IAAoB,EACpB,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,MAAM,EAAE,IAAI;QACZ,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,IAAoB,EACpB,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,MAAM,EAAE,IAAI;QACZ,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAAoB,EACpB,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,MAAM,EAAE,KAAK;QACb,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAAoB,EACpB,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,MAAM,EAAE,KAAK;QACb,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAoB,EACpB,UAAuC,EACvC,IAA8B;IAE9B,MAAM,mBAAmB,GAAG,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,KAAK,CAAA;IACrE,MAAM,SAAS,GAAG,mBAAmB,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IAEjF,OAAO,iBAAiB,CAAC,IAAI,EAAE,SAAS,EAAE;QACxC,MAAM,EAAE,KAAK;QACb,eAAe,EAAE,IAAI;QACrB,YAAY,EAAE,qBAAqB;QACnC,oBAAoB,EAAE,CAAC,EAAE,0CAA0C;QACnE,GAAG,IAAI;QACP,QAAQ,EAAE,WAAW;KACtB,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,0BAA0B,CACxC,IAAoB,EACpB,SAA4C,EAC5C,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,iDAAiD;QACjD,SAAS,EAAE,OAAO;QAClB,YAAY,EAAE,OAAO;QACrB,oBAAoB,EAAE,EAAE;QAExB,OAAO,EAAE,6BAA6B,CACpC,SAAS,CAAC,SAAS,CAAC,4BAA4B,EAChD,SAAS,CAAC,SAAS,CACpB;QACD,QAAQ,EAAE,WAAW;QACrB,MAAM,EAAE,KAAK;QACb,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,wBAAwB,CACtC,IAAoB,EACpB,aAA6C,EAC7C,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,kDAAkD;QAClD,SAAS,EAAE,OAAO;QAClB,YAAY,EAAE,SAAS;QACvB,oBAAoB,EAAE,EAAE;QACxB,MAAM,EAAE,KAAK;QAEb,OAAO,EAAE,2BAA2B,CAClC,aAAa,CAAC,SAAS,CAAC,yBAAyB,EACjD,aAAa,CAAC,SAAS,CACxB;QACD,QAAQ,EAAE,WAAW;QACrB,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAoB,EACpB,gBAAgD,EAChD,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,SAAS,EAAE,OAAO;QAClB,iBAAiB,EAAE,EAAE,EAAE,wCAAwC;QAC/D,YAAY,EAAE,MAAM;QACpB,oBAAoB,EAAE,EAAE;QACxB,MAAM,EAAE,KAAK;QAEb,OAAO,EAAE,2BAA2B,CAClC,gBAAgB,CAAC,SAAS,CAAC,yBAAyB,EACpD,gBAAgB,CAAC,SAAS,CAC3B;QACD,QAAQ,EAAE,WAAW;QACrB,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,kBAAkB,CAChC,IAAoB,EACpB,aAAiC,EACjC,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,kDAAkD;QAClD,WAAW,EAAE,UAAU;QACvB,YAAY,EAAE,SAAS;QACvB,MAAM,EAAE,KAAK;QAEb,OAAO,EAAE,oBAAoB,CAC3B,aAAa,CAAC,SAAS,CAAC,mBAAmB,EAC3C,aAAa,CAAC,OAAO,CACtB;QACD,QAAQ,EAAE,WAAW;QACrB,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC;AAOD,MAAM,UAAU,eAAe,CAC7B,IAAoB,EACpB,YAAmC,EACnC,IAA8B;IAE9B,OAAO,OAAO,CAAC,IAAI,EAAE;QACnB,kDAAkD;QAClD,SAAS,EAAE,OAAO;QAClB,YAAY,EAAE,SAAS;QACvB,oBAAoB,EAAE,EAAE;QACxB,MAAM,EAAE,IAAI;QAEZ,OAAO,EAAE,iBAAiB,CAAC,YAAY,CAAC,SAAS,CAAC,gBAAgB,EAAE,YAAY,CAAC,SAAS,CAAC;QAC3F,QAAQ,EAAE,WAAW;QACrB,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,iCAAiC,CAC/C,EAAwB,EACxB,SAAqC,EACrC,IAA8B;IAE9B,OAAO,UAAU,CAAC,EAAE,EAAE;QACpB,kDAAkD;QAClD,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;QACzE,YAAY,EAAE,SAAS;QACvB,iBAAiB,EAAE,EAAE;QACrB,oBAAoB,EAAE,EAAE;QACxB,MAAM,EAAE,IAAI;QACZ,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,gBAAgB,CAAC;QACtD,QAAQ,EAAE,WAAW;QACrB,GAAG,IAAI;KACR,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,163 @@
1
+ import { SSEConnectionSpy } from './SSEConnectionSpy.ts';
2
+ import type { AnySSERouteDefinition } from './sseContracts.ts';
3
+ import type { BuildSSERoutesReturnType, SSEConnection, SSEControllerConfig, SSEMessage } from './sseTypes.ts';
4
+ export type { SSEConnectionEvent } from './SSEConnectionSpy.ts';
5
+ export { SSEConnectionSpy } from './SSEConnectionSpy.ts';
6
+ export type { BuildSSERoutesReturnType, InferSSERequest, SSEConnection, SSEControllerConfig, SSEHandlerConfig, SSELogger, SSEMessage, SSEPreHandler, SSERouteHandler, SSERouteOptions, } from './sseTypes.ts';
7
+ /**
8
+ * Abstract base class for SSE controllers.
9
+ *
10
+ * Provides connection management, broadcasting, and lifecycle hooks.
11
+ * Extend this class to create SSE controllers that handle real-time
12
+ * streaming connections.
13
+ *
14
+ * @template APIContracts - Map of route names to SSE route definitions
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * class NotificationsSSEController extends AbstractSSEController<typeof contracts> {
19
+ * public static contracts = {
20
+ * notifications: buildSSERoute({ ... }),
21
+ * } as const
22
+ *
23
+ * public buildSSERoutes() {
24
+ * return {
25
+ * notifications: {
26
+ * contract: NotificationsSSEController.contracts.notifications,
27
+ * handler: this.handleNotifications,
28
+ * },
29
+ * }
30
+ * }
31
+ * }
32
+ * ```
33
+ */
34
+ export declare abstract class AbstractSSEController<APIContracts extends Record<string, AnySSERouteDefinition>> {
35
+ /** Map of connection ID to connection object */
36
+ protected connections: Map<string, SSEConnection>;
37
+ /** Private storage for connection spy */
38
+ private readonly _connectionSpy?;
39
+ /**
40
+ * SSE controllers must override this constructor and call super with their
41
+ * dependencies object and the SSE config.
42
+ *
43
+ * @param _dependencies - The dependencies object (cradle proxy in awilix)
44
+ * @param sseConfig - Optional SSE controller configuration
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * class MySSEController extends AbstractSSEController<MyContracts> {
49
+ * private myService: MyService
50
+ *
51
+ * constructor(deps: { myService: MyService }, sseConfig?: SSEControllerConfig) {
52
+ * super(deps, sseConfig)
53
+ * this.myService = deps.myService
54
+ * }
55
+ * }
56
+ * ```
57
+ */
58
+ constructor(_dependencies: object, sseConfig?: SSEControllerConfig);
59
+ /**
60
+ * Get the connection spy for testing.
61
+ * Throws an error if spies are not enabled.
62
+ * Enable spies by passing `{ enableConnectionSpy: true }` to the constructor.
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * // In test, create controller with spy enabled
67
+ * // Pass dependencies first, then config with enableConnectionSpy
68
+ * const controller = new MySSEController({}, { enableConnectionSpy: true })
69
+ *
70
+ * // Start connection (async)
71
+ * connectSSE(baseUrl, '/api/stream')
72
+ *
73
+ * // Wait for connection - handles race condition
74
+ * const connection = await controller.connectionSpy.waitForConnection()
75
+ * ```
76
+ *
77
+ * @throws Error if connection spy is not enabled
78
+ */
79
+ get connectionSpy(): SSEConnectionSpy;
80
+ /**
81
+ * Build and return SSE route configurations.
82
+ * Similar pattern to AbstractController.buildRoutes().
83
+ */
84
+ abstract buildSSERoutes(): BuildSSERoutesReturnType<APIContracts>;
85
+ /**
86
+ * Controller-level hook called when any connection is established.
87
+ * Override this method to add global connection handling logic.
88
+ * This is called AFTER the connection is registered and route-level onConnect.
89
+ *
90
+ * @param connection - The newly established connection
91
+ */
92
+ protected onConnectionEstablished?(connection: SSEConnection): void;
93
+ /**
94
+ * Controller-level hook called when any connection is closed.
95
+ * Override this method to add global disconnect handling logic.
96
+ * This is called BEFORE the connection is unregistered and route-level onDisconnect.
97
+ *
98
+ * @param connection - The connection being closed
99
+ */
100
+ protected onConnectionClosed?(connection: SSEConnection): void;
101
+ /**
102
+ * Send an event to a specific connection.
103
+ *
104
+ * @param connectionId - The connection to send to
105
+ * @param message - The SSE message to send
106
+ * @returns true if sent successfully, false if connection not found or closed
107
+ */
108
+ protected sendEvent<T>(connectionId: string, message: SSEMessage<T>): Promise<boolean>;
109
+ /**
110
+ * Broadcast an event to all connected clients.
111
+ *
112
+ * @param message - The SSE message to broadcast
113
+ * @returns Number of clients the message was sent to
114
+ */
115
+ protected broadcast<T>(message: SSEMessage<T>): Promise<number>;
116
+ /**
117
+ * Broadcast an event to connections matching a predicate.
118
+ *
119
+ * @param message - The SSE message to broadcast
120
+ * @param predicate - Function to filter connections
121
+ * @returns Number of clients the message was sent to
122
+ */
123
+ protected broadcastIf<T>(message: SSEMessage<T>, predicate: (connection: SSEConnection) => boolean): Promise<number>;
124
+ /**
125
+ * Get all active connections.
126
+ */
127
+ protected getConnections(): SSEConnection[];
128
+ /**
129
+ * Get the number of active connections.
130
+ */
131
+ protected getConnectionCount(): number;
132
+ /**
133
+ * Close a specific connection.
134
+ *
135
+ * This gracefully ends the SSE stream by calling the underlying `reply.sse.close()`.
136
+ * All previously sent data is flushed to the client before the connection terminates.
137
+ * Use this to signal end-of-stream after sending all events (e.g., in request-response
138
+ * style streaming like OpenAI completions).
139
+ *
140
+ * @param connectionId - The connection to close
141
+ * @returns true if connection was found and closed
142
+ */
143
+ protected closeConnection(connectionId: string): boolean;
144
+ /**
145
+ * Close all active connections.
146
+ * Called during graceful shutdown via asyncDispose.
147
+ */
148
+ closeAllConnections(): void;
149
+ /**
150
+ * Register a connection (called internally by route builder).
151
+ * Triggers the onConnectionEstablished hook and spy if defined.
152
+ * @internal
153
+ */
154
+ registerConnection(connection: SSEConnection): void;
155
+ /**
156
+ * Unregister a connection (called internally by route builder).
157
+ * Triggers the onConnectionClosed hook and spy if defined.
158
+ * This method is idempotent - calling it multiple times for the same
159
+ * connection ID has no effect after the first call.
160
+ * @internal
161
+ */
162
+ unregisterConnection(connectionId: string): void;
163
+ }