@mtcute/core 0.22.2 → 0.23.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 (105) hide show
  1. package/client.cjs +1 -1
  2. package/client.js +1 -1
  3. package/highlevel/base.cjs +1 -0
  4. package/highlevel/base.js +1 -0
  5. package/highlevel/client.d.cts +7 -3
  6. package/highlevel/client.d.ts +7 -3
  7. package/highlevel/methods/auth/send-code.cjs +8 -2
  8. package/highlevel/methods/auth/send-code.d.cts +4 -2
  9. package/highlevel/methods/auth/send-code.d.ts +4 -2
  10. package/highlevel/methods/auth/send-code.js +8 -2
  11. package/highlevel/methods/auth/start.cjs +6 -1
  12. package/highlevel/methods/auth/start.d.cts +1 -1
  13. package/highlevel/methods/auth/start.d.ts +1 -1
  14. package/highlevel/methods/auth/start.js +6 -1
  15. package/highlevel/methods/chats/open-chat.cjs +1 -1
  16. package/highlevel/methods/chats/open-chat.js +1 -1
  17. package/highlevel/methods/dialogs/find-dialogs.cjs +3 -1
  18. package/highlevel/methods/dialogs/find-dialogs.js +3 -1
  19. package/highlevel/methods/dialogs/get-peer-dialogs.cjs +49 -11
  20. package/highlevel/methods/dialogs/get-peer-dialogs.d.cts +1 -1
  21. package/highlevel/methods/dialogs/get-peer-dialogs.d.ts +1 -1
  22. package/highlevel/methods/dialogs/get-peer-dialogs.js +49 -11
  23. package/highlevel/methods/files/normalize-input-file.d.cts +1 -0
  24. package/highlevel/methods/files/normalize-input-file.d.ts +1 -0
  25. package/highlevel/methods/files/normalize-input-media.cjs +13 -6
  26. package/highlevel/methods/files/normalize-input-media.d.cts +1 -0
  27. package/highlevel/methods/files/normalize-input-media.d.ts +1 -0
  28. package/highlevel/methods/files/normalize-input-media.js +13 -6
  29. package/highlevel/methods/files/upload-file.cjs +6 -3
  30. package/highlevel/methods/files/upload-file.d.cts +1 -0
  31. package/highlevel/methods/files/upload-file.d.ts +1 -0
  32. package/highlevel/methods/files/upload-file.js +6 -3
  33. package/highlevel/methods/messages/send-common.d.cts +1 -0
  34. package/highlevel/methods/messages/send-common.d.ts +1 -0
  35. package/highlevel/methods/messages/send-media-group.cjs +3 -2
  36. package/highlevel/methods/messages/send-media-group.js +3 -2
  37. package/highlevel/methods/messages/send-media.cjs +3 -2
  38. package/highlevel/methods/messages/send-media.js +3 -2
  39. package/highlevel/methods/messages/send-text.cjs +1 -1
  40. package/highlevel/methods/messages/send-text.js +1 -1
  41. package/highlevel/methods/users/resolve-peer-many.cjs +8 -43
  42. package/highlevel/methods/users/resolve-peer-many.js +9 -44
  43. package/highlevel/storage/service/peers.cjs +37 -9
  44. package/highlevel/storage/service/peers.d.cts +4 -0
  45. package/highlevel/storage/service/peers.d.ts +4 -0
  46. package/highlevel/storage/service/peers.js +38 -10
  47. package/highlevel/storage/storage.cjs +3 -0
  48. package/highlevel/storage/storage.d.cts +1 -0
  49. package/highlevel/storage/storage.d.ts +1 -0
  50. package/highlevel/storage/storage.js +3 -0
  51. package/highlevel/types/conversation.cjs +3 -4
  52. package/highlevel/types/conversation.js +3 -4
  53. package/highlevel/types/messages/message-action.cjs +11 -0
  54. package/highlevel/types/messages/message-action.d.cts +15 -1
  55. package/highlevel/types/messages/message-action.d.ts +15 -1
  56. package/highlevel/types/messages/message-action.js +11 -0
  57. package/highlevel/types/peers/full-user.cjs +8 -0
  58. package/highlevel/types/peers/full-user.d.cts +4 -0
  59. package/highlevel/types/peers/full-user.d.ts +4 -0
  60. package/highlevel/types/peers/full-user.js +8 -0
  61. package/highlevel/types/peers/peers-index.cjs +10 -0
  62. package/highlevel/types/peers/peers-index.d.cts +1 -0
  63. package/highlevel/types/peers/peers-index.d.ts +1 -0
  64. package/highlevel/types/peers/peers-index.js +10 -0
  65. package/highlevel/types/premium/business-connection.cjs +3 -3
  66. package/highlevel/types/premium/business-connection.d.cts +2 -2
  67. package/highlevel/types/premium/business-connection.d.ts +2 -2
  68. package/highlevel/types/premium/business-connection.js +3 -3
  69. package/highlevel/types/premium/saved-star-gift.cjs +2 -2
  70. package/highlevel/types/premium/saved-star-gift.d.cts +2 -2
  71. package/highlevel/types/premium/saved-star-gift.d.ts +2 -2
  72. package/highlevel/types/premium/saved-star-gift.js +2 -2
  73. package/highlevel/types/premium/stars-gift-unique.cjs +11 -1
  74. package/highlevel/types/premium/stars-gift-unique.d.cts +7 -1
  75. package/highlevel/types/premium/stars-gift-unique.d.ts +7 -1
  76. package/highlevel/types/premium/stars-gift-unique.js +11 -1
  77. package/highlevel/types/premium/stars-transaction.cjs +4 -0
  78. package/highlevel/types/premium/stars-transaction.d.cts +2 -0
  79. package/highlevel/types/premium/stars-transaction.d.ts +2 -0
  80. package/highlevel/types/premium/stars-transaction.js +4 -0
  81. package/highlevel/worker/storage.cjs +4 -0
  82. package/highlevel/worker/storage.d.cts +2 -0
  83. package/highlevel/worker/storage.d.ts +2 -0
  84. package/highlevel/worker/storage.js +4 -0
  85. package/index.cjs +8 -0
  86. package/index.js +8 -0
  87. package/network/middlewares/flood-waiter.cjs +18 -2
  88. package/network/middlewares/flood-waiter.d.cts +9 -2
  89. package/network/middlewares/flood-waiter.d.ts +9 -2
  90. package/network/middlewares/flood-waiter.js +18 -2
  91. package/network/network-manager.cjs +1 -1
  92. package/network/network-manager.d.cts +1 -1
  93. package/network/network-manager.d.ts +1 -1
  94. package/network/network-manager.js +1 -1
  95. package/network/session-connection.cjs +3 -1
  96. package/network/session-connection.js +3 -1
  97. package/package.json +7 -7
  98. package/storage/memory/index.cjs +4 -0
  99. package/storage/memory/index.d.cts +4 -0
  100. package/storage/memory/index.d.ts +4 -0
  101. package/storage/memory/index.js +4 -0
  102. package/utils/binary/compat.cjs +2 -0
  103. package/utils/binary/compat.js +2 -0
  104. package/utils/early-timer.cjs +7 -2
  105. package/utils/early-timer.js +7 -2
