@pattern-stack/codegen 0.13.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) 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-delivery.memory-backend.d.ts +1 -1
  26. package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js +2 -2
  27. package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js.map +1 -1
  28. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +10 -2
  29. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
  30. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +1 -1
  31. package/dist/runtime/subsystems/bridge/bridge.module.js +186 -168
  32. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  33. package/dist/runtime/subsystems/bridge/bridge.protocol.d.ts +1 -1
  34. package/dist/runtime/subsystems/bridge/event-flow.service.d.ts +1 -1
  35. package/dist/runtime/subsystems/bridge/event-flow.service.js +9 -1
  36. package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
  37. package/dist/runtime/subsystems/bridge/index.d.ts +1 -1
  38. package/dist/runtime/subsystems/bridge/index.js +168 -150
  39. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  40. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +6 -1
  41. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -1
  42. package/dist/runtime/subsystems/cache/cache.memory-backend.js +6 -1
  43. package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -1
  44. package/dist/runtime/subsystems/cache/cache.module.js +6 -2
  45. package/dist/runtime/subsystems/cache/cache.module.js.map +1 -1
  46. package/dist/runtime/subsystems/cache/cache.tokens.d.ts +0 -10
  47. package/dist/runtime/subsystems/cache/cache.tokens.js +6 -2
  48. package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -1
  49. package/dist/runtime/subsystems/cache/index.js +6 -2
  50. package/dist/runtime/subsystems/cache/index.js.map +1 -1
  51. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +5 -0
  52. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  53. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +5 -0
  54. package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
  55. package/dist/runtime/subsystems/events/event-bus.redis-backend.js +5 -1
  56. package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
  57. package/dist/runtime/subsystems/events/events.module.js +5 -1
  58. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  59. package/dist/runtime/subsystems/events/events.tokens.d.ts +5 -11
  60. package/dist/runtime/subsystems/events/events.tokens.js +5 -1
  61. package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
  62. package/dist/runtime/subsystems/events/generated/bus.js +5 -0
  63. package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
  64. package/dist/runtime/subsystems/events/generated/index.js +5 -0
  65. package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
  66. package/dist/runtime/subsystems/events/index.js +5 -1
  67. package/dist/runtime/subsystems/events/index.js.map +1 -1
  68. package/dist/runtime/subsystems/index.d.ts +2 -2
  69. package/dist/runtime/subsystems/index.js +186 -168
  70. package/dist/runtime/subsystems/index.js.map +1 -1
  71. package/dist/runtime/subsystems/jobs/bullmq.config.d.ts +0 -9
  72. package/dist/runtime/subsystems/jobs/bullmq.config.js +6 -2
  73. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  74. package/dist/runtime/subsystems/jobs/index.d.ts +1 -1
  75. package/dist/runtime/subsystems/jobs/index.js +141 -124
  76. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  77. package/dist/runtime/subsystems/jobs/job-handler.base.d.ts +1 -1
  78. package/dist/runtime/subsystems/jobs/job-handler.base.js +5 -1
  79. package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
  80. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +1 -1
  81. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +10 -3
  82. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  83. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.d.ts +1 -1
  84. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +8 -1
  85. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js.map +1 -1
  86. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.d.ts +1 -1
  87. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +137 -7
  88. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
  89. package/dist/runtime/subsystems/jobs/job-orchestrator.protocol.d.ts +1 -1
  90. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +1 -1
  91. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +1 -1
  92. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +8 -2
  93. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
  94. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +1 -1
  95. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +25 -2
  96. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
  97. package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +1 -1
  98. package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js +25 -2
  99. package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js.map +1 -1
  100. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +1 -1
  101. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +5 -0
  102. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  103. package/dist/runtime/subsystems/jobs/job-worker.d.ts +1 -1
  104. package/dist/runtime/subsystems/jobs/job-worker.js +10 -4
  105. package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
  106. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +31 -3
  107. package/dist/runtime/subsystems/jobs/job-worker.module.js +163 -145
  108. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  109. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +144 -130
  110. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  111. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.d.ts +0 -11
  112. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js +8 -4
  113. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js.map +1 -1
  114. package/dist/runtime/subsystems/jobs/jobs-errors.d.ts +1 -1
  115. package/dist/runtime/subsystems/observability/index.d.ts +1 -1
  116. package/dist/runtime/subsystems/observability/index.js +9 -1
  117. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  118. package/dist/runtime/subsystems/observability/observability.module.js +9 -1
  119. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  120. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +1 -1
  121. package/dist/runtime/subsystems/observability/observability.service.d.ts +1 -1
  122. package/dist/runtime/subsystems/observability/observability.service.js +9 -1
  123. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  124. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +1 -1
  125. package/dist/runtime/subsystems/observability/reporters/index.d.ts +1 -1
  126. package/dist/runtime/subsystems/storage/index.js +5 -1
  127. package/dist/runtime/subsystems/storage/index.js.map +1 -1
  128. package/dist/runtime/subsystems/storage/storage.module.js +5 -1
  129. package/dist/runtime/subsystems/storage/storage.module.js.map +1 -1
  130. package/dist/runtime/subsystems/storage/storage.tokens.d.ts +0 -8
  131. package/dist/runtime/subsystems/storage/storage.tokens.js +5 -1
  132. package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -1
  133. package/dist/runtime/subsystems/token-key.d.ts +7 -0
  134. package/dist/runtime/subsystems/token-key.js +8 -0
  135. package/dist/runtime/subsystems/token-key.js.map +1 -0
  136. package/dist/src/cli/index.js +1160 -694
  137. package/dist/src/cli/index.js.map +1 -1
  138. package/package.json +5 -1
  139. package/runtime/subsystems/analytics/analytics.tokens.ts +6 -2
  140. package/runtime/subsystems/auth/auth.tokens.ts +15 -8
  141. package/runtime/subsystems/bridge/bridge-delivery.memory-backend.ts +8 -1
  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 +5 -1
  145. package/runtime/subsystems/jobs/bullmq.config.ts +5 -2
  146. package/runtime/subsystems/jobs/job-handler.base.ts +6 -1
  147. package/runtime/subsystems/jobs/job-orchestrator.memory-backend.ts +8 -3
  148. package/runtime/subsystems/jobs/job-run-service.memory-backend.ts +4 -1
  149. package/runtime/subsystems/jobs/job-step-service.memory-backend.ts +7 -2
  150. package/runtime/subsystems/jobs/job-worker.module.ts +18 -2
  151. package/runtime/subsystems/jobs/job-worker.ts +4 -1
  152. package/runtime/subsystems/jobs/jobs-domain.tokens.ts +10 -7
  153. package/runtime/subsystems/storage/storage.tokens.ts +6 -1
  154. package/runtime/subsystems/token-key.ts +7 -0
  155. package/src/config/runtime-mode.mjs +82 -0
  156. package/templates/entity/new/backend/modules/core/integration-source.ejs.t +3 -2
  157. package/templates/entity/new/clean-lite-ps/controller.ejs.t +1 -1
  158. package/templates/entity/new/clean-lite-ps/module.ejs.t +1 -1
  159. package/templates/entity/new/clean-lite-ps/prompt-extension.js +8 -2
  160. package/templates/entity/new/clean-lite-ps/repository.ejs.t +4 -4
  161. package/templates/entity/new/clean-lite-ps/service.ejs.t +4 -4
  162. 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.1",
