@pattern-stack/codegen 0.16.1 → 0.17.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 (127) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/consumer-skills/entities/families-and-queries.md +5 -3
  3. package/consumer-skills/integration/audit-and-detection.md +29 -4
  4. package/dist/{chunk-H6FO2ZDJ.js → chunk-4PFF3ED4.js} +4 -4
  5. package/dist/{chunk-CO6LUM72.js → chunk-7P5ODGLA.js} +34 -2
  6. package/dist/chunk-7P5ODGLA.js.map +1 -0
  7. package/dist/{chunk-QSJ3J4HE.js → chunk-BHZP6LOV.js} +7 -7
  8. package/dist/{chunk-RUSUZZAF.js → chunk-BK5ICA2F.js} +4 -4
  9. package/dist/{chunk-T4YJRD22.js → chunk-DUMI2J5M.js} +45 -14
  10. package/dist/chunk-DUMI2J5M.js.map +1 -0
  11. package/dist/{chunk-TKVTEUBD.js → chunk-EJBK7I4F.js} +2 -2
  12. package/dist/{chunk-IT6FRTEW.js → chunk-FVNAU7VO.js} +39 -18
  13. package/dist/chunk-FVNAU7VO.js.map +1 -0
  14. package/dist/{chunk-JM3T27ZW.js → chunk-FWRL7BZ5.js} +7 -7
  15. package/dist/{chunk-DGYTSCKN.js → chunk-HOIRY5XP.js} +14 -14
  16. package/dist/{chunk-AYC2HEAL.js → chunk-HPS554L4.js} +9 -9
  17. package/dist/{chunk-2WDX6I7T.js → chunk-IOQMMH6C.js} +16 -6
  18. package/dist/{chunk-2WDX6I7T.js.map → chunk-IOQMMH6C.js.map} +1 -1
  19. package/dist/{chunk-24WXSC3C.js → chunk-JA7GJDNI.js} +15 -9
  20. package/dist/chunk-JA7GJDNI.js.map +1 -0
  21. package/dist/{chunk-36U5UGIO.js → chunk-JEINYUJH.js} +8 -5
  22. package/dist/chunk-JEINYUJH.js.map +1 -0
  23. package/dist/{chunk-BOPZWRJK.js → chunk-JYBFPNBJ.js} +8 -8
  24. package/dist/chunk-JYBFPNBJ.js.map +1 -0
  25. package/dist/{chunk-K2I6XIK5.js → chunk-KSTZIULO.js} +4 -4
  26. package/dist/chunk-MKWQKKK7.js +72 -0
  27. package/dist/chunk-MKWQKKK7.js.map +1 -0
  28. package/dist/{chunk-CRBVI4GE.js → chunk-PSDVGPQR.js} +5 -5
  29. package/dist/{chunk-DLG62MQY.js → chunk-SFQRETXJ.js} +7 -7
  30. package/dist/{chunk-NXNVTXKG.js → chunk-SGSWVNNB.js} +5 -5
  31. package/dist/{chunk-5LXOJGO2.js → chunk-VNBC3VXM.js} +6 -6
  32. package/dist/{job-orchestrator.protocol-DubMVbm9.d.ts → job-orchestrator.protocol-ZuJ3ow-O.d.ts} +77 -3
  33. package/dist/runtime/base-classes/activity-entity-repository.d.ts +39 -7
  34. package/dist/runtime/base-classes/activity-entity-repository.js +1 -1
  35. package/dist/runtime/base-classes/activity-entity-service.d.ts +12 -10
  36. package/dist/runtime/base-classes/activity-entity-service.js +1 -1
  37. package/dist/runtime/base-classes/index.js +18 -18
  38. package/dist/runtime/shared/openapi/index.js +3 -3
  39. package/dist/runtime/subsystems/auth/index.js +3 -3
  40. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.d.ts +1 -1
  41. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +2 -2
  42. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +2 -2
  43. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +6 -6
  44. package/dist/runtime/subsystems/bridge/bridge.module.d.ts +1 -1
  45. package/dist/runtime/subsystems/bridge/bridge.module.js +19 -19
  46. package/dist/runtime/subsystems/bridge/event-flow.service.d.ts +1 -1
  47. package/dist/runtime/subsystems/bridge/index.d.ts +1 -1
  48. package/dist/runtime/subsystems/bridge/index.js +21 -21
  49. package/dist/runtime/subsystems/cache/cache.module.js +1 -1
  50. package/dist/runtime/subsystems/cache/index.js +3 -3
  51. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +2 -2
  52. package/dist/runtime/subsystems/events/events.module.js +3 -3
  53. package/dist/runtime/subsystems/events/index.js +3 -3
  54. package/dist/runtime/subsystems/index.d.ts +1 -1
  55. package/dist/runtime/subsystems/index.js +50 -50
  56. package/dist/runtime/subsystems/integration/deep-equal.differ.d.ts +19 -0
  57. package/dist/runtime/subsystems/integration/deep-equal.differ.js +1 -1
  58. package/dist/runtime/subsystems/integration/index.js +22 -22
  59. package/dist/runtime/subsystems/integration/integration.module.d.ts +20 -0
  60. package/dist/runtime/subsystems/integration/integration.module.js +4 -4
  61. package/dist/runtime/subsystems/jobs/index.d.ts +1 -1
  62. package/dist/runtime/subsystems/jobs/index.js +43 -43
  63. package/dist/runtime/subsystems/jobs/job-handler.base.d.ts +1 -1
  64. package/dist/runtime/subsystems/jobs/job-handler.base.js +11 -3
  65. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.d.ts +1 -1
  66. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +7 -6
  67. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  68. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.d.ts +1 -1
  69. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +4 -3
  70. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.d.ts +11 -1
  71. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +3 -3
  72. package/dist/runtime/subsystems/jobs/job-orchestrator.protocol.d.ts +1 -1
  73. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.d.ts +1 -1
  74. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.d.ts +1 -1
  75. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +3 -3
  76. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.d.ts +1 -1
  77. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +3 -3
  78. package/dist/runtime/subsystems/jobs/job-run-service.protocol.d.ts +1 -1
  79. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.d.ts +1 -1
  80. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -3
  81. package/dist/runtime/subsystems/jobs/job-worker.d.ts +1 -1
  82. package/dist/runtime/subsystems/jobs/job-worker.js +3 -3
  83. package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +1 -1
  84. package/dist/runtime/subsystems/jobs/job-worker.module.js +13 -13
  85. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +11 -11
  86. package/dist/runtime/subsystems/jobs/jobs-errors.d.ts +1 -1
  87. package/dist/runtime/subsystems/observability/index.d.ts +1 -1
  88. package/dist/runtime/subsystems/observability/observability.protocol.d.ts +1 -1
  89. package/dist/runtime/subsystems/observability/observability.service.d.ts +1 -1
  90. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +1 -1
  91. package/dist/runtime/subsystems/observability/reporters/index.d.ts +1 -1
  92. package/dist/runtime/subsystems/storage/index.js +4 -4
  93. package/dist/runtime/subsystems/storage/storage.module.js +2 -2
  94. package/dist/src/cli/index.js +34 -12
  95. package/dist/src/cli/index.js.map +1 -1
  96. package/dist/src/index.d.ts +23 -8
  97. package/dist/src/index.js +7 -7
  98. package/package.json +2 -1
  99. package/runtime/base-classes/activity-entity-repository.ts +72 -13
  100. package/runtime/base-classes/activity-entity-service.ts +14 -12
  101. package/runtime/subsystems/integration/deep-equal.differ.ts +34 -5
  102. package/runtime/subsystems/integration/integration.module.ts +26 -2
  103. package/runtime/subsystems/jobs/job-handler.base.ts +115 -2
  104. package/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.ts +43 -16
  105. package/runtime/subsystems/jobs/job-orchestrator.memory-backend.ts +58 -18
  106. package/src/patterns/library/activity.pattern.ts +40 -10
  107. package/templates/subsystem/integration-config/codegen-config-integration-block.ejs.t +17 -0
  108. package/dist/chunk-24WXSC3C.js.map +0 -1
  109. package/dist/chunk-36U5UGIO.js.map +0 -1
  110. package/dist/chunk-BOPZWRJK.js.map +0 -1
  111. package/dist/chunk-CO6LUM72.js.map +0 -1
  112. package/dist/chunk-IT6FRTEW.js.map +0 -1
  113. package/dist/chunk-T4YJRD22.js.map +0 -1
  114. package/dist/chunk-XCEI7NUH.js +0 -41
  115. package/dist/chunk-XCEI7NUH.js.map +0 -1
  116. /package/dist/{chunk-H6FO2ZDJ.js.map → chunk-4PFF3ED4.js.map} +0 -0
  117. /package/dist/{chunk-QSJ3J4HE.js.map → chunk-BHZP6LOV.js.map} +0 -0
  118. /package/dist/{chunk-RUSUZZAF.js.map → chunk-BK5ICA2F.js.map} +0 -0
  119. /package/dist/{chunk-TKVTEUBD.js.map → chunk-EJBK7I4F.js.map} +0 -0
  120. /package/dist/{chunk-JM3T27ZW.js.map → chunk-FWRL7BZ5.js.map} +0 -0
  121. /package/dist/{chunk-DGYTSCKN.js.map → chunk-HOIRY5XP.js.map} +0 -0
  122. /package/dist/{chunk-AYC2HEAL.js.map → chunk-HPS554L4.js.map} +0 -0
  123. /package/dist/{chunk-K2I6XIK5.js.map → chunk-KSTZIULO.js.map} +0 -0
  124. /package/dist/{chunk-CRBVI4GE.js.map → chunk-PSDVGPQR.js.map} +0 -0
  125. /package/dist/{chunk-DLG62MQY.js.map → chunk-SFQRETXJ.js.map} +0 -0
  126. /package/dist/{chunk-NXNVTXKG.js.map → chunk-SGSWVNNB.js.map} +0 -0
  127. /package/dist/{chunk-5LXOJGO2.js.map → chunk-VNBC3VXM.js.map} +0 -0
