@arcaelas/whatsapp 5.1.0 → 6.1.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.
Files changed (96) hide show
  1. package/README.md +284 -239
  2. package/build/cjs/decorators.js +0 -1
  3. package/build/cjs/index.js +0 -1
  4. package/build/cjs/lib/bot/decorator.js +0 -1
  5. package/build/cjs/lib/bot/decorators.d.ts +2 -2
  6. package/build/cjs/lib/bot/decorators.js +4 -4
  7. package/build/cjs/lib/bot/index.js +0 -1
  8. package/build/cjs/lib/chat/index.d.ts +41 -0
  9. package/build/cjs/lib/chat/index.js +27 -8
  10. package/build/cjs/lib/contact/index.js +0 -1
  11. package/build/cjs/lib/internal.js +0 -1
  12. package/build/cjs/lib/message/index.d.ts +10 -0
  13. package/build/cjs/lib/message/index.js +79 -14
  14. package/build/cjs/lib/status/index.js +0 -1
  15. package/build/cjs/lib/store/engine/index.d.ts +14 -0
  16. package/build/cjs/lib/store/engine/index.js +0 -1
  17. package/build/cjs/lib/store/engine/lib/file_system/index.d.ts +10 -0
  18. package/build/cjs/lib/store/engine/lib/file_system/index.js +27 -1
  19. package/build/cjs/lib/store/engine/lib/index.js +0 -1
  20. package/build/cjs/lib/store/engine/lib/redis/index.d.ts +18 -4
  21. package/build/cjs/lib/store/engine/lib/redis/index.js +21 -3
  22. package/build/cjs/lib/store/engine/lib/s3/index.js +0 -1
  23. package/build/cjs/lib/store/engine/lib/sqlite/index.d.ts +16 -4
  24. package/build/cjs/lib/store/engine/lib/sqlite/index.js +24 -6
  25. package/build/cjs/lib/store/index.js +0 -1
  26. package/build/cjs/lib/whatsapp/index.d.ts +1 -1
  27. package/build/cjs/lib/whatsapp/index.js +76 -20
  28. package/build/esm/decorators.js +0 -1
  29. package/build/esm/index.js +0 -1
  30. package/build/esm/lib/bot/decorator.js +0 -1
  31. package/build/esm/lib/bot/decorators.d.ts +2 -2
  32. package/build/esm/lib/bot/decorators.js +4 -4
  33. package/build/esm/lib/bot/index.js +0 -1
  34. package/build/esm/lib/chat/index.d.ts +41 -0
  35. package/build/esm/lib/chat/index.js +27 -8
  36. package/build/esm/lib/contact/index.js +0 -1
  37. package/build/esm/lib/internal.js +0 -1
  38. package/build/esm/lib/message/index.d.ts +10 -0
  39. package/build/esm/lib/message/index.js +79 -14
  40. package/build/esm/lib/status/index.js +0 -1
  41. package/build/esm/lib/store/engine/index.d.ts +14 -0
  42. package/build/esm/lib/store/engine/index.js +0 -1
  43. package/build/esm/lib/store/engine/lib/file_system/index.d.ts +10 -0
  44. package/build/esm/lib/store/engine/lib/file_system/index.js +27 -1
  45. package/build/esm/lib/store/engine/lib/index.js +0 -1
  46. package/build/esm/lib/store/engine/lib/redis/index.d.ts +18 -4
  47. package/build/esm/lib/store/engine/lib/redis/index.js +21 -3
  48. package/build/esm/lib/store/engine/lib/s3/index.js +0 -1
  49. package/build/esm/lib/store/engine/lib/sqlite/index.d.ts +16 -4
  50. package/build/esm/lib/store/engine/lib/sqlite/index.js +24 -6
  51. package/build/esm/lib/store/index.js +0 -1
  52. package/build/esm/lib/whatsapp/index.d.ts +1 -1
  53. package/build/esm/lib/whatsapp/index.js +76 -20
  54. package/package.json +24 -5
  55. package/build/cjs/decorators.js.map +0 -1
  56. package/build/cjs/index.js.map +0 -1
  57. package/build/cjs/lib/bot/decorator.js.map +0 -1
  58. package/build/cjs/lib/bot/decorators.js.map +0 -1
  59. package/build/cjs/lib/bot/index.js.map +0 -1
  60. package/build/cjs/lib/chat/index.js.map +0 -1
  61. package/build/cjs/lib/contact/index.js.map +0 -1
  62. package/build/cjs/lib/internal.js.map +0 -1
  63. package/build/cjs/lib/message/index.js.map +0 -1
  64. package/build/cjs/lib/status/index.js.map +0 -1
  65. package/build/cjs/lib/store/engine/index.js.map +0 -1
  66. package/build/cjs/lib/store/engine/lib/file_system/index.js.map +0 -1
  67. package/build/cjs/lib/store/engine/lib/index.js.map +0 -1
  68. package/build/cjs/lib/store/engine/lib/redis/index.js.map +0 -1
  69. package/build/cjs/lib/store/engine/lib/s3/index.js.map +0 -1
  70. package/build/cjs/lib/store/engine/lib/sqlite/index.js.map +0 -1
  71. package/build/cjs/lib/store/index.js.map +0 -1
  72. package/build/cjs/lib/whatsapp/index.js.map +0 -1
  73. package/build/cjs/test.d.ts +0 -1
  74. package/build/cjs/test.js +0 -72
  75. package/build/cjs/test.js.map +0 -1
  76. package/build/esm/decorators.js.map +0 -1
  77. package/build/esm/index.js.map +0 -1
  78. package/build/esm/lib/bot/decorator.js.map +0 -1
  79. package/build/esm/lib/bot/decorators.js.map +0 -1
  80. package/build/esm/lib/bot/index.js.map +0 -1
  81. package/build/esm/lib/chat/index.js.map +0 -1
  82. package/build/esm/lib/contact/index.js.map +0 -1
  83. package/build/esm/lib/internal.js.map +0 -1
  84. package/build/esm/lib/message/index.js.map +0 -1
  85. package/build/esm/lib/status/index.js.map +0 -1
  86. package/build/esm/lib/store/engine/index.js.map +0 -1
  87. package/build/esm/lib/store/engine/lib/file_system/index.js.map +0 -1
  88. package/build/esm/lib/store/engine/lib/index.js.map +0 -1
  89. package/build/esm/lib/store/engine/lib/redis/index.js.map +0 -1
  90. package/build/esm/lib/store/engine/lib/s3/index.js.map +0 -1
  91. package/build/esm/lib/store/engine/lib/sqlite/index.js.map +0 -1
  92. package/build/esm/lib/store/index.js.map +0 -1
  93. package/build/esm/lib/whatsapp/index.js.map +0 -1
  94. package/build/esm/test.d.ts +0 -1
  95. package/build/esm/test.js +0 -67
  96. package/build/esm/test.js.map +0 -1
