@clipboard-health/ai-rules 0.3.0 → 0.3.2

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 CHANGED
@@ -60,7 +60,7 @@ npm install --save-dev @clipboard-health/ai-rules
60
60
  git commit -m "feat: add AI coding rules"
61
61
  ```
62
62
 
63
- 5. That's it! Your AI assistants will automatically use these files.
63
+ 5. Bonus: For repo-specific rules, create an `OVERLAY.md` file. The generated files instruct agents to read this file if it exists for additional rules.
64
64
 
65
65
  ### Updating Rules
66
66
 
package/backend/AGENTS.md CHANGED
@@ -13,6 +13,149 @@
13
13
  - Requests and responses follow the JSON:API specification, including pagination for listings.
14
14
  - Use TypeDoc to document public functions, classes, methods, and complex code blocks.
15
15
 
16
+ <!-- Source: .ruler/backend/notifications.md -->
17
+
18
+ # Notifications
19
+
20
+ Send notifications through [Knock](https://docs.knock.app) using the `@clipboard-health/notifications` NPM library.
21
+
22
+ ## Usage
23
+
24
+ <embedex source="packages/notifications/examples/usage.md">
25
+
26
+ 1. Search your service for a `NotificationJobEnqueuer` instance. If there isn't one, create and export it:
27
+
28
+ ```ts
29
+ import { NotificationJobEnqueuer } from "@clipboard-health/notifications";
30
+
31
+ import { BackgroundJobsService } from "./setup";
32
+
33
+ // Create and export one instance of this in your microservice.
34
+ export const notificationJobEnqueuer = new NotificationJobEnqueuer({
35
+ // Use your instance of `@clipboard-health/mongo-jobs` or `@clipboard-health/background-jobs-postgres` here.
36
+ adapter: new BackgroundJobsService(),
37
+ });
38
+ ```
39
+
40
+ 1. Implement a minimal job, calling off to a NestJS service for any business logic and to send the notification.
41
+
42
+ ```ts
43
+ import { type BaseHandler } from "@clipboard-health/background-jobs-adapter";
44
+ import { type NotificationData } from "@clipboard-health/notifications";
45
+ import { isFailure, toError } from "@clipboard-health/util-ts";
46
+
47
+ import { type ExampleNotificationService } from "./exampleNotification.service";
48
+
49
+ export type ExampleNotificationData = NotificationData<{
50
+ workplaceId: string;
51
+ }>;
52
+
53
+ export const EXAMPLE_NOTIFICATION_JOB_NAME = "ExampleNotificationJob";
54
+
55
+ // For mongo-jobs, you'll implement HandlerInterface<ExampleNotificationData["Job"]>
56
+ // For background-jobs-postgres, you'll implement Handler<ExampleNotificationData["Job"]>
57
+ export class ExampleNotificationJob implements BaseHandler<ExampleNotificationData["Job"]> {
58
+ public name = EXAMPLE_NOTIFICATION_JOB_NAME;
59
+
60
+ constructor(private readonly service: ExampleNotificationService) {}
61
+
62
+ async perform(data: ExampleNotificationData["Job"], job: { attemptsCount: number }) {
63
+ const result = await this.service.sendNotification({
64
+ ...data,
65
+ // Include the job's attempts count for debugging, this is called `retryAttempts` in `background-jobs-postgres`.
66
+ attempt: job.attemptsCount + 1,
67
+ });
68
+
69
+ if (isFailure(result)) {
70
+ throw toError(result.error);
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ 1. Search your service for a constant that stores workflow keys. If there isn't one, create and export it:
77
+
78
+ ```ts
79
+ export const WORKFLOW_KEYS = {
80
+ eventStartingReminder: "event-starting-reminder",
81
+ } as const;
82
+ ```
83
+
84
+ 1. Enqueue your job:
85
+
86
+ ```ts
87
+ import {
88
+ EXAMPLE_NOTIFICATION_JOB_NAME,
89
+ type ExampleNotificationData,
90
+ } from "./exampleNotification.job";
91
+ import { notificationJobEnqueuer } from "./notificationJobEnqueuer";
92
+ import { WORKFLOW_KEYS } from "./workflowKeys";
93
+
94
+ async function enqueueNotificationJob() {
95
+ await notificationJobEnqueuer.enqueueOneOrMore<ExampleNotificationData["Enqueue"]>(
96
+ EXAMPLE_NOTIFICATION_JOB_NAME,
97
+ // Important: Read the TypeDoc documentation for additional context.
98
+ {
99
+ // Set expiresAt at enqueue-time so it remains stable across job retries.
100
+ // Use date-fns in your service instead of this manual calculation.
101
+ expiresAt: new Date(Date.now() + 60 * 60_000).toISOString(),
102
+ // Set idempotencyKey at enqueue-time so it remains stable across job retries.
103
+ idempotencyKey: {
104
+ resourceId: "event-123",
105
+ },
106
+ // Set recipients at enqueue-time so they respect our notification provider's limits.
107
+ recipients: ["userId-1"],
108
+
109
+ workflowKey: WORKFLOW_KEYS.eventStartingReminder,
110
+
111
+ // Any additional enqueue-time data passed to the job:
112
+ workplaceId: "workplaceId-123",
113
+ },
114
+ );
115
+ }
116
+
117
+ // eslint-disable-next-line unicorn/prefer-top-level-await
118
+ void enqueueNotificationJob();
119
+ ```
120
+
121
+ 1. Trigger the job in your NestJS service:
122
+
123
+ ```ts
124
+ import { type NotificationClient } from "@clipboard-health/notifications";
125
+
126
+ import { type ExampleNotificationData } from "./exampleNotification.job";
127
+
128
+ type ExampleNotificationDo = ExampleNotificationData["Job"] & { attempt: number };
129
+
130
+ export class ExampleNotificationService {
131
+ constructor(private readonly client: NotificationClient) {}
132
+
133
+ async sendNotification(params: ExampleNotificationDo) {
134
+ const { attempt, expiresAt, idempotencyKey, recipients, workflowKey, workplaceId } = params;
135
+
136
+ // Assume this comes from a database and are used as template variables...
137
+ // Use @clipboard-health/date-time's formatShortDateTime in your service for consistency.
138
+ const data = { favoriteColor: "blue", favoriteAt: new Date().toISOString(), secret: "2" };
139
+
140
+ // Important: Read the TypeDoc documentation for additional context.
141
+ return await this.client.trigger({
142
+ attempt,
143
+ body: {
144
+ data,
145
+ recipients,
146
+ workplaceId,
147
+ },
148
+ expiresAt: new Date(expiresAt),
149
+ idempotencyKey,
150
+ keysToRedact: ["secret"],
151
+ workflowKey,
152
+ });
153
+ }
154
+ }
155
+ ```
156
+
157
+ </embedex>
158
+
16
159
  <!-- Source: .ruler/common/codeStyleAndStructure.md -->
17
160
 
18
161
  # Code style and structure
package/backend/CLAUDE.md CHANGED
@@ -11,6 +11,149 @@
11
11
  - Requests and responses follow the JSON:API specification, including pagination for listings.
12
12
  - Use TypeDoc to document public functions, classes, methods, and complex code blocks.
13
13
 
14
+ <!-- Source: .ruler/backend/notifications.md -->
15
+
16
+ # Notifications
17
+
18
+ Send notifications through [Knock](https://docs.knock.app) using the `@clipboard-health/notifications` NPM library.
19
+
20
+ ## Usage
21
+
22
+ <embedex source="packages/notifications/examples/usage.md">
23
+
24
+ 1. Search your service for a `NotificationJobEnqueuer` instance. If there isn't one, create and export it:
25
+
26
+ ```ts
27
+ import { NotificationJobEnqueuer } from "@clipboard-health/notifications";
28
+
29
+ import { BackgroundJobsService } from "./setup";
30
+
31
+ // Create and export one instance of this in your microservice.
32
+ export const notificationJobEnqueuer = new NotificationJobEnqueuer({
33
+ // Use your instance of `@clipboard-health/mongo-jobs` or `@clipboard-health/background-jobs-postgres` here.
34
+ adapter: new BackgroundJobsService(),
35
+ });
36
+ ```
37
+
38
+ 1. Implement a minimal job, calling off to a NestJS service for any business logic and to send the notification.
39
+
40
+ ```ts
41
+ import { type BaseHandler } from "@clipboard-health/background-jobs-adapter";
42
+ import { type NotificationData } from "@clipboard-health/notifications";
43
+ import { isFailure, toError } from "@clipboard-health/util-ts";
44
+
45
+ import { type ExampleNotificationService } from "./exampleNotification.service";
46
+
47
+ export type ExampleNotificationData = NotificationData<{
48
+ workplaceId: string;
49
+ }>;
50
+
51
+ export const EXAMPLE_NOTIFICATION_JOB_NAME = "ExampleNotificationJob";
52
+
53
+ // For mongo-jobs, you'll implement HandlerInterface<ExampleNotificationData["Job"]>
54
+ // For background-jobs-postgres, you'll implement Handler<ExampleNotificationData["Job"]>
55
+ export class ExampleNotificationJob implements BaseHandler<ExampleNotificationData["Job"]> {
56
+ public name = EXAMPLE_NOTIFICATION_JOB_NAME;
57
+
58
+ constructor(private readonly service: ExampleNotificationService) {}
59
+
60
+ async perform(data: ExampleNotificationData["Job"], job: { attemptsCount: number }) {
61
+ const result = await this.service.sendNotification({
62
+ ...data,
63
+ // Include the job's attempts count for debugging, this is called `retryAttempts` in `background-jobs-postgres`.
64
+ attempt: job.attemptsCount + 1,
65
+ });
66
+
67
+ if (isFailure(result)) {
68
+ throw toError(result.error);
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ 1. Search your service for a constant that stores workflow keys. If there isn't one, create and export it:
75
+
76
+ ```ts
77
+ export const WORKFLOW_KEYS = {
78
+ eventStartingReminder: "event-starting-reminder",
79
+ } as const;
80
+ ```
81
+
82
+ 1. Enqueue your job:
83
+
84
+ ```ts
85
+ import {
86
+ EXAMPLE_NOTIFICATION_JOB_NAME,
87
+ type ExampleNotificationData,
88
+ } from "./exampleNotification.job";
89
+ import { notificationJobEnqueuer } from "./notificationJobEnqueuer";
90
+ import { WORKFLOW_KEYS } from "./workflowKeys";
91
+
92
+ async function enqueueNotificationJob() {
93
+ await notificationJobEnqueuer.enqueueOneOrMore<ExampleNotificationData["Enqueue"]>(
94
+ EXAMPLE_NOTIFICATION_JOB_NAME,
95
+ // Important: Read the TypeDoc documentation for additional context.
96
+ {
97
+ // Set expiresAt at enqueue-time so it remains stable across job retries.
98
+ // Use date-fns in your service instead of this manual calculation.
99
+ expiresAt: new Date(Date.now() + 60 * 60_000).toISOString(),
100
+ // Set idempotencyKey at enqueue-time so it remains stable across job retries.
101
+ idempotencyKey: {
102
+ resourceId: "event-123",
103
+ },
104
+ // Set recipients at enqueue-time so they respect our notification provider's limits.
105
+ recipients: ["userId-1"],
106
+
107
+ workflowKey: WORKFLOW_KEYS.eventStartingReminder,
108
+
109
+ // Any additional enqueue-time data passed to the job:
110
+ workplaceId: "workplaceId-123",
111
+ },
112
+ );
113
+ }
114
+
115
+ // eslint-disable-next-line unicorn/prefer-top-level-await
116
+ void enqueueNotificationJob();
117
+ ```
118
+
119
+ 1. Trigger the job in your NestJS service:
120
+
121
+ ```ts
122
+ import { type NotificationClient } from "@clipboard-health/notifications";
123
+
124
+ import { type ExampleNotificationData } from "./exampleNotification.job";
125
+
126
+ type ExampleNotificationDo = ExampleNotificationData["Job"] & { attempt: number };
127
+
128
+ export class ExampleNotificationService {
129
+ constructor(private readonly client: NotificationClient) {}
130
+
131
+ async sendNotification(params: ExampleNotificationDo) {
132
+ const { attempt, expiresAt, idempotencyKey, recipients, workflowKey, workplaceId } = params;
133
+
134
+ // Assume this comes from a database and are used as template variables...
135
+ // Use @clipboard-health/date-time's formatShortDateTime in your service for consistency.
136
+ const data = { favoriteColor: "blue", favoriteAt: new Date().toISOString(), secret: "2" };
137
+
138
+ // Important: Read the TypeDoc documentation for additional context.
139
+ return await this.client.trigger({
140
+ attempt,
141
+ body: {
142
+ data,
143
+ recipients,
144
+ workplaceId,
145
+ },
146
+ expiresAt: new Date(expiresAt),
147
+ idempotencyKey,
148
+ keysToRedact: ["secret"],
149
+ workflowKey,
150
+ });
151
+ }
152
+ }
153
+ ```
154
+
155
+ </embedex>
156
+
14
157
  <!-- Source: .ruler/common/codeStyleAndStructure.md -->
15
158
 
16
159
  # Code style and structure
@@ -1796,7 +1796,7 @@ npm run lint:fix
1796
1796
  ### Stale Time Configuration
1797
1797
 
1798
1798
  ```typescript
1799
- // Set appropriate staleTime to avoid unnecessary refetches
1799
+ // Set appropriate staleTime to avoid unnecessary refetch
1800
1800
  useGetQuery({
1801
1801
  url: "/api/resource",
1802
1802
  responseSchema: schema,
@@ -3094,14 +3094,14 @@ Projects often augment MUI's theme with custom properties:
3094
3094
  declare module "@mui/material/styles" {
3095
3095
  interface Theme {
3096
3096
  customSpacing: {
3097
+ small: string;
3097
3098
  large: string;
3098
- xlarge: string;
3099
3099
  };
3100
3100
  }
3101
3101
  interface ThemeOptions {
3102
3102
  customSpacing?: {
3103
+ small?: string;
3103
3104
  large?: string;
3104
- xlarge?: string;
3105
3105
  };
3106
3106
  }
3107
3107
  }
@@ -1794,7 +1794,7 @@ npm run lint:fix
1794
1794
  ### Stale Time Configuration
1795
1795
 
1796
1796
  ```typescript
1797
- // Set appropriate staleTime to avoid unnecessary refetches
1797
+ // Set appropriate staleTime to avoid unnecessary refetch
1798
1798
  useGetQuery({
1799
1799
  url: "/api/resource",
1800
1800
  responseSchema: schema,
@@ -3092,14 +3092,14 @@ Projects often augment MUI's theme with custom properties:
3092
3092
  declare module "@mui/material/styles" {
3093
3093
  interface Theme {
3094
3094
  customSpacing: {
3095
+ small: string;
3095
3096
  large: string;
3096
- xlarge: string;
3097
3097
  };
3098
3098
  }
3099
3099
  interface ThemeOptions {
3100
3100
  customSpacing?: {
3101
+ small?: string;
3101
3102
  large?: string;
3102
- xlarge?: string;
3103
3103
  };
3104
3104
  }
3105
3105
  }
@@ -13,6 +13,149 @@
13
13
  - Requests and responses follow the JSON:API specification, including pagination for listings.
14
14
  - Use TypeDoc to document public functions, classes, methods, and complex code blocks.
15
15
 
16
+ <!-- Source: .ruler/backend/notifications.md -->
17
+
18
+ # Notifications
19
+
20
+ Send notifications through [Knock](https://docs.knock.app) using the `@clipboard-health/notifications` NPM library.
21
+
22
+ ## Usage
23
+
24
+ <embedex source="packages/notifications/examples/usage.md">
25
+
26
+ 1. Search your service for a `NotificationJobEnqueuer` instance. If there isn't one, create and export it:
27
+
28
+ ```ts
29
+ import { NotificationJobEnqueuer } from "@clipboard-health/notifications";
30
+
31
+ import { BackgroundJobsService } from "./setup";
32
+
33
+ // Create and export one instance of this in your microservice.
34
+ export const notificationJobEnqueuer = new NotificationJobEnqueuer({
35
+ // Use your instance of `@clipboard-health/mongo-jobs` or `@clipboard-health/background-jobs-postgres` here.
36
+ adapter: new BackgroundJobsService(),
37
+ });
38
+ ```
39
+
40
+ 1. Implement a minimal job, calling off to a NestJS service for any business logic and to send the notification.
41
+
42
+ ```ts
43
+ import { type BaseHandler } from "@clipboard-health/background-jobs-adapter";
44
+ import { type NotificationData } from "@clipboard-health/notifications";
45
+ import { isFailure, toError } from "@clipboard-health/util-ts";
46
+
47
+ import { type ExampleNotificationService } from "./exampleNotification.service";
48
+
49
+ export type ExampleNotificationData = NotificationData<{
50
+ workplaceId: string;
51
+ }>;
52
+
53
+ export const EXAMPLE_NOTIFICATION_JOB_NAME = "ExampleNotificationJob";
54
+
55
+ // For mongo-jobs, you'll implement HandlerInterface<ExampleNotificationData["Job"]>
56
+ // For background-jobs-postgres, you'll implement Handler<ExampleNotificationData["Job"]>
57
+ export class ExampleNotificationJob implements BaseHandler<ExampleNotificationData["Job"]> {
58
+ public name = EXAMPLE_NOTIFICATION_JOB_NAME;
59
+
60
+ constructor(private readonly service: ExampleNotificationService) {}
61
+
62
+ async perform(data: ExampleNotificationData["Job"], job: { attemptsCount: number }) {
63
+ const result = await this.service.sendNotification({
64
+ ...data,
65
+ // Include the job's attempts count for debugging, this is called `retryAttempts` in `background-jobs-postgres`.
66
+ attempt: job.attemptsCount + 1,
67
+ });
68
+
69
+ if (isFailure(result)) {
70
+ throw toError(result.error);
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ 1. Search your service for a constant that stores workflow keys. If there isn't one, create and export it:
77
+
78
+ ```ts
79
+ export const WORKFLOW_KEYS = {
80
+ eventStartingReminder: "event-starting-reminder",
81
+ } as const;
82
+ ```
83
+
84
+ 1. Enqueue your job:
85
+
86
+ ```ts
87
+ import {
88
+ EXAMPLE_NOTIFICATION_JOB_NAME,
89
+ type ExampleNotificationData,
90
+ } from "./exampleNotification.job";
91
+ import { notificationJobEnqueuer } from "./notificationJobEnqueuer";
92
+ import { WORKFLOW_KEYS } from "./workflowKeys";
93
+
94
+ async function enqueueNotificationJob() {
95
+ await notificationJobEnqueuer.enqueueOneOrMore<ExampleNotificationData["Enqueue"]>(
96
+ EXAMPLE_NOTIFICATION_JOB_NAME,
97
+ // Important: Read the TypeDoc documentation for additional context.
98
+ {
99
+ // Set expiresAt at enqueue-time so it remains stable across job retries.
100
+ // Use date-fns in your service instead of this manual calculation.
101
+ expiresAt: new Date(Date.now() + 60 * 60_000).toISOString(),
102
+ // Set idempotencyKey at enqueue-time so it remains stable across job retries.
103
+ idempotencyKey: {
104
+ resourceId: "event-123",
105
+ },
106
+ // Set recipients at enqueue-time so they respect our notification provider's limits.
107
+ recipients: ["userId-1"],
108
+
109
+ workflowKey: WORKFLOW_KEYS.eventStartingReminder,
110
+
111
+ // Any additional enqueue-time data passed to the job:
112
+ workplaceId: "workplaceId-123",
113
+ },
114
+ );
115
+ }
116
+
117
+ // eslint-disable-next-line unicorn/prefer-top-level-await
118
+ void enqueueNotificationJob();
119
+ ```
120
+
121
+ 1. Trigger the job in your NestJS service:
122
+
123
+ ```ts
124
+ import { type NotificationClient } from "@clipboard-health/notifications";
125
+
126
+ import { type ExampleNotificationData } from "./exampleNotification.job";
127
+
128
+ type ExampleNotificationDo = ExampleNotificationData["Job"] & { attempt: number };
129
+
130
+ export class ExampleNotificationService {
131
+ constructor(private readonly client: NotificationClient) {}
132
+
133
+ async sendNotification(params: ExampleNotificationDo) {
134
+ const { attempt, expiresAt, idempotencyKey, recipients, workflowKey, workplaceId } = params;
135
+
136
+ // Assume this comes from a database and are used as template variables...
137
+ // Use @clipboard-health/date-time's formatShortDateTime in your service for consistency.
138
+ const data = { favoriteColor: "blue", favoriteAt: new Date().toISOString(), secret: "2" };
139
+
140
+ // Important: Read the TypeDoc documentation for additional context.
141
+ return await this.client.trigger({
142
+ attempt,
143
+ body: {
144
+ data,
145
+ recipients,
146
+ workplaceId,
147
+ },
148
+ expiresAt: new Date(expiresAt),
149
+ idempotencyKey,
150
+ keysToRedact: ["secret"],
151
+ workflowKey,
152
+ });
153
+ }
154
+ }
155
+ ```
156
+
157
+ </embedex>
158
+
16
159
  <!-- Source: .ruler/common/codeStyleAndStructure.md -->
17
160
 
18
161
  # Code style and structure
@@ -1809,7 +1952,7 @@ npm run lint:fix
1809
1952
  ### Stale Time Configuration
1810
1953
 
1811
1954
  ```typescript
1812
- // Set appropriate staleTime to avoid unnecessary refetches
1955
+ // Set appropriate staleTime to avoid unnecessary refetch
1813
1956
  useGetQuery({
1814
1957
  url: "/api/resource",
1815
1958
  responseSchema: schema,
@@ -3107,14 +3250,14 @@ Projects often augment MUI's theme with custom properties:
3107
3250
  declare module "@mui/material/styles" {
3108
3251
  interface Theme {
3109
3252
  customSpacing: {
3253
+ small: string;
3110
3254
  large: string;
3111
- xlarge: string;
3112
3255
  };
3113
3256
  }
3114
3257
  interface ThemeOptions {
3115
3258
  customSpacing?: {
3259
+ small?: string;
3116
3260
  large?: string;
3117
- xlarge?: string;
3118
3261
  };
3119
3262
  }
3120
3263
  }
@@ -11,6 +11,149 @@
11
11
  - Requests and responses follow the JSON:API specification, including pagination for listings.
12
12
  - Use TypeDoc to document public functions, classes, methods, and complex code blocks.
13
13
 
14
+ <!-- Source: .ruler/backend/notifications.md -->
15
+
16
+ # Notifications
17
+
18
+ Send notifications through [Knock](https://docs.knock.app) using the `@clipboard-health/notifications` NPM library.
19
+
20
+ ## Usage
21
+
22
+ <embedex source="packages/notifications/examples/usage.md">
23
+
24
+ 1. Search your service for a `NotificationJobEnqueuer` instance. If there isn't one, create and export it:
25
+
26
+ ```ts
27
+ import { NotificationJobEnqueuer } from "@clipboard-health/notifications";
28
+
29
+ import { BackgroundJobsService } from "./setup";
30
+
31
+ // Create and export one instance of this in your microservice.
32
+ export const notificationJobEnqueuer = new NotificationJobEnqueuer({
33
+ // Use your instance of `@clipboard-health/mongo-jobs` or `@clipboard-health/background-jobs-postgres` here.
34
+ adapter: new BackgroundJobsService(),
35
+ });
36
+ ```
37
+
38
+ 1. Implement a minimal job, calling off to a NestJS service for any business logic and to send the notification.
39
+
40
+ ```ts
41
+ import { type BaseHandler } from "@clipboard-health/background-jobs-adapter";
42
+ import { type NotificationData } from "@clipboard-health/notifications";
43
+ import { isFailure, toError } from "@clipboard-health/util-ts";
44
+
45
+ import { type ExampleNotificationService } from "./exampleNotification.service";
46
+
47
+ export type ExampleNotificationData = NotificationData<{
48
+ workplaceId: string;
49
+ }>;
50
+
51
+ export const EXAMPLE_NOTIFICATION_JOB_NAME = "ExampleNotificationJob";
52
+
53
+ // For mongo-jobs, you'll implement HandlerInterface<ExampleNotificationData["Job"]>
54
+ // For background-jobs-postgres, you'll implement Handler<ExampleNotificationData["Job"]>
55
+ export class ExampleNotificationJob implements BaseHandler<ExampleNotificationData["Job"]> {
56
+ public name = EXAMPLE_NOTIFICATION_JOB_NAME;
57
+
58
+ constructor(private readonly service: ExampleNotificationService) {}
59
+
60
+ async perform(data: ExampleNotificationData["Job"], job: { attemptsCount: number }) {
61
+ const result = await this.service.sendNotification({
62
+ ...data,
63
+ // Include the job's attempts count for debugging, this is called `retryAttempts` in `background-jobs-postgres`.
64
+ attempt: job.attemptsCount + 1,
65
+ });
66
+
67
+ if (isFailure(result)) {
68
+ throw toError(result.error);
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ 1. Search your service for a constant that stores workflow keys. If there isn't one, create and export it:
75
+
76
+ ```ts
77
+ export const WORKFLOW_KEYS = {
78
+ eventStartingReminder: "event-starting-reminder",
79
+ } as const;
80
+ ```
81
+
82
+ 1. Enqueue your job:
83
+
84
+ ```ts
85
+ import {
86
+ EXAMPLE_NOTIFICATION_JOB_NAME,
87
+ type ExampleNotificationData,
88
+ } from "./exampleNotification.job";
89
+ import { notificationJobEnqueuer } from "./notificationJobEnqueuer";
90
+ import { WORKFLOW_KEYS } from "./workflowKeys";
91
+
92
+ async function enqueueNotificationJob() {
93
+ await notificationJobEnqueuer.enqueueOneOrMore<ExampleNotificationData["Enqueue"]>(
94
+ EXAMPLE_NOTIFICATION_JOB_NAME,
95
+ // Important: Read the TypeDoc documentation for additional context.
96
+ {
97
+ // Set expiresAt at enqueue-time so it remains stable across job retries.
98
+ // Use date-fns in your service instead of this manual calculation.
99
+ expiresAt: new Date(Date.now() + 60 * 60_000).toISOString(),
100
+ // Set idempotencyKey at enqueue-time so it remains stable across job retries.
101
+ idempotencyKey: {
102
+ resourceId: "event-123",
103
+ },
104
+ // Set recipients at enqueue-time so they respect our notification provider's limits.
105
+ recipients: ["userId-1"],
106
+
107
+ workflowKey: WORKFLOW_KEYS.eventStartingReminder,
108
+
109
+ // Any additional enqueue-time data passed to the job:
110
+ workplaceId: "workplaceId-123",
111
+ },
112
+ );
113
+ }
114
+
115
+ // eslint-disable-next-line unicorn/prefer-top-level-await
116
+ void enqueueNotificationJob();
117
+ ```
118
+
119
+ 1. Trigger the job in your NestJS service:
120
+
121
+ ```ts
122
+ import { type NotificationClient } from "@clipboard-health/notifications";
123
+
124
+ import { type ExampleNotificationData } from "./exampleNotification.job";
125
+
126
+ type ExampleNotificationDo = ExampleNotificationData["Job"] & { attempt: number };
127
+
128
+ export class ExampleNotificationService {
129
+ constructor(private readonly client: NotificationClient) {}
130
+
131
+ async sendNotification(params: ExampleNotificationDo) {
132
+ const { attempt, expiresAt, idempotencyKey, recipients, workflowKey, workplaceId } = params;
133
+
134
+ // Assume this comes from a database and are used as template variables...
135
+ // Use @clipboard-health/date-time's formatShortDateTime in your service for consistency.
136
+ const data = { favoriteColor: "blue", favoriteAt: new Date().toISOString(), secret: "2" };
137
+
138
+ // Important: Read the TypeDoc documentation for additional context.
139
+ return await this.client.trigger({
140
+ attempt,
141
+ body: {
142
+ data,
143
+ recipients,
144
+ workplaceId,
145
+ },
146
+ expiresAt: new Date(expiresAt),
147
+ idempotencyKey,
148
+ keysToRedact: ["secret"],
149
+ workflowKey,
150
+ });
151
+ }
152
+ }
153
+ ```
154
+
155
+ </embedex>
156
+
14
157
  <!-- Source: .ruler/common/codeStyleAndStructure.md -->
15
158
 
16
159
  # Code style and structure
@@ -1807,7 +1950,7 @@ npm run lint:fix
1807
1950
  ### Stale Time Configuration
1808
1951
 
1809
1952
  ```typescript
1810
- // Set appropriate staleTime to avoid unnecessary refetches
1953
+ // Set appropriate staleTime to avoid unnecessary refetch
1811
1954
  useGetQuery({
1812
1955
  url: "/api/resource",
1813
1956
  responseSchema: schema,
@@ -3105,14 +3248,14 @@ Projects often augment MUI's theme with custom properties:
3105
3248
  declare module "@mui/material/styles" {
3106
3249
  interface Theme {
3107
3250
  customSpacing: {
3251
+ small: string;
3108
3252
  large: string;
3109
- xlarge: string;
3110
3253
  };
3111
3254
  }
3112
3255
  interface ThemeOptions {
3113
3256
  customSpacing?: {
3257
+ small?: string;
3114
3258
  large?: string;
3115
- xlarge?: string;
3116
3259
  };
3117
3260
  }
3118
3261
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clipboard-health/ai-rules",
3
3
  "description": "Pre-built AI agent rules for consistent coding standards.",
4
- "version": "0.3.0",
4
+ "version": "0.3.2",
5
5
  "bugs": "https://github.com/ClipboardHealth/core-utils/issues",
6
6
  "devDependencies": {
7
7
  "@intellectronica/ruler": "0.3.10"