@@ -1,15 +1,15 @@
1
- import {
2
- MemoryRunRecorder
3
- } from "./chunk-EO2QPOKH.js";
4
- import {
5
- PostgresCursorStore
6
- } from "./chunk-XWBK3XJK.js";
7
1
  import {
8
2
  MemoryCursorStore
9
3
  } from "./chunk-AHV4GDYM.js";
10
4
  import {
11
5
  DrizzleIntegrationRunRecorder
12
6
  } from "./chunk-YK5JEVLX.js";
7
+ import {
8
+ MemoryRunRecorder
9
+ } from "./chunk-EO2QPOKH.js";
10
+ import {
11
+ PostgresCursorStore
12
+ } from "./chunk-XWBK3XJK.js";
13
13
  import {
14
14
  INTEGRATION_CURSOR_STORE,
15
15
  INTEGRATION_FIELD_DIFFER,
@@ -19,7 +19,7 @@ import {
19
19
  } from "./chunk-S7C6TIIF.js";
20
20
  import {
21
21
  DeepEqualDiffer
22
- } from "./chunk-36U5UGIO.js";
22
+ } from "./chunk-JEINYUJH.js";
23
23
  import {
24
24
  __decorateClass
25
25
  } from "./chunk-2E224ZSN.js";
@@ -34,7 +34,13 @@ var IntegrationModule = class {
34
34
  { provide: INTEGRATION_MULTI_TENANT, useValue: multiTenant },
35
35
  // Default differ — consumers can override by binding a different
36
36
  // `IFieldDiffer<T>` to `INTEGRATION_FIELD_DIFFER` in their feature module.
37
- { provide: INTEGRATION_FIELD_DIFFER, useValue: new DeepEqualDiffer() }
37
+ // DIFFER-UNIGNORE: `options.differ` (ignore/unignore) is threaded here so
38
+ // a consumer can declare a default-ignored column (e.g. `deletedAt`) as
39
+ // domain data for their entities without binding a bespoke differ.
40
+ {
41
+ provide: INTEGRATION_FIELD_DIFFER,
42
+ useValue: new DeepEqualDiffer(options.differ ?? {})
43
+ }
38
44
  ];
