@pattern-stack/codegen 0.13.0 → 0.14.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 (161) hide show
  1. package/dist/{job-orchestrator.protocol-CHOEqBDk.d.ts → job-orchestrator.protocol-CARhMLCO.d.ts} +1 -1
  2. package/dist/runtime/subsystems/analytics/analytics.module.js +6 -2
  3. package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -1
  4. package/dist/runtime/subsystems/analytics/analytics.tokens.d.ts +0 -11
  5. package/dist/runtime/subsystems/analytics/analytics.tokens.js +6 -2
  6. package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -1
  7. package/dist/runtime/subsystems/analytics/cube-backend.js +6 -2
  8. package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -1
  9. package/dist/runtime/subsystems/analytics/index.js +6 -2
  10. package/dist/runtime/subsystems/analytics/index.js.map +1 -1
  11. package/dist/runtime/subsystems/auth/auth.module.js +12 -6
  12. package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
  13. package/dist/runtime/subsystems/auth/auth.tokens.d.ts +0 -28
  14. package/dist/runtime/subsystems/auth/auth.tokens.js +12 -8
  15. package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
  16. package/dist/runtime/subsystems/auth/controllers/auth.controller.js +12 -5
  17. package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -1
  18. package/dist/runtime/subsystems/auth/index.js +12 -8
  19. package/dist/runtime/subsystems/auth/index.js.map +1 -1
  20. package/dist/runtime/subsystems/auth/middleware/requester-context.js +12 -1
  21. package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -1
  22. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.d.ts +1 -1
  23. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +10 -2
  24. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
  25. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +10 -2
  26. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
  27. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +1 -1
  28. package/dist/runtime/subsystems/bridge/bridge.module.js +14 -9
  29. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  30. package/dist/runtime/subsystems/bridge/bridge.protocol.d.ts +1 -1
  31. package/dist/runtime/subsystems/bridge/event-flow.service.d.ts +1 -1
  32. package/dist/runtime/subsystems/bridge/event-flow.service.js +9 -1
  33. package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
  34. package/dist/runtime/subsystems/bridge/index.d.ts +1 -1
  35. package/dist/runtime/subsystems/bridge/index.js +14 -9
  36. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  37. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +6 -1
  38. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -1
  39. package/dist/runtime/subsystems/cache/cache.memory-backend.js +6 -1
  40. package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -1
  41. package/dist/runtime/subsystems/cache/cache.module.js +6 -2
  42. package/dist/runtime/subsystems/cache/cache.module.js.map +1 -1
  43. package/dist/runtime/subsystems/cache/cache.tokens.d.ts +0 -10
  44. package/dist/runtime/subsystems/cache/cache.tokens.js +6 -2
  45. package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -1
  46. package/dist/runtime/subsystems/cache/index.js +6 -2
  47. package/dist/runtime/subsystems/cache/index.js.map +1 -1
  48. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +5 -0
  49. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  50. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +5 -0
  51. package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
  52. package/dist/runtime/subsystems/events/event-bus.redis-backend.js +5 -1
  53. package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
  54. package/dist/runtime/subsystems/events/events.module.js +5 -1
  55. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  56. package/dist/runtime/subsystems/events/events.tokens.d.ts +5 -11
  57. package/dist/runtime/subsystems/events/events.tokens.js +5 -1
  58. package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
  59. package/dist/runtime/subsystems/events/generated/bus.js +5 -0
  60. package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
  61. package/dist/runtime/subsystems/events/generated/index.js +5 -0
  62. package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
  63. package/dist/runtime/subsystems/events/index.js +5 -1
  64. package/dist/runtime/subsystems/events/index.js.map +1 -1
  65. package/dist/runtime/subsystems/index.d.ts +3 -3
  66. package/dist/runtime/subsystems/index.js +34 -26
  67. package/dist/runtime/subsystems/index.js.map +1 -1
  68. package/dist/runtime/subsystems/integration/incremental-read.d.ts +35 -8
  69. package/dist/runtime/subsystems/integration/incremental-read.js +9 -6
  70. package/dist/runtime/subsystems/integration/incremental-read.js.map +1 -1
  71. package/dist/runtime/subsystems/integration/index.d.ts +1 -1
  72. package/dist/runtime/subsystems/integration/index.js +9 -6
  73. package/dist/runtime/subsystems/integration/index.js.map +1 -1
  74. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +0 -9
  75. package/dist/runtime/subsystems/jobs/bullmq.config.js +6 -2
  76. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  77. package/dist/runtime/subsystems/jobs/index.d.ts +1 -1
  78. package/dist/runtime/subsystems/jobs/index.js +13 -9
  79. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  80. package/dist/runtime/subsystems/jobs/job-handler.base.d.ts +1 -1
  81. package/dist/runtime/subsystems/jobs/job-handler.base.js +5 -1
  82. package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
  83. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +1 -1
  84. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +10 -3
  85. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  86. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.d.ts +1 -1
  87. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +8 -1
  88. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js.map +1 -1
  89. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.d.ts +1 -1
  90. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +9 -1
  91. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
  92. package/dist/runtime/subsystems/jobs/job-orchestrator.protocol.d.ts +1 -1
  93. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +1 -1
  94. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +1 -1
  95. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +8 -2
  96. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
  97. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +1 -1
  98. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +8 -2
  99. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
  100. package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +1 -1
  101. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +1 -1
  102. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +5 -0
  103. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  104. package/dist/runtime/subsystems/jobs/job-worker.d.ts +1 -1
  105. package/dist/runtime/subsystems/jobs/job-worker.js +10 -4
  106. package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
  107. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +5 -2
  108. package/dist/runtime/subsystems/jobs/job-worker.module.js +13 -8
  109. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  110. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +11 -6
  111. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  112. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.d.ts +0 -11
  113. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js +8 -4
  114. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js.map +1 -1
  115. package/dist/runtime/subsystems/jobs/jobs-errors.d.ts +1 -1
  116. package/dist/runtime/subsystems/observability/index.d.ts +1 -1
  117. package/dist/runtime/subsystems/observability/index.js +9 -1
  118. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  119. package/dist/runtime/subsystems/observability/observability.module.js +9 -1
  120. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  121. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +1 -1
  122. package/dist/runtime/subsystems/observability/observability.service.d.ts +1 -1
  123. package/dist/runtime/subsystems/observability/observability.service.js +9 -1
  124. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  125. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +1 -1
  126. package/dist/runtime/subsystems/observability/reporters/index.d.ts +1 -1
  127. package/dist/runtime/subsystems/storage/index.js +5 -1
  128. package/dist/runtime/subsystems/storage/index.js.map +1 -1
  129. package/dist/runtime/subsystems/storage/storage.module.js +5 -1
  130. package/dist/runtime/subsystems/storage/storage.module.js.map +1 -1
  131. package/dist/runtime/subsystems/storage/storage.tokens.d.ts +0 -8
  132. package/dist/runtime/subsystems/storage/storage.tokens.js +5 -1
  133. package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -1
  134. package/dist/runtime/subsystems/token-key.d.ts +7 -0
  135. package/dist/runtime/subsystems/token-key.js +8 -0
  136. package/dist/runtime/subsystems/token-key.js.map +1 -0
  137. package/dist/src/cli/index.js +362 -233
  138. package/dist/src/cli/index.js.map +1 -1
  139. package/package.json +5 -1
  140. package/runtime/subsystems/analytics/analytics.tokens.ts +6 -2
  141. package/runtime/subsystems/auth/auth.tokens.ts +15 -8
  142. package/runtime/subsystems/cache/cache.tokens.ts +7 -2
  143. package/runtime/subsystems/events/events.tokens.ts +8 -1
  144. package/runtime/subsystems/index.ts +6 -1
  145. package/runtime/subsystems/integration/incremental-read.ts +43 -9
  146. package/runtime/subsystems/integration/index.ts +1 -0
  147. package/runtime/subsystems/jobs/bullmq.config.ts +5 -2
  148. package/runtime/subsystems/jobs/job-handler.base.ts +6 -1
  149. package/runtime/subsystems/jobs/job-worker.module.ts +5 -1
  150. package/runtime/subsystems/jobs/job-worker.ts +4 -1
  151. package/runtime/subsystems/jobs/jobs-domain.tokens.ts +10 -7
  152. package/runtime/subsystems/storage/storage.tokens.ts +6 -1
  153. package/runtime/subsystems/token-key.ts +7 -0
  154. package/src/config/runtime-mode.mjs +82 -0
  155. package/templates/entity/new/backend/modules/core/integration-source.ejs.t +3 -2
  156. package/templates/entity/new/clean-lite-ps/controller.ejs.t +1 -1
  157. package/templates/entity/new/clean-lite-ps/module.ejs.t +1 -1
  158. package/templates/entity/new/clean-lite-ps/prompt-extension.js +8 -2
  159. package/templates/entity/new/clean-lite-ps/repository.ejs.t +4 -4
  160. package/templates/entity/new/clean-lite-ps/service.ejs.t +4 -4
  161. package/templates/entity/new/prompt.js +49 -10
@@ -310,4 +310,4 @@ interface IJobOrchestrator {
310
310
  }>;
311
311
  }
312
312
 
313
- export { type CancelOptions as C, type DedupePolicy as D, HandlerRegistry as H, type IJobOrchestrator as I, JOB_HANDLER_METADATA_KEY as J, ParentClosePolicy as P, type RetryPolicy as R, type ScopeRef as S, type ConcurrencyPolicy as a, type HandlerRegistryEntry as b, JOB_HANDLER_REGISTRY as c, type JobContext as d, JobHandler as e, JobHandlerBase as f, type JobHandlerMeta as g, type JobPoolDef as h, type JobRun as i, type JobTrigger as j, type JobUpsertEntry as k, type SpawnChildOptions as l, type StartOptions as m, type StepOptions as n };
313
+ export { type CancelOptions as C, type DedupePolicy as D, HandlerRegistry as H, type IJobOrchestrator as I, JOB_HANDLER_METADATA_KEY as J, ParentClosePolicy as P, type RetryPolicy as R, type ScopeRef as S, type ConcurrencyPolicy as a, type HandlerRegistryEntry as b, JOB_HANDLER_REGISTRY as c, type JobContext as d, JobHandler as e, JobHandlerBase as f, type JobHandlerMeta as g, type JobPoolDef as h, type JobRun as i, type JobUpsertEntry as j, type SpawnChildOptions as k, type StartOptions as l, type StepOptions as m, type JobTrigger as n };
@@ -13,10 +13,14 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
13
13
  // runtime/subsystems/analytics/analytics.module.ts
14
14
  import { Module } from "@nestjs/common";
15
15
 
16
+ // runtime/subsystems/token-key.ts
17
+ var PKG = "@pattern-stack/codegen";
18
+ var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
19
+
16
20
  // runtime/subsystems/analytics/analytics.tokens.ts
17
21
  var ANALYTICS_QUERY = "ANALYTICS_QUERY";
18
- var CUBE_API_URL = /* @__PURE__ */ Symbol("CUBE_API_URL");
19
- var CUBE_API_SECRET = /* @__PURE__ */ Symbol("CUBE_API_SECRET");
22
+ var CUBE_API_URL = Symbol.for(tokenKey("analytics", "cube-api-url"));
23
+ var CUBE_API_SECRET = Symbol.for(tokenKey("analytics", "cube-api-secret"));
20
24
 
21
25
  // runtime/subsystems/analytics/cube-backend.ts
