@pattern-stack/codegen 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/consumer-skills/entities/families-and-queries.md +5 -3
  3. package/consumer-skills/integration/change-sources-and-sinks.md +1 -1
  4. package/dist/{chunk-3RWMQC3K.js → chunk-3MAZ4TQH.js} +12 -12
  5. package/dist/{chunk-VNBC3VXM.js → chunk-3VEVGL74.js} +4 -4
  6. package/dist/{chunk-WWGYCIJX.js → chunk-43SBT72G.js} +2 -2
  7. package/dist/{chunk-Y7GDG744.js → chunk-4GLNY5V6.js} +5 -5
  8. package/dist/{chunk-BK5ICA2F.js → chunk-4MVGAMUA.js} +4 -4
  9. package/dist/{chunk-3NMCDN7L.js → chunk-5TK7MEN4.js} +2 -2
  10. package/dist/chunk-5TK7MEN4.js.map +1 -0
  11. package/dist/{chunk-T6SCOJF4.js → chunk-7LKAMLV4.js} +4 -4
  12. package/dist/{chunk-BHZP6LOV.js → chunk-CDLWYZVQ.js} +7 -7
  13. package/dist/{chunk-DKKFTHHI.js → chunk-CZQUOIDY.js} +4 -4
  14. package/dist/{chunk-XWBK3XJK.js → chunk-DCCZB4UC.js} +4 -4
  15. package/dist/{chunk-EBKVKN75.js → chunk-DTXH24LR.js} +2 -2
  16. package/dist/{chunk-RUYLXR5F.js → chunk-GJDEPTPY.js} +10 -10
  17. package/dist/{chunk-32DOFN3T.js → chunk-IOQMMH6C.js} +17 -7
  18. package/dist/{chunk-32DOFN3T.js.map → chunk-IOQMMH6C.js.map} +1 -1
  19. package/dist/{chunk-BOPZWRJK.js → chunk-JYBFPNBJ.js} +8 -8
  20. package/dist/chunk-JYBFPNBJ.js.map +1 -0
  21. package/dist/{chunk-KSTZIULO.js → chunk-K2I6XIK5.js} +4 -4
  22. package/dist/{chunk-CEWLVVAH.js → chunk-L3VJ47BU.js} +5 -5
  23. package/dist/chunk-MKWQKKK7.js +72 -0
  24. package/dist/chunk-MKWQKKK7.js.map +1 -0
  25. package/dist/{chunk-DRCLNYH7.js → chunk-NXNVTXKG.js} +4 -4
  26. package/dist/{chunk-TDEHU73T.js → chunk-OGIZXGPY.js} +4 -4
  27. package/dist/{chunk-XDIIVIIK.js → chunk-OITTYGJS.js} +4 -4
  28. package/dist/{chunk-24WXSC3C.js → chunk-P3AYBRP6.js} +7 -7
  29. package/dist/{chunk-EJBK7I4F.js → chunk-RHYNACZS.js} +3 -3
  30. package/dist/{chunk-YK5JEVLX.js → chunk-SR7F3TJY.js} +4 -4
  31. package/dist/{chunk-YLPAPPLW.js → chunk-TIZXQU26.js} +36 -9
  32. package/dist/chunk-TIZXQU26.js.map +1 -0
  33. package/dist/{chunk-4PFF3ED4.js → chunk-UTNWFHJF.js} +4 -4
  34. package/dist/{chunk-LQ6PYFU6.js → chunk-Z7PQCAVK.js} +4 -4
  35. package/dist/runtime/base-classes/activity-entity-repository.d.ts +39 -7
  36. package/dist/runtime/base-classes/activity-entity-repository.js +1 -1
  37. package/dist/runtime/base-classes/activity-entity-service.d.ts +12 -10
  38. package/dist/runtime/base-classes/activity-entity-service.js +1 -1
  39. package/dist/runtime/base-classes/index.js +23 -23
  40. package/dist/runtime/shared/openapi/index.js +7 -7
  41. package/dist/runtime/shared/openapi/registry.js +2 -2
  42. package/dist/runtime/subsystems/analytics/analytics.module.js +2 -2
  43. package/dist/runtime/subsystems/analytics/index.js +4 -4
  44. package/dist/runtime/subsystems/auth/auth.module.js +3 -3
  45. package/dist/runtime/subsystems/auth/index.js +7 -7
  46. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +2 -2
  47. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +2 -2
  48. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +4 -4
  49. package/dist/runtime/subsystems/bridge/bridge.module.js +15 -15
  50. package/dist/runtime/subsystems/bridge/index.js +17 -17
  51. package/dist/runtime/subsystems/cache/cache.module.js +1 -1
  52. package/dist/runtime/subsystems/cache/index.js +3 -3
  53. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +2 -2
  54. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +2 -2
  55. package/dist/runtime/subsystems/events/events.module.js +4 -4
  56. package/dist/runtime/subsystems/events/index.js +4 -4
  57. package/dist/runtime/subsystems/index.js +107 -107
  58. package/dist/runtime/subsystems/integration/build-change-source.js +2 -2
  59. package/dist/runtime/subsystems/integration/detection-config.schema.d.ts +23 -15
  60. package/dist/runtime/subsystems/integration/detection-config.schema.js +1 -1
  61. package/dist/runtime/subsystems/integration/execute-integration.use-case.js +2 -2
  62. package/dist/runtime/subsystems/integration/index.js +43 -43
  63. package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js +2 -2
  64. package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js +2 -2
  65. package/dist/runtime/subsystems/integration/integration.module.js +5 -5
  66. package/dist/runtime/subsystems/integration/webhook-change-source.d.ts +36 -6
  67. package/dist/runtime/subsystems/integration/webhook-change-source.js +1 -1
  68. package/dist/runtime/subsystems/jobs/index.js +30 -30
  69. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +4 -4
  70. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +1 -1
  71. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +2 -2
  72. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +2 -2
  73. package/dist/runtime/subsystems/jobs/job-worker.js +2 -2
  74. package/dist/runtime/subsystems/jobs/job-worker.module.js +10 -10
  75. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +8 -8
  76. package/dist/runtime/subsystems/storage/index.js +4 -4
  77. package/dist/runtime/subsystems/storage/storage.module.js +2 -2
  78. package/dist/src/cli/index.js +15 -15
  79. package/dist/src/index.d.ts +34 -19
  80. package/dist/src/index.js +14 -14
  81. package/package.json +2 -1
  82. package/runtime/base-classes/activity-entity-repository.ts +72 -13
  83. package/runtime/base-classes/activity-entity-service.ts +14 -12
  84. package/runtime/subsystems/integration/detection-config.schema.ts +64 -54
  85. package/runtime/subsystems/integration/webhook-change-source.ts +187 -133
  86. package/src/patterns/library/activity.pattern.ts +40 -10
  87. package/dist/chunk-3NMCDN7L.js.map +0 -1
  88. package/dist/chunk-BOPZWRJK.js.map +0 -1
  89. package/dist/chunk-XCEI7NUH.js +0 -41
  90. package/dist/chunk-XCEI7NUH.js.map +0 -1
  91. package/dist/chunk-YLPAPPLW.js.map +0 -1
  92. /package/dist/{chunk-3RWMQC3K.js.map → chunk-3MAZ4TQH.js.map} +0 -0
  93. /package/dist/{chunk-VNBC3VXM.js.map → chunk-3VEVGL74.js.map} +0 -0
  94. /package/dist/{chunk-WWGYCIJX.js.map → chunk-43SBT72G.js.map} +0 -0
  95. /package/dist/{chunk-Y7GDG744.js.map → chunk-4GLNY5V6.js.map} +0 -0
  96. /package/dist/{chunk-BK5ICA2F.js.map → chunk-4MVGAMUA.js.map} +0 -0
  97. /package/dist/{chunk-T6SCOJF4.js.map → chunk-7LKAMLV4.js.map} +0 -0
  98. /package/dist/{chunk-BHZP6LOV.js.map → chunk-CDLWYZVQ.js.map} +0 -0
  99. /package/dist/{chunk-DKKFTHHI.js.map → chunk-CZQUOIDY.js.map} +0 -0
  100. /package/dist/{chunk-XWBK3XJK.js.map → chunk-DCCZB4UC.js.map} +0 -0
  101. /package/dist/{chunk-EBKVKN75.js.map → chunk-DTXH24LR.js.map} +0 -0
  102. /package/dist/{chunk-RUYLXR5F.js.map → chunk-GJDEPTPY.js.map} +0 -0
  103. /package/dist/{chunk-KSTZIULO.js.map → chunk-K2I6XIK5.js.map} +0 -0
  104. /package/dist/{chunk-CEWLVVAH.js.map → chunk-L3VJ47BU.js.map} +0 -0
  105. /package/dist/{chunk-DRCLNYH7.js.map → chunk-NXNVTXKG.js.map} +0 -0
  106. /package/dist/{chunk-TDEHU73T.js.map → chunk-OGIZXGPY.js.map} +0 -0
  107. /package/dist/{chunk-XDIIVIIK.js.map → chunk-OITTYGJS.js.map} +0 -0
  108. /package/dist/{chunk-24WXSC3C.js.map → chunk-P3AYBRP6.js.map} +0 -0
  109. /package/dist/{chunk-EJBK7I4F.js.map → chunk-RHYNACZS.js.map} +0 -0
  110. /package/dist/{chunk-YK5JEVLX.js.map → chunk-SR7F3TJY.js.map} +0 -0
  111. /package/dist/{chunk-4PFF3ED4.js.map → chunk-UTNWFHJF.js.map} +0 -0
  112. /package/dist/{chunk-LQ6PYFU6.js.map → chunk-Z7PQCAVK.js.map} +0 -0
