vintasend-prisma 0.1.5 → 0.1.8

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.
@@ -1 +1 @@
1
- export { PrismaNotificationBackend } from './prisma-notification-backend';
1
+ export { PrismaNotificationBackend } from './prisma-notification-backend';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrismaNotificationBackend = void 0;
4
+ var prisma_notification_backend_1 = require("./prisma-notification-backend");
5
+ Object.defineProperty(exports, "PrismaNotificationBackend", { enumerable: true, get: function () { return prisma_notification_backend_1.PrismaNotificationBackend; } });
@@ -0,0 +1,159 @@
1
+ import type { BaseNotificationBackend } from 'vintasend/dist/services/notification-backends/base-notification-backend';
2
+ import type { ContextGenerator } from 'vintasend/dist/services/notification-context-registry';
3
+ import type { InputJsonValue, JsonValue } from 'vintasend/dist/types/json-values';
4
+ import type { DatabaseNotification, Notification, NotificationInput } from 'vintasend/dist/types/notification';
5
+ import type { NotificationStatus } from 'vintasend/dist/types/notification-status';
6
+ import type { NotificationType } from 'vintasend/dist/types/notification-type';
7
+ import type { Identifier } from 'vintasend/dist/types/identifier';
8
+ export declare const NotificationStatusEnum: {
9
+ readonly PENDING_SEND: "PENDING_SEND";
10
+ readonly SENT: "SENT";
11
+ readonly FAILED: "FAILED";
12
+ readonly READ: "READ";
13
+ readonly CANCELLED: "CANCELLED";
14
+ };
15
+ export declare const NotificationTypeEnum: {
16
+ readonly EMAIL: "EMAIL";
17
+ readonly PUSH: "PUSH";
18
+ readonly SMS: "SMS";
19
+ readonly IN_APP: "IN_APP";
20
+ };
21
+ export interface PrismaNotificationModel<IdType, UserId> {
22
+ id: IdType;
23
+ userId: UserId;
24
+ notificationType: NotificationType;
25
+ title: string | null;
26
+ bodyTemplate: string;
27
+ contextName: string;
28
+ contextParameters: JsonValue;
29
+ sendAfter: Date | null;
30
+ subjectTemplate: string | null;
31
+ status: NotificationStatus;
32
+ contextUsed: JsonValue | null;
33
+ extraParams: JsonValue | null;
34
+ adapterUsed: string | null;
35
+ sentAt: Date | null;
36
+ readAt: Date | null;
37
+ createdAt: Date;
38
+ updatedAt: Date;
39
+ user?: {
40
+ email: string;
41
+ };
42
+ }
43
+ export interface NotificationPrismaClientInterface<NotificationIdType, UserIdType> {
44
+ notification: {
45
+ findMany(args: {
46
+ where?: {
47
+ status?: NotificationStatus | {
48
+ not: NotificationStatus;
49
+ };
50
+ sendAfter?: {
51
+ lte: Date;
52
+ } | null;
53
+ userId?: UserIdType;
54
+ readAt?: null;
55
+ };
56
+ skip?: number;
57
+ take?: number;
58
+ include?: {
59
+ user?: boolean;
60
+ };
61
+ }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>[]>;
62
+ create(args: {
63
+ data: BaseNotificationCreateInput<UserIdType>;
64
+ include?: {
65
+ user?: boolean;
66
+ };
67
+ }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>>;
68
+ update(args: {
69
+ where: {
70
+ id: NotificationIdType;
71
+ };
72
+ data: Partial<BaseNotificationUpdateInput<UserIdType>>;
73
+ include?: {
74
+ user?: boolean;
75
+ };
76
+ }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>>;
77
+ findUnique(args: {
78
+ where: {
79
+ id: NotificationIdType;
80
+ };
81
+ include?: {
82
+ user?: boolean;
83
+ };
84
+ }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType> | null>;
85
+ };
86
+ }
87
+ type NoExpand<T> = T extends unknown ? T : never;
88
+ type AtLeast<O extends object, K extends string> = NoExpand<O extends unknown ? (K extends keyof O ? {
89
+ [P in K]: O[P];
90
+ } & O : O) | ({
91
+ [P in keyof O as P extends K ? K : never]-?: O[P];
92
+ } & O) : never>;
93
+ export interface BaseNotificationCreateInput<UserIdType> {
94
+ user: {
95
+ connect?: AtLeast<{
96
+ id?: UserIdType;
97
+ email?: string;
98
+ }, 'id' | 'email'>;
99
+ };
100
+ notificationType: NotificationType;
101
+ title?: string | null;
102
+ bodyTemplate: string;
103
+ contextName: string;
104
+ contextParameters: InputJsonValue;
105
+ sendAfter?: Date | null;
106
+ subjectTemplate?: string | null;
107
+ status?: NotificationStatus;
108
+ contextUsed?: InputJsonValue;
109
+ extraParams?: InputJsonValue;
110
+ adapterUsed?: string | null;
111
+ sentAt?: Date | null;
112
+ readAt?: Date | null;
113
+ }
114
+ export interface BaseNotificationUpdateInput<UserIdType> {
115
+ user: {
116
+ connect?: AtLeast<{
117
+ id?: UserIdType;
118
+ email?: string;
119
+ }, 'id' | 'email'>;
120
+ };
121
+ notificationType?: NotificationType;
122
+ title?: string | null;
123
+ bodyTemplate?: string;
124
+ contextName?: string;
125
+ contextParameters?: InputJsonValue;
126
+ sendAfter?: Date | null;
127
+ subjectTemplate?: string | null;
128
+ status?: NotificationStatus;
129
+ contextUsed?: InputJsonValue;
130
+ extraParams?: InputJsonValue;
131
+ adapterUsed?: string | null;
132
+ sentAt?: Date | null;
133
+ readAt?: Date | null;
134
+ }
135
+ export declare class PrismaNotificationBackend<Client extends NotificationPrismaClientInterface<NotificationIdType, UserIdType>, AvailableContexts extends Record<string, ContextGenerator>, NotificationIdType extends Identifier = Identifier, UserIdType extends Identifier = Identifier> implements BaseNotificationBackend<AvailableContexts, NotificationIdType, UserIdType> {
136
+ private prismaClient;
137
+ constructor(prismaClient: Client);
138
+ serializeNotification(notification: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>): DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>;
139
+ deserializeNotification(notification: NotificationInput<AvailableContexts, UserIdType>): BaseNotificationCreateInput<UserIdType>;
140
+ deserializeNotificationForUpdate(notification: Partial<Notification<AvailableContexts, NotificationIdType, UserIdType>>): Partial<Parameters<typeof this.prismaClient.notification.update>[0]['data']>;
141
+ getAllPendingNotifications(): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>[]>;
142
+ getPendingNotifications(): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>[]>;
143
+ getAllFutureNotifications(): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>[]>;
144
+ getFutureNotifications(): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>[]>;
145
+ getAllFutureNotificationsFromUser(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId']): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>[]>;
146
+ getFutureNotificationsFromUser(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId']): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>[]>;
147
+ persistNotification(notification: NotificationInput<AvailableContexts, UserIdType>): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>>;
148
+ persistNotificationUpdate(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], notification: Partial<Omit<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>, 'id'>>): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>>;
149
+ markPendingAsSent(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>>;
150
+ markPendingAsFailed(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>>;
151
+ markSentAsRead(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>>;
152
+ cancelNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<void>;
153
+ getNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], _forUpdate: boolean): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType> | null>;
154
+ filterAllInAppUnreadNotifications(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId']): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>[]>;
155
+ filterInAppUnreadNotifications(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId'], page: number, pageSize: number): Promise<DatabaseNotification<AvailableContexts, NotificationIdType, UserIdType>[]>;
156
+ getUserEmailFromNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<string | undefined>;
157
+ storeContextUsed(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], context: InputJsonValue): Promise<void>;
158
+ }
159
+ export {};
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrismaNotificationBackend = exports.NotificationTypeEnum = exports.NotificationStatusEnum = void 0;
4
+ exports.NotificationStatusEnum = {
5
+ PENDING_SEND: 'PENDING_SEND',
6
+ SENT: 'SENT',
7
+ FAILED: 'FAILED',
8
+ READ: 'READ',
9
+ CANCELLED: 'CANCELLED',
10
+ };
11
+ exports.NotificationTypeEnum = {
12
+ EMAIL: 'EMAIL',
13
+ PUSH: 'PUSH',
14
+ SMS: 'SMS',
15
+ IN_APP: 'IN_APP',
16
+ };
17
+ function convertJsonValueToRecord(jsonValue) {
18
+ if (typeof jsonValue === 'object' && !Array.isArray(jsonValue)) {
19
+ return jsonValue;
20
+ }
21
+ throw new Error('Invalid JSON value. It should be an object.');
22
+ }
23
+ class PrismaNotificationBackend {
24
+ constructor(prismaClient) {
25
+ this.prismaClient = prismaClient;
26
+ }
27
+ serializeNotification(notification) {
28
+ return {
29
+ id: notification.id,
30
+ userId: notification.userId,
31
+ notificationType: notification.notificationType,
32
+ title: notification.title,
33
+ bodyTemplate: notification.bodyTemplate,
34
+ contextName: notification.contextName,
35
+ contextParameters: notification.contextParameters
36
+ ? notification.contextParameters
37
+ : {},
38
+ sendAfter: notification.sendAfter,
39
+ subjectTemplate: notification.subjectTemplate,
40
+ status: notification.status,
41
+ contextUsed: notification.contextUsed,
42
+ extraParams: notification.extraParams
43
+ ? convertJsonValueToRecord(notification.extraParams)
44
+ : null,
45
+ adapterUsed: notification.adapterUsed,
46
+ sentAt: notification.sentAt,
47
+ readAt: notification.readAt,
48
+ createdAt: notification.createdAt,
49
+ updatedAt: notification.updatedAt,
50
+ };
51
+ }
52
+ deserializeNotification(notification) {
53
+ return {
54
+ user: {
55
+ connect: {
56
+ id: notification.userId,
57
+ },
58
+ },
59
+ notificationType: notification.notificationType,
60
+ title: notification.title,
61
+ bodyTemplate: notification.bodyTemplate,
62
+ contextName: notification.contextName,
63
+ contextParameters: notification.contextParameters,
64
+ sendAfter: notification.sendAfter,
65
+ subjectTemplate: notification.subjectTemplate,
66
+ extraParams: notification.extraParams,
67
+ };
68
+ }
69
+ deserializeNotificationForUpdate(notification) {
70
+ return {
71
+ ...(notification.userId ? { user: { connect: { id: notification.userId } } } : {}),
72
+ ...(notification.notificationType
73
+ ? {
74
+ notificationType: exports.NotificationTypeEnum[notification.notificationType],
75
+ }
76
+ : {}),
77
+ ...(notification.title ? { title: notification.title } : {}),
78
+ ...(notification.bodyTemplate ? { bodyTemplate: notification.bodyTemplate } : {}),
79
+ ...(notification.contextName ? { contextName: notification.contextName } : {}),
80
+ ...(notification.contextParameters
81
+ ? {
82
+ contextParameters: notification.contextParameters ? notification.contextParameters : {},
83
+ }
84
+ : {}),
85
+ ...(notification.sendAfter ? { sendAfter: notification.sendAfter } : {}),
86
+ ...(notification.subjectTemplate ? { subjectTemplate: notification.subjectTemplate } : {}),
87
+ };
88
+ }
89
+ async getAllPendingNotifications() {
90
+ const notifications = await this.prismaClient.notification.findMany({
91
+ where: {
92
+ status: exports.NotificationStatusEnum.PENDING_SEND,
93
+ },
94
+ });
95
+ return notifications.map(this.serializeNotification);
96
+ }
97
+ async getPendingNotifications() {
98
+ const notifications = await this.prismaClient.notification.findMany({
99
+ where: {
100
+ status: exports.NotificationStatusEnum.PENDING_SEND,
101
+ sendAfter: null,
102
+ },
103
+ });
104
+ return notifications.map(this.serializeNotification);
105
+ }
106
+ async getAllFutureNotifications() {
107
+ const notifications = await this.prismaClient.notification.findMany({
108
+ where: {
109
+ status: {
110
+ not: exports.NotificationStatusEnum.PENDING_SEND,
111
+ },
112
+ sendAfter: {
113
+ lte: new Date(),
114
+ },
115
+ },
116
+ });
117
+ return notifications.map(this.serializeNotification);
118
+ }
119
+ async getFutureNotifications() {
120
+ const notifications = await this.prismaClient.notification.findMany({
121
+ where: {
122
+ status: {
123
+ not: exports.NotificationStatusEnum.PENDING_SEND,
124
+ },
125
+ sendAfter: {
126
+ lte: new Date(),
127
+ },
128
+ },
129
+ });
130
+ return notifications.map(this.serializeNotification);
131
+ }
132
+ async getAllFutureNotificationsFromUser(userId) {
133
+ const notifications = await this.prismaClient.notification.findMany({
134
+ where: {
135
+ userId,
136
+ status: {
137
+ not: exports.NotificationStatusEnum.PENDING_SEND,
138
+ },
139
+ sendAfter: {
140
+ lte: new Date(),
141
+ },
142
+ },
143
+ });
144
+ return notifications.map(this.serializeNotification);
145
+ }
146
+ async getFutureNotificationsFromUser(userId) {
147
+ const notifications = await this.prismaClient.notification.findMany({
148
+ where: {
149
+ userId,
150
+ status: {
151
+ not: exports.NotificationStatusEnum.PENDING_SEND,
152
+ },
153
+ sendAfter: {
154
+ lte: new Date(),
155
+ },
156
+ },
157
+ });
158
+ return notifications.map(this.serializeNotification);
159
+ }
160
+ async persistNotification(notification) {
161
+ return this.serializeNotification(await this.prismaClient.notification.create({
162
+ data: this.deserializeNotification(notification),
163
+ }));
164
+ }
165
+ async persistNotificationUpdate(notificationId, notification) {
166
+ return this.serializeNotification(await this.prismaClient.notification.update({
167
+ where: {
168
+ id: notificationId,
169
+ },
170
+ data: this.deserializeNotificationForUpdate(notification),
171
+ }));
172
+ }
173
+ async markPendingAsSent(notificationId) {
174
+ return this.serializeNotification(await this.prismaClient.notification.update({
175
+ where: {
176
+ id: notificationId,
177
+ },
178
+ data: {
179
+ status: exports.NotificationStatusEnum.SENT,
180
+ sentAt: new Date(),
181
+ },
182
+ }));
183
+ }
184
+ async markPendingAsFailed(notificationId) {
185
+ return this.serializeNotification(await this.prismaClient.notification.update({
186
+ where: {
187
+ id: notificationId,
188
+ },
189
+ data: {
190
+ status: exports.NotificationStatusEnum.FAILED,
191
+ sentAt: new Date(),
192
+ },
193
+ }));
194
+ }
195
+ async markSentAsRead(notificationId) {
196
+ return this.serializeNotification(await this.prismaClient.notification.update({
197
+ where: {
198
+ id: notificationId,
199
+ },
200
+ data: {
201
+ status: 'READ',
202
+ readAt: new Date(),
203
+ },
204
+ }));
205
+ }
206
+ async cancelNotification(notificationId) {
207
+ await this.prismaClient.notification.update({
208
+ where: {
209
+ id: notificationId,
210
+ },
211
+ data: {
212
+ status: exports.NotificationStatusEnum.CANCELLED,
213
+ },
214
+ });
215
+ }
216
+ async getNotification(notificationId, _forUpdate) {
217
+ const notification = await this.prismaClient.notification.findUnique({
218
+ where: {
219
+ id: notificationId,
220
+ },
221
+ });
222
+ if (!notification) {
223
+ return null;
224
+ }
225
+ return this.serializeNotification(notification);
226
+ }
227
+ async filterAllInAppUnreadNotifications(userId) {
228
+ const notifications = await this.prismaClient.notification.findMany({
229
+ where: {
230
+ userId,
231
+ status: 'SENT',
232
+ readAt: null,
233
+ },
234
+ });
235
+ return notifications.map(this.serializeNotification);
236
+ }
237
+ async filterInAppUnreadNotifications(userId, page, pageSize) {
238
+ const notifications = await this.prismaClient.notification.findMany({
239
+ where: {
240
+ userId,
241
+ status: 'SENT',
242
+ readAt: null,
243
+ },
244
+ skip: page * pageSize,
245
+ take: pageSize,
246
+ });
247
+ return notifications.map(this.serializeNotification);
248
+ }
249
+ async getUserEmailFromNotification(notificationId) {
250
+ var _a;
251
+ const notification = await this.prismaClient.notification.findUnique({
252
+ where: {
253
+ id: notificationId,
254
+ },
255
+ include: {
256
+ user: true,
257
+ },
258
+ });
259
+ return (_a = notification === null || notification === void 0 ? void 0 : notification.user) === null || _a === void 0 ? void 0 : _a.email;
260
+ }
261
+ async storeContextUsed(notificationId, context) {
262
+ await this.prismaClient.notification.update({
263
+ where: {
264
+ id: notificationId,
265
+ },
266
+ data: {
267
+ contextUsed: context,
268
+ },
269
+ });
270
+ }
271
+ }
272
+ exports.PrismaNotificationBackend = PrismaNotificationBackend;
package/package.json CHANGED
@@ -1,17 +1,29 @@
1
1
  {
2
2
  "name": "vintasend-prisma",
3
- "version": "0.1.5",
3
+ "version": "0.1.8",
4
4
  "description": "VintaSend Backend implementation for Prisma",
5
- "main": "index.js",
5
+ "main": "dist/index.js",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1",
8
- "generate-prisma-client": "prisma generate"
7
+ "build": "tsc",
8
+ "prepublishOnly": "npm run build",
9
+ "test": "jest",
10
+ "test:watch": "jest --watch",
11
+ "test:coverage": "jest --coverage"
9
12
  },
10
- "author": "",
11
- "license": "ISC",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "author": "Hugo Bessa",
17
+ "license": "MIT",
12
18
  "dependencies": {
13
19
  "@prisma/client": "^6.3.1",
14
20
  "prisma": "^6.3.1",
15
- "vintasend": "^0.1.4"
21
+ "vintasend": "^0.1.8"
22
+ },
23
+ "devDependencies": {
24
+ "@types/jest": "^29.5.14",
25
+ "jest": "^29.7.0",
26
+ "ts-jest": "^29.2.6",
27
+ "typescript": "^5.8.2"
16
28
  }
17
29
  }
package/.editorconfig DELETED
@@ -1,9 +0,0 @@
1
- root = true
2
-
3
- [{src,scripts}/**.{ts,json,js}]
4
- end_of_line = crlf
5
- charset = utf-8
6
- trim_trailing_whitespace = true
7
- insert_final_newline = true
8
- indent_style = space
9
- indent_size = 2
package/biome.json DELETED
@@ -1,34 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
- "vcs": {
4
- "enabled": false,
5
- "clientKind": "git",
6
- "useIgnoreFile": false
7
- },
8
- "files": {
9
- "ignoreUnknown": false,
10
- "ignore": []
11
- },
12
- "formatter": {
13
- "enabled": true,
14
- "indentStyle": "space",
15
- "indentWidth": 2,
16
- "lineWidth": 100
17
- },
18
- "organizeImports": {
19
- "enabled": true
20
- },
21
- "linter": {
22
- "enabled": true,
23
- "rules": {
24
- "recommended": true
25
- }
26
- },
27
- "javascript": {
28
- "formatter": {
29
- "quoteStyle": "single",
30
- "semicolons": "always",
31
- "trailingCommas": "all"
32
- }
33
- }
34
- }
@@ -1,516 +0,0 @@
1
- import type { BaseNotificationBackend } from 'vintasend/src/services/notification-backends/base-notification-backend';
2
- import type { ContextGenerator } from 'vintasend/src/services/notification-context-registry';
3
- import type { InputJsonValue, JsonValue } from 'vintasend/src/types/json-values';
4
- import type { Notification, NotificationInput } from 'vintasend/src/types/notification';
5
- import type { NotificationStatus } from 'vintasend/src/types/notification-status';
6
- import type { NotificationType } from 'vintasend/src/types/notification-type';
7
- import type { Identifier } from 'vintasend/src/types/identifier';
8
-
9
- export const NotificationStatusEnum = {
10
- PENDING_SEND: 'PENDING_SEND',
11
- SENT: 'SENT',
12
- FAILED: 'FAILED',
13
- READ: 'READ',
14
- CANCELLED: 'CANCELLED',
15
- } as const;
16
-
17
- export const NotificationTypeEnum = {
18
- EMAIL: 'EMAIL',
19
- PUSH: 'PUSH',
20
- SMS: 'SMS',
21
- IN_APP: 'IN_APP',
22
- } as const;
23
-
24
- export interface PrismaNotificationModel<IdType, UserId> {
25
- id: IdType;
26
- userId: UserId;
27
- notificationType: NotificationType;
28
- title: string | null;
29
- bodyTemplate: string;
30
- contextName: string;
31
- contextParameters: JsonValue;
32
- sendAfter: Date | null;
33
- subjectTemplate: string | null;
34
- status: NotificationStatus;
35
- contextUsed: JsonValue | null;
36
- extraParams: JsonValue | null;
37
- adapterUsed: string | null;
38
- sentAt: Date | null;
39
- readAt: Date | null;
40
- createdAt: Date;
41
- updatedAt: Date;
42
- user?: {
43
- email: string;
44
- };
45
- }
46
-
47
- export interface NotificationPrismaClientInterface<NotificationIdType, UserIdType> {
48
- notification: {
49
- findMany(args: {
50
- where?: {
51
- status?: NotificationStatus | { not: NotificationStatus };
52
- sendAfter?: { lte: Date } | null;
53
- userId?: UserIdType;
54
- readAt?: null;
55
- };
56
- skip?: number;
57
- take?: number;
58
- include?: { user?: boolean };
59
- }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>[]>;
60
- create(args: {
61
- data: BaseNotificationCreateInput<UserIdType>;
62
- include?: { user?: boolean };
63
- }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>>;
64
- update(args: {
65
- where: { id: NotificationIdType };
66
- data: Partial<BaseNotificationUpdateInput<UserIdType>>;
67
- include?: { user?: boolean };
68
- }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>>;
69
- findUnique(args: {
70
- where: { id: NotificationIdType };
71
- include?: { user?: boolean };
72
- }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType> | null>;
73
- };
74
- }
75
-
76
- // cause typescript not to expand types and preserve names
77
- type NoExpand<T> = T extends unknown ? T : never;
78
-
79
- // this type assumes the passed object is entirely optional
80
- type AtLeast<O extends object, K extends string> = NoExpand<
81
- O extends unknown
82
- ?
83
- | (K extends keyof O ? { [P in K]: O[P] } & O : O)
84
- | ({ [P in keyof O as P extends K ? K : never]-?: O[P] } & O)
85
- : never
86
- >;
87
-
88
- export interface BaseNotificationCreateInput<UserIdType> {
89
- user: {
90
- connect?: AtLeast<
91
- {
92
- id?: UserIdType;
93
- email?: string;
94
- },
95
- 'id' | 'email'
96
- >;
97
- };
98
- notificationType: NotificationType;
99
- title?: string | null;
100
- bodyTemplate: string;
101
- contextName: string;
102
- contextParameters: InputJsonValue;
103
- sendAfter?: Date | null;
104
- subjectTemplate?: string | null;
105
- status?: NotificationStatus;
106
- contextUsed?: InputJsonValue;
107
- extraParams?: InputJsonValue;
108
- adapterUsed?: string | null;
109
- sentAt?: Date | null;
110
- readAt?: Date | null;
111
- }
112
-
113
- export interface BaseNotificationUpdateInput<UserIdType> {
114
- user: {
115
- connect?: AtLeast<
116
- {
117
- id?: UserIdType;
118
- email?: string;
119
- },
120
- 'id' | 'email'
121
- >;
122
- };
123
- notificationType?: NotificationType;
124
- title?: string | null;
125
- bodyTemplate?: string;
126
- contextName?: string;
127
- contextParameters?: InputJsonValue;
128
- sendAfter?: Date | null;
129
- subjectTemplate?: string | null;
130
- status?: NotificationStatus;
131
- contextUsed?: InputJsonValue;
132
- extraParams?: InputJsonValue;
133
- adapterUsed?: string | null;
134
- sentAt?: Date | null;
135
- readAt?: Date | null;
136
- }
137
-
138
- function convertJsonValueToRecord(jsonValue: JsonValue): Record<string, string | number | boolean> {
139
- if (typeof jsonValue === 'object' && !Array.isArray(jsonValue) && jsonValue !== null) {
140
- return jsonValue as Record<string, string | number | boolean>;
141
- }
142
-
143
- throw new Error('Invalid JSON value. It should be an object.');
144
- }
145
-
146
- export class PrismaNotificationBackend<
147
- Client extends NotificationPrismaClientInterface<NotificationIdType, UserIdType>,
148
- AvailableContexts extends Record<string, ContextGenerator>,
149
- NotificationIdType extends Identifier = Identifier,
150
- UserIdType extends Identifier = Identifier,
151
- > implements BaseNotificationBackend<AvailableContexts, NotificationIdType, UserIdType>
152
- {
153
- constructor(private prismaClient: Client) {}
154
-
155
- serializeNotification(
156
- notification: NonNullable<
157
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
158
- >,
159
- ): Notification<AvailableContexts, NotificationIdType, UserIdType> {
160
- return {
161
- id: notification.id,
162
- userId: notification.userId,
163
- notificationType: notification.notificationType,
164
- title: notification.title,
165
- bodyTemplate: notification.bodyTemplate,
166
- contextName: notification.contextName as keyof AvailableContexts,
167
- contextParameters: notification.contextParameters
168
- ? (notification.contextParameters as Parameters<
169
- AvailableContexts[keyof AvailableContexts]['generate']
170
- >[0])
171
- : {},
172
- sendAfter: notification.sendAfter,
173
- subjectTemplate: notification.subjectTemplate,
174
- status: notification.status,
175
- contextUsed: notification.contextUsed as ReturnType<
176
- AvailableContexts[keyof AvailableContexts]['generate']
177
- > | null,
178
- extraParams: notification.extraParams
179
- ? convertJsonValueToRecord(notification.extraParams)
180
- : null,
181
- adapterUsed: notification.adapterUsed,
182
- sentAt: notification.sentAt,
183
- readAt: notification.readAt,
184
- createdAt: notification.createdAt,
185
- updatedAt: notification.updatedAt,
186
- };
187
- }
188
-
189
- deserializeNotification(
190
- notification: NotificationInput<AvailableContexts, UserIdType>,
191
- ): BaseNotificationCreateInput<UserIdType> {
192
- return {
193
- user: {
194
- connect: {
195
- id: notification.userId,
196
- },
197
- },
198
- notificationType: notification.notificationType,
199
- title: notification.title,
200
- bodyTemplate: notification.bodyTemplate,
201
- contextName: notification.contextName as string,
202
- contextParameters: notification.contextParameters as InputJsonValue,
203
- sendAfter: notification.sendAfter,
204
- subjectTemplate: notification.subjectTemplate,
205
- extraParams: notification.extraParams as InputJsonValue,
206
- };
207
- }
208
-
209
- deserializeNotificationForUpdate(
210
- notification: Partial<Notification<AvailableContexts, NotificationIdType, UserIdType>>,
211
- ): Partial<Parameters<typeof this.prismaClient.notification.update>[0]['data']> {
212
- return {
213
- ...(notification.userId ? { user: { connect: { id: notification.userId } } } : {}),
214
- ...(notification.notificationType
215
- ? {
216
- notificationType: NotificationTypeEnum[
217
- notification.notificationType as keyof typeof NotificationTypeEnum
218
- ] as NotificationType,
219
- }
220
- : {}),
221
- ...(notification.title ? { title: notification.title } : {}),
222
- ...(notification.bodyTemplate ? { bodyTemplate: notification.bodyTemplate } : {}),
223
- ...(notification.contextName ? { contextName: notification.contextName } : {}),
224
- ...(notification.contextParameters
225
- ? {
226
- contextParameters: notification.contextParameters ? notification.contextParameters : {},
227
- }
228
- : {}),
229
- ...(notification.sendAfter ? { sendAfter: notification.sendAfter } : {}),
230
- ...(notification.subjectTemplate ? { subjectTemplate: notification.subjectTemplate } : {}),
231
- } as Partial<Parameters<typeof this.prismaClient.notification.update>[0]['data']>;
232
- }
233
-
234
- async getAllPendingNotifications(): Promise<
235
- Notification<AvailableContexts, NotificationIdType, UserIdType>[]
236
- > {
237
- const notifications = await this.prismaClient.notification.findMany({
238
- where: {
239
- status: NotificationStatusEnum.PENDING_SEND,
240
- },
241
- });
242
-
243
- return notifications.map(this.serializeNotification);
244
- }
245
-
246
- async getPendingNotifications(): Promise<
247
- Notification<AvailableContexts, NotificationIdType, UserIdType>[]
248
- > {
249
- const notifications = await this.prismaClient.notification.findMany({
250
- where: {
251
- status: NotificationStatusEnum.PENDING_SEND,
252
- sendAfter: null,
253
- },
254
- });
255
-
256
- return notifications.map(this.serializeNotification);
257
- }
258
-
259
- async getAllFutureNotifications(): Promise<
260
- Notification<AvailableContexts, NotificationIdType, UserIdType>[]
261
- > {
262
- const notifications = await this.prismaClient.notification.findMany({
263
- where: {
264
- status: {
265
- not: NotificationStatusEnum.PENDING_SEND,
266
- },
267
- sendAfter: {
268
- lte: new Date(),
269
- },
270
- },
271
- });
272
-
273
- return notifications.map(this.serializeNotification);
274
- }
275
-
276
- async getFutureNotifications(): Promise<
277
- Notification<AvailableContexts, NotificationIdType, UserIdType>[]
278
- > {
279
- const notifications = await this.prismaClient.notification.findMany({
280
- where: {
281
- status: {
282
- not: NotificationStatusEnum.PENDING_SEND,
283
- },
284
- sendAfter: {
285
- lte: new Date(),
286
- },
287
- },
288
- });
289
-
290
- return notifications.map(this.serializeNotification);
291
- }
292
-
293
- async getAllFutureNotificationsFromUser(
294
- userId: NonNullable<
295
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
296
- >['userId'],
297
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>[]> {
298
- const notifications = await this.prismaClient.notification.findMany({
299
- where: {
300
- userId,
301
- status: {
302
- not: NotificationStatusEnum.PENDING_SEND,
303
- },
304
- sendAfter: {
305
- lte: new Date(),
306
- },
307
- },
308
- });
309
-
310
- return notifications.map(this.serializeNotification);
311
- }
312
-
313
- async getFutureNotificationsFromUser(
314
- userId: NonNullable<
315
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
316
- >['userId'],
317
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>[]> {
318
- const notifications = await this.prismaClient.notification.findMany({
319
- where: {
320
- userId,
321
- status: {
322
- not: NotificationStatusEnum.PENDING_SEND,
323
- },
324
- sendAfter: {
325
- lte: new Date(),
326
- },
327
- },
328
- });
329
-
330
- return notifications.map(this.serializeNotification);
331
- }
332
-
333
- async persistNotification(
334
- notification: NotificationInput<AvailableContexts, UserIdType>,
335
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
336
- return this.serializeNotification(
337
- await this.prismaClient.notification.create({
338
- data: this.deserializeNotification(notification),
339
- }),
340
- );
341
- }
342
-
343
- async persistNotificationUpdate(
344
- notificationId: NonNullable<
345
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
346
- >['id'],
347
- notification: Partial<
348
- Omit<Notification<AvailableContexts, NotificationIdType, UserIdType>, 'id'>
349
- >,
350
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
351
- return this.serializeNotification(
352
- await this.prismaClient.notification.update({
353
- where: {
354
- id: notificationId,
355
- },
356
- data: this.deserializeNotificationForUpdate(notification),
357
- }),
358
- );
359
- }
360
-
361
- async markPendingAsSent(
362
- notificationId: NonNullable<
363
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
364
- >['id'],
365
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
366
- return this.serializeNotification(
367
- await this.prismaClient.notification.update({
368
- where: {
369
- id: notificationId,
370
- },
371
- data: {
372
- status: NotificationStatusEnum.SENT,
373
- sentAt: new Date(),
374
- },
375
- }),
376
- );
377
- }
378
-
379
- async markPendingAsFailed(
380
- notificationId: NonNullable<
381
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
382
- >['id'],
383
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
384
- return this.serializeNotification(
385
- await this.prismaClient.notification.update({
386
- where: {
387
- id: notificationId,
388
- },
389
- data: {
390
- status: NotificationStatusEnum.FAILED,
391
- sentAt: new Date(),
392
- },
393
- }),
394
- );
395
- }
396
-
397
- async markSentAsRead(
398
- notificationId: NonNullable<
399
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
400
- >['id'],
401
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>> {
402
- return this.serializeNotification(
403
- await this.prismaClient.notification.update({
404
- where: {
405
- id: notificationId,
406
- },
407
- data: {
408
- status: 'READ',
409
- readAt: new Date(),
410
- },
411
- }),
412
- );
413
- }
414
-
415
- async cancelNotification(
416
- notificationId: NonNullable<
417
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
418
- >['id'],
419
- ): Promise<void> {
420
- await this.prismaClient.notification.update({
421
- where: {
422
- id: notificationId,
423
- },
424
- data: {
425
- status: NotificationStatusEnum.CANCELLED,
426
- },
427
- });
428
- }
429
-
430
- async getNotification(
431
- notificationId: NonNullable<
432
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
433
- >['id'],
434
- _forUpdate: boolean,
435
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType> | null> {
436
- const notification = await this.prismaClient.notification.findUnique({
437
- where: {
438
- id: notificationId,
439
- },
440
- });
441
- if (!notification) {
442
- return null;
443
- }
444
-
445
- return this.serializeNotification(notification);
446
- }
447
-
448
- async filterAllInAppUnreadNotifications(
449
- userId: NonNullable<
450
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
451
- >['userId'],
452
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>[]> {
453
- const notifications = await this.prismaClient.notification.findMany({
454
- where: {
455
- userId,
456
- status: 'SENT',
457
- readAt: null,
458
- },
459
- });
460
-
461
- return notifications.map(this.serializeNotification);
462
- }
463
-
464
- async filterInAppUnreadNotifications(
465
- userId: NonNullable<
466
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
467
- >['userId'],
468
- page: number,
469
- pageSize: number,
470
- ): Promise<Notification<AvailableContexts, NotificationIdType, UserIdType>[]> {
471
- const notifications = await this.prismaClient.notification.findMany({
472
- where: {
473
- userId,
474
- status: 'SENT',
475
- readAt: null,
476
- },
477
- skip: page * pageSize,
478
- take: pageSize,
479
- });
480
-
481
- return notifications.map(this.serializeNotification);
482
- }
483
-
484
- async getUserEmailFromNotification(
485
- notificationId: NonNullable<
486
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
487
- >['id'],
488
- ): Promise<string | undefined> {
489
- const notification = await this.prismaClient.notification.findUnique({
490
- where: {
491
- id: notificationId,
492
- },
493
- include: {
494
- user: true,
495
- },
496
- });
497
-
498
- return notification?.user?.email;
499
- }
500
-
501
- async storeContextUsed(
502
- notificationId: NonNullable<
503
- Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>
504
- >['id'],
505
- context: InputJsonValue,
506
- ): Promise<void> {
507
- await this.prismaClient.notification.update({
508
- where: {
509
- id: notificationId,
510
- },
511
- data: {
512
- contextUsed: context,
513
- },
514
- });
515
- }
516
- }
@@ -1,49 +0,0 @@
1
- datasource db {
2
- provider = "postgresql"
3
- url = env("DATABASE_URL")
4
- }
5
-
6
- generator client {
7
- provider = "prisma-client-js"
8
- }
9
-
10
- model User {
11
- id Int @id @default(autoincrement())
12
- email String @unique
13
- notifications Notification[]
14
- }
15
-
16
- model Notification {
17
- id Int @id @default(autoincrement())
18
- user User @relation(fields: [userId], references: [id])
19
- userId Int
20
- notificationType NotificationType
21
- title String?
22
- bodyTemplate String
23
- contextName String
24
- contextParameters Json @default("{}")
25
- sendAfter DateTime?
26
- subjectTemplate String?
27
- status NotificationStatus @default(PENDING_SEND)
28
- contextUsed Json?
29
- adapterUsed String?
30
- sentAt DateTime?
31
- readAt DateTime?
32
- createdAt DateTime @default(now())
33
- updatedAt DateTime @updatedAt
34
- }
35
-
36
- enum NotificationType {
37
- EMAIL
38
- PUSH
39
- SMS
40
- IN_APP
41
- }
42
-
43
- enum NotificationStatus {
44
- PENDING_SEND
45
- SENT
46
- FAILED
47
- READ
48
- CANCELLED
49
- }