22
26
  import { Inject, Injectable, Logger } from "@nestjs/common";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/analytics/analytics.module.ts","../../../../runtime/subsystems/analytics/analytics.tokens.ts","../../../../runtime/subsystems/analytics/cube-backend.ts","../../../../runtime/subsystems/analytics/noop-backend.ts"],"sourcesContent":["/**\n * AnalyticsModule — DynamicModule factory for the analytics query subsystem.\n *\n * Register once in AppModule:\n * ```typescript\n * @Module({\n * imports: [\n * AnalyticsModule.forRoot({ backend: 'cube' }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * Tests swap to the noop backend without touching application code:\n * ```typescript\n * Test.createTestingModule({\n * imports: [AnalyticsModule.forRoot({ backend: 'noop' })],\n * });\n * ```\n *\n * `global: true` means entity modules do not need to import AnalyticsModule\n * individually — the ANALYTICS_QUERY token is available project-wide.\n */\nimport { Module, type DynamicModule } from '@nestjs/common';\nimport { ANALYTICS_QUERY, CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport { CubeAnalyticsBackend } from './cube-backend';\nimport { NoopAnalyticsBackend } from './noop-backend';\n\nexport interface AnalyticsModuleOptions {\n backend: 'cube' | 'noop';\n}\n\n@Module({})\nexport class AnalyticsModule {\n static forRoot(\n options: AnalyticsModuleOptions = { backend: 'noop' },\n ): DynamicModule {\n if (options.backend === 'cube') {\n return {\n module: AnalyticsModule,\n global: true,\n providers: [\n {\n provide: CUBE_API_URL,\n useValue: process.env['CUBE_API_URL'] ?? 'http://localhost:4000/cubejs-api/v1',\n },\n {\n provide: CUBE_API_SECRET,\n useValue: process.env['CUBE_API_SECRET'] ?? '',\n },\n { provide: ANALYTICS_QUERY, useClass: CubeAnalyticsBackend },\n ],\n exports: [ANALYTICS_QUERY],\n };\n }\n\n return {\n module: AnalyticsModule,\n global: true,\n providers: [{ provide: ANALYTICS_QUERY, useClass: NoopAnalyticsBackend }],\n exports: [ANALYTICS_QUERY],\n };\n }\n}\n","/**\n * Injection tokens for the analytics subsystem.\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as events.tokens.ts.\n *\n * Usage in services:\n * ```typescript\n * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}\n * ```\n */\nexport const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;\n\n/**\n * Injection token for the cube.js API URL.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_URL = Symbol('CUBE_API_URL');\n\n/**\n * Injection token for the cube.js API secret.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_SECRET = Symbol('CUBE_API_SECRET');\n","/**\n * CubeAnalyticsBackend — cube.js backend for the analytics query port.\n *\n * Connects to a running cube.js instance via @cubejs-client/core and\n * translates IAnalyticsQuery calls into cube.js query objects.\n *\n * @cubejs-client/core is an optional peer dependency; lazy-imported so\n * the module loads even if the package isn't installed. Consumers who\n * use the cube backend must install it separately:\n * bun add @cubejs-client/core\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nimport { Inject, Injectable, OnModuleInit, Logger } from '@nestjs/common';\nimport { CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class CubeAnalyticsBackend implements IAnalyticsQuery, OnModuleInit {\n private readonly logger = new Logger(CubeAnalyticsBackend.name);\n private cubejsApi: any;\n\n constructor(\n @Inject(CUBE_API_URL) private readonly apiUrl: string,\n @Inject(CUBE_API_SECRET) private readonly apiSecret: string,\n ) {}\n\n async onModuleInit(): Promise<void> {\n try {\n const { default: cubejs } = await import('@cubejs-client/core');\n this.cubejsApi = cubejs(this.apiSecret, { apiUrl: this.apiUrl });\n } catch {\n throw new Error(\n 'CubeAnalyticsBackend requires @cubejs-client/core. Install it: bun add @cubejs-client/core',\n );\n }\n }\n\n async execute(\n cube: string,\n measures: string[],\n dimensions: string[],\n where?: Record<string, any>,\n opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n if (!this.cubejsApi) {\n this.logger.warn('Cube.js client not initialized — returning empty result');\n return [];\n }\n\n const query: Record<string, any> = {\n measures: measures.map((m) => cube + '.' + m),\n dimensions: dimensions.map((d) => cube + '.' + d),\n };\n\n if (where && Object.keys(where).length > 0) {\n query.filters = Object.entries(where).map(([member, value]) => ({\n member: cube + '.' + member,\n operator: 'equals',\n values: Array.isArray(value) ? value : [String(value)],\n }));\n }\n\n if (opts?.limit) {\n query.limit = opts.limit;\n }\n\n const resultSet = await this.cubejsApi.load(query);\n return resultSet.tablePivot() as ResultRow[];\n }\n}\n","/**\n * NoopAnalyticsBackend — no-op backend for the analytics query port.\n *\n * Returns empty arrays for all queries. Use this backend when analytics\n * is disabled or in tests that don't need real analytics data.\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'noop' }).\n */\nimport { Injectable } from '@nestjs/common';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class NoopAnalyticsBackend implements IAnalyticsQuery {\n async execute(\n _cube: string,\n _measures: string[],\n _dimensions: string[],\n _where?: Record<string, any>,\n _opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n return [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,SAAS,cAAkC;;;ACZpC,IAAM,kBAAkB;AAMxB,IAAM,eAAe,uBAAO,cAAc;AAM1C,IAAM,kBAAkB,uBAAO,iBAAiB;;;ACVvD,SAAS,QAAQ,YAA0B,cAAc;AASlD,IAAM,uBAAN,MAAoE;AAAA,EAIzE,YACyC,QACG,WAC1C;AAFuC;AACG;AAAA,EACzC;AAAA,EAFsC;AAAA,EACG;AAAA,EAL3B,SAAS,IAAI,OAAO,qBAAqB,IAAI;AAAA,EACtD;AAAA,EAOR,MAAM,eAA8B;AAClC,QAAI;AACF,YAAM,EAAE,SAAS,OAAO,IAAI,MAAM,OAAO,qBAAqB;AAC9D,WAAK,YAAY,OAAO,KAAK,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjE,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,MACA,UACA,YACA,OACA,MACsB;AACtB,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,OAAO,KAAK,8DAAyD;AAC1E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,QAA6B;AAAA,MACjC,UAAU,SAAS,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,MAC5C,YAAY,WAAW,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,IAClD;AAEA,QAAI,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AAC1C,YAAM,UAAU,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;AAAA,QAC9D,QAAQ,OAAO,MAAM;AAAA,QACrB,UAAU;AAAA,QACV,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC;AAAA,MACvD,EAAE;AAAA,IACJ;AAEA,QAAI,MAAM,OAAO;AACf,YAAM,QAAQ,KAAK;AAAA,IACrB;AAEA,UAAM,YAAY,MAAM,KAAK,UAAU,KAAK,KAAK;AACjD,WAAO,UAAU,WAAW;AAAA,EAC9B;AACF;AApDa,uBAAN;AAAA,EADN,WAAW;AAAA,EAMP,0BAAO,YAAY;AAAA,EACnB,0BAAO,eAAe;AAAA,GANd;;;ACdb,SAAS,cAAAA,mBAAkB;AAQpB,IAAM,uBAAN,MAAsD;AAAA,EAC3D,MAAM,QACJ,OACA,WACA,aACA,QACA,OACsB;AACtB,WAAO,CAAC;AAAA,EACV;AACF;AAVa,uBAAN;AAAA,EADNC,YAAW;AAAA,GACC;;;AHiBN,IAAM,kBAAN,MAAsB;AAAA,EAC3B,OAAO,QACL,UAAkC,EAAE,SAAS,OAAO,GACrC;AACf,QAAI,QAAQ,YAAY,QAAQ;AAC9B,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,WAAW;AAAA,UACT;AAAA,YACE,SAAS;AAAA,YACT,UAAU,QAAQ,IAAI,cAAc,KAAK;AAAA,UAC3C;AAAA,UACA;AAAA,YACE,SAAS;AAAA,YACT,UAAU,QAAQ,IAAI,iBAAiB,KAAK;AAAA,UAC9C;AAAA,UACA,EAAE,SAAS,iBAAiB,UAAU,qBAAqB;AAAA,QAC7D;AAAA,QACA,SAAS,CAAC,eAAe;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,WAAW,CAAC,EAAE,SAAS,iBAAiB,UAAU,qBAAqB,CAAC;AAAA,MACxE,SAAS,CAAC,eAAe;AAAA,IAC3B;AAAA,EACF;AACF;AA9Ba,kBAAN;AAAA,EADN,OAAO,CAAC,CAAC;AAAA,GACG;","names":["Injectable","Injectable"]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/analytics/analytics.module.ts","../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/analytics/analytics.tokens.ts","../../../../runtime/subsystems/analytics/cube-backend.ts","../../../../runtime/subsystems/analytics/noop-backend.ts"],"sourcesContent":["/**\n * AnalyticsModule — DynamicModule factory for the analytics query subsystem.\n *\n * Register once in AppModule:\n * ```typescript\n * @Module({\n * imports: [\n * AnalyticsModule.forRoot({ backend: 'cube' }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * Tests swap to the noop backend without touching application code:\n * ```typescript\n * Test.createTestingModule({\n * imports: [AnalyticsModule.forRoot({ backend: 'noop' })],\n * });\n * ```\n *\n * `global: true` means entity modules do not need to import AnalyticsModule\n * individually — the ANALYTICS_QUERY token is available project-wide.\n */\nimport { Module, type DynamicModule } from '@nestjs/common';\nimport { ANALYTICS_QUERY, CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport { CubeAnalyticsBackend } from './cube-backend';\nimport { NoopAnalyticsBackend } from './noop-backend';\n\nexport interface AnalyticsModuleOptions {\n backend: 'cube' | 'noop';\n}\n\n@Module({})\nexport class AnalyticsModule {\n static forRoot(\n options: AnalyticsModuleOptions = { backend: 'noop' },\n ): DynamicModule {\n if (options.backend === 'cube') {\n return {\n module: AnalyticsModule,\n global: true,\n providers: [\n {\n provide: CUBE_API_URL,\n useValue: process.env['CUBE_API_URL'] ?? 'http://localhost:4000/cubejs-api/v1',\n },\n {\n provide: CUBE_API_SECRET,\n useValue: process.env['CUBE_API_SECRET'] ?? '',\n },\n { provide: ANALYTICS_QUERY, useClass: CubeAnalyticsBackend },\n ],\n exports: [ANALYTICS_QUERY],\n };\n }\n\n return {\n module: AnalyticsModule,\n global: true,\n providers: [{ provide: ANALYTICS_QUERY, useClass: NoopAnalyticsBackend }],\n exports: [ANALYTICS_QUERY],\n };\n }\n}\n","/** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded\n * constant (NOT derived from package.json) so a vendored copy — which lives inside the\n * CONSUMER's package — produces the identical key and the two copies share the symbol. */\nexport const PKG = '@pattern-stack/codegen';\n// TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only\n// (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.\nexport const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;\n","/**\n * Injection tokens for the analytics subsystem.\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as events.tokens.ts.\n *\n * Usage in services:\n * ```typescript\n * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}\n * ```\n */\nimport { tokenKey } from '../token-key';\n\nexport const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;\n\n// ADR-037: namespaced `Symbol.for(...)` keys (via `tokenKey()`) so these tokens\n// match by value across import boundaries (package vs vendored runtime copy).\n/**\n * Injection token for the cube.js API URL.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_URL = Symbol.for(tokenKey('analytics', 'cube-api-url'));\n\n/**\n * Injection token for the cube.js API secret.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_SECRET = Symbol.for(tokenKey('analytics', 'cube-api-secret'));\n","/**\n * CubeAnalyticsBackend — cube.js backend for the analytics query port.\n *\n * Connects to a running cube.js instance via @cubejs-client/core and\n * translates IAnalyticsQuery calls into cube.js query objects.\n *\n * @cubejs-client/core is an optional peer dependency; lazy-imported so\n * the module loads even if the package isn't installed. Consumers who\n * use the cube backend must install it separately:\n * bun add @cubejs-client/core\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nimport { Inject, Injectable, OnModuleInit, Logger } from '@nestjs/common';\nimport { CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class CubeAnalyticsBackend implements IAnalyticsQuery, OnModuleInit {\n private readonly logger = new Logger(CubeAnalyticsBackend.name);\n private cubejsApi: any;\n\n constructor(\n @Inject(CUBE_API_URL) private readonly apiUrl: string,\n @Inject(CUBE_API_SECRET) private readonly apiSecret: string,\n ) {}\n\n async onModuleInit(): Promise<void> {\n try {\n const { default: cubejs } = await import('@cubejs-client/core');\n this.cubejsApi = cubejs(this.apiSecret, { apiUrl: this.apiUrl });\n } catch {\n throw new Error(\n 'CubeAnalyticsBackend requires @cubejs-client/core. Install it: bun add @cubejs-client/core',\n );\n }\n }\n\n async execute(\n cube: string,\n measures: string[],\n dimensions: string[],\n where?: Record<string, any>,\n opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n if (!this.cubejsApi) {\n this.logger.warn('Cube.js client not initialized — returning empty result');\n return [];\n }\n\n const query: Record<string, any> = {\n measures: measures.map((m) => cube + '.' + m),\n dimensions: dimensions.map((d) => cube + '.' + d),\n };\n\n if (where && Object.keys(where).length > 0) {\n query.filters = Object.entries(where).map(([member, value]) => ({\n member: cube + '.' + member,\n operator: 'equals',\n values: Array.isArray(value) ? value : [String(value)],\n }));\n }\n\n if (opts?.limit) {\n query.limit = opts.limit;\n }\n\n const resultSet = await this.cubejsApi.load(query);\n return resultSet.tablePivot() as ResultRow[];\n }\n}\n","/**\n * NoopAnalyticsBackend — no-op backend for the analytics query port.\n *\n * Returns empty arrays for all queries. Use this backend when analytics\n * is disabled or in tests that don't need real analytics data.\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'noop' }).\n */\nimport { Injectable } from '@nestjs/common';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class NoopAnalyticsBackend implements IAnalyticsQuery {\n async execute(\n _cube: string,\n _measures: string[],\n _dimensions: string[],\n _where?: Record<string, any>,\n _opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n return [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,SAAS,cAAkC;;;ACpBpC,IAAM,MAAM;AAGZ,IAAM,WAAW,CAAC,MAAc,SAAyB,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI;;;ACO/E,IAAM,kBAAkB;AAQxB,IAAM,eAAe,OAAO,IAAI,SAAS,aAAa,cAAc,CAAC;AAMrE,IAAM,kBAAkB,OAAO,IAAI,SAAS,aAAa,iBAAiB,CAAC;;;ACdlF,SAAS,QAAQ,YAA0B,cAAc;AASlD,IAAM,uBAAN,MAAoE;AAAA,EAIzE,YACyC,QACG,WAC1C;AAFuC;AACG;AAAA,EACzC;AAAA,EAFsC;AAAA,EACG;AAAA,EAL3B,SAAS,IAAI,OAAO,qBAAqB,IAAI;AAAA,EACtD;AAAA,EAOR,MAAM,eAA8B;AAClC,QAAI;AACF,YAAM,EAAE,SAAS,OAAO,IAAI,MAAM,OAAO,qBAAqB;AAC9D,WAAK,YAAY,OAAO,KAAK,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjE,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,MACA,UACA,YACA,OACA,MACsB;AACtB,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,OAAO,KAAK,8DAAyD;AAC1E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,QAA6B;AAAA,MACjC,UAAU,SAAS,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,MAC5C,YAAY,WAAW,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,IAClD;AAEA,QAAI,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AAC1C,YAAM,UAAU,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;AAAA,QAC9D,QAAQ,OAAO,MAAM;AAAA,QACrB,UAAU;AAAA,QACV,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC;AAAA,MACvD,EAAE;AAAA,IACJ;AAEA,QAAI,MAAM,OAAO;AACf,YAAM,QAAQ,KAAK;AAAA,IACrB;AAEA,UAAM,YAAY,MAAM,KAAK,UAAU,KAAK,KAAK;AACjD,WAAO,UAAU,WAAW;AAAA,EAC9B;AACF;AApDa,uBAAN;AAAA,EADN,WAAW;AAAA,EAMP,0BAAO,YAAY;AAAA,EACnB,0BAAO,eAAe;AAAA,GANd;;;ACdb,SAAS,cAAAA,mBAAkB;AAQpB,IAAM,uBAAN,MAAsD;AAAA,EAC3D,MAAM,QACJ,OACA,WACA,aACA,QACA,OACsB;AACtB,WAAO,CAAC;AAAA,EACV;AACF;AAVa,uBAAN;AAAA,EADNC,YAAW;AAAA,GACC;;;AJiBN,IAAM,kBAAN,MAAsB;AAAA,EAC3B,OAAO,QACL,UAAkC,EAAE,SAAS,OAAO,GACrC;AACf,QAAI,QAAQ,YAAY,QAAQ;AAC9B,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,WAAW;AAAA,UACT;AAAA,YACE,SAAS;AAAA,YACT,UAAU,QAAQ,IAAI,cAAc,KAAK;AAAA,UAC3C;AAAA,UACA;AAAA,YACE,SAAS;AAAA,YACT,UAAU,QAAQ,IAAI,iBAAiB,KAAK;AAAA,UAC9C;AAAA,UACA,EAAE,SAAS,iBAAiB,UAAU,qBAAqB;AAAA,QAC7D;AAAA,QACA,SAAS,CAAC,eAAe;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,WAAW,CAAC,EAAE,SAAS,iBAAiB,UAAU,qBAAqB,CAAC;AAAA,MACxE,SAAS,CAAC,eAAe;AAAA,IAC3B;AAAA,EACF;AACF;AA9Ba,kBAAN;AAAA,EADN,OAAO,CAAC,CAAC;AAAA,GACG;","names":["Injectable","Injectable"]}
@@ -1,14 +1,3 @@
1
- /**
2
- * Injection tokens for the analytics subsystem.
3
- *
4
- * String constants (not Symbols) so they match by value across import
5
- * boundaries — same convention as events.tokens.ts.
6
- *
7
- * Usage in services:
8
- * ```typescript
9
- * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}
10
- * ```
11
- */
12
1
  declare const ANALYTICS_QUERY: "ANALYTICS_QUERY";
13
2
  /**
14
3
  * Injection token for the cube.js API URL.
@@ -1,7 +1,11 @@
1
+ // runtime/subsystems/token-key.ts
2
+ var PKG = "@pattern-stack/codegen";
3
+ var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
4
+
1
5
  // runtime/subsystems/analytics/analytics.tokens.ts
2
6
  var ANALYTICS_QUERY = "ANALYTICS_QUERY";
3
- var CUBE_API_URL = /* @__PURE__ */ Symbol("CUBE_API_URL");
4
- var CUBE_API_SECRET = /* @__PURE__ */ Symbol("CUBE_API_SECRET");
7
+ var CUBE_API_URL = Symbol.for(tokenKey("analytics", "cube-api-url"));
8
+ var CUBE_API_SECRET = Symbol.for(tokenKey("analytics", "cube-api-secret"));
5
9
  export {
6
10
  ANALYTICS_QUERY,
7
11
  CUBE_API_SECRET,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/analytics/analytics.tokens.ts"],"sourcesContent":["/**\n * Injection tokens for the analytics subsystem.\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as events.tokens.ts.\n *\n * Usage in services:\n * ```typescript\n * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}\n * ```\n */\nexport const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;\n\n/**\n * Injection token for the cube.js API URL.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_URL = Symbol('CUBE_API_URL');\n\n/**\n * Injection token for the cube.js API secret.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_SECRET = Symbol('CUBE_API_SECRET');\n"],"mappings":";AAWO,IAAM,kBAAkB;AAMxB,IAAM,eAAe,uBAAO,cAAc;AAM1C,IAAM,kBAAkB,uBAAO,iBAAiB;","names":[]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/analytics/analytics.tokens.ts"],"sourcesContent":["/** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded\n * constant (NOT derived from package.json) so a vendored copy — which lives inside the\n * CONSUMER's package — produces the identical key and the two copies share the symbol. */\nexport const PKG = '@pattern-stack/codegen';\n// TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only\n// (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.\nexport const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;\n","/**\n * Injection tokens for the analytics subsystem.\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as events.tokens.ts.\n *\n * Usage in services:\n * ```typescript\n * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}\n * ```\n */\nimport { tokenKey } from '../token-key';\n\nexport const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;\n\n// ADR-037: namespaced `Symbol.for(...)` keys (via `tokenKey()`) so these tokens\n// match by value across import boundaries (package vs vendored runtime copy).\n/**\n * Injection token for the cube.js API URL.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_URL = Symbol.for(tokenKey('analytics', 'cube-api-url'));\n\n/**\n * Injection token for the cube.js API secret.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_SECRET = Symbol.for(tokenKey('analytics', 'cube-api-secret'));\n"],"mappings":";AAGO,IAAM,MAAM;AAGZ,IAAM,WAAW,CAAC,MAAc,SAAyB,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI;;;ACO/E,IAAM,kBAAkB;AAQxB,IAAM,eAAe,OAAO,IAAI,SAAS,aAAa,cAAc,CAAC;AAMrE,IAAM,kBAAkB,OAAO,IAAI,SAAS,aAAa,iBAAiB,CAAC;","names":[]}
@@ -13,9 +13,13 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
13
13
  // runtime/subsystems/analytics/cube-backend.ts
14
14
  import { Inject, Injectable, Logger } from "@nestjs/common";
15
15
 
16
+ // runtime/subsystems/token-key.ts
17
+ var PKG = "@pattern-stack/codegen";
18
+ var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
19
+
16
20
  // runtime/subsystems/analytics/analytics.tokens.ts
17
- var CUBE_API_URL = /* @__PURE__ */ Symbol("CUBE_API_URL");
18
- var CUBE_API_SECRET = /* @__PURE__ */ Symbol("CUBE_API_SECRET");
21
+ var CUBE_API_URL = Symbol.for(tokenKey("analytics", "cube-api-url"));
22
+ var CUBE_API_SECRET = Symbol.for(tokenKey("analytics", "cube-api-secret"));
19
23
 
20
24
  // runtime/subsystems/analytics/cube-backend.ts
21
25
  var CubeAnalyticsBackend = class {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/analytics/cube-backend.ts","../../../../runtime/subsystems/analytics/analytics.tokens.ts"],"sourcesContent":["/**\n * CubeAnalyticsBackend — cube.js backend for the analytics query port.\n *\n * Connects to a running cube.js instance via @cubejs-client/core and\n * translates IAnalyticsQuery calls into cube.js query objects.\n *\n * @cubejs-client/core is an optional peer dependency; lazy-imported so\n * the module loads even if the package isn't installed. Consumers who\n * use the cube backend must install it separately:\n * bun add @cubejs-client/core\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nimport { Inject, Injectable, OnModuleInit, Logger } from '@nestjs/common';\nimport { CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class CubeAnalyticsBackend implements IAnalyticsQuery, OnModuleInit {\n private readonly logger = new Logger(CubeAnalyticsBackend.name);\n private cubejsApi: any;\n\n constructor(\n @Inject(CUBE_API_URL) private readonly apiUrl: string,\n @Inject(CUBE_API_SECRET) private readonly apiSecret: string,\n ) {}\n\n async onModuleInit(): Promise<void> {\n try {\n const { default: cubejs } = await import('@cubejs-client/core');\n this.cubejsApi = cubejs(this.apiSecret, { apiUrl: this.apiUrl });\n } catch {\n throw new Error(\n 'CubeAnalyticsBackend requires @cubejs-client/core. Install it: bun add @cubejs-client/core',\n );\n }\n }\n\n async execute(\n cube: string,\n measures: string[],\n dimensions: string[],\n where?: Record<string, any>,\n opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n if (!this.cubejsApi) {\n this.logger.warn('Cube.js client not initialized — returning empty result');\n return [];\n }\n\n const query: Record<string, any> = {\n measures: measures.map((m) => cube + '.' + m),\n dimensions: dimensions.map((d) => cube + '.' + d),\n };\n\n if (where && Object.keys(where).length > 0) {\n query.filters = Object.entries(where).map(([member, value]) => ({\n member: cube + '.' + member,\n operator: 'equals',\n values: Array.isArray(value) ? value : [String(value)],\n }));\n }\n\n if (opts?.limit) {\n query.limit = opts.limit;\n }\n\n const resultSet = await this.cubejsApi.load(query);\n return resultSet.tablePivot() as ResultRow[];\n }\n}\n","/**\n * Injection tokens for the analytics subsystem.\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as events.tokens.ts.\n *\n * Usage in services:\n * ```typescript\n * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}\n * ```\n */\nexport const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;\n\n/**\n * Injection token for the cube.js API URL.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_URL = Symbol('CUBE_API_URL');\n\n/**\n * Injection token for the cube.js API secret.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_SECRET = Symbol('CUBE_API_SECRET');\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAS,QAAQ,YAA0B,cAAc;;;ACIlD,IAAM,eAAe,uBAAO,cAAc;AAM1C,IAAM,kBAAkB,uBAAO,iBAAiB;;;ADDhD,IAAM,uBAAN,MAAoE;AAAA,EAIzE,YACyC,QACG,WAC1C;AAFuC;AACG;AAAA,EACzC;AAAA,EAFsC;AAAA,EACG;AAAA,EAL3B,SAAS,IAAI,OAAO,qBAAqB,IAAI;AAAA,EACtD;AAAA,EAOR,MAAM,eAA8B;AAClC,QAAI;AACF,YAAM,EAAE,SAAS,OAAO,IAAI,MAAM,OAAO,qBAAqB;AAC9D,WAAK,YAAY,OAAO,KAAK,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjE,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,MACA,UACA,YACA,OACA,MACsB;AACtB,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,OAAO,KAAK,8DAAyD;AAC1E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,QAA6B;AAAA,MACjC,UAAU,SAAS,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,MAC5C,YAAY,WAAW,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,IAClD;AAEA,QAAI,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AAC1C,YAAM,UAAU,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;AAAA,QAC9D,QAAQ,OAAO,MAAM;AAAA,QACrB,UAAU;AAAA,QACV,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC;AAAA,MACvD,EAAE;AAAA,IACJ;AAEA,QAAI,MAAM,OAAO;AACf,YAAM,QAAQ,KAAK;AAAA,IACrB;AAEA,UAAM,YAAY,MAAM,KAAK,UAAU,KAAK,KAAK;AACjD,WAAO,UAAU,WAAW;AAAA,EAC9B;AACF;AApDa,uBAAN;AAAA,EADN,WAAW;AAAA,EAMP,0BAAO,YAAY;AAAA,EACnB,0BAAO,eAAe;AAAA,GANd;","names":[]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/analytics/cube-backend.ts","../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/analytics/analytics.tokens.ts"],"sourcesContent":["/**\n * CubeAnalyticsBackend — cube.js backend for the analytics query port.\n *\n * Connects to a running cube.js instance via @cubejs-client/core and\n * translates IAnalyticsQuery calls into cube.js query objects.\n *\n * @cubejs-client/core is an optional peer dependency; lazy-imported so\n * the module loads even if the package isn't installed. Consumers who\n * use the cube backend must install it separately:\n * bun add @cubejs-client/core\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nimport { Inject, Injectable, OnModuleInit, Logger } from '@nestjs/common';\nimport { CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class CubeAnalyticsBackend implements IAnalyticsQuery, OnModuleInit {\n private readonly logger = new Logger(CubeAnalyticsBackend.name);\n private cubejsApi: any;\n\n constructor(\n @Inject(CUBE_API_URL) private readonly apiUrl: string,\n @Inject(CUBE_API_SECRET) private readonly apiSecret: string,\n ) {}\n\n async onModuleInit(): Promise<void> {\n try {\n const { default: cubejs } = await import('@cubejs-client/core');\n this.cubejsApi = cubejs(this.apiSecret, { apiUrl: this.apiUrl });\n } catch {\n throw new Error(\n 'CubeAnalyticsBackend requires @cubejs-client/core. Install it: bun add @cubejs-client/core',\n );\n }\n }\n\n async execute(\n cube: string,\n measures: string[],\n dimensions: string[],\n where?: Record<string, any>,\n opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n if (!this.cubejsApi) {\n this.logger.warn('Cube.js client not initialized — returning empty result');\n return [];\n }\n\n const query: Record<string, any> = {\n measures: measures.map((m) => cube + '.' + m),\n dimensions: dimensions.map((d) => cube + '.' + d),\n };\n\n if (where && Object.keys(where).length > 0) {\n query.filters = Object.entries(where).map(([member, value]) => ({\n member: cube + '.' + member,\n operator: 'equals',\n values: Array.isArray(value) ? value : [String(value)],\n }));\n }\n\n if (opts?.limit) {\n query.limit = opts.limit;\n }\n\n const resultSet = await this.cubejsApi.load(query);\n return resultSet.tablePivot() as ResultRow[];\n }\n}\n","/** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded\n * constant (NOT derived from package.json) so a vendored copy — which lives inside the\n * CONSUMER's package — produces the identical key and the two copies share the symbol. */\nexport const PKG = '@pattern-stack/codegen';\n// TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only\n// (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.\nexport const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;\n","/**\n * Injection tokens for the analytics subsystem.\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as events.tokens.ts.\n *\n * Usage in services:\n * ```typescript\n * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}\n * ```\n */\nimport { tokenKey } from '../token-key';\n\nexport const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;\n\n// ADR-037: namespaced `Symbol.for(...)` keys (via `tokenKey()`) so these tokens\n// match by value across import boundaries (package vs vendored runtime copy).\n/**\n * Injection token for the cube.js API URL.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_URL = Symbol.for(tokenKey('analytics', 'cube-api-url'));\n\n/**\n * Injection token for the cube.js API secret.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_SECRET = Symbol.for(tokenKey('analytics', 'cube-api-secret'));\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAS,QAAQ,YAA0B,cAAc;;;ACVlD,IAAM,MAAM;AAGZ,IAAM,WAAW,CAAC,MAAc,SAAyB,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI;;;ACe/E,IAAM,eAAe,OAAO,IAAI,SAAS,aAAa,cAAc,CAAC;AAMrE,IAAM,kBAAkB,OAAO,IAAI,SAAS,aAAa,iBAAiB,CAAC;;;AFL3E,IAAM,uBAAN,MAAoE;AAAA,EAIzE,YACyC,QACG,WAC1C;AAFuC;AACG;AAAA,EACzC;AAAA,EAFsC;AAAA,EACG;AAAA,EAL3B,SAAS,IAAI,OAAO,qBAAqB,IAAI;AAAA,EACtD;AAAA,EAOR,MAAM,eAA8B;AAClC,QAAI;AACF,YAAM,EAAE,SAAS,OAAO,IAAI,MAAM,OAAO,qBAAqB;AAC9D,WAAK,YAAY,OAAO,KAAK,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjE,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,MACA,UACA,YACA,OACA,MACsB;AACtB,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,OAAO,KAAK,8DAAyD;AAC1E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,QAA6B;AAAA,MACjC,UAAU,SAAS,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,MAC5C,YAAY,WAAW,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,IAClD;AAEA,QAAI,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AAC1C,YAAM,UAAU,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;AAAA,QAC9D,QAAQ,OAAO,MAAM;AAAA,QACrB,UAAU;AAAA,QACV,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC;AAAA,MACvD,EAAE;AAAA,IACJ;AAEA,QAAI,MAAM,OAAO;AACf,YAAM,QAAQ,KAAK;AAAA,IACrB;AAEA,UAAM,YAAY,MAAM,KAAK,UAAU,KAAK,KAAK;AACjD,WAAO,UAAU,WAAW;AAAA,EAC9B;AACF;AApDa,uBAAN;AAAA,EADN,WAAW;AAAA,EAMP,0BAAO,YAAY;AAAA,EACnB,0BAAO,eAAe;AAAA,GANd;","names":[]}
@@ -10,10 +10,14 @@ var __decorateClass = (decorators, target, key, kind) => {
10
10
  };
11
11
  var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
12
12
 
13
+ // runtime/subsystems/token-key.ts
14
+ var PKG = "@pattern-stack/codegen";
15
+ var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
16
+
13
17
  // runtime/subsystems/analytics/analytics.tokens.ts
14
18
  var ANALYTICS_QUERY = "ANALYTICS_QUERY";
15
- var CUBE_API_URL = /* @__PURE__ */ Symbol("CUBE_API_URL");
16
- var CUBE_API_SECRET = /* @__PURE__ */ Symbol("CUBE_API_SECRET");
19
+ var CUBE_API_URL = Symbol.for(tokenKey("analytics", "cube-api-url"));
20
+ var CUBE_API_SECRET = Symbol.for(tokenKey("analytics", "cube-api-secret"));
17
21
 
18
22
  // runtime/subsystems/analytics/analytics.module.ts
19
23
  import { Module } from "@nestjs/common";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/analytics/analytics.tokens.ts","../../../../runtime/subsystems/analytics/analytics.module.ts","../../../../runtime/subsystems/analytics/cube-backend.ts","../../../../runtime/subsystems/analytics/noop-backend.ts"],"sourcesContent":["/**\n * Injection tokens for the analytics subsystem.\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as events.tokens.ts.\n *\n * Usage in services:\n * ```typescript\n * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}\n * ```\n */\nexport const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;\n\n/**\n * Injection token for the cube.js API URL.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_URL = Symbol('CUBE_API_URL');\n\n/**\n * Injection token for the cube.js API secret.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_SECRET = Symbol('CUBE_API_SECRET');\n","/**\n * AnalyticsModule — DynamicModule factory for the analytics query subsystem.\n *\n * Register once in AppModule:\n * ```typescript\n * @Module({\n * imports: [\n * AnalyticsModule.forRoot({ backend: 'cube' }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * Tests swap to the noop backend without touching application code:\n * ```typescript\n * Test.createTestingModule({\n * imports: [AnalyticsModule.forRoot({ backend: 'noop' })],\n * });\n * ```\n *\n * `global: true` means entity modules do not need to import AnalyticsModule\n * individually — the ANALYTICS_QUERY token is available project-wide.\n */\nimport { Module, type DynamicModule } from '@nestjs/common';\nimport { ANALYTICS_QUERY, CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport { CubeAnalyticsBackend } from './cube-backend';\nimport { NoopAnalyticsBackend } from './noop-backend';\n\nexport interface AnalyticsModuleOptions {\n backend: 'cube' | 'noop';\n}\n\n@Module({})\nexport class AnalyticsModule {\n static forRoot(\n options: AnalyticsModuleOptions = { backend: 'noop' },\n ): DynamicModule {\n if (options.backend === 'cube') {\n return {\n module: AnalyticsModule,\n global: true,\n providers: [\n {\n provide: CUBE_API_URL,\n useValue: process.env['CUBE_API_URL'] ?? 'http://localhost:4000/cubejs-api/v1',\n },\n {\n provide: CUBE_API_SECRET,\n useValue: process.env['CUBE_API_SECRET'] ?? '',\n },\n { provide: ANALYTICS_QUERY, useClass: CubeAnalyticsBackend },\n ],\n exports: [ANALYTICS_QUERY],\n };\n }\n\n return {\n module: AnalyticsModule,\n global: true,\n providers: [{ provide: ANALYTICS_QUERY, useClass: NoopAnalyticsBackend }],\n exports: [ANALYTICS_QUERY],\n };\n }\n}\n","/**\n * CubeAnalyticsBackend — cube.js backend for the analytics query port.\n *\n * Connects to a running cube.js instance via @cubejs-client/core and\n * translates IAnalyticsQuery calls into cube.js query objects.\n *\n * @cubejs-client/core is an optional peer dependency; lazy-imported so\n * the module loads even if the package isn't installed. Consumers who\n * use the cube backend must install it separately:\n * bun add @cubejs-client/core\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nimport { Inject, Injectable, OnModuleInit, Logger } from '@nestjs/common';\nimport { CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class CubeAnalyticsBackend implements IAnalyticsQuery, OnModuleInit {\n private readonly logger = new Logger(CubeAnalyticsBackend.name);\n private cubejsApi: any;\n\n constructor(\n @Inject(CUBE_API_URL) private readonly apiUrl: string,\n @Inject(CUBE_API_SECRET) private readonly apiSecret: string,\n ) {}\n\n async onModuleInit(): Promise<void> {\n try {\n const { default: cubejs } = await import('@cubejs-client/core');\n this.cubejsApi = cubejs(this.apiSecret, { apiUrl: this.apiUrl });\n } catch {\n throw new Error(\n 'CubeAnalyticsBackend requires @cubejs-client/core. Install it: bun add @cubejs-client/core',\n );\n }\n }\n\n async execute(\n cube: string,\n measures: string[],\n dimensions: string[],\n where?: Record<string, any>,\n opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n if (!this.cubejsApi) {\n this.logger.warn('Cube.js client not initialized — returning empty result');\n return [];\n }\n\n const query: Record<string, any> = {\n measures: measures.map((m) => cube + '.' + m),\n dimensions: dimensions.map((d) => cube + '.' + d),\n };\n\n if (where && Object.keys(where).length > 0) {\n query.filters = Object.entries(where).map(([member, value]) => ({\n member: cube + '.' + member,\n operator: 'equals',\n values: Array.isArray(value) ? value : [String(value)],\n }));\n }\n\n if (opts?.limit) {\n query.limit = opts.limit;\n }\n\n const resultSet = await this.cubejsApi.load(query);\n return resultSet.tablePivot() as ResultRow[];\n }\n}\n","/**\n * NoopAnalyticsBackend — no-op backend for the analytics query port.\n *\n * Returns empty arrays for all queries. Use this backend when analytics\n * is disabled or in tests that don't need real analytics data.\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'noop' }).\n */\nimport { Injectable } from '@nestjs/common';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class NoopAnalyticsBackend implements IAnalyticsQuery {\n async execute(\n _cube: string,\n _measures: string[],\n _dimensions: string[],\n _where?: Record<string, any>,\n _opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n return [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAWO,IAAM,kBAAkB;AAMxB,IAAM,eAAe,uBAAO,cAAc;AAM1C,IAAM,kBAAkB,uBAAO,iBAAiB;;;ACAvD,SAAS,cAAkC;;;ACV3C,SAAS,QAAQ,YAA0B,cAAc;AASlD,IAAM,uBAAN,MAAoE;AAAA,EAIzE,YACyC,QACG,WAC1C;AAFuC;AACG;AAAA,EACzC;AAAA,EAFsC;AAAA,EACG;AAAA,EAL3B,SAAS,IAAI,OAAO,qBAAqB,IAAI;AAAA,EACtD;AAAA,EAOR,MAAM,eAA8B;AAClC,QAAI;AACF,YAAM,EAAE,SAAS,OAAO,IAAI,MAAM,OAAO,qBAAqB;AAC9D,WAAK,YAAY,OAAO,KAAK,WAAW,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,IACjE,QAAQ;AACN,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,MACA,UACA,YACA,OACA,MACsB;AACtB,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,OAAO,KAAK,8DAAyD;AAC1E,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,QAA6B;AAAA,MACjC,UAAU,SAAS,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,MAC5C,YAAY,WAAW,IAAI,CAAC,MAAM,OAAO,MAAM,CAAC;AAAA,IAClD;AAEA,QAAI,SAAS,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AAC1C,YAAM,UAAU,OAAO,QAAQ,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;AAAA,QAC9D,QAAQ,OAAO,MAAM;AAAA,QACrB,UAAU;AAAA,QACV,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,OAAO,KAAK,CAAC;AAAA,MACvD,EAAE;AAAA,IACJ;AAEA,QAAI,MAAM,OAAO;AACf,YAAM,QAAQ,KAAK;AAAA,IACrB;AAEA,UAAM,YAAY,MAAM,KAAK,UAAU,KAAK,KAAK;AACjD,WAAO,UAAU,WAAW;AAAA,EAC9B;AACF;AApDa,uBAAN;AAAA,EADN,WAAW;AAAA,EAMP,0BAAO,YAAY;AAAA,EACnB,0BAAO,eAAe;AAAA,GANd;;;ACdb,SAAS,cAAAA,mBAAkB;AAQpB,IAAM,uBAAN,MAAsD;AAAA,EAC3D,MAAM,QACJ,OACA,WACA,aACA,QACA,OACsB;AACtB,WAAO,CAAC;AAAA,EACV;AACF;AAVa,uBAAN;AAAA,EADNC,YAAW;AAAA,GACC;;;AFiBN,IAAM,kBAAN,MAAsB;AAAA,EAC3B,OAAO,QACL,UAAkC,EAAE,SAAS,OAAO,GACrC;AACf,QAAI,QAAQ,YAAY,QAAQ;AAC9B,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,WAAW;AAAA,UACT;AAAA,YACE,SAAS;AAAA,YACT,UAAU,QAAQ,IAAI,cAAc,KAAK;AAAA,UAC3C;AAAA,UACA;AAAA,YACE,SAAS;AAAA,YACT,UAAU,QAAQ,IAAI,iBAAiB,KAAK;AAAA,UAC9C;AAAA,UACA,EAAE,SAAS,iBAAiB,UAAU,qBAAqB;AAAA,QAC7D;AAAA,QACA,SAAS,CAAC,eAAe;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,WAAW,CAAC,EAAE,SAAS,iBAAiB,UAAU,qBAAqB,CAAC;AAAA,MACxE,SAAS,CAAC,eAAe;AAAA,IAC3B;AAAA,EACF;AACF;AA9Ba,kBAAN;AAAA,EADN,OAAO,CAAC,CAAC;AAAA,GACG;","names":["Injectable","Injectable"]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/analytics/analytics.tokens.ts","../../../../runtime/subsystems/analytics/analytics.module.ts","../../../../runtime/subsystems/analytics/cube-backend.ts","../../../../runtime/subsystems/analytics/noop-backend.ts"],"sourcesContent":["/** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded\n * constant (NOT derived from package.json) so a vendored copy — which lives inside the\n * CONSUMER's package — produces the identical key and the two copies share the symbol. */\nexport const PKG = '@pattern-stack/codegen';\n// TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only\n// (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.\nexport const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;\n","/**\n * Injection tokens for the analytics subsystem.\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as events.tokens.ts.\n *\n * Usage in services:\n * ```typescript\n * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}\n * ```\n */\nimport { tokenKey } from '../token-key';\n\nexport const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;\n\n// ADR-037: namespaced `Symbol.for(...)` keys (via `tokenKey()`) so these tokens\n// match by value across import boundaries (package vs vendored runtime copy).\n/**\n * Injection token for the cube.js API URL.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_URL = Symbol.for(tokenKey('analytics', 'cube-api-url'));\n\n/**\n * Injection token for the cube.js API secret.\n * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nexport const CUBE_API_SECRET = Symbol.for(tokenKey('analytics', 'cube-api-secret'));\n","/**\n * AnalyticsModule — DynamicModule factory for the analytics query subsystem.\n *\n * Register once in AppModule:\n * ```typescript\n * @Module({\n * imports: [\n * AnalyticsModule.forRoot({ backend: 'cube' }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * Tests swap to the noop backend without touching application code:\n * ```typescript\n * Test.createTestingModule({\n * imports: [AnalyticsModule.forRoot({ backend: 'noop' })],\n * });\n * ```\n *\n * `global: true` means entity modules do not need to import AnalyticsModule\n * individually — the ANALYTICS_QUERY token is available project-wide.\n */\nimport { Module, type DynamicModule } from '@nestjs/common';\nimport { ANALYTICS_QUERY, CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport { CubeAnalyticsBackend } from './cube-backend';\nimport { NoopAnalyticsBackend } from './noop-backend';\n\nexport interface AnalyticsModuleOptions {\n backend: 'cube' | 'noop';\n}\n\n@Module({})\nexport class AnalyticsModule {\n static forRoot(\n options: AnalyticsModuleOptions = { backend: 'noop' },\n ): DynamicModule {\n if (options.backend === 'cube') {\n return {\n module: AnalyticsModule,\n global: true,\n providers: [\n {\n provide: CUBE_API_URL,\n useValue: process.env['CUBE_API_URL'] ?? 'http://localhost:4000/cubejs-api/v1',\n },\n {\n provide: CUBE_API_SECRET,\n useValue: process.env['CUBE_API_SECRET'] ?? '',\n },\n { provide: ANALYTICS_QUERY, useClass: CubeAnalyticsBackend },\n ],\n exports: [ANALYTICS_QUERY],\n };\n }\n\n return {\n module: AnalyticsModule,\n global: true,\n providers: [{ provide: ANALYTICS_QUERY, useClass: NoopAnalyticsBackend }],\n exports: [ANALYTICS_QUERY],\n };\n }\n}\n","/**\n * CubeAnalyticsBackend — cube.js backend for the analytics query port.\n *\n * Connects to a running cube.js instance via @cubejs-client/core and\n * translates IAnalyticsQuery calls into cube.js query objects.\n *\n * @cubejs-client/core is an optional peer dependency; lazy-imported so\n * the module loads even if the package isn't installed. Consumers who\n * use the cube backend must install it separately:\n * bun add @cubejs-client/core\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'cube' }).\n */\nimport { Inject, Injectable, OnModuleInit, Logger } from '@nestjs/common';\nimport { CUBE_API_URL, CUBE_API_SECRET } from './analytics.tokens';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class CubeAnalyticsBackend implements IAnalyticsQuery, OnModuleInit {\n private readonly logger = new Logger(CubeAnalyticsBackend.name);\n private cubejsApi: any;\n\n constructor(\n @Inject(CUBE_API_URL) private readonly apiUrl: string,\n @Inject(CUBE_API_SECRET) private readonly apiSecret: string,\n ) {}\n\n async onModuleInit(): Promise<void> {\n try {\n const { default: cubejs } = await import('@cubejs-client/core');\n this.cubejsApi = cubejs(this.apiSecret, { apiUrl: this.apiUrl });\n } catch {\n throw new Error(\n 'CubeAnalyticsBackend requires @cubejs-client/core. Install it: bun add @cubejs-client/core',\n );\n }\n }\n\n async execute(\n cube: string,\n measures: string[],\n dimensions: string[],\n where?: Record<string, any>,\n opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n if (!this.cubejsApi) {\n this.logger.warn('Cube.js client not initialized — returning empty result');\n return [];\n }\n\n const query: Record<string, any> = {\n measures: measures.map((m) => cube + '.' + m),\n dimensions: dimensions.map((d) => cube + '.' + d),\n };\n\n if (where && Object.keys(where).length > 0) {\n query.filters = Object.entries(where).map(([member, value]) => ({\n member: cube + '.' + member,\n operator: 'equals',\n values: Array.isArray(value) ? value : [String(value)],\n }));\n }\n\n if (opts?.limit) {\n query.limit = opts.limit;\n }\n\n const resultSet = await this.cubejsApi.load(query);\n return resultSet.tablePivot() as ResultRow[];\n }\n}\n","/**\n * NoopAnalyticsBackend — no-op backend for the analytics query port.\n *\n * Returns empty arrays for all queries. Use this backend when analytics\n * is disabled or in tests that don't need real analytics data.\n *\n * Provided by AnalyticsModule.forRoot({ backend: 'noop' }).\n */\nimport { Injectable } from '@nestjs/common';\nimport type {\n AnalyticsQueryOpts,\n IAnalyticsQuery,\n ResultRow,\n} from './analytics-query.protocol';\n\n@Injectable()\nexport class NoopAnalyticsBackend implements IAnalyticsQuery {\n async execute(\n _cube: string,\n _measures: string[],\n _dimensions: string[],\n _where?: Record<string, any>,\n _opts?: AnalyticsQueryOpts,\n ): Promise<ResultRow[]> {\n return [];\n }\n}\n"],"mappings":"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","names":["Injectable","Injectable"]}
@@ -13,13 +13,19 @@ var __decorateParam = (index, decorator) => (target, key) => decorator(target, k
13
13
  // runtime/subsystems/auth/auth.module.ts
14
14
  import { Module } from "@nestjs/common";
15
15
 
16
+ // runtime/subsystems/token-key.ts
17
+ var PKG = "@pattern-stack/codegen";
18
+ var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
19
+
16
20
  // runtime/subsystems/auth/auth.tokens.ts
17
- var ENCRYPTION_KEY = /* @__PURE__ */ Symbol("ENCRYPTION_KEY");
18
- var OAUTH_STATE_STORE = /* @__PURE__ */ Symbol("OAUTH_STATE_STORE");
19
- var AUTH_CONNECTION_GRANT_SINK = /* @__PURE__ */ Symbol("AUTH_CONNECTION_GRANT_SINK");
20
- var AUTH_USER_CONTEXT = /* @__PURE__ */ Symbol("AUTH_USER_CONTEXT");
21
- var STRATEGY_REGISTRY = /* @__PURE__ */ Symbol("STRATEGY_REGISTRY");
22
- var AUTH_OPTIONS = /* @__PURE__ */ Symbol("AUTH_OPTIONS");
21
+ var ENCRYPTION_KEY = Symbol.for(tokenKey("auth", "encryption-key"));
22
+ var OAUTH_STATE_STORE = Symbol.for(tokenKey("auth", "oauth-state-store"));
23
+ var AUTH_CONNECTION_READER = Symbol.for(tokenKey("auth", "connection-reader"));
24
+ var AUTH_CONNECTION_TOKEN_WRITER = Symbol.for(tokenKey("auth", "connection-token-writer"));
25
+ var AUTH_CONNECTION_GRANT_SINK = Symbol.for(tokenKey("auth", "connection-grant-sink"));
26
+ var AUTH_USER_CONTEXT = Symbol.for(tokenKey("auth", "user-context"));
27
+ var STRATEGY_REGISTRY = Symbol.for(tokenKey("auth", "strategy-registry"));
28
+ var AUTH_OPTIONS = Symbol.for(tokenKey("auth", "options"));
23
29
 
24
30
  // runtime/subsystems/auth/backends/encryption-key/env.ts
25
31
  import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/auth/auth.module.ts","../../../../runtime/subsystems/auth/auth.tokens.ts","../../../../runtime/subsystems/auth/backends/encryption-key/env.ts","../../../../runtime/subsystems/auth/backends/state-store.memory-backend.ts","../../../../runtime/subsystems/auth/protocols/oauth-state-store.ts","../../../../runtime/subsystems/auth/backends/state-store.drizzle-backend.ts","../../../../runtime/subsystems/auth/auth-oauth-state.schema.ts","../../../../runtime/subsystems/auth/controllers/auth.controller.ts","../../../../runtime/constants/tokens.ts"],"sourcesContent":["/**\n * AuthModule — DynamicModule factory for the auth subsystem.\n *\n * Wires the pluggable backends the subsystem ships with:\n * - `ENCRYPTION_KEY` → `EnvEncryptionKey` (AES-256-GCM from env)\n * - `OAUTH_STATE_STORE` → `MemoryOAuthStateStore` (dev/tests) or\n * `DrizzleOAuthStateStore` (prod, requires\n * DRIZZLE provider).\n * - `AUTH_OPTIONS` → resolved options bag (used by AuthController\n * for `redirectUriBase`).\n *\n * The connection-store ports (`AUTH_CONNECTION_READER`,\n * `AUTH_CONNECTION_TOKEN_WRITER`, `AUTH_CONNECTION_GRANT_SINK`),\n * `AUTH_USER_CONTEXT`, and `STRATEGY_REGISTRY` are deliberately **not**\n * wired here — they are always consumer-specific:\n * - connection-store ports adapt the consumer's `connections` storage;\n * - `IUserContext` adapts the app's session/JWT scheme;\n * - `STRATEGY_REGISTRY` is populated from the per-provider strategy\n * classes the consumer maintains.\n *\n * Consumers provide them in their app module (or by importing the\n * `auth-integrations` starter, which binds the three connection-store\n * ports off a single canonical entity).\n *\n * Usage in AppModule:\n * ```typescript\n * AuthModule.forRoot({\n * encryptionKey: 'env',\n * oauthStateStore: 'memory', // or 'drizzle'\n * enableController: true,\n * redirectUriBase: 'http://localhost:3000',\n * });\n * ```\n *\n * `global: true` means other modules don't need to re-import AuthModule to\n * inject the auth tokens.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\nimport {\n AUTH_OPTIONS,\n ENCRYPTION_KEY,\n OAUTH_STATE_STORE,\n} from './auth.tokens';\nimport { EnvEncryptionKey } from './backends/encryption-key/env';\nimport { MemoryOAuthStateStore } from './backends/state-store.memory-backend';\nimport { DrizzleOAuthStateStore } from './backends/state-store.drizzle-backend';\nimport { AuthController } from './controllers/auth.controller';\nimport { DRIZZLE } from '../../constants/tokens';\nimport type { DrizzleClient } from '../../types/drizzle';\n\ntype EncryptionKeyChoice =\n | 'env'\n | Omit<Provider, 'provide'>;\n\ntype OAuthStateStoreChoice =\n | 'memory'\n | 'drizzle'\n | Omit<Provider, 'provide'>;\n\nexport interface AuthModuleOptions {\n /** `'env'` (default) or a full provider definition (e.g. `{ useClass: MyKmsEncryptionKey }`). */\n encryptionKey?: EncryptionKeyChoice;\n /**\n * `'memory'` (default — tests/dev) or `'drizzle'` (prod, requires DRIZZLE\n * provider) or a full provider definition for a custom impl.\n */\n oauthStateStore?: OAuthStateStoreChoice;\n /**\n * Mount `AuthController` (`/auth/:provider/connect` + `/callback`).\n * Default `false` — apps that hand-roll connect/callback (rare) or that\n * use the subsystem only for the refresh path can opt out.\n */\n enableController?: boolean;\n /**\n * Public base URL of the API server. Used to construct per-provider\n * callback URIs as `${redirectUriBase}/auth/:provider/callback`.\n * Required when `enableController: true`.\n */\n redirectUriBase?: string;\n}\n\nfunction resolveEncryptionKeyProvider(choice: EncryptionKeyChoice): Provider {\n if (choice === 'env') {\n return { provide: ENCRYPTION_KEY, useClass: EnvEncryptionKey };\n }\n return { provide: ENCRYPTION_KEY, ...choice } as Provider;\n}\n\nfunction resolveOAuthStateStoreProvider(\n choice: OAuthStateStoreChoice,\n): Provider {\n if (choice === 'memory') {\n return { provide: OAUTH_STATE_STORE, useClass: MemoryOAuthStateStore };\n }\n if (choice === 'drizzle') {\n return {\n provide: OAUTH_STATE_STORE,\n useFactory: (db: DrizzleClient | null) => {\n if (!db) {\n throw new Error(\n \"AuthModule.forRoot: oauthStateStore: 'drizzle' selected but DRIZZLE provider is not available. \" +\n 'Ensure DatabaseModule (or another provider exposing DRIZZLE) is imported before AuthModule.forRoot.',\n );\n }\n return new DrizzleOAuthStateStore(db);\n },\n inject: [{ token: DRIZZLE, optional: true }],\n };\n }\n return { provide: OAUTH_STATE_STORE, ...choice } as Provider;\n}\n\n@Module({})\nexport class AuthModule {\n static forRoot(options: AuthModuleOptions = {}): DynamicModule {\n const resolved: AuthModuleOptions = {\n encryptionKey: options.encryptionKey ?? 'env',\n oauthStateStore: options.oauthStateStore ?? 'memory',\n enableController: options.enableController ?? false,\n redirectUriBase: options.redirectUriBase,\n };\n\n if (resolved.enableController && !resolved.redirectUriBase) {\n throw new Error(\n 'AuthModule.forRoot: redirectUriBase is required when enableController: true',\n );\n }\n\n const encryptionKeyProvider = resolveEncryptionKeyProvider(\n resolved.encryptionKey!,\n );\n const oauthStateStoreProvider = resolveOAuthStateStoreProvider(\n resolved.oauthStateStore!,\n );\n const optionsProvider: Provider = {\n provide: AUTH_OPTIONS,\n useValue: resolved,\n };\n\n return {\n module: AuthModule,\n global: true,\n providers: [encryptionKeyProvider, oauthStateStoreProvider, optionsProvider],\n controllers: resolved.enableController ? [AuthController] : [],\n exports: [ENCRYPTION_KEY, OAUTH_STATE_STORE, AUTH_OPTIONS],\n };\n }\n}\n","/**\n * Auth subsystem — injection tokens.\n *\n * Following ADR-008 guidance: `Symbol()` tokens for type safety and collision\n * avoidance. Consumers inject these via `@Inject(...)` against the matching\n * protocol interface.\n *\n * Usage:\n * ```typescript\n * constructor(\n * @Inject(ENCRYPTION_KEY) private readonly key: IEncryptionKey,\n * @Inject(OAUTH_STATE_STORE) private readonly states: IOAuthStateStore,\n * @Inject(AUTH_CONNECTION_READER) private readonly reader: IConnectionReader,\n * @Inject(AUTH_CONNECTION_TOKEN_WRITER) private readonly writer: IConnectionTokenWriter,\n * @Inject(AUTH_CONNECTION_GRANT_SINK) private readonly grants: IConnectionGrantSink,\n * @Inject(AUTH_USER_CONTEXT) private readonly userCtx: IUserContext,\n * @Inject(STRATEGY_REGISTRY) private readonly registry: ProviderStrategyRegistry,\n * ) {}\n * ```\n *\n * `IAuthStrategy` implementations are provider-specific and registered under\n * provider-specific tokens (e.g. `SALESFORCE_AUTH_STRATEGY`,\n * `HUBSPOT_AUTH_STRATEGY`) by each connection module — this subsystem does\n * not mandate a single `AUTH_STRATEGY` token because an app typically has\n * many concurrent strategies, one per provider. They are dispatched through\n * `STRATEGY_REGISTRY` (a `ReadonlyMap<slug, IProviderStrategy>`), populated\n * by per-provider modules via a `useFactory` provider.\n */\nexport const ENCRYPTION_KEY = Symbol('ENCRYPTION_KEY');\nexport const OAUTH_STATE_STORE = Symbol('OAUTH_STATE_STORE');\nexport const AUTH_CONNECTION_READER = Symbol('AUTH_CONNECTION_READER');\nexport const AUTH_CONNECTION_TOKEN_WRITER = Symbol('AUTH_CONNECTION_TOKEN_WRITER');\nexport const AUTH_CONNECTION_GRANT_SINK = Symbol('AUTH_CONNECTION_GRANT_SINK');\nexport const AUTH_USER_CONTEXT = Symbol('AUTH_USER_CONTEXT');\nexport const STRATEGY_REGISTRY = Symbol('STRATEGY_REGISTRY');\n/**\n * Holds the resolved `AuthModuleOptions` (used by `AuthController` to read\n * `redirectUriBase` for building per-provider callback URIs).\n */\nexport const AUTH_OPTIONS = Symbol('AUTH_OPTIONS');\n","/**\n * Env-backed AES-256-GCM encryption.\n *\n * Framing: `base64( nonce(12B) || ciphertext || authTag(16B) )`. Random nonce\n * per call means two encryptions of the same plaintext produce different\n * ciphertexts — prevents replay-style inference. Auth tag enforces integrity;\n * any tampering throws on decrypt.\n *\n * Key source: `INTEGRATION_TOKEN_ENCRYPTION_KEY` env var, 32 bytes base64-encoded.\n * Generate via `openssl rand -base64 32`.\n *\n * Future backend: `kms.ts` (AWS/GCP KMS) for production deployments that\n * need key rotation + audit trails.\n */\nimport { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport type { IEncryptionKey } from '../../protocols/encryption-key';\n\nexport interface EnvEncryptionKeyOptions {\n /** Defaults to `process.env`. Tests inject a fixture. */\n env?: NodeJS.ProcessEnv;\n /** Defaults to `'INTEGRATION_TOKEN_ENCRYPTION_KEY'`. */\n envVar?: string;\n}\n\nconst ALGO = 'aes-256-gcm';\nconst NONCE_BYTES = 12;\nconst TAG_BYTES = 16;\nconst KEY_BYTES = 32;\n\nexport class EnvEncryptionKey implements IEncryptionKey {\n private readonly key: Buffer;\n\n constructor(opts: EnvEncryptionKeyOptions = {}) {\n const env = opts.env ?? process.env;\n const envVar = opts.envVar ?? 'INTEGRATION_TOKEN_ENCRYPTION_KEY';\n const raw = env[envVar];\n if (!raw) {\n throw new Error(\n `EnvEncryptionKey: ${envVar} is not set. Generate with: openssl rand -base64 32`,\n );\n }\n const decoded = Buffer.from(raw, 'base64');\n if (decoded.length !== KEY_BYTES) {\n throw new Error(\n `EnvEncryptionKey: ${envVar} must decode to ${KEY_BYTES} bytes (got ${decoded.length}). Use: openssl rand -base64 32`,\n );\n }\n this.key = decoded;\n }\n\n async encrypt(plaintext: string): Promise<string> {\n const nonce = randomBytes(NONCE_BYTES);\n const cipher = createCipheriv(ALGO, this.key, nonce);\n const ciphertext = Buffer.concat([\n cipher.update(plaintext, 'utf8'),\n cipher.final(),\n ]);\n const authTag = cipher.getAuthTag();\n return Buffer.concat([nonce, ciphertext, authTag]).toString('base64');\n }\n\n async decrypt(ciphertext: string): Promise<string> {\n const buf = Buffer.from(ciphertext, 'base64');\n if (buf.length < NONCE_BYTES + TAG_BYTES) {\n throw new Error('EnvEncryptionKey: ciphertext too short');\n }\n const nonce = buf.subarray(0, NONCE_BYTES);\n const authTag = buf.subarray(buf.length - TAG_BYTES);\n const body = buf.subarray(NONCE_BYTES, buf.length - TAG_BYTES);\n\n const decipher = createDecipheriv(ALGO, this.key, nonce);\n decipher.setAuthTag(authTag);\n const plain = Buffer.concat([decipher.update(body), decipher.final()]);\n return plain.toString('utf8');\n }\n}\n","/**\n * In-memory `IOAuthStateStore` backend.\n *\n * Single-process store — Map<state, { record, expiresAt }>. Suitable for\n * tests and single-worker dev. Production deployments select the drizzle\n * backend so state survives restarts and is shared across workers.\n *\n * Single-use semantics:\n * - `generate(record)` mints a 256-bit random token (base64url, opaque).\n * - `consume(state)` deletes the entry on read. A second call with the\n * same state throws `OAuthStateError('replay')`.\n * - Expired entries also throw (`'expired'`); the entry is deleted as a\n * side effect so a later replay still surfaces correctly.\n *\n * TTL defaults to 10 minutes — long enough for a user to complete the\n * provider's consent screen, short enough that abandoned states age out.\n */\nimport { randomBytes } from 'node:crypto';\nimport {\n type IOAuthStateStore,\n type OAuthStateRecord,\n OAuthStateError,\n} from '../protocols/oauth-state-store';\n\nexport interface MemoryOAuthStateStoreOptions {\n /** TTL in ms. Default 10 minutes. */\n ttlMs?: number;\n /** Injectable clock for tests. Default `Date.now`. */\n now?: () => number;\n /** Injectable token generator for tests. Default 32-byte base64url. */\n generateToken?: () => string;\n}\n\ninterface Slot {\n record: OAuthStateRecord;\n expiresAt: number;\n}\n\nexport class MemoryOAuthStateStore implements IOAuthStateStore {\n private readonly store = new Map<string, Slot>();\n private readonly ttlMs: number;\n private readonly now: () => number;\n private readonly generateToken: () => string;\n\n constructor(opts: MemoryOAuthStateStoreOptions = {}) {\n this.ttlMs = opts.ttlMs ?? 10 * 60 * 1000;\n this.now = opts.now ?? (() => Date.now());\n this.generateToken =\n opts.generateToken ?? (() => randomBytes(32).toString('base64url'));\n }\n\n async generate(record: OAuthStateRecord): Promise<string> {\n const state = this.generateToken();\n this.store.set(state, {\n record: { ...record },\n expiresAt: this.now() + this.ttlMs,\n });\n return state;\n }\n\n async consume(state: string): Promise<OAuthStateRecord> {\n const slot = this.store.get(state);\n if (!slot) {\n throw new OAuthStateError(\n `OAuth state token unknown or already consumed`,\n 'missing',\n );\n }\n // Delete first so a concurrent consume can't replay.\n this.store.delete(state);\n if (slot.expiresAt <= this.now()) {\n throw new OAuthStateError(`OAuth state token expired`, 'expired');\n }\n return slot.record;\n }\n}\n","/**\n * Auth subsystem — `IOAuthStateStore` port.\n *\n * CSRF protection for the OAuth2 authorize-code callback. Generic across\n * providers. The store mints opaque state tokens at /connect time and\n * single-use consumes them at /callback time, returning the original\n * record (userId + optional post-callback redirect path).\n *\n * Concrete backends live under `../backends/`:\n * - `state-store.memory-backend.ts` — in-process Map (tests/dev).\n * - `state-store.drizzle-backend.ts` — Postgres (prod).\n *\n * Semantics:\n * - `generate(record)` → returns an opaque state token; record is stored\n * under that token until consumed or until TTL expires.\n * - `consume(state)` → atomically deletes the entry and returns the\n * record. Throws on missing, expired, or replayed state. Never returns\n * null — a missing/expired state is a CSRF signal.\n */\nexport interface OAuthStateRecord {\n userId: string;\n /** Optional post-callback redirect path (relative URL). */\n redirect?: string;\n}\n\nexport interface IOAuthStateStore {\n /** Mint an opaque state token bound to `record`. Single-use. */\n generate(record: OAuthStateRecord): Promise<string>;\n /**\n * Atomically consume `state`, returning the bound record. Throws on\n * missing / expired / replayed state.\n */\n consume(state: string): Promise<OAuthStateRecord>;\n}\n\n/**\n * Thrown by `IOAuthStateStore.consume` when the state token is unknown,\n * expired, or has already been consumed (replay attempt).\n */\nexport class OAuthStateError extends Error {\n constructor(\n message: string,\n public readonly reason: 'missing' | 'expired',\n ) {\n super(message);\n this.name = 'OAuthStateError';\n }\n}\n","/**\n * Drizzle-backed `IOAuthStateStore`.\n *\n * Uses the `auth_oauth_state` table (see `auth-oauth-state.schema.ts`).\n * Single-use semantics enforced via `DELETE ... RETURNING`: the consume\n * path atomically deletes and returns the row, so a concurrent /callback\n * with the same state cannot replay.\n *\n * Behaviour:\n * - `generate(record)` mints a 256-bit base64url token, INSERTs the row\n * with `expires_at = now() + ttlMs`.\n * - `consume(state)` runs `DELETE ... WHERE state = $1 RETURNING ...`\n * once. Throws `OAuthStateError('missing')` if no row was deleted\n * (unknown or already consumed) and `OAuthStateError('expired')` if\n * the deleted row was past its `expires_at`.\n */\nimport { randomBytes } from 'node:crypto';\nimport { eq } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../../types/drizzle';\nimport { authOAuthState } from '../auth-oauth-state.schema';\nimport {\n type IOAuthStateStore,\n type OAuthStateRecord,\n OAuthStateError,\n} from '../protocols/oauth-state-store';\n\nexport interface DrizzleOAuthStateStoreOptions {\n /** TTL in ms. Default 10 minutes. */\n ttlMs?: number;\n /** Injectable clock for tests. Default `Date.now`. */\n now?: () => number;\n /** Injectable token generator for tests. Default 32-byte base64url. */\n generateToken?: () => string;\n}\n\nexport class DrizzleOAuthStateStore implements IOAuthStateStore {\n private readonly ttlMs: number;\n private readonly now: () => number;\n private readonly generateToken: () => string;\n\n constructor(\n private readonly db: DrizzleClient,\n opts: DrizzleOAuthStateStoreOptions = {},\n ) {\n this.ttlMs = opts.ttlMs ?? 10 * 60 * 1000;\n this.now = opts.now ?? (() => Date.now());\n this.generateToken =\n opts.generateToken ?? (() => randomBytes(32).toString('base64url'));\n }\n\n async generate(record: OAuthStateRecord): Promise<string> {\n const state = this.generateToken();\n const expiresAt = new Date(this.now() + this.ttlMs);\n await this.db.insert(authOAuthState).values({\n state,\n userId: record.userId,\n redirect: record.redirect ?? null,\n expiresAt,\n });\n return state;\n }\n\n async consume(state: string): Promise<OAuthStateRecord> {\n const rows = await this.db\n .delete(authOAuthState)\n .where(eq(authOAuthState.state, state))\n .returning();\n const row = rows[0];\n if (!row) {\n throw new OAuthStateError(\n `OAuth state token unknown or already consumed`,\n 'missing',\n );\n }\n if (row.expiresAt.getTime() <= this.now()) {\n throw new OAuthStateError(`OAuth state token expired`, 'expired');\n }\n return {\n userId: row.userId,\n redirect: row.redirect ?? undefined,\n };\n }\n}\n","/**\n * Drizzle schema for the `auth_oauth_state` table — backs the\n * `DrizzleOAuthStateStore` (`state-store.drizzle-backend.ts`).\n *\n * One row per outstanding /connect → /callback dance. Single-use; rows are\n * deleted on consume. A periodic sweep (or a `WHERE expires_at < now()`\n * filter on read) clears abandoned rows.\n *\n * Columns:\n * - `state` — opaque random token, primary key.\n * - `user_id` — text (matches the consumer-defined user-id shape;\n * the auth subsystem doesn't constrain this to UUID\n * because some apps key users by external id).\n * - `redirect` — optional post-callback redirect path.\n * - `expires_at` — TTL boundary; entries past this are treated as absent.\n *\n * Convention: schema files live at the root of the subsystem dir\n * (mirrors `cache.schema.ts`, `integration-audit.schema.ts`, `domain-events.schema.ts`).\n */\nimport { pgTable, text, timestamp } from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport const authOAuthState = pgTable('auth_oauth_state', {\n state: text('state').primaryKey(),\n userId: text('user_id').notNull(),\n redirect: text('redirect'),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n});\n\nexport type AuthOAuthState = InferSelectModel<typeof authOAuthState>;\n","/**\n * AuthController — provider-agnostic OAuth2 connect/callback dance.\n *\n * Mounts two routes:\n * - `GET /auth/:provider/connect?redirect=...` — generates state, builds\n * the provider's authorize-url, 302-redirects the browser there.\n * - `GET /auth/:provider/callback?code=...&state=...` — consumes state,\n * exchanges the code for tokens, hands them to the grant sink, then\n * 302-redirects to the post-connect path.\n *\n * Hexagonal seams:\n * - `STRATEGY_REGISTRY` (ReadonlyMap<slug, IProviderStrategy>) — dispatch.\n * Concrete per-provider strategies live consumer-side and contribute\n * entries via a `useFactory` in the consumer's app module.\n * - `AUTH_USER_CONTEXT` (IUserContext) — resolves \"who is this request\"\n * from the consumer's session/JWT/etc.\n * - `OAUTH_STATE_STORE` (IOAuthStateStore) — CSRF state minting/consume.\n * - `AUTH_CONNECTION_GRANT_SINK` (IConnectionGrantSink) — persists the\n * freshly-minted grant. Adapter lives consumer-side (e.g. the\n * auth-integrations starter from #285).\n *\n * The controller never imports `ConnectionsService` or any other concrete\n * consumer type — it goes through ports only.\n */\nimport {\n Controller,\n Get,\n Inject,\n Param,\n Query,\n Req,\n Res,\n HttpException,\n HttpStatus,\n} from '@nestjs/common';\nimport {\n AUTH_CONNECTION_GRANT_SINK,\n AUTH_OPTIONS,\n AUTH_USER_CONTEXT,\n OAUTH_STATE_STORE,\n STRATEGY_REGISTRY,\n} from '../auth.tokens';\nimport type { AuthModuleOptions } from '../auth.module';\nimport type { IOAuthStateStore } from '../protocols/oauth-state-store';\nimport type { IUserContext } from '../protocols/user-context';\nimport type {\n IProviderStrategy,\n ProviderStrategyRegistry,\n} from '../protocols/provider-strategy';\nimport type { IConnectionGrantSink } from '../protocols/connection-store';\n\n/**\n * Minimal response surface used by the controller — typed loosely so we\n * don't pull a hard dep on `express` or `fastify`. Both popular HTTP\n * adapters expose `redirect(status, url)`.\n */\ninterface RedirectingResponse {\n redirect(statusCode: number, url: string): unknown;\n}\n\n@Controller('auth')\nexport class AuthController {\n constructor(\n @Inject(STRATEGY_REGISTRY)\n private readonly registry: ProviderStrategyRegistry,\n @Inject(AUTH_USER_CONTEXT)\n private readonly userContext: IUserContext,\n @Inject(OAUTH_STATE_STORE)\n private readonly stateStore: IOAuthStateStore,\n @Inject(AUTH_CONNECTION_GRANT_SINK)\n private readonly grantSink: IConnectionGrantSink,\n @Inject(AUTH_OPTIONS)\n private readonly options: AuthModuleOptions,\n ) {}\n\n @Get(':provider/connect')\n async connect(\n @Param('provider') slug: string,\n @Query('redirect') redirect: string | undefined,\n @Req() req: unknown,\n @Res() res: RedirectingResponse,\n ): Promise<unknown> {\n const strategy = this.requireStrategy(slug);\n const userId = await this.userContext.getCurrentUserId(req);\n const state = await this.stateStore.generate({ userId, redirect });\n const url = strategy.buildAuthorizeUrl({\n state,\n redirectUri: this.redirectUriFor(slug),\n });\n return res.redirect(HttpStatus.FOUND, url);\n }\n\n @Get(':provider/callback')\n async callback(\n @Param('provider') slug: string,\n @Query('code') code: string | undefined,\n @Query('state') state: string | undefined,\n @Res() res: RedirectingResponse,\n ): Promise<unknown> {\n const strategy = this.requireStrategy(slug);\n if (!code) {\n throw new HttpException(\n `Missing 'code' query param`,\n HttpStatus.BAD_REQUEST,\n );\n }\n if (!state) {\n throw new HttpException(\n `Missing 'state' query param`,\n HttpStatus.BAD_REQUEST,\n );\n }\n const { userId, redirect } = await this.stateStore.consume(state);\n const tokens = await strategy.exchangeCodeForTokens({\n code,\n redirectUri: this.redirectUriFor(slug),\n });\n await this.grantSink.createOrUpdateFromOAuthGrant({\n userId,\n provider: slug,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: tokens.expiresAt,\n scope: tokens.scope,\n externalAccountId: tokens.externalAccountId,\n providerMetadata: tokens.providerMetadata,\n });\n return res.redirect(\n HttpStatus.FOUND,\n redirect ?? `/settings/connections?connected=${encodeURIComponent(slug)}`,\n );\n }\n\n private requireStrategy(slug: string): IProviderStrategy {\n const strategy = this.registry.get(slug);\n if (!strategy) {\n throw new HttpException(\n `Unknown provider '${slug}'`,\n HttpStatus.NOT_FOUND,\n );\n }\n return strategy;\n }\n\n private redirectUriFor(slug: string): string {\n const base = this.options.redirectUriBase;\n if (!base) {\n throw new Error(\n `AuthModule.forRoot: redirectUriBase is required when AuthController is enabled`,\n );\n }\n const trimmed = base.replace(/\\/+$/, '');\n return `${trimmed}/auth/${encodeURIComponent(slug)}/callback`;\n }\n}\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n"],"mappings":"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","names":["randomBytes","randomBytes","randomBytes","randomBytes"]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/auth/auth.module.ts","../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/auth/auth.tokens.ts","../../../../runtime/subsystems/auth/backends/encryption-key/env.ts","../../../../runtime/subsystems/auth/backends/state-store.memory-backend.ts","../../../../runtime/subsystems/auth/protocols/oauth-state-store.ts","../../../../runtime/subsystems/auth/backends/state-store.drizzle-backend.ts","../../../../runtime/subsystems/auth/auth-oauth-state.schema.ts","../../../../runtime/subsystems/auth/controllers/auth.controller.ts","../../../../runtime/constants/tokens.ts"],"sourcesContent":["/**\n * AuthModule — DynamicModule factory for the auth subsystem.\n *\n * Wires the pluggable backends the subsystem ships with:\n * - `ENCRYPTION_KEY` → `EnvEncryptionKey` (AES-256-GCM from env)\n * - `OAUTH_STATE_STORE` → `MemoryOAuthStateStore` (dev/tests) or\n * `DrizzleOAuthStateStore` (prod, requires\n * DRIZZLE provider).\n * - `AUTH_OPTIONS` → resolved options bag (used by AuthController\n * for `redirectUriBase`).\n *\n * The connection-store ports (`AUTH_CONNECTION_READER`,\n * `AUTH_CONNECTION_TOKEN_WRITER`, `AUTH_CONNECTION_GRANT_SINK`),\n * `AUTH_USER_CONTEXT`, and `STRATEGY_REGISTRY` are deliberately **not**\n * wired here — they are always consumer-specific:\n * - connection-store ports adapt the consumer's `connections` storage;\n * - `IUserContext` adapts the app's session/JWT scheme;\n * - `STRATEGY_REGISTRY` is populated from the per-provider strategy\n * classes the consumer maintains.\n *\n * Consumers provide them in their app module (or by importing the\n * `auth-integrations` starter, which binds the three connection-store\n * ports off a single canonical entity).\n *\n * Usage in AppModule:\n * ```typescript\n * AuthModule.forRoot({\n * encryptionKey: 'env',\n * oauthStateStore: 'memory', // or 'drizzle'\n * enableController: true,\n * redirectUriBase: 'http://localhost:3000',\n * });\n * ```\n *\n * `global: true` means other modules don't need to re-import AuthModule to\n * inject the auth tokens.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\nimport {\n AUTH_OPTIONS,\n ENCRYPTION_KEY,\n OAUTH_STATE_STORE,\n} from './auth.tokens';\nimport { EnvEncryptionKey } from './backends/encryption-key/env';\nimport { MemoryOAuthStateStore } from './backends/state-store.memory-backend';\nimport { DrizzleOAuthStateStore } from './backends/state-store.drizzle-backend';\nimport { AuthController } from './controllers/auth.controller';\nimport { DRIZZLE } from '../../constants/tokens';\nimport type { DrizzleClient } from '../../types/drizzle';\n\ntype EncryptionKeyChoice =\n | 'env'\n | Omit<Provider, 'provide'>;\n\ntype OAuthStateStoreChoice =\n | 'memory'\n | 'drizzle'\n | Omit<Provider, 'provide'>;\n\nexport interface AuthModuleOptions {\n /** `'env'` (default) or a full provider definition (e.g. `{ useClass: MyKmsEncryptionKey }`). */\n encryptionKey?: EncryptionKeyChoice;\n /**\n * `'memory'` (default — tests/dev) or `'drizzle'` (prod, requires DRIZZLE\n * provider) or a full provider definition for a custom impl.\n */\n oauthStateStore?: OAuthStateStoreChoice;\n /**\n * Mount `AuthController` (`/auth/:provider/connect` + `/callback`).\n * Default `false` — apps that hand-roll connect/callback (rare) or that\n * use the subsystem only for the refresh path can opt out.\n */\n enableController?: boolean;\n /**\n * Public base URL of the API server. Used to construct per-provider\n * callback URIs as `${redirectUriBase}/auth/:provider/callback`.\n * Required when `enableController: true`.\n */\n redirectUriBase?: string;\n}\n\nfunction resolveEncryptionKeyProvider(choice: EncryptionKeyChoice): Provider {\n if (choice === 'env') {\n return { provide: ENCRYPTION_KEY, useClass: EnvEncryptionKey };\n }\n return { provide: ENCRYPTION_KEY, ...choice } as Provider;\n}\n\nfunction resolveOAuthStateStoreProvider(\n choice: OAuthStateStoreChoice,\n): Provider {\n if (choice === 'memory') {\n return { provide: OAUTH_STATE_STORE, useClass: MemoryOAuthStateStore };\n }\n if (choice === 'drizzle') {\n return {\n provide: OAUTH_STATE_STORE,\n useFactory: (db: DrizzleClient | null) => {\n if (!db) {\n throw new Error(\n \"AuthModule.forRoot: oauthStateStore: 'drizzle' selected but DRIZZLE provider is not available. \" +\n 'Ensure DatabaseModule (or another provider exposing DRIZZLE) is imported before AuthModule.forRoot.',\n );\n }\n return new DrizzleOAuthStateStore(db);\n },\n inject: [{ token: DRIZZLE, optional: true }],\n };\n }\n return { provide: OAUTH_STATE_STORE, ...choice } as Provider;\n}\n\n@Module({})\nexport class AuthModule {\n static forRoot(options: AuthModuleOptions = {}): DynamicModule {\n const resolved: AuthModuleOptions = {\n encryptionKey: options.encryptionKey ?? 'env',\n oauthStateStore: options.oauthStateStore ?? 'memory',\n enableController: options.enableController ?? false,\n redirectUriBase: options.redirectUriBase,\n };\n\n if (resolved.enableController && !resolved.redirectUriBase) {\n throw new Error(\n 'AuthModule.forRoot: redirectUriBase is required when enableController: true',\n );\n }\n\n const encryptionKeyProvider = resolveEncryptionKeyProvider(\n resolved.encryptionKey!,\n );\n const oauthStateStoreProvider = resolveOAuthStateStoreProvider(\n resolved.oauthStateStore!,\n );\n const optionsProvider: Provider = {\n provide: AUTH_OPTIONS,\n useValue: resolved,\n };\n\n return {\n module: AuthModule,\n global: true,\n providers: [encryptionKeyProvider, oauthStateStoreProvider, optionsProvider],\n controllers: resolved.enableController ? [AuthController] : [],\n exports: [ENCRYPTION_KEY, OAUTH_STATE_STORE, AUTH_OPTIONS],\n };\n }\n}\n","/** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded\n * constant (NOT derived from package.json) so a vendored copy — which lives inside the\n * CONSUMER's package — produces the identical key and the two copies share the symbol. */\nexport const PKG = '@pattern-stack/codegen';\n// TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only\n// (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.\nexport const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;\n","/**\n * Auth subsystem — injection tokens.\n *\n * Following ADR-008 guidance: `Symbol()` tokens for type safety and collision\n * avoidance. Consumers inject these via `@Inject(...)` against the matching\n * protocol interface.\n *\n * Usage:\n * ```typescript\n * constructor(\n * @Inject(ENCRYPTION_KEY) private readonly key: IEncryptionKey,\n * @Inject(OAUTH_STATE_STORE) private readonly states: IOAuthStateStore,\n * @Inject(AUTH_CONNECTION_READER) private readonly reader: IConnectionReader,\n * @Inject(AUTH_CONNECTION_TOKEN_WRITER) private readonly writer: IConnectionTokenWriter,\n * @Inject(AUTH_CONNECTION_GRANT_SINK) private readonly grants: IConnectionGrantSink,\n * @Inject(AUTH_USER_CONTEXT) private readonly userCtx: IUserContext,\n * @Inject(STRATEGY_REGISTRY) private readonly registry: ProviderStrategyRegistry,\n * ) {}\n * ```\n *\n * `IAuthStrategy` implementations are provider-specific and registered under\n * provider-specific tokens (e.g. `SALESFORCE_AUTH_STRATEGY`,\n * `HUBSPOT_AUTH_STRATEGY`) by each connection module — this subsystem does\n * not mandate a single `AUTH_STRATEGY` token because an app typically has\n * many concurrent strategies, one per provider. They are dispatched through\n * `STRATEGY_REGISTRY` (a `ReadonlyMap<slug, IProviderStrategy>`), populated\n * by per-provider modules via a `useFactory` provider.\n */\nimport { tokenKey } from '../token-key';\n\n// ADR-037: namespaced `Symbol.for(...)` keys so a token matches by VALUE across\n// import boundaries — the package copy and a (legacy) vendored copy resolve to\n// the SAME symbol, eliminating the dual-package DI-token identity hazard that\n// crashed boot once the emitter began emitting `STRATEGY_REGISTRY` as a runtime\n// value (RFC-0003 R5). Matches the convention surface packages already use.\nexport const ENCRYPTION_KEY = Symbol.for(tokenKey('auth', 'encryption-key'));\nexport const OAUTH_STATE_STORE = Symbol.for(tokenKey('auth', 'oauth-state-store'));\nexport const AUTH_CONNECTION_READER = Symbol.for(tokenKey('auth', 'connection-reader'));\nexport const AUTH_CONNECTION_TOKEN_WRITER = Symbol.for(tokenKey('auth', 'connection-token-writer'));\nexport const AUTH_CONNECTION_GRANT_SINK = Symbol.for(tokenKey('auth', 'connection-grant-sink'));\nexport const AUTH_USER_CONTEXT = Symbol.for(tokenKey('auth', 'user-context'));\nexport const STRATEGY_REGISTRY = Symbol.for(tokenKey('auth', 'strategy-registry'));\n/**\n * Holds the resolved `AuthModuleOptions` (used by `AuthController` to read\n * `redirectUriBase` for building per-provider callback URIs).\n */\nexport const AUTH_OPTIONS = Symbol.for(tokenKey('auth', 'options'));\n","/**\n * Env-backed AES-256-GCM encryption.\n *\n * Framing: `base64( nonce(12B) || ciphertext || authTag(16B) )`. Random nonce\n * per call means two encryptions of the same plaintext produce different\n * ciphertexts — prevents replay-style inference. Auth tag enforces integrity;\n * any tampering throws on decrypt.\n *\n * Key source: `INTEGRATION_TOKEN_ENCRYPTION_KEY` env var, 32 bytes base64-encoded.\n * Generate via `openssl rand -base64 32`.\n *\n * Future backend: `kms.ts` (AWS/GCP KMS) for production deployments that\n * need key rotation + audit trails.\n */\nimport { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport type { IEncryptionKey } from '../../protocols/encryption-key';\n\nexport interface EnvEncryptionKeyOptions {\n /** Defaults to `process.env`. Tests inject a fixture. */\n env?: NodeJS.ProcessEnv;\n /** Defaults to `'INTEGRATION_TOKEN_ENCRYPTION_KEY'`. */\n envVar?: string;\n}\n\nconst ALGO = 'aes-256-gcm';\nconst NONCE_BYTES = 12;\nconst TAG_BYTES = 16;\nconst KEY_BYTES = 32;\n\nexport class EnvEncryptionKey implements IEncryptionKey {\n private readonly key: Buffer;\n\n constructor(opts: EnvEncryptionKeyOptions = {}) {\n const env = opts.env ?? process.env;\n const envVar = opts.envVar ?? 'INTEGRATION_TOKEN_ENCRYPTION_KEY';\n const raw = env[envVar];\n if (!raw) {\n throw new Error(\n `EnvEncryptionKey: ${envVar} is not set. Generate with: openssl rand -base64 32`,\n );\n }\n const decoded = Buffer.from(raw, 'base64');\n if (decoded.length !== KEY_BYTES) {\n throw new Error(\n `EnvEncryptionKey: ${envVar} must decode to ${KEY_BYTES} bytes (got ${decoded.length}). Use: openssl rand -base64 32`,\n );\n }\n this.key = decoded;\n }\n\n async encrypt(plaintext: string): Promise<string> {\n const nonce = randomBytes(NONCE_BYTES);\n const cipher = createCipheriv(ALGO, this.key, nonce);\n const ciphertext = Buffer.concat([\n cipher.update(plaintext, 'utf8'),\n cipher.final(),\n ]);\n const authTag = cipher.getAuthTag();\n return Buffer.concat([nonce, ciphertext, authTag]).toString('base64');\n }\n\n async decrypt(ciphertext: string): Promise<string> {\n const buf = Buffer.from(ciphertext, 'base64');\n if (buf.length < NONCE_BYTES + TAG_BYTES) {\n throw new Error('EnvEncryptionKey: ciphertext too short');\n }\n const nonce = buf.subarray(0, NONCE_BYTES);\n const authTag = buf.subarray(buf.length - TAG_BYTES);\n const body = buf.subarray(NONCE_BYTES, buf.length - TAG_BYTES);\n\n const decipher = createDecipheriv(ALGO, this.key, nonce);\n decipher.setAuthTag(authTag);\n const plain = Buffer.concat([decipher.update(body), decipher.final()]);\n return plain.toString('utf8');\n }\n}\n","/**\n * In-memory `IOAuthStateStore` backend.\n *\n * Single-process store — Map<state, { record, expiresAt }>. Suitable for\n * tests and single-worker dev. Production deployments select the drizzle\n * backend so state survives restarts and is shared across workers.\n *\n * Single-use semantics:\n * - `generate(record)` mints a 256-bit random token (base64url, opaque).\n * - `consume(state)` deletes the entry on read. A second call with the\n * same state throws `OAuthStateError('replay')`.\n * - Expired entries also throw (`'expired'`); the entry is deleted as a\n * side effect so a later replay still surfaces correctly.\n *\n * TTL defaults to 10 minutes — long enough for a user to complete the\n * provider's consent screen, short enough that abandoned states age out.\n */\nimport { randomBytes } from 'node:crypto';\nimport {\n type IOAuthStateStore,\n type OAuthStateRecord,\n OAuthStateError,\n} from '../protocols/oauth-state-store';\n\nexport interface MemoryOAuthStateStoreOptions {\n /** TTL in ms. Default 10 minutes. */\n ttlMs?: number;\n /** Injectable clock for tests. Default `Date.now`. */\n now?: () => number;\n /** Injectable token generator for tests. Default 32-byte base64url. */\n generateToken?: () => string;\n}\n\ninterface Slot {\n record: OAuthStateRecord;\n expiresAt: number;\n}\n\nexport class MemoryOAuthStateStore implements IOAuthStateStore {\n private readonly store = new Map<string, Slot>();\n private readonly ttlMs: number;\n private readonly now: () => number;\n private readonly generateToken: () => string;\n\n constructor(opts: MemoryOAuthStateStoreOptions = {}) {\n this.ttlMs = opts.ttlMs ?? 10 * 60 * 1000;\n this.now = opts.now ?? (() => Date.now());\n this.generateToken =\n opts.generateToken ?? (() => randomBytes(32).toString('base64url'));\n }\n\n async generate(record: OAuthStateRecord): Promise<string> {\n const state = this.generateToken();\n this.store.set(state, {\n record: { ...record },\n expiresAt: this.now() + this.ttlMs,\n });\n return state;\n }\n\n async consume(state: string): Promise<OAuthStateRecord> {\n const slot = this.store.get(state);\n if (!slot) {\n throw new OAuthStateError(\n `OAuth state token unknown or already consumed`,\n 'missing',\n );\n }\n // Delete first so a concurrent consume can't replay.\n this.store.delete(state);\n if (slot.expiresAt <= this.now()) {\n throw new OAuthStateError(`OAuth state token expired`, 'expired');\n }\n return slot.record;\n }\n}\n","/**\n * Auth subsystem — `IOAuthStateStore` port.\n *\n * CSRF protection for the OAuth2 authorize-code callback. Generic across\n * providers. The store mints opaque state tokens at /connect time and\n * single-use consumes them at /callback time, returning the original\n * record (userId + optional post-callback redirect path).\n *\n * Concrete backends live under `../backends/`:\n * - `state-store.memory-backend.ts` — in-process Map (tests/dev).\n * - `state-store.drizzle-backend.ts` — Postgres (prod).\n *\n * Semantics:\n * - `generate(record)` → returns an opaque state token; record is stored\n * under that token until consumed or until TTL expires.\n * - `consume(state)` → atomically deletes the entry and returns the\n * record. Throws on missing, expired, or replayed state. Never returns\n * null — a missing/expired state is a CSRF signal.\n */\nexport interface OAuthStateRecord {\n userId: string;\n /** Optional post-callback redirect path (relative URL). */\n redirect?: string;\n}\n\nexport interface IOAuthStateStore {\n /** Mint an opaque state token bound to `record`. Single-use. */\n generate(record: OAuthStateRecord): Promise<string>;\n /**\n * Atomically consume `state`, returning the bound record. Throws on\n * missing / expired / replayed state.\n */\n consume(state: string): Promise<OAuthStateRecord>;\n}\n\n/**\n * Thrown by `IOAuthStateStore.consume` when the state token is unknown,\n * expired, or has already been consumed (replay attempt).\n */\nexport class OAuthStateError extends Error {\n constructor(\n message: string,\n public readonly reason: 'missing' | 'expired',\n ) {\n super(message);\n this.name = 'OAuthStateError';\n }\n}\n","/**\n * Drizzle-backed `IOAuthStateStore`.\n *\n * Uses the `auth_oauth_state` table (see `auth-oauth-state.schema.ts`).\n * Single-use semantics enforced via `DELETE ... RETURNING`: the consume\n * path atomically deletes and returns the row, so a concurrent /callback\n * with the same state cannot replay.\n *\n * Behaviour:\n * - `generate(record)` mints a 256-bit base64url token, INSERTs the row\n * with `expires_at = now() + ttlMs`.\n * - `consume(state)` runs `DELETE ... WHERE state = $1 RETURNING ...`\n * once. Throws `OAuthStateError('missing')` if no row was deleted\n * (unknown or already consumed) and `OAuthStateError('expired')` if\n * the deleted row was past its `expires_at`.\n */\nimport { randomBytes } from 'node:crypto';\nimport { eq } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../../types/drizzle';\nimport { authOAuthState } from '../auth-oauth-state.schema';\nimport {\n type IOAuthStateStore,\n type OAuthStateRecord,\n OAuthStateError,\n} from '../protocols/oauth-state-store';\n\nexport interface DrizzleOAuthStateStoreOptions {\n /** TTL in ms. Default 10 minutes. */\n ttlMs?: number;\n /** Injectable clock for tests. Default `Date.now`. */\n now?: () => number;\n /** Injectable token generator for tests. Default 32-byte base64url. */\n generateToken?: () => string;\n}\n\nexport class DrizzleOAuthStateStore implements IOAuthStateStore {\n private readonly ttlMs: number;\n private readonly now: () => number;\n private readonly generateToken: () => string;\n\n constructor(\n private readonly db: DrizzleClient,\n opts: DrizzleOAuthStateStoreOptions = {},\n ) {\n this.ttlMs = opts.ttlMs ?? 10 * 60 * 1000;\n this.now = opts.now ?? (() => Date.now());\n this.generateToken =\n opts.generateToken ?? (() => randomBytes(32).toString('base64url'));\n }\n\n async generate(record: OAuthStateRecord): Promise<string> {\n const state = this.generateToken();\n const expiresAt = new Date(this.now() + this.ttlMs);\n await this.db.insert(authOAuthState).values({\n state,\n userId: record.userId,\n redirect: record.redirect ?? null,\n expiresAt,\n });\n return state;\n }\n\n async consume(state: string): Promise<OAuthStateRecord> {\n const rows = await this.db\n .delete(authOAuthState)\n .where(eq(authOAuthState.state, state))\n .returning();\n const row = rows[0];\n if (!row) {\n throw new OAuthStateError(\n `OAuth state token unknown or already consumed`,\n 'missing',\n );\n }\n if (row.expiresAt.getTime() <= this.now()) {\n throw new OAuthStateError(`OAuth state token expired`, 'expired');\n }\n return {\n userId: row.userId,\n redirect: row.redirect ?? undefined,\n };\n }\n}\n","/**\n * Drizzle schema for the `auth_oauth_state` table — backs the\n * `DrizzleOAuthStateStore` (`state-store.drizzle-backend.ts`).\n *\n * One row per outstanding /connect → /callback dance. Single-use; rows are\n * deleted on consume. A periodic sweep (or a `WHERE expires_at < now()`\n * filter on read) clears abandoned rows.\n *\n * Columns:\n * - `state` — opaque random token, primary key.\n * - `user_id` — text (matches the consumer-defined user-id shape;\n * the auth subsystem doesn't constrain this to UUID\n * because some apps key users by external id).\n * - `redirect` — optional post-callback redirect path.\n * - `expires_at` — TTL boundary; entries past this are treated as absent.\n *\n * Convention: schema files live at the root of the subsystem dir\n * (mirrors `cache.schema.ts`, `integration-audit.schema.ts`, `domain-events.schema.ts`).\n */\nimport { pgTable, text, timestamp } from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport const authOAuthState = pgTable('auth_oauth_state', {\n state: text('state').primaryKey(),\n userId: text('user_id').notNull(),\n redirect: text('redirect'),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n});\n\nexport type AuthOAuthState = InferSelectModel<typeof authOAuthState>;\n","/**\n * AuthController — provider-agnostic OAuth2 connect/callback dance.\n *\n * Mounts two routes:\n * - `GET /auth/:provider/connect?redirect=...` — generates state, builds\n * the provider's authorize-url, 302-redirects the browser there.\n * - `GET /auth/:provider/callback?code=...&state=...` — consumes state,\n * exchanges the code for tokens, hands them to the grant sink, then\n * 302-redirects to the post-connect path.\n *\n * Hexagonal seams:\n * - `STRATEGY_REGISTRY` (ReadonlyMap<slug, IProviderStrategy>) — dispatch.\n * Concrete per-provider strategies live consumer-side and contribute\n * entries via a `useFactory` in the consumer's app module.\n * - `AUTH_USER_CONTEXT` (IUserContext) — resolves \"who is this request\"\n * from the consumer's session/JWT/etc.\n * - `OAUTH_STATE_STORE` (IOAuthStateStore) — CSRF state minting/consume.\n * - `AUTH_CONNECTION_GRANT_SINK` (IConnectionGrantSink) — persists the\n * freshly-minted grant. Adapter lives consumer-side (e.g. the\n * auth-integrations starter from #285).\n *\n * The controller never imports `ConnectionsService` or any other concrete\n * consumer type — it goes through ports only.\n */\nimport {\n Controller,\n Get,\n Inject,\n Param,\n Query,\n Req,\n Res,\n HttpException,\n HttpStatus,\n} from '@nestjs/common';\nimport {\n AUTH_CONNECTION_GRANT_SINK,\n AUTH_OPTIONS,\n AUTH_USER_CONTEXT,\n OAUTH_STATE_STORE,\n STRATEGY_REGISTRY,\n} from '../auth.tokens';\nimport type { AuthModuleOptions } from '../auth.module';\nimport type { IOAuthStateStore } from '../protocols/oauth-state-store';\nimport type { IUserContext } from '../protocols/user-context';\nimport type {\n IProviderStrategy,\n ProviderStrategyRegistry,\n} from '../protocols/provider-strategy';\nimport type { IConnectionGrantSink } from '../protocols/connection-store';\n\n/**\n * Minimal response surface used by the controller — typed loosely so we\n * don't pull a hard dep on `express` or `fastify`. Both popular HTTP\n * adapters expose `redirect(status, url)`.\n */\ninterface RedirectingResponse {\n redirect(statusCode: number, url: string): unknown;\n}\n\n@Controller('auth')\nexport class AuthController {\n constructor(\n @Inject(STRATEGY_REGISTRY)\n private readonly registry: ProviderStrategyRegistry,\n @Inject(AUTH_USER_CONTEXT)\n private readonly userContext: IUserContext,\n @Inject(OAUTH_STATE_STORE)\n private readonly stateStore: IOAuthStateStore,\n @Inject(AUTH_CONNECTION_GRANT_SINK)\n private readonly grantSink: IConnectionGrantSink,\n @Inject(AUTH_OPTIONS)\n private readonly options: AuthModuleOptions,\n ) {}\n\n @Get(':provider/connect')\n async connect(\n @Param('provider') slug: string,\n @Query('redirect') redirect: string | undefined,\n @Req() req: unknown,\n @Res() res: RedirectingResponse,\n ): Promise<unknown> {\n const strategy = this.requireStrategy(slug);\n const userId = await this.userContext.getCurrentUserId(req);\n const state = await this.stateStore.generate({ userId, redirect });\n const url = strategy.buildAuthorizeUrl({\n state,\n redirectUri: this.redirectUriFor(slug),\n });\n return res.redirect(HttpStatus.FOUND, url);\n }\n\n @Get(':provider/callback')\n async callback(\n @Param('provider') slug: string,\n @Query('code') code: string | undefined,\n @Query('state') state: string | undefined,\n @Res() res: RedirectingResponse,\n ): Promise<unknown> {\n const strategy = this.requireStrategy(slug);\n if (!code) {\n throw new HttpException(\n `Missing 'code' query param`,\n HttpStatus.BAD_REQUEST,\n );\n }\n if (!state) {\n throw new HttpException(\n `Missing 'state' query param`,\n HttpStatus.BAD_REQUEST,\n );\n }\n const { userId, redirect } = await this.stateStore.consume(state);\n const tokens = await strategy.exchangeCodeForTokens({\n code,\n redirectUri: this.redirectUriFor(slug),\n });\n await this.grantSink.createOrUpdateFromOAuthGrant({\n userId,\n provider: slug,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: tokens.expiresAt,\n scope: tokens.scope,\n externalAccountId: tokens.externalAccountId,\n providerMetadata: tokens.providerMetadata,\n });\n return res.redirect(\n HttpStatus.FOUND,\n redirect ?? `/settings/connections?connected=${encodeURIComponent(slug)}`,\n );\n }\n\n private requireStrategy(slug: string): IProviderStrategy {\n const strategy = this.registry.get(slug);\n if (!strategy) {\n throw new HttpException(\n `Unknown provider '${slug}'`,\n HttpStatus.NOT_FOUND,\n );\n }\n return strategy;\n }\n\n private redirectUriFor(slug: string): string {\n const base = this.options.redirectUriBase;\n if (!base) {\n throw new Error(\n `AuthModule.forRoot: redirectUriBase is required when AuthController is enabled`,\n );\n }\n const trimmed = base.replace(/\\/+$/, '');\n return `${trimmed}/auth/${encodeURIComponent(slug)}/callback`;\n }\n}\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n"],"mappings":"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","names":["randomBytes","randomBytes","randomBytes","randomBytes"]}
@@ -1,31 +1,3 @@
1
- /**
2
- * Auth subsystem — injection tokens.
3
- *
4
- * Following ADR-008 guidance: `Symbol()` tokens for type safety and collision
5
- * avoidance. Consumers inject these via `@Inject(...)` against the matching
6
- * protocol interface.
7
- *
8
- * Usage:
9
- * ```typescript
10
- * constructor(
11
- * @Inject(ENCRYPTION_KEY) private readonly key: IEncryptionKey,
12
- * @Inject(OAUTH_STATE_STORE) private readonly states: IOAuthStateStore,
13
- * @Inject(AUTH_CONNECTION_READER) private readonly reader: IConnectionReader,
14
- * @Inject(AUTH_CONNECTION_TOKEN_WRITER) private readonly writer: IConnectionTokenWriter,
15
- * @Inject(AUTH_CONNECTION_GRANT_SINK) private readonly grants: IConnectionGrantSink,
16
- * @Inject(AUTH_USER_CONTEXT) private readonly userCtx: IUserContext,
17
- * @Inject(STRATEGY_REGISTRY) private readonly registry: ProviderStrategyRegistry,
18
- * ) {}
19
- * ```
20
- *
21
- * `IAuthStrategy` implementations are provider-specific and registered under
22
- * provider-specific tokens (e.g. `SALESFORCE_AUTH_STRATEGY`,
23
- * `HUBSPOT_AUTH_STRATEGY`) by each connection module — this subsystem does
24
- * not mandate a single `AUTH_STRATEGY` token because an app typically has
25
- * many concurrent strategies, one per provider. They are dispatched through
26
- * `STRATEGY_REGISTRY` (a `ReadonlyMap<slug, IProviderStrategy>`), populated
27
- * by per-provider modules via a `useFactory` provider.
28
- */
29
1
  declare const ENCRYPTION_KEY: unique symbol;
30
2
  declare const OAUTH_STATE_STORE: unique symbol;
31
3
  declare const AUTH_CONNECTION_READER: unique symbol;
@@ -1,12 +1,16 @@
1
+ // runtime/subsystems/token-key.ts
2
+ var PKG = "@pattern-stack/codegen";
3
+ var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
4
+
1
5
  // runtime/subsystems/auth/auth.tokens.ts
2
- var ENCRYPTION_KEY = /* @__PURE__ */ Symbol("ENCRYPTION_KEY");
3
- var OAUTH_STATE_STORE = /* @__PURE__ */ Symbol("OAUTH_STATE_STORE");
4
- var AUTH_CONNECTION_READER = /* @__PURE__ */ Symbol("AUTH_CONNECTION_READER");
5
- var AUTH_CONNECTION_TOKEN_WRITER = /* @__PURE__ */ Symbol("AUTH_CONNECTION_TOKEN_WRITER");
6
- var AUTH_CONNECTION_GRANT_SINK = /* @__PURE__ */ Symbol("AUTH_CONNECTION_GRANT_SINK");
7
- var AUTH_USER_CONTEXT = /* @__PURE__ */ Symbol("AUTH_USER_CONTEXT");
8
- var STRATEGY_REGISTRY = /* @__PURE__ */ Symbol("STRATEGY_REGISTRY");
9
- var AUTH_OPTIONS = /* @__PURE__ */ Symbol("AUTH_OPTIONS");
6
+ var ENCRYPTION_KEY = Symbol.for(tokenKey("auth", "encryption-key"));
7
+ var OAUTH_STATE_STORE = Symbol.for(tokenKey("auth", "oauth-state-store"));
8
+ var AUTH_CONNECTION_READER = Symbol.for(tokenKey("auth", "connection-reader"));
9
+ var AUTH_CONNECTION_TOKEN_WRITER = Symbol.for(tokenKey("auth", "connection-token-writer"));
10
+ var AUTH_CONNECTION_GRANT_SINK = Symbol.for(tokenKey("auth", "connection-grant-sink"));
11
+ var AUTH_USER_CONTEXT = Symbol.for(tokenKey("auth", "user-context"));
12
+ var STRATEGY_REGISTRY = Symbol.for(tokenKey("auth", "strategy-registry"));
13
+ var AUTH_OPTIONS = Symbol.for(tokenKey("auth", "options"));
10
14
  export {
11
15
  AUTH_CONNECTION_GRANT_SINK,
12
16
  AUTH_CONNECTION_READER,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../runtime/subsystems/auth/auth.tokens.ts"],"sourcesContent":["/**\n * Auth subsystem — injection tokens.\n *\n * Following ADR-008 guidance: `Symbol()` tokens for type safety and collision\n * avoidance. Consumers inject these via `@Inject(...)` against the matching\n * protocol interface.\n *\n * Usage:\n * ```typescript\n * constructor(\n * @Inject(ENCRYPTION_KEY) private readonly key: IEncryptionKey,\n * @Inject(OAUTH_STATE_STORE) private readonly states: IOAuthStateStore,\n * @Inject(AUTH_CONNECTION_READER) private readonly reader: IConnectionReader,\n * @Inject(AUTH_CONNECTION_TOKEN_WRITER) private readonly writer: IConnectionTokenWriter,\n * @Inject(AUTH_CONNECTION_GRANT_SINK) private readonly grants: IConnectionGrantSink,\n * @Inject(AUTH_USER_CONTEXT) private readonly userCtx: IUserContext,\n * @Inject(STRATEGY_REGISTRY) private readonly registry: ProviderStrategyRegistry,\n * ) {}\n * ```\n *\n * `IAuthStrategy` implementations are provider-specific and registered under\n * provider-specific tokens (e.g. `SALESFORCE_AUTH_STRATEGY`,\n * `HUBSPOT_AUTH_STRATEGY`) by each connection module — this subsystem does\n * not mandate a single `AUTH_STRATEGY` token because an app typically has\n * many concurrent strategies, one per provider. They are dispatched through\n * `STRATEGY_REGISTRY` (a `ReadonlyMap<slug, IProviderStrategy>`), populated\n * by per-provider modules via a `useFactory` provider.\n */\nexport const ENCRYPTION_KEY = Symbol('ENCRYPTION_KEY');\nexport const OAUTH_STATE_STORE = Symbol('OAUTH_STATE_STORE');\nexport const AUTH_CONNECTION_READER = Symbol('AUTH_CONNECTION_READER');\nexport const AUTH_CONNECTION_TOKEN_WRITER = Symbol('AUTH_CONNECTION_TOKEN_WRITER');\nexport const AUTH_CONNECTION_GRANT_SINK = Symbol('AUTH_CONNECTION_GRANT_SINK');\nexport const AUTH_USER_CONTEXT = Symbol('AUTH_USER_CONTEXT');\nexport const STRATEGY_REGISTRY = Symbol('STRATEGY_REGISTRY');\n/**\n * Holds the resolved `AuthModuleOptions` (used by `AuthController` to read\n * `redirectUriBase` for building per-provider callback URIs).\n */\nexport const AUTH_OPTIONS = Symbol('AUTH_OPTIONS');\n"],"mappings":";AA4BO,IAAM,iBAAiB,uBAAO,gBAAgB;AAC9C,IAAM,oBAAoB,uBAAO,mBAAmB;AACpD,IAAM,yBAAyB,uBAAO,wBAAwB;AAC9D,IAAM,+BAA+B,uBAAO,8BAA8B;AAC1E,IAAM,6BAA6B,uBAAO,4BAA4B;AACtE,IAAM,oBAAoB,uBAAO,mBAAmB;AACpD,IAAM,oBAAoB,uBAAO,mBAAmB;AAKpD,IAAM,eAAe,uBAAO,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../../../runtime/subsystems/token-key.ts","../../../../runtime/subsystems/auth/auth.tokens.ts"],"sourcesContent":["/** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded\n * constant (NOT derived from package.json) so a vendored copy — which lives inside the\n * CONSUMER's package — produces the identical key and the two copies share the symbol. */\nexport const PKG = '@pattern-stack/codegen';\n// TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only\n// (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.\nexport const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;\n","/**\n * Auth subsystem — injection tokens.\n *\n * Following ADR-008 guidance: `Symbol()` tokens for type safety and collision\n * avoidance. Consumers inject these via `@Inject(...)` against the matching\n * protocol interface.\n *\n * Usage:\n * ```typescript\n * constructor(\n * @Inject(ENCRYPTION_KEY) private readonly key: IEncryptionKey,\n * @Inject(OAUTH_STATE_STORE) private readonly states: IOAuthStateStore,\n * @Inject(AUTH_CONNECTION_READER) private readonly reader: IConnectionReader,\n * @Inject(AUTH_CONNECTION_TOKEN_WRITER) private readonly writer: IConnectionTokenWriter,\n * @Inject(AUTH_CONNECTION_GRANT_SINK) private readonly grants: IConnectionGrantSink,\n * @Inject(AUTH_USER_CONTEXT) private readonly userCtx: IUserContext,\n * @Inject(STRATEGY_REGISTRY) private readonly registry: ProviderStrategyRegistry,\n * ) {}\n * ```\n *\n * `IAuthStrategy` implementations are provider-specific and registered under\n * provider-specific tokens (e.g. `SALESFORCE_AUTH_STRATEGY`,\n * `HUBSPOT_AUTH_STRATEGY`) by each connection module — this subsystem does\n * not mandate a single `AUTH_STRATEGY` token because an app typically has\n * many concurrent strategies, one per provider. They are dispatched through\n * `STRATEGY_REGISTRY` (a `ReadonlyMap<slug, IProviderStrategy>`), populated\n * by per-provider modules via a `useFactory` provider.\n */\nimport { tokenKey } from '../token-key';\n\n// ADR-037: namespaced `Symbol.for(...)` keys so a token matches by VALUE across\n// import boundaries — the package copy and a (legacy) vendored copy resolve to\n// the SAME symbol, eliminating the dual-package DI-token identity hazard that\n// crashed boot once the emitter began emitting `STRATEGY_REGISTRY` as a runtime\n// value (RFC-0003 R5). Matches the convention surface packages already use.\nexport const ENCRYPTION_KEY = Symbol.for(tokenKey('auth', 'encryption-key'));\nexport const OAUTH_STATE_STORE = Symbol.for(tokenKey('auth', 'oauth-state-store'));\nexport const AUTH_CONNECTION_READER = Symbol.for(tokenKey('auth', 'connection-reader'));\nexport const AUTH_CONNECTION_TOKEN_WRITER = Symbol.for(tokenKey('auth', 'connection-token-writer'));\nexport const AUTH_CONNECTION_GRANT_SINK = Symbol.for(tokenKey('auth', 'connection-grant-sink'));\nexport const AUTH_USER_CONTEXT = Symbol.for(tokenKey('auth', 'user-context'));\nexport const STRATEGY_REGISTRY = Symbol.for(tokenKey('auth', 'strategy-registry'));\n/**\n * Holds the resolved `AuthModuleOptions` (used by `AuthController` to read\n * `redirectUriBase` for building per-provider callback URIs).\n */\nexport const AUTH_OPTIONS = Symbol.for(tokenKey('auth', 'options'));\n"],"mappings":";AAGO,IAAM,MAAM;AAGZ,IAAM,WAAW,CAAC,MAAc,SAAyB,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI;;;AC6B/E,IAAM,iBAAiB,OAAO,IAAI,SAAS,QAAQ,gBAAgB,CAAC;AACpE,IAAM,oBAAoB,OAAO,IAAI,SAAS,QAAQ,mBAAmB,CAAC;AAC1E,IAAM,yBAAyB,OAAO,IAAI,SAAS,QAAQ,mBAAmB,CAAC;AAC/E,IAAM,+BAA+B,OAAO,IAAI,SAAS,QAAQ,yBAAyB,CAAC;AAC3F,IAAM,6BAA6B,OAAO,IAAI,SAAS,QAAQ,uBAAuB,CAAC;AACvF,IAAM,oBAAoB,OAAO,IAAI,SAAS,QAAQ,cAAc,CAAC;AACrE,IAAM,oBAAoB,OAAO,IAAI,SAAS,QAAQ,mBAAmB,CAAC;AAK1E,IAAM,eAAe,OAAO,IAAI,SAAS,QAAQ,SAAS,CAAC;","names":[]}
@@ -23,12 +23,19 @@ import {
23
23
  HttpStatus
24
24
  } from "@nestjs/common";
25
25
 
26
+ // runtime/subsystems/token-key.ts
27
+ var PKG = "@pattern-stack/codegen";
28
+ var tokenKey = (area, name) => `${PKG}.${area}.${name}`;
29
+
26
30
  // runtime/subsystems/auth/auth.tokens.ts
27
- var OAUTH_STATE_STORE = /* @__PURE__ */ Symbol("OAUTH_STATE_STORE");
28
- var AUTH_CONNECTION_GRANT_SINK = /* @__PURE__ */ Symbol("AUTH_CONNECTION_GRANT_SINK");
29
- var AUTH_USER_CONTEXT = /* @__PURE__ */ Symbol("AUTH_USER_CONTEXT");
30
- var STRATEGY_REGISTRY = /* @__PURE__ */ Symbol("STRATEGY_REGISTRY");
31
- var AUTH_OPTIONS = /* @__PURE__ */ Symbol("AUTH_OPTIONS");
31
+ var ENCRYPTION_KEY = Symbol.for(tokenKey("auth", "encryption-key"));
32
+ var OAUTH_STATE_STORE = Symbol.for(tokenKey("auth", "oauth-state-store"));
33
+ var AUTH_CONNECTION_READER = Symbol.for(tokenKey("auth", "connection-reader"));
34
+ var AUTH_CONNECTION_TOKEN_WRITER = Symbol.for(tokenKey("auth", "connection-token-writer"));
35
+ var AUTH_CONNECTION_GRANT_SINK = Symbol.for(tokenKey("auth", "connection-grant-sink"));
36
+ var AUTH_USER_CONTEXT = Symbol.for(tokenKey("auth", "user-context"));
37
+ var STRATEGY_REGISTRY = Symbol.for(tokenKey("auth", "strategy-registry"));
38
+ var AUTH_OPTIONS = Symbol.for(tokenKey("auth", "options"));
32
39
 
33
40
  // runtime/subsystems/auth/controllers/auth.controller.ts
34
41
  var AuthController = class {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../runtime/subsystems/auth/controllers/auth.controller.ts","../../../../../runtime/subsystems/auth/auth.tokens.ts"],"sourcesContent":["/**\n * AuthController — provider-agnostic OAuth2 connect/callback dance.\n *\n * Mounts two routes:\n * - `GET /auth/:provider/connect?redirect=...` — generates state, builds\n * the provider's authorize-url, 302-redirects the browser there.\n * - `GET /auth/:provider/callback?code=...&state=...` — consumes state,\n * exchanges the code for tokens, hands them to the grant sink, then\n * 302-redirects to the post-connect path.\n *\n * Hexagonal seams:\n * - `STRATEGY_REGISTRY` (ReadonlyMap<slug, IProviderStrategy>) — dispatch.\n * Concrete per-provider strategies live consumer-side and contribute\n * entries via a `useFactory` in the consumer's app module.\n * - `AUTH_USER_CONTEXT` (IUserContext) — resolves \"who is this request\"\n * from the consumer's session/JWT/etc.\n * - `OAUTH_STATE_STORE` (IOAuthStateStore) — CSRF state minting/consume.\n * - `AUTH_CONNECTION_GRANT_SINK` (IConnectionGrantSink) — persists the\n * freshly-minted grant. Adapter lives consumer-side (e.g. the\n * auth-integrations starter from #285).\n *\n * The controller never imports `ConnectionsService` or any other concrete\n * consumer type — it goes through ports only.\n */\nimport {\n Controller,\n Get,\n Inject,\n Param,\n Query,\n Req,\n Res,\n HttpException,\n HttpStatus,\n} from '@nestjs/common';\nimport {\n AUTH_CONNECTION_GRANT_SINK,\n AUTH_OPTIONS,\n AUTH_USER_CONTEXT,\n OAUTH_STATE_STORE,\n STRATEGY_REGISTRY,\n} from '../auth.tokens';\nimport type { AuthModuleOptions } from '../auth.module';\nimport type { IOAuthStateStore } from '../protocols/oauth-state-store';\nimport type { IUserContext } from '../protocols/user-context';\nimport type {\n IProviderStrategy,\n ProviderStrategyRegistry,\n} from '../protocols/provider-strategy';\nimport type { IConnectionGrantSink } from '../protocols/connection-store';\n\n/**\n * Minimal response surface used by the controller — typed loosely so we\n * don't pull a hard dep on `express` or `fastify`. Both popular HTTP\n * adapters expose `redirect(status, url)`.\n */\ninterface RedirectingResponse {\n redirect(statusCode: number, url: string): unknown;\n}\n\n@Controller('auth')\nexport class AuthController {\n constructor(\n @Inject(STRATEGY_REGISTRY)\n private readonly registry: ProviderStrategyRegistry,\n @Inject(AUTH_USER_CONTEXT)\n private readonly userContext: IUserContext,\n @Inject(OAUTH_STATE_STORE)\n private readonly stateStore: IOAuthStateStore,\n @Inject(AUTH_CONNECTION_GRANT_SINK)\n private readonly grantSink: IConnectionGrantSink,\n @Inject(AUTH_OPTIONS)\n private readonly options: AuthModuleOptions,\n ) {}\n\n @Get(':provider/connect')\n async connect(\n @Param('provider') slug: string,\n @Query('redirect') redirect: string | undefined,\n @Req() req: unknown,\n @Res() res: RedirectingResponse,\n ): Promise<unknown> {\n const strategy = this.requireStrategy(slug);\n const userId = await this.userContext.getCurrentUserId(req);\n const state = await this.stateStore.generate({ userId, redirect });\n const url = strategy.buildAuthorizeUrl({\n state,\n redirectUri: this.redirectUriFor(slug),\n });\n return res.redirect(HttpStatus.FOUND, url);\n }\n\n @Get(':provider/callback')\n async callback(\n @Param('provider') slug: string,\n @Query('code') code: string | undefined,\n @Query('state') state: string | undefined,\n @Res() res: RedirectingResponse,\n ): Promise<unknown> {\n const strategy = this.requireStrategy(slug);\n if (!code) {\n throw new HttpException(\n `Missing 'code' query param`,\n HttpStatus.BAD_REQUEST,\n );\n }\n if (!state) {\n throw new HttpException(\n `Missing 'state' query param`,\n HttpStatus.BAD_REQUEST,\n );\n }\n const { userId, redirect } = await this.stateStore.consume(state);\n const tokens = await strategy.exchangeCodeForTokens({\n code,\n redirectUri: this.redirectUriFor(slug),\n });\n await this.grantSink.createOrUpdateFromOAuthGrant({\n userId,\n provider: slug,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: tokens.expiresAt,\n scope: tokens.scope,\n externalAccountId: tokens.externalAccountId,\n providerMetadata: tokens.providerMetadata,\n });\n return res.redirect(\n HttpStatus.FOUND,\n redirect ?? `/settings/connections?connected=${encodeURIComponent(slug)}`,\n );\n }\n\n private requireStrategy(slug: string): IProviderStrategy {\n const strategy = this.registry.get(slug);\n if (!strategy) {\n throw new HttpException(\n `Unknown provider '${slug}'`,\n HttpStatus.NOT_FOUND,\n );\n }\n return strategy;\n }\n\n private redirectUriFor(slug: string): string {\n const base = this.options.redirectUriBase;\n if (!base) {\n throw new Error(\n `AuthModule.forRoot: redirectUriBase is required when AuthController is enabled`,\n );\n }\n const trimmed = base.replace(/\\/+$/, '');\n return `${trimmed}/auth/${encodeURIComponent(slug)}/callback`;\n }\n}\n","/**\n * Auth subsystem — injection tokens.\n *\n * Following ADR-008 guidance: `Symbol()` tokens for type safety and collision\n * avoidance. Consumers inject these via `@Inject(...)` against the matching\n * protocol interface.\n *\n * Usage:\n * ```typescript\n * constructor(\n * @Inject(ENCRYPTION_KEY) private readonly key: IEncryptionKey,\n * @Inject(OAUTH_STATE_STORE) private readonly states: IOAuthStateStore,\n * @Inject(AUTH_CONNECTION_READER) private readonly reader: IConnectionReader,\n * @Inject(AUTH_CONNECTION_TOKEN_WRITER) private readonly writer: IConnectionTokenWriter,\n * @Inject(AUTH_CONNECTION_GRANT_SINK) private readonly grants: IConnectionGrantSink,\n * @Inject(AUTH_USER_CONTEXT) private readonly userCtx: IUserContext,\n * @Inject(STRATEGY_REGISTRY) private readonly registry: ProviderStrategyRegistry,\n * ) {}\n * ```\n *\n * `IAuthStrategy` implementations are provider-specific and registered under\n * provider-specific tokens (e.g. `SALESFORCE_AUTH_STRATEGY`,\n * `HUBSPOT_AUTH_STRATEGY`) by each connection module — this subsystem does\n * not mandate a single `AUTH_STRATEGY` token because an app typically has\n * many concurrent strategies, one per provider. They are dispatched through\n * `STRATEGY_REGISTRY` (a `ReadonlyMap<slug, IProviderStrategy>`), populated\n * by per-provider modules via a `useFactory` provider.\n */\nexport const ENCRYPTION_KEY = Symbol('ENCRYPTION_KEY');\nexport const OAUTH_STATE_STORE = Symbol('OAUTH_STATE_STORE');\nexport const AUTH_CONNECTION_READER = Symbol('AUTH_CONNECTION_READER');\nexport const AUTH_CONNECTION_TOKEN_WRITER = Symbol('AUTH_CONNECTION_TOKEN_WRITER');\nexport const AUTH_CONNECTION_GRANT_SINK = Symbol('AUTH_CONNECTION_GRANT_SINK');\nexport const AUTH_USER_CONTEXT = Symbol('AUTH_USER_CONTEXT');\nexport const STRATEGY_REGISTRY = Symbol('STRATEGY_REGISTRY');\n/**\n * Holds the resolved `AuthModuleOptions` (used by `AuthController` to read\n * `redirectUriBase` for building per-provider callback URIs).\n */\nexport const AUTH_OPTIONS = Symbol('AUTH_OPTIONS');\n"],"mappings":";;;;;;;;;;;;;AAwBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACLA,IAAM,oBAAoB,uBAAO,mBAAmB;AAGpD,IAAM,6BAA6B,uBAAO,4BAA4B;AACtE,IAAM,oBAAoB,uBAAO,mBAAmB;AACpD,IAAM,oBAAoB,uBAAO,mBAAmB;AAKpD,IAAM,eAAe,uBAAO,cAAc;;;ADsB1C,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAEmB,UAEA,aAEA,YAEA,WAEA,SACjB;AATiB;AAEA;AAEA;AAEA;AAEA;AAAA,EAChB;AAAA,EATgB;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EAInB,MAAM,QACe,MACA,UACZ,KACA,KACW;AAClB,UAAM,WAAW,KAAK,gBAAgB,IAAI;AAC1C,UAAM,SAAS,MAAM,KAAK,YAAY,iBAAiB,GAAG;AAC1D,UAAM,QAAQ,MAAM,KAAK,WAAW,SAAS,EAAE,QAAQ,SAAS,CAAC;AACjE,UAAM,MAAM,SAAS,kBAAkB;AAAA,MACrC;AAAA,MACA,aAAa,KAAK,eAAe,IAAI;AAAA,IACvC,CAAC;AACD,WAAO,IAAI,SAAS,WAAW,OAAO,GAAG;AAAA,EAC3C;AAAA,EAGA,MAAM,SACe,MACJ,MACC,OACT,KACW;AAClB,UAAM,WAAW,KAAK,gBAAgB,IAAI;AAC1C,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR;AAAA,QACA,WAAW;AAAA,MACb;AAAA,IACF;AACA,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,QACA,WAAW;AAAA,MACb;AAAA,IACF;AACA,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,KAAK,WAAW,QAAQ,KAAK;AAChE,UAAM,SAAS,MAAM,SAAS,sBAAsB;AAAA,MAClD;AAAA,MACA,aAAa,KAAK,eAAe,IAAI;AAAA,IACvC,CAAC;AACD,UAAM,KAAK,UAAU,6BAA6B;AAAA,MAChD;AAAA,MACA,UAAU;AAAA,MACV,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,MACrB,WAAW,OAAO;AAAA,MAClB,OAAO,OAAO;AAAA,MACd,mBAAmB,OAAO;AAAA,MAC1B,kBAAkB,OAAO;AAAA,IAC3B,CAAC;AACD,WAAO,IAAI;AAAA,MACT,WAAW;AAAA,MACX,YAAY,mCAAmC,mBAAmB,IAAI,CAAC;AAAA,IACzE;AAAA,EACF;AAAA,EAEQ,gBAAgB,MAAiC;AACvD,UAAM,WAAW,KAAK,SAAS,IAAI,IAAI;AACvC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;AAAA,QACR,qBAAqB,IAAI;AAAA,QACzB,WAAW;AAAA,MACb;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,MAAsB;AAC3C,UAAM,OAAO,KAAK,QAAQ;AAC1B,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,UAAU,KAAK,QAAQ,QAAQ,EAAE;AACvC,WAAO,GAAG,OAAO,SAAS,mBAAmB,IAAI,CAAC;AAAA,EACpD;AACF;AA9EQ;AAAA,EADL,IAAI,mBAAmB;AAAA,EAErB,yBAAM,UAAU;AAAA,EAChB,yBAAM,UAAU;AAAA,EAChB,uBAAI;AAAA,EACJ,uBAAI;AAAA,GAnBI,eAeL;AAiBA;AAAA,EADL,IAAI,oBAAoB;AAAA,EAEtB,yBAAM,UAAU;AAAA,EAChB,yBAAM,MAAM;AAAA,EACZ,yBAAM,OAAO;AAAA,EACb,uBAAI;AAAA,GApCI,eAgCL;AAhCK,iBAAN;AAAA,EADN,WAAW,MAAM;AAAA,EAGb,0BAAO,iBAAiB;AAAA,EAExB,0BAAO,iBAAiB;AAAA,EAExB,0BAAO,iBAAiB;AAAA,EAExB,0BAAO,0BAA0B;AAAA,EAEjC,0BAAO,YAAY;AAAA,GAVX;","names":[]}
1
+ {"version":3,"sources":["../../../../../runtime/subsystems/auth/controllers/auth.controller.ts","../../../../../runtime/subsystems/token-key.ts","../../../../../runtime/subsystems/auth/auth.tokens.ts"],"sourcesContent":["/**\n * AuthController — provider-agnostic OAuth2 connect/callback dance.\n *\n * Mounts two routes:\n * - `GET /auth/:provider/connect?redirect=...` — generates state, builds\n * the provider's authorize-url, 302-redirects the browser there.\n * - `GET /auth/:provider/callback?code=...&state=...` — consumes state,\n * exchanges the code for tokens, hands them to the grant sink, then\n * 302-redirects to the post-connect path.\n *\n * Hexagonal seams:\n * - `STRATEGY_REGISTRY` (ReadonlyMap<slug, IProviderStrategy>) — dispatch.\n * Concrete per-provider strategies live consumer-side and contribute\n * entries via a `useFactory` in the consumer's app module.\n * - `AUTH_USER_CONTEXT` (IUserContext) — resolves \"who is this request\"\n * from the consumer's session/JWT/etc.\n * - `OAUTH_STATE_STORE` (IOAuthStateStore) — CSRF state minting/consume.\n * - `AUTH_CONNECTION_GRANT_SINK` (IConnectionGrantSink) — persists the\n * freshly-minted grant. Adapter lives consumer-side (e.g. the\n * auth-integrations starter from #285).\n *\n * The controller never imports `ConnectionsService` or any other concrete\n * consumer type — it goes through ports only.\n */\nimport {\n Controller,\n Get,\n Inject,\n Param,\n Query,\n Req,\n Res,\n HttpException,\n HttpStatus,\n} from '@nestjs/common';\nimport {\n AUTH_CONNECTION_GRANT_SINK,\n AUTH_OPTIONS,\n AUTH_USER_CONTEXT,\n OAUTH_STATE_STORE,\n STRATEGY_REGISTRY,\n} from '../auth.tokens';\nimport type { AuthModuleOptions } from '../auth.module';\nimport type { IOAuthStateStore } from '../protocols/oauth-state-store';\nimport type { IUserContext } from '../protocols/user-context';\nimport type {\n IProviderStrategy,\n ProviderStrategyRegistry,\n} from '../protocols/provider-strategy';\nimport type { IConnectionGrantSink } from '../protocols/connection-store';\n\n/**\n * Minimal response surface used by the controller — typed loosely so we\n * don't pull a hard dep on `express` or `fastify`. Both popular HTTP\n * adapters expose `redirect(status, url)`.\n */\ninterface RedirectingResponse {\n redirect(statusCode: number, url: string): unknown;\n}\n\n@Controller('auth')\nexport class AuthController {\n constructor(\n @Inject(STRATEGY_REGISTRY)\n private readonly registry: ProviderStrategyRegistry,\n @Inject(AUTH_USER_CONTEXT)\n private readonly userContext: IUserContext,\n @Inject(OAUTH_STATE_STORE)\n private readonly stateStore: IOAuthStateStore,\n @Inject(AUTH_CONNECTION_GRANT_SINK)\n private readonly grantSink: IConnectionGrantSink,\n @Inject(AUTH_OPTIONS)\n private readonly options: AuthModuleOptions,\n ) {}\n\n @Get(':provider/connect')\n async connect(\n @Param('provider') slug: string,\n @Query('redirect') redirect: string | undefined,\n @Req() req: unknown,\n @Res() res: RedirectingResponse,\n ): Promise<unknown> {\n const strategy = this.requireStrategy(slug);\n const userId = await this.userContext.getCurrentUserId(req);\n const state = await this.stateStore.generate({ userId, redirect });\n const url = strategy.buildAuthorizeUrl({\n state,\n redirectUri: this.redirectUriFor(slug),\n });\n return res.redirect(HttpStatus.FOUND, url);\n }\n\n @Get(':provider/callback')\n async callback(\n @Param('provider') slug: string,\n @Query('code') code: string | undefined,\n @Query('state') state: string | undefined,\n @Res() res: RedirectingResponse,\n ): Promise<unknown> {\n const strategy = this.requireStrategy(slug);\n if (!code) {\n throw new HttpException(\n `Missing 'code' query param`,\n HttpStatus.BAD_REQUEST,\n );\n }\n if (!state) {\n throw new HttpException(\n `Missing 'state' query param`,\n HttpStatus.BAD_REQUEST,\n );\n }\n const { userId, redirect } = await this.stateStore.consume(state);\n const tokens = await strategy.exchangeCodeForTokens({\n code,\n redirectUri: this.redirectUriFor(slug),\n });\n await this.grantSink.createOrUpdateFromOAuthGrant({\n userId,\n provider: slug,\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n expiresAt: tokens.expiresAt,\n scope: tokens.scope,\n externalAccountId: tokens.externalAccountId,\n providerMetadata: tokens.providerMetadata,\n });\n return res.redirect(\n HttpStatus.FOUND,\n redirect ?? `/settings/connections?connected=${encodeURIComponent(slug)}`,\n );\n }\n\n private requireStrategy(slug: string): IProviderStrategy {\n const strategy = this.registry.get(slug);\n if (!strategy) {\n throw new HttpException(\n `Unknown provider '${slug}'`,\n HttpStatus.NOT_FOUND,\n );\n }\n return strategy;\n }\n\n private redirectUriFor(slug: string): string {\n const base = this.options.redirectUriBase;\n if (!base) {\n throw new Error(\n `AuthModule.forRoot: redirectUriBase is required when AuthController is enabled`,\n );\n }\n const trimmed = base.replace(/\\/+$/, '');\n return `${trimmed}/auth/${encodeURIComponent(slug)}/callback`;\n }\n}\n","/** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded\n * constant (NOT derived from package.json) so a vendored copy — which lives inside the\n * CONSUMER's package — produces the identical key and the two copies share the symbol. */\nexport const PKG = '@pattern-stack/codegen';\n// TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only\n// (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.\nexport const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;\n","/**\n * Auth subsystem — injection tokens.\n *\n * Following ADR-008 guidance: `Symbol()` tokens for type safety and collision\n * avoidance. Consumers inject these via `@Inject(...)` against the matching\n * protocol interface.\n *\n * Usage:\n * ```typescript\n * constructor(\n * @Inject(ENCRYPTION_KEY) private readonly key: IEncryptionKey,\n * @Inject(OAUTH_STATE_STORE) private readonly states: IOAuthStateStore,\n * @Inject(AUTH_CONNECTION_READER) private readonly reader: IConnectionReader,\n * @Inject(AUTH_CONNECTION_TOKEN_WRITER) private readonly writer: IConnectionTokenWriter,\n * @Inject(AUTH_CONNECTION_GRANT_SINK) private readonly grants: IConnectionGrantSink,\n * @Inject(AUTH_USER_CONTEXT) private readonly userCtx: IUserContext,\n * @Inject(STRATEGY_REGISTRY) private readonly registry: ProviderStrategyRegistry,\n * ) {}\n * ```\n *\n * `IAuthStrategy` implementations are provider-specific and registered under\n * provider-specific tokens (e.g. `SALESFORCE_AUTH_STRATEGY`,\n * `HUBSPOT_AUTH_STRATEGY`) by each connection module — this subsystem does\n * not mandate a single `AUTH_STRATEGY` token because an app typically has\n * many concurrent strategies, one per provider. They are dispatched through\n * `STRATEGY_REGISTRY` (a `ReadonlyMap<slug, IProviderStrategy>`), populated\n * by per-provider modules via a `useFactory` provider.\n */\nimport { tokenKey } from '../token-key';\n\n// ADR-037: namespaced `Symbol.for(...)` keys so a token matches by VALUE across\n// import boundaries — the package copy and a (legacy) vendored copy resolve to\n// the SAME symbol, eliminating the dual-package DI-token identity hazard that\n// crashed boot once the emitter began emitting `STRATEGY_REGISTRY` as a runtime\n// value (RFC-0003 R5). Matches the convention surface packages already use.\nexport const ENCRYPTION_KEY = Symbol.for(tokenKey('auth', 'encryption-key'));\nexport const OAUTH_STATE_STORE = Symbol.for(tokenKey('auth', 'oauth-state-store'));\nexport const AUTH_CONNECTION_READER = Symbol.for(tokenKey('auth', 'connection-reader'));\nexport const AUTH_CONNECTION_TOKEN_WRITER = Symbol.for(tokenKey('auth', 'connection-token-writer'));\nexport const AUTH_CONNECTION_GRANT_SINK = Symbol.for(tokenKey('auth', 'connection-grant-sink'));\nexport const AUTH_USER_CONTEXT = Symbol.for(tokenKey('auth', 'user-context'));\nexport const STRATEGY_REGISTRY = Symbol.for(tokenKey('auth', 'strategy-registry'));\n/**\n * Holds the resolved `AuthModuleOptions` (used by `AuthController` to read\n * `redirectUriBase` for building per-provider callback URIs).\n */\nexport const AUTH_OPTIONS = Symbol.for(tokenKey('auth', 'options'));\n"],"mappings":";;;;;;;;;;;;;AAwBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;AC/BA,IAAM,MAAM;AAGZ,IAAM,WAAW,CAAC,MAAc,SAAyB,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI;;;AC6B/E,IAAM,iBAAiB,OAAO,IAAI,SAAS,QAAQ,gBAAgB,CAAC;AACpE,IAAM,oBAAoB,OAAO,IAAI,SAAS,QAAQ,mBAAmB,CAAC;AAC1E,IAAM,yBAAyB,OAAO,IAAI,SAAS,QAAQ,mBAAmB,CAAC;AAC/E,IAAM,+BAA+B,OAAO,IAAI,SAAS,QAAQ,yBAAyB,CAAC;AAC3F,IAAM,6BAA6B,OAAO,IAAI,SAAS,QAAQ,uBAAuB,CAAC;AACvF,IAAM,oBAAoB,OAAO,IAAI,SAAS,QAAQ,cAAc,CAAC;AACrE,IAAM,oBAAoB,OAAO,IAAI,SAAS,QAAQ,mBAAmB,CAAC;AAK1E,IAAM,eAAe,OAAO,IAAI,SAAS,QAAQ,SAAS,CAAC;;;AFe3D,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAEmB,UAEA,aAEA,YAEA,WAEA,SACjB;AATiB;AAEA;AAEA;AAEA;AAEA;AAAA,EAChB;AAAA,EATgB;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EAEA;AAAA,EAInB,MAAM,QACe,MACA,UACZ,KACA,KACW;AAClB,UAAM,WAAW,KAAK,gBAAgB,IAAI;AAC1C,UAAM,SAAS,MAAM,KAAK,YAAY,iBAAiB,GAAG;AAC1D,UAAM,QAAQ,MAAM,KAAK,WAAW,SAAS,EAAE,QAAQ,SAAS,CAAC;AACjE,UAAM,MAAM,SAAS,kBAAkB;AAAA,MACrC;AAAA,MACA,aAAa,KAAK,eAAe,IAAI;AAAA,IACvC,CAAC;AACD,WAAO,IAAI,SAAS,WAAW,OAAO,GAAG;AAAA,EAC3C;AAAA,EAGA,MAAM,SACe,MACJ,MACC,OACT,KACW;AAClB,UAAM,WAAW,KAAK,gBAAgB,IAAI;AAC1C,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR;AAAA,QACA,WAAW;AAAA,MACb;AAAA,IACF;AACA,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,QACA,WAAW;AAAA,MACb;AAAA,IACF;AACA,UAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,KAAK,WAAW,QAAQ,KAAK;AAChE,UAAM,SAAS,MAAM,SAAS,sBAAsB;AAAA,MAClD;AAAA,MACA,aAAa,KAAK,eAAe,IAAI;AAAA,IACvC,CAAC;AACD,UAAM,KAAK,UAAU,6BAA6B;AAAA,MAChD;AAAA,MACA,UAAU;AAAA,MACV,aAAa,OAAO;AAAA,MACpB,cAAc,OAAO;AAAA,MACrB,WAAW,OAAO;AAAA,MAClB,OAAO,OAAO;AAAA,MACd,mBAAmB,OAAO;AAAA,MAC1B,kBAAkB,OAAO;AAAA,IAC3B,CAAC;AACD,WAAO,IAAI;AAAA,MACT,WAAW;AAAA,MACX,YAAY,mCAAmC,mBAAmB,IAAI,CAAC;AAAA,IACzE;AAAA,EACF;AAAA,EAEQ,gBAAgB,MAAiC;AACvD,UAAM,WAAW,KAAK,SAAS,IAAI,IAAI;AACvC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;AAAA,QACR,qBAAqB,IAAI;AAAA,QACzB,WAAW;AAAA,MACb;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,MAAsB;AAC3C,UAAM,OAAO,KAAK,QAAQ;AAC1B,QAAI,CAAC,MAAM;AACT,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,UAAM,UAAU,KAAK,QAAQ,QAAQ,EAAE;AACvC,WAAO,GAAG,OAAO,SAAS,mBAAmB,IAAI,CAAC;AAAA,EACpD;AACF;AA9EQ;AAAA,EADL,IAAI,mBAAmB;AAAA,EAErB,yBAAM,UAAU;AAAA,EAChB,yBAAM,UAAU;AAAA,EAChB,uBAAI;AAAA,EACJ,uBAAI;AAAA,GAnBI,eAeL;AAiBA;AAAA,EADL,IAAI,oBAAoB;AAAA,EAEtB,yBAAM,UAAU;AAAA,EAChB,yBAAM,MAAM;AAAA,EACZ,yBAAM,OAAO;AAAA,EACb,uBAAI;AAAA,GApCI,eAgCL;AAhCK,iBAAN;AAAA,EADN,WAAW,MAAM;AAAA,EAGb,0BAAO,iBAAiB;AAAA,EAExB,0BAAO,iBAAiB;AAAA,EAExB,0BAAO,iBAAiB;AAAA,EAExB,0BAAO,0BAA0B;AAAA,EAEjC,0BAAO,YAAY;AAAA,GAVX;","names":[]}