notifkit 0.1.3 → 0.1.5
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 +179 -152
- package/dist/index.d.mts +193 -129
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{main-DtHWhueo.mjs → main-40zwq6b0.mjs} +28 -3
- package/dist/{main-DtHWhueo.mjs.map → main-40zwq6b0.mjs.map} +1 -1
- package/dist/{main-DyfbnJc3.mjs → main-BFre2-HQ.mjs} +2 -2
- package/dist/{main-DyfbnJc3.mjs.map → main-BFre2-HQ.mjs.map} +1 -1
- package/dist/{main-CAH0_Q6d.mjs → main-BNJtzY61.mjs} +3 -3
- package/dist/main-BNJtzY61.mjs.map +1 -0
- package/dist/{main-B561M1d3.mjs → main-BOPMYqsW.mjs} +2 -2
- package/dist/{main-B561M1d3.mjs.map → main-BOPMYqsW.mjs.map} +1 -1
- package/dist/{main-CCfc45ev.mjs → main-CiigNpsP.mjs} +7 -4
- package/dist/main-CiigNpsP.mjs.map +1 -0
- package/dist/{main-Ce9dcrsg.mjs → main-DeNFQ-UL.mjs} +6 -3
- package/dist/{main-Ce9dcrsg.mjs.map → main-DeNFQ-UL.mjs.map} +1 -1
- package/dist/{main-B-jwm8ED.mjs → main-DmCPcxOc.mjs} +2 -2
- package/dist/{main-B-jwm8ED.mjs.map → main-DmCPcxOc.mjs.map} +1 -1
- package/dist/{main-C45e7grq.mjs → main-DvgJSm11.mjs} +2 -2
- package/dist/{main-C45e7grq.mjs.map → main-DvgJSm11.mjs.map} +1 -1
- package/dist/{src-C-PfEDMY.mjs → src-vG79L-8m.mjs} +57 -26
- package/dist/src-vG79L-8m.mjs.map +1 -0
- package/drizzle/0002_wide_colleen_wing.sql +2 -0
- package/drizzle/0003_skinny_daimon_hellstrom.sql +1 -0
- package/drizzle/0004_pretty_bruce_banner.sql +1 -0
- package/drizzle/meta/0002_snapshot.json +1460 -0
- package/drizzle/meta/0003_snapshot.json +1460 -0
- package/drizzle/meta/0004_snapshot.json +1470 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +7 -1
- package/scripts/create-project.mjs +61 -0
- package/src/client.ts +412 -0
- package/src/config/index.ts +107 -0
- package/src/contracts/common.ts +28 -0
- package/src/contracts/envelope.ts +31 -0
- package/src/contracts/events/notification-ai-pending.ts +18 -0
- package/src/contracts/events/notification-canceled.ts +7 -0
- package/src/contracts/events/notification-created.ts +14 -0
- package/src/contracts/events/notification-delivered.ts +17 -0
- package/src/contracts/events/notification-dispatched.ts +45 -0
- package/src/contracts/events/notification-enriched.ts +46 -0
- package/src/contracts/events/notification-failed.ts +19 -0
- package/src/contracts/events/notification-requested.ts +36 -0
- package/src/contracts/events/notification-scheduled.ts +9 -0
- package/src/contracts/events/notification-skipped.ts +9 -0
- package/src/contracts/helpers.ts +21 -0
- package/src/contracts/index.ts +46 -0
- package/src/contracts/metadata.ts +10 -0
- package/src/contracts/registry.ts +88 -0
- package/src/contracts/sdk.ts +242 -0
- package/src/contracts/streams.ts +62 -0
- package/src/db/index.ts +69 -0
- package/src/db/schema.ts +412 -0
- package/src/idempotency/index.ts +50 -0
- package/src/index.ts +19 -0
- package/src/logger/index.ts +60 -0
- package/src/metrics/index.ts +53 -0
- package/src/queue/index.ts +501 -0
- package/src/rate-limiter/index.ts +210 -0
- package/src/redis/index.ts +89 -0
- package/src/repositories/index.ts +1246 -0
- package/src/server.ts +277 -0
- package/src/services/ai/main.ts +404 -0
- package/src/services/api/handlers.ts +1734 -0
- package/src/services/api/http.ts +64 -0
- package/src/services/api/main.ts +693 -0
- package/src/services/api/router.ts +82 -0
- package/src/services/delivery/main.ts +842 -0
- package/src/services/delivery/throttle.ts +71 -0
- package/src/services/engine/main.ts +827 -0
- package/src/services/enricher/main.ts +594 -0
- package/src/services/events/main.ts +365 -0
- package/src/services/scheduler/main.ts +319 -0
- package/src/services/workflow/main.ts +627 -0
- package/src/shared/batch-processor.ts +67 -0
- package/src/shared/cache.ts +47 -0
- package/src/shared/circuit-breaker.ts +74 -0
- package/src/shared/dataloader.ts +41 -0
- package/src/shared/events.ts +3 -0
- package/src/shared/index.ts +39 -0
- package/src/shared/semaphore.ts +33 -0
- package/src/shared/utils.ts +64 -0
- package/src/templates/cache.ts +32 -0
- package/src/templates/index.ts +69 -0
- package/src/templates/render.ts +128 -0
- package/src/transport/index.ts +96 -0
- package/src/unsubscribe/index.ts +127 -0
- package/src/workers/health.ts +31 -0
- package/src/workers/index.ts +266 -0
- package/src/workflows/index.ts +2 -0
- package/src/workflows/registry.ts +21 -0
- package/src/workflows/sdk.ts +106 -0
- package/dist/main-CAH0_Q6d.mjs.map +0 -1
- package/dist/main-CCfc45ev.mjs.map +0 -1
- package/dist/src-C-PfEDMY.mjs.map +0 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { EventMetadataSchema, type EventMetadata } from "./metadata.js";
|
|
3
|
+
import type { EventPayloadMap, KnownEventType } from "./registry.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Wire format written to Redis Streams. The payload field is an opaque record
|
|
7
|
+
* at the envelope level — use registry.parsePayload() to get a typed payload.
|
|
8
|
+
*/
|
|
9
|
+
export const EventEnvelopeSchema = z.object({
|
|
10
|
+
id: z.string().uuid(),
|
|
11
|
+
type: z.string(),
|
|
12
|
+
timestamp: z.string().datetime(),
|
|
13
|
+
payload: z.record(z.string(), z.unknown()),
|
|
14
|
+
metadata: EventMetadataSchema,
|
|
15
|
+
});
|
|
16
|
+
export type EventEnvelope = z.infer<typeof EventEnvelopeSchema>;
|
|
17
|
+
|
|
18
|
+
/** Typed envelope where the payload is strongly typed via EventPayloadMap. */
|
|
19
|
+
export type TypedEventEnvelope<K extends KnownEventType> = Omit<
|
|
20
|
+
EventEnvelope,
|
|
21
|
+
"type" | "payload"
|
|
22
|
+
> & {
|
|
23
|
+
type: K;
|
|
24
|
+
payload: EventPayloadMap[K];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Backward-compatible alias used by the queue / workers packages.
|
|
28
|
+
export type StreamEvent = EventEnvelope;
|
|
29
|
+
export const StreamEventSchema = EventEnvelopeSchema;
|
|
30
|
+
export type StreamEventMetadata = EventMetadata;
|
|
31
|
+
export const StreamEventMetadataSchema = EventMetadataSchema;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NotificationChannelSchema, NotificationPrioritySchema } from "@/contracts/common.js";
|
|
3
|
+
import { RecipientProfileSchema } from "./notification-enriched.js";
|
|
4
|
+
|
|
5
|
+
export const NotificationAiPendingPayloadSchema = z.object({
|
|
6
|
+
projectId: z.string().uuid(),
|
|
7
|
+
enrichedEventId: z.string().uuid(),
|
|
8
|
+
recipientId: z.string().min(1),
|
|
9
|
+
channel: NotificationChannelSchema,
|
|
10
|
+
priority: NotificationPrioritySchema,
|
|
11
|
+
templateId: z.string().min(1).optional(),
|
|
12
|
+
templateVariables: z.record(z.string(), z.unknown()),
|
|
13
|
+
recipient: RecipientProfileSchema,
|
|
14
|
+
aiPrompts: z.record(z.string(), z.string()),
|
|
15
|
+
scheduledAt: z.string().datetime().optional(),
|
|
16
|
+
fallbackChain: z.array(NotificationChannelSchema).optional(),
|
|
17
|
+
});
|
|
18
|
+
export type NotificationAiPendingPayload = z.infer<typeof NotificationAiPendingPayloadSchema>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NotificationChannelSchema, NotificationPrioritySchema } from "@/contracts/common.js";
|
|
3
|
+
|
|
4
|
+
export const NotificationCreatedPayloadSchema = z.object({
|
|
5
|
+
projectId: z.string().uuid(),
|
|
6
|
+
recipientId: z.string().min(1),
|
|
7
|
+
channel: NotificationChannelSchema,
|
|
8
|
+
priority: NotificationPrioritySchema.default("normal"),
|
|
9
|
+
templateId: z.string().min(1).optional(),
|
|
10
|
+
payload: z.record(z.string(), z.unknown()),
|
|
11
|
+
scheduledAt: z.string().datetime().optional(),
|
|
12
|
+
idempotencyKey: z.string().optional(),
|
|
13
|
+
});
|
|
14
|
+
export type NotificationCreatedPayload = z.infer<typeof NotificationCreatedPayloadSchema>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NotificationChannelSchema } from "@/contracts/common.js";
|
|
3
|
+
|
|
4
|
+
export const NotificationDeliveredPayloadSchema = z.object({
|
|
5
|
+
projectId: z.string().uuid(),
|
|
6
|
+
taskId: z.string().min(1),
|
|
7
|
+
enrichedEventId: z.string().uuid(),
|
|
8
|
+
channel: NotificationChannelSchema,
|
|
9
|
+
deliveredAt: z.string().datetime(),
|
|
10
|
+
providerMessageId: z.string().optional(),
|
|
11
|
+
providerResponse: z.record(z.string(), z.unknown()).optional(),
|
|
12
|
+
templateId: z.string().uuid().optional(),
|
|
13
|
+
workflowInstanceId: z.string().uuid().optional(),
|
|
14
|
+
/** Campaign this message belongs to, carried from the originating request. */
|
|
15
|
+
campaignId: z.string().min(1).max(128).optional(),
|
|
16
|
+
});
|
|
17
|
+
export type NotificationDeliveredPayload = z.infer<typeof NotificationDeliveredPayloadSchema>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NotificationChannelSchema, NotificationPrioritySchema } from "@/contracts/common.js";
|
|
3
|
+
import { RecipientProfileSchema } from "./notification-enriched.js";
|
|
4
|
+
|
|
5
|
+
export const RenderedContentSchema = z.object({
|
|
6
|
+
content: z.record(z.string(), z.unknown()),
|
|
7
|
+
attachments: z
|
|
8
|
+
.array(
|
|
9
|
+
z.object({
|
|
10
|
+
name: z.string(),
|
|
11
|
+
contentType: z.string(),
|
|
12
|
+
url: z.string().url(),
|
|
13
|
+
}),
|
|
14
|
+
)
|
|
15
|
+
.optional(),
|
|
16
|
+
});
|
|
17
|
+
export type RenderedContent = z.infer<typeof RenderedContentSchema>;
|
|
18
|
+
|
|
19
|
+
export const DeliveryOptionsSchema = z.object({
|
|
20
|
+
maxAttempts: z.number().int().positive().default(3),
|
|
21
|
+
timeoutMs: z.number().int().positive().default(10_000),
|
|
22
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
23
|
+
});
|
|
24
|
+
export type DeliveryOptions = z.infer<typeof DeliveryOptionsSchema>;
|
|
25
|
+
|
|
26
|
+
export const NotificationDispatchedPayloadSchema = z.object({
|
|
27
|
+
projectId: z.string().uuid(),
|
|
28
|
+
taskId: z.string().min(1),
|
|
29
|
+
enrichedEventId: z.string().uuid(),
|
|
30
|
+
recipientId: z.string().min(1),
|
|
31
|
+
channel: NotificationChannelSchema,
|
|
32
|
+
priority: NotificationPrioritySchema,
|
|
33
|
+
templateId: z.string().min(1).optional(),
|
|
34
|
+
templateVariables: z.record(z.string(), z.unknown()).default({}),
|
|
35
|
+
aiPrompts: z.record(z.string(), z.string()).optional(),
|
|
36
|
+
recipient: RecipientProfileSchema.optional(),
|
|
37
|
+
renderedContent: RenderedContentSchema,
|
|
38
|
+
destination: z.string().min(1).optional(),
|
|
39
|
+
deliveryOptions: DeliveryOptionsSchema,
|
|
40
|
+
fallbackChain: z.array(NotificationChannelSchema).optional(),
|
|
41
|
+
throttleAttemptCount: z.number().int().nonnegative().optional(),
|
|
42
|
+
/** Campaign this message belongs to, carried from the originating request. */
|
|
43
|
+
campaignId: z.string().min(1).max(128).optional(),
|
|
44
|
+
});
|
|
45
|
+
export type NotificationDispatchedPayload = z.infer<typeof NotificationDispatchedPayloadSchema>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NotificationChannelSchema, NotificationPrioritySchema } from "@/contracts/common.js";
|
|
3
|
+
|
|
4
|
+
export const RecipientProfileSchema = z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
email: z.string().email().optional(),
|
|
7
|
+
phone: z.string().optional(),
|
|
8
|
+
webhook: z.string().url().optional(),
|
|
9
|
+
pushTokens: z.array(z.string()).optional(),
|
|
10
|
+
pushToken: z.string().optional(),
|
|
11
|
+
telegram: z.string().optional(),
|
|
12
|
+
discord: z.string().url().optional(),
|
|
13
|
+
slack: z.string().optional(),
|
|
14
|
+
locale: z.string().default("en"),
|
|
15
|
+
timezone: z.string().default("UTC"),
|
|
16
|
+
preferences: z.object({
|
|
17
|
+
optedOut: z.boolean().default(false),
|
|
18
|
+
channels: z.array(NotificationChannelSchema).default([]),
|
|
19
|
+
quietHours: z
|
|
20
|
+
.array(
|
|
21
|
+
z.object({
|
|
22
|
+
start: z.string(),
|
|
23
|
+
end: z.string(),
|
|
24
|
+
}),
|
|
25
|
+
)
|
|
26
|
+
.optional(),
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
export type RecipientProfile = z.infer<typeof RecipientProfileSchema>;
|
|
30
|
+
|
|
31
|
+
export const NotificationEnrichedPayloadSchema = z.object({
|
|
32
|
+
projectId: z.string().uuid(),
|
|
33
|
+
rawEventId: z.string().uuid(),
|
|
34
|
+
recipientId: z.string().min(1),
|
|
35
|
+
channel: NotificationChannelSchema,
|
|
36
|
+
priority: NotificationPrioritySchema,
|
|
37
|
+
templateId: z.string().min(1).optional(),
|
|
38
|
+
templateVariables: z.record(z.string(), z.unknown()),
|
|
39
|
+
recipient: RecipientProfileSchema,
|
|
40
|
+
aiPrompts: z.record(z.string(), z.string()).optional(),
|
|
41
|
+
scheduledAt: z.string().datetime().optional(),
|
|
42
|
+
fallbackChain: z.array(NotificationChannelSchema).optional(),
|
|
43
|
+
/** Campaign this message belongs to, carried from the originating request. */
|
|
44
|
+
campaignId: z.string().min(1).max(128).optional(),
|
|
45
|
+
});
|
|
46
|
+
export type NotificationEnrichedPayload = z.infer<typeof NotificationEnrichedPayloadSchema>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NotificationChannelSchema } from "@/contracts/common.js";
|
|
3
|
+
|
|
4
|
+
export const NotificationFailedPayloadSchema = z.object({
|
|
5
|
+
projectId: z.string().uuid(),
|
|
6
|
+
taskId: z.string().min(1),
|
|
7
|
+
enrichedEventId: z.string().uuid(),
|
|
8
|
+
channel: NotificationChannelSchema,
|
|
9
|
+
failureReason: z.string(),
|
|
10
|
+
failureCode: z.string(),
|
|
11
|
+
retryable: z.boolean(),
|
|
12
|
+
attempt: z.number().int().positive(),
|
|
13
|
+
providerResponse: z.record(z.string(), z.unknown()).optional(),
|
|
14
|
+
templateId: z.string().uuid().optional(),
|
|
15
|
+
workflowInstanceId: z.string().uuid().optional(),
|
|
16
|
+
/** Campaign this message belongs to, carried from the originating request. */
|
|
17
|
+
campaignId: z.string().min(1).max(128).optional(),
|
|
18
|
+
});
|
|
19
|
+
export type NotificationFailedPayload = z.infer<typeof NotificationFailedPayloadSchema>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NotificationChannelSchema, NotificationPrioritySchema } from "@/contracts/common.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A high-level notification request as issued by the SDK's `notify()` call.
|
|
6
|
+
*
|
|
7
|
+
* Unlike `notification.created` (which targets a single resolved recipient),
|
|
8
|
+
* this event carries the *unresolved* target — a user id, a segment, or a
|
|
9
|
+
* topic. A downstream resolver stage fans it out into one
|
|
10
|
+
* `notification.created` per matching recipient, applying preference filters.
|
|
11
|
+
*/
|
|
12
|
+
export const NotificationTargetSchema = z.discriminatedUnion("type", [
|
|
13
|
+
z.object({ type: z.literal("user"), userId: z.string().min(1) }),
|
|
14
|
+
z.object({ type: z.literal("segment"), segment: z.string().min(1) }),
|
|
15
|
+
z.object({ type: z.literal("topic"), topic: z.string().min(1) }),
|
|
16
|
+
]);
|
|
17
|
+
export type NotificationTarget = z.infer<typeof NotificationTargetSchema>;
|
|
18
|
+
|
|
19
|
+
export const NotificationRequestedPayloadSchema = z.object({
|
|
20
|
+
projectId: z.string().uuid(),
|
|
21
|
+
target: NotificationTargetSchema,
|
|
22
|
+
templateId: z.string().min(1),
|
|
23
|
+
priority: NotificationPrioritySchema.default("normal"),
|
|
24
|
+
channels: z.array(NotificationChannelSchema).nonempty().optional(),
|
|
25
|
+
data: z.record(z.string(), z.unknown()).default({}),
|
|
26
|
+
fallback: z.boolean().default(false),
|
|
27
|
+
aiPrompts: z.record(z.string(), z.string()).optional(),
|
|
28
|
+
scheduledAt: z.string().datetime().optional(),
|
|
29
|
+
idempotencyKey: z.string().optional(),
|
|
30
|
+
/**
|
|
31
|
+
* Groups every message this request fans out into, so the send can be
|
|
32
|
+
* reported on afterwards. Carried unchanged to the delivery log.
|
|
33
|
+
*/
|
|
34
|
+
campaignId: z.string().min(1).max(128).optional(),
|
|
35
|
+
});
|
|
36
|
+
export type NotificationRequestedPayload = z.infer<typeof NotificationRequestedPayloadSchema>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const NotificationScheduledPayloadSchema = z.object({
|
|
4
|
+
projectId: z.string().uuid(),
|
|
5
|
+
enrichedEventId: z.string().uuid(),
|
|
6
|
+
taskId: z.string().min(1),
|
|
7
|
+
scheduledAt: z.string().datetime(),
|
|
8
|
+
});
|
|
9
|
+
export type NotificationScheduledPayload = z.infer<typeof NotificationScheduledPayloadSchema>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const NotificationSkippedPayloadSchema = z.object({
|
|
4
|
+
projectId: z.string().uuid(),
|
|
5
|
+
eventId: z.string().uuid(),
|
|
6
|
+
recipientId: z.string(),
|
|
7
|
+
reason: z.string(),
|
|
8
|
+
});
|
|
9
|
+
export type NotificationSkippedPayload = z.infer<typeof NotificationSkippedPayloadSchema>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { type EventEnvelope } from "./envelope.js";
|
|
3
|
+
|
|
4
|
+
// Legacy factory helpers — backward compatible with the queue package's publish() API,
|
|
5
|
+
// which accepts Omit<StreamEvent, "id" | "timestamp">.
|
|
6
|
+
export function buildStreamEvent(
|
|
7
|
+
type: string,
|
|
8
|
+
payload: Record<string, unknown>,
|
|
9
|
+
source: string,
|
|
10
|
+
traceId?: string,
|
|
11
|
+
): Omit<EventEnvelope, "id" | "timestamp"> {
|
|
12
|
+
return {
|
|
13
|
+
type,
|
|
14
|
+
payload,
|
|
15
|
+
metadata: {
|
|
16
|
+
traceId: traceId ?? randomUUID(),
|
|
17
|
+
source,
|
|
18
|
+
retryCount: 0,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Core building blocks
|
|
2
|
+
export * from "./common.js";
|
|
3
|
+
export * from "./metadata.js";
|
|
4
|
+
export * from "./registry.js";
|
|
5
|
+
export * from "./envelope.js";
|
|
6
|
+
export * from "./streams.js";
|
|
7
|
+
export * from "./helpers.js";
|
|
8
|
+
export * from "./sdk.js";
|
|
9
|
+
|
|
10
|
+
// Event payload schemas and types
|
|
11
|
+
export * from "./events/notification-requested.js";
|
|
12
|
+
export * from "./events/notification-created.js";
|
|
13
|
+
export * from "./events/notification-enriched.js";
|
|
14
|
+
export * from "./events/notification-scheduled.js";
|
|
15
|
+
export * from "./events/notification-dispatched.js";
|
|
16
|
+
export * from "./events/notification-delivered.js";
|
|
17
|
+
export * from "./events/notification-failed.js";
|
|
18
|
+
export * from "./events/notification-skipped.js";
|
|
19
|
+
export * from "./events/notification-canceled.js";
|
|
20
|
+
export * from "./events/notification-ai-pending.js";
|
|
21
|
+
|
|
22
|
+
// Register all built-in event schemas in the global registry.
|
|
23
|
+
// This runs once at module load time; call registry.define() in your own
|
|
24
|
+
// module to add new event types without modifying this file.
|
|
25
|
+
import { registry } from "./registry.js";
|
|
26
|
+
import { NotificationRequestedPayloadSchema } from "./events/notification-requested.js";
|
|
27
|
+
import { NotificationCreatedPayloadSchema } from "./events/notification-created.js";
|
|
28
|
+
import { NotificationEnrichedPayloadSchema } from "./events/notification-enriched.js";
|
|
29
|
+
import { NotificationScheduledPayloadSchema } from "./events/notification-scheduled.js";
|
|
30
|
+
import { NotificationDispatchedPayloadSchema } from "./events/notification-dispatched.js";
|
|
31
|
+
import { NotificationDeliveredPayloadSchema } from "./events/notification-delivered.js";
|
|
32
|
+
import { NotificationFailedPayloadSchema } from "./events/notification-failed.js";
|
|
33
|
+
import { NotificationSkippedPayloadSchema } from "./events/notification-skipped.js";
|
|
34
|
+
import { NotificationCanceledPayloadSchema } from "./events/notification-canceled.js";
|
|
35
|
+
import { NotificationAiPendingPayloadSchema } from "./events/notification-ai-pending.js";
|
|
36
|
+
|
|
37
|
+
registry.define("notification.requested", NotificationRequestedPayloadSchema);
|
|
38
|
+
registry.define("notification.created", NotificationCreatedPayloadSchema);
|
|
39
|
+
registry.define("notification.enriched", NotificationEnrichedPayloadSchema);
|
|
40
|
+
registry.define("notification.scheduled", NotificationScheduledPayloadSchema);
|
|
41
|
+
registry.define("notification.dispatched", NotificationDispatchedPayloadSchema);
|
|
42
|
+
registry.define("notification.delivered", NotificationDeliveredPayloadSchema);
|
|
43
|
+
registry.define("notification.failed", NotificationFailedPayloadSchema);
|
|
44
|
+
registry.define("notification.skipped", NotificationSkippedPayloadSchema);
|
|
45
|
+
registry.define("notification.canceled", NotificationCanceledPayloadSchema);
|
|
46
|
+
registry.define("notification.ai_pending", NotificationAiPendingPayloadSchema);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const EventMetadataSchema = z.object({
|
|
4
|
+
traceId: z.string(),
|
|
5
|
+
source: z.string(),
|
|
6
|
+
retryCount: z.number().int().nonnegative().default(0),
|
|
7
|
+
correlationId: z.string().optional(),
|
|
8
|
+
causationId: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
export type EventMetadata = z.infer<typeof EventMetadataSchema>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z, type ZodTypeAny, type ZodError } from "zod";
|
|
2
|
+
import type { NotificationRequestedPayload } from "./events/notification-requested.js";
|
|
3
|
+
import type { NotificationCreatedPayload } from "./events/notification-created.js";
|
|
4
|
+
import type { NotificationEnrichedPayload } from "./events/notification-enriched.js";
|
|
5
|
+
import type { NotificationScheduledPayload } from "./events/notification-scheduled.js";
|
|
6
|
+
import type { NotificationDispatchedPayload } from "./events/notification-dispatched.js";
|
|
7
|
+
import type { NotificationDeliveredPayload } from "./events/notification-delivered.js";
|
|
8
|
+
import type { NotificationFailedPayload } from "./events/notification-failed.js";
|
|
9
|
+
import type { NotificationSkippedPayload } from "./events/notification-skipped.js";
|
|
10
|
+
import type { NotificationCanceledPayload } from "./events/notification-canceled.js";
|
|
11
|
+
import type { NotificationAiPendingPayload } from "./events/notification-ai-pending.js";
|
|
12
|
+
/**
|
|
13
|
+
* Built-in event payload types.
|
|
14
|
+
* External packages extend this interface via declaration merging:
|
|
15
|
+
*
|
|
16
|
+
* declare module "../index.js" {
|
|
17
|
+
* interface EventPayloadMap {
|
|
18
|
+
* "my.custom.event": { field: string };
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Then call registry.define("my.custom.event", MySchema) at app startup.
|
|
23
|
+
*/
|
|
24
|
+
export interface EventPayloadMap {
|
|
25
|
+
"notification.requested": NotificationRequestedPayload;
|
|
26
|
+
"notification.created": NotificationCreatedPayload;
|
|
27
|
+
"notification.enriched": NotificationEnrichedPayload;
|
|
28
|
+
"notification.scheduled": NotificationScheduledPayload;
|
|
29
|
+
"notification.dispatched": NotificationDispatchedPayload;
|
|
30
|
+
"notification.delivered": NotificationDeliveredPayload;
|
|
31
|
+
"notification.failed": NotificationFailedPayload;
|
|
32
|
+
"notification.skipped": NotificationSkippedPayload;
|
|
33
|
+
"notification.canceled": NotificationCanceledPayload;
|
|
34
|
+
"notification.ai_pending": NotificationAiPendingPayload;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type KnownEventType = keyof EventPayloadMap & string;
|
|
38
|
+
|
|
39
|
+
export type ParseResult<T> = { success: true; data: T } | { success: false; error: ZodError };
|
|
40
|
+
|
|
41
|
+
export class EventRegistry {
|
|
42
|
+
private readonly schemas = new Map<string, ZodTypeAny>();
|
|
43
|
+
|
|
44
|
+
define(type: string, schema: ZodTypeAny): void {
|
|
45
|
+
if (this.schemas.has(type)) {
|
|
46
|
+
throw new Error(`Event type "${type}" is already registered`);
|
|
47
|
+
}
|
|
48
|
+
this.schemas.set(type, schema);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getSchema(type: string): ZodTypeAny | undefined {
|
|
52
|
+
return this.schemas.get(type);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
has(type: string): boolean {
|
|
56
|
+
return this.schemas.has(type);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
types(): string[] {
|
|
60
|
+
return [...this.schemas.keys()];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
parsePayload<K extends KnownEventType>(type: K, payload: unknown): EventPayloadMap[K] {
|
|
64
|
+
const schema = this.schemas.get(type);
|
|
65
|
+
if (!schema) throw new Error(`Unknown event type: "${type}"`);
|
|
66
|
+
return schema.parse(payload) as EventPayloadMap[K];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
safeParsePayload<K extends KnownEventType>(
|
|
70
|
+
type: K,
|
|
71
|
+
payload: unknown,
|
|
72
|
+
): ParseResult<EventPayloadMap[K]> {
|
|
73
|
+
const schema = this.schemas.get(type);
|
|
74
|
+
if (!schema) {
|
|
75
|
+
return {
|
|
76
|
+
success: false,
|
|
77
|
+
error: new z.ZodError([
|
|
78
|
+
{ code: "custom", message: `Unknown event type: "${type}"`, path: [] },
|
|
79
|
+
]),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const result = schema.safeParse(payload);
|
|
83
|
+
if (result.success) return { success: true, data: result.data as EventPayloadMap[K] };
|
|
84
|
+
return { success: false, error: result.error };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const registry = new EventRegistry();
|