whatsapp-store-db 1.3.43

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.
@@ -0,0 +1,17 @@
1
+ /// <reference types="node" />
2
+ import type { BaileysEventMap } from 'baileys';
3
+ import type Long from 'long';
4
+ export type BaileysEventHandler<T extends keyof BaileysEventMap> = (args: BaileysEventMap[T]) => void;
5
+ type TransformPrisma<T, TransformObject> = T extends Long ? number : T extends Uint8Array ? Buffer : T extends null ? never : T extends object ? TransformObject extends true ? object : T : T;
6
+ /** Transform unsupported types into supported Prisma types */
7
+ export type MakeTransformedPrisma<T extends Record<string, any>, TransformObject extends boolean = true> = {
8
+ [K in keyof T]: TransformPrisma<T[K], TransformObject>;
9
+ };
10
+ type SerializePrisma<T> = T extends Buffer ? {
11
+ type: 'Buffer';
12
+ data: number[];
13
+ } : T extends bigint ? string : T extends null ? never : T;
14
+ export type MakeSerializedPrisma<T extends Record<string, any>> = {
15
+ [K in keyof T]: SerializePrisma<T[K]>;
16
+ };
17
+ export {};
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,27 @@
1
+ import type { MakeTransformedPrisma, MakeSerializedPrisma } from './types';
2
+ /**
3
+ * Transform object props value into Prisma-supported types
4
+ * Handles complex WhatsApp message structures
5
+ */
6
+ export declare function transformPrisma<T extends Record<string, any>>(data: T, removeNullable?: boolean): MakeTransformedPrisma<T>;
7
+ /** Transform prisma result into JSON serializable types */
8
+ export declare function serializePrisma<T extends Record<string, any>>(data: T, removeNullable?: boolean): MakeSerializedPrisma<T>;
9
+ /**
10
+ * Validate message data before Prisma operations
11
+ * This helps catch validation errors early and provides better error messages
12
+ */
13
+ export declare function validateMessageData(data: any): any;
14
+ /**
15
+ * Validate chat data before Prisma operations
16
+ * This helps catch validation errors early and provides better error messages
17
+ */
18
+ export declare function validateChatData(data: any): any;
19
+ /**
20
+ * Wrapper for Prisma operations that provides better error handling
21
+ * and validation error messages
22
+ */
23
+ export declare function safePrismaOperation<T>(operation: () => Promise<T>, operationName: string, logger?: any): Promise<T>;
24
+ /**
25
+ * Retry database operations with exponential backoff for deadlock recovery
26
+ */
27
+ export declare function retryDatabaseOperation<T>(operation: () => Promise<T>, operationName: string, logger?: any, maxRetries?: number, baseDelay?: number): Promise<T>;
package/dist/utils.js ADDED
@@ -0,0 +1,553 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.retryDatabaseOperation = exports.safePrismaOperation = exports.validateChatData = exports.validateMessageData = exports.serializePrisma = exports.transformPrisma = void 0;
7
+ const baileys_1 = require("baileys");
8
+ const long_1 = __importDefault(require("long"));
9
+ let messageFieldTypesCache;
10
+ let attemptedMessageFieldTypeResolution = false;
11
+ const TIMESTAMP_FIELD_NAMES = new Set(['messageTimestamp', 'messageC2STimestamp']);
12
+ const TIMESTAMP_TYPE_OVERRIDE = normalizeTimestampOverride(process.env.WHATSAPP_STORE_TIMESTAMP_TYPE);
13
+ let loggedFieldTypeResolutionError = false;
14
+ function resolveMessageFieldTypes() {
15
+ var _a, _b;
16
+ if (attemptedMessageFieldTypeResolution) {
17
+ return;
18
+ }
19
+ attemptedMessageFieldTypeResolution = true;
20
+ try {
21
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
22
+ const { Prisma } = require('@prisma/client');
23
+ const models = (_b = (_a = Prisma === null || Prisma === void 0 ? void 0 : Prisma.dmmf) === null || _a === void 0 ? void 0 : _a.datamodel) === null || _b === void 0 ? void 0 : _b.models;
24
+ if (Array.isArray(models)) {
25
+ const messageModel = models.find((model) => model.name === 'Message');
26
+ if (messageModel === null || messageModel === void 0 ? void 0 : messageModel.fields) {
27
+ messageFieldTypesCache = {};
28
+ for (const field of messageModel.fields) {
29
+ messageFieldTypesCache[field.name] = field.type;
30
+ }
31
+ }
32
+ }
33
+ }
34
+ catch (error) {
35
+ messageFieldTypesCache = undefined;
36
+ if (!loggedFieldTypeResolutionError) {
37
+ console.warn('[whatsapp-store] Unable to resolve Prisma model metadata, falling back to heuristics.', error);
38
+ loggedFieldTypeResolutionError = true;
39
+ }
40
+ }
41
+ }
42
+ function getMessageFieldType(fieldName) {
43
+ resolveMessageFieldTypes();
44
+ return messageFieldTypesCache === null || messageFieldTypesCache === void 0 ? void 0 : messageFieldTypesCache[fieldName];
45
+ }
46
+ function normalizeTimestampOverride(value) {
47
+ if (!value)
48
+ return undefined;
49
+ const normalized = value.trim().toLowerCase();
50
+ if (['bigint', 'big-int', 'big', 'int64', 'bigint64', '64'].includes(normalized)) {
51
+ return 'bigint';
52
+ }
53
+ if (['int', 'integer', 'number', 'numeric', 'int32', '32'].includes(normalized)) {
54
+ return 'number';
55
+ }
56
+ return undefined;
57
+ }
58
+ function isTimestampField(fieldName) {
59
+ return TIMESTAMP_FIELD_NAMES.has(fieldName);
60
+ }
61
+ function parseNumericInput(value) {
62
+ if (value === null || value === undefined) {
63
+ return undefined;
64
+ }
65
+ if (typeof value === 'number') {
66
+ return Number.isFinite(value) ? value : undefined;
67
+ }
68
+ if (typeof value === 'bigint') {
69
+ return value;
70
+ }
71
+ if (typeof value === 'string') {
72
+ const trimmed = value.trim();
73
+ if (!trimmed) {
74
+ return undefined;
75
+ }
76
+ if (/^-?\d+$/.test(trimmed)) {
77
+ try {
78
+ return BigInt(trimmed);
79
+ }
80
+ catch (error) {
81
+ const parsed = Number(trimmed);
82
+ return Number.isFinite(parsed) ? parsed : undefined;
83
+ }
84
+ }
85
+ const parsed = Number(trimmed);
86
+ return Number.isFinite(parsed) ? parsed : undefined;
87
+ }
88
+ const parsed = Number(value);
89
+ return Number.isFinite(parsed) ? parsed : undefined;
90
+ }
91
+ function coerceTimestampValue(value, fieldName, preferredType = undefined) {
92
+ const expectedType = getMessageFieldType(fieldName);
93
+ const targetType = TIMESTAMP_TYPE_OVERRIDE ||
94
+ (expectedType === 'BigInt'
95
+ ? 'bigint'
96
+ : expectedType === 'Int'
97
+ ? 'number'
98
+ : preferredType);
99
+ const numericValue = parseNumericInput(value);
100
+ if (targetType === 'bigint') {
101
+ if (typeof numericValue === 'bigint') {
102
+ return numericValue;
103
+ }
104
+ try {
105
+ return BigInt(typeof numericValue === 'number' && Number.isFinite(numericValue)
106
+ ? Math.trunc(numericValue)
107
+ : 0);
108
+ }
109
+ catch (error) {
110
+ return BigInt(0);
111
+ }
112
+ }
113
+ if (numericValue === undefined) {
114
+ return 0;
115
+ }
116
+ if (typeof numericValue === 'bigint') {
117
+ const asNumber = Number(numericValue);
118
+ return Number.isFinite(asNumber) ? asNumber : 0;
119
+ }
120
+ return numericValue;
121
+ }
122
+ /**
123
+ * Transform object props value into Prisma-supported types
124
+ * Handles complex WhatsApp message structures
125
+ */
126
+ function transformPrisma(data, removeNullable = true) {
127
+ // Make a shallow clone to avoid modifying the original object
128
+ const obj = Object.assign({}, data);
129
+ for (const [key, val] of Object.entries(obj)) {
130
+ if (typeof val === 'function') {
131
+ // Remove function properties as they cannot be serialized to JSON
132
+ delete obj[key];
133
+ }
134
+ else if (val instanceof Uint8Array) {
135
+ obj[key] = Buffer.from(val);
136
+ }
137
+ else if (typeof val === 'number' || val instanceof long_1.default) {
138
+ obj[key] = (0, baileys_1.toNumber)(val);
139
+ }
140
+ else if (typeof val === 'string' && isTimestampField(key)) {
141
+ obj[key] = coerceTimestampValue(val, key);
142
+ }
143
+ else if (typeof val === 'object' && val !== null && !Buffer.isBuffer(val)) {
144
+ // Handle serialized Buffer objects (e.g., {type: "Buffer", data: [...]})
145
+ if (val.type === 'Buffer' && Array.isArray(val.data)) {
146
+ // Special handling for Bytes fields like messageSecret, mediaCiphertextSha256
147
+ if (key === 'messageSecret' || key === 'mediaCiphertextSha256') {
148
+ obj[key] = Buffer.from(val.data);
149
+ }
150
+ else {
151
+ // For JSON fields containing Buffer data, convert to base64 string
152
+ obj[key] = Buffer.from(val.data).toString('base64');
153
+ }
154
+ }
155
+ else {
156
+ // For Prisma's JSON fields, recursively clean the object to remove functions
157
+ obj[key] = cleanObjectForPrisma(val, key);
158
+ }
159
+ }
160
+ else if (removeNullable && (typeof val === 'undefined' || val === null)) {
161
+ delete obj[key];
162
+ }
163
+ }
164
+ return obj;
165
+ }
166
+ exports.transformPrisma = transformPrisma;
167
+ /**
168
+ * Recursively clean an object to remove functions and other non-serializable values
169
+ * This prevents Prisma serialization errors when storing complex objects as JSON
170
+ */
171
+ function cleanObjectForPrisma(obj, parentKey) {
172
+ if (obj === null || obj === undefined) {
173
+ return obj;
174
+ }
175
+ // Debug: Log when we're cleaning an object that looks like a Buffer
176
+ if (typeof obj === 'object' && obj !== null && obj.type === 'Buffer') {
177
+ // Log via console for immediate visibility
178
+ console.error('[cleanObjectForPrisma] Processing Buffer-like object:', { type: obj.type, hasData: Array.isArray(obj.data) });
179
+ }
180
+ if (typeof obj === 'function') {
181
+ return undefined; // Functions cannot be serialized
182
+ }
183
+ if (typeof obj === 'symbol') {
184
+ return undefined; // Symbols cannot be serialized
185
+ }
186
+ if (obj instanceof Date) {
187
+ return obj.toISOString(); // Convert dates to ISO strings
188
+ }
189
+ if (obj instanceof Buffer) {
190
+ return obj; // Keep buffers as-is
191
+ }
192
+ // Handle objects that look like a numeric-indexed byte dictionary: {"0":83, "1":118, ...}
193
+ if (typeof obj === 'object' &&
194
+ obj !== null &&
195
+ !Array.isArray(obj) &&
196
+ Object.keys(obj).length > 0 &&
197
+ Object.keys(obj).every((k) => /^\d+$/.test(k)) &&
198
+ Object.values(obj).every((v) => typeof v === 'number')) {
199
+ try {
200
+ const entries = Object.entries(obj)
201
+ .map(([k, v]) => [parseInt(k, 10), v])
202
+ .sort((a, b) => a[0] - b[0]);
203
+ const bytes = Uint8Array.from(entries.map(([, v]) => v));
204
+ const b64 = Buffer.from(bytes).toString('base64');
205
+ // console.error('[cleanObjectForPrisma] Converted numeric-keyed byte object to base64', { length: bytes.length, parentKey });
206
+ return b64;
207
+ }
208
+ catch (e) {
209
+ console.error('[cleanObjectForPrisma] Failed to convert numeric-keyed byte object', e);
210
+ // fall through to regular handling
211
+ }
212
+ }
213
+ // Handle serialized Buffer objects (e.g., {type: "Buffer", data: [...]})
214
+ if (typeof obj === 'object' && obj !== null && obj.type === 'Buffer' && Array.isArray(obj.data)) {
215
+ // Debug logging for Buffer conversion
216
+ console.error('[cleanObjectForPrisma] Converting Buffer object:', { type: obj.type, dataLength: obj.data.length, parentKey });
217
+ // For JSON storage in Prisma, convert to base64 string instead of Buffer instance
218
+ try {
219
+ const buffer = Buffer.from(obj.data);
220
+ return buffer.toString('base64');
221
+ }
222
+ catch (e) {
223
+ console.error('[cleanObjectForPrisma] Failed to convert Buffer data to base64:', e);
224
+ return null; // Return null if conversion fails
225
+ }
226
+ }
227
+ // Handle Long objects from Baileys (e.g., {low: x, high: y, unsigned: z})
228
+ if (typeof obj === 'object' &&
229
+ typeof obj.low === 'number' &&
230
+ typeof obj.high === 'number' &&
231
+ typeof obj.unsigned === 'boolean') {
232
+ return (0, baileys_1.toNumber)(obj);
233
+ }
234
+ if (Array.isArray(obj)) {
235
+ return obj
236
+ .map(item => cleanObjectForPrisma(item, parentKey))
237
+ .filter(item => item !== undefined); // Remove undefined items
238
+ }
239
+ if (typeof obj === 'object') {
240
+ const cleaned = {};
241
+ for (const [key, value] of Object.entries(obj)) {
242
+ // Debug: Log when we encounter nested objects
243
+ if (typeof value === 'object' && value !== null && value.type === 'Buffer') {
244
+ console.error(`[cleanObjectForPrisma] Found nested Buffer in key: ${key}`);
245
+ }
246
+ const cleanedValue = cleanObjectForPrisma(value, key);
247
+ if (cleanedValue !== undefined) {
248
+ cleaned[key] = cleanedValue;
249
+ }
250
+ }
251
+ return cleaned;
252
+ }
253
+ // For primitive values (string, number, boolean), return as-is
254
+ return obj;
255
+ }
256
+ /** Transform prisma result into JSON serializable types */
257
+ function serializePrisma(data, removeNullable = true) {
258
+ const obj = Object.assign({}, data);
259
+ for (const [key, val] of Object.entries(obj)) {
260
+ if (val instanceof Buffer) {
261
+ obj[key] = val.toJSON();
262
+ }
263
+ else if (typeof val === 'bigint' || val instanceof BigInt) {
264
+ obj[key] = val.toString();
265
+ }
266
+ else if (typeof val === 'string') {
267
+ // Try to parse JSON strings
268
+ try {
269
+ obj[key] = JSON.parse(val);
270
+ }
271
+ catch (e) {
272
+ // If parsing fails, keep the original string
273
+ obj[key] = val;
274
+ }
275
+ }
276
+ else if (removeNullable && (typeof val === 'undefined' || val === null)) {
277
+ delete obj[key];
278
+ }
279
+ }
280
+ return obj;
281
+ }
282
+ exports.serializePrisma = serializePrisma;
283
+ /**
284
+ * Deeply convert any BigInt values to Number to ensure JSON.stringify safety
285
+ * and compatibility with Prisma inputs/logging.
286
+ */
287
+ function normalizeBigIntDeep(input) {
288
+ if (input === null || input === undefined)
289
+ return input;
290
+ if (typeof input === 'bigint')
291
+ return Number(input);
292
+ if (Array.isArray(input))
293
+ return input.map((v) => normalizeBigIntDeep(v));
294
+ if (typeof input === 'object') {
295
+ const out = Array.isArray(input) ? [] : {};
296
+ for (const [k, v] of Object.entries(input)) {
297
+ out[k] = normalizeBigIntDeep(v);
298
+ }
299
+ return out;
300
+ }
301
+ return input;
302
+ }
303
+ /**
304
+ * Validate message data before Prisma operations
305
+ * This helps catch validation errors early and provides better error messages
306
+ */
307
+ function validateMessageData(data) {
308
+ // First, deeply clean the entire data object to handle all nested Buffers and Long objects
309
+ const cleanedData = cleanObjectForPrisma(data);
310
+ const timestampTypeHints = {
311
+ messageTimestamp: typeof (cleanedData === null || cleanedData === void 0 ? void 0 : cleanedData.messageTimestamp) === 'bigint' ? 'bigint' : undefined,
312
+ messageC2STimestamp: typeof (cleanedData === null || cleanedData === void 0 ? void 0 : cleanedData.messageC2STimestamp) === 'bigint' ? 'bigint' : undefined,
313
+ };
314
+ // Normalize BigInt early to avoid JSON.stringify errors in diagnostics below
315
+ let validated = normalizeBigIntDeep(Object.assign({}, cleanedData));
316
+ // Check if we still have Buffer objects after cleaning and log to error if found
317
+ const cleanedDataStr = JSON.stringify(validated, (_k, v) => (typeof v === 'bigint' ? Number(v) : v));
318
+ const hasBuffersAfter = cleanedDataStr.includes('"type":"Buffer"');
319
+ if (hasBuffersAfter) {
320
+ // Force convert any remaining Buffer objects using aggressive string replacement
321
+ try {
322
+ const fixedJsonStr = cleanedDataStr.replace(/\{"type":"Buffer","data":\[([^\]]+)\]\}/g, (match, dataArray) => {
323
+ try {
324
+ const dataNumbers = dataArray.split(',').map((n) => parseInt(n.trim()));
325
+ const buffer = Buffer.from(dataNumbers);
326
+ // Convert to base64 string for JSON storage
327
+ return `"${buffer.toString('base64')}"`;
328
+ }
329
+ catch (e) {
330
+ return 'null'; // Fallback to null if parsing fails
331
+ }
332
+ });
333
+ const finalData = JSON.parse(fixedJsonStr);
334
+ Object.assign(validated, finalData);
335
+ }
336
+ catch (parseError) {
337
+ // If JSON parsing still fails, log the error and continue with the original data
338
+ console.error('[validateMessageData] Failed to fix Buffer serialization, using original data');
339
+ }
340
+ }
341
+ // List of fields that exist in the Prisma Message schema
342
+ const validFields = [
343
+ // Schema fields
344
+ 'sessionId', 'remoteJid', 'id', 'agentId', 'bizPrivacyStatus', 'broadcast',
345
+ 'clearMedia', 'duration', 'ephemeralDuration', 'ephemeralOffToOn', 'ephemeralOutOfSync',
346
+ 'ephemeralStartTimestamp', 'finalLiveLocation', 'futureproofData', 'ignore',
347
+ 'keepInChat', 'key', 'labels', 'mediaCiphertextSha256', 'mediaData', 'message',
348
+ 'messageC2STimestamp', 'messageSecret', 'messageStubParameters', 'messageStubType',
349
+ 'messageTimestamp', 'multicast', 'originalSelfAuthorUserJidString', 'participant',
350
+ 'paymentInfo', 'photoChange', 'pollAdditionalMetadata', 'pollUpdates', 'pushName',
351
+ 'quotedPaymentInfo', 'quotedStickerData', 'reactions', 'revokeMessageTimestamp',
352
+ 'starred', 'status', 'statusAlreadyViewed', 'statusPsa', 'urlNumber', 'urlText',
353
+ 'userReceipt', 'verifiedBizName', 'eventResponses'
354
+ ];
355
+ // Filter out unknown fields that don't exist in the schema
356
+ const filteredFields = [];
357
+ Object.keys(validated).forEach(key => {
358
+ if (!validFields.includes(key)) {
359
+ filteredFields.push(key);
360
+ delete validated[key];
361
+ }
362
+ });
363
+ // Log filtered fields for debugging (only if there are any)
364
+ if (filteredFields.length > 0) {
365
+ // console.log(`[validateMessageData] Filtered out unknown fields: ${filteredFields.join(', ')}`);
366
+ }
367
+ // Debug: Check if reportingTokenInfo is still present after filtering
368
+ if (validated.reportingTokenInfo) {
369
+ console.error('[validateMessageData] WARNING: reportingTokenInfo still present after filtering!');
370
+ }
371
+ // Ensure timestamp fields are numbers (avoid BigInt to prevent JSON/logging issues and match Prisma Int clients)
372
+ if (validated.messageTimestamp !== undefined) {
373
+ validated.messageTimestamp = coerceTimestampValue(validated.messageTimestamp, 'messageTimestamp', timestampTypeHints.messageTimestamp);
374
+ }
375
+ if (validated.messageC2STimestamp !== undefined) {
376
+ validated.messageC2STimestamp = coerceTimestampValue(validated.messageC2STimestamp, 'messageC2STimestamp', timestampTypeHints.messageC2STimestamp);
377
+ }
378
+ // Ensure messageSecret is a Buffer (fallback for any missed cases)
379
+ if (validated.messageSecret && typeof validated.messageSecret === 'string') {
380
+ try {
381
+ validated.messageSecret = Buffer.from(validated.messageSecret, 'base64');
382
+ }
383
+ catch (e) {
384
+ // If base64 conversion fails, remove the field to avoid validation errors
385
+ delete validated.messageSecret;
386
+ }
387
+ }
388
+ // Remove undefined values that could cause Prisma validation errors
389
+ Object.keys(validated).forEach(key => {
390
+ if (validated[key] === undefined) {
391
+ delete validated[key];
392
+ }
393
+ });
394
+ return validated;
395
+ }
396
+ exports.validateMessageData = validateMessageData;
397
+ /**
398
+ * Validate chat data before Prisma operations
399
+ * This helps catch validation errors early and provides better error messages
400
+ */
401
+ function validateChatData(data) {
402
+ // First, deeply clean the entire data object to handle all nested Buffers and Long objects
403
+ const cleanedData = cleanObjectForPrisma(data);
404
+ const validated = Object.assign({}, cleanedData);
405
+ // List of fields that exist in the Prisma Chat schema
406
+ const validFields = [
407
+ // Prisma internal fields
408
+ 'pkId',
409
+ // Schema fields
410
+ 'sessionId', 'archived', 'conversationTimestamp', 'createdAt', 'createdBy',
411
+ 'displayName', 'endOfHistoryTransfer', 'endOfHistoryTransferType', 'ephemeralExpiration',
412
+ 'ephemeralSettingTimestamp', 'id', 'isDefaultSubgroup', 'isParentGroup', 'lastMsgTimestamp',
413
+ 'lidJid', 'markedAsUnread', 'mediaVisibility', 'messages', 'muteEndTime', 'name',
414
+ 'newJid', 'notSpam', 'oldJid', 'pHash', 'parentGroupId', 'pinned', 'pnJid',
415
+ 'pnhDuplicateLidThread', 'readOnly', 'shareOwnPn', 'support', 'suspended',
416
+ 'tcTokenSenderTimestamp', 'tcTokenTimestamp', 'terminated', 'unreadCount',
417
+ 'unreadMentionCount', 'lastMessageRecvTimestamp'
418
+ ];
419
+ // Filter out unknown fields that don't exist in the schema
420
+ const filteredFields = [];
421
+ Object.keys(validated).forEach(key => {
422
+ if (!validFields.includes(key)) {
423
+ filteredFields.push(key);
424
+ delete validated[key];
425
+ }
426
+ });
427
+ // Log filtered fields for debugging (only if there are any)
428
+ if (filteredFields.length > 0) {
429
+ // console.log(`[validateChatData] Filtered out unknown fields: ${filteredFields.join(', ')}`);
430
+ }
431
+ // Ensure timestamp fields are numbers (avoid BigInt for compatibility)
432
+ if (validated.conversationTimestamp !== undefined) {
433
+ if (typeof validated.conversationTimestamp === 'string') {
434
+ const numVal = parseInt(validated.conversationTimestamp, 10);
435
+ validated.conversationTimestamp = isNaN(numVal) ? 0 : numVal;
436
+ }
437
+ else if (typeof validated.conversationTimestamp === 'bigint') {
438
+ validated.conversationTimestamp = Number(validated.conversationTimestamp);
439
+ }
440
+ else if (typeof validated.conversationTimestamp !== 'number') {
441
+ validated.conversationTimestamp = 0;
442
+ }
443
+ }
444
+ if (validated.lastMessageRecvTimestamp !== undefined) {
445
+ if (typeof validated.lastMessageRecvTimestamp === 'string') {
446
+ const numVal = parseInt(validated.lastMessageRecvTimestamp, 10);
447
+ validated.lastMessageRecvTimestamp = isNaN(numVal) ? 0 : numVal;
448
+ }
449
+ else if (typeof validated.lastMessageRecvTimestamp !== 'number') {
450
+ validated.lastMessageRecvTimestamp = 0;
451
+ }
452
+ }
453
+ // Remove undefined values that could cause Prisma validation errors
454
+ Object.keys(validated).forEach(key => {
455
+ if (validated[key] === undefined) {
456
+ delete validated[key];
457
+ }
458
+ });
459
+ return validated;
460
+ }
461
+ exports.validateChatData = validateChatData;
462
+ /**
463
+ * Wrapper for Prisma operations that provides better error handling
464
+ * and validation error messages
465
+ */
466
+ async function safePrismaOperation(operation, operationName, logger) {
467
+ try {
468
+ return await operation();
469
+ }
470
+ catch (error) {
471
+ if ((error === null || error === void 0 ? void 0 : error.code) === 'P2002') {
472
+ // Unique constraint violation
473
+ const message = `Duplicate entry detected in ${operationName}. This usually means the data already exists.`;
474
+ if (logger)
475
+ logger.warn(message);
476
+ throw new Error(message);
477
+ }
478
+ else if ((error === null || error === void 0 ? void 0 : error.code) === 'P2003') {
479
+ // Foreign key constraint violation
480
+ const message = `Referenced record not found in ${operationName}. Check if related records exist.`;
481
+ if (logger)
482
+ logger.error(message);
483
+ throw new Error(message);
484
+ }
485
+ else if ((error === null || error === void 0 ? void 0 : error.code) === 'P2025') {
486
+ // Record not found
487
+ const message = `Record not found in ${operationName}.`;
488
+ if (logger)
489
+ logger.warn(message);
490
+ throw new Error(message);
491
+ }
492
+ else if ((error === null || error === void 0 ? void 0 : error.code) === 'P2034') {
493
+ // Transaction conflict/deadlock - these should be retried by the caller
494
+ const message = `Transaction conflict or deadlock in ${operationName}. Retry recommended.`;
495
+ if (logger)
496
+ logger.warn({ error, operationName }, message);
497
+ // Re-throw the original error so retry logic can handle it
498
+ throw error;
499
+ }
500
+ else if ((error === null || error === void 0 ? void 0 : error.name) === 'PrismaClientValidationError') {
501
+ // Validation error - provide detailed information
502
+ const message = `Data validation failed in ${operationName}: ${error.message}`;
503
+ if (logger)
504
+ logger.error({ error, operationName }, message);
505
+ throw new Error(message);
506
+ }
507
+ else {
508
+ // Generic error
509
+ const message = `Database operation failed in ${operationName}: ${error.message}`;
510
+ if (logger)
511
+ logger.error({ error, operationName }, message);
512
+ throw new Error(message);
513
+ }
514
+ }
515
+ }
516
+ exports.safePrismaOperation = safePrismaOperation;
517
+ /**
518
+ * Retry database operations with exponential backoff for deadlock recovery
519
+ */
520
+ async function retryDatabaseOperation(operation, operationName, logger, maxRetries = 3, baseDelay = 100) {
521
+ var _a, _b;
522
+ let lastError;
523
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
524
+ try {
525
+ return await operation();
526
+ }
527
+ catch (error) {
528
+ lastError = error;
529
+ // Only retry for specific retryable errors
530
+ if ((error === null || error === void 0 ? void 0 : error.code) === 'P2034' || ((_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.includes('deadlock')) || ((_b = error === null || error === void 0 ? void 0 : error.message) === null || _b === void 0 ? void 0 : _b.includes('write conflict'))) {
531
+ if (attempt < maxRetries) {
532
+ const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 100; // Add jitter
533
+ if (logger) {
534
+ logger.warn({
535
+ attempt,
536
+ maxRetries,
537
+ delay,
538
+ error: error.code || error.name,
539
+ operationName
540
+ }, `Retrying ${operationName} after ${delay}ms due to ${error.code || 'database conflict'}`);
541
+ }
542
+ await new Promise(resolve => setTimeout(resolve, delay));
543
+ continue;
544
+ }
545
+ }
546
+ // If it's not retryable or we've exhausted retries, throw the error
547
+ throw error;
548
+ }
549
+ }
550
+ // This should never be reached, but just in case
551
+ throw lastError;
552
+ }
553
+ exports.retryDatabaseOperation = retryDatabaseOperation;
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "whatsapp-store-db",
3
+ "version": "1.3.43",
4
+ "description": "Minimal Baileys data storage for your favorite DBMS built with Prisma",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git://github.com/hung101/whatsapp-store.git"
10
+ },
11
+ "author": "Hung (https://github.com/hung101)",
12
+ "license": "MIT",
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "typecheck": "tsc --noEmit",
16
+ "lint": "eslint .",
17
+ "format": "prettier . --write"
18
+ },
19
+ "devDependencies": {
20
+ "@prisma/client": "^5.10.2",
21
+ "@types/node": "^18.11.10",
22
+ "@typescript-eslint/eslint-plugin": "^5.45.0",
23
+ "@typescript-eslint/parser": "^5.45.0",
24
+ "eslint": "^8.29.0",
25
+ "eslint-config-prettier": "^8.5.0",
26
+ "prettier": "^2.8.0",
27
+ "prisma": "^5.10.2",
28
+ "ts-node": "^10.9.1",
29
+ "typescript": "^4.9.3"
30
+ },
31
+ "dependencies": {
32
+ "baileys": "7.0.0-rc.9",
33
+ "tiny-invariant": "^1.3.1"
34
+ },
35
+ "peerDependencies": {
36
+ "@prisma/client": "^5.10.2"
37
+ },
38
+ "files": [
39
+ "dist/",
40
+ "prisma/schema.prisma",
41
+ ".env.example"
42
+ ]
43
+ }