@@ -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
- bridgeDelivery
3
- } from "./chunk-2TVVBC53.js";
4
1
  import {
5
2
  assertTenantId
6
3
  } from "./chunk-6DWFJNIK.js";
4
+ import {
5
+ bridgeDelivery
6
+ } from "./chunk-2TVVBC53.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-KSTZIULO.js.map
122
+ //# sourceMappingURL=chunk-K2I6XIK5.js.map
@@ -9,22 +9,22 @@ import {
9
9
  } from "./chunk-EDKJU5BO.js";
10
10
  import {
11
11
  DrizzleBridgeDeliveryRepo
12
- } from "./chunk-KSTZIULO.js";
12
+ } from "./chunk-K2I6XIK5.js";
13
13
  import {
14
14
  MemoryBridgeDeliveryRepo
15
15
  } from "./chunk-4DOJBQTP.js";
16
16
  import {
17
17
  BridgeOutboxDrainHook
18
- } from "./chunk-EBKVKN75.js";
18
+ } from "./chunk-DTXH24LR.js";
19
19
  import {
20
20
  BridgeDeliveryHandler
21
- } from "./chunk-DRCLNYH7.js";
21
+ } from "./chunk-NXNVTXKG.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-RUYLXR5F.js";
27
+ } from "./chunk-GJDEPTPY.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-CEWLVVAH.js.map
122
+ //# sourceMappingURL=chunk-L3VJ47BU.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":[]}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  assertTenantId
3
3
  } from "./chunk-6DWFJNIK.js";
