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.js
CHANGED
|
@@ -1213,9 +1213,10 @@ var INDEX_TAG = "__GRAMOBASE_INDEX__";
|
|
|
1213
1213
|
var DOC_TAG = "__GRAMOBASE_DOC__";
|
|
1214
1214
|
var MAX_MSG_BYTES = 4e3;
|
|
1215
1215
|
var TelegramStorage = class {
|
|
1216
|
-
constructor(pool, defaultChannelId, encryptionKey, debug = false) {
|
|
1216
|
+
constructor(pool, defaultChannelId, registry, encryptionKey, debug = false) {
|
|
1217
1217
|
this.pool = pool;
|
|
1218
1218
|
this.defaultChannelId = defaultChannelId;
|
|
1219
|
+
this.registry = registry;
|
|
1219
1220
|
this.debug = debug;
|
|
1220
1221
|
if (encryptionKey) {
|
|
1221
1222
|
this.encryptionKey = createHash("sha256").update(encryptionKey).digest();
|
|
@@ -1223,20 +1224,52 @@ var TelegramStorage = class {
|
|
|
1223
1224
|
}
|
|
1224
1225
|
pool;
|
|
1225
1226
|
defaultChannelId;
|
|
1227
|
+
registry;
|
|
1226
1228
|
debug;
|
|
1227
1229
|
encryptionKey = null;
|
|
1228
1230
|
// collection → pinned index message ID
|
|
1229
1231
|
indexMsgIds = /* @__PURE__ */ new Map();
|
|
1230
1232
|
// ─── Index management ─────────────────────────────────────────────────────
|
|
1233
|
+
async readRawMessageText(msgId, channel) {
|
|
1234
|
+
try {
|
|
1235
|
+
const msg = await this.pool.execute(
|
|
1236
|
+
(bot) => bot.forwardMessage(channel, channel, msgId)
|
|
1237
|
+
);
|
|
1238
|
+
if (!msg?.text) return null;
|
|
1239
|
+
let text = msg.text;
|
|
1240
|
+
if (this.encryptionKey && text.startsWith("ENC:")) {
|
|
1241
|
+
text = this.decrypt(text);
|
|
1242
|
+
}
|
|
1243
|
+
return text;
|
|
1244
|
+
} catch {
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1231
1248
|
async loadIndex(collection, channelId) {
|
|
1232
1249
|
const channel = channelId ?? this.defaultChannelId;
|
|
1250
|
+
try {
|
|
1251
|
+
const msgId = await this.registry.getCollectionIndexMsgId(collection);
|
|
1252
|
+
if (msgId) {
|
|
1253
|
+
const text = await this.readRawMessageText(msgId, channel);
|
|
1254
|
+
if (text && text.startsWith(INDEX_TAG)) {
|
|
1255
|
+
const json = text.replace(INDEX_TAG + "\n", "");
|
|
1256
|
+
const parsed = JSON.parse(json);
|
|
1257
|
+
this.indexMsgIds.set(collection, msgId);
|
|
1258
|
+
return parsed;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
} catch {
|
|
1262
|
+
}
|
|
1233
1263
|
try {
|
|
1234
1264
|
const chat = await this.pool.execute((bot) => bot.getChat(channel));
|
|
1235
1265
|
if (chat.pinned_message?.text?.startsWith(INDEX_TAG)) {
|
|
1236
1266
|
const json = chat.pinned_message.text.replace(INDEX_TAG + "\n", "");
|
|
1237
1267
|
const parsed = JSON.parse(json);
|
|
1238
|
-
|
|
1239
|
-
|
|
1268
|
+
if (parsed.collection === collection) {
|
|
1269
|
+
this.indexMsgIds.set(collection, chat.pinned_message.message_id);
|
|
1270
|
+
await this.registry.setCollectionIndexMsgId(collection, chat.pinned_message.message_id);
|
|
1271
|
+
return parsed;
|
|
1272
|
+
}
|
|
1240
1273
|
}
|
|
1241
1274
|
} catch {
|
|
1242
1275
|
}
|
|
@@ -1251,7 +1284,7 @@ var TelegramStorage = class {
|
|
|
1251
1284
|
const channel = channelId ?? this.defaultChannelId;
|
|
1252
1285
|
const text = `${INDEX_TAG}
|
|
1253
1286
|
${JSON.stringify(index)}`;
|
|
1254
|
-
const existingMsgId = this.indexMsgIds.get(index.collection);
|
|
1287
|
+
const existingMsgId = this.indexMsgIds.get(index.collection) || await this.registry.getCollectionIndexMsgId(index.collection);
|
|
1255
1288
|
if (existingMsgId) {
|
|
1256
1289
|
try {
|
|
1257
1290
|
await this.pool.execute(
|
|
@@ -1267,10 +1300,9 @@ ${JSON.stringify(index)}`;
|
|
|
1267
1300
|
const msg = await this.pool.execute(
|
|
1268
1301
|
(bot) => bot.sendMessage(channel, text, { disable_notification: true })
|
|
1269
1302
|
);
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
);
|
|
1303
|
+
const newMsgId = msg.message_id;
|
|
1304
|
+
this.indexMsgIds.set(index.collection, newMsgId);
|
|
1305
|
+
await this.registry.setCollectionIndexMsgId(index.collection, newMsgId);
|
|
1274
1306
|
}
|
|
1275
1307
|
// ─── Document CRUD ────────────────────────────────────────────────────────
|
|
1276
1308
|
async writeDocument(doc, channelId) {
|
|
@@ -1290,10 +1322,9 @@ ${JSON.stringify(index)}`;
|
|
|
1290
1322
|
async readDocument(msgId, channelId) {
|
|
1291
1323
|
const channel = channelId ?? this.defaultChannelId;
|
|
1292
1324
|
try {
|
|
1293
|
-
const
|
|
1294
|
-
(bot) => bot.
|
|
1325
|
+
const msg = await this.pool.execute(
|
|
1326
|
+
(bot) => bot.forwardMessage(channel, channel, msgId)
|
|
1295
1327
|
);
|
|
1296
|
-
const msg = Array.isArray(msgs) ? msgs[0] : msgs;
|
|
1297
1328
|
if (!msg?.text) return null;
|
|
1298
1329
|
let text = msg.text;
|
|
1299
1330
|
if (this.encryptionKey && text.startsWith("ENC:")) {
|
|
@@ -1304,6 +1335,7 @@ ${JSON.stringify(index)}`;
|
|
|
1304
1335
|
}
|
|
1305
1336
|
const parsed = JSON.parse(text);
|
|
1306
1337
|
delete parsed[DOC_TAG];
|
|
1338
|
+
parsed._msgId = msgId;
|
|
1307
1339
|
return parsed;
|
|
1308
1340
|
} catch {
|
|
1309
1341
|
return null;
|
|
@@ -1342,10 +1374,9 @@ ${JSON.stringify(index)}`;
|
|
|
1342
1374
|
const msgIds = JSON.parse(headerText.replace("CHUNK:", ""));
|
|
1343
1375
|
const parts = [];
|
|
1344
1376
|
for (const id of msgIds) {
|
|
1345
|
-
const
|
|
1346
|
-
(bot) => bot.
|
|
1377
|
+
const msg = await this.pool.execute(
|
|
1378
|
+
(bot) => bot.forwardMessage(channel, channel, id)
|
|
1347
1379
|
);
|
|
1348
|
-
const msg = Array.isArray(msgs) ? msgs[0] : msgs;
|
|
1349
1380
|
if (msg?.text) parts.push(msg.text);
|
|
1350
1381
|
}
|
|
1351
1382
|
return parts.join("");
|
|
@@ -1546,7 +1577,8 @@ var Registry = class {
|
|
|
1546
1577
|
this.state = {
|
|
1547
1578
|
activeLease: null,
|
|
1548
1579
|
instanceId: this.instanceId,
|
|
1549
|
-
registryMsgId: null
|
|
1580
|
+
registryMsgId: null,
|
|
1581
|
+
indexes: {}
|
|
1550
1582
|
};
|
|
1551
1583
|
}
|
|
1552
1584
|
pool;
|
|
@@ -1554,14 +1586,33 @@ var Registry = class {
|
|
|
1554
1586
|
debug;
|
|
1555
1587
|
state;
|
|
1556
1588
|
instanceId;
|
|
1557
|
-
async acquireWriteLease() {
|
|
1589
|
+
async acquireWriteLease(options = { wait: true }) {
|
|
1558
1590
|
const existing = await this.readRegistryMessage();
|
|
1559
1591
|
if (existing?.activeLease) {
|
|
1560
1592
|
const lease2 = existing.activeLease;
|
|
1561
1593
|
if (lease2.instanceId !== this.instanceId && Date.now() < lease2.expiresAt) {
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1594
|
+
if (!options.wait) {
|
|
1595
|
+
throw new Error(
|
|
1596
|
+
`[gramobase Registry] Another instance (${lease2.instanceId}) holds the write lease until ${new Date(
|
|
1597
|
+
lease2.expiresAt
|
|
1598
|
+
).toISOString()}. Use Registry.forceRelease() to break a stale lease.`
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
const waitMs = lease2.expiresAt - Date.now() + 250;
|
|
1602
|
+
if (this.debug) {
|
|
1603
|
+
console.log(
|
|
1604
|
+
`[Registry] Lease held by ${lease2.instanceId}, waiting ${waitMs}ms for it to expire...`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
1608
|
+
const recheckState = await this.readRegistryMessage();
|
|
1609
|
+
if (recheckState?.activeLease && recheckState.activeLease.instanceId !== this.instanceId && Date.now() < recheckState.activeLease.expiresAt) {
|
|
1610
|
+
throw new Error(
|
|
1611
|
+
`[gramobase Registry] Another instance (${recheckState.activeLease.instanceId}) holds the write lease until ${new Date(
|
|
1612
|
+
recheckState.activeLease.expiresAt
|
|
1613
|
+
).toISOString()}. Use Registry.forceRelease() to break a stale lease.`
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1565
1616
|
}
|
|
1566
1617
|
}
|
|
1567
1618
|
const lease = {
|
|
@@ -1613,15 +1664,26 @@ var Registry = class {
|
|
|
1613
1664
|
if (chat.pinned_message?.text?.startsWith(REGISTRY_TAG)) {
|
|
1614
1665
|
this.state.registryMsgId = chat.pinned_message.message_id;
|
|
1615
1666
|
const json = chat.pinned_message.text.replace(REGISTRY_TAG + "\n", "");
|
|
1616
|
-
|
|
1667
|
+
const parsed = JSON.parse(json);
|
|
1668
|
+
this.state.indexes = parsed.indexes || {};
|
|
1669
|
+
return parsed;
|
|
1617
1670
|
}
|
|
1618
1671
|
} catch {
|
|
1619
1672
|
}
|
|
1620
1673
|
return null;
|
|
1621
1674
|
}
|
|
1622
1675
|
async writeRegistryMessage(data) {
|
|
1676
|
+
let leasePayload = null;
|
|
1677
|
+
if (data.activeLease) {
|
|
1678
|
+
const { heartbeatInterval, ...rest } = data.activeLease;
|
|
1679
|
+
leasePayload = rest;
|
|
1680
|
+
}
|
|
1681
|
+
const payload = {
|
|
1682
|
+
activeLease: leasePayload,
|
|
1683
|
+
indexes: data.indexes || this.state.indexes || {}
|
|
1684
|
+
};
|
|
1623
1685
|
const text = `${REGISTRY_TAG}
|
|
1624
|
-
${JSON.stringify(
|
|
1686
|
+
${JSON.stringify(payload, null, 0)}`;
|
|
1625
1687
|
if (this.state.registryMsgId) {
|
|
1626
1688
|
try {
|
|
1627
1689
|
await this.pool.execute(
|
|
@@ -1644,6 +1706,22 @@ ${JSON.stringify(data, null, 0)}`;
|
|
|
1644
1706
|
})
|
|
1645
1707
|
);
|
|
1646
1708
|
}
|
|
1709
|
+
async getCollectionIndexMsgId(collection) {
|
|
1710
|
+
if (!this.state.registryMsgId) {
|
|
1711
|
+
await this.readRegistryMessage();
|
|
1712
|
+
}
|
|
1713
|
+
return this.state.indexes[collection] || null;
|
|
1714
|
+
}
|
|
1715
|
+
async setCollectionIndexMsgId(collection, msgId) {
|
|
1716
|
+
if (!this.state.registryMsgId) {
|
|
1717
|
+
await this.readRegistryMessage();
|
|
1718
|
+
}
|
|
1719
|
+
this.state.indexes[collection] = msgId;
|
|
1720
|
+
await this.writeRegistryMessage({
|
|
1721
|
+
activeLease: this.state.activeLease,
|
|
1722
|
+
indexes: this.state.indexes
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1647
1725
|
getInstanceId() {
|
|
1648
1726
|
return this.instanceId;
|
|
1649
1727
|
}
|
|
@@ -2399,9 +2477,15 @@ var GramoBase = class {
|
|
|
2399
2477
|
const tokens = Array.isArray(config.botToken) ? config.botToken : [config.botToken];
|
|
2400
2478
|
this.pool = new BotWorkerPool(tokens, config.concurrency ?? 25, config.debug ?? false);
|
|
2401
2479
|
this.cache = new HotCache(config.cacheMaxBytes, config.cacheTtlMs);
|
|
2480
|
+
this.registry = new Registry(
|
|
2481
|
+
this.pool,
|
|
2482
|
+
config.indexChannelId ?? config.channelId,
|
|
2483
|
+
config.debug ?? false
|
|
2484
|
+
);
|
|
2402
2485
|
this.storage = new TelegramStorage(
|
|
2403
2486
|
this.pool,
|
|
2404
2487
|
config.channelId,
|
|
2488
|
+
this.registry,
|
|
2405
2489
|
config.encryptionKey,
|
|
2406
2490
|
config.debug ?? false
|
|
2407
2491
|
);
|
|
@@ -2410,11 +2494,6 @@ var GramoBase = class {
|
|
|
2410
2494
|
config.walChannelId ?? config.channelId,
|
|
2411
2495
|
config.debug ?? false
|
|
2412
2496
|
);
|
|
2413
|
-
this.registry = new Registry(
|
|
2414
|
-
this.pool,
|
|
2415
|
-
config.indexChannelId ?? config.channelId,
|
|
2416
|
-
config.debug ?? false
|
|
2417
|
-
);
|
|
2418
2497
|
this.realtime = new RealtimeManager(
|
|
2419
2498
|
this.pool,
|
|
2420
2499
|
config.webhookUrl,
|
|
@@ -2545,7 +2624,20 @@ var GramoBase = class {
|
|
|
2545
2624
|
}
|
|
2546
2625
|
}
|
|
2547
2626
|
};
|
|
2627
|
+
var globalForGramo = globalThis;
|
|
2548
2628
|
function createClient(config) {
|
|
2629
|
+
if (config.global) {
|
|
2630
|
+
if (!globalForGramo.__gramobase_clients__) {
|
|
2631
|
+
globalForGramo.__gramobase_clients__ = /* @__PURE__ */ new Map();
|
|
2632
|
+
}
|
|
2633
|
+
const cacheKey = Array.isArray(config.botToken) ? `${config.channelId}:${config.botToken.join(",")}` : `${config.channelId}:${config.botToken}`;
|
|
2634
|
+
if (globalForGramo.__gramobase_clients__.has(cacheKey)) {
|
|
2635
|
+
return globalForGramo.__gramobase_clients__.get(cacheKey);
|
|
2636
|
+
}
|
|
2637
|
+
const client = new GramoBase(config);
|
|
2638
|
+
globalForGramo.__gramobase_clients__.set(cacheKey, client);
|
|
2639
|
+
return client;
|
|
2640
|
+
}
|
|
2549
2641
|
return new GramoBase(config);
|
|
2550
2642
|
}
|
|
2551
2643
|
|
|
@@ -2624,6 +2716,21 @@ async function startStudio(port, cwd = process.cwd()) {
|
|
|
2624
2716
|
db = null;
|
|
2625
2717
|
}
|
|
2626
2718
|
}
|
|
2719
|
+
const cleanShutdown = async () => {
|
|
2720
|
+
if (db) {
|
|
2721
|
+
try {
|
|
2722
|
+
await db.disconnect();
|
|
2723
|
+
} catch (_) {
|
|
2724
|
+
}
|
|
2725
|
+
db = null;
|
|
2726
|
+
}
|
|
2727
|
+
server.close(() => {
|
|
2728
|
+
process.exit(0);
|
|
2729
|
+
});
|
|
2730
|
+
setTimeout(() => process.exit(0), 1e3);
|
|
2731
|
+
};
|
|
2732
|
+
process.once("SIGINT", cleanShutdown);
|
|
2733
|
+
process.once("SIGTERM", cleanShutdown);
|
|
2627
2734
|
const sseClients = /* @__PURE__ */ new Set();
|
|
2628
2735
|
if (db) {
|
|
2629
2736
|
const forward = (ev) => {
|