3
+ "version": "0.14.1",
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'));
@@ -116,12 +116,19 @@ export class MemoryBridgeDeliveryRepo implements IJobBridge {
116
116
  async getStatusHistogram(
117
117
  windowHours: number,
118
118
  tenantId?: string | null,
119
+ // Reference instant for the window cutoff. Defaults to the wall clock; exposed
120
+ // as a test-injection seam so a caller can pin the cutoff to the same `now` it
121
+ // positioned a boundary row from. Without it the cutoff is re-sampled here a few
122
+ // ms later, pushing an "exactly at the boundary" row just below the window
123
+ // (the OBS-3 boundary test flake). The drizzle backend has no analogue — it
124
+ // evaluates the cutoff once in SQL via `now()`.
125
+ nowMs: number = Date.now(),
119
126
  ): Promise<StatusHistogram> {
120
127
  if (!Number.isFinite(windowHours) || windowHours <= 0) {
121
128
  throw new RangeError('windowHours must be positive');
122
129
  }
123
130
 
124
- const cutoffMs = Date.now() - windowHours * 3_600_000;
131
+ const cutoffMs = nowMs - windowHours * 3_600_000;
125
132
  const histogram: StatusHistogram = {
126
133
  pending: 0,
127
134
  delivered: 0,
@@ -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.
@@ -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
@@ -137,10 +137,15 @@ export class MemoryJobOrchestrator implements IJobOrchestrator {
137
137
  private readonly queueBlockers = new Map<string, string[]>();
138
138
 
139
139
  constructor(
140
- private readonly store: MemoryJobStore,
141
- private readonly stepService: MemoryJobStepService,
140
+ // ADR-037 (package-mode DI): explicit `@Inject` tokens on every param —
141
+ // the published bundle has no `design:paramtypes` metadata (built without
142
+ // `emitDecoratorMetadata`), so by-type injection would resolve to
143
+ // `undefined` in package mode. Class tokens (`MemoryJobStore`,
144
+ // `MemoryJobStepService`, `ModuleRef`) are passed to `@Inject` explicitly.
145
+ @Inject(MemoryJobStore) private readonly store: MemoryJobStore,
146
+ @Inject(MemoryJobStepService) private readonly stepService: MemoryJobStepService,
142
147
  @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,
143
- @Optional() private readonly moduleRef?: ModuleRef,
148
+ @Optional() @Inject(ModuleRef) private readonly moduleRef?: ModuleRef,
144
149
  ) {}
145
150
 
146
151
  /**
@@ -39,7 +39,10 @@ const NON_TERMINAL_STATUSES: JobRunRow['status'][] = [
39
39
  @Injectable()
40
40
  export class MemoryJobRunService implements IJobRunService {
41
41
  constructor(
42
- private readonly store: MemoryJobStore,
42
+ // ADR-037 (package-mode DI): explicit `@Inject(MemoryJobStore)` — the
43
+ // published bundle carries no `design:paramtypes`, so a by-type inject
44
+ // would resolve to `undefined` in package mode.
45
+ @Inject(MemoryJobStore) private readonly store: MemoryJobStore,
43
46
  @Inject(JOB_ORCHESTRATOR) private readonly orchestrator: IJobOrchestrator,
44
47
  @Inject(JOBS_MULTI_TENANT) private readonly multiTenant: boolean,
45
48
  ) {}
@@ -8,7 +8,7 @@
8
8
  * the Drizzle backend (which deletes non-completed rows on replay).
9
9
  */
10
10
  import { randomUUID } from 'node:crypto';
11
- import { Injectable } from '@nestjs/common';
11
+ import { Inject, Injectable } from '@nestjs/common';
12
12
  import type { JobStepRow } from './job-orchestration.schema';
13
13
  import type {
14
14
  IJobStepService,
@@ -19,7 +19,12 @@ import { MemoryJobStore } from './memory-job-store';
19
19
 
20
20
  @Injectable()
21
21
  export class MemoryJobStepService implements IJobStepService {
22
- constructor(private readonly store: MemoryJobStore) {}
22
+ // ADR-037 (package-mode DI): explicit `@Inject(MemoryJobStore)` — the
23
+ // published bundle carries no `design:paramtypes`, so a by-type inject
24
+ // would resolve to `undefined` in package mode.
25
+ constructor(
26
+ @Inject(MemoryJobStore) private readonly store: MemoryJobStore,
27
+ ) {}
23
28
 
24
29
  async findStep(runId: string, stepId: string): Promise<JobStep | null> {
25
30
  const rows = this.store.steps.get(runId);
@@ -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
@@ -154,7 +158,19 @@ export class JobWorkerOrchestrator implements OnModuleInit, OnModuleDestroy {
154
158
  * without supplying a `DRIZZLE` provider.
155
159
  */
156
160
  @Optional() @Inject(DRIZZLE) private readonly db: DrizzleClient | null = null,
157
- private readonly moduleRef?: ModuleRef,
161
+ /**
162
+ * ADR-037 (package-mode DI): inject `ModuleRef` EXPLICITLY via `@Inject`
163
+ * rather than relying on `design:paramtypes` reflection. The published
164
+ * package bundle is built without `emitDecoratorMetadata` (tsup/esbuild
165
+ * default), so a by-type injection here would resolve to `undefined` at
166
+ * boot in package mode — breaking the worker entirely (the
167
+ * `ModuleRef not available` throw). Vendored mode happened to work only
168
+ * because the consumer's own `tsc` (emitDecoratorMetadata: true)
169
+ * recompiled the source and emitted the metadata. The explicit token is
170
+ * mode-agnostic. `ModuleRef` is always provided by `@nestjs/core`, so no
171
+ * `@Optional()` is needed (it's a hard dependency of the worker path).
172
+ */
173
+ @Inject(ModuleRef) private readonly moduleRef?: ModuleRef,
158
174
  /**
159
175
  * BULLMQ-1 — resolved BullMQ connection + config, only bound when the
160
176
  * inner `JobsDomainModule` was booted with `backend: 'bullmq'`. `@Optional()`
@@ -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 */ _%>