4
- import {
5
- JOB_ORCHESTRATOR
6
- } from "./chunk-ZPL74UQN.js";
7
4
  import {
8
5
  JobHandler,
9
6
  JobHandlerBase
10
7
  } from "./chunk-CO6LUM72.js";
8
+ import {
9
+ JOB_ORCHESTRATOR
10
+ } from "./chunk-ZPL74UQN.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-DRCLNYH7.js.map
121
+ //# sourceMappingURL=chunk-NXNVTXKG.js.map
@@ -1,3 +1,6 @@
1
+ import {
2
+ assertTenantId
3
+ } from "./chunk-MZ6GV4YF.js";
1
4
  import {
2
5
  INTEGRATION_CHANGE_SOURCE,
3
6
  INTEGRATION_CURSOR_STORE,
@@ -6,9 +9,6 @@ import {
6
9
  INTEGRATION_RUN_RECORDER,
7
10
  INTEGRATION_SINK
8
11
  } from "./chunk-S7C6TIIF.js";
9
- import {
10
- assertTenantId
11
- } from "./chunk-MZ6GV4YF.js";
12
12
  import {
13
13
  __decorateClass,
14
14
  __decorateParam
@@ -219,4 +219,4 @@ ExecuteIntegrationUseCase = __decorateClass([
219
219
  export {
220
220
  ExecuteIntegrationUseCase
221
221
  };
222
- //# sourceMappingURL=chunk-TDEHU73T.js.map
222
+ //# sourceMappingURL=chunk-OGIZXGPY.js.map
@@ -1,11 +1,11 @@
1
+ import {
2
+ JOB_HANDLER_REGISTRY
3
+ } from "./chunk-CO6LUM72.js";
1
4
  import {
2
5
  JOB_ORCHESTRATOR,
3
6
  JOB_RUN_SERVICE,
4
7
  JOB_STEP_SERVICE
5
8
  } from "./chunk-ZPL74UQN.js";
6
- import {
7
- JOB_HANDLER_REGISTRY
8
- } from "./chunk-CO6LUM72.js";
9
9
  import {
10
10
  jobRuns
11
11
  } from "./chunk-OKXZ63IA.js";
@@ -518,4 +518,4 @@ export {
518
518
  buildStaleSweepQuery,
519
519
  JobWorker
520
520
  };
521
- //# sourceMappingURL=chunk-XDIIVIIK.js.map
521
+ //# sourceMappingURL=chunk-OITTYGJS.js.map
@@ -1,15 +1,18 @@
1
+ import {
2
+ DrizzleIntegrationRunRecorder
3
+ } from "./chunk-SR7F3TJY.js";
1
4
  import {
2
5
  MemoryRunRecorder
3
6
  } from "./chunk-EO2QPOKH.js";
4
7
  import {
5
8
  PostgresCursorStore
6
- } from "./chunk-XWBK3XJK.js";
9
+ } from "./chunk-DCCZB4UC.js";
7
10
  import {
8
11
  MemoryCursorStore
9
12
  } from "./chunk-AHV4GDYM.js";
10
13
  import {
11
- DrizzleIntegrationRunRecorder
12
- } from "./chunk-YK5JEVLX.js";
14
+ DeepEqualDiffer
15
+ } from "./chunk-36U5UGIO.js";
13
16
  import {
14
17
  INTEGRATION_CURSOR_STORE,
15
18
  INTEGRATION_FIELD_DIFFER,
@@ -17,9 +20,6 @@ import {
17
20
  INTEGRATION_MULTI_TENANT,
18
21
  INTEGRATION_RUN_RECORDER
19
22
  } from "./chunk-S7C6TIIF.js";
20
- import {
21
- DeepEqualDiffer
22
- } from "./chunk-36U5UGIO.js";
23
23
  import {
24
24
  __decorateClass
25
25
  } from "./chunk-2E224ZSN.js";
@@ -78,4 +78,4 @@ IntegrationModule = __decorateClass([
78
78
  export {
79
79
  IntegrationModule
80
80
  };
81
- //# sourceMappingURL=chunk-24WXSC3C.js.map
81
+ //# sourceMappingURL=chunk-P3AYBRP6.js.map
@@ -3,10 +3,10 @@ import {
3
3
  } from "./chunk-GM3RMJIJ.js";
4
4
  import {
5
5
  DrizzleEventBus
6
- } from "./chunk-4PFF3ED4.js";
6
+ } from "./chunk-UTNWFHJF.js";
7
7
  import {
8
8
  MemoryEventBus
9
- } from "./chunk-LQ6PYFU6.js";
9
+ } from "./chunk-Z7PQCAVK.js";
10
10
  import {
11
11
  EVENTS_MODULE_OPTIONS,
12
12
  EVENTS_MULTI_TENANT,
@@ -152,4 +152,4 @@ EventsModule = __decorateClass([
152
152
  export {
153
153
  EventsModule
154
154
  };
155
- //# sourceMappingURL=chunk-EJBK7I4F.js.map
155
+ //# sourceMappingURL=chunk-RHYNACZS.js.map
@@ -1,9 +1,6 @@
1
1
  import {
2
2
  FieldDiffSchema
3
3
  } from "./chunk-SQDOBLBP.js";
4
- import {
5
- INTEGRATION_MULTI_TENANT
6
- } from "./chunk-S7C6TIIF.js";
7
4
  import {
8
5
  assertTenantId
9
6
  } from "./chunk-MZ6GV4YF.js";
@@ -12,6 +9,9 @@ import {
12
9
  integrationRuns,
13
10
  integrationSubscriptions
14
11
  } from "./chunk-HNWZFNKP.js";
12
+ import {
13
+ INTEGRATION_MULTI_TENANT
14
+ } from "./chunk-S7C6TIIF.js";
15
15
  import {
16
16
  DRIZZLE
17
17
  } from "./chunk-U64T4YZE.js";
@@ -127,4 +127,4 @@ DrizzleIntegrationRunRecorder = __decorateClass([
127
127
  export {
128
128
  DrizzleIntegrationRunRecorder
129
129
  };
130
- //# sourceMappingURL=chunk-YK5JEVLX.js.map
130
+ //# sourceMappingURL=chunk-SR7F3TJY.js.map
@@ -3,6 +3,11 @@ var WebhookChangeSource = class {
3
3
  label;
4
4
  queue;
5
5
  externalIdSourceField;
6
+ /**
7
+ * Record field carrying the event id, when `webhook.eventIdField` is
8
+ * configured. Used only as the fallback when the queue iterator does NOT
9
+ * yield an `eventId` — see {@link WebhookFetchCallback} for the precedence.
10
+ */
6
11
  eventIdSourceField;
7
12
  composed;
8
13
  constructor(opts) {
@@ -39,19 +44,18 @@ var WebhookChangeSource = class {
39
44
  subscription,
40
45
  cursor
41
46
  };
42
- for await (const { record, cursor: nextCursor } of this.queue(ctx)) {
47
+ for await (const {
48
+ record,
49
+ eventId: yieldedEventId,
50
+ cursor: nextCursor
51
+ } of this.queue(ctx)) {
43
52
  const externalIdRaw = record[this.externalIdSourceField];
44
53
  if (typeof externalIdRaw !== "string" || externalIdRaw.length === 0) {
45
54
  throw new Error(
46
55
  `WebhookChangeSource: record missing string '${this.externalIdSourceField}' \u2014 emitted records MUST carry the canonical external id keyed by the mapping source`
47
56
  );
48
57
  }
49
- const eventIdRaw = record[this.eventIdSourceField];
50
- if (typeof eventIdRaw !== "string" || eventIdRaw.length === 0) {
51
- throw new Error(
52
- `WebhookChangeSource: record missing string '${this.eventIdSourceField}' \u2014 webhook records MUST carry the event id (DetectionConfig.webhook.eventIdField) so Change<T>.dedupKey can be populated`
53
- );
54
- }
58
+ const dedupKey = this.deriveDedupKey(yieldedEventId, record);
55
59
  const change = {
56
60
  externalId: externalIdRaw,
57
61
  // Webhook mode cannot distinguish create vs. update vs. delete on
@@ -62,14 +66,37 @@ var WebhookChangeSource = class {
62
66
  record,
63
67
  cursor: nextCursor ?? null,
64
68
  source: "webhook",
65
- dedupKey: eventIdRaw
69
+ dedupKey
66
70
  };
67
71
  yield change;
68
72
  }
69
73
  }
74
+ /**
75
+ * Resolve `Change<T>.dedupKey` with the precedence: yielded `eventId` >
76
+ * `webhook.eventIdField` record extraction > `undefined`. A non-empty
77
+ * yielded `eventId` always wins; otherwise the configured field is read off
78
+ * the record (and must be a non-empty string when the field is configured);
79
+ * with neither, `dedupKey` is `undefined` (the orchestrator then has no
80
+ * delivery-level dedup signal for this change).
81
+ */
82
+ deriveDedupKey(yieldedEventId, record) {
83
+ if (yieldedEventId !== void 0 && yieldedEventId.length > 0) {
84
+ return yieldedEventId;
85
+ }
86
+ if (this.eventIdSourceField === void 0) {
87
+ return void 0;
88
+ }
89
+ const eventIdRaw = record[this.eventIdSourceField];
90
+ if (typeof eventIdRaw !== "string" || eventIdRaw.length === 0) {
91
+ throw new Error(
92
+ `WebhookChangeSource: record missing string '${this.eventIdSourceField}' \u2014 a webhook record MUST carry the event id (DetectionConfig.webhook.eventIdField) so Change<T>.dedupKey can be populated, unless the queue iterator yields an 'eventId' alongside the record`
93
+ );
94
+ }
95
+ return eventIdRaw;
96
+ }
70
97
  };
71
98
 
72
99
  export {
73
100
  WebhookChangeSource
74
101
  };
75
- //# sourceMappingURL=chunk-YLPAPPLW.js.map
102
+ //# sourceMappingURL=chunk-TIZXQU26.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../runtime/subsystems/integration/webhook-change-source.ts"],"sourcesContent":["/**\n * Integration subsystem — `WebhookChangeSource<T>` primitive (#226-4, ADR-033).\n *\n * Generic webhook-mode `IChangeSource<T>` implementation parameterized by a\n * parsed `DetectionConfig` (webhook mode) and a consumer-supplied\n * `WebhookFetchCallback<T>` that iterates a consumer-owned inbound staging\n * queue. The primitive owns:\n *\n * - canonical `Change<T>.source = 'webhook'` stamping;\n * - `dedupKey` derivation, preferring the `eventId` yielded alongside the\n * record by the queue iterator, and falling back to the configured\n * `webhook.eventIdField` on the emitted record when no `eventId` is yielded\n * (precedence: yielded `eventId` > `eventIdField` record extraction >\n * undefined `dedupKey`);\n * - `externalId` derivation: the mapping entry whose `target === 'external_id'`\n * names — via its `source` — the field on the emitted record that carries\n * the canonical external id (mirrors `PollChangeSource`);\n * - middleware-chain composition via the locked `ChangeMiddleware<T>` shape\n * (#226-1) — same composition seam as the poll primitive.\n *\n * The primitive is **passive**: it iterates whatever the consumer-owned\n * queue yields. It does NOT synchronously drive the orchestrator, does NOT\n * own a transport, and does NOT manage acks. The inbound staging table\n * schema is consumer-owned and deferred per ADR-0002 §Phase 4 — the\n * `WebhookFetchCallback<T>` is the queue contract the consumer injects.\n *\n * Shape locks (decision memo Q5, mirrored from poll primitive):\n * - `WebhookFetchContext = { subscription, cursor }` — explicitly NO\n * `userId` / `tenantId`. Run-scope identity is closed over by the\n * consumer at queue construction or resolved inside the callback via\n * consumer services. There are no `filters` on the webhook context —\n * filtering is done at registration / on the staging row, not at the\n * port seam (the queue is already filtered by the time the primitive\n * iterates).\n *\n * Long-lived streaming CDC primitives (SFDC Pub-Sub gRPC, Debezium/Kafka,\n * Postgres logical replication) are deferred to `#226-8` — they need a\n * fundamentally different lifecycle (`subscribe(onChange, onError)`,\n * server-paced backpressure, ack-on-yield) and shouldn't be retrofitted\n * into either this primitive or the poll primitive.\n */\n\nimport type { DetectionConfig } from \"./detection-config.schema\";\nimport type {\n\tChange,\n\tIChangeSource,\n\tIntegrationSubscriptionView,\n} from \"./integration-change-source.protocol\";\nimport type {\n\tChangeIterator,\n\tChangeMiddleware,\n} from \"./integration-middleware.protocol\";\n\n// ============================================================================\n// Cursor + queue callback shapes\n// ============================================================================\n\n/**\n * Opaque webhook cursor shape. Webhook mode typically has a cursor of\n * `{ ts: ISO-string }` (last drained staging-row timestamp) but the\n * primitive treats it as opaque. Consumer-owned queue iterators interpret\n * it however the staging schema needs.\n */\nexport type WebhookCursor = unknown;\n\n/**\n * Context the primitive forwards to the queue iterator. Locked to exactly\n * two fields per the same Q5 reasoning that locks `PollFetchContext` — no\n * `userId` / `tenantId`.\n */\nexport interface WebhookFetchContext {\n\treadonly subscription: IntegrationSubscriptionView;\n\treadonly cursor: WebhookCursor | null;\n}\n\n/**\n * Consumer-supplied queue iterator. Returns an async iterable of\n * `{ record, eventId?, cursor? }` tuples — the consumer drains the inbound\n * staging queue and emits already-mapped canonical records `T`. The primitive\n * stamps `source: 'webhook'` and derives `dedupKey` with this precedence:\n *\n * 1. the yielded `eventId` (vendor delivery metadata — the queue is the\n * right channel for it: a vendor's event id should never need a field\n * on the vendor-neutral canonical record);\n * 2. else the record field named by `webhook.eventIdField`, when configured;\n * 3. else `undefined`.\n *\n * Yielding `eventId` is the safe channel when one canonical record identity\n * (the `external_id`) can recur across distinct vendor events in a single\n * drain batch — e.g. a message create and its later edit share an\n * `external_id` but are different events. Reading dedup identity off the\n * record (`eventIdField`) collapses those into one `dedupKey`; the yielded\n * `eventId` keeps them distinct. The consumer is the one who decided when a\n * staging row is \"ready\" to drain.\n *\n * Webhook mode has no per-record cursor advance — the staging-row drain\n * order is consumer policy (FIFO by ingestion timestamp, by event id, etc.)\n * and is opaque to the primitive. The orchestrator's last-yielded cursor\n * is whatever the consumer chooses to surface, if anything.\n */\nexport type WebhookFetchCallback<T> = (\n\tctx: WebhookFetchContext,\n) => AsyncIterable<{ record: T; eventId?: string; cursor?: WebhookCursor }>;\n\n// ============================================================================\n// Constructor options\n// ============================================================================\n\nexport interface WebhookChangeSourceOptions<T> {\n\t/** Consumer-supplied inbound queue iterator. */\n\treadonly queue: WebhookFetchCallback<T>;\n\t/**\n\t * Parsed detection config. MUST be `mode: 'webhook'`; the constructor\n\t * throws if a poll config is supplied. Codegen-emitted factories call\n\t * `DetectionConfigSchema.parse(...)` upstream so this is a safety net,\n\t * not the primary validation point.\n\t */\n\treadonly config: DetectionConfig;\n\t/**\n\t * Optional middleware chain. Same shape and composition rules as\n\t * `PollChangeSource` — first element is the outermost layer.\n\t */\n\treadonly middlewares?: ReadonlyArray<ChangeMiddleware<T>>;\n\t/**\n\t * Optional human label for run logs (e.g. `'stripe-webhook-charge'`).\n\t * Defaults to a derived label based on the mapping at construction.\n\t */\n\treadonly label?: string;\n}\n\n// ============================================================================\n// WebhookChangeSource<T>\n// ============================================================================\n\nexport class WebhookChangeSource<T> implements IChangeSource<T> {\n\tpublic readonly label: string;\n\n\tprivate readonly queue: WebhookFetchCallback<T>;\n\tprivate readonly externalIdSourceField: string;\n\t/**\n\t * Record field carrying the event id, when `webhook.eventIdField` is\n\t * configured. Used only as the fallback when the queue iterator does NOT\n\t * yield an `eventId` — see {@link WebhookFetchCallback} for the precedence.\n\t */\n\tprivate readonly eventIdSourceField: string | undefined;\n\tprivate readonly composed: ChangeIterator<T>;\n\n\tconstructor(opts: WebhookChangeSourceOptions<T>) {\n\t\tif (opts.config.mode !== \"webhook\") {\n\t\t\tthrow new Error(\n\t\t\t\t`WebhookChangeSource requires DetectionConfig.mode === 'webhook'; got '${(opts.config as { mode: string }).mode}'`,\n\t\t\t);\n\t\t}\n\t\tconst config = opts.config;\n\n\t\t// Field mapping: locate the entry whose canonical `target` is `external_id`\n\t\t// — mirrors the poll primitive's contract. Adapters emit records\n\t\t// already-mapped; the primitive needs to know which key on T carries the\n\t\t// external id so it can stamp `Change.externalId`. That key is the\n\t\t// mapping's `source` (the field on the emitted record), NOT its `target`\n\t\t// (the canonical column) — they differ whenever the canonical record is\n\t\t// vendor-neutral camelCase (e.g. `source: 'externalId'` → `target: 'external_id'`).\n\t\tconst externalIdMapping = config.mapping.find(\n\t\t\t(m) => m.target === \"external_id\",\n\t\t);\n\t\tif (!externalIdMapping) {\n\t\t\tthrow new Error(\n\t\t\t\t\"WebhookChangeSource: DetectionConfig.mapping must include an entry with target 'external_id' so emitted Change<T>.externalId can be populated\",\n\t\t\t);\n\t\t}\n\t\tthis.externalIdSourceField = externalIdMapping.source;\n\t\tthis.eventIdSourceField = config.webhook.eventIdField;\n\t\t// `eventIdField` is optional (a callback that always yields `eventId` need\n\t\t// not declare one); `undefined` here just disables the fallback extraction.\n\n\t\tthis.queue = opts.queue;\n\n\t\tthis.label =\n\t\t\topts.label ?? `webhook-change-source:${externalIdMapping.source}`;\n\n\t\t// Compose middleware chain — same shape as PollChangeSource.\n\t\tconst inner: ChangeIterator<T> = (sub, cur) => this.fetch(sub, cur);\n\t\tconst middlewares = opts.middlewares ?? [];\n\t\tthis.composed = middlewares.reduceRight<ChangeIterator<T>>(\n\t\t\t(next, mw) => mw(next),\n\t\t\tinner,\n\t\t);\n\t}\n\n\tlistChanges(\n\t\tsubscription: IntegrationSubscriptionView,\n\t\tcursor: unknown | null,\n\t): AsyncIterable<Change<T>> {\n\t\treturn this.composed(subscription, cursor);\n\t}\n\n\tprivate async *fetch(\n\t\tsubscription: IntegrationSubscriptionView,\n\t\tcursor: unknown | null,\n\t): AsyncIterable<Change<T>> {\n\t\tconst ctx: WebhookFetchContext = {\n\t\t\tsubscription,\n\t\t\tcursor: cursor as WebhookCursor | null,\n\t\t};\n\n\t\tfor await (const {\n\t\t\trecord,\n\t\t\teventId: yieldedEventId,\n\t\t\tcursor: nextCursor,\n\t\t} of this.queue(ctx)) {\n\t\t\tconst externalIdRaw = (record as Record<string, unknown>)[\n\t\t\t\tthis.externalIdSourceField\n\t\t\t];\n\t\t\tif (typeof externalIdRaw !== \"string\" || externalIdRaw.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`WebhookChangeSource: record missing string '${this.externalIdSourceField}' — emitted records MUST carry the canonical external id keyed by the mapping source`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// dedupKey precedence: yielded `eventId` > `eventIdField` record\n\t\t\t// extraction > undefined. The yielded id is vendor delivery metadata\n\t\t\t// (the right channel for it), and keeps distinct vendor events for the\n\t\t\t// same `external_id` (e.g. a message and its edit) from collapsing to\n\t\t\t// one dedupKey — which a record-field extraction would do.\n\t\t\tconst dedupKey = this.deriveDedupKey(yieldedEventId, record);\n\n\t\t\tconst change: Change<T> = {\n\t\t\t\texternalId: externalIdRaw,\n\t\t\t\t// Webhook mode cannot distinguish create vs. update vs. delete on\n\t\t\t\t// its own — the orchestrator's diff stage handles classification.\n\t\t\t\t// Tombstone / soft-delete detection is consumer-driven (same as\n\t\t\t\t// poll mode — see ADR-033).\n\t\t\t\toperation: \"updated\",\n\t\t\t\trecord,\n\t\t\t\tcursor: nextCursor ?? null,\n\t\t\t\tsource: \"webhook\",\n\t\t\t\tdedupKey,\n\t\t\t};\n\t\t\tyield change;\n\t\t}\n\t}\n\n\t/**\n\t * Resolve `Change<T>.dedupKey` with the precedence: yielded `eventId` >\n\t * `webhook.eventIdField` record extraction > `undefined`. A non-empty\n\t * yielded `eventId` always wins; otherwise the configured field is read off\n\t * the record (and must be a non-empty string when the field is configured);\n\t * with neither, `dedupKey` is `undefined` (the orchestrator then has no\n\t * delivery-level dedup signal for this change).\n\t */\n\tprivate deriveDedupKey(\n\t\tyieldedEventId: string | undefined,\n\t\trecord: T,\n\t): string | undefined {\n\t\tif (yieldedEventId !== undefined && yieldedEventId.length > 0) {\n\t\t\treturn yieldedEventId;\n\t\t}\n\t\tif (this.eventIdSourceField === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst eventIdRaw = (record as Record<string, unknown>)[\n\t\t\tthis.eventIdSourceField\n\t\t];\n\t\tif (typeof eventIdRaw !== \"string\" || eventIdRaw.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`WebhookChangeSource: record missing string '${this.eventIdSourceField}' — a webhook record MUST carry the event id (DetectionConfig.webhook.eventIdField) so Change<T>.dedupKey can be populated, unless the queue iterator yields an 'eventId' alongside the record`,\n\t\t\t);\n\t\t}\n\t\treturn eventIdRaw;\n\t}\n}\n"],"mappings":";AAsIO,IAAM,sBAAN,MAAyD;AAAA,EAC/C;AAAA,EAEC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAqC;AAChD,QAAI,KAAK,OAAO,SAAS,WAAW;AACnC,YAAM,IAAI;AAAA,QACT,yEAA0E,KAAK,OAA4B,IAAI;AAAA,MAChH;AAAA,IACD;AACA,UAAM,SAAS,KAAK;AASpB,UAAM,oBAAoB,OAAO,QAAQ;AAAA,MACxC,CAAC,MAAM,EAAE,WAAW;AAAA,IACrB;AACA,QAAI,CAAC,mBAAmB;AACvB,YAAM,IAAI;AAAA,QACT;AAAA,MACD;AAAA,IACD;AACA,SAAK,wBAAwB,kBAAkB;AAC/C,SAAK,qBAAqB,OAAO,QAAQ;AAIzC,SAAK,QAAQ,KAAK;AAElB,SAAK,QACJ,KAAK,SAAS,yBAAyB,kBAAkB,MAAM;AAGhE,UAAM,QAA2B,CAAC,KAAK,QAAQ,KAAK,MAAM,KAAK,GAAG;AAClE,UAAM,cAAc,KAAK,eAAe,CAAC;AACzC,SAAK,WAAW,YAAY;AAAA,MAC3B,CAAC,MAAM,OAAO,GAAG,IAAI;AAAA,MACrB;AAAA,IACD;AAAA,EACD;AAAA,EAEA,YACC,cACA,QAC2B;AAC3B,WAAO,KAAK,SAAS,cAAc,MAAM;AAAA,EAC1C;AAAA,EAEA,OAAe,MACd,cACA,QAC2B;AAC3B,UAAM,MAA2B;AAAA,MAChC;AAAA,MACA;AAAA,IACD;AAEA,qBAAiB;AAAA,MAChB;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,IACT,KAAK,KAAK,MAAM,GAAG,GAAG;AACrB,YAAM,gBAAiB,OACtB,KAAK,qBACN;AACA,UAAI,OAAO,kBAAkB,YAAY,cAAc,WAAW,GAAG;AACpE,cAAM,IAAI;AAAA,UACT,+CAA+C,KAAK,qBAAqB;AAAA,QAC1E;AAAA,MACD;AAOA,YAAM,WAAW,KAAK,eAAe,gBAAgB,MAAM;AAE3D,YAAM,SAAoB;AAAA,QACzB,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA,QAKZ,WAAW;AAAA,QACX;AAAA,QACA,QAAQ,cAAc;AAAA,QACtB,QAAQ;AAAA,QACR;AAAA,MACD;AACA,YAAM;AAAA,IACP;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,eACP,gBACA,QACqB;AACrB,QAAI,mBAAmB,UAAa,eAAe,SAAS,GAAG;AAC9D,aAAO;AAAA,IACR;AACA,QAAI,KAAK,uBAAuB,QAAW;AAC1C,aAAO;AAAA,IACR;AACA,UAAM,aAAc,OACnB,KAAK,kBACN;AACA,QAAI,OAAO,eAAe,YAAY,WAAW,WAAW,GAAG;AAC9D,YAAM,IAAI;AAAA,QACT,+CAA+C,KAAK,kBAAkB;AAAA,MACvE;AAAA,IACD;AACA,WAAO;AAAA,EACR;AACD;","names":[]}
@@ -9,14 +9,14 @@ import {
9
9
  import {
10
10
  domainEvents
11
11
  } from "./chunk-OFRRBC7M.js";
12
- import {
13
- EVENTS_MODULE_OPTIONS
14
- } from "./chunk-H5NH7KPE.js";
15
12
  import {
16
13
  clampEventLimit,
17
14
  decodeEventCursor,
18
15
  encodeEventCursor
19
16
  } from "./chunk-UQ5EHOH2.js";
17
+ import {
18
+ EVENTS_MODULE_OPTIONS
19
+ } from "./chunk-H5NH7KPE.js";
20
20
  import {
21
21
  DRIZZLE
22
22
  } from "./chunk-U64T4YZE.js";
@@ -393,4 +393,4 @@ DrizzleEventBus = __decorateClass([
393
393
  export {
394
394
  DrizzleEventBus
395
395
  };
396
- //# sourceMappingURL=chunk-4PFF3ED4.js.map
396
+ //# sourceMappingURL=chunk-UTNWFHJF.js.map
@@ -1,11 +1,11 @@
1
- import {
2
- EVENTS_MODULE_OPTIONS
3
- } from "./chunk-H5NH7KPE.js";
4
1
  import {
5
2
  clampEventLimit,
6
3
  decodeEventCursor,
7
4
  encodeEventCursor
8
5
  } from "./chunk-UQ5EHOH2.js";
6
+ import {
7
+ EVENTS_MODULE_OPTIONS
8
+ } from "./chunk-H5NH7KPE.js";
9
9
  import {
10
10
  __decorateClass,
11
11
  __decorateParam
@@ -197,4 +197,4 @@ MemoryEventBus = __decorateClass([
197
197
  export {
198
198
  MemoryEventBus
199
199
  };
200
- //# sourceMappingURL=chunk-LQ6PYFU6.js.map
200
+ //# sourceMappingURL=chunk-Z7PQCAVK.js.map
@@ -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";