gramobase 1.0.11 → 1.0.13
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/README.md +74 -28
- package/dist/{BotWorkerPool-9ndHQt2g.d.cts → BotWorkerPool-h_8a20dt.d.cts} +8 -3
- package/dist/{BotWorkerPool-9ndHQt2g.d.ts → BotWorkerPool-h_8a20dt.d.ts} +8 -3
- package/dist/{GramoBaseAuth-00fg0u_b.d.ts → GramoBaseAuth-DfRKq2yW.d.ts} +52 -15
- package/dist/{GramoBaseAuth-CHNn2_e5.d.cts → GramoBaseAuth-ObeOxqKj.d.cts} +52 -15
- package/dist/auth/index.d.cts +2 -2
- package/dist/auth/index.d.ts +2 -2
- package/dist/index.cjs +118 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -37
- package/dist/index.d.ts +6 -37
- package/dist/index.js +118 -26
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.cts +1 -2
- package/dist/migrations/index.d.ts +1 -2
- package/dist/studio/server.cjs +133 -26
- package/dist/studio/server.cjs.map +1 -1
- package/dist/studio/server.js +133 -26
- package/dist/studio/server.js.map +1 -1
- package/package.json +23 -23
package/dist/index.cjs
CHANGED
|
@@ -299,9 +299,10 @@ var INDEX_TAG = "__GRAMOBASE_INDEX__";
|
|
|
299
299
|
var DOC_TAG = "__GRAMOBASE_DOC__";
|
|
300
300
|
var MAX_MSG_BYTES = 4e3;
|
|
301
301
|
var TelegramStorage = class {
|
|
302
|
-
constructor(pool, defaultChannelId, encryptionKey, debug = false) {
|
|
302
|
+
constructor(pool, defaultChannelId, registry, encryptionKey, debug = false) {
|
|
303
303
|
this.pool = pool;
|
|
304
304
|
this.defaultChannelId = defaultChannelId;
|
|
305
|
+
this.registry = registry;
|
|
305
306
|
this.debug = debug;
|
|
306
307
|
if (encryptionKey) {
|
|
307
308
|
this.encryptionKey = crypto.createHash("sha256").update(encryptionKey).digest();
|
|
@@ -309,20 +310,52 @@ var TelegramStorage = class {
|
|
|
309
310
|
}
|
|
310
311
|
pool;
|
|
311
312
|
defaultChannelId;
|
|
313
|
+
registry;
|
|
312
314
|
debug;
|
|
313
315
|
encryptionKey = null;
|
|
314
316
|
// collection → pinned index message ID
|
|
315
317
|
indexMsgIds = /* @__PURE__ */ new Map();
|
|
316
318
|
// ─── Index management ─────────────────────────────────────────────────────
|
|
319
|
+
async readRawMessageText(msgId, channel) {
|
|
320
|
+
try {
|
|
321
|
+
const msg = await this.pool.execute(
|
|
322
|
+
(bot) => bot.forwardMessage(channel, channel, msgId)
|
|
323
|
+
);
|
|
324
|
+
if (!msg?.text) return null;
|
|
325
|
+
let text = msg.text;
|
|
326
|
+
if (this.encryptionKey && text.startsWith("ENC:")) {
|
|
327
|
+
text = this.decrypt(text);
|
|
328
|
+
}
|
|
329
|
+
return text;
|
|
330
|
+
} catch {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
317
334
|
async loadIndex(collection, channelId) {
|
|
318
335
|
const channel = channelId ?? this.defaultChannelId;
|
|
336
|
+
try {
|
|
337
|
+
const msgId = await this.registry.getCollectionIndexMsgId(collection);
|
|
338
|
+
if (msgId) {
|
|
339
|
+
const text = await this.readRawMessageText(msgId, channel);
|
|
340
|
+
if (text && text.startsWith(INDEX_TAG)) {
|
|
341
|
+
const json = text.replace(INDEX_TAG + "\n", "");
|
|
342
|
+
const parsed = JSON.parse(json);
|
|
343
|
+
this.indexMsgIds.set(collection, msgId);
|
|
344
|
+
return parsed;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
319
349
|
try {
|
|
320
350
|
const chat = await this.pool.execute((bot) => bot.getChat(channel));
|
|
321
351
|
if (chat.pinned_message?.text?.startsWith(INDEX_TAG)) {
|
|
322
352
|
const json = chat.pinned_message.text.replace(INDEX_TAG + "\n", "");
|
|
323
353
|
const parsed = JSON.parse(json);
|
|
324
|
-
|
|
325
|
-
|
|
354
|
+
if (parsed.collection === collection) {
|
|
355
|
+
this.indexMsgIds.set(collection, chat.pinned_message.message_id);
|
|
356
|
+
await this.registry.setCollectionIndexMsgId(collection, chat.pinned_message.message_id);
|
|
357
|
+
return parsed;
|
|
358
|
+
}
|
|
326
359
|
}
|
|
327
360
|
} catch {
|
|
328
361
|
}
|
|
@@ -337,7 +370,7 @@ var TelegramStorage = class {
|
|
|
337
370
|
const channel = channelId ?? this.defaultChannelId;
|
|
338
371
|
const text = `${INDEX_TAG}
|
|
339
372
|
${JSON.stringify(index)}`;
|
|
340
|
-
const existingMsgId = this.indexMsgIds.get(index.collection);
|
|
373
|
+
const existingMsgId = this.indexMsgIds.get(index.collection) || await this.registry.getCollectionIndexMsgId(index.collection);
|
|
341
374
|
if (existingMsgId) {
|
|
342
375
|
try {
|
|
343
376
|
await this.pool.execute(
|
|
@@ -353,10 +386,9 @@ ${JSON.stringify(index)}`;
|
|
|
353
386
|
const msg = await this.pool.execute(
|
|
354
387
|
(bot) => bot.sendMessage(channel, text, { disable_notification: true })
|
|
355
388
|
);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
);
|
|
389
|
+
const newMsgId = msg.message_id;
|
|
390
|
+
this.indexMsgIds.set(index.collection, newMsgId);
|
|
391
|
+
await this.registry.setCollectionIndexMsgId(index.collection, newMsgId);
|
|
360
392
|
}
|
|
361
393
|
// ─── Document CRUD ────────────────────────────────────────────────────────
|
|
362
394
|
async writeDocument(doc, channelId) {
|
|
@@ -376,10 +408,9 @@ ${JSON.stringify(index)}`;
|
|
|
376
408
|
async readDocument(msgId, channelId) {
|
|
377
409
|
const channel = channelId ?? this.defaultChannelId;
|
|
378
410
|
try {
|
|
379
|
-
const
|
|
380
|
-
(bot) => bot.
|
|
411
|
+
const msg = await this.pool.execute(
|
|
412
|
+
(bot) => bot.forwardMessage(channel, channel, msgId)
|
|
381
413
|
);
|
|
382
|
-
const msg = Array.isArray(msgs) ? msgs[0] : msgs;
|
|
383
414
|
if (!msg?.text) return null;
|
|
384
415
|
let text = msg.text;
|
|
385
416
|
if (this.encryptionKey && text.startsWith("ENC:")) {
|
|
@@ -390,6 +421,7 @@ ${JSON.stringify(index)}`;
|
|
|
390
421
|
}
|
|
391
422
|
const parsed = JSON.parse(text);
|
|
392
423
|
delete parsed[DOC_TAG];
|
|
424
|
+
parsed._msgId = msgId;
|
|
393
425
|
return parsed;
|
|
394
426
|
} catch {
|
|
395
427
|
return null;
|
|
@@ -428,10 +460,9 @@ ${JSON.stringify(index)}`;
|
|
|
428
460
|
const msgIds = JSON.parse(headerText.replace("CHUNK:", ""));
|
|
429
461
|
const parts = [];
|
|
430
462
|
for (const id of msgIds) {
|
|
431
|
-
const
|
|
432
|
-
(bot) => bot.
|
|
463
|
+
const msg = await this.pool.execute(
|
|
464
|
+
(bot) => bot.forwardMessage(channel, channel, id)
|
|
433
465
|
);
|
|
434
|
-
const msg = Array.isArray(msgs) ? msgs[0] : msgs;
|
|
435
466
|
if (msg?.text) parts.push(msg.text);
|
|
436
467
|
}
|
|
437
468
|
return parts.join("");
|
|
@@ -632,7 +663,8 @@ var Registry = class {
|
|
|
632
663
|
this.state = {
|
|
633
664
|
activeLease: null,
|
|
634
665
|
instanceId: this.instanceId,
|
|
635
|
-
registryMsgId: null
|
|
666
|
+
registryMsgId: null,
|
|
667
|
+
indexes: {}
|
|
636
668
|
};
|
|
637
669
|
}
|
|
638
670
|
pool;
|
|
@@ -640,14 +672,33 @@ var Registry = class {
|
|
|
640
672
|
debug;
|
|
641
673
|
state;
|
|
642
674
|
instanceId;
|
|
643
|
-
async acquireWriteLease() {
|
|
675
|
+
async acquireWriteLease(options = { wait: true }) {
|
|
644
676
|
const existing = await this.readRegistryMessage();
|
|
645
677
|
if (existing?.activeLease) {
|
|
646
678
|
const lease2 = existing.activeLease;
|
|
647
679
|
if (lease2.instanceId !== this.instanceId && Date.now() < lease2.expiresAt) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
680
|
+
if (!options.wait) {
|
|
681
|
+
throw new Error(
|
|
682
|
+
`[gramobase Registry] Another instance (${lease2.instanceId}) holds the write lease until ${new Date(
|
|
683
|
+
lease2.expiresAt
|
|
684
|
+
).toISOString()}. Use Registry.forceRelease() to break a stale lease.`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
const waitMs = lease2.expiresAt - Date.now() + 250;
|
|
688
|
+
if (this.debug) {
|
|
689
|
+
console.log(
|
|
690
|
+
`[Registry] Lease held by ${lease2.instanceId}, waiting ${waitMs}ms for it to expire...`
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
694
|
+
const recheckState = await this.readRegistryMessage();
|
|
695
|
+
if (recheckState?.activeLease && recheckState.activeLease.instanceId !== this.instanceId && Date.now() < recheckState.activeLease.expiresAt) {
|
|
696
|
+
throw new Error(
|
|
697
|
+
`[gramobase Registry] Another instance (${recheckState.activeLease.instanceId}) holds the write lease until ${new Date(
|
|
698
|
+
recheckState.activeLease.expiresAt
|
|
699
|
+
).toISOString()}. Use Registry.forceRelease() to break a stale lease.`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
651
702
|
}
|
|
652
703
|
}
|
|
653
704
|
const lease = {
|
|
@@ -699,15 +750,26 @@ var Registry = class {
|
|
|
699
750
|
if (chat.pinned_message?.text?.startsWith(REGISTRY_TAG)) {
|
|
700
751
|
this.state.registryMsgId = chat.pinned_message.message_id;
|
|
701
752
|
const json = chat.pinned_message.text.replace(REGISTRY_TAG + "\n", "");
|
|
702
|
-
|
|
753
|
+
const parsed = JSON.parse(json);
|
|
754
|
+
this.state.indexes = parsed.indexes || {};
|
|
755
|
+
return parsed;
|
|
703
756
|
}
|
|
704
757
|
} catch {
|
|
705
758
|
}
|
|
706
759
|
return null;
|
|
707
760
|
}
|
|
708
761
|
async writeRegistryMessage(data) {
|
|
762
|
+
let leasePayload = null;
|
|
763
|
+
if (data.activeLease) {
|
|
764
|
+
const { heartbeatInterval, ...rest } = data.activeLease;
|
|
765
|
+
leasePayload = rest;
|
|
766
|
+
}
|
|
767
|
+
const payload = {
|
|
768
|
+
activeLease: leasePayload,
|
|
769
|
+
indexes: data.indexes || this.state.indexes || {}
|
|
770
|
+
};
|
|
709
771
|
const text = `${REGISTRY_TAG}
|
|
710
|
-
${JSON.stringify(
|
|
772
|
+
${JSON.stringify(payload, null, 0)}`;
|
|
711
773
|
if (this.state.registryMsgId) {
|
|
712
774
|
try {
|
|
713
775
|
await this.pool.execute(
|
|
@@ -730,6 +792,22 @@ ${JSON.stringify(data, null, 0)}`;
|
|
|
730
792
|
})
|
|
731
793
|
);
|
|
732
794
|
}
|
|
795
|
+
async getCollectionIndexMsgId(collection) {
|
|
796
|
+
if (!this.state.registryMsgId) {
|
|
797
|
+
await this.readRegistryMessage();
|
|
798
|
+
}
|
|
799
|
+
return this.state.indexes[collection] || null;
|
|
800
|
+
}
|
|
801
|
+
async setCollectionIndexMsgId(collection, msgId) {
|
|
802
|
+
if (!this.state.registryMsgId) {
|
|
803
|
+
await this.readRegistryMessage();
|
|
804
|
+
}
|
|
805
|
+
this.state.indexes[collection] = msgId;
|
|
806
|
+
await this.writeRegistryMessage({
|
|
807
|
+
activeLease: this.state.activeLease,
|
|
808
|
+
indexes: this.state.indexes
|
|
809
|
+
});
|
|
810
|
+
}
|
|
733
811
|
getInstanceId() {
|
|
734
812
|
return this.instanceId;
|
|
735
813
|
}
|
|
@@ -1485,9 +1563,15 @@ var GramoBase = class {
|
|
|
1485
1563
|
const tokens = Array.isArray(config.botToken) ? config.botToken : [config.botToken];
|
|
1486
1564
|
this.pool = new BotWorkerPool(tokens, config.concurrency ?? 25, config.debug ?? false);
|
|
1487
1565
|
this.cache = new HotCache(config.cacheMaxBytes, config.cacheTtlMs);
|
|
1566
|
+
this.registry = new Registry(
|
|
1567
|
+
this.pool,
|
|
1568
|
+
config.indexChannelId ?? config.channelId,
|
|
1569
|
+
config.debug ?? false
|
|
1570
|
+
);
|
|
1488
1571
|
this.storage = new TelegramStorage(
|
|
1489
1572
|
this.pool,
|
|
1490
1573
|
config.channelId,
|
|
1574
|
+
this.registry,
|
|
1491
1575
|
config.encryptionKey,
|
|
1492
1576
|
config.debug ?? false
|
|
1493
1577
|
);
|
|
@@ -1496,11 +1580,6 @@ var GramoBase = class {
|
|
|
1496
1580
|
config.walChannelId ?? config.channelId,
|
|
1497
1581
|
config.debug ?? false
|
|
1498
1582
|
);
|
|
1499
|
-
this.registry = new Registry(
|
|
1500
|
-
this.pool,
|
|
1501
|
-
config.indexChannelId ?? config.channelId,
|
|
1502
|
-
config.debug ?? false
|
|
1503
|
-
);
|
|
1504
1583
|
this.realtime = new RealtimeManager(
|
|
1505
1584
|
this.pool,
|
|
1506
1585
|
config.webhookUrl,
|
|
@@ -1631,7 +1710,20 @@ var GramoBase = class {
|
|
|
1631
1710
|
}
|
|
1632
1711
|
}
|
|
1633
1712
|
};
|
|
1713
|
+
var globalForGramo = globalThis;
|
|
1634
1714
|
function createClient(config) {
|
|
1715
|
+
if (config.global) {
|
|
1716
|
+
if (!globalForGramo.__gramobase_clients__) {
|
|
1717
|
+
globalForGramo.__gramobase_clients__ = /* @__PURE__ */ new Map();
|
|
1718
|
+
}
|
|
1719
|
+
const cacheKey = Array.isArray(config.botToken) ? `${config.channelId}:${config.botToken.join(",")}` : `${config.channelId}:${config.botToken}`;
|
|
1720
|
+
if (globalForGramo.__gramobase_clients__.has(cacheKey)) {
|
|
1721
|
+
return globalForGramo.__gramobase_clients__.get(cacheKey);
|
|
1722
|
+
}
|
|
1723
|
+
const client = new GramoBase(config);
|
|
1724
|
+
globalForGramo.__gramobase_clients__.set(cacheKey, client);
|
|
1725
|
+
return client;
|
|
1726
|
+
}
|
|
1635
1727
|
return new GramoBase(config);
|
|
1636
1728
|
}
|
|
1637
1729
|
|