@hogsend/core 0.4.0 → 0.6.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/README.md +11 -0
- package/package.json +4 -2
- package/src/index.ts +1 -0
- package/src/providers/analytics.ts +20 -0
- package/src/providers/email.ts +138 -0
- package/src/providers/index.ts +2 -0
- package/src/schemas/journey.schema.ts +13 -0
- package/src/types/journey.ts +7 -0
package/README.md
CHANGED
|
@@ -4,6 +4,17 @@ Core types, Zod schemas, the condition-evaluation engine, duration helpers, and
|
|
|
4
4
|
the `JourneyRegistry` for [Hogsend](https://github.com/dougwithseismic/hogsend) —
|
|
5
5
|
a code-first lifecycle orchestration engine for teams on PostHog + Resend.
|
|
6
6
|
|
|
7
|
+
## Capability-provider contracts
|
|
8
|
+
|
|
9
|
+
Core also **owns** the capability-provider contracts — `EmailProvider` (with its
|
|
10
|
+
supporting `SendEmailOptions`, `BatchEmailItem`, `SendResult`, `WebhookEvent`,
|
|
11
|
+
`WebhookEventType`, `WebhookHandlerMap`) and `PostHogService` (with
|
|
12
|
+
`CaptureOptions`). These are the engine-owned contracts a swappable email/analytics
|
|
13
|
+
implementation satisfies; they live here and are re-exported by `@hogsend/engine`
|
|
14
|
+
(the canonical author import) and the vendor plugins (`@hogsend/plugin-resend`,
|
|
15
|
+
`@hogsend/plugin-posthog`) for back-compat. See
|
|
16
|
+
[docs/adr/0001-provider-boundary.md](https://github.com/dougwithseismic/hogsend/blob/main/docs/adr/0001-provider-boundary.md).
|
|
17
|
+
|
|
7
18
|
This package ships raw TypeScript source; consumers bundle it via their own build
|
|
8
19
|
(tsup `noExternal`). See the repo docs for the full architecture and the
|
|
9
20
|
[release model](https://github.com/dougwithseismic/hogsend/blob/main/docs/RELEASING.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hogsend/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"exports": {
|
|
15
15
|
".": "./src/index.ts",
|
|
16
16
|
"./types": "./src/types/index.ts",
|
|
17
|
+
"./providers": "./src/providers/index.ts",
|
|
17
18
|
"./registry": "./src/registry/index.ts",
|
|
18
19
|
"./conditions": "./src/conditions/index.ts",
|
|
19
20
|
"./schedule": "./src/schedule/index.ts",
|
|
@@ -31,10 +32,11 @@
|
|
|
31
32
|
"drizzle-orm": "^0.45.2",
|
|
32
33
|
"iana-db-timezones": "^0.3.0",
|
|
33
34
|
"zod": "^4.4.3",
|
|
34
|
-
"@hogsend/db": "^0.
|
|
35
|
+
"@hogsend/db": "^0.6.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@types/node": "latest",
|
|
39
|
+
"@types/react": "^19.2.16",
|
|
38
40
|
"vitest": "^4.1.8",
|
|
39
41
|
"@repo/typescript-config": "0.0.0"
|
|
40
42
|
},
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface PostHogService {
|
|
2
|
+
getPersonProperties(distinctId: string): Promise<Record<string, unknown>>;
|
|
3
|
+
|
|
4
|
+
captureEvent(opts: CaptureOptions): void;
|
|
5
|
+
|
|
6
|
+
identify(distinctId: string, properties: Record<string, unknown>): void;
|
|
7
|
+
|
|
8
|
+
isFeatureEnabled(opts: {
|
|
9
|
+
distinctId: string;
|
|
10
|
+
flag: string;
|
|
11
|
+
}): Promise<boolean>;
|
|
12
|
+
|
|
13
|
+
shutdown(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CaptureOptions {
|
|
17
|
+
distinctId: string;
|
|
18
|
+
event: string;
|
|
19
|
+
properties?: Record<string, unknown>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { ReactElement } from "react";
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Send options
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
export interface SendEmailOptions {
|
|
8
|
+
from: string;
|
|
9
|
+
to: string | string[];
|
|
10
|
+
subject: string;
|
|
11
|
+
react?: ReactElement;
|
|
12
|
+
html?: string;
|
|
13
|
+
replyTo?: string | string[];
|
|
14
|
+
cc?: string | string[];
|
|
15
|
+
bcc?: string | string[];
|
|
16
|
+
scheduledAt?: string;
|
|
17
|
+
tags?: Array<{ name: string; value: string }>;
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface BatchEmailItem {
|
|
22
|
+
from: string;
|
|
23
|
+
to: string | string[];
|
|
24
|
+
subject: string;
|
|
25
|
+
react: ReactElement;
|
|
26
|
+
replyTo?: string | string[];
|
|
27
|
+
cc?: string | string[];
|
|
28
|
+
bcc?: string | string[];
|
|
29
|
+
tags?: Array<{ name: string; value: string }>;
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SendResult {
|
|
34
|
+
id: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Webhook events
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
interface WebhookEventBase {
|
|
42
|
+
created_at: string;
|
|
43
|
+
data: {
|
|
44
|
+
email_id: string;
|
|
45
|
+
from: string;
|
|
46
|
+
to: string[];
|
|
47
|
+
subject: string;
|
|
48
|
+
created_at: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface EmailSentEvent extends WebhookEventBase {
|
|
53
|
+
type: "email.sent";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface EmailDeliveredEvent extends WebhookEventBase {
|
|
57
|
+
type: "email.delivered";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface EmailBouncedEvent extends WebhookEventBase {
|
|
61
|
+
type: "email.bounced";
|
|
62
|
+
data: WebhookEventBase["data"] & {
|
|
63
|
+
bounce: {
|
|
64
|
+
message: string;
|
|
65
|
+
type: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface EmailComplainedEvent extends WebhookEventBase {
|
|
71
|
+
type: "email.complained";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface EmailDeliveryDelayedEvent extends WebhookEventBase {
|
|
75
|
+
type: "email.delivery_delayed";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface EmailOpenedEvent extends WebhookEventBase {
|
|
79
|
+
type: "email.opened";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface EmailClickedEvent extends WebhookEventBase {
|
|
83
|
+
type: "email.clicked";
|
|
84
|
+
data: WebhookEventBase["data"] & {
|
|
85
|
+
click: {
|
|
86
|
+
link: string;
|
|
87
|
+
timestamp: string;
|
|
88
|
+
ipAddress: string;
|
|
89
|
+
userAgent: string;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type WebhookEvent =
|
|
95
|
+
| EmailSentEvent
|
|
96
|
+
| EmailDeliveredEvent
|
|
97
|
+
| EmailBouncedEvent
|
|
98
|
+
| EmailComplainedEvent
|
|
99
|
+
| EmailDeliveryDelayedEvent
|
|
100
|
+
| EmailOpenedEvent
|
|
101
|
+
| EmailClickedEvent;
|
|
102
|
+
|
|
103
|
+
export type WebhookEventType = WebhookEvent["type"];
|
|
104
|
+
|
|
105
|
+
export type WebhookHandlerMap = {
|
|
106
|
+
[K in WebhookEventType]?: (
|
|
107
|
+
event: Extract<WebhookEvent, { type: K }>,
|
|
108
|
+
) => void | Promise<void>;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// EmailProvider contract (the entire provider surface)
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The dumb delivery + webhook parse/verify contract every email provider
|
|
117
|
+
* implements (Resend, Postmark, SES, …). All tracking, DB, preference, and
|
|
118
|
+
* render logic lives in the engine's `createTrackedMailer`, never here.
|
|
119
|
+
*/
|
|
120
|
+
export interface EmailProvider {
|
|
121
|
+
/** Deliver a single message. Returns the provider message id. */
|
|
122
|
+
send(options: SendEmailOptions): Promise<SendResult>;
|
|
123
|
+
|
|
124
|
+
/** Deliver a batch of messages. */
|
|
125
|
+
sendBatch(emails: BatchEmailItem[]): Promise<{ results: SendResult[] }>;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Verify a provider webhook signature and return the parsed event. Throws
|
|
129
|
+
* if the signature is missing/invalid.
|
|
130
|
+
*/
|
|
131
|
+
verifyWebhook(opts: {
|
|
132
|
+
payload: string;
|
|
133
|
+
headers: Record<string, string>;
|
|
134
|
+
}): WebhookEvent;
|
|
135
|
+
|
|
136
|
+
/** Parse an unsigned webhook payload (used in trusted contexts/tests). */
|
|
137
|
+
parseWebhook(payload: string): WebhookEvent;
|
|
138
|
+
}
|
|
@@ -85,4 +85,17 @@ export const journeyMetaSchema = z.object({
|
|
|
85
85
|
minutes: z.number().optional(),
|
|
86
86
|
seconds: z.number().optional(),
|
|
87
87
|
}),
|
|
88
|
+
|
|
89
|
+
// Bucket-reaction tagging. journeyMetaSchema.parse runs inside
|
|
90
|
+
// JourneyRegistry.register and STRIPS unknown keys, so these MUST be declared
|
|
91
|
+
// here or the dwell-cron lookup + Studio grouping silently break.
|
|
92
|
+
sourceBucketId: z.string().optional(),
|
|
93
|
+
reactionKind: z.enum(["enter", "leave", "dwell"]).optional(),
|
|
94
|
+
dwellSchedule: z
|
|
95
|
+
.object({
|
|
96
|
+
label: z.string(),
|
|
97
|
+
after: z.number().optional(),
|
|
98
|
+
every: z.number().optional(),
|
|
99
|
+
})
|
|
100
|
+
.optional(),
|
|
88
101
|
});
|
package/src/types/journey.ts
CHANGED
|
@@ -21,6 +21,13 @@ export interface JourneyMeta {
|
|
|
21
21
|
}>;
|
|
22
22
|
|
|
23
23
|
suppress: DurationObject;
|
|
24
|
+
|
|
25
|
+
// Bucket-reaction tagging (set by buildBucketReaction). Generated reactions
|
|
26
|
+
// carry these so the worker's dwell-cron lookup and Studio bucket-detail
|
|
27
|
+
// grouping can discover owned reactions by sourceBucketId.
|
|
28
|
+
sourceBucketId?: string;
|
|
29
|
+
reactionKind?: "enter" | "leave" | "dwell";
|
|
30
|
+
dwellSchedule?: { label: string; after?: number; every?: number };
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
export interface JourneyUser {
|