zapo-js 1.6.1 → 1.6.2

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.
@@ -34,6 +34,7 @@ import { WaSignalMemoryStore } from './memory/signal.store.js';
34
34
  import { WaThreadMemoryStore } from './memory/thread.store.js';
35
35
  import { NOOP_CONTACT_STORE, NOOP_DEVICE_LIST_STORE, NOOP_GROUP_METADATA_STORE, NOOP_MESSAGE_SECRET_STORE, NOOP_MESSAGE_STORE, NOOP_RETRY_STORE, NOOP_THREAD_STORE } from './noop.store.js';
36
36
  import { resolvePositive } from '../util/coercion.js';
37
+ import { toError } from '../util/primitives.js';
37
38
  const DEFAULT_CACHE_TTLS_MS = Object.freeze({
38
39
  retryMs: 60 * 1000,
39
40
  groupMetadataMs: 5 * 60 * 1000,
@@ -109,6 +110,7 @@ export function createStore(options) {
109
110
  messageSecret: resolvePositive(options.memory?.cacheTtlMs?.messageSecretMs, DEFAULT_CACHE_TTLS_MS.messageSecretMs, 'memory.cacheTtlMs.messageSecretMs')
110
111
  });
111
112
  const sessions = new Map();
113
+ const pendingSessionDestroys = new Set();
112
114
  let storeDestroyed = false;
113
115
  return {
114
116
  session(sessionId) {
@@ -148,37 +150,48 @@ export function createStore(options) {
148
150
  ? new WaContactMemoryStore({ maxContacts: ml.contacts })
149
151
  : NOOP_CONTACT_STORE);
150
152
  const rawPrivacyToken = resolveStore(id, backends, providers.privacyToken ?? 'memory', 'privacyToken', 'stores', () => new WaPrivacyTokenMemoryStore(ml.privacyTokens));
151
- const rawRetry = resolveStore(id, backends, cacheProviders.retry ?? 'memory', 'retry', 'caches', () => cacheProviders.retry === 'memory' || !cacheProviders.retry
152
- ? new WaRetryMemoryStore(cacheTtlsMs.retry, {
153
- maxOutboundMessages: ml.retryOutboundMessages,
154
- maxInboundCounters: ml.retryInboundCounters,
155
- logger: memoryLogger?.child({ domain: 'retry', sessionId: id })
156
- })
157
- : NOOP_RETRY_STORE);
158
- const rawGroupMetadata = resolveStore(id, backends, cacheProviders.groupMetadata ?? 'memory', 'groupMetadata', 'caches', () => cacheProviders.groupMetadata === 'memory' || !cacheProviders.groupMetadata
159
- ? new WaGroupMetadataMemoryStore(cacheTtlsMs.groupMetadata, {
160
- maxGroups: ml.groupMetadataGroups,
161
- logger: memoryLogger?.child({
162
- domain: 'groupMetadata',
163
- sessionId: id
153
+ const buildCaches = () => ({
154
+ retry: withRetryLock(resolveStore(id, backends, cacheProviders.retry ?? 'memory', 'retry', 'caches', () => cacheProviders.retry === 'memory' || !cacheProviders.retry
155
+ ? new WaRetryMemoryStore(cacheTtlsMs.retry, {
156
+ maxOutboundMessages: ml.retryOutboundMessages,
157
+ maxInboundCounters: ml.retryInboundCounters,
158
+ logger: memoryLogger?.child({
159
+ domain: 'retry',
160
+ sessionId: id
161
+ })
164
162
  })
165
- })
166
- : NOOP_GROUP_METADATA_STORE);
167
- const rawDeviceList = resolveStore(id, backends, cacheProviders.deviceList ?? 'memory', 'deviceList', 'caches', () => cacheProviders.deviceList === 'memory'
168
- ? new WaDeviceListMemoryStore(cacheTtlsMs.deviceList, {
169
- maxUsers: ml.deviceListUsers,
170
- logger: memoryLogger?.child({ domain: 'deviceList', sessionId: id })
171
- })
172
- : NOOP_DEVICE_LIST_STORE);
173
- const rawMessageSecret = resolveStore(id, backends, cacheProviders.messageSecret ?? 'memory', 'messageSecret', 'caches', () => cacheProviders.messageSecret === 'memory' || !cacheProviders.messageSecret
174
- ? new WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
175
- maxSecrets: ml.messageSecrets,
176
- logger: memoryLogger?.child({
177
- domain: 'messageSecret',
178
- sessionId: id
163
+ : NOOP_RETRY_STORE)),
164
+ groupMetadata: withGroupMetadataLock(resolveStore(id, backends, cacheProviders.groupMetadata ?? 'memory', 'groupMetadata', 'caches', () => cacheProviders.groupMetadata === 'memory' ||
165
+ !cacheProviders.groupMetadata
166
+ ? new WaGroupMetadataMemoryStore(cacheTtlsMs.groupMetadata, {
167
+ maxGroups: ml.groupMetadataGroups,
168
+ logger: memoryLogger?.child({
169
+ domain: 'groupMetadata',
170
+ sessionId: id
171
+ })
179
172
  })
180
- })
181
- : NOOP_MESSAGE_SECRET_STORE);
173
+ : NOOP_GROUP_METADATA_STORE)),
174
+ deviceList: withDeviceListLock(resolveStore(id, backends, cacheProviders.deviceList ?? 'memory', 'deviceList', 'caches', () => cacheProviders.deviceList === 'memory' || !cacheProviders.deviceList
175
+ ? new WaDeviceListMemoryStore(cacheTtlsMs.deviceList, {
176
+ maxUsers: ml.deviceListUsers,
177
+ logger: memoryLogger?.child({
178
+ domain: 'deviceList',
179
+ sessionId: id
180
+ })
181
+ })
182
+ : NOOP_DEVICE_LIST_STORE)),
183
+ messageSecret: withMessageSecretLock(resolveStore(id, backends, cacheProviders.messageSecret ?? 'memory', 'messageSecret', 'caches', () => cacheProviders.messageSecret === 'memory' ||
184
+ !cacheProviders.messageSecret
185
+ ? new WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
186
+ maxSecrets: ml.messageSecrets,
187
+ logger: memoryLogger?.child({
188
+ domain: 'messageSecret',
189
+ sessionId: id
190
+ })
191
+ })
192
+ : NOOP_MESSAGE_SECRET_STORE))
193
+ });
194
+ let caches = buildCaches();
182
195
  const authStore = withAuthLock(rawAuth);
183
196
  const signalStore = withSignalLock(rawSignal);
184
197
  const preKeyStore = withPreKeyLock(rawPreKey);
@@ -192,41 +205,72 @@ export function createStore(options) {
192
205
  ? withSenderKeyCache(rawSenderKey, cacheLayer.limits?.senderKey)
193
206
  : rawSenderKey);
194
207
  const appStateStore = withAppStateLock(rawAppState);
195
- const retryStore = withRetryLock(rawRetry);
196
- const groupMetadataStore = withGroupMetadataLock(rawGroupMetadata);
197
- const deviceListStore = withDeviceListLock(rawDeviceList);
198
208
  const messageStore = withMessageLock(rawMessages);
199
- const messageSecretStore = withMessageSecretLock(rawMessageSecret);
200
209
  const threadStore = withThreadLock(rawThreads);
201
210
  const contactStore = withContactLock(rawContacts);
202
211
  const privacyTokenStore = withPrivacyTokenLock(cacheLayer.privacyToken && usesBackend(providers.privacyToken)
203
212
  ? withPrivacyTokenCache(rawPrivacyToken, cacheLayer.limits?.privacyToken)
204
213
  : rawPrivacyToken);
205
- let cachesDestroyed = false;
206
214
  let sessionDestroyed = false;
207
- const destroyCaches = async () => {
208
- if (cachesDestroyed)
209
- return;
210
- cachesDestroyed = true;
211
- await Promise.all([
212
- retryStore.clear(),
213
- groupMetadataStore.clear(),
214
- deviceListStore.clear(),
215
- messageSecretStore.clear()
215
+ let destroyPromise = null;
216
+ let cacheLifecycle = Promise.resolve();
217
+ const teardownCaches = async (target) => {
218
+ const cleared = await Promise.allSettled([
219
+ target.retry.clear(),
220
+ target.groupMetadata.clear(),
221
+ target.deviceList.clear(),
222
+ target.messageSecret.clear()
216
223
  ]);
217
- await Promise.all([
218
- destroyIfSupported(retryStore),
219
- destroyIfSupported(groupMetadataStore),
220
- destroyIfSupported(deviceListStore),
221
- destroyIfSupported(messageSecretStore)
224
+ const destroyed = await Promise.allSettled([
225
+ destroyIfSupported(target.retry),
226
+ destroyIfSupported(target.groupMetadata),
227
+ destroyIfSupported(target.deviceList),
228
+ destroyIfSupported(target.messageSecret)
222
229
  ]);
230
+ const failures = [...cleared, ...destroyed].filter((result) => result.status === 'rejected');
231
+ if (failures.length > 0) {
232
+ storeLogger?.warn('cache teardown had failures', {
233
+ sessionId: id,
234
+ droppedCount: failures.length,
235
+ totalExpected: cleared.length + destroyed.length,
236
+ sample: toError(failures[0].reason).message
237
+ });
238
+ }
239
+ return failures.length;
240
+ };
241
+ const destroyCaches = () => {
242
+ const run = cacheLifecycle.then(async () => {
243
+ if (sessionDestroyed)
244
+ return;
245
+ const failureCount = await teardownCaches(caches);
246
+ caches = buildCaches();
247
+ if (failureCount > 0) {
248
+ throw new Error(`cache reset finished with ${failureCount} teardown failure(s); ` +
249
+ 'fresh caches are in place but old persistent entries may remain');
250
+ }
251
+ });
252
+ cacheLifecycle = run.then(() => undefined, () => undefined);
253
+ return run;
223
254
  };
224
- const destroy = async () => {
225
- if (sessionDestroyed)
226
- return;
255
+ const destroy = () => {
256
+ if (!destroyPromise) {
257
+ const pending = destroyInternal().finally(() => pendingSessionDestroys.delete(pending));
258
+ destroyPromise = pending;
259
+ pendingSessionDestroys.add(pending);
260
+ }
261
+ return destroyPromise;
262
+ };
263
+ const destroyInternal = async () => {
227
264
  sessionDestroyed = true;
228
- await destroyCaches();
229
- await Promise.all([
265
+ if (sessions.get(id) === storeSession) {
266
+ sessions.delete(id);
267
+ }
268
+ await cacheLifecycle;
269
+ await teardownCaches(caches);
270
+ await destroyPersistentStores();
271
+ };
272
+ const destroyPersistentStores = async () => {
273
+ const results = await Promise.allSettled([
230
274
  destroyIfSupported(authStore),
231
275
  destroyIfSupported(signalStore),
232
276
  destroyIfSupported(preKeyStore),
@@ -239,6 +283,15 @@ export function createStore(options) {
239
283
  destroyIfSupported(contactStore),
240
284
  destroyIfSupported(privacyTokenStore)
241
285
  ]);
286
+ const failures = results.filter((result) => result.status === 'rejected');
287
+ if (failures.length > 0) {
288
+ storeLogger?.warn('persistent store teardown had failures', {
289
+ sessionId: id,
290
+ droppedCount: failures.length,
291
+ totalExpected: results.length,
292
+ sample: toError(failures[0].reason).message
293
+ });
294
+ }
242
295
  };
243
296
  const storeSession = {
244
297
  auth: authStore,
@@ -248,11 +301,19 @@ export function createStore(options) {
248
301
  identity: identityStore,
249
302
  senderKey: senderKeyStore,
250
303
  appState: appStateStore,
251
- retry: retryStore,
252
- groupMetadata: groupMetadataStore,
253
- deviceList: deviceListStore,
304
+ get retry() {
305
+ return caches.retry;
306
+ },
307
+ get groupMetadata() {
308
+ return caches.groupMetadata;
309
+ },
310
+ get deviceList() {
311
+ return caches.deviceList;
312
+ },
254
313
  messages: messageStore,
255
- messageSecret: messageSecretStore,
314
+ get messageSecret() {
315
+ return caches.messageSecret;
316
+ },
256
317
  threads: threadStore,
257
318
  contacts: contactStore,
258
319
  privacyToken: privacyTokenStore,
@@ -273,6 +334,7 @@ export function createStore(options) {
273
334
  const list = Array.from(sessions.values());
274
335
  sessions.clear();
275
336
  await Promise.all(list.map((s) => s.destroy()));
337
+ await Promise.all(Array.from(pendingSessionDestroys));
276
338
  const uniqueBackends = new Set(Object.values(backends));
277
339
  await Promise.all(Array.from(uniqueBackends, (backend) => destroyIfSupported(backend)));
278
340
  }
@@ -37,6 +37,7 @@ const signal_store_1 = require("./memory/signal.store");
37
37
  const thread_store_1 = require("./memory/thread.store");
38
38
  const noop_store_1 = require("./noop.store");
39
39
  const coercion_1 = require("../util/coercion");
40
+ const primitives_1 = require("../util/primitives");
40
41
  const DEFAULT_CACHE_TTLS_MS = Object.freeze({
41
42
  retryMs: 60 * 1000,
42
43
  groupMetadataMs: 5 * 60 * 1000,
@@ -112,6 +113,7 @@ function createStore(options) {
112
113
  messageSecret: (0, coercion_1.resolvePositive)(options.memory?.cacheTtlMs?.messageSecretMs, DEFAULT_CACHE_TTLS_MS.messageSecretMs, 'memory.cacheTtlMs.messageSecretMs')
113
114
  });
114
115
  const sessions = new Map();
116
+ const pendingSessionDestroys = new Set();
115
117
  let storeDestroyed = false;
116
118
  return {
117
119
  session(sessionId) {
@@ -151,37 +153,48 @@ function createStore(options) {
151
153
  ? new contact_store_1.WaContactMemoryStore({ maxContacts: ml.contacts })
152
154
  : noop_store_1.NOOP_CONTACT_STORE);
153
155
  const rawPrivacyToken = resolveStore(id, backends, providers.privacyToken ?? 'memory', 'privacyToken', 'stores', () => new privacy_token_store_1.WaPrivacyTokenMemoryStore(ml.privacyTokens));
154
- const rawRetry = resolveStore(id, backends, cacheProviders.retry ?? 'memory', 'retry', 'caches', () => cacheProviders.retry === 'memory' || !cacheProviders.retry
155
- ? new retry_store_1.WaRetryMemoryStore(cacheTtlsMs.retry, {
156
- maxOutboundMessages: ml.retryOutboundMessages,
157
- maxInboundCounters: ml.retryInboundCounters,
158
- logger: memoryLogger?.child({ domain: 'retry', sessionId: id })
159
- })
160
- : noop_store_1.NOOP_RETRY_STORE);
161
- const rawGroupMetadata = resolveStore(id, backends, cacheProviders.groupMetadata ?? 'memory', 'groupMetadata', 'caches', () => cacheProviders.groupMetadata === 'memory' || !cacheProviders.groupMetadata
162
- ? new group_metadata_store_1.WaGroupMetadataMemoryStore(cacheTtlsMs.groupMetadata, {
163
- maxGroups: ml.groupMetadataGroups,
164
- logger: memoryLogger?.child({
165
- domain: 'groupMetadata',
166
- sessionId: id
156
+ const buildCaches = () => ({
157
+ retry: (0, retry_lock_1.withRetryLock)(resolveStore(id, backends, cacheProviders.retry ?? 'memory', 'retry', 'caches', () => cacheProviders.retry === 'memory' || !cacheProviders.retry
158
+ ? new retry_store_1.WaRetryMemoryStore(cacheTtlsMs.retry, {
159
+ maxOutboundMessages: ml.retryOutboundMessages,
160
+ maxInboundCounters: ml.retryInboundCounters,
161
+ logger: memoryLogger?.child({
162
+ domain: 'retry',
163
+ sessionId: id
164
+ })
167
165
  })
168
- })
169
- : noop_store_1.NOOP_GROUP_METADATA_STORE);
170
- const rawDeviceList = resolveStore(id, backends, cacheProviders.deviceList ?? 'memory', 'deviceList', 'caches', () => cacheProviders.deviceList === 'memory'
171
- ? new device_list_store_1.WaDeviceListMemoryStore(cacheTtlsMs.deviceList, {
172
- maxUsers: ml.deviceListUsers,
173
- logger: memoryLogger?.child({ domain: 'deviceList', sessionId: id })
174
- })
175
- : noop_store_1.NOOP_DEVICE_LIST_STORE);
176
- const rawMessageSecret = resolveStore(id, backends, cacheProviders.messageSecret ?? 'memory', 'messageSecret', 'caches', () => cacheProviders.messageSecret === 'memory' || !cacheProviders.messageSecret
177
- ? new message_secret_store_1.WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
178
- maxSecrets: ml.messageSecrets,
179
- logger: memoryLogger?.child({
180
- domain: 'messageSecret',
181
- sessionId: id
166
+ : noop_store_1.NOOP_RETRY_STORE)),
167
+ groupMetadata: (0, group_metadata_lock_1.withGroupMetadataLock)(resolveStore(id, backends, cacheProviders.groupMetadata ?? 'memory', 'groupMetadata', 'caches', () => cacheProviders.groupMetadata === 'memory' ||
168
+ !cacheProviders.groupMetadata
169
+ ? new group_metadata_store_1.WaGroupMetadataMemoryStore(cacheTtlsMs.groupMetadata, {
170
+ maxGroups: ml.groupMetadataGroups,
171
+ logger: memoryLogger?.child({
172
+ domain: 'groupMetadata',
173
+ sessionId: id
174
+ })
182
175
  })
183
- })
184
- : noop_store_1.NOOP_MESSAGE_SECRET_STORE);
176
+ : noop_store_1.NOOP_GROUP_METADATA_STORE)),
177
+ deviceList: (0, device_list_lock_1.withDeviceListLock)(resolveStore(id, backends, cacheProviders.deviceList ?? 'memory', 'deviceList', 'caches', () => cacheProviders.deviceList === 'memory' || !cacheProviders.deviceList
178
+ ? new device_list_store_1.WaDeviceListMemoryStore(cacheTtlsMs.deviceList, {
179
+ maxUsers: ml.deviceListUsers,
180
+ logger: memoryLogger?.child({
181
+ domain: 'deviceList',
182
+ sessionId: id
183
+ })
184
+ })
185
+ : noop_store_1.NOOP_DEVICE_LIST_STORE)),
186
+ messageSecret: (0, message_secret_lock_1.withMessageSecretLock)(resolveStore(id, backends, cacheProviders.messageSecret ?? 'memory', 'messageSecret', 'caches', () => cacheProviders.messageSecret === 'memory' ||
187
+ !cacheProviders.messageSecret
188
+ ? new message_secret_store_1.WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
189
+ maxSecrets: ml.messageSecrets,
190
+ logger: memoryLogger?.child({
191
+ domain: 'messageSecret',
192
+ sessionId: id
193
+ })
194
+ })
195
+ : noop_store_1.NOOP_MESSAGE_SECRET_STORE))
196
+ });
197
+ let caches = buildCaches();
185
198
  const authStore = (0, auth_lock_1.withAuthLock)(rawAuth);
186
199
  const signalStore = (0, signal_lock_1.withSignalLock)(rawSignal);
187
200
  const preKeyStore = (0, pre_key_lock_1.withPreKeyLock)(rawPreKey);
@@ -195,41 +208,72 @@ function createStore(options) {
195
208
  ? (0, sender_key_cache_1.withSenderKeyCache)(rawSenderKey, cacheLayer.limits?.senderKey)
196
209
  : rawSenderKey);
197
210
  const appStateStore = (0, appstate_lock_1.withAppStateLock)(rawAppState);
198
- const retryStore = (0, retry_lock_1.withRetryLock)(rawRetry);
199
- const groupMetadataStore = (0, group_metadata_lock_1.withGroupMetadataLock)(rawGroupMetadata);
200
- const deviceListStore = (0, device_list_lock_1.withDeviceListLock)(rawDeviceList);
201
211
  const messageStore = (0, message_lock_1.withMessageLock)(rawMessages);
202
- const messageSecretStore = (0, message_secret_lock_1.withMessageSecretLock)(rawMessageSecret);
203
212
  const threadStore = (0, thread_lock_1.withThreadLock)(rawThreads);
204
213
  const contactStore = (0, contact_lock_1.withContactLock)(rawContacts);
205
214
  const privacyTokenStore = (0, privacy_token_lock_1.withPrivacyTokenLock)(cacheLayer.privacyToken && usesBackend(providers.privacyToken)
206
215
  ? (0, privacy_token_cache_1.withPrivacyTokenCache)(rawPrivacyToken, cacheLayer.limits?.privacyToken)
207
216
  : rawPrivacyToken);
208
- let cachesDestroyed = false;
209
217
  let sessionDestroyed = false;
210
- const destroyCaches = async () => {
211
- if (cachesDestroyed)
212
- return;
213
- cachesDestroyed = true;
214
- await Promise.all([
215
- retryStore.clear(),
216
- groupMetadataStore.clear(),
217
- deviceListStore.clear(),
218
- messageSecretStore.clear()
218
+ let destroyPromise = null;
219
+ let cacheLifecycle = Promise.resolve();
220
+ const teardownCaches = async (target) => {
221
+ const cleared = await Promise.allSettled([
222
+ target.retry.clear(),
223
+ target.groupMetadata.clear(),
224
+ target.deviceList.clear(),
225
+ target.messageSecret.clear()
219
226
  ]);
220
- await Promise.all([
221
- destroyIfSupported(retryStore),
222
- destroyIfSupported(groupMetadataStore),
223
- destroyIfSupported(deviceListStore),
224
- destroyIfSupported(messageSecretStore)
227
+ const destroyed = await Promise.allSettled([
228
+ destroyIfSupported(target.retry),
229
+ destroyIfSupported(target.groupMetadata),
230
+ destroyIfSupported(target.deviceList),
231
+ destroyIfSupported(target.messageSecret)
225
232
  ]);
233
+ const failures = [...cleared, ...destroyed].filter((result) => result.status === 'rejected');
234
+ if (failures.length > 0) {
235
+ storeLogger?.warn('cache teardown had failures', {
236
+ sessionId: id,
237
+ droppedCount: failures.length,
238
+ totalExpected: cleared.length + destroyed.length,
239
+ sample: (0, primitives_1.toError)(failures[0].reason).message
240
+ });
241
+ }
242
+ return failures.length;
243
+ };
244
+ const destroyCaches = () => {
245
+ const run = cacheLifecycle.then(async () => {
246
+ if (sessionDestroyed)
247
+ return;
248
+ const failureCount = await teardownCaches(caches);
249
+ caches = buildCaches();
250
+ if (failureCount > 0) {
251
+ throw new Error(`cache reset finished with ${failureCount} teardown failure(s); ` +
252
+ 'fresh caches are in place but old persistent entries may remain');
253
+ }
254
+ });
255
+ cacheLifecycle = run.then(() => undefined, () => undefined);
256
+ return run;
226
257
  };
227
- const destroy = async () => {
228
- if (sessionDestroyed)
229
- return;
258
+ const destroy = () => {
259
+ if (!destroyPromise) {
260
+ const pending = destroyInternal().finally(() => pendingSessionDestroys.delete(pending));
261
+ destroyPromise = pending;
262
+ pendingSessionDestroys.add(pending);
263
+ }
264
+ return destroyPromise;
265
+ };
266
+ const destroyInternal = async () => {
230
267
  sessionDestroyed = true;
231
- await destroyCaches();
232
- await Promise.all([
268
+ if (sessions.get(id) === storeSession) {
269
+ sessions.delete(id);
270
+ }
271
+ await cacheLifecycle;
272
+ await teardownCaches(caches);
273
+ await destroyPersistentStores();
274
+ };
275
+ const destroyPersistentStores = async () => {
276
+ const results = await Promise.allSettled([
233
277
  destroyIfSupported(authStore),
234
278
  destroyIfSupported(signalStore),
235
279
  destroyIfSupported(preKeyStore),
@@ -242,6 +286,15 @@ function createStore(options) {
242
286
  destroyIfSupported(contactStore),
243
287
  destroyIfSupported(privacyTokenStore)
244
288
  ]);
289
+ const failures = results.filter((result) => result.status === 'rejected');
290
+ if (failures.length > 0) {
291
+ storeLogger?.warn('persistent store teardown had failures', {
292
+ sessionId: id,
293
+ droppedCount: failures.length,
294
+ totalExpected: results.length,
295
+ sample: (0, primitives_1.toError)(failures[0].reason).message
296
+ });
297
+ }
245
298
  };
246
299
  const storeSession = {
247
300
  auth: authStore,
@@ -251,11 +304,19 @@ function createStore(options) {
251
304
  identity: identityStore,
252
305
  senderKey: senderKeyStore,
253
306
  appState: appStateStore,
254
- retry: retryStore,
255
- groupMetadata: groupMetadataStore,
256
- deviceList: deviceListStore,
307
+ get retry() {
308
+ return caches.retry;
309
+ },
310
+ get groupMetadata() {
311
+ return caches.groupMetadata;
312
+ },
313
+ get deviceList() {
314
+ return caches.deviceList;
315
+ },
257
316
  messages: messageStore,
258
- messageSecret: messageSecretStore,
317
+ get messageSecret() {
318
+ return caches.messageSecret;
319
+ },
259
320
  threads: threadStore,
260
321
  contacts: contactStore,
261
322
  privacyToken: privacyTokenStore,
@@ -276,6 +337,7 @@ function createStore(options) {
276
337
  const list = Array.from(sessions.values());
277
338
  sessions.clear();
278
339
  await Promise.all(list.map((s) => s.destroy()));
340
+ await Promise.all(Array.from(pendingSessionDestroys));
279
341
  const uniqueBackends = new Set(Object.values(backends));
280
342
  await Promise.all(Array.from(uniqueBackends, (backend) => destroyIfSupported(backend)));
281
343
  }
@@ -33,12 +33,43 @@ export interface WaStoreSession {
33
33
  readonly threads: WaThreadStore;
34
34
  readonly contacts: WaContactStore;
35
35
  readonly privacyToken: WaPrivacyTokenStore;
36
+ /**
37
+ * Tears down the cache domains (retry/groupMetadata/deviceList/
38
+ * messageSecret) and swaps fresh, empty ones into this bundle. The
39
+ * session stays usable; the caches rebuild on demand. The swap happens
40
+ * after the old stores finish tearing down (so a persistent cache
41
+ * backend is never cleared under the fresh stores), which means cache
42
+ * operations issued while the reset is in flight may reject. References
43
+ * to the old cache stores captured before the call (e.g. by a live
44
+ * client) reject afterwards - recreate the client to pick up the fresh
45
+ * stores. Rejects when the old generation's teardown had failures - the
46
+ * fresh caches are still in place, but stale entries may remain in a
47
+ * persistent cache backend.
48
+ */
36
49
  destroyCaches(): Promise<void>;
50
+ /**
51
+ * Final teardown of every domain store in this bundle. The bundle is
52
+ * single-shot: every operation on it rejects afterwards. The sessionId
53
+ * is released as soon as destruction starts, so a concurrent
54
+ * `store.session(id)` builds a fresh bundle instead of returning this
55
+ * one. Repeat calls return the same in-flight promise. Teardown
56
+ * failures are logged (warn), never thrown.
57
+ */
37
58
  destroy(): Promise<void>;
38
59
  }
39
60
  export interface WaStore {
61
+ /**
62
+ * Returns the lock-wrapped per-domain store bundle for `sessionId`,
63
+ * building it on first use and caching it until its `destroy()` (or the
64
+ * store-wide `destroy()`) releases it.
65
+ */
40
66
  session(sessionId: string): WaStoreSession;
67
+ /** Runs {@link WaStoreSession.destroyCaches} on every live session. */
41
68
  destroyCaches(): Promise<void>;
69
+ /**
70
+ * Destroys every live session bundle and the registered backends. The
71
+ * store is single-shot: `session()` throws afterwards.
72
+ */
42
73
  destroy(): Promise<void>;
43
74
  }
44
75
  export interface WaStoreBackend {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapo-js",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "High-performance WhatsApp Web TypeScript library",
5
5
  "license": "MIT",
6
6
  "author": "vinikjkkj <contact@vinicius.email> (https://github.com/vinikjkkj)",