Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(recipes): Add type-safe bulk trigger workflow recipe #726

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,8 @@
"recipes/workflows/shipping-confirmation",
"recipes/workflows/feedback-reviews",
"recipes/workflows/multi-workflow-digest",
"recipes/workflows/translations"
"recipes/workflows/translations",
"recipes/workflows/bulk-trigger-typesafe"
]
},
{
Expand Down
143 changes: 143 additions & 0 deletions recipes/workflows/bulk-trigger-typesafe.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
title: 'Type-safe Bulk Trigger'
sidebarTitle: 'Type-safe Bulk Trigger'
description: 'Bulk trigger your workflows with type-safe data'
---

**Use case:** You have a list of `@novu/framework` defined `workflow`'s that you want to bulk trigger using the `POST /v1/events/trigger/bulk` endpoint with type-safe data.

Start by declaring your workflows with a `payloadSchema` as you would normally do.

```typescript workflows.ts
import { workflow } from '@novu/framework';
import { z } from 'zod';

const smsWorkflow = workflow(
'sms-workflow',
async ({ step, payload }) => {
await step.sms('send-sms', async () => ({
body: payload.greeting,
}));
},
{
payloadSchema: z.object({
greeting: z.enum(['Hello', 'Hi']),
}),
}
);

const emailWorkflow = workflow(
'email-workflow',
async ({ step, payload }) => {
await step.email('send-email', async () => ({
body: 'Static body',
subject: payload.subject,
}));
},
{
payloadSchema: z.object({
subject: z.string(),
}),
}
);

// Declare your list of workflows to pass to the `serve` function
// for your chosen web framework
export const workflowList = [smsWorkflow, emailWorkflow];

// Construct a type for your list of workflows to pass across module
// boundaries. This is later used to type the bulk trigger method.
export type MyWorkflows = typeof workflowList;
```

Next, create a `typedBulkTrigger` utility type using the `InferWorkflowId` and `InferWorkflowPayload` types to strongly type the bulk trigger method.

```typescript utils.ts
import {
InferWorkflowId,
InferWorkflowPayload,
Workflow,
} from '@novu/framework';
import { Novu } from '@novu/node';

// Initialize the Novu client
const novu = new Novu('my-secret-key');

// A single, default bulk trigger event for the bulk trigger method
type BulkTriggerEvent = Parameters<Novu['bulkTrigger']>[0][number];

// A generic type for a bulk trigger event with type-safe `name` and `payload`
type BulkEventParams<T_Workflows extends Workflow[]> = {
[K in InferWorkflowId<T_Workflows[number]>]: Omit<
BulkTriggerEvent,
'name' | 'payload'
> & {
name: K;
payload: InferWorkflowPayload<T_Workflows, K>;
};
}[InferWorkflowId<T_Workflows[number]>];

// Using the `BulkEventParams` utility type to strongly type the
// bulk trigger method. The function takes a generic parameter of
// `T_Workflows` which is your list of workflows (e.g. `MyWorkflows`).
export const typedBulkTrigger = <T_Workflows extends Workflow[]>(
params: BulkEventParams<T_Workflows>[]
): ReturnType<Novu['bulkTrigger']> => {
return novu.bulkTrigger(params);
};
```

Finally, use the `typedBulkTrigger` utility type in your notifications business logic with your `MyWorkflows` type provided as a generic parameter to the `typedBulkTrigger` function.

```typescript main.ts
import { MyWorkflows } from './workflows';
import { typedBulkTrigger } from './utils';

const main = async () => {
// Example bulk trigger usage, with strongly typed `name` and `payload`
await typedBulkTrigger<MyWorkflows>([
{
name: 'sms-workflow',
payload: {
greeting: 'Hello',
},
to: '+1234567890',
},
{
name: 'email-workflow',
payload: {
subject: 'Hello there',
},
to: '[email protected]',
},
]);

// Type safety on `name`!
await typedBulkTrigger<MyWorkflows>([
{
// @ts-expect-error - Type '"invalid-name"' is not assignable to type '"sms-workflow" | "email-workflow"'.ts(2322)
name: 'invalid-name',
payload: {
greeting: 'Hello',
},
to: '+1234567890',
},
]);

// Type safety on `payload`!
await typedBulkTrigger<MyWorkflows>([
{
name: 'sms-workflow',
payload: {
// @ts-expect-error - Type '"Hey"' is not assignable to type '"Hello" | "Hi"'.
greeting: 'Hey',
},
to: '+1234567890',
},
]);
};
```

Now you can trigger your workflows in bulk with type-safe data! As you expand your list of workflows, you can use the `typedBulkTrigger` utility type to ensure type safety across your entire list of workflows.

See the full example on [StackBlitz](https://stackblitz.com/edit/novu-type-safe-bulk-trigger?file=api%2Fnovu%2Froute.tsx&view=editor).