@@ -103,6 +103,10 @@ export declare class Message {
103
103
  get status(): (typeof STATUS)[number];
104
104
  /** true si el mensaje fue visto. / true when the message was seen. */
105
105
  get read(): boolean;
106
+ /** Motivo del rechazo cuando `status` es `error` (`restricted`, `invalid-session`, o el código crudo); null en cualquier otro caso. / Rejection reason when `status` is `error` (`restricted`, `invalid-session`, or the raw code); null otherwise. */
107
+ get reason(): string | null;
108
+ /** Nombre del negocio verificado que firma el mensaje, o null. / Verified business name signing the message, or null. */
109
+ get business(): string | null;
106
110
  /** true si está destacado. / true when starred. */
107
111
  get starred(): boolean;
108
112
  /** true si fue reenviado. / true when forwarded. */
@@ -451,6 +455,12 @@ export declare class Sticker extends Message {
451
455
  * Document message; the actual MIME lives in the base `mime`.
452
456
  */
453
457
  export declare class Document extends Message {
458
+ /** @internal */
459
+ private get _media();
460
+ /** Nombre del archivo. / File name. */
461
+ get name(): string;
462
+ /** Número de páginas (PDF); 0 cuando no aplica. / Page count (PDF); 0 when not applicable. */
463
+ get pages(): number;
454
464
  /** Peso en bytes. / Size in bytes. */
455
465
  get size(): number;
456
466
  content(): Promise<Buffer>;
@@ -14,6 +14,16 @@ import { Chat } from '../../lib/chat/index.js';
14
14
  import { deserialize, serialize } from '../../lib/store/index.js';
15
15
  /** Estados legibles indexados por el status numérico de baileys. / Readable states indexed by the baileys numeric status. */
16
16
  const STATUS = ['error', 'pending', 'sent', 'delivered', 'read', 'played'];
17
+ /**
18
+ * Motivos legibles de los códigos con los que el servidor rechaza un mensaje. `restricted`
19
+ * es la cuenta limitada por WhatsApp: puede seguir chats existentes pero no abrir nuevos.
20
+ * Readable reasons for the codes the server rejects a message with. `restricted` is an
21
+ * account limited by WhatsApp: it can continue existing chats but not open new ones.
22
+ */
23
+ const REASON = {
24
+ 463: 'restricted',
25
+ 479: 'invalid-session',
26
+ };
17
27
  const MESSAGE_TYPE_MAP = {
18
28
  conversation: 'text',
19
29
  extendedTextMessage: 'text',
@@ -75,11 +85,11 @@ async function send(wa, cid, content, binary, extra = {}, doc_overrides) {
75
85
  const quoted = extra.mid
76
86
  ? (deserialize(await wa.engine.get(`/chat/${jid}/message/${extra.mid}`))?.raw ?? undefined)
77
87
  : undefined;
78
- const raw = await socket.sendMessage(jid, {
79
- ...content,
80
- ...(extra.once && { viewOnce: true }),
81
- ...(quoted && { quoted }),
82
- });
88
+ // La cita viaja en las opciones del socket, no en el contenido: dentro del contenido
89
+ // baileys la ignora y el mensaje sale sin `contextInfo`.
90
+ // The quote travels in the socket options, not in the content: inside the content
91
+ // baileys ignores it and the message goes out without `contextInfo`.
92
+ const raw = await socket.sendMessage(jid, { ...content, ...(extra.once && { viewOnce: true }) }, { ...(quoted && { quoted }) });
83
93
  const sent_id = raw?.key?.id;
84
94
  if (raw && sent_id) {
85
95
  const instance = message(wa, raw);
@@ -88,21 +98,56 @@ async function send(wa, cid, content, binary, extra = {}, doc_overrides) {
88
98
  }
89
99
  await wa.engine.set(`/chat/${jid}/message/${sent_id}`, serialize(instance._raw), instance._raw.created_at);
90
100
  if (binary) {
91
- await wa.engine.set(`/chat/${jid}/message/${sent_id}/content`, serialize({ data: binary.toString('base64') }));
101
+ await write_content(wa, `/chat/${jid}/message/${sent_id}/content`, binary);
92
102
  }
93
103
  result = instance;
94
104
  }
95
105
  }
96
106
  return result;
97
107
  }
108
+ /**
109
+ * Binario persistido de un documento: primero el crudo del engine y, si el driver no soporta
110
+ * binarios o el dato viene de una sesión anterior, el documento JSON con base64.
111
+ * A document's persisted binary: the engine's raw one first and, when the driver lacks binary
112
+ * support or the data comes from an older session, the base64 JSON document.
113
+ *
114
+ * @param wa - Cliente dueño / Owner client
115
+ * @param path - Ruta del contenido / Content path
116
+ * @returns Binario o null / Binary or null
117
+ */
118
+ async function read_content(wa, path) {
119
+ const raw = await wa.engine.get_buffer?.(path);
120
+ if (raw?.length) {
121
+ return raw;
122
+ }
123
+ const legacy = deserialize(await wa.engine.get(path));
124
+ return legacy?.data ? Buffer.from(legacy.data, 'base64') : null;
125
+ }
126
+ /**
127
+ * Persiste el binario de un documento: crudo cuando el driver lo soporta, JSON con base64
128
+ * en caso contrario.
129
+ * Persists a document's binary: raw when the driver supports it, base64 JSON otherwise.
130
+ *
131
+ * @param wa - Cliente dueño / Owner client
132
+ * @param path - Ruta del contenido / Content path
133
+ * @param data - Binario a guardar / Binary to store
134
+ */
135
+ async function write_content(wa, path, data) {
136
+ if (wa.engine.set_buffer) {
137
+ await wa.engine.set_buffer(path, data);
138
+ }
139
+ else {
140
+ await wa.engine.set(path, serialize({ data: data.toString('base64') }));
141
+ }
142
+ }
98
143
  /**
99
144
  * Stream del binario del mensaje: cache del engine o descarga de baileys.
100
145
  * Message binary stream: engine cache or baileys download.
101
146
  */
102
147
  async function media_stream(wa, doc) {
103
- const cached = deserialize(await wa.engine.get(`/chat/${doc.cid}/message/${doc.id}/content`));
104
- if (cached?.data) {
105
- return Readable.from(Buffer.from(cached.data, 'base64'));
148
+ const cached = await read_content(wa, `/chat/${doc.cid}/message/${doc.id}/content`);
149
+ if (cached) {
150
+ return Readable.from(cached);
106
151
  }
107
152
  const socket = internals(wa).socket;
108
153
  if (socket) {
@@ -192,6 +237,17 @@ export class Message {
192
237
  get read() {
193
238
  return this._raw.status >= 4;
194
239
  }
240
+ /** Motivo del rechazo cuando `status` es `error` (`restricted`, `invalid-session`, o el código crudo); null en cualquier otro caso. / Rejection reason when `status` is `error` (`restricted`, `invalid-session`, or the raw code); null otherwise. */
241
+ get reason() {
242
+ const code = this._raw.raw.messageStubParameters?.[0];
243
+ if (this._raw.status === 0 && code)
244
+ return REASON[code] ?? code;
245
+ return null;
246
+ }
247
+ /** Nombre del negocio verificado que firma el mensaje, o null. / Verified business name signing the message, or null. */
248
+ get business() {
249
+ return this._raw.raw.verifiedBizName ?? null;
250
+ }
195
251
  /** true si está destacado. / true when starred. */
196
252
  get starred() {
197
253
  return this._raw.starred;
@@ -255,8 +311,7 @@ export class Message {
255
311
  * @returns Buffer del contenido / Content buffer
256
312
  */
257
313
  async content() {
258
- const cached = deserialize(await this._wa.engine.get(`/chat/${this._raw.cid}/message/${this._raw.id}/content`));
259
- return cached?.data ? Buffer.from(cached.data, 'base64') : Buffer.alloc(0);
314
+ return (await read_content(this._wa, `/chat/${this._raw.cid}/message/${this._raw.id}/content`)) ?? Buffer.alloc(0);
260
315
  }
261
316
  /**
262
317
  * Stream del contenido; para media descarga de baileys si el cache falta.
@@ -779,10 +834,21 @@ export class Sticker extends Message {
779
834
  * Document message; the actual MIME lives in the base `mime`.
780
835
  */
781
836
  export class Document extends Message {
837
+ /** @internal */
838
+ get _media() {
839
+ return unwrap(this._raw.raw.message ?? {}).documentMessage;
840
+ }
841
+ /** Nombre del archivo. / File name. */
842
+ get name() {
843
+ return this._media?.fileName ?? '';
844
+ }
845
+ /** Número de páginas (PDF); 0 cuando no aplica. / Page count (PDF); 0 when not applicable. */
846
+ get pages() {
847
+ return this._media?.pageCount ?? 0;
848
+ }
782
849
  /** Peso en bytes. / Size in bytes. */
783
850
  get size() {
784
- const media = unwrap(this._raw.raw.message ?? {}).documentMessage;
785
- return to_number(media?.fileLength);
851
+ return to_number(this._media?.fileLength);
786
852
  }
787
853
  async content() {
788
854
  return drain(await this.stream());
@@ -1064,4 +1130,3 @@ export function message(wa, raw) {
1064
1130
  };
1065
1131
  return new (types[doc.type] ?? Message)(wa, doc);
1066
1132
  }
1067
- //# sourceMappingURL=index.js.map
@@ -125,4 +125,3 @@ export class Feed extends Message {
125
125
  throw new Error('ERR_FEED_UNSUPPORTED');
126
126
  }
127
127
  }
128
- //# sourceMappingURL=index.js.map
@@ -42,6 +42,20 @@ export interface Engine {
42
42
  count(path: string): Promise<number>;
43
43
  /** Vacía completamente el almacén. / Clears the entire store. */
44
44
  clear(): Promise<void>;
45
+ /**
46
+ * Lee un binario por path. Opcional: un driver que no lo implemente sigue siendo válido y
47
+ * la librería cae al documento serializado.
48
+ * Reads a binary by path. Optional: a driver that does not implement it stays valid and the
49
+ * library falls back to the serialized document.
50
+ */
51
+ get_buffer?(path: string): Promise<Buffer | null>;
52
+ /**
53
+ * Escribe un binario sin pasar por JSON ni base64 (un 33% menos de peso y sin parseo para
54
+ * leerlo). Opcional, igual que `get_buffer`.
55
+ * Writes a binary without JSON nor base64 (33% less weight and no parsing to read it back).
56
+ * Optional, just like `get_buffer`.
57
+ */
58
+ set_buffer?(path: string, data: Buffer, score?: number): Promise<void>;
45
59
  }
46
60
  export { FileSystemEngine } from '../../../lib/store/engine/lib/file_system/index.js';
47
61
  export { RedisEngine, type RedisClient } from '../../../lib/store/engine/lib/redis/index.js';
@@ -8,4 +8,3 @@ export { FileSystemEngine } from '../../../lib/store/engine/lib/file_system/inde
8
8
  export { RedisEngine } from '../../../lib/store/engine/lib/redis/index.js';
9
9
  export { S3Engine } from '../../../lib/store/engine/lib/s3/index.js';
10
10
  export { SQLiteEngine } from '../../../lib/store/engine/lib/sqlite/index.js';
11
- //# sourceMappingURL=index.js.map
@@ -78,6 +78,16 @@ export declare class FileSystemEngine implements Engine {
78
78
  * Counts direct children with a valid document.
79
79
  */
80
80
  count(path: string): Promise<number>;
81
+ /**
82
+ * Lee el binario del documento, o null si no existe.
83
+ * Reads the document binary, or null when missing.
84
+ */
85
+ get_buffer(path: string): Promise<Buffer | null>;
86
+ /**
87
+ * Escribe el binario del documento como archivo crudo, sin JSON ni base64.
88
+ * Writes the document binary as a raw file, with no JSON nor base64.
89
+ */
90
+ set_buffer(path: string, data: Buffer, score?: number): Promise<void>;
81
91
  /**
82
92
  * Vacía completamente el almacén.
83
93
  * Clears the entire store.
@@ -10,6 +10,8 @@ import { IndexCache, normalize_path, SortedIndex, split_path } from '../../../..
10
10
  const INDEX_FILE = 'index.json';
11
11
  /** Índice de orden persistido junto a los hijos del directorio. / Ordering index persisted next to the directory children. */
12
12
  const ORDER_FILE = '.order';
13
+ /** Binario del documento, guardado crudo junto a su JSON. / Document binary, stored raw next to its JSON. */
14
+ const BINARY_FILE = 'content.bin';
13
15
  /**
14
16
  * Driver de persistencia en sistema de archivos.
15
17
  * Cada documento se almacena como `<base>/<path>/index.json` de modo que un recurso pueda
@@ -158,6 +160,31 @@ export class FileSystemEngine {
158
160
  async count(path) {
159
161
  return (await this._index(path)).size;
160
162
  }
163
+ /**
164
+ * Lee el binario del documento, o null si no existe.
165
+ * Reads the document binary, or null when missing.
166
+ */
167
+ async get_buffer(path) {
168
+ return readFile(join(this._dir(path), BINARY_FILE)).catch(() => null);
169
+ }
170
+ /**
171
+ * Escribe el binario del documento como archivo crudo, sin JSON ni base64.
172
+ * Writes the document binary as a raw file, with no JSON nor base64.
173
+ */
174
+ async set_buffer(path, data, score) {
175
+ const dir = this._dir(path);
176
+ await mkdir(dir, { recursive: true });
177
+ const file = join(dir, BINARY_FILE);
178
+ const tmp = `${file}.${randomBytes(6).toString('hex')}.tmp`;
179
+ await writeFile(tmp, data);
180
+ await rename(tmp, file);
181
+ const when = score ?? Date.now();
182
+ if (score !== undefined) {
183
+ await utimes(file, new Date(score), new Date(score)).catch(() => { });
184
+ }
185
+ const { parent, name } = split_path(path);
186
+ this._indexes.get(parent)?.set(name, when);
187
+ }
161
188
  /**
162
189
  * Vacía completamente el almacén.
163
190
  * Clears the entire store.
@@ -172,4 +199,3 @@ export class FileSystemEngine {
172
199
  this._indexes.clear();
173
200
  }
174
201
  }
175
- //# sourceMappingURL=index.js.map
@@ -203,4 +203,3 @@ export function split_path(path) {
203
203
  const cut = full.lastIndexOf('/');
204
204
  return cut === -1 ? { parent: '', name: full } : { parent: full.slice(0, cut), name: full.slice(cut + 1) };
205
205
  }
206
- //# sourceMappingURL=index.js.map
@@ -13,18 +13,20 @@ import type { Engine } from '../../../../../lib/store/engine/index.js';
13
13
  export interface RedisClient {
14
14
  get(key: string): Promise<string | null>;
15
15
  set(key: string, value: string): Promise<unknown>;
16
- del(keys: string | string[]): Promise<unknown>;
16
+ del(...keys: unknown[]): Promise<unknown>;
17
17
  mget(keys: string[]): Promise<(string | null)[]>;
18
18
  scan(cursor: number | string, ...args: unknown[]): Promise<[string, string[]]>;
19
19
  zadd(key: string, score: number, member: string): Promise<unknown>;
20
- zrem(key: string, members: string | string[]): Promise<unknown>;
20
+ zrem(...args: unknown[]): Promise<unknown>;
21
21
  zrevrange(key: string, start: number, stop: number): Promise<string[]>;
22
22
  zcard(key: string): Promise<number>;
23
+ getBuffer?(...args: unknown[]): Promise<Buffer | null>;
24
+ setBuffer?(...args: unknown[]): Promise<unknown>;
23
25
  pipeline?(): {
24
26
  set(key: string, value: string): unknown;
25
- del(keys: string | string[]): unknown;
27
+ del(...keys: unknown[]): unknown;
26
28
  zadd(key: string, score: number, member: string): unknown;
27
- zrem(key: string, members: string | string[]): unknown;
29
+ zrem(...args: unknown[]): unknown;
28
30
  exec(): Promise<unknown>;
29
31
  };
30
32
  }
@@ -82,6 +84,18 @@ export declare class RedisEngine implements Engine {
82
84
  * Counts direct children in O(1) via ZCARD.
83
85
  */
84
86
  count(path: string): Promise<number>;
87
+ /**
88
+ * Lee el binario del documento. Requiere un cliente con soporte de buffers (ioredis lo
89
+ * trae con `getBuffer`); sin él la librería cae al documento serializado.
90
+ * Reads the document binary. Requires a buffer-capable client (ioredis ships `getBuffer`);
91
+ * without it the library falls back to the serialized document.
92
+ */
93
+ get_buffer(path: string): Promise<Buffer | null>;
94
+ /**
95
+ * Escribe el binario del documento en su propia key, sin JSON ni base64.
96
+ * Writes the document binary in its own key, with no JSON nor base64.
97
+ */
98
+ set_buffer(path: string, data: Buffer, score?: number): Promise<void>;
85
99
  /**
86
100
  * Vacía todo el prefix del cliente.
87
101
  * Clears the entire client prefix.
@@ -75,12 +75,12 @@ export class RedisEngine {
75
75
  const full = normalize_path(path);
76
76
  const pipeline = this._client.pipeline?.();
77
77
  if (pipeline) {
78
- pipeline.del(this._doc_key(full));
78
+ pipeline.del([this._doc_key(full), `${this._doc_key(full)}:bin`]);
79
79
  pipeline.zrem(this._idx_key(parent), full);
80
80
  await pipeline.exec();
81
81
  }
82
82
  else {
83
- await this._client.del(this._doc_key(full));
83
+ await this._client.del([this._doc_key(full), `${this._doc_key(full)}:bin`]);
84
84
  await this._client.zrem(this._idx_key(parent), full);
85
85
  }
86
86
  await Promise.all([
@@ -109,6 +109,25 @@ export class RedisEngine {
109
109
  async count(path) {
110
110
  return this._client.zcard(this._idx_key(path));
111
111
  }
112
+ /**
113
+ * Lee el binario del documento. Requiere un cliente con soporte de buffers (ioredis lo
114
+ * trae con `getBuffer`); sin él la librería cae al documento serializado.
115
+ * Reads the document binary. Requires a buffer-capable client (ioredis ships `getBuffer`);
116
+ * without it the library falls back to the serialized document.
117
+ */
118
+ async get_buffer(path) {
119
+ return (await this._client.getBuffer?.(`${this._doc_key(path)}:bin`)) ?? null;
120
+ }
121
+ /**
122
+ * Escribe el binario del documento en su propia key, sin JSON ni base64.
123
+ * Writes the document binary in its own key, with no JSON nor base64.
124
+ */
125
+ async set_buffer(path, data, score) {
126
+ const { parent } = split_path(path);
127
+ const full = normalize_path(path);
128
+ await this._client.setBuffer?.(`${this._doc_key(full)}:bin`, data);
129
+ await this._client.zadd(this._idx_key(parent), score ?? Date.now(), full);
130
+ }
112
131
  /**
113
132
  * Vacía todo el prefix del cliente.
114
133
  * Clears the entire client prefix.
@@ -133,4 +152,3 @@ export class RedisEngine {
133
152
  } while (cursor !== '0');
134
153
  }
135
154
  }
136
- //# sourceMappingURL=index.js.map
@@ -210,4 +210,3 @@ export class S3Engine {
210
210
  await this.delete_prefix('');
211
211
  }
212
212
  }
213
- //# sourceMappingURL=index.js.map
@@ -33,22 +33,22 @@ export interface SQLiteDatabase {
33
33
  * resuelven a mano: `list` es un `ORDER BY score DESC LIMIT/OFFSET` sobre el índice (sin
34
34
  * índice en memoria ni archivo de orden), `count` un `COUNT(*)` y el `unset` en cascada una
35
35
  * sola sentencia por rango de `path`. Medido sobre un chat real de 55.146 mensajes frente al
36
- * filesystem: 220 MB → 111 MB en disco, primer `list` 115 ms → 0,7 ms y dos inodes en total.
36
+ * filesystem: 220 MB → 64 MB en disco, primer `list` 115 ms → 0,6 ms y dos archivos en total.
37
37
  *
38
38
  * SQLite persistence driver: a single `(path, parent, score, value)` table with a
39
39
  * `(parent, score DESC)` index. The most efficient built-in driver, since it delegates to the
40
40
  * engine what the others hand-roll: `list` is an indexed `ORDER BY score DESC LIMIT/OFFSET`
41
41
  * (no in-memory index, no order file), `count` a `COUNT(*)` and cascading `unset` a single
42
42
  * range statement. Measured on a real 55,146-message chat against the filesystem: 220 MB →
43
- * 111 MB on disk, first `list` 115 ms → 0.7 ms and two inodes in total.
43
+ * 64 MB on disk, first `list` 115 ms → 0.6 ms and two files in total.
44
44
  *
45
45
  * @example
46
46
  * import Database from 'better-sqlite3';
47
- * const engine = new SQLiteEngine(new Database('.sessions/584144709840.db'));
47
+ * const engine = new SQLiteEngine(new Database('.sessions/5491112345678.db'));
48
48
  *
49
49
  * @example
50
50
  * import { DatabaseSync } from 'node:sqlite';
51
- * const engine = new SQLiteEngine(new DatabaseSync('.sessions/584144709840.db'));
51
+ * const engine = new SQLiteEngine(new DatabaseSync('.sessions/5491112345678.db'));
52
52
  */
53
53
  export declare class SQLiteEngine implements Engine {
54
54
  private readonly _db;
@@ -58,6 +58,8 @@ export declare class SQLiteEngine implements Engine {
58
58
  private readonly _page;
59
59
  private readonly _total;
60
60
  private readonly _wipe;
61
+ private readonly _read_blob;
62
+ private readonly _write_blob;
61
63
  /**
62
64
  * @param _db - Base SQLite ya abierta / Already open SQLite database
63
65
  * @param table - Tabla donde viven los documentos / Table holding the documents
@@ -89,6 +91,16 @@ export declare class SQLiteEngine implements Engine {
89
91
  * Counts direct children.
90
92
  */
91
93
  count(path: string): Promise<number>;
94
+ /**
95
+ * Lee el binario del documento, o null si la fila no lo tiene.
96
+ * Reads the document binary, or null when the row has none.
97
+ */
98
+ get_buffer(path: string): Promise<Buffer | null>;
99
+ /**
100
+ * Escribe el binario del documento como BLOB, sin JSON ni base64.
101
+ * Writes the document binary as a BLOB, with no JSON nor base64.
102
+ */
103
+ set_buffer(path: string, data: Buffer, score?: number): Promise<void>;
92
104
  /**
93
105
  * Vacía la tabla completa.
94
106
  * Clears the entire table.
@@ -12,22 +12,22 @@ import { normalize_path, split_path } from '../../../../../lib/store/engine/lib/
12
12
  * resuelven a mano: `list` es un `ORDER BY score DESC LIMIT/OFFSET` sobre el índice (sin
13
13
  * índice en memoria ni archivo de orden), `count` un `COUNT(*)` y el `unset` en cascada una
14
14
  * sola sentencia por rango de `path`. Medido sobre un chat real de 55.146 mensajes frente al
15
- * filesystem: 220 MB → 111 MB en disco, primer `list` 115 ms → 0,7 ms y dos inodes en total.
15
+ * filesystem: 220 MB → 64 MB en disco, primer `list` 115 ms → 0,6 ms y dos archivos en total.
16
16
  *
17
17
  * SQLite persistence driver: a single `(path, parent, score, value)` table with a
18
18
  * `(parent, score DESC)` index. The most efficient built-in driver, since it delegates to the
19
19
  * engine what the others hand-roll: `list` is an indexed `ORDER BY score DESC LIMIT/OFFSET`
20
20
  * (no in-memory index, no order file), `count` a `COUNT(*)` and cascading `unset` a single
21
21
  * range statement. Measured on a real 55,146-message chat against the filesystem: 220 MB →
22
- * 111 MB on disk, first `list` 115 ms → 0.7 ms and two inodes in total.
22
+ * 64 MB on disk, first `list` 115 ms → 0.6 ms and two files in total.
23
23
  *
24
24
  * @example
25
25
  * import Database from 'better-sqlite3';
26
- * const engine = new SQLiteEngine(new Database('.sessions/584144709840.db'));
26
+ * const engine = new SQLiteEngine(new Database('.sessions/5491112345678.db'));
27
27
  *
28
28
  * @example
29
29
  * import { DatabaseSync } from 'node:sqlite';
30
- * const engine = new SQLiteEngine(new DatabaseSync('.sessions/584144709840.db'));
30
+ * const engine = new SQLiteEngine(new DatabaseSync('.sessions/5491112345678.db'));
31
31
  */
32
32
  export class SQLiteEngine {
33
33
  /**
@@ -46,7 +46,7 @@ export class SQLiteEngine {
46
46
  catch {
47
47
  /* optional tuning */
48
48
  }
49
- _db.exec(`CREATE TABLE IF NOT EXISTS ${table} (path TEXT PRIMARY KEY, parent TEXT NOT NULL, score INTEGER NOT NULL, value TEXT NOT NULL);
49
+ _db.exec(`CREATE TABLE IF NOT EXISTS ${table} (path TEXT PRIMARY KEY, parent TEXT NOT NULL, score INTEGER NOT NULL, value TEXT NOT NULL DEFAULT '', binary BLOB);
50
50
  CREATE INDEX IF NOT EXISTS ${table}_order ON ${table} (parent, score DESC);`);
51
51
  this._read = _db.prepare(`SELECT value FROM ${table} WHERE path = ?`);
52
52
  this._write = _db.prepare(`INSERT INTO ${table} (path, parent, score, value) VALUES (?, ?, ?, ?)
@@ -59,6 +59,9 @@ export class SQLiteEngine {
59
59
  this._page = _db.prepare(`SELECT value FROM ${table} WHERE parent = ? ORDER BY score DESC LIMIT ? OFFSET ?`);
60
60
  this._total = _db.prepare(`SELECT COUNT(*) AS total FROM ${table} WHERE parent = ?`);
61
61
  this._wipe = _db.prepare(`DELETE FROM ${table}`);
62
+ this._read_blob = _db.prepare(`SELECT binary FROM ${table} WHERE path = ?`);
63
+ this._write_blob = _db.prepare(`INSERT INTO ${table} (path, parent, score, value, binary) VALUES (?, ?, ?, '', ?)
64
+ ON CONFLICT(path) DO UPDATE SET binary = excluded.binary, score = excluded.score`);
62
65
  }
63
66
  /**
64
67
  * Lee el valor de un documento.
@@ -99,6 +102,22 @@ export class SQLiteEngine {
99
102
  async count(path) {
100
103
  return Number(this._total.get(normalize_path(path)).total);
101
104
  }
105
+ /**
106
+ * Lee el binario del documento, o null si la fila no lo tiene.
107
+ * Reads the document binary, or null when the row has none.
108
+ */
109
+ async get_buffer(path) {
110
+ const row = this._read_blob.get(normalize_path(path));
111
+ return row?.binary ? Buffer.from(row.binary) : null;
112
+ }
113
+ /**
114
+ * Escribe el binario del documento como BLOB, sin JSON ni base64.
115
+ * Writes the document binary as a BLOB, with no JSON nor base64.
116
+ */
117
+ async set_buffer(path, data, score) {
118
+ const { parent } = split_path(path);
119
+ this._write_blob.run(normalize_path(path), parent, score ?? Date.now(), data);
120
+ }
102
121
  /**
103
122
  * Vacía la tabla completa.
104
123
  * Clears the entire table.
@@ -107,4 +126,3 @@ export class SQLiteEngine {
107
126
  this._wipe.run();
108
127
  }
109
128
  }
110
- //# sourceMappingURL=index.js.map
@@ -37,4 +37,3 @@ export function deserialize(raw) {
37
37
  return null;
38
38
  }
39
39
  }
40
- //# sourceMappingURL=index.js.map
@@ -111,7 +111,7 @@ interface WhatsAppEventMap {
111
111
  * Main WhatsApp client. Does not connect on instantiation.
112
112
  *
113
113
  * @example
114
- * const wa = new WhatsApp({ engine: new FileSystemEngine(__dirname), phone: 584144709840 });
114
+ * const wa = new WhatsApp({ engine: new FileSystemEngine(__dirname), phone: 5491112345678 });
115
115
  * wa.on('message:created', (msg) => console.log(msg.caption));
116
116
  * await wa.connect((code) => console.log(code));
117
117
  */