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 ADDED
@@ -0,0 +1,2 @@
1
+ # Your Database Connection URL here, please see https://pris.ly/d/connection-strings
2
+ DATABASE_URL="mysql://root:12345@localhost:3306/baileys_store"
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Royhan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ > 🚨 **NOTICE**: `baileys` which is this project relied on, has been discontinued. Thus, this project will be archived and stopped receiving updates anymore. Thanks everyone who's been part of this❤️
2
+
3
+ ---
4
+
5
+ # Baileys Store
6
+
7
+ Minimal Baileys data storage for your favorite DBMS built with Prisma. This library is a simple handler for Baileys event emitter that will listen and update your data in the database
8
+
9
+ ## Requirements
10
+
11
+ - **Prisma** version **4.7.x** or higher
12
+ - **Baileys** version **5.x.x** or higher
13
+
14
+ ## Supported Databases
15
+
16
+ - MySQL and PostgreSQL database should support the default schema out of the box
17
+ - For CockroachDB, you need to do this small change in the schema file
18
+
19
+ ```diff prisma
20
+ model Session {
21
+ pkId BigInt @id @default(autoincrement())
22
+ sessionId String
23
+ id String
24
+ - data String @db.Text
25
+ + data String
26
+
27
+ @@unique([sessionId, id], map: "unique_id_per_session_id_session")
28
+ @@index([sessionId])
29
+ }
30
+ ```
31
+
32
+ - For MongoDB, you need to follow [this convention](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference?query=getdmff&page=1#mongodb-10) and update the `pkId` field. Then follow the previous CockroachDB guide
33
+ - SQLite and SQL Server database are not supported since they didn't support Prisma's `JSON` scalar type
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ # Using npm
39
+ npm i @ookamiiixd/baileys-store
40
+
41
+ # Using yarn
42
+ yarn add @ookamiiixd/baileys-store
43
+ ```
44
+
45
+ ## Setup
46
+
47
+ Before you can actually use this library, you have to setup your database first
48
+
49
+ 1. Copy the `.env.example` file from this repository or from the `node_modules` directory (should be located at `node_modules/@ookamiiixd/baileys-store/.env.example`). Rename it into `.env` and then update your [connection url](https://www.prisma.io/docs/reference/database-reference/connection-urls) in the `DATABASE_URL` field
50
+ 1. Copy the `prisma` directory from this repository or from the `node_modules` directory (should be located at `node_modules/@ookamiiixd/baileys-store/prisma/`). Additionaly, you may want to update your `provider` in the `schema.prisma` file if you're not using MySQL database
51
+ 1. Run your [migration](https://www.prisma.io/docs/reference/api-reference/command-reference#prisma-migrate)
52
+
53
+ ## Usage
54
+
55
+ ```ts
56
+ import pino from 'pino';
57
+ import makeWASocket from 'baileys';
58
+ import { PrismaClient } from '@prisma/client';
59
+ import { initStore, Store } from '@ookamiiixd/baileys-store';
60
+
61
+ const logger = pino();
62
+ const socket = makeWASocket();
63
+
64
+ // Configure Prisma client with increased transaction timeout
65
+ const prisma = new PrismaClient({
66
+ transactionOptions: {
67
+ timeout: 60000, // 60 seconds (increase from default 5 seconds)
68
+ maxWait: 30000, // 30 seconds max wait time
69
+ isolationLevel: 'ReadCommitted', // Optional: set isolation level
70
+ },
71
+ });
72
+
73
+ // You only need to run this once
74
+ initStore({
75
+ prisma, // Prisma client instance
76
+ logger, // Pino logger (Optional)
77
+ });
78
+
79
+ // Create a store and start listening to the events
80
+ const store = new Store('unique-session-id-here', socket.ev);
81
+
82
+ // That's it, you can now query from the prisma client without having to worry about handling the events
83
+ const messages = prisma.message.findMany();
84
+ ```
85
+
86
+ ## Transaction Timeout Issue
87
+
88
+ If you encounter the `P2028` error (transaction timeout), it's because the default transaction timeout is 5 seconds. When processing large amounts of data (like message history), transactions may take longer. You can:
89
+
90
+ 1. **Increase the timeout** (shown above) - Set `timeout` to 60000ms (60 seconds) or higher
91
+ 2. **Optimize database performance** - Ensure proper indexing and database optimization
92
+ 3. **Batch operations** - Process data in smaller chunks if dealing with very large datasets
93
+
94
+ For production environments, consider:
95
+ - Setting `timeout` to at least 30-60 seconds
96
+ - Monitoring database performance
97
+ - Using connection pooling if needed
98
+
99
+ ## Contributing
100
+
101
+ PRs, issues, suggestions, etc are welcome. Please kindly open a new issue to discuss it
@@ -0,0 +1,5 @@
1
+ import type { BaileysEventEmitter } from 'baileys';
2
+ export default function chatHandler(sessionId: string, event: BaileysEventEmitter, getJid?: Function | undefined): {
3
+ listen: () => void;
4
+ unlisten: () => void;
5
+ };
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const baileys_1 = require("baileys");
4
+ const client_1 = require(".prisma/client");
5
+ const shared_1 = require("../shared");
6
+ const utils_1 = require("../utils");
7
+ function chatHandler(sessionId, event, getJid = undefined) {
8
+ const prisma = (0, shared_1.usePrisma)();
9
+ const logger = (0, shared_1.useLogger)();
10
+ let listening = false;
11
+ const resolveChatId = (id, chatOrUpdate) => {
12
+ // console.log("chatHandler:chatOrUpdate:", chatOrUpdate);
13
+ // Prefer primary number JID when id is a LID
14
+ if (id === null || id === void 0 ? void 0 : id.endsWith('@lid')) {
15
+ const candidate = (chatOrUpdate === null || chatOrUpdate === void 0 ? void 0 : chatOrUpdate.pnJid) || (chatOrUpdate === null || chatOrUpdate === void 0 ? void 0 : chatOrUpdate.senderPn) || (chatOrUpdate === null || chatOrUpdate === void 0 ? void 0 : chatOrUpdate.jid);
16
+ if (candidate) {
17
+ return (0, baileys_1.jidNormalizedUser)(candidate);
18
+ }
19
+ }
20
+ const jidByLid = typeof getJid === 'function' ? getJid(id || '') : undefined;
21
+ return (0, baileys_1.jidNormalizedUser)(jidByLid !== null && jidByLid !== void 0 ? jidByLid : id);
22
+ };
23
+ const persistChatRecord = async ({ id, createData, updateData, }) => {
24
+ const where = { sessionId, id };
25
+ const createPayload = Object.assign(Object.assign({}, createData), { id, sessionId });
26
+ const updatePayload = Object.assign({}, updateData);
27
+ for (let attempt = 0; attempt < 3; attempt++) {
28
+ try {
29
+ const updateResult = await prisma.chat.updateMany({
30
+ data: updatePayload,
31
+ where,
32
+ });
33
+ if (updateResult.count > 0) {
34
+ return;
35
+ }
36
+ await prisma.chat.create({
37
+ select: { pkId: true },
38
+ data: createPayload,
39
+ });
40
+ return;
41
+ }
42
+ catch (err) {
43
+ if (err instanceof client_1.Prisma.PrismaClientKnownRequestError && err.code === 'P2002') {
44
+ continue;
45
+ }
46
+ throw err;
47
+ }
48
+ }
49
+ logger.error({ id, sessionId }, 'Failed to persist chat record after repeated retries');
50
+ };
51
+ const set = async ({ chats, isLatest }) => {
52
+ try {
53
+ await prisma.$transaction(async (tx) => {
54
+ if (isLatest)
55
+ await tx.chat.deleteMany({ where: { sessionId } });
56
+ // Process chats in batches to avoid timeout
57
+ const BATCH_SIZE = 100;
58
+ const normalizedChats = chats.map((c) => {
59
+ const id = resolveChatId(c.id, c);
60
+ const transformedData = (0, utils_1.transformPrisma)(c);
61
+ const validatedData = (0, utils_1.validateChatData)(transformedData);
62
+ return Object.assign(Object.assign({}, validatedData), { id });
63
+ });
64
+ const existingIds = (await tx.chat.findMany({
65
+ select: { id: true },
66
+ where: { id: { in: normalizedChats.map((c) => c.id) }, sessionId },
67
+ })).map((i) => i.id);
68
+ const newChats = normalizedChats.filter((c) => !existingIds.includes(c.id));
69
+ let totalAdded = 0;
70
+ for (let i = 0; i < newChats.length; i += BATCH_SIZE) {
71
+ const batch = newChats.slice(i, i + BATCH_SIZE);
72
+ const result = await tx.chat.createMany({
73
+ data: batch.map((c) => (Object.assign(Object.assign({}, c), { sessionId }))),
74
+ });
75
+ totalAdded += result.count;
76
+ }
77
+ logger.info({ chatsAdded: totalAdded }, 'Synced chats');
78
+ }, {
79
+ timeout: 30000, // 30 seconds for this specific transaction
80
+ });
81
+ }
82
+ catch (e) {
83
+ logger.error(e, 'An error occured during chats set');
84
+ }
85
+ };
86
+ const upsert = async (chats) => {
87
+ try {
88
+ // Normalize and de-duplicate by resolved id (keep the last occurrence)
89
+ const dedupedById = new Map();
90
+ for (const c of chats) {
91
+ const id = resolveChatId(c.id, c);
92
+ const transformedData = (0, utils_1.transformPrisma)(c);
93
+ const validatedData = (0, utils_1.validateChatData)(transformedData);
94
+ dedupedById.set(id, Object.assign(Object.assign({}, validatedData), { id }));
95
+ }
96
+ const normalizedChats = Array.from(dedupedById.values());
97
+ await Promise.all(normalizedChats.map((data) => persistChatRecord({
98
+ id: data.id,
99
+ createData: Object.assign({}, data),
100
+ updateData: Object.assign(Object.assign({}, data), { id: undefined }),
101
+ })));
102
+ }
103
+ catch (e) {
104
+ logger.error(e, 'An error occured during chats upsert');
105
+ }
106
+ };
107
+ const update = async (updates) => {
108
+ for (const update of updates) {
109
+ if (!update.id) {
110
+ logger.warn({ update }, 'Skipping chat update with no ID');
111
+ continue;
112
+ }
113
+ try {
114
+ const chatId = resolveChatId(update.id, update);
115
+ const transformedData = (0, utils_1.transformPrisma)(update);
116
+ const validatedData = (0, utils_1.validateChatData)(transformedData);
117
+ await persistChatRecord({
118
+ id: chatId,
119
+ createData: Object.assign(Object.assign({}, validatedData), { id: chatId }),
120
+ updateData: Object.assign(Object.assign({}, validatedData), { id: undefined, unreadCount: typeof validatedData.unreadCount === 'number'
121
+ ? validatedData.unreadCount > 0
122
+ ? { increment: validatedData.unreadCount }
123
+ : { set: validatedData.unreadCount }
124
+ : undefined }),
125
+ });
126
+ }
127
+ catch (e) {
128
+ logger.error(e, 'An error occured during chat update');
129
+ }
130
+ }
131
+ };
132
+ const del = async (ids) => {
133
+ try {
134
+ const normalizedIds = ids.map((id) => resolveChatId(id));
135
+ await prisma.chat.deleteMany({
136
+ where: { id: { in: normalizedIds } },
137
+ });
138
+ }
139
+ catch (e) {
140
+ logger.error(e, 'An error occured during chats delete');
141
+ }
142
+ };
143
+ const listen = () => {
144
+ if (listening)
145
+ return;
146
+ event.on('messaging-history.set', set);
147
+ event.on('chats.upsert', upsert);
148
+ event.on('chats.update', update);
149
+ event.on('chats.delete', del);
150
+ listening = true;
151
+ };
152
+ const unlisten = () => {
153
+ if (!listening)
154
+ return;
155
+ event.off('messaging-history.set', set);
156
+ event.off('chats.upsert', upsert);
157
+ event.off('chats.update', update);
158
+ event.off('chats.delete', del);
159
+ listening = false;
160
+ };
161
+ return { listen, unlisten };
162
+ }
163
+ exports.default = chatHandler;
@@ -0,0 +1,5 @@
1
+ import type { BaileysEventEmitter } from 'baileys';
2
+ export default function contactHandler(sessionId: string, event: BaileysEventEmitter, getJid?: Function | undefined): {
3
+ listen: () => void;
4
+ unlisten: () => void;
5
+ };
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const baileys_1 = require("baileys");
4
+ const shared_1 = require("../shared");
5
+ const utils_1 = require("../utils");
6
+ function contactHandler(sessionId, event, getJid = undefined) {
7
+ const prisma = (0, shared_1.usePrisma)();
8
+ const logger = (0, shared_1.useLogger)();
9
+ let listening = false;
10
+ const resolveContactId = (id, contact) => {
11
+ // console.log("contactHandler:contact:", contact);
12
+ // Prefer primary number when we get a LID id and contact carries pn/jid
13
+ if (id === null || id === void 0 ? void 0 : id.endsWith('@lid')) {
14
+ const candidate = ((contact === null || contact === void 0 ? void 0 : contact.senderPn) || (contact === null || contact === void 0 ? void 0 : contact.pnJid) || (contact === null || contact === void 0 ? void 0 : contact.jid));
15
+ if (candidate) {
16
+ return (0, baileys_1.jidNormalizedUser)(candidate);
17
+ }
18
+ }
19
+ const jidByLid = typeof getJid === 'function' ? getJid(id || '') : undefined;
20
+ return (0, baileys_1.jidNormalizedUser)(jidByLid !== null && jidByLid !== void 0 ? jidByLid : id);
21
+ };
22
+ const sanitizeContactData = (raw) => {
23
+ // Only keep fields that exist in the Contact model
24
+ const allowed = ['id', 'name', 'notify', 'verifiedName', 'imgUrl', 'status'];
25
+ const out = {};
26
+ for (const k of allowed) {
27
+ if (raw[k] !== undefined)
28
+ out[k] = raw[k];
29
+ }
30
+ return out;
31
+ };
32
+ const set = async ({ contacts }) => {
33
+ try {
34
+ const normalizedContacts = contacts.map((c) => {
35
+ const id = resolveContactId(c.id, c);
36
+ const data = sanitizeContactData((0, utils_1.transformPrisma)(c));
37
+ return Object.assign(Object.assign({}, data), { id });
38
+ });
39
+ const contactIds = normalizedContacts.map((c) => c.id);
40
+ const deletedOldContactIds = (await prisma.contact.findMany({
41
+ select: { id: true },
42
+ where: { id: { notIn: contactIds }, sessionId },
43
+ })).map((c) => c.id);
44
+ const upsertPromises = normalizedContacts
45
+ .map((data) => prisma.contact.upsert({
46
+ select: { pkId: true },
47
+ create: Object.assign(Object.assign({}, data), { sessionId }),
48
+ update: data,
49
+ where: { sessionId_id: { id: data.id, sessionId } },
50
+ }));
51
+ await Promise.any([
52
+ ...upsertPromises,
53
+ prisma.contact.deleteMany({ where: { id: { in: deletedOldContactIds }, sessionId } }),
54
+ ]);
55
+ logger.info({ deletedContacts: deletedOldContactIds.length, newContacts: contacts.length }, 'Synced contacts');
56
+ }
57
+ catch (e) {
58
+ logger.error(e, 'An error occured during contacts set');
59
+ }
60
+ };
61
+ const upsert = async (contacts) => {
62
+ try {
63
+ const normalizedContacts = contacts.map((c) => {
64
+ const id = resolveContactId(c.id, c);
65
+ const data = sanitizeContactData((0, utils_1.transformPrisma)(c));
66
+ return Object.assign(Object.assign({}, data), { id });
67
+ });
68
+ await Promise.any(normalizedContacts.map((data) => prisma.contact.upsert({
69
+ select: { pkId: true },
70
+ create: Object.assign(Object.assign({}, data), { sessionId }),
71
+ update: data,
72
+ where: { sessionId_id: { id: data.id, sessionId } },
73
+ })));
74
+ }
75
+ catch (e) {
76
+ logger.error(e, 'An error occured during contacts upsert');
77
+ }
78
+ };
79
+ const update = async (updates) => {
80
+ for (const update of updates) {
81
+ if (!update.id) {
82
+ logger.warn({ update }, 'Skipping contact update with no ID');
83
+ continue;
84
+ }
85
+ try {
86
+ const contactId = resolveContactId(update.id, update);
87
+ const transformedData = sanitizeContactData((0, utils_1.transformPrisma)(update));
88
+ await prisma.contact.upsert({
89
+ select: { pkId: true },
90
+ create: Object.assign(Object.assign({}, transformedData), { id: contactId, sessionId }),
91
+ update: transformedData,
92
+ where: { sessionId_id: { id: contactId, sessionId } },
93
+ });
94
+ }
95
+ catch (e) {
96
+ logger.error(e, 'An error occured during contact update');
97
+ }
98
+ }
99
+ };
100
+ const listen = () => {
101
+ if (listening)
102
+ return;
103
+ event.on('messaging-history.set', set);
104
+ event.on('contacts.upsert', upsert);
105
+ event.on('contacts.update', update);
106
+ listening = true;
107
+ };
108
+ const unlisten = () => {
109
+ if (!listening)
110
+ return;
111
+ event.off('messaging-history.set', set);
112
+ event.off('contacts.upsert', upsert);
113
+ event.off('contacts.update', update);
114
+ listening = false;
115
+ };
116
+ return { listen, unlisten };
117
+ }
118
+ exports.default = contactHandler;
@@ -0,0 +1,3 @@
1
+ export { default as chatHandler } from './chat';
2
+ export { default as messageHandler } from './message';
3
+ export { default as contactHandler } from './contact';
@@ -0,0 +1,12 @@
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.contactHandler = exports.messageHandler = exports.chatHandler = void 0;
7
+ var chat_1 = require("./chat");
8
+ Object.defineProperty(exports, "chatHandler", { enumerable: true, get: function () { return __importDefault(chat_1).default; } });
9
+ var message_1 = require("./message");
10
+ Object.defineProperty(exports, "messageHandler", { enumerable: true, get: function () { return __importDefault(message_1).default; } });
11
+ var contact_1 = require("./contact");
12
+ Object.defineProperty(exports, "contactHandler", { enumerable: true, get: function () { return __importDefault(contact_1).default; } });
@@ -0,0 +1,5 @@
1
+ import type { BaileysEventEmitter } from 'baileys';
2
+ export default function messageHandler(sessionId: string, event: BaileysEventEmitter, getJid?: Function | undefined): {
3
+ listen: () => void;
4
+ unlisten: () => void;
5
+ };