@pattern-stack/codegen 0.23.0 → 0.24.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.
- package/CHANGELOG.md +23 -1
- package/consumer-skills/integration/SKILL.md +11 -3
- package/dist/{chunk-XKWOJZZ4.js → chunk-37PILMIT.js} +4 -4
- package/dist/{chunk-42763UEE.js → chunk-6M6LZEP6.js} +2 -2
- package/dist/{chunk-FIUC6QB5.js → chunk-CKLM57IE.js} +10 -10
- package/dist/{chunk-SH76CFAY.js → chunk-ENAR3F5S.js} +2 -2
- package/dist/{chunk-FFUDEIFF.js → chunk-HN5HT5WL.js} +2 -2
- package/dist/{chunk-4M66MQYA.js → chunk-K4BQQ2NN.js} +4 -2
- package/dist/chunk-K4BQQ2NN.js.map +1 -0
- package/dist/{chunk-QFUIE37H.js → chunk-KFXXOFDC.js} +4 -4
- package/dist/{chunk-O2A6XHGD.js → chunk-LLDJS7PJ.js} +2 -2
- package/dist/{chunk-JOBQ6RUU.js → chunk-LQZESSM3.js} +28 -1
- package/dist/chunk-LQZESSM3.js.map +1 -0
- package/dist/{chunk-JRQO2IOF.js → chunk-MU54DZCC.js} +27 -1
- package/dist/chunk-MU54DZCC.js.map +1 -0
- package/dist/{chunk-INO47JXD.js → chunk-PBENHIN2.js} +3 -3
- package/dist/{chunk-CLWBNXKF.js → chunk-PLUJEQLU.js} +2 -2
- package/dist/{chunk-S7C6TIIF.js → chunk-S5G3HO7N.js} +3 -1
- package/dist/chunk-S5G3HO7N.js.map +1 -0
- package/dist/{chunk-6XP2Q5SS.js → chunk-WZOPWQN2.js} +2 -2
- package/dist/{chunk-TDEHU73T.js → chunk-YIVQ7KLS.js} +46 -5
- package/dist/chunk-YIVQ7KLS.js.map +1 -0
- package/dist/runtime/base-classes/index.js +17 -17
- package/dist/runtime/subsystems/auth/auth.module.js +1 -1
- package/dist/runtime/subsystems/auth/index.js +3 -3
- package/dist/runtime/subsystems/bridge/bridge.module.js +6 -6
- package/dist/runtime/subsystems/bridge/index.js +6 -6
- package/dist/runtime/subsystems/events/events.module.js +4 -4
- package/dist/runtime/subsystems/events/generated/bus.js +3 -3
- package/dist/runtime/subsystems/events/generated/index.d.ts +2 -2
- package/dist/runtime/subsystems/events/generated/index.js +9 -3
- package/dist/runtime/subsystems/events/generated/registry.d.ts +36 -0
- package/dist/runtime/subsystems/events/generated/registry.js +1 -1
- package/dist/runtime/subsystems/events/generated/schemas.d.ts +109 -1
- package/dist/runtime/subsystems/events/generated/schemas.js +7 -1
- package/dist/runtime/subsystems/events/generated/types.d.ts +48 -2
- package/dist/runtime/subsystems/events/index.js +4 -4
- package/dist/runtime/subsystems/index.d.ts +3 -2
- package/dist/runtime/subsystems/index.js +26 -22
- package/dist/runtime/subsystems/integration/execute-integration.use-case.d.ts +11 -1
- package/dist/runtime/subsystems/integration/execute-integration.use-case.js +2 -2
- package/dist/runtime/subsystems/integration/index.d.ts +2 -1
- package/dist/runtime/subsystems/integration/index.js +10 -8
- package/dist/runtime/subsystems/integration/integration-change-emitter.protocol.d.ts +106 -0
- package/dist/runtime/subsystems/integration/integration-change-emitter.protocol.js +1 -0
- package/dist/runtime/subsystems/integration/integration-change-emitter.protocol.js.map +1 -0
- package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js +2 -2
- package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js +2 -2
- package/dist/runtime/subsystems/integration/integration.module.js +4 -4
- package/dist/runtime/subsystems/integration/integration.tokens.d.ts +11 -1
- package/dist/runtime/subsystems/integration/integration.tokens.js +3 -1
- package/dist/runtime/subsystems/jobs/index.js +11 -11
- package/dist/runtime/subsystems/jobs/job-worker.module.js +5 -5
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +4 -4
- package/dist/runtime/subsystems/observability/index.js +3 -3
- package/dist/runtime/subsystems/observability/observability.module.js +3 -3
- package/dist/runtime/subsystems/observability/observability.service.js +2 -2
- package/dist/src/cli/index.js +203 -26
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +7 -7
- package/package.json +1 -1
- package/runtime/subsystems/events/generated/registry.ts +27 -0
- package/runtime/subsystems/events/generated/schemas.ts +26 -0
- package/runtime/subsystems/events/generated/types.ts +52 -0
- package/runtime/subsystems/index.ts +23 -0
- package/runtime/subsystems/integration/execute-integration.use-case.ts +69 -1
- package/runtime/subsystems/integration/index.ts +6 -0
- package/runtime/subsystems/integration/integration-change-emitter.protocol.ts +107 -0
- package/runtime/subsystems/integration/integration.tokens.ts +11 -0
- package/dist/chunk-4M66MQYA.js.map +0 -1
- package/dist/chunk-JOBQ6RUU.js.map +0 -1
- package/dist/chunk-JRQO2IOF.js.map +0 -1
- package/dist/chunk-S7C6TIIF.js.map +0 -1
- package/dist/chunk-TDEHU73T.js.map +0 -1
- /package/dist/{chunk-XKWOJZZ4.js.map → chunk-37PILMIT.js.map} +0 -0
- /package/dist/{chunk-42763UEE.js.map → chunk-6M6LZEP6.js.map} +0 -0
- /package/dist/{chunk-FIUC6QB5.js.map → chunk-CKLM57IE.js.map} +0 -0
- /package/dist/{chunk-SH76CFAY.js.map → chunk-ENAR3F5S.js.map} +0 -0
- /package/dist/{chunk-FFUDEIFF.js.map → chunk-HN5HT5WL.js.map} +0 -0
- /package/dist/{chunk-QFUIE37H.js.map → chunk-KFXXOFDC.js.map} +0 -0
- /package/dist/{chunk-O2A6XHGD.js.map → chunk-LLDJS7PJ.js.map} +0 -0
- /package/dist/{chunk-INO47JXD.js.map → chunk-PBENHIN2.js.map} +0 -0
- /package/dist/{chunk-CLWBNXKF.js.map → chunk-PLUJEQLU.js.map} +0 -0
- /package/dist/{chunk-6XP2Q5SS.js.map → chunk-WZOPWQN2.js.map} +0 -0
package/dist/src/index.d.ts
CHANGED
|
@@ -1531,12 +1531,15 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
1531
1531
|
sink: z.ZodOptional<z.ZodObject<{
|
|
1532
1532
|
delete: z.ZodOptional<z.ZodEnum<["soft", "tombstone", "noop"]>>;
|
|
1533
1533
|
exclude_fields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
1534
|
+
emit_changes: z.ZodOptional<z.ZodBoolean>;
|
|
1534
1535
|
}, "strict", z.ZodTypeAny, {
|
|
1535
1536
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
1536
1537
|
exclude_fields?: string[] | undefined;
|
|
1538
|
+
emit_changes?: boolean | undefined;
|
|
1537
1539
|
}, {
|
|
1538
1540
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
1539
1541
|
exclude_fields?: string[] | undefined;
|
|
1542
|
+
emit_changes?: boolean | undefined;
|
|
1540
1543
|
}>>;
|
|
1541
1544
|
}, "strip", z.ZodTypeAny, {
|
|
1542
1545
|
electric: boolean;
|
|
@@ -1550,6 +1553,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
1550
1553
|
sink?: {
|
|
1551
1554
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
1552
1555
|
exclude_fields?: string[] | undefined;
|
|
1556
|
+
emit_changes?: boolean | undefined;
|
|
1553
1557
|
} | undefined;
|
|
1554
1558
|
}, {
|
|
1555
1559
|
electric?: boolean | undefined;
|
|
@@ -1563,6 +1567,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
1563
1567
|
sink?: {
|
|
1564
1568
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
1565
1569
|
exclude_fields?: string[] | undefined;
|
|
1570
|
+
emit_changes?: boolean | undefined;
|
|
1566
1571
|
} | undefined;
|
|
1567
1572
|
}>>;
|
|
1568
1573
|
detection: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<"mode", [z.ZodObject<{
|
|
@@ -2208,6 +2213,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
2208
2213
|
sink?: {
|
|
2209
2214
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
2210
2215
|
exclude_fields?: string[] | undefined;
|
|
2216
|
+
emit_changes?: boolean | undefined;
|
|
2211
2217
|
} | undefined;
|
|
2212
2218
|
} | undefined;
|
|
2213
2219
|
detection?: Record<string, {
|
|
@@ -2425,6 +2431,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
2425
2431
|
sink?: {
|
|
2426
2432
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
2427
2433
|
exclude_fields?: string[] | undefined;
|
|
2434
|
+
emit_changes?: boolean | undefined;
|
|
2428
2435
|
} | undefined;
|
|
2429
2436
|
} | undefined;
|
|
2430
2437
|
detection?: Record<string, {
|
|
@@ -2642,6 +2649,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
2642
2649
|
sink?: {
|
|
2643
2650
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
2644
2651
|
exclude_fields?: string[] | undefined;
|
|
2652
|
+
emit_changes?: boolean | undefined;
|
|
2645
2653
|
} | undefined;
|
|
2646
2654
|
} | undefined;
|
|
2647
2655
|
detection?: Record<string, {
|
|
@@ -2859,6 +2867,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
2859
2867
|
sink?: {
|
|
2860
2868
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
2861
2869
|
exclude_fields?: string[] | undefined;
|
|
2870
|
+
emit_changes?: boolean | undefined;
|
|
2862
2871
|
} | undefined;
|
|
2863
2872
|
} | undefined;
|
|
2864
2873
|
detection?: Record<string, {
|
|
@@ -3076,6 +3085,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
3076
3085
|
sink?: {
|
|
3077
3086
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
3078
3087
|
exclude_fields?: string[] | undefined;
|
|
3088
|
+
emit_changes?: boolean | undefined;
|
|
3079
3089
|
} | undefined;
|
|
3080
3090
|
} | undefined;
|
|
3081
3091
|
detection?: Record<string, {
|
|
@@ -3293,6 +3303,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
3293
3303
|
sink?: {
|
|
3294
3304
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
3295
3305
|
exclude_fields?: string[] | undefined;
|
|
3306
|
+
emit_changes?: boolean | undefined;
|
|
3296
3307
|
} | undefined;
|
|
3297
3308
|
} | undefined;
|
|
3298
3309
|
detection?: Record<string, {
|
|
@@ -3510,6 +3521,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
3510
3521
|
sink?: {
|
|
3511
3522
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
3512
3523
|
exclude_fields?: string[] | undefined;
|
|
3524
|
+
emit_changes?: boolean | undefined;
|
|
3513
3525
|
} | undefined;
|
|
3514
3526
|
} | undefined;
|
|
3515
3527
|
detection?: Record<string, {
|
|
@@ -3727,6 +3739,7 @@ declare const EntityDefinitionSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.Z
|
|
|
3727
3739
|
sink?: {
|
|
3728
3740
|
delete?: "noop" | "soft" | "tombstone" | undefined;
|
|
3729
3741
|
exclude_fields?: string[] | undefined;
|
|
3742
|
+
emit_changes?: boolean | undefined;
|
|
3730
3743
|
} | undefined;
|
|
3731
3744
|
} | undefined;
|
|
3732
3745
|
detection?: Record<string, {
|
package/dist/src/index.js
CHANGED
|
@@ -45,13 +45,13 @@ import {
|
|
|
45
45
|
validateOrchestrationProject,
|
|
46
46
|
validatePatternComposition,
|
|
47
47
|
validatePatternProject
|
|
48
|
-
} from "../chunk-
|
|
48
|
+
} from "../chunk-K4BQQ2NN.js";
|
|
49
49
|
import "../chunk-KVOWSC5S.js";
|
|
50
|
-
import "../chunk-QFUIE37H.js";
|
|
51
|
-
import "../chunk-FFUDEIFF.js";
|
|
52
|
-
import "../chunk-EO2QPOKH.js";
|
|
53
50
|
import "../chunk-PRWIX6UW.js";
|
|
54
|
-
import "../chunk-
|
|
51
|
+
import "../chunk-KFXXOFDC.js";
|
|
52
|
+
import "../chunk-HN5HT5WL.js";
|
|
53
|
+
import "../chunk-EO2QPOKH.js";
|
|
54
|
+
import "../chunk-LLDJS7PJ.js";
|
|
55
55
|
import "../chunk-HNWZFNKP.js";
|
|
56
56
|
import "../chunk-AHV4GDYM.js";
|
|
57
57
|
import "../chunk-SQDOBLBP.js";
|
|
@@ -62,8 +62,8 @@ import "../chunk-JEINYUJH.js";
|
|
|
62
62
|
import "../chunk-5TK7MEN4.js";
|
|
63
63
|
import "../chunk-4KNXX6TI.js";
|
|
64
64
|
import "../chunk-3CJFPU6Q.js";
|
|
65
|
-
import "../chunk-
|
|
66
|
-
import "../chunk-
|
|
65
|
+
import "../chunk-YIVQ7KLS.js";
|
|
66
|
+
import "../chunk-S5G3HO7N.js";
|
|
67
67
|
import "../chunk-MZ6GV4YF.js";
|
|
68
68
|
import "../chunk-LG57S2SC.js";
|
|
69
69
|
import "../chunk-U64T4YZE.js";
|
package/package.json
CHANGED
|
@@ -71,6 +71,33 @@ export const eventRegistry = {
|
|
|
71
71
|
version: 1,
|
|
72
72
|
retry: { attempts: 3, backoff: 'exponential' },
|
|
73
73
|
},
|
|
74
|
+
'message_created': {
|
|
75
|
+
type: 'message_created',
|
|
76
|
+
tier: 'domain',
|
|
77
|
+
direction: 'change',
|
|
78
|
+
pool: 'events_change',
|
|
79
|
+
aggregate: 'message',
|
|
80
|
+
version: 1,
|
|
81
|
+
retry: { attempts: 3, backoff: 'exponential' },
|
|
82
|
+
},
|
|
83
|
+
'message_deleted': {
|
|
84
|
+
type: 'message_deleted',
|
|
85
|
+
tier: 'domain',
|
|
86
|
+
direction: 'change',
|
|
87
|
+
pool: 'events_change',
|
|
88
|
+
aggregate: 'message',
|
|
89
|
+
version: 1,
|
|
90
|
+
retry: { attempts: 3, backoff: 'exponential' },
|
|
91
|
+
},
|
|
92
|
+
'message_edited': {
|
|
93
|
+
type: 'message_edited',
|
|
94
|
+
tier: 'domain',
|
|
95
|
+
direction: 'change',
|
|
96
|
+
pool: 'events_change',
|
|
97
|
+
aggregate: 'message',
|
|
98
|
+
version: 1,
|
|
99
|
+
retry: { attempts: 3, backoff: 'exponential' },
|
|
100
|
+
},
|
|
74
101
|
'stripe_payment_received': {
|
|
75
102
|
type: 'stripe_payment_received',
|
|
76
103
|
tier: 'domain',
|
|
@@ -39,6 +39,29 @@ export const dealStageChangedPayloadSchema = z.object({
|
|
|
39
39
|
oldStage: z.string(),
|
|
40
40
|
}).strict();
|
|
41
41
|
|
|
42
|
+
export const messageCreatedPayloadSchema = z.object({
|
|
43
|
+
changedFields: z.record(z.unknown()).nullable(),
|
|
44
|
+
entityId: z.string().uuid(),
|
|
45
|
+
externalId: z.string(),
|
|
46
|
+
provider: z.string(),
|
|
47
|
+
source: z.string(),
|
|
48
|
+
}).strict();
|
|
49
|
+
|
|
50
|
+
export const messageDeletedPayloadSchema = z.object({
|
|
51
|
+
entityId: z.string().uuid(),
|
|
52
|
+
externalId: z.string(),
|
|
53
|
+
provider: z.string(),
|
|
54
|
+
source: z.string(),
|
|
55
|
+
}).strict();
|
|
56
|
+
|
|
57
|
+
export const messageEditedPayloadSchema = z.object({
|
|
58
|
+
changedFields: z.record(z.unknown()).nullable(),
|
|
59
|
+
entityId: z.string().uuid(),
|
|
60
|
+
externalId: z.string(),
|
|
61
|
+
provider: z.string(),
|
|
62
|
+
source: z.string(),
|
|
63
|
+
}).strict();
|
|
64
|
+
|
|
42
65
|
export const stripePaymentReceivedPayloadSchema = z.object({
|
|
43
66
|
amountCents: z.number(),
|
|
44
67
|
currency: z.string(),
|
|
@@ -60,6 +83,9 @@ export const eventPayloadSchemas = {
|
|
|
60
83
|
'crm_sync_started': crmSyncStartedPayloadSchema,
|
|
61
84
|
'deal_created': dealCreatedPayloadSchema,
|
|
62
85
|
'deal_stage_changed': dealStageChangedPayloadSchema,
|
|
86
|
+
'message_created': messageCreatedPayloadSchema,
|
|
87
|
+
'message_deleted': messageDeletedPayloadSchema,
|
|
88
|
+
'message_edited': messageEditedPayloadSchema,
|
|
63
89
|
'stripe_payment_received': stripePaymentReceivedPayloadSchema,
|
|
64
90
|
'webhook_outbound_contact_sync': webhookOutboundContactSyncPayloadSchema,
|
|
65
91
|
} as const satisfies Record<EventTypeName, z.ZodType>;
|
|
@@ -64,6 +64,55 @@ export interface DealStageChangedEvent extends DomainEvent {
|
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
export interface MessageCreatedEvent extends DomainEvent {
|
|
68
|
+
readonly type: 'message_created';
|
|
69
|
+
readonly aggregateType: 'message';
|
|
70
|
+
readonly payload: {
|
|
71
|
+
/** Differ's per-field before/after map (same value as integration_run_items.changed_fields). */
|
|
72
|
+
changedFields: Record<string, unknown> | null;
|
|
73
|
+
/** Local aggregate id the sink wrote/soft-deleted. */
|
|
74
|
+
entityId: string;
|
|
75
|
+
/** Vendor external id the change keyed on. */
|
|
76
|
+
externalId: string;
|
|
77
|
+
/** Provider label (e.g. 'slack', 'google'). */
|
|
78
|
+
provider: string;
|
|
79
|
+
/** Provenance marker — always 'integration'. A write-back action reads this to avoid echoing the change back to the vendor. */
|
|
80
|
+
source: string;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface MessageDeletedEvent extends DomainEvent {
|
|
85
|
+
readonly type: 'message_deleted';
|
|
86
|
+
readonly aggregateType: 'message';
|
|
87
|
+
readonly payload: {
|
|
88
|
+
/** Local aggregate id the sink wrote/soft-deleted. */
|
|
89
|
+
entityId: string;
|
|
90
|
+
/** Vendor external id the change keyed on. */
|
|
91
|
+
externalId: string;
|
|
92
|
+
/** Provider label (e.g. 'slack', 'google'). */
|
|
93
|
+
provider: string;
|
|
94
|
+
/** Provenance marker — always 'integration'. A write-back action reads this to avoid echoing the change back to the vendor. */
|
|
95
|
+
source: string;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface MessageEditedEvent extends DomainEvent {
|
|
100
|
+
readonly type: 'message_edited';
|
|
101
|
+
readonly aggregateType: 'message';
|
|
102
|
+
readonly payload: {
|
|
103
|
+
/** Differ's per-field before/after map (same value as integration_run_items.changed_fields). */
|
|
104
|
+
changedFields: Record<string, unknown> | null;
|
|
105
|
+
/** Local aggregate id the sink wrote/soft-deleted. */
|
|
106
|
+
entityId: string;
|
|
107
|
+
/** Vendor external id the change keyed on. */
|
|
108
|
+
externalId: string;
|
|
109
|
+
/** Provider label (e.g. 'slack', 'google'). */
|
|
110
|
+
provider: string;
|
|
111
|
+
/** Provenance marker — always 'integration'. A write-back action reads this to avoid echoing the change back to the vendor. */
|
|
112
|
+
source: string;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
67
116
|
/** Stripe charge.succeeded webhook, post-signature-verification. */
|
|
68
117
|
export interface StripePaymentReceivedEvent extends DomainEvent {
|
|
69
118
|
readonly type: 'stripe_payment_received';
|
|
@@ -97,6 +146,9 @@ export type AppDomainEvent =
|
|
|
97
146
|
| CrmSyncStartedEvent
|
|
98
147
|
| DealCreatedEvent
|
|
99
148
|
| DealStageChangedEvent
|
|
149
|
+
| MessageCreatedEvent
|
|
150
|
+
| MessageDeletedEvent
|
|
151
|
+
| MessageEditedEvent
|
|
100
152
|
| StripePaymentReceivedEvent
|
|
101
153
|
| WebhookOutboundContactSyncEvent;
|
|
102
154
|
|
|
@@ -12,6 +12,17 @@ export { EVENT_BUS } from './events';
|
|
|
12
12
|
// mode reaches it via `@shared/subsystems/events`; both must export it.
|
|
13
13
|
export type { DomainEvent, IEventBus, DrizzleTransaction } from './events';
|
|
14
14
|
export { EventsModule, DrizzleEventBus, MemoryEventBus } from './events';
|
|
15
|
+
// The `TYPED_EVENT_BUS` token is re-exported here so package-mode generated code
|
|
16
|
+
// that publishes events from the single `@pattern-stack/codegen/subsystems`
|
|
17
|
+
// barrel resolves it — notably the EMIT-CHANGES integration change-emitter
|
|
18
|
+
// (`<entity>.change-emitter.ts`), which injects `TYPED_EVENT_BUS`. The emitter
|
|
19
|
+
// types the bus with a local structural `publish` shape (NOT the package's
|
|
20
|
+
// `TypedEventBus`, whose `EventTypeName` union is the package's own events — the
|
|
21
|
+
// consumer's `<entity>_*` events live in the CONSUMER registry), so only the
|
|
22
|
+
// token needs forwarding. The CONSUMER binds their generated `TypedEventBus` to
|
|
23
|
+
// the token via `EventsModule.forRoot()`. Vendored mode reaches it via
|
|
24
|
+
// `@shared/subsystems/events`.
|
|
25
|
+
export { TYPED_EVENT_BUS } from './events';
|
|
15
26
|
|
|
16
27
|
// Jobs — orchestration schema only (JOB-1). Protocols / modules land in JOB-2 / JOB-5.
|
|
17
28
|
export { jobs, jobRuns, jobSteps } from './jobs';
|
|
@@ -131,6 +142,18 @@ export {
|
|
|
131
142
|
} from './integration';
|
|
132
143
|
export type { IIntegrationSink } from './integration';
|
|
133
144
|
|
|
145
|
+
// Integration — EMIT-CHANGES seam. The generated per-entity change-emitter
|
|
146
|
+
// (`<entity>.change-emitter.ts`) imports the `INTEGRATION_CHANGE_EMITTER` token
|
|
147
|
+
// + the `IIntegrationChangeEmitter` port + `IntegrationChangeNotification` from
|
|
148
|
+
// `@pattern-stack/codegen/subsystems`. Forwarded here so the emitted opt-in
|
|
149
|
+
// emitter resolves them across the package boundary (package mode).
|
|
150
|
+
export { INTEGRATION_CHANGE_EMITTER } from './integration';
|
|
151
|
+
export type {
|
|
152
|
+
IIntegrationChangeEmitter,
|
|
153
|
+
IntegrationChangeAction,
|
|
154
|
+
IntegrationChangeNotification,
|
|
155
|
+
} from './integration';
|
|
156
|
+
|
|
134
157
|
// Auth
|
|
135
158
|
export {
|
|
136
159
|
ENCRYPTION_KEY,
|
|
@@ -50,8 +50,13 @@ import type { ICursorStore } from './integration-cursor-store.protocol';
|
|
|
50
50
|
import type { IFieldDiffer, FieldDiff } from './integration-field-diff.protocol';
|
|
51
51
|
import type { IIntegrationSink } from './integration-sink.protocol';
|
|
52
52
|
import type { IIntegrationRunRecorder } from './integration-run-recorder.protocol';
|
|
53
|
+
import type {
|
|
54
|
+
IIntegrationChangeEmitter,
|
|
55
|
+
IntegrationChangeAction,
|
|
56
|
+
} from './integration-change-emitter.protocol';
|
|
53
57
|
import { assertTenantId } from './integration-errors';
|
|
54
58
|
import {
|
|
59
|
+
INTEGRATION_CHANGE_EMITTER,
|
|
55
60
|
INTEGRATION_CHANGE_SOURCE,
|
|
56
61
|
INTEGRATION_CURSOR_STORE,
|
|
57
62
|
INTEGRATION_FIELD_DIFFER,
|
|
@@ -118,6 +123,13 @@ export class ExecuteIntegrationUseCase<T extends Record<string, unknown>> {
|
|
|
118
123
|
@Optional()
|
|
119
124
|
@Inject(INTEGRATION_MULTI_TENANT)
|
|
120
125
|
private readonly multiTenant: boolean = false,
|
|
126
|
+
// EMIT-CHANGES seam — optional post-upsert domain-event emitter. Bound only
|
|
127
|
+
// by codegen-emitted assemblies whose entity opts into
|
|
128
|
+
// `integration.sink.emit_changes: true`. Unbound (the default) ⇒ no events
|
|
129
|
+
// published, zero behavior change.
|
|
130
|
+
@Optional()
|
|
131
|
+
@Inject(INTEGRATION_CHANGE_EMITTER)
|
|
132
|
+
private readonly emitter: IIntegrationChangeEmitter | null = null,
|
|
121
133
|
) {}
|
|
122
134
|
|
|
123
135
|
async execute(input: ExecuteIntegrationInput<T>): Promise<ExecuteIntegrationResult> {
|
|
@@ -263,6 +275,12 @@ export class ExecuteIntegrationUseCase<T extends Record<string, unknown>> {
|
|
|
263
275
|
changedFields: {},
|
|
264
276
|
tenantId: input.tenantId,
|
|
265
277
|
});
|
|
278
|
+
// EMIT-CHANGES: a real tombstone (a local row existed and was soft-deleted)
|
|
279
|
+
// is a `<entity>_deleted` event. A delete that hit no local row is a noop —
|
|
280
|
+
// nothing changed, nothing to emit.
|
|
281
|
+
if (result) {
|
|
282
|
+
await this.emitChange(input, change.externalId, result.id, 'deleted');
|
|
283
|
+
}
|
|
266
284
|
return;
|
|
267
285
|
}
|
|
268
286
|
|
|
@@ -321,15 +339,65 @@ export class ExecuteIntegrationUseCase<T extends Record<string, unknown>> {
|
|
|
321
339
|
input.provider,
|
|
322
340
|
);
|
|
323
341
|
|
|
342
|
+
const action: IntegrationChangeAction =
|
|
343
|
+
existing === null ? 'created' : 'updated';
|
|
344
|
+
|
|
324
345
|
await this.recorder.recordItem({
|
|
325
346
|
integrationRunId: runId,
|
|
326
347
|
entityType: input.subscription.domain,
|
|
327
348
|
externalId: change.externalId,
|
|
328
349
|
localId,
|
|
329
|
-
operation:
|
|
350
|
+
operation: action,
|
|
330
351
|
status: 'success',
|
|
331
352
|
changedFields: diff as FieldDiff,
|
|
332
353
|
tenantId: input.tenantId,
|
|
333
354
|
});
|
|
355
|
+
|
|
356
|
+
// EMIT-CHANGES: a real create/update (the diff was NOT noop) publishes a
|
|
357
|
+
// typed `<entity>_created` / `<entity>_edited` event. The noop-reproject path
|
|
358
|
+
// above intentionally does NOT emit — the canonical state is unchanged.
|
|
359
|
+
await this.emitChange(
|
|
360
|
+
input,
|
|
361
|
+
change.externalId,
|
|
362
|
+
localId,
|
|
363
|
+
action,
|
|
364
|
+
diff as FieldDiff,
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Publish one typed data-level change event via the optional emitter
|
|
370
|
+
* (EMIT-CHANGES seam). No-op when no emitter is bound (the back-compat
|
|
371
|
+
* default). A failed publish is logged but never aborts the run — the row is
|
|
372
|
+
* already written; emission is best-effort (the outbox tx, when the generated
|
|
373
|
+
* adapter rides on one, gives the at-least-once guarantee, not this try/catch).
|
|
374
|
+
*/
|
|
375
|
+
private async emitChange(
|
|
376
|
+
input: ExecuteIntegrationInput<T>,
|
|
377
|
+
externalId: string,
|
|
378
|
+
entityId: string,
|
|
379
|
+
action: IntegrationChangeAction,
|
|
380
|
+
changedFields?: FieldDiff,
|
|
381
|
+
): Promise<void> {
|
|
382
|
+
if (this.emitter === null) return;
|
|
383
|
+
try {
|
|
384
|
+
await this.emitter.emitChange({
|
|
385
|
+
entityId,
|
|
386
|
+
externalId,
|
|
387
|
+
provider: input.provider,
|
|
388
|
+
action,
|
|
389
|
+
changedFields:
|
|
390
|
+
action === 'deleted'
|
|
391
|
+
? undefined
|
|
392
|
+
: (changedFields as Record<string, unknown> | undefined),
|
|
393
|
+
tenantId: input.tenantId,
|
|
394
|
+
});
|
|
395
|
+
} catch (err) {
|
|
396
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
397
|
+
this.logger.warn(
|
|
398
|
+
`integration change-emit failed: subscription=${input.subscription.id} ` +
|
|
399
|
+
`externalId=${externalId} action=${action}: ${message}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
334
402
|
}
|
|
335
403
|
}
|
|
@@ -35,6 +35,11 @@ export {
|
|
|
35
35
|
FieldDiffValueSchema,
|
|
36
36
|
} from './integration-field-diff.protocol';
|
|
37
37
|
export type { IIntegrationSink } from './integration-sink.protocol';
|
|
38
|
+
export type {
|
|
39
|
+
IIntegrationChangeEmitter,
|
|
40
|
+
IntegrationChangeAction,
|
|
41
|
+
IntegrationChangeNotification,
|
|
42
|
+
} from './integration-change-emitter.protocol';
|
|
38
43
|
export type {
|
|
39
44
|
CompleteRunInput,
|
|
40
45
|
IIntegrationRunRecorder,
|
|
@@ -125,6 +130,7 @@ export { buildChangeSource } from './build-change-source';
|
|
|
125
130
|
// Tokens
|
|
126
131
|
export {
|
|
127
132
|
ENTITY_CHANGE_SOURCE_REGISTRY,
|
|
133
|
+
INTEGRATION_CHANGE_EMITTER,
|
|
128
134
|
INTEGRATION_CHANGE_SOURCE,
|
|
129
135
|
INTEGRATION_CURSOR_STORE,
|
|
130
136
|
INTEGRATION_FIELD_DIFFER,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration subsystem — change-emitter protocol (port).
|
|
3
|
+
*
|
|
4
|
+
* `IIntegrationChangeEmitter` is the OPT-IN seam through which the generic
|
|
5
|
+
* orchestrator (`ExecuteIntegrationUseCase`) publishes a typed, data-level
|
|
6
|
+
* domain event after every sink write/soft-delete. It is the upstream-generalized
|
|
7
|
+
* form of the per-sink event emission swe-brain hand-built (ADR-0009 Amendment B):
|
|
8
|
+
* the differ already records `changed_fields` on `integration_run_items`, but
|
|
9
|
+
* nothing publishes a "this entity changed" domain event for downstream
|
|
10
|
+
* trigger→action consumers. This port closes that gap.
|
|
11
|
+
*
|
|
12
|
+
* ## Why a port (not a direct TypedEventBus call in the orchestrator)
|
|
13
|
+
*
|
|
14
|
+
* The orchestrator is strictly provider- AND entity-agnostic: `entityType` is a
|
|
15
|
+
* bare `string` and the canonical record is generic `T` (see the use-case header
|
|
16
|
+
* "No CRM bleed"). It therefore cannot know the typed event NAME
|
|
17
|
+
* (`<entity>_created`) or the typed payload shape at compile time. The typed
|
|
18
|
+
* knowledge lives at the per-entity assembly wiring (codegen knows the entity
|
|
19
|
+
* name there). So the orchestrator depends on this thin, untyped port; codegen
|
|
20
|
+
* binds a per-entity adapter that maps `(operation) → <entity>_<verb>` and calls
|
|
21
|
+
* the project's generated `TypedEventBus.publish(...)` with the typed payload.
|
|
22
|
+
*
|
|
23
|
+
* ## Backwards compatibility
|
|
24
|
+
*
|
|
25
|
+
* The port is `@Optional()` on the orchestrator. Entities that do NOT opt in
|
|
26
|
+
* (`integration.sink.emit_changes` absent/false) bind no emitter, so the
|
|
27
|
+
* orchestrator's `this.emitter` is `undefined` and NOTHING is published — zero
|
|
28
|
+
* behavior change. This is the invariant the snapshot fixture (which opts none
|
|
29
|
+
* in) keeps green.
|
|
30
|
+
*
|
|
31
|
+
* ## Provenance — loop-breaking
|
|
32
|
+
*
|
|
33
|
+
* Every emitted event carries `source: 'integration'` in its payload. A future
|
|
34
|
+
* write-back action (the Intervention layer in swe-brain terms) that subscribes
|
|
35
|
+
* to these events can detect `source === 'integration'` and decline to echo the
|
|
36
|
+
* change back to the vendor, breaking the inbound→writeback→inbound loop. This
|
|
37
|
+
* is the data-layer counterpart of the loopback middleware that already guards
|
|
38
|
+
* the read side (`createLoopbackMiddleware`).
|
|
39
|
+
*
|
|
40
|
+
* ## Transactionality
|
|
41
|
+
*
|
|
42
|
+
* `emitChange` receives the same `tx` the sink wrote under (when the sink exposes
|
|
43
|
+
* one — today the sink owns its own transaction internally, so `tx` is reserved
|
|
44
|
+
* for the future where the orchestrator drives the transaction). The generated
|
|
45
|
+
* adapter forwards `tx` into `TypedEventBus.publish(type, id, payload, { tx })`,
|
|
46
|
+
* so the event lands in the outbox iff the row commits (the events subsystem's
|
|
47
|
+
* outbox guarantee). When `tx` is absent the publish is post-commit best-effort,
|
|
48
|
+
* matching today's sink-owns-its-own-transaction reality.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/** The data-level action the orchestrator observed. Maps onto the generated
|
|
52
|
+
* event verb: `created → <entity>_created`, `updated → <entity>_edited`
|
|
53
|
+
* (per swe-brain ADR-0009 B1 — `_edited`, never `_updated`),
|
|
54
|
+
* `deleted → <entity>_deleted` (tombstone soft-delete). `noop` never emits. */
|
|
55
|
+
export type IntegrationChangeAction = 'created' | 'updated' | 'deleted';
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The vendor-blind change descriptor the orchestrator hands the emitter. The
|
|
59
|
+
* generated adapter reshapes this into the typed `<entity>_<verb>` payload
|
|
60
|
+
* (`{ entityId, externalId, provider, changedFields?, source: 'integration' }`).
|
|
61
|
+
*/
|
|
62
|
+
export interface IntegrationChangeNotification {
|
|
63
|
+
/** The local row id the sink wrote/soft-deleted (the domain aggregate id —
|
|
64
|
+
* becomes `aggregateId` on the published event AND `entityId` in the payload). */
|
|
65
|
+
readonly entityId: string;
|
|
66
|
+
/** Vendor-prefixed-or-bare external id the change keyed on (e.g. `slack:123`). */
|
|
67
|
+
readonly externalId: string;
|
|
68
|
+
/** Provider label from `ExecuteIntegrationInput.provider` (e.g. `'slack'`). */
|
|
69
|
+
readonly provider: string;
|
|
70
|
+
/** The observed action. `created`/`updated` come from the existing-row check;
|
|
71
|
+
* `deleted` from the soft-delete path. */
|
|
72
|
+
readonly action: IntegrationChangeAction;
|
|
73
|
+
/** The differ's structured per-field before/after map (the same value written
|
|
74
|
+
* to `integration_run_items.changed_fields`). Absent on deletes. */
|
|
75
|
+
readonly changedFields?: Record<string, unknown>;
|
|
76
|
+
/** Multi-tenant deployments thread the tenant id through to the event metadata. */
|
|
77
|
+
readonly tenantId?: string | null;
|
|
78
|
+
/** The transaction the sink wrote under, when the orchestrator drives one.
|
|
79
|
+
* Forwarded to `TypedEventBus.publish(..., { tx })` for the outbox guarantee.
|
|
80
|
+
* Reserved: today the sink owns its own transaction, so this is usually
|
|
81
|
+
* `undefined` and the publish is post-commit. Typed `unknown` here so the
|
|
82
|
+
* port stays free of a Drizzle type dependency (the generated adapter narrows). */
|
|
83
|
+
readonly tx?: unknown;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Post-upsert change-event emission port.
|
|
88
|
+
*
|
|
89
|
+
* One implementation per opted-in (entity, provider) assembly — codegen-emitted,
|
|
90
|
+
* bound to `INTEGRATION_CHANGE_EMITTER` in that assembly module. The orchestrator
|
|
91
|
+
* injects it `@Optional()`; an unbound token means no emission (back-compat).
|
|
92
|
+
*/
|
|
93
|
+
export interface IIntegrationChangeEmitter {
|
|
94
|
+
/**
|
|
95
|
+
* Publish the typed `<entity>_<verb>` domain event for one observed change.
|
|
96
|
+
*
|
|
97
|
+
* MUST be called only for real changes — the orchestrator never calls this on
|
|
98
|
+
* a `noop` diff (canonical state unchanged) or a delete that hit no local row
|
|
99
|
+
* (no tombstone created). Implementations should treat a call as "this thing
|
|
100
|
+
* happened" and publish unconditionally.
|
|
101
|
+
*
|
|
102
|
+
* Errors are the orchestrator's concern: it wraps the call so a failed publish
|
|
103
|
+
* does not abort the run (the row is already written; emission is best-effort
|
|
104
|
+
* unless ridden on the outbox tx). See `ExecuteIntegrationUseCase.processChange`.
|
|
105
|
+
*/
|
|
106
|
+
emitChange(notification: IntegrationChangeNotification): Promise<void>;
|
|
107
|
+
}
|
|
@@ -25,6 +25,17 @@ export const INTEGRATION_CURSOR_STORE = 'INTEGRATION_CURSOR_STORE' as const;
|
|
|
25
25
|
export const INTEGRATION_FIELD_DIFFER = 'INTEGRATION_FIELD_DIFFER' as const;
|
|
26
26
|
export const INTEGRATION_SINK = 'INTEGRATION_SINK' as const;
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Optional post-upsert change-event emitter token (EMIT-CHANGES seam).
|
|
30
|
+
*
|
|
31
|
+
* Backed by `IIntegrationChangeEmitter`. Bound ONLY by codegen-emitted assembly
|
|
32
|
+
* modules whose entity opts in via `integration.sink.emit_changes: true`. The
|
|
33
|
+
* orchestrator injects it `@Optional()` — an unbound token means no domain
|
|
34
|
+
* events are published (the back-compat default for non-opted-in entities).
|
|
35
|
+
* See `integration-change-emitter.protocol.ts`.
|
|
36
|
+
*/
|
|
37
|
+
export const INTEGRATION_CHANGE_EMITTER = 'INTEGRATION_CHANGE_EMITTER' as const;
|
|
38
|
+
|
|
28
39
|
/**
|
|
29
40
|
* Run-recorder token (SYNC-5). Backed by `IIntegrationRunRecorder`. Drizzle impl
|
|
30
41
|
* lands in SYNC-4; tests provide inline fakes.
|