opinionated-machine 6.2.0 → 6.2.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.
Files changed (59) hide show
  1. package/README.md +341 -7
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +2 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/DIContext.d.ts +31 -1
  6. package/dist/lib/DIContext.js +80 -3
  7. package/dist/lib/DIContext.js.map +1 -1
  8. package/dist/lib/dualmode/AbstractDualModeController.d.ts +97 -0
  9. package/dist/lib/dualmode/AbstractDualModeController.js +79 -0
  10. package/dist/lib/dualmode/AbstractDualModeController.js.map +1 -0
  11. package/dist/lib/dualmode/dualModeContracts.d.ts +134 -0
  12. package/dist/lib/dualmode/dualModeContracts.js +77 -0
  13. package/dist/lib/dualmode/dualModeContracts.js.map +1 -0
  14. package/dist/lib/dualmode/dualModeTypes.d.ts +23 -0
  15. package/dist/lib/dualmode/dualModeTypes.js +2 -0
  16. package/dist/lib/dualmode/dualModeTypes.js.map +1 -0
  17. package/dist/lib/dualmode/fastifyDualModeRouteBuilder.d.ts +30 -0
  18. package/dist/lib/dualmode/fastifyDualModeRouteBuilder.js +137 -0
  19. package/dist/lib/dualmode/fastifyDualModeRouteBuilder.js.map +1 -0
  20. package/dist/lib/dualmode/fastifyDualModeTypes.d.ts +153 -0
  21. package/dist/lib/dualmode/fastifyDualModeTypes.js +25 -0
  22. package/dist/lib/dualmode/fastifyDualModeTypes.js.map +1 -0
  23. package/dist/lib/dualmode/index.d.ts +5 -0
  24. package/dist/lib/dualmode/index.js +6 -0
  25. package/dist/lib/dualmode/index.js.map +1 -0
  26. package/dist/lib/resolverFunctions.d.ts +24 -0
  27. package/dist/lib/resolverFunctions.js +31 -0
  28. package/dist/lib/resolverFunctions.js.map +1 -1
  29. package/dist/lib/sse/AbstractSSEController.d.ts +8 -6
  30. package/dist/lib/sse/AbstractSSEController.js +1 -1
  31. package/dist/lib/sse/AbstractSSEController.js.map +1 -1
  32. package/dist/lib/sse/{sseRouteBuilder.d.ts → fastifySSERouteBuilder.d.ts} +7 -4
  33. package/dist/lib/sse/fastifySSERouteBuilder.js +53 -0
  34. package/dist/lib/sse/fastifySSERouteBuilder.js.map +1 -0
  35. package/dist/lib/sse/fastifySSERouteUtils.d.ts +83 -0
  36. package/dist/lib/sse/fastifySSERouteUtils.js +163 -0
  37. package/dist/lib/sse/fastifySSERouteUtils.js.map +1 -0
  38. package/dist/lib/sse/fastifySSETypes.d.ts +200 -0
  39. package/dist/lib/sse/fastifySSETypes.js +47 -0
  40. package/dist/lib/sse/fastifySSETypes.js.map +1 -0
  41. package/dist/lib/sse/index.d.ts +5 -4
  42. package/dist/lib/sse/index.js +4 -2
  43. package/dist/lib/sse/index.js.map +1 -1
  44. package/dist/lib/sse/sseContracts.d.ts +50 -60
  45. package/dist/lib/sse/sseContracts.js +8 -54
  46. package/dist/lib/sse/sseContracts.js.map +1 -1
  47. package/dist/lib/sse/sseTypes.d.ts +4 -157
  48. package/dist/lib/testing/index.d.ts +1 -1
  49. package/dist/lib/testing/index.js +1 -1
  50. package/dist/lib/testing/index.js.map +1 -1
  51. package/dist/lib/testing/sseHttpClient.d.ts +1 -1
  52. package/dist/lib/testing/sseHttpClient.js.map +1 -1
  53. package/dist/lib/testing/sseInjectHelpers.d.ts +3 -10
  54. package/dist/lib/testing/sseInjectHelpers.js +16 -15
  55. package/dist/lib/testing/sseInjectHelpers.js.map +1 -1
  56. package/dist/lib/testing/sseTestTypes.d.ts +3 -3
  57. package/package.json +1 -1
  58. package/dist/lib/sse/sseRouteBuilder.js +0 -177
  59. package/dist/lib/sse/sseRouteBuilder.js.map +0 -1