@@ -201,14 +201,15 @@ async function _normalizeInputMedia(client, media, params = {}, uploadMedia = fa
201
201
  fileMime: sendMime,
202
202
  fileSize: media.fileSize,
203
203
  requireFileSize: media.type === "photo",
204
- requireExtension: media.type === "photo"
204
+ requireExtension: media.type === "photo",
205
+ abortSignal: params.abortSignal
205
206
  });
206
207
  inputFile = uploaded.inputFile;
207
208
  mime = uploaded.mime;
208
209
  };
209
210
  let videoCover;
210
211
  if (media.type === "video" && media.cover) {
211
- const inputMedia = await _normalizeInputMedia(client, media.cover);
212
+ const inputMedia = await _normalizeInputMedia(client, media.cover, params);
212
213
  assertTypeIs("uploadMediaIfNeeded", inputMedia, "inputMediaPhoto");
213
214
  videoCover = inputMedia.id;
214
215
  }
@@ -220,7 +221,7 @@ async function _normalizeInputMedia(client, media, params = {}, uploadMedia = fa
220
221
  peer: uploadPeer,
221
222
  media: inputMedia,
222
223
  businessConnectionId: params.businessConnectionId
223
- });
224
+ }, { abortSignal: params.abortSignal });
224
225
  if (photo) {
225
226
  assertTypeIs("normalizeInputMedia (@ messages.uploadMedia)", res, "messageMediaPhoto");
226
227
  assertTypeIs("normalizeInputMedia (@ messages.uploadMedia)", res.photo, "photo");
@@ -308,7 +309,7 @@ async function _normalizeInputMedia(client, media, params = {}, uploadMedia = fa
308
309
  );
309
310
  }
310
311
  if ("thumb" in media && media.thumb) {
311
- thumb = await _normalizeInputFile(client, media.thumb, {});
312
+ thumb = await _normalizeInputFile(client, media.thumb, { abortSignal: params.abortSignal });
312
313
  }
313
314
  const attributes = [];