39
45
  const backendProviders = options.backend === "memory" ? [
40
46
  // Wired as singletons via `useValue` so tests can pull
@@ -78,4 +84,4 @@ IntegrationModule = __decorateClass([
78
84
  export {
79
85
  IntegrationModule
80
86
  };
81
- //# sourceMappingURL=chunk-24WXSC3C.js.map
87
+ //# sourceMappingURL=chunk-JA7GJDNI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/subsystems/integration/integration.module.ts"],"sourcesContent":["/**\n * IntegrationModule — `DynamicModule.forRoot({ backend, multiTenant? })` factory\n * wiring the integration subsystem's substrate (SYNC-6, ADR-008 subsystem pattern).\n *\n * ## What this module provides\n *\n * - `INTEGRATION_CURSOR_STORE` — Drizzle or Memory cursor store\n * - `INTEGRATION_RUN_RECORDER` — Drizzle or Memory run recorder\n * - `INTEGRATION_FIELD_DIFFER` — default `DeepEqualDiffer`\n * - `INTEGRATION_MULTI_TENANT` — resolved boolean flag (defaults to false)\n * - `INTEGRATION_MODULE_OPTIONS` — the options object itself, for backends\n * that need to inspect config at construction time\n *\n * ## What this module does NOT provide\n *\n * - `INTEGRATION_CHANGE_SOURCE` — per-provider per-entity; consumer binds in\n * their feature module (e.g. `OpportunityIntegrationModule` provides a\n * `SalesforceOpportunityChangeSource`). Loopback suppression — when\n * needed — is composed into the primitive's middleware chain via\n * `createLoopbackMiddleware(store)` (#226-5 / ADR-033); the\n * orchestrator no longer accepts a fingerprint store directly.\n * - `INTEGRATION_SINK` — per canonical entity; consumer binds in their feature\n * module.\n * - `ExecuteIntegrationUseCase` — registered by the feature module alongside\n * its source + sink bindings. Providing the orchestrator here would\n * force Nest to resolve INTEGRATION_CHANGE_SOURCE + INTEGRATION_SINK at module\n * compile time, which fails when the feature module hasn't been\n * imported yet. Consumers register `ExecuteIntegrationUseCase` in the same\n * `providers` array as their source + sink so resolution is local\n * to where all three are bound.\n *\n * Same shape as `EventsModule.forRoot` — the module wires the bus; you\n * bring your own handlers. Here: the module wires the substrate; you\n * bring your own source + sink.\n *\n * ## Usage\n *\n * ```ts\n * // AppModule — single source of truth for backend + multi-tenancy.\n * @Module({\n * imports: [IntegrationModule.forRoot({ backend: 'drizzle' })],\n * })\n * export class AppModule {}\n *\n * // Per-entity feature module — binds source + sink, gets the\n * // orchestrator for free.\n * @Module({\n * providers: [\n * { provide: INTEGRATION_CHANGE_SOURCE, useClass: SalesforceOpportunitySource },\n * { provide: INTEGRATION_SINK, useClass: OpportunityIntegrationSink },\n * ExecuteIntegrationUseCase,\n * ],\n * })\n * export class OpportunityIntegrationModule {\n * constructor(\n * private readonly execute: ExecuteIntegrationUseCase<CanonicalOpportunity>,\n * ) {}\n * }\n * ```\n *\n * `global: true` means feature modules do not need to re-import\n * `IntegrationModule` — the substrate tokens are available project-wide.\n */\nimport { Module, type DynamicModule, type Provider } from '@nestjs/common';\nimport {\n INTEGRATION_CURSOR_STORE,\n INTEGRATION_FIELD_DIFFER,\n INTEGRATION_MODULE_OPTIONS,\n INTEGRATION_MULTI_TENANT,\n INTEGRATION_RUN_RECORDER,\n} from './integration.tokens';\nimport { MemoryCursorStore } from './integration-cursor-store.memory-backend';\nimport { MemoryRunRecorder } from './integration-run-recorder.memory-backend';\nimport { PostgresCursorStore } from './integration-cursor-store.drizzle-backend';\nimport { DrizzleIntegrationRunRecorder } from './integration-run-recorder.drizzle-backend';\nimport { DeepEqualDiffer, type DeepEqualDifferOptions } from './deep-equal.differ';\n\nexport interface IntegrationModuleOptions {\n /**\n * Backend selection. `drizzle` wires the Postgres cursor store +\n * run-log recorder; `memory` wires in-memory doubles suitable for\n * tests + local dev.\n */\n backend: 'drizzle' | 'memory';\n\n /**\n * Multi-tenancy opt-in (SYNC-6).\n *\n * When `true`, every call to the orchestrator + both Drizzle backends\n * must supply a non-null `tenantId`; missing values throw\n * `MissingTenantIdError`. Defense-in-depth: the orchestrator rejects\n * at entry (no dangling `status=running` rows) AND the Drizzle\n * backends reject at their write boundary (belt-and-braces for any\n * path that bypasses the orchestrator). Both sites use the shared\n * `assertTenantId` helper so error messages match.\n *\n * Memory backends accept `tenantId` unconditionally — their state is\n * process-local; cross-tenant isolation there is not meaningful.\n *\n * Defaults to `false`.\n */\n multiTenant?: boolean;\n\n /**\n * Default-differ configuration (DIFFER-UNIGNORE, 0.17.1). Threaded into the\n * `DeepEqualDiffer` bound to `INTEGRATION_FIELD_DIFFER`. Omit for the\n * historical behaviour (the default ignore list, unchanged).\n *\n * Mirrors `DeepEqualDifferOptions`:\n * - `ignore` — extra field names to ignore (merged with the defaults).\n * - `unignore` — default-ignored field names to RE-include as domain data\n * (e.g. `['deletedAt']` for an entity whose `deletedAt` is a\n * vendor-observed retraction tombstone, not row metadata — swe-brain\n * ADR-0008 §1). Subtracted after the merge, so it wins.\n *\n * A feature module that binds its own `IFieldDiffer<T>` to\n * `INTEGRATION_FIELD_DIFFER` overrides this entirely (per-entity escape hatch\n * unchanged).\n */\n differ?: DeepEqualDifferOptions;\n}\n\n@Module({})\nexport class IntegrationModule {\n static forRoot(options: IntegrationModuleOptions): DynamicModule {\n const multiTenant = options.multiTenant ?? false;\n\n const sharedProviders: Provider[] = [\n { provide: INTEGRATION_MODULE_OPTIONS, useValue: options },\n { provide: INTEGRATION_MULTI_TENANT, useValue: multiTenant },\n // Default differ — consumers can override by binding a different\n // `IFieldDiffer<T>` to `INTEGRATION_FIELD_DIFFER` in their feature module.\n // DIFFER-UNIGNORE: `options.differ` (ignore/unignore) is threaded here so\n // a consumer can declare a default-ignored column (e.g. `deletedAt`) as\n // domain data for their entities without binding a bespoke differ.\n {\n provide: INTEGRATION_FIELD_DIFFER,\n useValue: new DeepEqualDiffer(options.differ ?? {}),\n },\n ];\n\n const backendProviders: Provider[] =\n options.backend === 'memory'\n ? [\n // Wired as singletons via `useValue` so tests can pull\n // them out via `moduleRef.get(MemoryCursorStore)` for\n // direct assertions. Matches JOB-4 / MemoryJobStore shape.\n { provide: MemoryCursorStore, useValue: new MemoryCursorStore() },\n {\n provide: INTEGRATION_CURSOR_STORE,\n useExisting: MemoryCursorStore,\n },\n { provide: MemoryRunRecorder, useValue: new MemoryRunRecorder() },\n {\n provide: INTEGRATION_RUN_RECORDER,\n useExisting: MemoryRunRecorder,\n },\n ]\n : [\n // Drizzle backends — injected with DRIZZLE (provided by the\n // consumer's DrizzleModule) + the INTEGRATION_MULTI_TENANT flag\n // we bound above.\n { provide: INTEGRATION_CURSOR_STORE, useClass: PostgresCursorStore },\n { provide: INTEGRATION_RUN_RECORDER, useClass: DrizzleIntegrationRunRecorder },\n ];\n\n return {\n module: IntegrationModule,\n global: true,\n providers: [...sharedProviders, ...backendProviders],\n exports: [\n INTEGRATION_MODULE_OPTIONS,\n INTEGRATION_MULTI_TENANT,\n INTEGRATION_FIELD_DIFFER,\n INTEGRATION_CURSOR_STORE,\n INTEGRATION_RUN_RECORDER,\n ],\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAS,cAAiD;AA4DnD,IAAM,oBAAN,MAAwB;AAAA,EAC7B,OAAO,QAAQ,SAAkD;AAC/D,UAAM,cAAc,QAAQ,eAAe;AAE3C,UAAM,kBAA8B;AAAA,MAClC,EAAE,SAAS,4BAA4B,UAAU,QAAQ;AAAA,MACzD,EAAE,SAAS,0BAA0B,UAAU,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAM3D;AAAA,QACE,SAAS;AAAA,QACT,UAAU,IAAI,gBAAgB,QAAQ,UAAU,CAAC,CAAC;AAAA,MACpD;AAAA,IACF;AAEA,UAAM,mBACJ,QAAQ,YAAY,WAChB;AAAA;AAAA;AAAA;AAAA,MAIE,EAAE,SAAS,mBAAmB,UAAU,IAAI,kBAAkB,EAAE;AAAA,MAChE;AAAA,QACE,SAAS;AAAA,QACT,aAAa;AAAA,MACf;AAAA,MACA,EAAE,SAAS,mBAAmB,UAAU,IAAI,kBAAkB,EAAE;AAAA,MAChE;AAAA,QACE,SAAS;AAAA,QACT,aAAa;AAAA,MACf;AAAA,IACF,IACA;AAAA;AAAA;AAAA;AAAA,MAIE,EAAE,SAAS,0BAA0B,UAAU,oBAAoB;AAAA,MACnE,EAAE,SAAS,0BAA0B,UAAU,8BAA8B;AAAA,IAC/E;AAEN,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,WAAW,CAAC,GAAG,iBAAiB,GAAG,gBAAgB;AAAA,MACnD,SAAS;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAxDa,oBAAN;AAAA,EADN,OAAO,CAAC,CAAC;AAAA,GACG;","names":[]}
@@ -21,11 +21,14 @@ var DEFAULT_IGNORE_FIELDS = /* @__PURE__ */ new Set([
21
21
  var DeepEqualDiffer = class {
22
22
  ignore;
23
23
  constructor(opts = {}) {
24
- if (opts.ignore && opts.ignore.length > 0) {
25
- this.ignore = /* @__PURE__ */ new Set([...DEFAULT_IGNORE_FIELDS, ...opts.ignore]);
26
- } else {
27
- this.ignore = DEFAULT_IGNORE_FIELDS;
24
+ const merged = new Set(DEFAULT_IGNORE_FIELDS);
25
+ if (opts.ignore) {
26
+ for (const field of opts.ignore) merged.add(field);
27
+ }
28
+ if (opts.unignore) {
29
+ for (const field of opts.unignore) merged.delete(field);
28
30
  }
31
+ this.ignore = merged;
29
32
  }
30
33
  diff(existing, incoming, providerChangedFields) {
31
34
  if (existing === null) {
@@ -104,4 +107,4 @@ function deepEqualObject(a, b) {
104
107
  export {
105
108
  DeepEqualDiffer
106
109
  };
107
- //# sourceMappingURL=chunk-36U5UGIO.js.map
110
+ //# sourceMappingURL=chunk-JEINYUJH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/subsystems/integration/deep-equal.differ.ts"],"sourcesContent":["/**\n * DeepEqualDiffer — default `IFieldDiffer<T>` for the integration subsystem (SYNC-5).\n *\n * Walks every field of `incoming` against `existing`, emitting a structured\n * per-field diff (`{ from, to }`) for every field whose value changed.\n * Returns `'noop'` when the record is unchanged.\n *\n * Design decisions (extracted from the upstream consumer + HS-9 findings):\n *\n * 1. **Ignore list** — row metadata that sinks/services stamp unconditionally\n * so upstream cannot reasonably disagree:\n * `id`, `createdAt`, `updatedAt`, `deletedAt`, `type`,\n * `lastModifiedAt`, `fields`, `providerMetadata`\n * (`fields` is the EAV bag — it's diffed by the sink's EAV dual-write\n * path, not at the canonical-record layer.) Consumers augment the list via\n * `options.ignore` and — when a default is domain data for their entity —\n * REMOVE a default via `options.unignore` (e.g. an entity whose\n * `deletedAt` is a vendor-observed retraction tombstone, not row metadata;\n * see `DeepEqualDifferOptions.unignore`).\n *\n * 2. **`providerChangedFields` hint (CDC)** — when present, restricts the\n * comparison to the hinted field set. The hint is advisory; fields in\n * the ignore list are still filtered out even when hinted. Provider\n * hints are field-NAME-level; they don't override the ignore rules.\n *\n * 3. **Date → ISO string** — `Date` instances are normalized to\n * `toISOString()` before comparison. Sinks return `Date` from the DB\n * driver; adapters typically deliver strings. Direct `===` would\n * always say \"changed.\"\n *\n * 4. **Decimal-string vs number** — Postgres `numeric` columns return as\n * strings through Drizzle; adapters deliver numbers. When one side is a\n * number and the other is a numeric string that parses to the same\n * number, they're equal. The normalizer does NOT coerce non-numeric\n * strings, and it preserves zero-vs-null distinction.\n *\n * 5. **null-existing path** — `diff(null, incoming)` produces a full\n * created-shape diff (`{from: null, to: <value>}` for every non-ignored\n * field). Orchestrator sees this and records `operation: 'created'`.\n */\nimport { Injectable } from '@nestjs/common';\nimport type {\n DiffResult,\n FieldDiff,\n IFieldDiffer,\n} from './integration-field-diff.protocol';\n\n/**\n * Default ignore list. Keep in integration with consumer canonical-record shapes —\n * adding a row-metadata field here means no integration will ever mark it changed.\n *\n * Includes the columns contributed by the `external_id_tracking` behavior\n * (`external_id`/`externalId`, `provider`, `provider_metadata`/`providerMetadata`).\n * These are integration-tracking metadata, not domain attributes: they ride on the\n * canonical record but must never register as a field change (the external id\n * is the record's identity, not a mutable value). Listed in both snake_case\n * and camelCase so the differ ignores them regardless of the consumer's\n * canonical projection casing.\n */\nconst DEFAULT_IGNORE_FIELDS: ReadonlySet<string> = new Set([\n 'id',\n 'createdAt',\n 'updatedAt',\n 'deletedAt',\n 'type',\n 'lastModifiedAt',\n 'fields',\n 'external_id',\n 'externalId',\n 'provider',\n 'provider_metadata',\n 'providerMetadata',\n]);\n\nexport interface DeepEqualDifferOptions {\n /**\n * Extra field names to ignore in addition to the defaults. Consumers can\n * pass `['integration_version']` etc. to augment the base list; values here are\n * merged (not replaced) with `DEFAULT_IGNORE_FIELDS`.\n */\n readonly ignore?: readonly string[];\n\n /**\n * Field names to REMOVE from the default ignore list — the inverse of\n * `ignore`. Use this to declare that a normally-metadata column is in fact\n * DOMAIN DATA for this entity and must register as a field change.\n *\n * The canonical case (swe-brain ADR-0008 §1, the gap this knob closes):\n * `deletedAt` is in `DEFAULT_IGNORE_FIELDS` because most sinks stamp it as\n * row metadata sinks own unconditionally. But an entity with\n * `softDelete: false` and a domain-owned `deleted_at` carries the\n * vendor-observed retraction tombstone ON the canonical record (a Slack\n * `message_deleted` → `deletedAt`). Without un-ignoring it, the tombstone\n * overlay diffs to `'noop'`, the upsert is skipped, and `deleted_at` never\n * lands. `unignore: ['deletedAt']` makes the differ treat it as domain data.\n *\n * Applied AFTER `ignore` is merged, so `unignore` wins on a field listed in\n * both. Subtracting a field not in the (merged) ignore set is a harmless\n * no-op. Does not touch `DEFAULT_IGNORE_FIELDS` for any other instance.\n */\n readonly unignore?: readonly string[];\n}\n\n@Injectable()\nexport class DeepEqualDiffer<T extends Record<string, unknown>>\n implements IFieldDiffer<T>\n{\n private readonly ignore: ReadonlySet<string>;\n\n constructor(opts: DeepEqualDifferOptions = {}) {\n const merged = new Set<string>(DEFAULT_IGNORE_FIELDS);\n if (opts.ignore) {\n for (const field of opts.ignore) merged.add(field);\n }\n // `unignore` is subtracted last so it wins over a field that also appears\n // in `ignore` or the defaults — \"this column is domain data here.\"\n if (opts.unignore) {\n for (const field of opts.unignore) merged.delete(field);\n }\n this.ignore = merged;\n }\n\n diff(\n existing: T | null,\n incoming: T,\n providerChangedFields?: string[],\n ): DiffResult {\n // Created-shape: every non-ignored field becomes `{from: null, to}`.\n if (existing === null) {\n const out: FieldDiff = {};\n for (const key of Object.keys(incoming)) {\n if (this.ignore.has(key)) continue;\n const value = (incoming as Record<string, unknown>)[key];\n // Skip fields that are themselves null/undefined — a created record\n // doesn't need to declare \"this field is null now\" for every\n // untouched column.\n if (value === null || value === undefined) continue;\n out[key] = { from: null, to: value };\n }\n return Object.keys(out).length === 0 ? 'noop' : out;\n }\n\n // Field set to compare. `providerChangedFields` narrows to a hint set;\n // ignored fields are filtered out regardless of hint.\n const candidates = new Set<string>();\n if (providerChangedFields && providerChangedFields.length > 0) {\n for (const key of providerChangedFields) {\n if (!this.ignore.has(key)) candidates.add(key);\n }\n } else {\n for (const key of Object.keys(incoming)) {\n if (!this.ignore.has(key)) candidates.add(key);\n }\n // Also include keys that exist on existing but not on incoming —\n // e.g. a field that was cleared. This would otherwise be missed when\n // incoming carries an undefined column we drop from the iteration.\n for (const key of Object.keys(existing)) {\n if (this.ignore.has(key)) continue;\n if (!(key in (incoming as Record<string, unknown>))) continue;\n candidates.add(key);\n }\n }\n\n const out: FieldDiff = {};\n for (const key of candidates) {\n const before = (existing as Record<string, unknown>)[key];\n const after = (incoming as Record<string, unknown>)[key];\n if (!isEqual(before, after)) {\n out[key] = { from: before ?? null, to: after ?? null };\n }\n }\n\n return Object.keys(out).length === 0 ? 'noop' : out;\n }\n}\n\n// ─── equality helpers ───────────────────────────────────────────────────────\n\n/**\n * Field-level equality with the canonical-integration normalizations:\n * - Date → toISOString (adapters deliver strings)\n * - numeric-string vs number → numeric equality when both parse\n * - deep equality for plain objects/arrays (single-level is enough for\n * canonical records; nested records travel as jsonb columns where the\n * sink already owns the comparison)\n */\nfunction isEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n\n const na = normalize(a);\n const nb = normalize(b);\n if (na === nb) return true;\n\n // After normalization: both may still be non-primitive objects.\n if (\n typeof na === 'object' &&\n typeof nb === 'object' &&\n na !== null &&\n nb !== null\n ) {\n return deepEqualObject(na as Record<string, unknown>, nb as Record<string, unknown>);\n }\n\n // Numeric string ↔ number: when one side is a number and the other is a\n // string that parses to the same finite number.\n const numericEqual = maybeNumericEqual(na, nb) || maybeNumericEqual(nb, na);\n return numericEqual;\n}\n\nfunction normalize(value: unknown): unknown {\n if (value instanceof Date) return value.toISOString();\n return value;\n}\n\nfunction maybeNumericEqual(a: unknown, b: unknown): boolean {\n // a is string-shape, b is number — parse a and compare. Only when the\n // string looks numeric AND the parse round-trips (no silent NaN pass-\n // through on non-numeric strings).\n if (typeof a !== 'string' || typeof b !== 'number') return false;\n if (a.trim() === '') return false;\n const parsed = Number(a);\n if (!Number.isFinite(parsed)) return false;\n return parsed === b;\n}\n\nfunction deepEqualObject(\n a: Record<string, unknown>,\n b: Record<string, unknown>,\n): boolean {\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n if (aKeys.length !== bKeys.length) return false;\n for (const key of aKeys) {\n if (!(key in b)) return false;\n if (!isEqual(a[key], b[key])) return false;\n }\n return true;\n}\n"],"mappings":";;;;;AAwCA,SAAS,kBAAkB;AAmB3B,IAAM,wBAA6C,oBAAI,IAAI;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAgCM,IAAM,kBAAN,MAEP;AAAA,EACmB;AAAA,EAEjB,YAAY,OAA+B,CAAC,GAAG;AAC7C,UAAM,SAAS,IAAI,IAAY,qBAAqB;AACpD,QAAI,KAAK,QAAQ;AACf,iBAAW,SAAS,KAAK,OAAQ,QAAO,IAAI,KAAK;AAAA,IACnD;AAGA,QAAI,KAAK,UAAU;AACjB,iBAAW,SAAS,KAAK,SAAU,QAAO,OAAO,KAAK;AAAA,IACxD;AACA,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,KACE,UACA,UACA,uBACY;AAEZ,QAAI,aAAa,MAAM;AACrB,YAAMA,OAAiB,CAAC;AACxB,iBAAW,OAAO,OAAO,KAAK,QAAQ,GAAG;AACvC,YAAI,KAAK,OAAO,IAAI,GAAG,EAAG;AAC1B,cAAM,QAAS,SAAqC,GAAG;AAIvD,YAAI,UAAU,QAAQ,UAAU,OAAW;AAC3C,QAAAA,KAAI,GAAG,IAAI,EAAE,MAAM,MAAM,IAAI,MAAM;AAAA,MACrC;AACA,aAAO,OAAO,KAAKA,IAAG,EAAE,WAAW,IAAI,SAASA;AAAA,IAClD;AAIA,UAAM,aAAa,oBAAI,IAAY;AACnC,QAAI,yBAAyB,sBAAsB,SAAS,GAAG;AAC7D,iBAAW,OAAO,uBAAuB;AACvC,YAAI,CAAC,KAAK,OAAO,IAAI,GAAG,EAAG,YAAW,IAAI,GAAG;AAAA,MAC/C;AAAA,IACF,OAAO;AACL,iBAAW,OAAO,OAAO,KAAK,QAAQ,GAAG;AACvC,YAAI,CAAC,KAAK,OAAO,IAAI,GAAG,EAAG,YAAW,IAAI,GAAG;AAAA,MAC/C;AAIA,iBAAW,OAAO,OAAO,KAAK,QAAQ,GAAG;AACvC,YAAI,KAAK,OAAO,IAAI,GAAG,EAAG;AAC1B,YAAI,EAAE,OAAQ,UAAuC;AACrD,mBAAW,IAAI,GAAG;AAAA,MACpB;AAAA,IACF;AAEA,UAAM,MAAiB,CAAC;AACxB,eAAW,OAAO,YAAY;AAC5B,YAAM,SAAU,SAAqC,GAAG;AACxD,YAAM,QAAS,SAAqC,GAAG;AACvD,UAAI,CAAC,QAAQ,QAAQ,KAAK,GAAG;AAC3B,YAAI,GAAG,IAAI,EAAE,MAAM,UAAU,MAAM,IAAI,SAAS,KAAK;AAAA,MACvD;AAAA,IACF;AAEA,WAAO,OAAO,KAAK,GAAG,EAAE,WAAW,IAAI,SAAS;AAAA,EAClD;AACF;AAtEa,kBAAN;AAAA,EADN,WAAW;AAAA,GACC;AAkFb,SAAS,QAAQ,GAAY,GAAqB;AAChD,MAAI,MAAM,EAAG,QAAO;AAEpB,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,KAAK,UAAU,CAAC;AACtB,MAAI,OAAO,GAAI,QAAO;AAGtB,MACE,OAAO,OAAO,YACd,OAAO,OAAO,YACd,OAAO,QACP,OAAO,MACP;AACA,WAAO,gBAAgB,IAA+B,EAA6B;AAAA,EACrF;AAIA,QAAM,eAAe,kBAAkB,IAAI,EAAE,KAAK,kBAAkB,IAAI,EAAE;AAC1E,SAAO;AACT;AAEA,SAAS,UAAU,OAAyB;AAC1C,MAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,SAAO;AACT;AAEA,SAAS,kBAAkB,GAAY,GAAqB;AAI1D,MAAI,OAAO,MAAM,YAAY,OAAO,MAAM,SAAU,QAAO;AAC3D,MAAI,EAAE,KAAK,MAAM,GAAI,QAAO;AAC5B,QAAM,SAAS,OAAO,CAAC;AACvB,MAAI,CAAC,OAAO,SAAS,MAAM,EAAG,QAAO;AACrC,SAAO,WAAW;AACpB;AAEA,SAAS,gBACP,GACA,GACS;AACT,MAAI,MAAM,QAAQ,CAAC,MAAM,MAAM,QAAQ,CAAC,EAAG,QAAO;AAClD,QAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,QAAM,QAAQ,OAAO,KAAK,CAAC;AAC3B,MAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;AAC1C,aAAW,OAAO,OAAO;AACvB,QAAI,EAAE,OAAO,GAAI,QAAO;AACxB,QAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,EAAE,GAAG,CAAC,EAAG,QAAO;AAAA,EACvC;AACA,SAAO;AACT;","names":["out"]}
@@ -11,26 +11,26 @@ var ActivityEntityService = class extends BaseService {
11
11
  return this.repository.findByDateRange(start, end);
12
12
  }
13
13
  /**
14
- * Find all activities for a specific user.
14
+ * Find all activities for a specific user (actor / owner scoping).
15
15
  */
16
16
  findByUser(userId) {
17
17
  return this.repository.findByUserId(userId);
18
18
  }
19
19
  /**
20
- * Find all activities for a specific opportunity.
20
+ * Find all activities for a specific subject (config-driven FK column).
21
21
  */
22
- findByOpportunity(opportunityId) {
23
- return this.repository.findByOpportunityId(opportunityId);
22
+ findBySubject(subjectId) {
23
+ return this.repository.findBySubjectId(subjectId);
24
24
  }
25
25
  /**
26
- * Find the most recent activities for an opportunity.
26
+ * Find the most recent activities for a subject.
27
27
  */
28
- findRecent(opportunityId, limit) {
29
- return this.repository.findRecentByOpportunityId(opportunityId, limit);
28
+ findRecent(subjectId, limit) {
29
+ return this.repository.findRecentBySubjectId(subjectId, limit);
30
30
  }
31
31
  };
32
32
 
33
33
  export {
34
34
  ActivityEntityService
35
35
  };
36
- //# sourceMappingURL=chunk-BOPZWRJK.js.map
36
+ //# sourceMappingURL=chunk-JYBFPNBJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/base-classes/activity-entity-service.ts"],"sourcesContent":["/**\n * ActivityEntityService<TRepo, TEntity>\n *\n * Family-specific base service for activity / interaction entities. Delegates\n * to an activity repository that provides date-range, actor (`user_id`), and\n * config-driven subject queries. The subject FK column is resolved inside the\n * repository from its `patternConfig` (ADR-031 §4) — the service is\n * subject-name-agnostic. See ACTIVITY-SUBJECT-1.\n */\nimport { BaseService, type IBaseRepository } from './base-service';\n\nexport interface IActivityEntityRepository<TEntity> extends IBaseRepository<TEntity> {\n findByDateRange(start: Date, end: Date): Promise<TEntity[]>;\n findByUserId(userId: string): Promise<TEntity[]>;\n findBySubjectId(subjectId: string): Promise<TEntity[]>;\n findRecentBySubjectId(subjectId: string, limit?: number): Promise<TEntity[]>;\n}\n\nexport abstract class ActivityEntityService<\n TRepo extends IActivityEntityRepository<TEntity>,\n TEntity,\n> extends BaseService<TRepo, TEntity> {\n /**\n * Find activities within a date range (inclusive).\n */\n findByDateRange(start: Date, end: Date): Promise<TEntity[]> {\n return this.repository.findByDateRange(start, end);\n }\n\n /**\n * Find all activities for a specific user (actor / owner scoping).\n */\n findByUser(userId: string): Promise<TEntity[]> {\n return this.repository.findByUserId(userId);\n }\n\n /**\n * Find all activities for a specific subject (config-driven FK column).\n */\n findBySubject(subjectId: string): Promise<TEntity[]> {\n return this.repository.findBySubjectId(subjectId);\n }\n\n /**\n * Find the most recent activities for a subject.\n */\n findRecent(subjectId: string, limit?: number): Promise<TEntity[]> {\n return this.repository.findRecentBySubjectId(subjectId, limit);\n }\n}\n"],"mappings":";;;;;AAkBO,IAAe,wBAAf,cAGG,YAA4B;AAAA;AAAA;AAAA;AAAA,EAIpC,gBAAgB,OAAa,KAA+B;AAC1D,WAAO,KAAK,WAAW,gBAAgB,OAAO,GAAG;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,QAAoC;AAC7C,WAAO,KAAK,WAAW,aAAa,MAAM;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,WAAuC;AACnD,WAAO,KAAK,WAAW,gBAAgB,SAAS;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,WAAW,WAAmB,OAAoC;AAChE,WAAO,KAAK,WAAW,sBAAsB,WAAW,KAAK;AAAA,EAC/D;AACF;","names":[]}
@@ -1,9 +1,9 @@
1
- import {
2
- assertTenantId
3
- } from "./chunk-6DWFJNIK.js";
4
1
  import {
5
2
  bridgeDelivery
6
3
  } from "./chunk-2TVVBC53.js";
4
+ import {
5
+ assertTenantId
6
+ } from "./chunk-6DWFJNIK.js";
7
7
  import {
8
8
  BRIDGE_MULTI_TENANT
9
9
  } from "./chunk-4LH67P4U.js";
@@ -119,4 +119,4 @@ DrizzleBridgeDeliveryRepo = __decorateClass([
119
119
  export {
120
120
  DrizzleBridgeDeliveryRepo
121
121
  };
122
- //# sourceMappingURL=chunk-K2I6XIK5.js.map
122
+ //# sourceMappingURL=chunk-KSTZIULO.js.map
@@ -0,0 +1,72 @@
1
+ import {
2
+ BaseRepository
3
+ } from "./chunk-J6KZS54B.js";
4
+
5
+ // runtime/base-classes/activity-entity-repository.ts
6
+ import { eq, between, desc } from "drizzle-orm";
7
+ var toCamel = (snake) => snake.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
8
+ var ActivityEntityRepository = class extends BaseRepository {
9
+ /**
10
+ * Per-entity Activity config. The template emits this from `config:
11
+ * { Activity: {...} }`; entities that only use date-range / user scoping omit
12
+ * it (and must not call the subject finders).
13
+ */
14
+ patternConfig;
15
+ /**
16
+ * camelCase key for the recency-ordering column. Defaults to `occurredAt`
17
+ * (column `occurred_at`); override via `patternConfig.occurredAt`.
18
+ */
19
+ get occurredAtColumn() {
20
+ const snake = this.patternConfig?.occurredAt ?? "occurred_at";
21
+ return toCamel(snake);
22
+ }
23
+ /**
24
+ * camelCase key for the subject FK column, resolved from `patternConfig`:
25
+ * `subjectColumn` (explicit) → `<subject>_id` (derived). Throws when neither
26
+ * is configured — the subject finders are unusable without it, and a clear
27
+ * error beats a silent `undefined` column index.
28
+ */
29
+ get subjectColumn() {
30
+ const explicit = this.patternConfig?.subjectColumn;
31
+ if (explicit) return toCamel(explicit);
32
+ const subject = this.patternConfig?.subject;
33
+ if (subject) return toCamel(`${subject}_id`);
34
+ throw new Error(
35
+ "ActivityEntityRepository: subject finders require a subject column. Set `config: { Activity: { subject: '<entity>' } }` (\u2192 <entity>_id) or `config: { Activity: { subjectColumn: '<column>' } }` on the entity YAML."
36
+ );
37
+ }
38
+ /**
39
+ * Find activities within a date range (inclusive), by the recency column.
40
+ */
41
+ async findByDateRange(start, end) {
42
+ const rows = await this.baseQuery().where(between(this.table[this.occurredAtColumn], start, end));
43
+ return rows;
44
+ }
45
+ /**
46
+ * Find all activities for a specific user (actor / owner scoping).
47
+ */
48
+ async findByUserId(userId) {
49
+ const rows = await this.baseQuery().where(eq(this.table["userId"], userId));
50
+ return rows;
51
+ }
52
+ /**
53
+ * Find all activities for a specific subject (config-driven FK column).
54
+ */
55
+ async findBySubjectId(subjectId) {
56
+ const rows = await this.baseQuery().where(eq(this.table[this.subjectColumn], subjectId));
57
+ return rows;
58
+ }
59
+ /**
60
+ * Find the most recent activities for a subject, ordered by the recency
61
+ * column descending.
62
+ */
63
+ async findRecentBySubjectId(subjectId, limit = 10) {
64
+ const rows = await this.baseQuery().where(eq(this.table[this.subjectColumn], subjectId)).orderBy(desc(this.table[this.occurredAtColumn])).limit(limit);
65
+ return rows;
66
+ }
67
+ };
68
+
69
+ export {
70
+ ActivityEntityRepository
71
+ };
72
+ //# sourceMappingURL=chunk-MKWQKKK7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/base-classes/activity-entity-repository.ts"],"sourcesContent":["/**\n * ActivityEntityRepository<TEntity>\n *\n * Family-specific base for activity / interaction entities (emails, calls,\n * meetings, messages, transcripts). Adds date-range queries, actor (`user_id`)\n * scoping, recency ordering, and **config-driven subject scoping** — the\n * subject FK column is resolved from the concrete repo's `patternConfig`\n * (ADR-031 §4) rather than hardcoded, so the same base serves a CRM\n * `opportunity`-scoped activity and a swe-brain `person`-scoped interaction.\n *\n * Concrete repos extend this and declare their table + behaviors, and (when\n * they use the subject finders) a `patternConfig` carrying `subject` /\n * `subjectColumn` / `occurredAt`. The template emits that property from the\n * entity's `config: { Activity: {...} }` block. See ACTIVITY-SUBJECT-1.\n */\nimport { eq, between, desc } from 'drizzle-orm';\nimport { BaseRepository } from './base-repository';\n\n/**\n * Per-entity Activity config (matches `ActivityPatternConfigSchema` in\n * `src/patterns/library/activity.pattern.ts`). Carried on the concrete repo as\n * `patternConfig` and read here to resolve column names at runtime.\n */\nexport interface ActivityPatternConfig {\n /** Subject entity name → derives the FK column `<subject>_id`. */\n subject?: string;\n /** Explicit snake_case FK column, when it does not follow `<subject>_id`. */\n subjectColumn?: string;\n /** snake_case recency-ordering column; defaults to `occurred_at`. */\n occurredAt?: string;\n}\n\nconst toCamel = (snake: string): string =>\n snake.replace(/_([a-z0-9])/g, (_, c: string) => c.toUpperCase());\n\nexport abstract class ActivityEntityRepository<TEntity> extends BaseRepository<TEntity> {\n /**\n * Per-entity Activity config. The template emits this from `config:\n * { Activity: {...} }`; entities that only use date-range / user scoping omit\n * it (and must not call the subject finders).\n */\n protected readonly patternConfig?: ActivityPatternConfig;\n\n /**\n * camelCase key for the recency-ordering column. Defaults to `occurredAt`\n * (column `occurred_at`); override via `patternConfig.occurredAt`.\n */\n protected get occurredAtColumn(): string {\n const snake = this.patternConfig?.occurredAt ?? 'occurred_at';\n return toCamel(snake);\n }\n\n /**\n * camelCase key for the subject FK column, resolved from `patternConfig`:\n * `subjectColumn` (explicit) → `<subject>_id` (derived). Throws when neither\n * is configured — the subject finders are unusable without it, and a clear\n * error beats a silent `undefined` column index.\n */\n protected get subjectColumn(): string {\n const explicit = this.patternConfig?.subjectColumn;\n if (explicit) return toCamel(explicit);\n const subject = this.patternConfig?.subject;\n if (subject) return toCamel(`${subject}_id`);\n throw new Error(\n 'ActivityEntityRepository: subject finders require a subject column. ' +\n \"Set `config: { Activity: { subject: '<entity>' } }` (→ <entity>_id) \" +\n \"or `config: { Activity: { subjectColumn: '<column>' } }` on the entity YAML.\",\n );\n }\n\n /**\n * Find activities within a date range (inclusive), by the recency column.\n */\n async findByDateRange(start: Date, end: Date): Promise<TEntity[]> {\n const rows = await this.baseQuery()\n .where(between(this.table[this.occurredAtColumn], start, end));\n return rows as TEntity[];\n }\n\n /**\n * Find all activities for a specific user (actor / owner scoping).\n */\n async findByUserId(userId: string): Promise<TEntity[]> {\n const rows = await this.baseQuery()\n .where(eq(this.table['userId'], userId));\n return rows as TEntity[];\n }\n\n /**\n * Find all activities for a specific subject (config-driven FK column).\n */\n async findBySubjectId(subjectId: string): Promise<TEntity[]> {\n const rows = await this.baseQuery()\n .where(eq(this.table[this.subjectColumn], subjectId));\n return rows as TEntity[];\n }\n\n /**\n * Find the most recent activities for a subject, ordered by the recency\n * column descending.\n */\n async findRecentBySubjectId(subjectId: string, limit = 10): Promise<TEntity[]> {\n const rows = await this.baseQuery()\n .where(eq(this.table[this.subjectColumn], subjectId))\n .orderBy(desc(this.table[this.occurredAtColumn]))\n .limit(limit);\n return rows as TEntity[];\n }\n}\n"],"mappings":";;;;;AAeA,SAAS,IAAI,SAAS,YAAY;AAiBlC,IAAM,UAAU,CAAC,UACf,MAAM,QAAQ,gBAAgB,CAAC,GAAG,MAAc,EAAE,YAAY,CAAC;AAE1D,IAAe,2BAAf,cAAyD,eAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMnE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMnB,IAAc,mBAA2B;AACvC,UAAM,QAAQ,KAAK,eAAe,cAAc;AAChD,WAAO,QAAQ,KAAK;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAc,gBAAwB;AACpC,UAAM,WAAW,KAAK,eAAe;AACrC,QAAI,SAAU,QAAO,QAAQ,QAAQ;AACrC,UAAM,UAAU,KAAK,eAAe;AACpC,QAAI,QAAS,QAAO,QAAQ,GAAG,OAAO,KAAK;AAC3C,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,OAAa,KAA+B;AAChE,UAAM,OAAO,MAAM,KAAK,UAAU,EAC/B,MAAM,QAAQ,KAAK,MAAM,KAAK,gBAAgB,GAAG,OAAO,GAAG,CAAC;AAC/D,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,QAAoC;AACrD,UAAM,OAAO,MAAM,KAAK,UAAU,EAC/B,MAAM,GAAG,KAAK,MAAM,QAAQ,GAAG,MAAM,CAAC;AACzC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,gBAAgB,WAAuC;AAC3D,UAAM,OAAO,MAAM,KAAK,UAAU,EAC/B,MAAM,GAAG,KAAK,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;AACtD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,sBAAsB,WAAmB,QAAQ,IAAwB;AAC7E,UAAM,OAAO,MAAM,KAAK,UAAU,EAC/B,MAAM,GAAG,KAAK,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC,EACnD,QAAQ,KAAK,KAAK,MAAM,KAAK,gBAAgB,CAAC,CAAC,EAC/C,MAAM,KAAK;AACd,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -9,22 +9,22 @@ import {
9
9
  } from "./chunk-EDKJU5BO.js";
10
10
  import {
11
11
  DrizzleBridgeDeliveryRepo
12
- } from "./chunk-K2I6XIK5.js";
12
+ } from "./chunk-KSTZIULO.js";
13
13
  import {
14
14
  MemoryBridgeDeliveryRepo
15
15
  } from "./chunk-4DOJBQTP.js";
16
16
  import {
17
17
  BridgeOutboxDrainHook
18
- } from "./chunk-DLG62MQY.js";
18
+ } from "./chunk-SFQRETXJ.js";
19
19
  import {
20
20
  BridgeDeliveryHandler
21
- } from "./chunk-NXNVTXKG.js";
21
+ } from "./chunk-SGSWVNNB.js";
22
22
  import {
23
23
  BridgeReservedPoolsNotPolledError
24
24
  } from "./chunk-NXXDZ6ZF.js";
25
25
  import {
26
26
  JOB_WORKER_MODULE_OPTIONS
27
- } from "./chunk-AYC2HEAL.js";
27
+ } from "./chunk-HPS554L4.js";
28
28
  import {
29
29
  BRIDGE_DELIVERY_REPO,
30
30
  BRIDGE_MODULE_OPTIONS,
@@ -119,4 +119,4 @@ BridgeModule = __decorateClass([
119
119
  export {
120
120
  BridgeModule
121
121
  };
122
- //# sourceMappingURL=chunk-CRBVI4GE.js.map
122
+ //# sourceMappingURL=chunk-PSDVGPQR.js.map
@@ -1,22 +1,22 @@
1
1
  import {
2
2
  BRIDGE_DELIVERY_JOB_TYPE
3
- } from "./chunk-NXNVTXKG.js";
3
+ } from "./chunk-SGSWVNNB.js";
4
4
  import {
5
5
  bridgeDelivery
6
6
  } from "./chunk-2TVVBC53.js";
7
- import {
8
- jobRuns
9
- } from "./chunk-OKXZ63IA.js";
10
7
  import {
11
8
  JOBS_LISTEN_NOTIFY
12
9
  } from "./chunk-ZPL74UQN.js";
13
10
  import {
14
- BRIDGE_REGISTRY
15
- } from "./chunk-4LH67P4U.js";
11
+ jobRuns
12
+ } from "./chunk-OKXZ63IA.js";
16
13
  import {
17
14
  JOBS_WAKE_CHANNEL,
18
15
  pgNotify
19
16
  } from "./chunk-MYQIQ27N.js";
17
+ import {
18
+ BRIDGE_REGISTRY
19
+ } from "./chunk-4LH67P4U.js";
20
20
  import {
21
21
  __decorateClass,
22
22
  __decorateParam
@@ -151,4 +151,4 @@ BridgeOutboxDrainHook = __decorateClass([
151
151
  export {
152
152
  BridgeOutboxDrainHook
153
153
  };
154
- //# sourceMappingURL=chunk-DLG62MQY.js.map
154
+ //# sourceMappingURL=chunk-SFQRETXJ.js.map
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  assertTenantId
3
3
  } from "./chunk-6DWFJNIK.js";
4
- import {
5
- JobHandler,
6
- JobHandlerBase
7
- } from "./chunk-CO6LUM72.js";
8
4
  import {
9
5
  JOB_ORCHESTRATOR
10
6
  } from "./chunk-ZPL74UQN.js";
7
+ import {
8
+ JobHandler,
9
+ JobHandlerBase
10
+ } from "./chunk-7P5ODGLA.js";
11
11
  import {
12
12
  BRIDGE_DELIVERY_REPO,
13
13
  BRIDGE_MULTI_TENANT,
@@ -118,4 +118,4 @@ export {
118
118
  BRIDGE_DELIVERY_JOB_TYPE,
119
119
  BridgeDeliveryHandler
120
120
  };
121
- //# sourceMappingURL=chunk-NXNVTXKG.js.map
121
+ //# sourceMappingURL=chunk-SGSWVNNB.js.map
@@ -1,19 +1,19 @@
1
+ import {
2
+ MissingTenantIdError
3
+ } from "./chunk-T4BIIU5E.js";
1
4
  import {
2
5
  clampLimit,
3
6
  decodeKeysetCursor,
4
7
  encodeKeysetCursor,
5
8
  toJobRunSummary
6
9
  } from "./chunk-L3LZWWSX.js";
7
- import {
8
- jobRuns
9
- } from "./chunk-OKXZ63IA.js";
10
10
  import {
11
11
  JOBS_MULTI_TENANT,
12
12
  JOB_ORCHESTRATOR
13
13
  } from "./chunk-ZPL74UQN.js";
14
14
  import {
15
- MissingTenantIdError
16
- } from "./chunk-T4BIIU5E.js";
15
+ jobRuns
16
+ } from "./chunk-OKXZ63IA.js";
17
17
  import {
18
18
  DRIZZLE
19
19
  } from "./chunk-U64T4YZE.js";
@@ -198,4 +198,4 @@ DrizzleJobRunService = __decorateClass([
198
198
  export {
199
199
  DrizzleJobRunService
200
200
  };
201
- //# sourceMappingURL=chunk-5LXOJGO2.js.map
201
+ //# sourceMappingURL=chunk-VNBC3VXM.js.map
@@ -36,12 +36,31 @@ interface RetryPolicy {
36
36
  baseMs: number;
37
37
  nonRetryableErrors?: string[];
38
38
  }
39
+ /**
40
+ * Concurrency lane key (JOB-FN-KEY, 0.16.2).
41
+ *
42
+ * Two authoring forms, both honored end-to-end (the typed function form was
43
+ * previously dropped to `null` at registration — see `upsertJobRows` — so
44
+ * `collisionMode` silently never engaged):
45
+ *
46
+ * - **`string`** — a `{{field}}` template evaluated against the start
47
+ * payload by `evaluateKeyTemplate` (single-key substitution, no dotted
48
+ * paths). Persisted verbatim to `job.concurrency_key_template`.
49
+ * - **`(input) => string`** — an arbitrary function of the input. Persisted
50
+ * as the `FN_KEY_SENTINEL` marker so the definition-hash gate stays stable
51
+ * and the collision path engages; `start()` re-resolves the live function
52
+ * from `JOB_HANDLER_REGISTRY` and evaluates it against the payload.
53
+ *
54
+ * Both forms produce a per-lane key; same key + in-flight incumbent ⇒
55
+ * `collisionMode` ('queue' | 'reject' | 'replace') decides.
56
+ */
57
+ type JobKeySelector<TInput> = string | ((input: TInput) => string);
39
58
  interface ConcurrencyPolicy<TInput> {
40
- key: (input: TInput) => string;
59
+ key: JobKeySelector<TInput>;
41
60
  collisionMode: 'queue' | 'reject' | 'replace';
42
61
  }
43
62
  interface DedupePolicy<TInput> {
44
- key: (input: TInput) => string;
63
+ key: JobKeySelector<TInput>;
45
64
  windowMs: number;
46
65
  }
47
66
  /**
@@ -165,6 +184,61 @@ declare namespace HandlerRegistry {
165
184
  /** Lookup by job type, or `undefined` if no `@JobHandler` is registered. */
166
185
  function get(type: string): HandlerRegistryEntry | undefined;
167
186
  }
187
+ /**
188
+ * Sentinel persisted to `job.concurrency_key_template` / `dedupe_key_template`
189
+ * when the authored `key` is a function rather than a `{{field}}` template.
190
+ *
191
+ * Why a sentinel (not `null`): the collision/dedupe paths in both backends gate
192
+ * on `definition.concurrencyKeyTemplate != null`. A function key persisted as
193
+ * `null` (the pre-0.16.2 bug) left those columns empty, so `collisionMode` /
194
+ * the dedupe window never engaged — the job ran with NO key. A stable sentinel
195
+ * keeps the column non-null (path engages) AND keeps the definition-hash gate
196
+ * (`upsertJobRows`' `IS DISTINCT FROM` clause) stable across boots, since the
197
+ * function identity itself can't be hashed. `start()` detects the sentinel and
198
+ * re-resolves the live function from `JOB_HANDLER_REGISTRY`.
199
+ *
200
+ * Chosen as an angle-bracketed token so it can never collide with a real
201
+ * `{{field}}` template (which never contains a literal `<`).
202
+ */
203
+ declare const FN_KEY_SENTINEL = "<fn>";
204
+ /**
205
+ * Registration-time projection: collapse an authored `JobKeySelector` to the
206
+ * string stored in the `job` definition row. A string template is stored
207
+ * verbatim; a function is stored as `FN_KEY_SENTINEL`; absence stays `null`.
208
+ */
209
+ declare function keySelectorToTemplate(key: JobKeySelector<unknown> | undefined): string | null;
210
+ /** Which meta policy a key belongs to — selects the live fn at `start()`. */
211
+ type KeyKind = 'concurrency' | 'dedupe';
212
+ /**
213
+ * `start()`-time resolution shared by every backend. Turns the persisted
214
+ * template column into the concrete per-run key for the given payload.
215
+ *
216
+ * - `template == null` → `null` (no key; caller skips the collision/dedupe path).
217
+ * - `template === FN_KEY_SENTINEL` → look the live `@JobHandler` meta up in
218
+ * `JOB_HANDLER_REGISTRY`, pull `meta[kind].key`, and invoke it against the
219
+ * payload. The registry is the runtime source of truth (the worker already
220
+ * resolves handler classes the same way), so the function survives the DB
221
+ * round-trip even though it can't be persisted.
222
+ * - otherwise → a `{{field}}` template, evaluated via the injected
223
+ * `evaluateTemplate` (each backend passes its own copy to avoid a runtime
224
+ * import cycle).
225
+ *
226
+ * Throws `JobKeyFunctionUnavailableError` if the sentinel is present but no
227
+ * live function can be found (e.g. the registry was reset, or a function key
228
+ * was persisted by a newer build and read by an older one). Failing loud beats
229
+ * silently degrading to no-key — the exact regression this fix exists to kill.
230
+ */
231
+ declare function resolveJobKey(kind: KeyKind, type: string, template: string | null, payload: Record<string, unknown>, evaluateTemplate: (template: string, payload: Record<string, unknown>) => string): string | null;
232
+ /**
233
+ * Raised when a `${FN_KEY_SENTINEL}` template is read but the live function
234
+ * key is missing from `JOB_HANDLER_REGISTRY`. Kept here (not in `jobs-errors`)
235
+ * so `job-handler.base` stays import-cycle-free.
236
+ */
237
+ declare class JobKeyFunctionUnavailableError extends Error {
238
+ readonly jobType: string;
239
+ readonly kind: KeyKind;
240
+ constructor(jobType: string, kind: KeyKind);
241
+ }
168
242
 
169
243
  /**
170
244
  * Public return type for orchestrator reads. Re-exported as `JobRun` so
@@ -310,4 +384,4 @@ interface IJobOrchestrator {
310
384
  }>;
311
385
  }
312
386
 
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 };
387
+ export { type CancelOptions as C, type DedupePolicy as D, FN_KEY_SENTINEL as F, HandlerRegistry as H, type IJobOrchestrator as I, JOB_HANDLER_METADATA_KEY as J, type KeyKind as K, 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, JobKeyFunctionUnavailableError as n, type JobKeySelector as o, type JobTrigger as p, keySelectorToTemplate as q, resolveJobKey as r };
@@ -4,23 +4,55 @@ import 'drizzle-orm';
4
4
  import '../types/drizzle.js';
5
5
  import 'drizzle-orm/node-postgres';
6
6
 
7
+ /**
8
+ * Per-entity Activity config (matches `ActivityPatternConfigSchema` in
9
+ * `src/patterns/library/activity.pattern.ts`). Carried on the concrete repo as
10
+ * `patternConfig` and read here to resolve column names at runtime.
11
+ */
12
+ interface ActivityPatternConfig {
13
+ /** Subject entity name → derives the FK column `<subject>_id`. */
14
+ subject?: string;
15
+ /** Explicit snake_case FK column, when it does not follow `<subject>_id`. */
16
+ subjectColumn?: string;
17
+ /** snake_case recency-ordering column; defaults to `occurred_at`. */
18
+ occurredAt?: string;
19
+ }
7
20
  declare abstract class ActivityEntityRepository<TEntity> extends BaseRepository<TEntity> {
8
21
  /**
9
- * Find activities within a date range (inclusive).
22
+ * Per-entity Activity config. The template emits this from `config:
23
+ * { Activity: {...} }`; entities that only use date-range / user scoping omit
24
+ * it (and must not call the subject finders).
25
+ */
26
+ protected readonly patternConfig?: ActivityPatternConfig;
27
+ /**
28
+ * camelCase key for the recency-ordering column. Defaults to `occurredAt`
29
+ * (column `occurred_at`); override via `patternConfig.occurredAt`.
30
+ */
31
+ protected get occurredAtColumn(): string;
32
+ /**
33
+ * camelCase key for the subject FK column, resolved from `patternConfig`:
34
+ * `subjectColumn` (explicit) → `<subject>_id` (derived). Throws when neither
35
+ * is configured — the subject finders are unusable without it, and a clear
36
+ * error beats a silent `undefined` column index.
37
+ */
38
+ protected get subjectColumn(): string;
39
+ /**
40
+ * Find activities within a date range (inclusive), by the recency column.
10
41
  */
11
42
  findByDateRange(start: Date, end: Date): Promise<TEntity[]>;
12
43
  /**
13
- * Find all activities for a specific user.
44
+ * Find all activities for a specific user (actor / owner scoping).
14
45
  */
15
46
  findByUserId(userId: string): Promise<TEntity[]>;
16
47
  /**
17
- * Find all activities for a specific opportunity.
48
+ * Find all activities for a specific subject (config-driven FK column).
18
49
  */
19
- findByOpportunityId(opportunityId: string): Promise<TEntity[]>;
50
+ findBySubjectId(subjectId: string): Promise<TEntity[]>;
20
51
  /**
21
- * Find the most recent activities for an opportunity, ordered by occurredAt desc.
52
+ * Find the most recent activities for a subject, ordered by the recency
53
+ * column descending.
22
54
  */
23
- findRecentByOpportunityId(opportunityId: string, limit?: number): Promise<TEntity[]>;
55
+ findRecentBySubjectId(subjectId: string, limit?: number): Promise<TEntity[]>;
24
56
  }
25
57
 
26
- export { ActivityEntityRepository };
58
+ export { ActivityEntityRepository, type ActivityPatternConfig };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ActivityEntityRepository
3
- } from "../../chunk-XCEI7NUH.js";
3
+ } from "../../chunk-MKWQKKK7.js";
4
4
  import "../../chunk-J6KZS54B.js";
5
5
  import "../../chunk-ZUKFQL6E.js";
6
6
  import "../../chunk-2E224ZSN.js";
@@ -6,16 +6,18 @@ import 'drizzle-orm/node-postgres';
6
6
  /**
7
7
  * ActivityEntityService<TRepo, TEntity>
8
8
  *
9
- * Family-specific base service for activity entities.
10
- * Delegates to an activity repository that provides date-range,
11
- * user, and opportunity queries.
9
+ * Family-specific base service for activity / interaction entities. Delegates
10
+ * to an activity repository that provides date-range, actor (`user_id`), and
11
+ * config-driven subject queries. The subject FK column is resolved inside the
12
+ * repository from its `patternConfig` (ADR-031 §4) — the service is
13
+ * subject-name-agnostic. See ACTIVITY-SUBJECT-1.
12
14
  */
13
15
 
14
16
  interface IActivityEntityRepository<TEntity> extends IBaseRepository<TEntity> {
15
17
  findByDateRange(start: Date, end: Date): Promise<TEntity[]>;
16
18
  findByUserId(userId: string): Promise<TEntity[]>;
17
- findByOpportunityId(opportunityId: string): Promise<TEntity[]>;
18
- findRecentByOpportunityId(opportunityId: string, limit?: number): Promise<TEntity[]>;
19
+ findBySubjectId(subjectId: string): Promise<TEntity[]>;
20
+ findRecentBySubjectId(subjectId: string, limit?: number): Promise<TEntity[]>;
19
21
  }
20
22
  declare abstract class ActivityEntityService<TRepo extends IActivityEntityRepository<TEntity>, TEntity> extends BaseService<TRepo, TEntity> {
21
23
  /**
@@ -23,17 +25,17 @@ declare abstract class ActivityEntityService<TRepo extends IActivityEntityReposi
23
25
  */
24
26
  findByDateRange(start: Date, end: Date): Promise<TEntity[]>;
25
27
  /**
26
- * Find all activities for a specific user.
28
+ * Find all activities for a specific user (actor / owner scoping).
27
29
  */
28
30
  findByUser(userId: string): Promise<TEntity[]>;
29
31
  /**
30
- * Find all activities for a specific opportunity.
32
+ * Find all activities for a specific subject (config-driven FK column).
31
33
  */
32
- findByOpportunity(opportunityId: string): Promise<TEntity[]>;
34
+ findBySubject(subjectId: string): Promise<TEntity[]>;
33
35
  /**
34
- * Find the most recent activities for an opportunity.
36
+ * Find the most recent activities for a subject.
35
37
  */
36
- findRecent(opportunityId: string, limit?: number): Promise<TEntity[]>;
38
+ findRecent(subjectId: string, limit?: number): Promise<TEntity[]>;
37
39
  }
38
40
 
39
41
  export { ActivityEntityService, type IActivityEntityRepository };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ActivityEntityService
3
- } from "../../chunk-BOPZWRJK.js";
3
+ } from "../../chunk-JYBFPNBJ.js";
4
4
  import "../../chunk-W72PRNJY.js";
5
5
  import "../../chunk-KYR3B3OW.js";
6
6
  import "../../chunk-2E224ZSN.js";