vintasend-prisma 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,12 @@
1
1
  import type { BaseNotificationBackend } from 'vintasend/dist/services/notification-backends/base-notification-backend';
2
2
  import type { InputJsonValue, JsonValue } from 'vintasend/dist/types/json-values';
3
- import type { DatabaseNotification, Notification, NotificationInput } from 'vintasend/dist/types/notification';
3
+ import type { AnyNotificationInput, DatabaseNotification, Notification, NotificationInput } from 'vintasend/dist/types/notification';
4
+ import type { AnyDatabaseNotification, AnyNotification, DatabaseOneOffNotification, OneOffNotificationInput } from 'vintasend/dist/types/notification';
4
5
  import type { NotificationStatus } from 'vintasend/dist/types/notification-status';
5
6
  import type { NotificationType } from 'vintasend/dist/types/notification-type';
6
7
  import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
8
+ import type { AttachmentFileRecord, StoredAttachment } from 'vintasend/dist/types/attachment';
9
+ import type { BaseAttachmentManager } from 'vintasend/dist/services/attachment-manager/base-attachment-manager';
7
10
  export declare const NotificationStatusEnum: {
8
11
  readonly PENDING_SEND: "PENDING_SEND";
9
12
  readonly SENT: "SENT";
@@ -19,7 +22,10 @@ export declare const NotificationTypeEnum: {
19
22
  };
20
23
  export interface PrismaNotificationModel<IdType, UserId> {
21
24
  id: IdType;
22
- userId: UserId;
25
+ userId: UserId | null;
26
+ emailOrPhone: string | null;
27
+ firstName: string | null;
28
+ lastName: string | null;
23
29
  notificationType: NotificationType;
24
30
  title: string | null;
25
31
  bodyTemplate: string;
@@ -38,10 +44,31 @@ export interface PrismaNotificationModel<IdType, UserId> {
38
44
  user?: {
39
45
  email: string;
40
46
  };
47
+ attachments?: PrismaNotificationAttachmentModel[];
48
+ }
49
+ export interface PrismaAttachmentFileModel {
50
+ id: string;
51
+ filename: string;
52
+ contentType: string;
53
+ size: number;
54
+ checksum: string;
55
+ storageMetadata: JsonValue;
56
+ createdAt: Date;
57
+ updatedAt: Date;
58
+ }
59
+ export interface PrismaNotificationAttachmentModel {
60
+ id: string;
61
+ notificationId: number;
62
+ fileId: string;
63
+ description: string | null;
64
+ createdAt: Date;
65
+ updatedAt: Date;
66
+ attachmentFile?: PrismaAttachmentFileModel;
41
67
  }
42
68
  export interface NotificationPrismaClientInterface<NotificationIdType, UserIdType> {
69
+ $transaction<R>(fn: (prisma: NotificationPrismaClientInterface<NotificationIdType, UserIdType>) => Promise<R>): Promise<R>;
43
70
  notification: {
44
- findMany(args: {
71
+ findMany(args?: {
45
72
  where?: {
46
73
  status?: NotificationStatus | {
47
74
  not: NotificationStatus;
@@ -49,31 +76,49 @@ export interface NotificationPrismaClientInterface<NotificationIdType, UserIdTyp
49
76
  sendAfter?: {
50
77
  lte: Date;
51
78
  } | null;
52
- userId?: UserIdType;
79
+ userId?: UserIdType | null;
53
80
  readAt?: null;
81
+ emailOrPhone?: string | {
82
+ not: null;
83
+ };
54
84
  };
55
85
  skip?: number;
56
86
  take?: number;
57
87
  include?: {
58
88
  user?: boolean;
89
+ attachments?: boolean | {
90
+ include: {
91
+ attachmentFile: boolean;
92
+ };
93
+ };
59
94
  };
60
95
  }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>[]>;
61
96
  create(args: {
62
- data: BaseNotificationCreateInput<UserIdType>;
97
+ data: PrismaNotificationCreateData<UserIdType>;
63
98
  include?: {
64
99
  user?: boolean;
100
+ attachments?: boolean | {
101
+ include: {
102
+ attachmentFile: boolean;
103
+ };
104
+ };
65
105
  };
66
106
  }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>>;
67
- createMany(args: {
68
- data: BaseNotificationCreateInput<UserIdType>[];
69
- }): Promise<NotificationIdType[]>;
107
+ createManyAndReturn(args: {
108
+ data: PrismaNotificationCreateData<UserIdType>[];
109
+ }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>[]>;
70
110
  update(args: {
71
111
  where: {
72
112
  id: NotificationIdType;
73
113
  };
74
- data: Partial<BaseNotificationUpdateInput<UserIdType>>;
114
+ data: BaseNotificationUpdateInput<UserIdType>;
75
115
  include?: {
76
116
  user?: boolean;
117
+ attachments?: boolean | {
118
+ include: {
119
+ attachmentFile: boolean;
120
+ };
121
+ };
77
122
  };
78
123
  }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType>>;
79
124
  findUnique(args: {
@@ -82,9 +127,79 @@ export interface NotificationPrismaClientInterface<NotificationIdType, UserIdTyp
82
127
  };
83
128
  include?: {
84
129
  user?: boolean;
130
+ attachments?: boolean | {
131
+ include: {
132
+ attachmentFile: boolean;
133
+ };
134
+ };
85
135
  };
86
136
  }): Promise<PrismaNotificationModel<NotificationIdType, UserIdType> | null>;
87
137
  };
138
+ attachmentFile: {
139
+ findUnique(args: {
140
+ where: {
141
+ id: string;
142
+ } | {
143
+ checksum: string;
144
+ };
145
+ }): Promise<PrismaAttachmentFileModel | null>;
146
+ create(args: {
147
+ data: {
148
+ id?: string;
149
+ filename: string;
150
+ contentType: string;
151
+ size: number;
152
+ checksum: string;
153
+ storageMetadata: InputJsonValue;
154
+ };
155
+ }): Promise<PrismaAttachmentFileModel>;
156
+ delete(args: {
157
+ where: {
158
+ id: string;
159
+ };
160
+ }): Promise<PrismaAttachmentFileModel>;
161
+ findMany(args?: {
162
+ where?: {
163
+ notificationAttachments?: {
164
+ none: object;
165
+ };
166
+ createdAt?: {
167
+ lt: Date;
168
+ };
169
+ };
170
+ }): Promise<PrismaAttachmentFileModel[]>;
171
+ };
172
+ notificationAttachment: {
173
+ findMany(args: {
174
+ where: {
175
+ notificationId: NotificationIdType;
176
+ };
177
+ include?: {
178
+ attachmentFile: boolean;
179
+ };
180
+ }): Promise<PrismaNotificationAttachmentModel[]>;
181
+ delete(args: {
182
+ where: {
183
+ id: string;
184
+ };
185
+ }): Promise<PrismaNotificationAttachmentModel>;
186
+ deleteMany(args: {
187
+ where: {
188
+ id: string;
189
+ notificationId: NotificationIdType;
190
+ };
191
+ }): Promise<{
192
+ count: number;
193
+ }>;
194
+ create(args: {
195
+ data: {
196
+ id?: string;
197
+ notificationId: NotificationIdType;
198
+ fileId: string;
199
+ description?: string | null;
200
+ };
201
+ }): Promise<PrismaNotificationAttachmentModel>;
202
+ };
88
203
  }
89
204
  type NoExpand<T> = T extends unknown ? T : never;
90
205
  type AtLeast<O extends object, K extends string> = NoExpand<O extends unknown ? (K extends keyof O ? {
@@ -93,12 +208,6 @@ type AtLeast<O extends object, K extends string> = NoExpand<O extends unknown ?
93
208
  [P in keyof O as P extends K ? K : never]-?: O[P];
94
209
  } & O) : never>;
95
210
  export interface BaseNotificationCreateInput<UserIdType> {
96
- user: {
97
- connect?: AtLeast<{
98
- id?: UserIdType;
99
- email?: string;
100
- }, 'id' | 'email'>;
101
- };
102
211
  notificationType: NotificationType;
103
212
  title?: string | null;
104
213
  bodyTemplate: string;
@@ -112,14 +221,28 @@ export interface BaseNotificationCreateInput<UserIdType> {
112
221
  adapterUsed?: string | null;
113
222
  sentAt?: Date | null;
114
223
  readAt?: Date | null;
224
+ emailOrPhone?: string | null;
225
+ firstName?: string | null;
226
+ lastName?: string | null;
115
227
  }
228
+ export type PrismaNotificationCreateData<UserIdType> = BaseNotificationCreateInput<UserIdType> & {
229
+ userId?: UserIdType | null;
230
+ user?: {
231
+ connect: {
232
+ id: UserIdType;
233
+ };
234
+ };
235
+ };
116
236
  export interface BaseNotificationUpdateInput<UserIdType> {
117
- user: {
237
+ user?: {
118
238
  connect?: AtLeast<{
119
239
  id?: UserIdType;
120
240
  email?: string;
121
241
  }, 'id' | 'email'>;
122
242
  };
243
+ emailOrPhone?: string | null;
244
+ firstName?: string | null;
245
+ lastName?: string | null;
123
246
  notificationType?: NotificationType;
124
247
  title?: string | null;
125
248
  bodyTemplate?: string;
@@ -136,32 +259,155 @@ export interface BaseNotificationUpdateInput<UserIdType> {
136
259
  }
137
260
  export declare class PrismaNotificationBackend<Client extends NotificationPrismaClientInterface<Config['NotificationIdType'], Config['UserIdType']>, Config extends BaseNotificationTypeConfig> implements BaseNotificationBackend<Config> {
138
261
  private prismaClient;
139
- constructor(prismaClient: Client);
140
- serializeNotification(notification: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>): DatabaseNotification<Config>;
141
- deserializeNotification(notification: NotificationInput<Config>): BaseNotificationCreateInput<Config['UserIdType']>;
262
+ private attachmentManager?;
263
+ constructor(prismaClient: Client, attachmentManager?: BaseAttachmentManager | undefined);
264
+ /**
265
+ * Inject attachment manager (called by VintaSend when both service and backend exist)
266
+ */
267
+ injectAttachmentManager(manager: BaseAttachmentManager): void;
268
+ /**
269
+ * Build a where clause for status-based updates
270
+ */
271
+ private buildStatusWhere;
272
+ /**
273
+ * Serialize a Prisma notification model to either DatabaseNotification or DatabaseOneOffNotification
274
+ * based on whether it has a userId or not (internal implementation)
275
+ */
276
+ private serializeAnyNotification;
277
+ /**
278
+ * Serialize a Prisma notification model to DatabaseNotification
279
+ */
280
+ private serializeRegularNotification;
281
+ /**
282
+ * Serialize a Prisma notification model to DatabaseOneOffNotification
283
+ */
284
+ private serializeOneOffNotification;
285
+ /**
286
+ * Public accessor for serialization - primarily for testing
287
+ * @internal
288
+ */
289
+ serializeNotification(notification: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>): AnyDatabaseNotification<Config>;
290
+ /**
291
+ * Core internal builder for creating notification data
292
+ * Validates that notification has either userId or emailOrPhone (but not neither)
293
+ */
294
+ private buildCreateData;
295
+ /**
296
+ * Deserialize a regular notification input for creation
297
+ */
298
+ private deserializeRegularNotification;
299
+ /**
300
+ * Build one-off notification data for creation
301
+ */
302
+ private buildOneOffNotificationData;
303
+ /**
304
+ * Core internal builder for update data (supports both regular and one-off)
305
+ */
306
+ private buildUpdateData;
307
+ /**
308
+ * Get or create file record for attachment upload with deduplication (transaction-aware)
309
+ * @private
310
+ */
311
+ private getOrCreateFileRecordForUploadInTransaction;
312
+ /**
313
+ * Get or create file record for attachment upload with deduplication (non-transactional)
314
+ * @private
315
+ */
316
+ private getOrCreateFileRecordForUpload;
317
+ /**
318
+ * Create notification attachment link (transaction-aware)
319
+ * @private
320
+ */
321
+ private createNotificationAttachmentLinkInTransaction;
322
+ /**
323
+ * Create notification attachment link (non-transactional)
324
+ * @private
325
+ */
326
+ private createNotificationAttachmentLink;
327
+ /**
328
+ * Get attachment file by ID (transaction-aware)
329
+ * @private
330
+ */
331
+ private getAttachmentFileInTransaction;
332
+ /**
333
+ * Find attachment file by checksum (transaction-aware)
334
+ * @private
335
+ */
336
+ private findAttachmentFileByChecksumInTransaction;
337
+ /**
338
+ * Core helper for creating notifications with attachments (both regular and one-off)
339
+ * Uses transactions to ensure atomicity - if attachment processing fails, the notification won't be created
340
+ * @private
341
+ */
342
+ private createNotificationWithAttachments;
343
+ /**
344
+ * Get attachment manager with null check
345
+ * @private
346
+ */
347
+ private getAttachmentManager;
348
+ deserializeNotification(notification: AnyNotificationInput<Config> | Omit<AnyNotificationInput<Config>, 'id'>): PrismaNotificationCreateData<Config['UserIdType']>;
142
349
  deserializeNotificationForUpdate(notification: Partial<Notification<Config>>): Partial<Parameters<typeof this.prismaClient.notification.update>[0]['data']>;
143
- getAllPendingNotifications(): Promise<DatabaseNotification<Config>[]>;
144
- getPendingNotifications(): Promise<DatabaseNotification<Config>[]>;
145
- getAllFutureNotifications(): Promise<DatabaseNotification<Config>[]>;
146
- getFutureNotifications(): Promise<DatabaseNotification<Config>[]>;
350
+ getAllPendingNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
351
+ getPendingNotifications(page?: number, pageSize?: number): Promise<AnyDatabaseNotification<Config>[]>;
352
+ getAllFutureNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
353
+ getFutureNotifications(page?: number, pageSize?: number): Promise<AnyDatabaseNotification<Config>[]>;
147
354
  getAllFutureNotificationsFromUser(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId']): Promise<DatabaseNotification<Config>[]>;
148
355
  getFutureNotificationsFromUser(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
149
- getAllNotifications(): Promise<DatabaseNotification<Config>[]>;
150
- getNotifications(page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
356
+ getAllNotifications(): Promise<AnyDatabaseNotification<Config>[]>;
357
+ getNotifications(page: number, pageSize: number): Promise<AnyDatabaseNotification<Config>[]>;
151
358
  persistNotification(notification: NotificationInput<Config>): Promise<DatabaseNotification<Config>>;
152
359
  persistNotificationUpdate(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], notification: Partial<Omit<DatabaseNotification<Config>, 'id'>>): Promise<DatabaseNotification<Config>>;
153
- markAsSent(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsPending?: boolean): Promise<DatabaseNotification<Config>>;
154
- markAsFailed(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsPending?: boolean): Promise<DatabaseNotification<Config>>;
360
+ persistOneOffNotification(notification: OneOffNotificationInput<Config>): Promise<DatabaseOneOffNotification<Config>>;
361
+ persistOneOffNotificationUpdate(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], notification: Partial<Omit<DatabaseOneOffNotification<Config>, 'id'>>): Promise<DatabaseOneOffNotification<Config>>;
362
+ getOneOffNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], _forUpdate: boolean): Promise<DatabaseOneOffNotification<Config> | null>;
363
+ getAllOneOffNotifications(): Promise<DatabaseOneOffNotification<Config>[]>;
364
+ getOneOffNotifications(page: number, pageSize: number): Promise<DatabaseOneOffNotification<Config>[]>;
365
+ markAsSent(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsPending?: boolean): Promise<AnyDatabaseNotification<Config>>;
366
+ markAsFailed(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsPending?: boolean): Promise<AnyDatabaseNotification<Config>>;
155
367
  markAsRead(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], checkIsSent?: boolean): Promise<DatabaseNotification<Config>>;
156
368
  cancelNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<void>;
157
- getNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], _forUpdate: boolean): Promise<DatabaseNotification<Config> | null>;
369
+ getNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], _forUpdate: boolean): Promise<AnyDatabaseNotification<Config> | null>;
158
370
  filterAllInAppUnreadNotifications(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId']): Promise<DatabaseNotification<Config>[]>;
159
371
  filterInAppUnreadNotifications(userId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['userId'], page: number, pageSize: number): Promise<DatabaseNotification<Config>[]>;
160
372
  getUserEmailFromNotification(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id']): Promise<string | undefined>;
161
373
  storeContextUsed(notificationId: NonNullable<Awaited<ReturnType<typeof this.prismaClient.notification.findUnique>>>['id'], context: InputJsonValue): Promise<void>;
162
- bulkPersistNotifications(notifications: Omit<Notification<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
374
+ bulkPersistNotifications(notifications: Omit<AnyNotification<Config>, 'id'>[]): Promise<Config['NotificationIdType'][]>;
375
+ getAttachmentFile(fileId: string): Promise<AttachmentFileRecord | null>;
376
+ /**
377
+ * Find an attachment file by checksum for deduplication.
378
+ * This allows the backend to check if a file already exists before uploading.
379
+ */
380
+ findAttachmentFileByChecksum(checksum: string): Promise<AttachmentFileRecord | null>;
381
+ deleteAttachmentFile(fileId: string): Promise<void>;
382
+ getOrphanedAttachmentFiles(): Promise<AttachmentFileRecord[]>;
383
+ getAttachments(notificationId: Config['NotificationIdType']): Promise<StoredAttachment[]>;
384
+ deleteNotificationAttachment(notificationId: Config['NotificationIdType'], attachmentId: string): Promise<void>;
385
+ /**
386
+ * Process and store attachments for a notification within a transaction.
387
+ * Handles both new file uploads and references to existing files.
388
+ * Uses attachmentManager for checksum calculation and storage operations.
389
+ * @private
390
+ */
391
+ private processAndStoreAttachmentsInTransaction;
392
+ /**
393
+ * Process and store attachments for a notification (non-transactional version).
394
+ * Handles both new file uploads and references to existing files.
395
+ * Uses attachmentManager for checksum calculation and storage operations.
396
+ * @private
397
+ */
398
+ private processAndStoreAttachments;
399
+ /**
400
+ * Serialize a Prisma attachment file model to AttachmentFileRecord
401
+ * @private
402
+ */
403
+ private serializeAttachmentFileRecord;
404
+ /**
405
+ * Serialize a Prisma notification attachment model to StoredAttachment
406
+ * @private
407
+ */
408
+ private serializeStoredAttachment;
163
409
  }
164
410
  export declare class PrismaNotificationBackendFactory<Config extends BaseNotificationTypeConfig> {
165
- create<Client extends NotificationPrismaClientInterface<Config['NotificationIdType'], Config['UserIdType']>>(prismaClient: Client): PrismaNotificationBackend<Client, Config>;
411
+ create<Client extends NotificationPrismaClientInterface<Config['NotificationIdType'], Config['UserIdType']>>(prismaClient: Client, attachmentManager?: BaseAttachmentManager): PrismaNotificationBackend<Client, Config>;
166
412
  }
167
413
  export {};