@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pattern-stack/codegen",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Entity-driven code generation for full-stack TypeScript applications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,6 +31,10 @@
31
31
  "types": "./dist/runtime/subsystems/index.d.ts",
32
32
  "default": "./dist/runtime/subsystems/index.js"
33
33
  },
34
+ "./runtime/shared/openapi": {
35
+ "types": "./dist/runtime/shared/openapi/index.d.ts",
36
+ "default": "./dist/runtime/shared/openapi/index.js"
37
+ },
34
38
  "./runtime/*": {
35
39
  "types": "./dist/runtime/*.d.ts",
36
40
  "default": "./dist/runtime/*.js"
@@ -9,16 +9,20 @@
9
9
  * constructor(@Inject(ANALYTICS_QUERY) private readonly analytics: IAnalyticsQuery) {}
10
10
  * ```
11
11
  */
12
+ import { tokenKey } from '../token-key';
13
+
12
14
  export const ANALYTICS_QUERY = 'ANALYTICS_QUERY' as const;
13
15
 
16
+ // ADR-037: namespaced `Symbol.for(...)` keys (via `tokenKey()`) so these tokens
17
+ // match by value across import boundaries (package vs vendored runtime copy).
14
18
  /**
15
19
  * Injection token for the cube.js API URL.
16
20
  * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).
17
21
  */
18
- export const CUBE_API_URL = Symbol('CUBE_API_URL');
22
+ export const CUBE_API_URL = Symbol.for(tokenKey('analytics', 'cube-api-url'));
19
23
 
20
24
  /**
21
25
  * Injection token for the cube.js API secret.
22
26
  * Provided automatically by AnalyticsModule.forRoot({ backend: 'cube' }).
23
27
  */
24
- export const CUBE_API_SECRET = Symbol('CUBE_API_SECRET');
28
+ export const CUBE_API_SECRET = Symbol.for(tokenKey('analytics', 'cube-api-secret'));
@@ -26,15 +26,22 @@
26
26
  * `STRATEGY_REGISTRY` (a `ReadonlyMap<slug, IProviderStrategy>`), populated
27
27
  * by per-provider modules via a `useFactory` provider.
28
28
  */
29
- export const ENCRYPTION_KEY = Symbol('ENCRYPTION_KEY');
30
- export const OAUTH_STATE_STORE = Symbol('OAUTH_STATE_STORE');
31
- export const AUTH_CONNECTION_READER = Symbol('AUTH_CONNECTION_READER');
32
- export const AUTH_CONNECTION_TOKEN_WRITER = Symbol('AUTH_CONNECTION_TOKEN_WRITER');
33
- export const AUTH_CONNECTION_GRANT_SINK = Symbol('AUTH_CONNECTION_GRANT_SINK');
34
- export const AUTH_USER_CONTEXT = Symbol('AUTH_USER_CONTEXT');
35
- export const STRATEGY_REGISTRY = Symbol('STRATEGY_REGISTRY');
29
+ import { tokenKey } from '../token-key';
30
+
31
+ // ADR-037: namespaced `Symbol.for(...)` keys so a token matches by VALUE across
32
+ // import boundaries the package copy and a (legacy) vendored copy resolve to
33
+ // the SAME symbol, eliminating the dual-package DI-token identity hazard that
34
+ // crashed boot once the emitter began emitting `STRATEGY_REGISTRY` as a runtime
35
+ // value (RFC-0003 R5). Matches the convention surface packages already use.
36
+ export const ENCRYPTION_KEY = Symbol.for(tokenKey('auth', 'encryption-key'));
37
+ export const OAUTH_STATE_STORE = Symbol.for(tokenKey('auth', 'oauth-state-store'));
38
+ export const AUTH_CONNECTION_READER = Symbol.for(tokenKey('auth', 'connection-reader'));
39
+ export const AUTH_CONNECTION_TOKEN_WRITER = Symbol.for(tokenKey('auth', 'connection-token-writer'));
40
+ export const AUTH_CONNECTION_GRANT_SINK = Symbol.for(tokenKey('auth', 'connection-grant-sink'));
41
+ export const AUTH_USER_CONTEXT = Symbol.for(tokenKey('auth', 'user-context'));
42
+ export const STRATEGY_REGISTRY = Symbol.for(tokenKey('auth', 'strategy-registry'));
36
43
  /**
37
44
  * Holds the resolved `AuthModuleOptions` (used by `AuthController` to read
38
45
  * `redirectUriBase` for building per-provider callback URIs).
39
46
  */
40
- export const AUTH_OPTIONS = Symbol('AUTH_OPTIONS');
47
+ export const AUTH_OPTIONS = Symbol.for(tokenKey('auth', 'options'));
@@ -7,11 +7,16 @@
7
7
  * ```
8
8
  *
9
9
  * Services may also inject CACHE for reads (get, has) per ADR-003.
10
+ *
11
+ * ADR-037: namespaced `Symbol.for(...)` key (via `tokenKey()`) so the token matches
12
+ * by value across import boundaries (package vs vendored runtime copy).
10
13
  */
11
- export const CACHE = Symbol('CACHE');
14
+ import { tokenKey } from '../token-key';
15
+
16
+ export const CACHE = Symbol.for(tokenKey('cache', 'cache'));
12
17
 
13
18
  /**
14
19
  * Injection token for the default TTL (in seconds) passed from CacheModule.forRoot().
15
20
  * Optional — omit for no-expiry behavior.
16
21
  */
17
- export const CACHE_DEFAULT_TTL = Symbol('CACHE_DEFAULT_TTL');
22
+ export const CACHE_DEFAULT_TTL = Symbol.for(tokenKey('cache', 'default-ttl'));
@@ -9,6 +9,8 @@
9
9
  * constructor(@Inject(EVENT_BUS) private readonly eventBus: IEventBus) {}
10
10
  * ```
11
11
  */
12
+ import { tokenKey } from '../token-key';
13
+
12
14
  export const EVENT_BUS = 'EVENT_BUS' as const;
13
15
 
14
16
  /**
@@ -61,8 +63,13 @@ export const EVENTS_MULTI_TENANT = 'EVENTS_MULTI_TENANT' as const;
61
63
  /**
62
64
  * Injection token for the Redis connection URL used by RedisEventBus.
63
65
  * Provided automatically by EventsModule.forRoot({ backend: 'redis' }).
66
+ *
67
+ * ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) so it matches by value
68
+ * across runtime copies (the sibling string tokens above are already value-stable).
69
+ * Note the jobs subsystem defines its own `REDIS_URL`-equivalent; this is the
70
+ * events one.
64
71
  */
65
- export const REDIS_URL = Symbol('REDIS_URL');
72
+ export const REDIS_URL = Symbol.for(tokenKey('events', 'redis-url'));
66
73
 
67
74
  /**
68
75
  * Injection token for the resolved `EventsModuleOptions` object.
@@ -6,7 +6,11 @@
6
6
 
7
7
  // Events
8
8
  export { EVENT_BUS } from './events';
9
- export type { DomainEvent, IEventBus } from './events';
9
+ // `DrizzleTransaction` is re-exported here (not just from the `events` barrel)
10
+ // so package-mode generated code — which imports from the single
11
+ // `@pattern-stack/codegen/subsystems` barrel — resolves it (ADR-037). Vendored
12
+ // mode reaches it via `@shared/subsystems/events`; both must export it.
13
+ export type { DomainEvent, IEventBus, DrizzleTransaction } from './events';
10
14
  export { EventsModule, DrizzleEventBus, MemoryEventBus } from './events';
11
15
 
12
16
  // Jobs — orchestration schema only (JOB-1). Protocols / modules land in JOB-2 / JOB-5.
@@ -80,6 +84,7 @@ export {
80
84
  export type {
81
85
  IncrementalRead,
82
86
  RandomRead,
87
+ ReadContext,
83
88
  ReadMode,
84
89
  ReadRequest,
85
90
  Ref,
@@ -79,6 +79,26 @@ export interface ReadRequest<F = unknown> {
79
79
  readonly pageSize?: number;
80
80
  }
81
81
 
82
+ /**
83
+ * Per-run context threaded from `listChanges` into the vendor read body (R5).
84
+ *
85
+ * Carries the `subscription` framing the run so `enumerate`/`hydrate` can resolve
86
+ * **per-connection credentials** (and raw-landing keys) from
87
+ * `subscription.externalRef` — the gap a multi-account consumer surfaced: a
88
+ * singleton change source cannot hold connection-scoped auth, and before R5 the
89
+ * base forwarded the subscription only into `filterFor`, never into the fetch.
90
+ *
91
+ * Optional throughout (the core contract): a direct `read()` / `get()` call — the
92
+ * query surface's "fill one record on click" — may omit it. An adapter that needs
93
+ * per-connection auth reads `ctx?.subscription?.externalRef` and asserts its
94
+ * presence; a provider-level-auth adapter ignores it.
95
+ */
96
+ export interface ReadContext {
97
+ /** The subscription framing this run; `externalRef` is the upstream scope /
98
+ * connection id the adapter resolves credentials + raw-landing keys from. */
99
+ readonly subscription?: IntegrationSubscriptionView;
100
+ }
101
+
82
102
  /**
83
103
  * The `read()`-side envelope: canonical record + the raw vendor payload it came
84
104
  * from + the originating external id + the per-ref cursor.
@@ -101,7 +121,7 @@ export interface SourcedRecord<T> {
101
121
  * hydration, and cursor emission are the providing base's concern.
102
122
  */
103
123
  export interface IncrementalRead<T, F = unknown> {
104
- read(req: ReadRequest<F>): AsyncIterable<SourcedRecord<T>>;
124
+ read(req: ReadRequest<F>, ctx?: ReadContext): AsyncIterable<SourcedRecord<T>>;
105
125
  }
106
126
 
107
127
  /**
@@ -111,7 +131,7 @@ export interface IncrementalRead<T, F = unknown> {
111
131
  * surface.
112
132
  */
113
133
  export interface RandomRead<T> {
114
- get(id: string): Promise<T | null>;
134
+ get(id: string, ctx?: ReadContext): Promise<T | null>;
115
135
  }
116
136
 
117
137
  // ============================================================================
@@ -208,11 +228,16 @@ export abstract class IncrementalReadBase<T, F = unknown, M = Record<string, unk
208
228
  * `pageSize` (from `ReadRequest`) is the adapter's requested vendor page size
209
229
  * — also the atomic-cursor backfill blast-radius bound (§ `cursorDivisible`).
210
230
  * Honor it as a hint; vendors that cap page size clamp it.
231
+ *
232
+ * `ctx?.subscription` (R5) carries the run's subscription, so a per-connection
233
+ * adapter resolves credentials / upstream scope from `externalRef` here; absent
234
+ * on a direct `read()` with no run subscription.
211
235
  */
212
236
  protected abstract enumerate(
213
237
  mode: ReadMode,
214
238
  filter?: F,
215
239
  pageSize?: number,
240
+ ctx?: ReadContext,
216
241
  ): AsyncIterable<Ref<M>[]>;
217
242
 
218
243
  /**
@@ -221,8 +246,12 @@ export abstract class IncrementalReadBase<T, F = unknown, M = Record<string, unk
221
246
  * alignment. Write it over `mapConcurrent(ids, (id) => this.fetchOne(id),
222
247
  * this.hydrateConcurrency)`; override with a real `/batch` call or a
223
248
  * passthrough (full-object list) where the vendor allows.
249
+ *
250
+ * `ctx?.subscription` (R5) carries the run's subscription for per-connection
251
+ * credential resolution (the fetch is where the vendor call happens) and is the
252
+ * natural place to land raw payloads keyed by `subscription.id`.
224
253
  */
225
- protected abstract hydrate(ids: string[]): Promise<Map<string, unknown>>;
254
+ protected abstract hydrate(ids: string[], ctx?: ReadContext): Promise<Map<string, unknown>>;
226
255
 
227
256
  /** Provider payload → canonical record. Return `null` to drop a record. */
228
257
  protected abstract toCanonical(raw: unknown): T | null;
@@ -256,11 +285,14 @@ export abstract class IncrementalReadBase<T, F = unknown, M = Record<string, unk
256
285
  * adapter cannot hydrate-then-discard. A hydrate miss (deleted mid-run) is
257
286
  * skipped, never fabricated.
258
287
  */
259
- async *read(req: ReadRequest<F>): AsyncIterable<SourcedRecord<T>> {
260
- for await (const refPage of this.enumerate(req.mode, req.filter, req.pageSize)) {
288
+ async *read(req: ReadRequest<F>, ctx?: ReadContext): AsyncIterable<SourcedRecord<T>> {
289
+ for await (const refPage of this.enumerate(req.mode, req.filter, req.pageSize, ctx)) {
261
290
  const kept = refPage.filter((ref) => this.matchesRef(ref, req.filter));
262
291
  if (kept.length === 0) continue;
263
- const raws = await this.hydrate(kept.map((ref) => ref.externalId));
292
+ const raws = await this.hydrate(
293
+ kept.map((ref) => ref.externalId),
294
+ ctx,
295
+ );
264
296
  for (const ref of kept) {
265
297
  const raw = raws.get(ref.externalId);
266
298
  if (raw === undefined || raw === null) continue; // deleted mid-run → skip
@@ -277,8 +309,8 @@ export abstract class IncrementalReadBase<T, F = unknown, M = Record<string, unk
277
309
  * `toCanonical ∘ hydrate([id])`. Reuses the adapter's batched fetch + miss
278
310
  * tolerance; returns `null` for a missing or undecodable record.
279
311
  */
280
- async get(id: string): Promise<T | null> {
281
- const raws = await this.hydrate([id]);
312
+ async get(id: string, ctx?: ReadContext): Promise<T | null> {
313
+ const raws = await this.hydrate([id], ctx);
282
314
  const raw = raws.get(id);
283
315
  if (raw === undefined || raw === null) return null;
284
316
  return this.toCanonical(raw);
@@ -308,7 +340,9 @@ export abstract class IncrementalReadBase<T, F = unknown, M = Record<string, unk
308
340
  ? { kind: 'full' }
309
341
  : { kind: 'delta', cursor };
310
342
  const filter = this.filterFor(subscription);
311
- const stream = this.read({ mode, filter });
343
+ // R5: thread the run's subscription into the read body so `enumerate`/`hydrate`
344
+ // can resolve per-connection credentials (and raw-landing keys) from it.
345
+ const stream = this.read({ mode, filter }, { subscription });
312
346
 
313
347
  if (this.cursorDivisible) {
314
348
  for await (const sourced of stream) {
@@ -101,6 +101,7 @@ export {
101
101
  mapConcurrent,
102
102
  type IncrementalRead,
103
103
  type RandomRead,
104
+ type ReadContext,
104
105
  type ReadMode,
105
106
  type ReadRequest,
106
107
  type Ref,
@@ -6,6 +6,7 @@
6
6
  * `jobs.extensions.bullmq.*` config namespace (CLAUDE.md core/extension
7
7
  * protocol). The Drizzle backend never reads any of it.
8
8
  */
9
+ import { tokenKey } from '../token-key';
9
10
  import { loadPoolConfig, type PoolConfig } from './pool-config.loader';
10
11
 
11
12
  /**
@@ -78,11 +79,13 @@ export interface BullMqResolvedConfig {
78
79
  bullBoard?: { enabled: boolean; mountPath: string };
79
80
  }
80
81
 
82
+ // ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) — matches by value
83
+ // across runtime copies.
81
84
  /** DI token for the resolved BullMQ `ConnectionOptions` (ioredis-compatible). */
82
- export const BULLMQ_CONNECTION = Symbol('BULLMQ_CONNECTION');
85
+ export const BULLMQ_CONNECTION = Symbol.for(tokenKey('jobs', 'bullmq-connection'));
83
86
 
84
87
  /** DI token for the full resolved BullMQ config (prefix + bull board). */
85
- export const BULLMQ_RESOLVED_CONFIG = Symbol('BULLMQ_RESOLVED_CONFIG');
88
+ export const BULLMQ_RESOLVED_CONFIG = Symbol.for(tokenKey('jobs', 'bullmq-resolved-config'));
86
89
 
87
90
  const DEFAULT_REDIS_URL = 'redis://localhost:6379';
88
91
  const DEFAULT_BULL_BOARD_MOUNT = '/admin/queues';
@@ -16,6 +16,7 @@
16
16
  */
17
17
  // TODO(logging-subsystem): swap to ILogger once ADR-028 lands
18
18
  import type { Logger } from '@nestjs/common';
19
+ import { tokenKey } from '../token-key';
19
20
  import type { EventOfType, EventTypeName } from '../events/generated/types';
20
21
  import type { JobRun } from './job-orchestrator.protocol';
21
22
 
@@ -160,7 +161,11 @@ export const JOB_HANDLER_REGISTRY = new Map<
160
161
  }
161
162
  >();
162
163
 
163
- export const JOB_HANDLER_METADATA_KEY = Symbol('JobHandlerMeta');
164
+ // ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) so the reflection-metadata
165
+ // key matches by value across import boundaries (the @JobHandler decorator and the
166
+ // reader may resolve different runtime copies). Distinct from the DI tokens but
167
+ // subject to the same dual-package identity hazard.
168
+ export const JOB_HANDLER_METADATA_KEY = Symbol.for(tokenKey('jobs', 'handler-metadata'));
164
169
 
165
170
  /**
166
171
  * Class decorator that registers a handler with the job type, the full
@@ -30,6 +30,7 @@ import {
30
30
  type OnModuleInit,
31
31
  } from '@nestjs/common';
32
32
  import { ModuleRef } from '@nestjs/core';
33
+ import { tokenKey } from '../token-key';
33
34
  import { DRIZZLE } from '../../constants/tokens';
34
35
  import type { DrizzleClient } from '../../types/drizzle';
35
36
  import { HandlerRegistry, type HandlerRegistryEntry } from './job-handler.base';
@@ -128,8 +129,11 @@ export interface JobWorkerModuleOptions {
128
129
  * configuration — e.g. `BridgeModule.onModuleInit` checks
129
130
  * `options.pools` against `BRIDGE_RESERVED_POOLS` to fail fast when a
130
131
  * reserved pool isn't being polled (BRIDGE-8).
132
+ *
133
+ * ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) — matches by value
134
+ * across runtime copies.
131
135
  */
132
- export const JOB_WORKER_MODULE_OPTIONS = Symbol('JOB_WORKER_MODULE_OPTIONS');
136
+ export const JOB_WORKER_MODULE_OPTIONS = Symbol.for(tokenKey('jobs', 'worker-module-options'));
133
137
 
134
138
  /**
135
139
  * The lifecycle holder. Named `JobWorkerOrchestrator` in the spec to avoid
@@ -17,6 +17,7 @@ import { Inject, Injectable, Logger, type OnModuleDestroy, type OnModuleInit } f
17
17
  import { ModuleRef } from '@nestjs/core';
18
18
  import { and, asc, desc, eq, inArray, lt, lte, sql } from 'drizzle-orm';
19
19
  import type { DrizzleClient } from '../../types/drizzle';
20
+ import { tokenKey } from '../token-key';
20
21
  import { DRIZZLE } from '../../constants/tokens';
21
22
  import { jobRuns, type JobRunRow } from './job-orchestration.schema';
22
23
  import type { IJobOrchestrator, JobRun } from './job-orchestrator.protocol';
@@ -60,7 +61,9 @@ export interface JobWorkerOptions {
60
61
  shutdownTimeoutMs?: number;
61
62
  }
62
63
 
63
- export const JOB_WORKER_OPTIONS = Symbol('JOB_WORKER_OPTIONS');
64
+ // ADR-037: namespaced `Symbol.for(...)` (via `tokenKey()`) — matches by value
65
+ // across runtime copies.
66
+ export const JOB_WORKER_OPTIONS = Symbol.for(tokenKey('jobs', 'worker-options'));
64
67
 
65
68
  const DEFAULT_POLL_INTERVAL_MS = 1_000;
66
69
  const DEFAULT_STALE_SWEEPER_INTERVAL_MS = 60_000;
@@ -5,13 +5,16 @@
5
5
  * concrete backends (JOB-3 Drizzle, JOB-4 Memory) provide the implementations
6
6
  * through `JobsDomainModule.forRoot({ backend })` in JOB-5.
7
7
  *
8
- * Each token is a unique `Symbol` guaranteed distinct from every other
9
- * Symbol at runtime, which is exactly the uniqueness guarantee Nest's DI
10
- * container relies on for token-based lookup.
8
+ * Each token is a namespaced `Symbol.for(...)` (ADR-037, via `tokenKey()`)
9
+ * distinct per key, so Nest's DI lookup is unambiguous, AND matching by VALUE
10
+ * across import boundaries so the package and a (legacy) vendored runtime copy
11
+ * resolve to the same symbol.
11
12
  */
12
- export const JOB_ORCHESTRATOR = Symbol('JOB_ORCHESTRATOR');
13
- export const JOB_RUN_SERVICE = Symbol('JOB_RUN_SERVICE');
14
- export const JOB_STEP_SERVICE = Symbol('JOB_STEP_SERVICE');
13
+ import { tokenKey } from '../token-key';
14
+
15
+ export const JOB_ORCHESTRATOR = Symbol.for(tokenKey('jobs', 'orchestrator'));
16
+ export const JOB_RUN_SERVICE = Symbol.for(tokenKey('jobs', 'run-service'));
17
+ export const JOB_STEP_SERVICE = Symbol.for(tokenKey('jobs', 'step-service'));
15
18
 
16
19
  /**
17
20
  * Multi-tenancy opt-in flag (JOB-8). Bound to the boolean passed in via
@@ -27,4 +30,4 @@ export const JOB_STEP_SERVICE = Symbol('JOB_STEP_SERVICE');
27
30
  * tenant context; `tenantId` is populated at write time and enforced on
28
31
  * targeted reads. See docs/specs/JOB-8.md.
29
32
  */
30
- export const JOBS_MULTI_TENANT = Symbol('JOBS_MULTI_TENANT');
33
+ export const JOBS_MULTI_TENANT = Symbol.for(tokenKey('jobs', 'multi-tenant'));
@@ -5,5 +5,10 @@
5
5
  * ```typescript
6
6
  * constructor(@Inject(STORAGE) private readonly storage: IStorageService) {}
7
7
  * ```
8
+ *
9
+ * ADR-037: namespaced `Symbol.for(...)` key (via `tokenKey()`) so the token matches
10
+ * by value across import boundaries (package vs vendored runtime copy).
8
11
  */
9
- export const STORAGE = Symbol('STORAGE');
12
+ import { tokenKey } from '../token-key';
13
+
14
+ export const STORAGE = Symbol.for(tokenKey('storage', 'storage'));
@@ -0,0 +1,7 @@
1
+ /** Canonical package namespace for cross-boundary DI token keys. MUST be a hardcoded
2
+ * constant (NOT derived from package.json) so a vendored copy — which lives inside the
3
+ * CONSUMER's package — produces the identical key and the two copies share the symbol. */
4
+ export const PKG = '@pattern-stack/codegen';
5
+ // TODO(token-version): if/when a runtime contract version is adopted, inject it HERE only
6
+ // (e.g. `${PKG}#${ABI}.${area}.${name}`) — this helper is the single chokepoint.
7
+ export const tokenKey = (area: string, name: string): string => `${PKG}.${area}.${name}`;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Runtime mode resolver — `.mjs` twin of `src/cli/shared/runtime-import.ts`
3
+ * (ADR-037), for the Hygen entity templates that run in a subprocess and can
4
+ * only import plain ESM. Keep the two in sync: both map a runtime mode +
5
+ * logical subpath to the concrete import specifier.
6
+ *
7
+ * - `package` → `@pattern-stack/codegen/subsystems` +
8
+ * `@pattern-stack/codegen/runtime/<relpath>`.
9
+ * - `vendored` → `@shared/subsystems/<name>` + `@shared/<relpath>` (the
10
+ * convention the entity templates have always emitted).
11
+ *
12
+ * NOT routed through here: consumer-app files the package never owns and that
13
+ * `project init` always scaffolds locally regardless of mode —
14
+ * `@shared/database/*`, `@shared/http/*`, `@shared/openapi`, `@shared/pipes/*`,
15
+ * `@shared/connections/*`. Those stay `@shared/*` in both modes.
16
+ */
17
+
18
+ import fs from "node:fs";
19
+ import path from "node:path";
20
+ import yaml from "yaml";
21
+
22
+ const PACKAGE = "@pattern-stack/codegen";
23
+
24
+ /**
25
+ * Read the `runtime` mode from `codegen.config.yaml` at `cwd`. Defaults to
26
+ * `package` (ADR-037) when the file/key is absent or invalid.
27
+ * @returns {'package' | 'vendored'}
28
+ */
29
+ export function loadRuntimeMode(cwd = process.cwd()) {
30
+ const configPath = path.resolve(cwd, "codegen.config.yaml");
31
+ if (!fs.existsSync(configPath)) return "package";
32
+ try {
33
+ const parsed = yaml.parse(fs.readFileSync(configPath, "utf-8"));
34
+ return parsed?.runtime === "vendored" ? "vendored" : "package";
35
+ } catch {
36
+ return "package";
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Import specifier for a subsystem barrel.
42
+ * @param {'package' | 'vendored'} mode
43
+ * @param {string} [subsystem] logical subsystem name (`events`, `integration`,
44
+ * `auth`, …) — selects the vendored per-subsystem barrel; ignored in package
45
+ * mode (one barrel serves all).
46
+ */
47
+ export function subsystemsImport(mode, subsystem) {
48
+ if (mode === "vendored") {
49
+ return subsystem ? `@shared/subsystems/${subsystem}` : "@shared/subsystems";
50
+ }
51
+ return `${PACKAGE}/subsystems`;
52
+ }
53
+
54
+ /**
55
+ * Import specifier for a non-subsystem runtime file (base-classes, types,
56
+ * constants, helpers). `relpath` is the path under the runtime root WITHOUT a
57
+ * leading slash (e.g. `base-classes/integrated-entity-repository`,
58
+ * `constants/tokens`, `types/drizzle`, `eav-helpers`).
59
+ * @param {'package' | 'vendored'} mode
60
+ * @param {string} relpath
61
+ */
62
+ export function runtimeImport(mode, relpath) {
63
+ const clean = String(relpath).replace(/^\/+/, "");
64
+ return mode === "vendored" ? `@shared/${clean}` : `${PACKAGE}/runtime/${clean}`;
65
+ }
66
+
67
+ /**
68
+ * Rewrite a legacy `@shared/<relpath>` specifier to the mode-correct form. Used
69
+ * to convert pattern-library base-class imports (authored as `@shared/...`) to
70
+ * the package form in package mode without duplicating the path in every
71
+ * pattern. A non-`@shared/` specifier (app-defined pattern alias, e.g.
72
+ * `@/patterns/...`) is returned untouched.
73
+ * @param {'package' | 'vendored'} mode
74
+ * @param {string} specifier
75
+ */
76
+ export function rewriteSharedImport(mode, specifier) {
77
+ if (mode === "vendored") return specifier;
78
+ if (typeof specifier !== "string" || !specifier.startsWith("@shared/")) {
79
+ return specifier;
80
+ }
81
+ return runtimeImport(mode, specifier.slice("@shared/".length));
82
+ }
@@ -5,12 +5,13 @@ force: true
5
5
  ---
6
6
  <%- typeof generatedBanner !== 'undefined' ? generatedBanner : '' %>
7
7
  import { Module } from '@nestjs/common';
8
- import { buildChangeSource } from '@shared/subsystems/integration';
8
+ <% const _integSubsys = typeof integrationSubsystemImport !== 'undefined' ? integrationSubsystemImport : '@shared/subsystems/integration'; -%>
9
+ import { buildChangeSource } from '<%= _integSubsys %>';
9
10
  import type {
10
11
  DetectionConfig,
11
12
  IChangeSource,
12
13
  PollFetchCallback,
13
- } from '@shared/subsystems/integration';
14
+ } from '<%= _integSubsys %>';
14
15
  import type { <%= className %> } from '<%= isCleanLitePs ? clpImports.integrationSourceToEntity : imports.moduleToDomain %>';
15
16
 
16
17
  const <%= name.toUpperCase() %>_DETECTION_CONFIGS: Record<string, DetectionConfig> = <%- detectionConfigsLiteral %>;
@@ -13,7 +13,7 @@ import { <%= classNames.findByIdWithFieldsUseCase %> } from './use-cases/find-<%
13
13
  import { <%= classNames.listWithFieldsUseCase %> } from './use-cases/list-<%= entityNamePlural %>-with-fields.use-case';
14
14
  <% } -%>
15
15
  <% if (generateWrites) { -%>
16
- import { ZodValidationPipe } from '@shared/pipes/zod-validation.pipe';
16
+ import { ZodValidationPipe } from '<%= typeof zodValidationPipeImport !== 'undefined' ? zodValidationPipeImport : '@shared/pipes/zod-validation.pipe' %>';
17
17
  import { <%= classNames.createUseCase %> } from './use-cases/create-<%= entityName %>.use-case';
18
18
  import { <%= classNames.updateUseCase %> } from './use-cases/update-<%= entityName %>.use-case';
19
19
  import { <%= classNames.deleteUseCase %> } from './use-cases/delete-<%= entityName %>.use-case';
@@ -12,7 +12,7 @@ force: true
12
12
  */
13
13
  <% } -%>
14
14
  import { Inject, Module, type OnModuleInit } from '@nestjs/common';
15
- import { OPENAPI_REGISTRY, type OpenApiRegistry } from '@shared/openapi';
15
+ import { OPENAPI_REGISTRY, type OpenApiRegistry } from '<%= typeof openApiImport !== 'undefined' ? openApiImport : '@shared/openapi' %>';
16
16
  import { DatabaseModule } from '@shared/database/database.module';
17
17
  <%_ /* CGP-358b: Import cross-entity repos needed for has_many composition */ _%>
18
18
  <%_ if (typeof clpExistingHasMany !== 'undefined') { _%>
@@ -15,6 +15,7 @@ import pluralizePkg from 'pluralize';
15
15
  // globs before this helper runs — we only read the registry here.
16
16
  import { getPattern } from '../../../../src/patterns/registry.js';
17
17
  import '../../../../src/patterns/library/index.js';
18
+ import { rewriteSharedImport } from '../../../../src/config/runtime-mode.mjs';
18
19
 
19
20
  // ============================================================================
20
21
  // Pattern registry resolution
@@ -1058,14 +1059,19 @@ export function buildCleanLitePsLocals(definition, baseLocals) {
1058
1059
  // emitted output is byte-identical.
1059
1060
  const patternBase = resolvePatternBaseClasses(entity);
1060
1061
  const { patternName } = patternBase;
1062
+ // Runtime mode (ADR-037) — rewrite library base-class imports authored as
1063
+ // `@shared/base-classes/…` to the mode-correct form (package mode →
1064
+ // `@pattern-stack/codegen/runtime/base-classes/…`). App-defined pattern
1065
+ // aliases (non-`@shared/`) pass through untouched.
1066
+ const runtimeMode = baseLocals?.runtimeMode === 'vendored' ? 'vendored' : 'package';
1061
1067
  // FAMILY_MAP is gone (PATTERN-5); `patternConfigClasses` is the structural
1062
1068
  // equivalent — repository + service class names + import paths + inherited
1063
1069
  // method comment lists, sourced directly from the pattern registry.
1064
1070
  const patternConfigClasses = {
1065
1071
  repositoryBaseClass: patternBase.repositoryBaseClass,
1066
1072
  serviceBaseClass: patternBase.serviceBaseClass,
1067
- repositoryBaseImport: patternBase.repositoryBaseImport,
1068
- serviceBaseImport: patternBase.serviceBaseImport,
1073
+ repositoryBaseImport: rewriteSharedImport(runtimeMode, patternBase.repositoryBaseImport),
1074
+ serviceBaseImport: rewriteSharedImport(runtimeMode, patternBase.serviceBaseImport),
1069
1075
  repositoryInheritedMethods: patternBase.repositoryInheritedMethods,
1070
1076
  serviceInheritedMethods: patternBase.serviceInheritedMethods,
1071
1077
  };
@@ -21,14 +21,14 @@ import { eq<%= hasMultiFieldQuery ? ', and' : '' %><%= hasOrderedQuery ? ', desc
21
21
  <% if (eavValueTable) { -%>
22
22
  import { sql } from 'drizzle-orm';
23
23
  <% } -%>
24
- import { DRIZZLE } from '@shared/constants/tokens';
25
- import type { DrizzleClient<% if (eavValueTable || (typeof hasIntegrationSurface !== 'undefined' && hasIntegrationSurface)) { %>, DrizzleTx<% } %> } from '@shared/types/drizzle';
24
+ import { DRIZZLE } from '<%= typeof drizzleTokenImport !== 'undefined' ? drizzleTokenImport : '@shared/constants/tokens' %>';
25
+ import type { DrizzleClient<% if (eavValueTable || (typeof hasIntegrationSurface !== 'undefined' && hasIntegrationSurface)) { %>, DrizzleTx<% } %> } from '<%= typeof drizzleTypeImport !== 'undefined' ? drizzleTypeImport : '@shared/types/drizzle' %>';
26
26
  import { <%= repositoryBaseClass %> } from '<%= repositoryBaseImport %>';
27
27
  <% if (typeof hasIntegrationSurface !== 'undefined' && hasIntegrationSurface) { -%>
28
- import type { IntegrationUpsertConfig } from '@shared/base-classes/integration-upsert-config';
28
+ import type { IntegrationUpsertConfig } from '<%= typeof integrationUpsertConfigImport !== 'undefined' ? integrationUpsertConfigImport : '@shared/base-classes/integration-upsert-config' %>';
29
29
  <% } -%>
30
30
  <% if (hasTimestamps || hasSoftDelete || hasUserTracking) { -%>
31
- import type { BehaviorConfig } from '@shared/base-classes/base-repository';
31
+ import type { BehaviorConfig } from '<%= typeof baseRepositoryImport !== 'undefined' ? baseRepositoryImport : '@shared/base-classes/base-repository' %>';
32
32
  <% } -%>
33
33
  <% if (eavEnabled) { -%>
34
34
  import { FieldValueService } from '../field_values/field_value.service';
@@ -5,8 +5,8 @@ force: true
5
5
  ---
6
6
  <%- typeof generatedBanner !== 'undefined' ? generatedBanner : '' %>
7
7
  import { Injectable, Inject, Optional } from '@nestjs/common';
8
- import { WithAnalytics } from '@shared/base-classes/with-analytics';
9
- import { EVENT_BUS } from '@shared/constants/tokens';
8
+ import { WithAnalytics } from '<%= typeof withAnalyticsImport !== 'undefined' ? withAnalyticsImport : '@shared/base-classes/with-analytics' %>';
9
+ import { EVENT_BUS } from '<%= typeof drizzleTokenImport !== 'undefined' ? drizzleTokenImport : '@shared/constants/tokens' %>';
10
10
  import { <%= serviceBaseClass %> } from '<%= serviceBaseImport %>';
11
11
  import { <%= classNames.repository %> } from './<%= entityName %>.repository';
12
12
  import type { <%= classNames.entity %> } from './<%= entityName %>.entity';
@@ -14,8 +14,8 @@ import type { <%= classNames.entity %> } from './<%= entityName %>.entity';
14
14
  import { FieldValueService } from '../field_values/field_value.service';
15
15
  <% } -%>
16
16
  <% if (eavValueTable) { -%>
17
- import { toEavRows, mergeEavRows } from '@shared/eav-helpers';
18
- import type { DrizzleTx } from '@shared/types/drizzle';
17
+ import { toEavRows, mergeEavRows } from '<%= typeof eavHelpersImport !== 'undefined' ? eavHelpersImport : '@shared/eav-helpers' %>';
18
+ import type { DrizzleTx } from '<%= typeof drizzleTypeImport !== 'undefined' ? drizzleTypeImport : '@shared/types/drizzle' %>';
19
19
  import { <%= eavDefinitionPascal %>Repository } from '../<%= eavDefinitionEntityPlural %>/<%= eavDefinitionEntity %>.repository';
20
20
  <% } -%>
21
21
  <%_ /* CGP-358b — service-layer composition: import target repos for belongs_to relationships */ _%>