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/studio/server.cjs
CHANGED
|
@@ -1244,9 +1244,10 @@ var INDEX_TAG = "__GRAMOBASE_INDEX__";
|
|
|
1244
1244
|
var DOC_TAG = "__GRAMOBASE_DOC__";
|
|
1245
1245
|
var MAX_MSG_BYTES = 4e3;
|
|
1246
1246
|
var TelegramStorage = class {
|
|
1247
|
-
constructor(pool, defaultChannelId, encryptionKey, debug = false) {
|
|
1247
|
+
constructor(pool, defaultChannelId, registry, encryptionKey, debug = false) {
|
|
1248
1248
|
this.pool = pool;
|
|
1249
1249
|
this.defaultChannelId = defaultChannelId;
|
|
1250
|
+
this.registry = registry;
|
|
1250
1251
|
this.debug = debug;
|
|
1251
1252
|
if (encryptionKey) {
|
|
1252
1253
|
this.encryptionKey = crypto.createHash("sha256").update(encryptionKey).digest();
|
|
@@ -1254,20 +1255,52 @@ var TelegramStorage = class {
|
|
|
1254
1255
|
}
|
|
1255
1256
|
pool;
|
|
1256
1257
|
defaultChannelId;
|
|
1258
|
+
registry;
|
|
1257
1259
|
debug;
|
|
1258
1260
|
encryptionKey = null;
|
|
1259
1261
|
// collection → pinned index message ID
|
|
1260
1262
|
indexMsgIds = /* @__PURE__ */ new Map();
|
|
1261
1263
|
// ─── Index management ─────────────────────────────────────────────────────
|
|
1264
|
+
async readRawMessageText(msgId, channel) {
|
|
1265
|
+
try {
|
|
1266
|
+
const msg = await this.pool.execute(
|
|
1267
|
+
(bot) => bot.forwardMessage(channel, channel, msgId)
|
|
1268
|
+
);
|
|
1269
|
+
if (!msg?.text) return null;
|
|
1270
|
+
let text = msg.text;
|
|
1271
|
+
if (this.encryptionKey && text.startsWith("ENC:")) {
|
|
1272
|
+
text = this.decrypt(text);
|
|
1273
|
+
}
|
|
1274
|
+
return text;
|
|
1275
|
+
} catch {
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1262
1279
|
async loadIndex(collection, channelId) {
|
|
1263
1280
|
const channel = channelId ?? this.defaultChannelId;
|
|
1281
|
+
try {
|
|
1282
|
+
const msgId = await this.registry.getCollectionIndexMsgId(collection);
|
|
1283
|
+
if (msgId) {
|
|
1284
|
+
const text = await this.readRawMessageText(msgId, channel);
|
|
1285
|
+
if (text && text.startsWith(INDEX_TAG)) {
|
|
1286
|
+
const json = text.replace(INDEX_TAG + "\n", "");
|
|
1287
|
+
const parsed = JSON.parse(json);
|
|
1288
|
+
this.indexMsgIds.set(collection, msgId);
|
|
1289
|
+
return parsed;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
} catch {
|
|
1293
|
+
}
|
|
1264
1294
|
try {
|
|
1265
1295
|
const chat = await this.pool.execute((bot) => bot.getChat(channel));
|
|
1266
1296
|
if (chat.pinned_message?.text?.startsWith(INDEX_TAG)) {
|
|
1267
1297
|
const json = chat.pinned_message.text.replace(INDEX_TAG + "\n", "");
|
|
1268
1298
|
const parsed = JSON.parse(json);
|
|
1269
|
-
|
|
1270
|
-
|
|
1299
|
+
if (parsed.collection === collection) {
|
|
1300
|
+
this.indexMsgIds.set(collection, chat.pinned_message.message_id);
|
|
1301
|
+
await this.registry.setCollectionIndexMsgId(collection, chat.pinned_message.message_id);
|
|
1302
|
+
return parsed;
|
|
1303
|
+
}
|
|
1271
1304
|
}
|
|
1272
1305
|
} catch {
|
|
1273
1306
|
}
|
|
@@ -1282,7 +1315,7 @@ var TelegramStorage = class {
|
|
|
1282
1315
|
const channel = channelId ?? this.defaultChannelId;
|
|
1283
1316
|
const text = `${INDEX_TAG}
|
|
1284
1317
|
${JSON.stringify(index)}`;
|
|
1285
|
-
const existingMsgId = this.indexMsgIds.get(index.collection);
|
|
1318
|
+
const existingMsgId = this.indexMsgIds.get(index.collection) || await this.registry.getCollectionIndexMsgId(index.collection);
|
|
1286
1319
|
if (existingMsgId) {
|
|
1287
1320
|
try {
|
|
1288
1321
|
await this.pool.execute(
|
|
@@ -1298,10 +1331,9 @@ ${JSON.stringify(index)}`;
|
|
|
1298
1331
|
const msg = await this.pool.execute(
|
|
1299
1332
|
(bot) => bot.sendMessage(channel, text, { disable_notification: true })
|
|
1300
1333
|
);
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
);
|
|
1334
|
+
const newMsgId = msg.message_id;
|
|
1335
|
+
this.indexMsgIds.set(index.collection, newMsgId);
|
|
1336
|
+
await this.registry.setCollectionIndexMsgId(index.collection, newMsgId);
|
|
1305
1337
|
}
|
|
1306
1338
|
// ─── Document CRUD ────────────────────────────────────────────────────────
|
|
1307
1339
|
async writeDocument(doc, channelId) {
|
|
@@ -1321,10 +1353,9 @@ ${JSON.stringify(index)}`;
|
|
|
1321
1353
|
async readDocument(msgId, channelId) {
|
|
1322
1354
|
const channel = channelId ?? this.defaultChannelId;
|
|
1323
1355
|
try {
|
|
1324
|
-
const
|
|
1325
|
-
(bot) => bot.
|
|
1356
|
+
const msg = await this.pool.execute(
|
|
1357
|
+
(bot) => bot.forwardMessage(channel, channel, msgId)
|
|
1326
1358
|
);
|
|
1327
|
-
const msg = Array.isArray(msgs) ? msgs[0] : msgs;
|
|
1328
1359
|
if (!msg?.text) return null;
|
|
1329
1360
|
let text = msg.text;
|
|
1330
1361
|
if (this.encryptionKey && text.startsWith("ENC:")) {
|
|
@@ -1335,6 +1366,7 @@ ${JSON.stringify(index)}`;
|
|
|
1335
1366
|
}
|
|
1336
1367
|
const parsed = JSON.parse(text);
|
|
1337
1368
|
delete parsed[DOC_TAG];
|
|
1369
|
+
parsed._msgId = msgId;
|
|
1338
1370
|
return parsed;
|
|
1339
1371
|
} catch {
|
|
1340
1372
|
return null;
|
|
@@ -1373,10 +1405,9 @@ ${JSON.stringify(index)}`;
|
|
|
1373
1405
|
const msgIds = JSON.parse(headerText.replace("CHUNK:", ""));
|
|
1374
1406
|
const parts = [];
|
|
1375
1407
|
for (const id of msgIds) {
|
|
1376
|
-
const
|
|
1377
|
-
(bot) => bot.
|
|
1408
|
+
const msg = await this.pool.execute(
|
|
1409
|
+
(bot) => bot.forwardMessage(channel, channel, id)
|
|
1378
1410
|
);
|
|
1379
|
-
const msg = Array.isArray(msgs) ? msgs[0] : msgs;
|
|
1380
1411
|
if (msg?.text) parts.push(msg.text);
|
|
1381
1412
|
}
|
|
1382
1413
|
return parts.join("");
|
|
@@ -1577,7 +1608,8 @@ var Registry = class {
|
|
|
1577
1608
|
this.state = {
|
|
1578
1609
|
activeLease: null,
|
|
1579
1610
|
instanceId: this.instanceId,
|
|
1580
|
-
registryMsgId: null
|
|
1611
|
+
registryMsgId: null,
|
|
1612
|
+
indexes: {}
|
|
1581
1613
|
};
|
|
1582
1614
|
}
|
|
1583
1615
|
pool;
|
|
@@ -1585,14 +1617,33 @@ var Registry = class {
|
|
|
1585
1617
|
debug;
|
|
1586
1618
|
state;
|
|
1587
1619
|
instanceId;
|
|
1588
|
-
async acquireWriteLease() {
|
|
1620
|
+
async acquireWriteLease(options = { wait: true }) {
|
|
1589
1621
|
const existing = await this.readRegistryMessage();
|
|
1590
1622
|
if (existing?.activeLease) {
|
|
1591
1623
|
const lease2 = existing.activeLease;
|
|
1592
1624
|
if (lease2.instanceId !== this.instanceId && Date.now() < lease2.expiresAt) {
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1625
|
+
if (!options.wait) {
|
|
1626
|
+
throw new Error(
|
|
1627
|
+
`[gramobase Registry] Another instance (${lease2.instanceId}) holds the write lease until ${new Date(
|
|
1628
|
+
lease2.expiresAt
|
|
1629
|
+
).toISOString()}. Use Registry.forceRelease() to break a stale lease.`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
const waitMs = lease2.expiresAt - Date.now() + 250;
|
|
1633
|
+
if (this.debug) {
|
|
1634
|
+
console.log(
|
|
1635
|
+
`[Registry] Lease held by ${lease2.instanceId}, waiting ${waitMs}ms for it to expire...`
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
1639
|
+
const recheckState = await this.readRegistryMessage();
|
|
1640
|
+
if (recheckState?.activeLease && recheckState.activeLease.instanceId !== this.instanceId && Date.now() < recheckState.activeLease.expiresAt) {
|
|
1641
|
+
throw new Error(
|
|
1642
|
+
`[gramobase Registry] Another instance (${recheckState.activeLease.instanceId}) holds the write lease until ${new Date(
|
|
1643
|
+
recheckState.activeLease.expiresAt
|
|
1644
|
+
).toISOString()}. Use Registry.forceRelease() to break a stale lease.`
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1596
1647
|
}
|
|
1597
1648
|
}
|
|
1598
1649
|
const lease = {
|
|
@@ -1644,15 +1695,26 @@ var Registry = class {
|
|
|
1644
1695
|
if (chat.pinned_message?.text?.startsWith(REGISTRY_TAG)) {
|
|
1645
1696
|
this.state.registryMsgId = chat.pinned_message.message_id;
|
|
1646
1697
|
const json = chat.pinned_message.text.replace(REGISTRY_TAG + "\n", "");
|
|
1647
|
-
|
|
1698
|
+
const parsed = JSON.parse(json);
|
|
1699
|
+
this.state.indexes = parsed.indexes || {};
|
|
1700
|
+
return parsed;
|
|
1648
1701
|
}
|
|
1649
1702
|
} catch {
|
|
1650
1703
|
}
|
|
1651
1704
|
return null;
|
|
1652
1705
|
}
|
|
1653
1706
|
async writeRegistryMessage(data) {
|
|
1707
|
+
let leasePayload = null;
|
|
1708
|
+
if (data.activeLease) {
|
|
1709
|
+
const { heartbeatInterval, ...rest } = data.activeLease;
|
|
1710
|
+
leasePayload = rest;
|
|
1711
|
+
}
|
|
1712
|
+
const payload = {
|
|
1713
|
+
activeLease: leasePayload,
|
|
1714
|
+
indexes: data.indexes || this.state.indexes || {}
|
|
1715
|
+
};
|
|
1654
1716
|
const text = `${REGISTRY_TAG}
|
|
1655
|
-
${JSON.stringify(
|
|
1717
|
+
${JSON.stringify(payload, null, 0)}`;
|
|
1656
1718
|
if (this.state.registryMsgId) {
|
|
1657
1719
|
try {
|
|
1658
1720
|
await this.pool.execute(
|
|
@@ -1675,6 +1737,22 @@ ${JSON.stringify(data, null, 0)}`;
|
|
|
1675
1737
|
})
|
|
1676
1738
|
);
|
|
1677
1739
|
}
|
|
1740
|
+
async getCollectionIndexMsgId(collection) {
|
|
1741
|
+
if (!this.state.registryMsgId) {
|
|
1742
|
+
await this.readRegistryMessage();
|
|
1743
|
+
}
|
|
1744
|
+
return this.state.indexes[collection] || null;
|
|
1745
|
+
}
|
|
1746
|
+
async setCollectionIndexMsgId(collection, msgId) {
|
|
1747
|
+
if (!this.state.registryMsgId) {
|
|
1748
|
+
await this.readRegistryMessage();
|
|
1749
|
+
}
|
|
1750
|
+
this.state.indexes[collection] = msgId;
|
|
1751
|
+
await this.writeRegistryMessage({
|
|
1752
|
+
activeLease: this.state.activeLease,
|
|
1753
|
+
indexes: this.state.indexes
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1678
1756
|
getInstanceId() {
|
|
1679
1757
|
return this.instanceId;
|
|
1680
1758
|
}
|
|
@@ -2430,9 +2508,15 @@ var GramoBase = class {
|
|
|
2430
2508
|
const tokens = Array.isArray(config.botToken) ? config.botToken : [config.botToken];
|
|
2431
2509
|
this.pool = new BotWorkerPool(tokens, config.concurrency ?? 25, config.debug ?? false);
|
|
2432
2510
|
this.cache = new HotCache(config.cacheMaxBytes, config.cacheTtlMs);
|
|
2511
|
+
this.registry = new Registry(
|
|
2512
|
+
this.pool,
|
|
2513
|
+
config.indexChannelId ?? config.channelId,
|
|
2514
|
+
config.debug ?? false
|
|
2515
|
+
);
|
|
2433
2516
|
this.storage = new TelegramStorage(
|
|
2434
2517
|
this.pool,
|
|
2435
2518
|
config.channelId,
|
|
2519
|
+
this.registry,
|
|
2436
2520
|
config.encryptionKey,
|
|
2437
2521
|
config.debug ?? false
|
|
2438
2522
|
);
|
|
@@ -2441,11 +2525,6 @@ var GramoBase = class {
|
|
|
2441
2525
|
config.walChannelId ?? config.channelId,
|
|
2442
2526
|
config.debug ?? false
|
|
2443
2527
|
);
|
|
2444
|
-
this.registry = new Registry(
|
|
2445
|
-
this.pool,
|
|
2446
|
-
config.indexChannelId ?? config.channelId,
|
|
2447
|
-
config.debug ?? false
|
|
2448
|
-
);
|
|
2449
2528
|
this.realtime = new RealtimeManager(
|
|
2450
2529
|
this.pool,
|
|
2451
2530
|
config.webhookUrl,
|
|
@@ -2576,7 +2655,20 @@ var GramoBase = class {
|
|
|
2576
2655
|
}
|
|
2577
2656
|
}
|
|
2578
2657
|
};
|
|
2658
|
+
var globalForGramo = globalThis;
|
|
2579
2659
|
function createClient(config) {
|
|
2660
|
+
if (config.global) {
|
|
2661
|
+
if (!globalForGramo.__gramobase_clients__) {
|
|
2662
|
+
globalForGramo.__gramobase_clients__ = /* @__PURE__ */ new Map();
|
|
2663
|
+
}
|
|
2664
|
+
const cacheKey = Array.isArray(config.botToken) ? `${config.channelId}:${config.botToken.join(",")}` : `${config.channelId}:${config.botToken}`;
|
|
2665
|
+
if (globalForGramo.__gramobase_clients__.has(cacheKey)) {
|
|
2666
|
+
return globalForGramo.__gramobase_clients__.get(cacheKey);
|
|
2667
|
+
}
|
|
2668
|
+
const client = new GramoBase(config);
|
|
2669
|
+
globalForGramo.__gramobase_clients__.set(cacheKey, client);
|
|
2670
|
+
return client;
|
|
2671
|
+
}
|
|
2580
2672
|
return new GramoBase(config);
|
|
2581
2673
|
}
|
|
2582
2674
|
|
|
@@ -2655,6 +2747,21 @@ async function startStudio(port, cwd = process.cwd()) {
|
|
|
2655
2747
|
db = null;
|
|
2656
2748
|
}
|
|
2657
2749
|
}
|
|
2750
|
+
const cleanShutdown = async () => {
|
|
2751
|
+
if (db) {
|
|
2752
|
+
try {
|
|
2753
|
+
await db.disconnect();
|
|
2754
|
+
} catch (_) {
|
|
2755
|
+
}
|
|
2756
|
+
db = null;
|
|
2757
|
+
}
|
|
2758
|
+
server.close(() => {
|
|
2759
|
+
process.exit(0);
|
|
2760
|
+
});
|
|
2761
|
+
setTimeout(() => process.exit(0), 1e3);
|
|
2762
|
+
};
|
|
2763
|
+
process.once("SIGINT", cleanShutdown);
|
|
2764
|
+
process.once("SIGTERM", cleanShutdown);
|
|
2658
2765
|
const sseClients = /* @__PURE__ */ new Set();
|
|
2659
2766
|
if (db) {
|
|
2660
2767
|
const forward = (ev) => {
|