package/README.md CHANGED
@@ -17,6 +17,7 @@ Very opinionated DI framework for fastify, built on top of awilix
17
17
  - [`asRepositoryClass`](#asrepositoryclasstype-opts)
18
18
  - [`asControllerClass`](#ascontrollerclasstype-opts)
19
19
  - [`asSSEControllerClass`](#asssecontrollerclasstype-sseoptions-opts)
20
+ - [`asDualModeControllerClass`](#asdualmodecontrollerclasstype-sseoptions-opts)
20
21
  - [Message Queue Resolvers](#message-queue-resolvers)
21
22
  - [`asMessageQueueHandlerClass`](#asmessagequeuehandlerclasstype-mqoptions-opts)
22
23
  - [Background Job Resolvers](#background-job-resolvers)
@@ -53,6 +54,13 @@ Very opinionated DI framework for fastify, built on top of awilix
53
54
  - [SSEHttpClient](#ssehttpclient)
54
55
  - [SSEInjectClient](#sseinjectclient)
55
56
  - [Contract-Aware Inject Helpers](#contract-aware-inject-helpers)
57
+ - [Dual-Mode Controllers (SSE + JSON)](#dual-mode-controllers-sse--json)
58
+ - [Overview](#overview)
59
+ - [Defining Dual-Mode Contracts](#defining-dual-mode-contracts)
60
+ - [Implementing Dual-Mode Controllers](#implementing-dual-mode-controllers)
61
+ - [Registering Dual-Mode Controllers](#registering-dual-mode-controllers)
62
+ - [Accept Header Routing](#accept-header-routing)
63
+ - [Testing Dual-Mode Controllers](#testing-dual-mode-controllers)
56
64
 
57
65
  ## Basic usage
58
66
 
@@ -297,6 +305,19 @@ resolveControllers(diOptions: DependencyInjectionOptions) {
297
305
  }
298
306
  ```
299
307
 
308
+ #### `asDualModeControllerClass(Type, sseOptions?, opts?)`
309
+ For dual-mode controller classes that handle both SSE and JSON responses on the same route. Marks the dependency as **private** with `isDualModeController: true` for auto-detection. Inherits all SSE controller features including connection management and graceful shutdown. When `sseOptions.diOptions.isTestMode` is true, enables the connection spy for testing SSE mode.
310
+
311
+ ```ts
312
+ // In resolveControllers()
313
+ resolveControllers(diOptions: DependencyInjectionOptions) {
314
+ return {
315
+ userController: asControllerClass(UserController),
316
+ chatController: asDualModeControllerClass(ChatDualModeController, { diOptions }),
317
+ }
318
+ }
319
+ ```
320
+
300
321
  ### Message Queue Resolvers
301
322
 
302
323
  #### `asMessageQueueHandlerClass(Type, mqOptions, opts?)`
@@ -377,15 +398,26 @@ await app.register(FastifySSEPlugin)
377
398
 
378
399
  ### Defining SSE Contracts
379
400
 
380
- Use `buildSSERoute` for GET-based SSE streams or `buildPayloadSSERoute` for POST/PUT/PATCH streams:
401
+ Use `buildSSEContract` for GET-based SSE streams or `buildPayloadSSEContract` for POST/PUT/PATCH streams. Paths are defined using `pathResolver`, a type-safe function that receives typed params and returns the URL path:
381
402
 
382
403
  ```ts
383
404
  import { z } from 'zod'
384
- import { buildSSERoute, buildPayloadSSERoute } from 'opinionated-machine'
405
+ import { buildSSEContract, buildPayloadSSEContract } from 'opinionated-machine'
385
406
 
386
- // GET-based SSE stream (e.g., notifications)
387
- export const notificationsContract = buildSSERoute({
388
- path: '/api/notifications/stream',
407
+ // GET-based SSE stream with path params
408
+ export const channelStreamContract = buildSSEContract({
409
+ pathResolver: (params) => `/api/channels/${params.channelId}/stream`,
410
+ params: z.object({ channelId: z.string() }),
411
+ query: z.object({}),
412
+ requestHeaders: z.object({}),
413
+ events: {
414
+ message: z.object({ content: z.string() }),
415
+ },
416
+ })
417
+
418
+ // GET-based SSE stream without path params
419
+ export const notificationsContract = buildSSEContract({
420
+ pathResolver: () => '/api/notifications/stream',
389
421
  params: z.object({}),
390
422
  query: z.object({ userId: z.string().optional() }),
391
423
  requestHeaders: z.object({}),
@@ -398,9 +430,9 @@ export const notificationsContract = buildSSERoute({
398
430
  })
399
431
 
400
432
  // POST-based SSE stream (e.g., AI chat completions)
401
- export const chatCompletionContract = buildPayloadSSERoute({
433
+ export const chatCompletionContract = buildPayloadSSEContract({
402
434
  method: 'POST',
403
- path: '/api/chat/completions',
435
+ pathResolver: () => '/api/chat/completions',
404
436
  params: z.object({}),
405
437
  query: z.object({}),
406
438
  requestHeaders: z.object({}),
@@ -1260,3 +1292,305 @@ const result = await closed
1260
1292
  const events = parseSSEEvents(result.body)
1261
1293
  ```
1262
1294
 
1295
+ ## Dual-Mode Controllers (SSE + JSON)
1296
+
1297
+ Dual-mode controllers handle both SSE streaming and JSON responses on the same route path, automatically branching based on the `Accept` header. This is ideal for APIs that support both real-time streaming and traditional request-response patterns.
1298
+
1299
+ ### Overview
1300
+
1301
+ | Accept Header | Response Mode |
1302
+ | ------------- | ------------- |
1303
+ | `text/event-stream` | SSE streaming |
1304
+ | `application/json` | JSON response |
1305
+ | `*/*` or missing | JSON (default, configurable) |
1306
+
1307
+ Dual-mode controllers extend `AbstractDualModeController` which inherits from `AbstractSSEController`, providing access to all SSE features (connection management, broadcasting, lifecycle hooks) while adding JSON response support.
1308
+
1309
+ ### Defining Dual-Mode Contracts
1310
+
1311
+ Use `buildDualModeContract` for GET routes or `buildPayloadDualModeContract` for POST/PUT/PATCH routes. The key difference from SSE contracts is the addition of `jsonResponse` schema:
1312
+
1313
+ ```ts
1314
+ import { z } from 'zod'
1315
+ import { buildDualModeContract, buildPayloadDualModeContract } from 'opinionated-machine'
1316
+
1317
+ // GET dual-mode route (polling or streaming job status)
1318
+ export const jobStatusContract = buildDualModeContract({
1319
+ pathResolver: (params) => `/api/jobs/${params.jobId}/status`,
1320
+ params: z.object({ jobId: z.string().uuid() }),
1321
+ query: z.object({ verbose: z.string().optional() }),
1322
+ requestHeaders: z.object({}),
1323
+ jsonResponse: z.object({
1324
+ status: z.enum(['pending', 'running', 'completed', 'failed']),
1325
+ progress: z.number(),
1326
+ result: z.string().optional(),
1327
+ }),
1328
+ events: {
1329
+ progress: z.object({ percent: z.number(), message: z.string().optional() }),
1330
+ done: z.object({ result: z.string() }),
1331
+ },
1332
+ })
1333
+
1334
+ // POST dual-mode route (OpenAI-style chat completion)
1335
+ export const chatCompletionContract = buildPayloadDualModeContract({
1336
+ method: 'POST',
1337
+ pathResolver: (params) => `/api/chats/${params.chatId}/completions`,
1338
+ params: z.object({ chatId: z.string().uuid() }),
1339
+ query: z.object({}),
1340
+ requestHeaders: z.object({ authorization: z.string() }),
1341
+ body: z.object({ message: z.string() }),
1342
+ jsonResponse: z.object({
1343
+ reply: z.string(),
1344
+ usage: z.object({ tokens: z.number() }),
1345
+ }),
1346
+ events: {
1347
+ chunk: z.object({ delta: z.string() }),
1348
+ done: z.object({ usage: z.object({ total: z.number() }) }),
1349
+ },
1350
+ })
1351
+ ```
1352
+
1353
+ **Note**: Dual-mode contracts use `pathResolver` instead of static `path` for type-safe path construction. The `pathResolver` function receives typed params and returns the URL path.
1354
+
1355
+ ### Implementing Dual-Mode Controllers
1356
+
1357
+ Dual-mode controllers use `buildDualModeHandler` to define both JSON and SSE handlers:
1358
+
1359
+ ```ts
1360
+ import {
1361
+ AbstractDualModeController,
1362
+ buildDualModeHandler,
1363
+ type BuildFastifyDualModeRoutesReturnType,
1364
+ type DualModeControllerConfig,
1365
+ } from 'opinionated-machine'
1366
+
1367
+ type Contracts = {
1368
+ chatCompletion: typeof chatCompletionContract
1369
+ }
1370
+
1371
+ type Dependencies = {
1372
+ aiService: AIService
1373
+ }
1374
+
1375
+ export class ChatDualModeController extends AbstractDualModeController<Contracts> {
1376
+ public static contracts = {
1377
+ chatCompletion: chatCompletionContract,
1378
+ } as const
1379
+
1380
+ private readonly aiService: AIService
1381
+
1382
+ constructor(deps: Dependencies, config?: DualModeControllerConfig) {
1383
+ super(deps, config)
1384
+ this.aiService = deps.aiService
1385
+ }
1386
+
1387
+ public buildDualModeRoutes(): BuildFastifyDualModeRoutesReturnType<Contracts> {
1388
+ return {
1389
+ chatCompletion: {
1390
+ contract: ChatDualModeController.contracts.chatCompletion,
1391
+ handlers: buildDualModeHandler(chatCompletionContract, {
1392
+ // JSON mode - return complete response
1393
+ json: async (ctx) => {
1394
+ const result = await this.aiService.complete(ctx.request.body.message)
1395
+ return {
1396
+ reply: result.text,
1397
+ usage: { tokens: result.tokenCount },
1398
+ }
1399
+ },
1400
+ // SSE mode - stream response chunks
1401
+ sse: async (ctx) => {
1402
+ let totalTokens = 0
1403
+ for await (const chunk of this.aiService.stream(ctx.request.body.message)) {
1404
+ await ctx.connection.send('chunk', { delta: chunk.text })
1405
+ totalTokens += chunk.tokenCount ?? 0
1406
+ }
1407
+ await ctx.connection.send('done', { usage: { total: totalTokens } })
1408
+ this.closeConnection(ctx.connection.id)
1409
+ },
1410
+ }),
1411
+ options: {
1412
+ // Optional: set SSE as default mode (instead of JSON)
1413
+ defaultMode: 'sse',
1414
+ // Optional: route-level authentication
1415
+ preHandler: (request, reply) => {
1416
+ if (!request.headers.authorization) {
1417
+ return Promise.resolve(reply.code(401).send({ error: 'Unauthorized' }))
1418
+ }
1419
+ },
1420
+ // Optional: SSE lifecycle hooks
1421
+ onConnect: (conn) => console.log('Client connected:', conn.id),
1422
+ onDisconnect: (conn) => console.log('Client disconnected:', conn.id),
1423
+ },
1424
+ },
1425
+ }
1426
+ }
1427
+ }
1428
+ ```
1429
+
1430
+ **Handler Context:**
1431
+
1432
+ | Mode | Context Properties |
1433
+ | ---- | ------------------ |
1434
+ | `json` | `ctx.mode`, `ctx.request`, `ctx.reply` |
1435
+ | `sse` | `ctx.mode`, `ctx.connection`, `ctx.request` |
1436
+
1437
+ The `json` handler must return a value matching `jsonResponse` schema. The `sse` handler uses `ctx.connection.send()` for type-safe event streaming.
1438
+
1439
+ ### Registering Dual-Mode Controllers
1440
+
1441
+ Use `asDualModeControllerClass` in your module:
1442
+
1443
+ ```ts
1444
+ import {
1445
+ AbstractModule,
1446
+ asControllerClass,
1447
+ asDualModeControllerClass,
1448
+ asServiceClass,
1449
+ } from 'opinionated-machine'
1450
+
1451
+ export class ChatModule extends AbstractModule<Dependencies> {
1452
+ resolveDependencies() {
1453
+ return {
1454
+ aiService: asServiceClass(AIService),
1455
+ }
1456
+ }
1457
+
1458
+ resolveControllers(diOptions: DependencyInjectionOptions) {
1459
+ return {
1460
+ // REST controller
1461
+ usersController: asControllerClass(UsersController),
1462
+ // Dual-mode controller (auto-detected via isDualModeController flag)
1463
+ chatController: asDualModeControllerClass(ChatDualModeController, { diOptions }),
1464
+ }
1465
+ }
1466
+ }
1467
+ ```
1468
+
1469
+ Register dual-mode routes after the `@fastify/sse` plugin:
1470
+
1471
+ ```ts
1472
+ const app = fastify()
1473
+ app.setValidatorCompiler(validatorCompiler)
1474
+ app.setSerializerCompiler(serializerCompiler)
1475
+
1476
+ // Register @fastify/sse plugin
1477
+ await app.register(FastifySSEPlugin)
1478
+
1479
+ // Register routes
1480
+ context.registerRoutes(app) // REST routes
1481
+ context.registerSSERoutes(app) // SSE-only routes
1482
+ context.registerDualModeRoutes(app) // Dual-mode routes
1483
+
1484
+ // Check if controllers exist before registration (optional)
1485
+ if (context.hasDualModeControllers()) {
1486
+ context.registerDualModeRoutes(app)
1487
+ }
1488
+
1489
+ await app.ready()
1490
+ ```
1491
+
1492
+ ### Accept Header Routing
1493
+
1494
+ The `Accept` header determines response mode:
1495
+
1496
+ ```bash
1497
+ # JSON mode (complete response)
1498
+ curl -X POST http://localhost:3000/api/chats/123/completions \
1499
+ -H "Content-Type: application/json" \
1500
+ -H "Accept: application/json" \
1501
+ -d '{"message": "Hello world"}'
1502
+
1503
+ # SSE mode (streaming response)
1504
+ curl -X POST http://localhost:3000/api/chats/123/completions \
1505
+ -H "Content-Type: application/json" \
1506
+ -H "Accept: text/event-stream" \
1507
+ -d '{"message": "Hello world"}'
1508
+ ```
1509
+
1510
+ **Quality values** are supported for content negotiation:
1511
+
1512
+ ```bash
1513
+ # Prefer JSON (higher quality value)
1514
+ curl -H "Accept: text/event-stream;q=0.5, application/json;q=1.0" ...
1515
+
1516
+ # Prefer SSE (higher quality value)
1517
+ curl -H "Accept: application/json;q=0.5, text/event-stream;q=1.0" ...
1518
+ ```
1519
+
1520
+ ### Testing Dual-Mode Controllers
1521
+
1522
+ Test both JSON and SSE modes:
1523
+
1524
+ ```ts
1525
+ import { createContainer } from 'awilix'
1526
+ import { DIContext, SSETestServer, SSEInjectClient } from 'opinionated-machine'
1527
+
1528
+ describe('ChatDualModeController', () => {
1529
+ let server: SSETestServer
1530
+ let injectClient: SSEInjectClient
1531
+
1532
+ beforeEach(async () => {
1533
+ const container = createContainer({ injectionMode: 'PROXY' })
1534
+ const context = new DIContext(container, { isTestMode: true }, {})
1535
+ context.registerDependencies({ modules: [new ChatModule()] }, undefined)
1536
+
1537
+ server = await SSETestServer.create(
1538
+ (app) => {
1539
+ context.registerDualModeRoutes(app)
1540
+ },
1541
+ {
1542
+ configureApp: (app) => {
1543
+ app.setValidatorCompiler(validatorCompiler)
1544
+ app.setSerializerCompiler(serializerCompiler)
1545
+ },
1546
+ setup: () => ({ context }),
1547
+ },
1548
+ )
1549
+
1550
+ injectClient = new SSEInjectClient(server.app)
1551
+ })
1552
+
1553
+ afterEach(async () => {
1554
+ await server.resources.context.destroy()
1555
+ await server.close()
1556
+ })
1557
+
1558
+ it('returns JSON for Accept: application/json', async () => {
1559
+ const response = await server.app.inject({
1560
+ method: 'POST',
1561
+ url: '/api/chats/550e8400-e29b-41d4-a716-446655440000/completions',
1562
+ headers: {
1563
+ 'content-type': 'application/json',
1564
+ accept: 'application/json',
1565
+ authorization: 'Bearer token',
1566
+ },
1567
+ payload: { message: 'Hello' },
1568
+ })
1569
+
1570
+ expect(response.statusCode).toBe(200)
1571
+ expect(response.headers['content-type']).toContain('application/json')
1572
+
1573
+ const body = JSON.parse(response.body)
1574
+ expect(body).toHaveProperty('reply')
1575
+ expect(body).toHaveProperty('usage')
1576
+ })
1577
+
1578
+ it('streams SSE for Accept: text/event-stream', async () => {
1579
+ const conn = await injectClient.connectWithBody(
1580
+ '/api/chats/550e8400-e29b-41d4-a716-446655440000/completions',
1581
+ { message: 'Hello' },
1582
+ { headers: { authorization: 'Bearer token' } },
1583
+ )
1584
+
1585
+ expect(conn.getStatusCode()).toBe(200)
1586
+ expect(conn.getHeaders()['content-type']).toContain('text/event-stream')
1587
+
1588
+ const events = conn.getReceivedEvents()
1589
+ const chunks = events.filter((e) => e.event === 'chunk')
1590
+ const doneEvents = events.filter((e) => e.event === 'done')
1591
+
1592
+ expect(chunks.length).toBeGreaterThan(0)
1593
+ expect(doneEvents).toHaveLength(1)
1594
+ })
1595
+ })
1596
+
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ 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 * from './lib/dualmode/index.js';
7
8
  export * from './lib/resolverFunctions.js';
8
9
  export * from './lib/sse/index.js';
9
10
  export * from './lib/testing/index.js';
package/dist/index.js CHANGED
@@ -3,6 +3,8 @@ 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
+ // Dual-mode (SSE + JSON)
7
+ export * from './lib/dualmode/index.js';
6
8
  export * from './lib/resolverFunctions.js';
7
9
  // SSE
8
10
  export * from './lib/sse/index.js';
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;AAC/B,cAAc,4BAA4B,CAAA;AAC1C,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,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,yBAAyB;AACzB,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,MAAM;AACN,cAAc,oBAAoB,CAAA;AAClC,wBAAwB;AACxB,cAAc,wBAAwB,CAAA"}
@@ -4,7 +4,8 @@ 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
+ import type { RegisterDualModeRoutesOptions } from './dualmode/fastifyDualModeTypes.js';
8
+ import { type RegisterSSERoutesOptions } from './sse/fastifySSERouteBuilder.js';
8
9
  export type RegisterDependenciesParams<Dependencies, Config, ExternalDependencies> = {
9
10
  modules: readonly AbstractModule<unknown, ExternalDependencies>[];
10
11
  secondaryModules?: readonly AbstractModule<unknown, ExternalDependencies>[];
@@ -30,6 +31,7 @@ export declare class DIContext<Dependencies extends object, Config extends objec
30
31
  readonly diContainer: AwilixContainer<Dependencies>;
31
32
  private readonly controllerResolvers;
32
33
  private readonly sseControllerNames;
34
+ private readonly dualModeControllerNames;
33
35
  private readonly appConfig;
34
36
  constructor(diContainer: AwilixContainer, options: DependencyInjectionOptions, appConfig: Config, awilixManager?: AwilixManager);
35
37
  private registerModule;
@@ -40,6 +42,11 @@ export declare class DIContext<Dependencies extends object, Config extends objec
40
42
  * Use this to conditionally call registerSSERoutes().
41
43
  */
42
44
  hasSSEControllers(): boolean;
45
+ /**
46
+ * Check if any dual-mode controllers are registered.
47
+ * Use this to conditionally call registerDualModeRoutes().
48
+ */
49
+ hasDualModeControllers(): boolean;
43
50
  /**
44
51
  * Register SSE routes with the Fastify app.
45
52
  *
@@ -59,6 +66,29 @@ export declare class DIContext<Dependencies extends object, Config extends objec
59
66
  * ```
60
67
  */
61
68
  registerSSERoutes(app: FastifyInstance<any, any, any, any>, options?: RegisterSSERoutesOptions): void;
69
+ /**
70
+ * Register dual-mode routes with the Fastify app.
71
+ *
72
+ * Dual-mode routes handle both SSE streaming and JSON responses on the
73
+ * same path, automatically branching based on the `Accept` header.
74
+ *
75
+ * Must be called separately from registerRoutes() and registerSSERoutes().
76
+ * Requires @fastify/sse plugin to be registered on the app.
77
+ *
78
+ * @param app - Fastify instance with @fastify/sse registered
79
+ * @param options - Optional configuration for dual-mode routes
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // Register @fastify/sse plugin first
84
+ * await app.register(fastifySSE, { heartbeatInterval: 30000 })
85
+ *
86
+ * // Then register dual-mode routes
87
+ * context.registerDualModeRoutes(app)
88
+ * ```
89
+ */
90
+ registerDualModeRoutes(app: FastifyInstance<any, any, any, any>, options?: RegisterDualModeRoutesOptions): void;
91
+ private applyDualModeRouteOptions;
62
92
  private applySSERouteOptions;
63
93
  private applyPreHandlers;
64
94
  private applyRateLimit;
@@ -1,7 +1,8 @@
1
1
  import { AwilixManager } from 'awilix-manager';
2
2
  import { merge } from 'ts-deepmerge';
3
3
  import { mergeConfigAndDependencyOverrides } from './configUtils.js';
4
- import { buildFastifySSERoute } from './sse/sseRouteBuilder.js';
4
+ import { buildFastifyDualModeRoute } from './dualmode/fastifyDualModeRouteBuilder.js';
5
+ import { buildFastifySSERoute, } from './sse/fastifySSERouteBuilder.js';
5
6
  export class DIContext {
6
7
  options;
7
8
  awilixManager;
@@ -10,6 +11,8 @@ export class DIContext {
10
11
  controllerResolvers;
11
12
  // SSE controller dependency names (resolved from container to preserve singletons)
12
13
  sseControllerNames;
14
+ // Dual-mode controller dependency names (resolved from container to preserve singletons)
15
+ dualModeControllerNames;
13
16
  appConfig;
14
17
  constructor(diContainer, options, appConfig, awilixManager) {
15
18
  this.options = options;
@@ -26,6 +29,7 @@ export class DIContext {
26
29
  });
27
30
  this.controllerResolvers = [];
28
31
  this.sseControllerNames = [];
32
+ this.dualModeControllerNames = [];
29
33
  }
30
34
  registerModule(module, targetDiConfig, externalDependencies, resolveControllers, isPrimaryModule) {
31
35
  const resolvedDIConfig = module.resolveDependencies(this.options, externalDependencies);
@@ -39,8 +43,15 @@ export class DIContext {
39
43
  if (isPrimaryModule && resolveControllers) {
40
44
  const controllers = module.resolveControllers(this.options);
41
45
  for (const [name, resolver] of Object.entries(controllers)) {
42
- // @ts-expect-error isSSEController is a custom property on the resolver
43
- if (resolver.isSSEController) {
46
+ // @ts-expect-error isDualModeController is a custom property on the resolver
47
+ if (resolver.isDualModeController) {
48
+ // Dual-mode controller: register in DI container and track name for route registration
49
+ this.dualModeControllerNames.push(name);
50
+ // @ts-expect-error we can't really ensure type-safety here
51
+ targetDiConfig[name] = resolver;
52
+ // @ts-expect-error isSSEController is a custom property on the resolver
53
+ }
54
+ else if (resolver.isSSEController) {
44
55
  // SSE controller: register in DI container and track name for route registration
45
56
  this.sseControllerNames.push(name);
46
57
  // @ts-expect-error we can't really ensure type-safety here
@@ -99,6 +110,13 @@ export class DIContext {
99
110
  hasSSEControllers() {
100
111
  return this.sseControllerNames.length > 0;
101
112
  }
113
+ /**
114
+ * Check if any dual-mode controllers are registered.
115
+ * Use this to conditionally call registerDualModeRoutes().
116
+ */
117
+ hasDualModeControllers() {
118
+ return this.dualModeControllerNames.length > 0;
119
+ }
102
120
  /**
103
121
  * Register SSE routes with the Fastify app.
104
122
  *
@@ -134,6 +152,65 @@ export class DIContext {
134
152
  }
135
153
  }
136
154
  }
155
+ /**
156
+ * Register dual-mode routes with the Fastify app.
157
+ *
158
+ * Dual-mode routes handle both SSE streaming and JSON responses on the
159
+ * same path, automatically branching based on the `Accept` header.
160
+ *
161
+ * Must be called separately from registerRoutes() and registerSSERoutes().
162
+ * Requires @fastify/sse plugin to be registered on the app.
163
+ *
164
+ * @param app - Fastify instance with @fastify/sse registered
165
+ * @param options - Optional configuration for dual-mode routes
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * // Register @fastify/sse plugin first
170
+ * await app.register(fastifySSE, { heartbeatInterval: 30000 })
171
+ *
172
+ * // Then register dual-mode routes
173
+ * context.registerDualModeRoutes(app)
174
+ * ```
175
+ */
176
+ registerDualModeRoutes(
177
+ // biome-ignore lint/suspicious/noExplicitAny: Fastify instance types are complex
178
+ app, options) {
179
+ if (!this.hasDualModeControllers()) {
180
+ return;
181
+ }
182
+ for (const controllerName of this.dualModeControllerNames) {
183
+ // Resolve from container to use the singleton instance
184
+ const dualModeController = this.diContainer.resolve(controllerName);
185
+ const dualModeRoutes = dualModeController.buildDualModeRoutes();
186
+ for (const routeConfig of Object.values(dualModeRoutes)) {
187
+ const route = buildFastifyDualModeRoute(dualModeController, routeConfig);
188
+ this.applyDualModeRouteOptions(route, options);
189
+ app.route(route);
190
+ }
191
+ }
192
+ }
193
+ applyDualModeRouteOptions(route, options) {
194
+ if (options?.preHandler) {
195
+ this.applyPreHandlers(route, options.preHandler);
196
+ }
197
+ if (options?.rateLimit) {
198
+ this.applyRateLimit(route, options.rateLimit);
199
+ }
200
+ // Apply SSE-specific options (heartbeatInterval, serializer) for SSE mode
201
+ if (options?.heartbeatInterval !== undefined || options?.serializer !== undefined) {
202
+ // biome-ignore lint/suspicious/noExplicitAny: config types vary by plugins
203
+ const routeWithConfig = route;
204
+ routeWithConfig.config = merge(routeWithConfig.config || {}, {
205
+ sse: {
206
+ ...(options.heartbeatInterval !== undefined && {
207
+ heartbeatInterval: options.heartbeatInterval,
208
+ }),
209
+ ...(options.serializer !== undefined && { serializer: options.serializer }),
210
+ },
211
+ });
212
+ }
213
+ }
137
214
  applySSERouteOptions(route, options) {
138
215
  if (options?.preHandler) {
139
216
  this.applyPreHandlers(route, options.preHandler);
@@ -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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"}