314
315
  if (media.type !== "voice") {
@@ -318,14 +319,20 @@ async function _normalizeInputMedia(client, media, params = {}, uploadMedia = fa
318
319
  });
319
320
  }
320
321
  if (media.type === "video") {
321
- attributes.push({
322
+ const attr = {
322
323
  _: "documentAttributeVideo",
323
324
  duration: media.duration || 0,
324
325
  w: media.width || 0,
325
326
  h: media.height || 0,
326
327
  supportsStreaming: media.supportsStreaming,
327
328
  roundMessage: media.isRound
328
- });
329
+ };
330
+ if (media.isRound) {
331
+ if (attr.w === 0) attr.w = 1;
332
+ if (attr.h === 0) attr.h = 1;
333
+ if (attr.duration === 0) attr.duration = 1;
334
+ }
335
+ attributes.push(attr);
329
336
  if (media.isAnimated) {
330
337
  attributes.push({ _: "documentAttributeAnimated" });
331
338
  }
@@ -30,6 +30,7 @@ async function uploadFile(client, params) {
30
30
  let fileSize = -1;
31
31
  let fileName = params.fileName;
32
32
  let fileMime = params.fileMime;
33
+ const abortSignal = params.abortSignal;
33
34
  if (client.platform.normalizeFile) {
34
35
  const res = await client.platform.normalizeFile(file);
35
36
  if (res?.file) {
@@ -127,6 +128,7 @@ async function uploadFile(client, params) {
127
128
  let idx = 0;
128
129
  let readableEnded = false;
129
130
  const uploadNextPart = async () => {
131
+ if (abortSignal?.aborted) return;
130
132
  const thisIdx = idx++;
131
133
  await lock.acquire();
132
134
  if (readableEnded) {
@@ -139,6 +141,7 @@ async function uploadFile(client, params) {
139
141
  } finally {
140
142
  lock.release();
141
143
  }
144
+ if (abortSignal?.aborted) return;
142
145
  const ended = part.length < partSize;
143
146
  if (ended) {
144
147
  readableEnded = true;
@@ -171,8 +174,9 @@ async function uploadFile(client, params) {
171
174
  filePart: thisIdx,
172
175
  bytes: part
173
176
  };
174
- const result = await client.call(request, { kind: connectionKind });
177
+ const result = await client.call(request, { kind: connectionKind, abortSignal });
175
178
  if (!result) throw new Error(`Failed to upload part ${idx}`);
179
+ if (abortSignal?.aborted) return;
176
180
  pos += part.length;
177
181
  params.progressCallback?.(pos, fileSize);
178
182
  if (idx === partCount) return;
@@ -181,6 +185,7 @@ async function uploadFile(client, params) {
181
185
  let poolSize = partCount === -1 ? 1 : connectionPoolSize * requestsPerConnection;
182
186
  if (partCount !== -1 && poolSize > partCount) poolSize = partCount;
183
187
  await Promise.all(Array.from({ length: poolSize }, uploadNextPart));
188
+ abortSignal?.throwIfAborted();
184
189
  if (fileName === void 0) {
185
190
  const ext = fileType.MIME_TO_EXTENSION[fileMime];
186
191
  fileName = ext ? `${DEFAULT_FILE_NAME}.${ext}` : DEFAULT_FILE_NAME;
@@ -193,8 +198,6 @@ async function uploadFile(client, params) {
193
198
  throw new errors.MtArgumentError(`File name does not have an extension, and we cannot guess it from the mime type (${fileMime})`);
194
199
  }
195
200
  fileName = `${fileName}.${extFromMime}`;
196
- } else if (extFromMime && extFromName !== extFromMime) {
197
- throw new errors.MtArgumentError(`File name has ${extFromName} extension (${fileName}), but the mime type (${fileMime}) expects it to be ${extFromMime}`);
198
201
  }
199
202
  }
200
203
  let inputFile;
@@ -69,4 +69,5 @@ export declare function uploadFile(client: ITelegramClient, params: {
69
69
  * or throw an error if it cannot be guessed.
70
70
  */
71
71
  requireExtension?: boolean;
72
+ abortSignal?: AbortSignal;
72
73
  }): Promise<UploadedFile>;
@@ -69,4 +69,5 @@ export declare function uploadFile(client: ITelegramClient, params: {
69
69
  * or throw an error if it cannot be guessed.
70
70
  */
71
71
  requireExtension?: boolean;
72
+ abortSignal?: AbortSignal;
72
73
  }): Promise<UploadedFile>;
@@ -23,6 +23,7 @@ async function uploadFile(client, params) {
23
23
  let fileSize = -1;
24
24
  let fileName = params.fileName;
25
25
  let fileMime = params.fileMime;
26
+ const abortSignal = params.abortSignal;
26
27
  if (client.platform.normalizeFile) {
27
28
  const res = await client.platform.normalizeFile(file);
28
29
  if (res?.file) {
@@ -120,6 +121,7 @@ async function uploadFile(client, params) {
120
121
  let idx = 0;
121
122
  let readableEnded = false;
122
123
  const uploadNextPart = async () => {
124
+ if (abortSignal?.aborted) return;
123
125
  const thisIdx = idx++;
124
126
  await lock.acquire();
125
127
  if (readableEnded) {
@@ -132,6 +134,7 @@ async function uploadFile(client, params) {
132
134
  } finally {
133
135
  lock.release();
134
136
  }
137
+ if (abortSignal?.aborted) return;
135
138
  const ended = part.length < partSize;
136
139
  if (ended) {
137
140
  readableEnded = true;
@@ -164,8 +167,9 @@ async function uploadFile(client, params) {
164
167
  filePart: thisIdx,
165
168
  bytes: part
166
169
  };
167
- const result = await client.call(request, { kind: connectionKind });
170
+ const result = await client.call(request, { kind: connectionKind, abortSignal });
168
171
  if (!result) throw new Error(`Failed to upload part ${idx}`);
172
+ if (abortSignal?.aborted) return;
169
173
  pos += part.length;
170
174
  params.progressCallback?.(pos, fileSize);
171
175
  if (idx === partCount) return;
@@ -174,6 +178,7 @@ async function uploadFile(client, params) {
174
178
  let poolSize = partCount === -1 ? 1 : connectionPoolSize * requestsPerConnection;
175
179
  if (partCount !== -1 && poolSize > partCount) poolSize = partCount;
176
180
  await Promise.all(Array.from({ length: poolSize }, uploadNextPart));
181
+ abortSignal?.throwIfAborted();
177
182
  if (fileName === void 0) {
178
183
  const ext = MIME_TO_EXTENSION[fileMime];
179
184
  fileName = ext ? `${DEFAULT_FILE_NAME}.${ext}` : DEFAULT_FILE_NAME;
@@ -186,8 +191,6 @@ async function uploadFile(client, params) {
186
191
  throw new MtArgumentError(`File name does not have an extension, and we cannot guess it from the mime type (${fileMime})`);
187
192
  }
188
193
  fileName = `${fileName}.${extFromMime}`;
189
- } else if (extFromMime && extFromName !== extFromMime) {
190
- throw new MtArgumentError(`File name has ${extFromName} extension (${fileName}), but the mime type (${fileMime}) expects it to be ${extFromMime}`);
191
194
  }
192
195
  }
193
196
  let inputFile;
@@ -103,6 +103,7 @@ export interface CommonSendParams {
103
103
  * If set, the value represents the maximum number of stars to be paid
104
104
  */
105
105
  allowPaidMessages?: tl.Long;
106
+ abortSignal?: AbortSignal;
106
107
  /**
107
108
  * ID of a message effect to use when sending the message
108
109
  * (see {@link TelegramClient.getAvailableMessageEffects})
@@ -103,6 +103,7 @@ export interface CommonSendParams {
103
103
  * If set, the value represents the maximum number of stars to be paid
104
104
  */
105
105
  allowPaidMessages?: tl.Long;
106
+ abortSignal?: AbortSignal;
106
107
  /**
107
108
  * ID of a message effect to use when sending the message
108
109
  * (see {@link TelegramClient.getAvailableMessageEffects})
@@ -39,7 +39,8 @@ async function sendMediaGroup(client, chatId, medias, params) {
39
39
  // but otherwise Telegram throws MEDIA_INVALID
40
40
  // fuck my life
41
41
  uploadPeer: peer,
42
- businessConnectionId: params.businessConnectionId
42
+ businessConnectionId: params.businessConnectionId,
43
+ abortSignal: params.abortSignal
43
44
  },
44
45
  true
45
46
  );
@@ -76,7 +77,7 @@ async function sendMediaGroup(client, chatId, medias, params) {
76
77
  allowPaidFloodskip: params.allowPaidFloodskip,
77
78
  allowPaidStars: params.allowPaidMessages
78
79
  },
79
- { chainId }
80
+ { chainId, abortSignal: params.abortSignal }
80
81
  );
81
82
  utils.assertIsUpdatesGroup("sendMediaGroup", res);
82
83
  client.handleClientUpdate(res);
@@ -32,7 +32,8 @@ async function sendMediaGroup(client, chatId, medias, params) {
32
32
  // but otherwise Telegram throws MEDIA_INVALID
33
33
  // fuck my life
34
34
  uploadPeer: peer,
35
- businessConnectionId: params.businessConnectionId
35
+ businessConnectionId: params.businessConnectionId,
36
+ abortSignal: params.abortSignal
36
37
  },
37
38
  true
38
39
  );
@@ -69,7 +70,7 @@ async function sendMediaGroup(client, chatId, medias, params) {
69
70
  allowPaidFloodskip: params.allowPaidFloodskip,
70
71
  allowPaidStars: params.allowPaidMessages
71
72
  },
72
- { chainId }
73
+ { chainId, abortSignal: params.abortSignal }
73
74
  );
74
75
  assertIsUpdatesGroup("sendMediaGroup", res);
75
76
  client.handleClientUpdate(res);
@@ -28,7 +28,8 @@ async function sendMedia(client, chatId, media, params) {
28
28
  );
29
29
  const inputMedia = await normalizeInputMedia._normalizeInputMedia(client, media, {
30
30
  progressCallback: params.progressCallback,
31
- uploadPeer: peer
31
+ uploadPeer: peer,
32
+ abortSignal: params.abortSignal
32
33
  });
33
34
  const [message, entities] = await normalizeText._normalizeInputText(
34
35
  client,
@@ -61,7 +62,7 @@ async function sendMedia(client, chatId, media, params) {
61
62
  allowPaidFloodskip: params.allowPaidFloodskip,
62
63
  allowPaidStars: params.allowPaidMessages
63
64
  },
64
- { chainId }
65
+ { chainId, abortSignal: params.abortSignal }
65
66
  );
66
67
  const msg = findInUpdate._findMessageInUpdate(client, res, false, !params.shouldDispatch, false, randomId);
67
68
  return msg;
@@ -21,7 +21,8 @@ async function sendMedia(client, chatId, media, params) {
21
21
  );
22
22
  const inputMedia = await _normalizeInputMedia(client, media, {
23
23
  progressCallback: params.progressCallback,
24
- uploadPeer: peer
24
+ uploadPeer: peer,
25
+ abortSignal: params.abortSignal
25
26
  });
26
27
  const [message, entities] = await _normalizeInputText(
27
28
  client,
@@ -54,7 +55,7 @@ async function sendMedia(client, chatId, media, params) {
54
55
  allowPaidFloodskip: params.allowPaidFloodskip,
55
56
  allowPaidStars: params.allowPaidMessages
56
57
  },
57
- { chainId }
58
+ { chainId, abortSignal: params.abortSignal }
58
59
  );
59
60
  const msg = _findMessageInUpdate(client, res, false, !params.shouldDispatch, false, randomId);
60
61
  return msg;
@@ -52,7 +52,7 @@ async function sendText(client, chatId, text, params) {
52
52
  allowPaidFloodskip: params.allowPaidFloodskip,
53
53
  allowPaidStars: params.allowPaidMessages
54
54
  },
55
- { chainId }
55
+ { chainId, abortSignal: params.abortSignal }
56
56
  );
57
57
  if (res._ === "updateShortSentMessage") {
58
58
  const msg2 = {
@@ -45,7 +45,7 @@ async function sendText(client, chatId, text, params) {
45
45
  allowPaidFloodskip: params.allowPaidFloodskip,
46
46
  allowPaidStars: params.allowPaidMessages
47
47
  },
48
- { chainId }
48
+ { chainId, abortSignal: params.abortSignal }
49
49
  );
50
50
  if (res._ === "updateShortSentMessage") {
51
51
  const msg2 = {
@@ -32,53 +32,18 @@ async function resolvePeerMany(client, peerIds, normalizer) {
32
32
  }
33
33
  return ret;
34
34
  }
35
- const cv = new utils.ConditionVariable();
36
- const buffer = {};
37
- let nextIdx = 0;
38
- let nextWorkerIdx = 0;
39
- const fetchNext = async (idx = nextWorkerIdx++) => {
35
+ return utils.parallelMap(peerIds, async (it) => {
36
+ let peer;
40
37
  try {
41
- const result = await resolvePeer.resolvePeer(client, peerIds[idx]);
42
- buffer[idx] = result;
38
+ peer = await resolvePeer.resolvePeer(client, it);
43
39
  } catch (e) {
44
40
  if (e instanceof errors.MtPeerNotFoundError) {
45
- buffer[idx] = null;
46
- } else {
47
- throw e;
41
+ return null;
48
42
  }
43
+ throw e;
49
44
  }
50
- if (nextIdx === idx) {
51
- cv.notify();
52
- }
53
- if (nextWorkerIdx < peerIds.length) {
54
- await fetchNext(nextWorkerIdx++);
55
- }
56
- };
57
- let error;
58
- void Promise.all(Array.from({ length: limit }, (_, i) => fetchNext(i))).catch((e) => {
59
- client.log.debug("resolvePeerMany errored: %e", e);
60
- error = e;
61
- cv.notify();
62
- });
63
- while (nextIdx < peerIds.length) {
64
- await cv.wait();
65
- if (error) throw error;
66
- while (nextIdx in buffer) {
67
- const buf = buffer[nextIdx];
68
- delete buffer[nextIdx];
69
- nextIdx++;
70
- if (!normalizer) {
71
- ret.push(buf);
72
- continue;
73
- }
74
- if (buf !== null) {
75
- const norm = normalizer(buf);
76
- if (norm) {
77
- ret.push(norm);
78
- }
79
- }
80
- }
81
- }
82
- return ret;
45
+ if (normalizer) return normalizer(peer);
46
+ return peer;
47
+ }, { limit });
83
48
  }
84
49
  exports.resolvePeerMany = resolvePeerMany;
@@ -1,4 +1,4 @@
1
- import { ConditionVariable } from "@fuman/utils";
1
+ import { parallelMap } from "@fuman/utils";
2
2
  import { MtPeerNotFoundError } from "../../types/errors.js";
3
3
  import { resolvePeer } from "./resolve-peer.js";
4
4
  async function resolvePeerMany(client, peerIds, normalizer) {
@@ -25,54 +25,19 @@ async function resolvePeerMany(client, peerIds, normalizer) {
25
25
  }
26
26
  return ret;
27
27
  }
28
- const cv = new ConditionVariable();
29
- const buffer = {};
30
- let nextIdx = 0;
31
- let nextWorkerIdx = 0;
32
- const fetchNext = async (idx = nextWorkerIdx++) => {
28
+ return parallelMap(peerIds, async (it) => {
29
+ let peer;
33
30
  try {
34
- const result = await resolvePeer(client, peerIds[idx]);
35
- buffer[idx] = result;
31
+ peer = await resolvePeer(client, it);
36
32
  } catch (e) {
37
33
  if (e instanceof MtPeerNotFoundError) {
38
- buffer[idx] = null;
39
- } else {
40
- throw e;
34
+ return null;
41
35
  }
36
+ throw e;
42
37
  }
43
- if (nextIdx === idx) {
44
- cv.notify();
45
- }
46
- if (nextWorkerIdx < peerIds.length) {
47
- await fetchNext(nextWorkerIdx++);
48
- }
49
- };
50
- let error;
51
- void Promise.all(Array.from({ length: limit }, (_, i) => fetchNext(i))).catch((e) => {
52
- client.log.debug("resolvePeerMany errored: %e", e);
53
- error = e;
54
- cv.notify();
55
- });
56
- while (nextIdx < peerIds.length) {
57
- await cv.wait();
58
- if (error) throw error;
59
- while (nextIdx in buffer) {
60
- const buf = buffer[nextIdx];
61
- delete buffer[nextIdx];
62
- nextIdx++;
63
- if (!normalizer) {
64
- ret.push(buf);
65
- continue;
66
- }
67
- if (buf !== null) {
68
- const norm = normalizer(buf);
69
- if (norm) {
70
- ret.push(norm);
71
- }
72
- }
73
- }
74
- }
75
- return ret;
38
+ if (normalizer) return normalizer(peer);
39
+ return peer;
40
+ }, { limit });
76
41
  }
77
42
  export {
78
43
  resolvePeerMany
@@ -41,9 +41,25 @@ class PeersService extends base.BaseService {
41
41
  this._peers = _peers;
42
42
  this._refs = _refs;
43
43
  this._cache = new utils.LruMap(options.cacheSize ?? 100);
44
+ this._writePending = this._writePending.bind(this);
44
45
  }
45
46
  _cache;
46
47
  _pendingWrites = /* @__PURE__ */ new Map();
48
+ _pendingWritesTimer = null;
49
+ async close() {
50
+ if (this._pendingWritesTimer) {
51
+ utils.timers.clearTimeout(this._pendingWritesTimer);
52
+ this._pendingWritesTimer = null;
53
+ }
54
+ await this._writePending();
55
+ }
56
+ _writePendingLater() {
57
+ if (this._pendingWritesTimer) return;
58
+ this._pendingWritesTimer = utils.timers.setTimeout(
59
+ this._writePending,
60
+ this.options.updatesWriteInterval ?? 3e4
61
+ );
62
+ }
47
63
  async updatePeersFrom(obj) {
48
64
  let count = 0;
49
65
  let minCount = 0;
@@ -117,17 +133,9 @@ class PeersService extends base.BaseService {
117
133
  return;
118
134
  }
119
135
  const cached = this._cache.get(peer.id);
120
- if (cached && this.options.updatesWriteInterval !== 0) {
121
- const oldAccessHash = cached.peer.accessHash;
122
- if (oldAccessHash?.eq(accessHash)) {
123
- this._pendingWrites.set(peer.id, dto);
124
- cached.complete = peer;
125
- return;
126
- }
127
- }
128
136
  let newComplete = peer;
129
137
  if (peer.min) {
130
- const existing = this._cache.get(peer.id)?.complete ?? await this.getCompleteById(peer.id);
138
+ const existing = cached?.complete ?? await this.getCompleteById(peer.id);
131
139
  if (existing && !existing.min) {
132
140
  if (existing._ === "channel" && peer._ === "channel") {
133
141
  newComplete = {
@@ -188,6 +196,15 @@ class PeersService extends base.BaseService {
188
196
  }
189
197
  }
190
198
  }
199
+ if (cached && this.options.updatesWriteInterval !== 0) {
200
+ const oldAccessHash = cached.peer.accessHash;
201
+ if (oldAccessHash?.eq(accessHash)) {
202
+ this._pendingWrites.set(peer.id, dto);
203
+ this._writePendingLater();
204
+ cached.complete = newComplete;
205
+ return;
206
+ }
207
+ }
191
208
  await this._peers.store(dto);
192
209
  this._cache.set(peer.id, {
193
210
  peer: getInputPeer(dto),
@@ -195,6 +212,17 @@ class PeersService extends base.BaseService {
195
212
  });
196
213
  await this._refs.deleteByPeer(peer.id);
197
214
  }
215
+ async _writePending() {
216
+ try {
217
+ await utils.asyncPool(this._pendingWrites.values(), async (dto) => {
218
+ await this._peers.store(dto);
219
+ await this._refs.deleteByPeer(dto.id);
220
+ });
221
+ this._pendingWrites.clear();
222
+ } catch (err) {
223
+ this._log.warn("failed to write pending updates: %e", err);
224
+ }
225
+ }
198
226
  _returnCaching(id, dto) {
199
227
  const peer = getInputPeer(dto);
200
228
  const complete = this._deserializeTl(dto.complete);
@@ -12,9 +12,13 @@ export declare class PeersService extends BaseService {
12
12
  private _refs;
13
13
  private _cache;
14
14
  private _pendingWrites;
15
+ private _pendingWritesTimer;
15
16
  constructor(options: PeersServiceOptions, _peers: IPeersRepository, _refs: RefMessagesService, common: ServiceOptions);
17
+ close(): Promise<void>;
18
+ private _writePendingLater;
16
19
  updatePeersFrom(obj: tl.TlObject | tl.TlObject[]): Promise<boolean>;
17
20
  store(peer: tl.TypeUser | tl.TypeChat): Promise<void>;
21
+ private _writePending;
18
22
  private _returnCaching;
19
23
  getById(id: number, allowRefs?: boolean): Promise<tl.TypeInputPeer | null>;
20
24
  getMinAccessHash(id: number): Promise<tl.Long | null>;
@@ -12,9 +12,13 @@ export declare class PeersService extends BaseService {
12
12
  private _refs;
13
13
  private _cache;
14
14
  private _pendingWrites;
15
+ private _pendingWritesTimer;
15
16
  constructor(options: PeersServiceOptions, _peers: IPeersRepository, _refs: RefMessagesService, common: ServiceOptions);
17
+ close(): Promise<void>;
18
+ private _writePendingLater;
16
19
  updatePeersFrom(obj: tl.TlObject | tl.TlObject[]): Promise<boolean>;
17
20
  store(peer: tl.TypeUser | tl.TypeChat): Promise<void>;
21
+ private _writePending;
18
22
  private _returnCaching;
19
23
  getById(id: number, allowRefs?: boolean): Promise<tl.TypeInputPeer | null>;
20
24
  getMinAccessHash(id: number): Promise<tl.Long | null>;
@@ -1,4 +1,4 @@
1
- import { LruMap } from "@fuman/utils";
1
+ import { LruMap, timers, asyncPool } from "@fuman/utils";
2
2
  import Long from "long";
3
3
  import { BaseService } from "../../../storage/service/base.js";
4
4
  import { longToFastString, longFromFastString } from "../../../utils/long-utils.js";
@@ -34,9 +34,25 @@ class PeersService extends BaseService {
34
34
  this._peers = _peers;
35
35
  this._refs = _refs;
36
36
  this._cache = new LruMap(options.cacheSize ?? 100);
37
+ this._writePending = this._writePending.bind(this);
37
38
  }
38
39
  _cache;
39
40
  _pendingWrites = /* @__PURE__ */ new Map();
41
+ _pendingWritesTimer = null;
42
+ async close() {
43
+ if (this._pendingWritesTimer) {
44
+ timers.clearTimeout(this._pendingWritesTimer);
45
+ this._pendingWritesTimer = null;
46
+ }
47
+ await this._writePending();
48
+ }
49
+ _writePendingLater() {
50
+ if (this._pendingWritesTimer) return;
51
+ this._pendingWritesTimer = timers.setTimeout(
52
+ this._writePending,
53
+ this.options.updatesWriteInterval ?? 3e4
54
+ );
55
+ }
40
56
  async updatePeersFrom(obj) {
41
57
  let count = 0;
42
58
  let minCount = 0;
@@ -110,17 +126,9 @@ class PeersService extends BaseService {
110
126
  return;
111
127
  }
112
128
  const cached = this._cache.get(peer.id);
113
- if (cached && this.options.updatesWriteInterval !== 0) {
114
- const oldAccessHash = cached.peer.accessHash;
115
- if (oldAccessHash?.eq(accessHash)) {
116
- this._pendingWrites.set(peer.id, dto);
117
- cached.complete = peer;
118
- return;
119
- }
120
- }
121
129
  let newComplete = peer;
122
130
  if (peer.min) {
123
- const existing = this._cache.get(peer.id)?.complete ?? await this.getCompleteById(peer.id);
131
+ const existing = cached?.complete ?? await this.getCompleteById(peer.id);
124
132
  if (existing && !existing.min) {
125
133
  if (existing._ === "channel" && peer._ === "channel") {
126
134
  newComplete = {
@@ -181,6 +189,15 @@ class PeersService extends BaseService {
181
189
  }
182
190
  }
183
191
  }
192
+ if (cached && this.options.updatesWriteInterval !== 0) {
193
+ const oldAccessHash = cached.peer.accessHash;
194
+ if (oldAccessHash?.eq(accessHash)) {
195
+ this._pendingWrites.set(peer.id, dto);
196
+ this._writePendingLater();
197
+ cached.complete = newComplete;
198
+ return;
199
+ }
200
+ }
184
201
  await this._peers.store(dto);
185
202
  this._cache.set(peer.id, {
186
203
  peer: getInputPeer(dto),
@@ -188,6 +205,17 @@ class PeersService extends BaseService {
188
205
  });
189
206
  await this._refs.deleteByPeer(peer.id);
190
207
  }
208
+ async _writePending() {
209
+ try {
210
+ await asyncPool(this._pendingWrites.values(), async (dto) => {
211
+ await this._peers.store(dto);
212
+ await this._refs.deleteByPeer(dto.id);
213
+ });
214
+ this._pendingWrites.clear();
215
+ } catch (err) {
216
+ this._log.warn("failed to write pending updates: %e", err);
217
+ }
218
+ }
191
219
  _returnCaching(id, dto) {
192
220
  const peer = getInputPeer(dto);
193
221
  const complete = this._deserializeTl(dto.complete);
@@ -39,6 +39,9 @@ class TelegramStorageManager {
39
39
  self;
40
40
  refMsgs;
41
41
  peers;
42
+ async close() {
43
+ await this.peers.close();
44
+ }
42
45
  async clear(withAuthKeys = false) {
43
46
  await this.provider.peers.deleteAll();
44
47
  await this.provider.refMessages.deleteAll();
@@ -22,6 +22,7 @@ export declare class TelegramStorageManager {
22
22
  readonly refMsgs: RefMessagesService;
23
23
  readonly peers: PublicPart<PeersService>;
24
24
  constructor(mt: StorageManager, options: TelegramStorageManagerOptions & TelegramStorageManagerExtraOptions);
25
+ close(): Promise<void>;
25
26
  clear(withAuthKeys?: boolean): Promise<void>;
26
27
  }
27
28
  export {};
@@ -22,6 +22,7 @@ export declare class TelegramStorageManager {
22
22
  readonly refMsgs: RefMessagesService;
23
23
  readonly peers: PublicPart<PeersService>;
24
24
  constructor(mt: StorageManager, options: TelegramStorageManagerOptions & TelegramStorageManagerExtraOptions);
25
+ close(): Promise<void>;
25
26
  clear(withAuthKeys?: boolean): Promise<void>;
26
27
  }
27
28
  export {};
@@ -32,6 +32,9 @@ class TelegramStorageManager {
32
32
  self;
33
33
  refMsgs;
34
34
  peers;
35
+ async close() {
36
+ await this.peers.close();
37
+ }
35
38
  async clear(withAuthKeys = false) {
36
39
  await this.provider.peers.deleteAll();
37
40
  await this.provider.refMessages.deleteAll();