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.
- package/.env.example +2 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/handlers/chat.d.ts +5 -0
- package/dist/handlers/chat.js +163 -0
- package/dist/handlers/contact.d.ts +5 -0
- package/dist/handlers/contact.js +118 -0
- package/dist/handlers/index.d.ts +3 -0
- package/dist/handlers/index.js +12 -0
- package/dist/handlers/message.d.ts +5 -0
- package/dist/handlers/message.js +369 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/dist/session.d.ts +20 -0
- package/dist/session.js +133 -0
- package/dist/shared.d.ts +6 -0
- package/dist/shared.js +27 -0
- package/dist/store.d.ts +22 -0
- package/dist/store.js +56 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.js +553 -0
- package/package.json +43 -0
- package/prisma/schema.prisma +134 -0
package/dist/types.d.ts
ADDED
|
@@ -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
package/dist/utils.d.ts
ADDED
|
@@ -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
|
+
}
|