gramobase 1.0.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.
- package/CODE_OF_CONDUCT.md +132 -0
- package/LICENSE +21 -0
- package/README.md +314 -0
- package/dist/BotWorkerPool-9ndHQt2g.d.cts +201 -0
- package/dist/BotWorkerPool-9ndHQt2g.d.ts +201 -0
- package/dist/GramoBaseAuth-00fg0u_b.d.ts +218 -0
- package/dist/GramoBaseAuth-CHNn2_e5.d.cts +218 -0
- package/dist/auth/index.cjs +226 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/index.d.cts +5 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +203 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/bin/gramobase.cjs +167 -0
- package/dist/bin/gramobase.cjs.map +1 -0
- package/dist/bin/gramobase.d.cts +1 -0
- package/dist/bin/gramobase.d.ts +1 -0
- package/dist/bin/gramobase.js +160 -0
- package/dist/bin/gramobase.js.map +1 -0
- package/dist/index.cjs +1644 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +1611 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/index.cjs +98 -0
- package/dist/migrations/index.cjs.map +1 -0
- package/dist/migrations/index.d.cts +23 -0
- package/dist/migrations/index.d.ts +23 -0
- package/dist/migrations/index.js +96 -0
- package/dist/migrations/index.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import TelegramBot from 'node-telegram-bot-api';
|
|
3
|
+
import EventEmitter from 'eventemitter3';
|
|
4
|
+
|
|
5
|
+
interface GramoBaseDocument {
|
|
6
|
+
_id: string;
|
|
7
|
+
_collection: string;
|
|
8
|
+
_msgId: number;
|
|
9
|
+
_createdAt: string;
|
|
10
|
+
_updatedAt: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
type WithId<T> = T & GramoBaseDocument;
|
|
14
|
+
interface CollectionConfig<T extends z.ZodType> {
|
|
15
|
+
schema: T;
|
|
16
|
+
/** Channel override — uses the default if omitted */
|
|
17
|
+
channelId?: string | undefined;
|
|
18
|
+
/** Bloom-filter field list for fast-miss short-circuit */
|
|
19
|
+
indexes?: string[] | undefined;
|
|
20
|
+
/** Encrypt all documents in this collection with AES-256 */
|
|
21
|
+
encrypt?: boolean | undefined;
|
|
22
|
+
/** TTL seconds — documents are automatically expired after this many seconds */
|
|
23
|
+
ttl?: number | undefined;
|
|
24
|
+
}
|
|
25
|
+
type ElemOf<T> = T extends (infer U)[] ? U : T;
|
|
26
|
+
type ComparisonOperator<T> = {
|
|
27
|
+
$eq?: T | ElemOf<T> | undefined;
|
|
28
|
+
$ne?: T | ElemOf<T> | undefined;
|
|
29
|
+
$gt?: T | ElemOf<T> | number | undefined;
|
|
30
|
+
$gte?: T | ElemOf<T> | number | undefined;
|
|
31
|
+
$lt?: T | ElemOf<T> | number | undefined;
|
|
32
|
+
$lte?: T | ElemOf<T> | number | undefined;
|
|
33
|
+
$in?: (T | ElemOf<T>)[] | undefined;
|
|
34
|
+
$nin?: (T | ElemOf<T>)[] | undefined;
|
|
35
|
+
$exists?: boolean | undefined;
|
|
36
|
+
$regex?: RegExp | string | undefined;
|
|
37
|
+
};
|
|
38
|
+
type Filter<T> = {
|
|
39
|
+
[K in keyof T]?: T[K] | ElemOf<T[K]> | ComparisonOperator<T[K]> | undefined;
|
|
40
|
+
} | {
|
|
41
|
+
$and?: Filter<T>[] | undefined;
|
|
42
|
+
$or?: Filter<T>[] | undefined;
|
|
43
|
+
$not?: Filter<T> | undefined;
|
|
44
|
+
};
|
|
45
|
+
interface FindOptions<T> {
|
|
46
|
+
filter?: Filter<T> | undefined;
|
|
47
|
+
sort?: Partial<Record<keyof T | string, 1 | -1>> | undefined;
|
|
48
|
+
limit?: number | undefined;
|
|
49
|
+
skip?: number | undefined;
|
|
50
|
+
projection?: Partial<Record<keyof T | string, 1 | 0>> | undefined;
|
|
51
|
+
useCache?: boolean | undefined;
|
|
52
|
+
}
|
|
53
|
+
interface UpdateOperators<T> {
|
|
54
|
+
$set?: (Partial<T> & Record<string, unknown>) | undefined;
|
|
55
|
+
$unset?: Partial<Record<keyof T | string, '' | true>> | undefined;
|
|
56
|
+
$inc?: Partial<Record<keyof T | string, number>> | undefined;
|
|
57
|
+
$push?: Partial<Record<keyof T | string, unknown>> | undefined;
|
|
58
|
+
}
|
|
59
|
+
type WalOpType = 'INSERT' | 'UPDATE' | 'DELETE';
|
|
60
|
+
interface WalEntry {
|
|
61
|
+
seq: number;
|
|
62
|
+
op: WalOpType;
|
|
63
|
+
collection: string;
|
|
64
|
+
id: string;
|
|
65
|
+
data?: unknown;
|
|
66
|
+
timestamp: string;
|
|
67
|
+
checksum: string;
|
|
68
|
+
}
|
|
69
|
+
interface Lease {
|
|
70
|
+
instanceId: string;
|
|
71
|
+
acquiredAt: number;
|
|
72
|
+
expiresAt: number;
|
|
73
|
+
heartbeatInterval: ReturnType<typeof setInterval> | null;
|
|
74
|
+
}
|
|
75
|
+
interface User {
|
|
76
|
+
_id: string;
|
|
77
|
+
email: string;
|
|
78
|
+
passwordHash: string;
|
|
79
|
+
roles: string[];
|
|
80
|
+
metadata?: Record<string, unknown> | undefined;
|
|
81
|
+
createdAt: string;
|
|
82
|
+
updatedAt: string;
|
|
83
|
+
}
|
|
84
|
+
interface Session {
|
|
85
|
+
userId: string;
|
|
86
|
+
roles: string[];
|
|
87
|
+
expiresAt: number;
|
|
88
|
+
token: string;
|
|
89
|
+
}
|
|
90
|
+
interface AuthConfig {
|
|
91
|
+
jwtSecret: string;
|
|
92
|
+
jwtExpiresIn?: string | undefined;
|
|
93
|
+
bcryptRounds?: number | undefined;
|
|
94
|
+
onSignIn?: ((user: User) => Promise<void>) | undefined;
|
|
95
|
+
onSignOut?: ((userId: string) => Promise<void>) | undefined;
|
|
96
|
+
}
|
|
97
|
+
interface UploadOptions {
|
|
98
|
+
fileName?: string | undefined;
|
|
99
|
+
mimeType?: string | undefined;
|
|
100
|
+
metadata?: Record<string, unknown> | undefined;
|
|
101
|
+
}
|
|
102
|
+
interface FileRecord {
|
|
103
|
+
_id: string;
|
|
104
|
+
fileId: string;
|
|
105
|
+
fileName: string;
|
|
106
|
+
mimeType: string;
|
|
107
|
+
sizeBytes: number;
|
|
108
|
+
url?: string | undefined;
|
|
109
|
+
uploadedAt: string;
|
|
110
|
+
metadata?: Record<string, unknown> | undefined;
|
|
111
|
+
}
|
|
112
|
+
type GramoBaseEvent = {
|
|
113
|
+
type: 'insert';
|
|
114
|
+
collection: string;
|
|
115
|
+
doc: unknown;
|
|
116
|
+
} | {
|
|
117
|
+
type: 'update';
|
|
118
|
+
collection: string;
|
|
119
|
+
id: string;
|
|
120
|
+
changes: unknown;
|
|
121
|
+
doc: unknown;
|
|
122
|
+
} | {
|
|
123
|
+
type: 'delete';
|
|
124
|
+
collection: string;
|
|
125
|
+
id: string;
|
|
126
|
+
} | {
|
|
127
|
+
type: 'worker:rotate';
|
|
128
|
+
tokenIndex: number;
|
|
129
|
+
} | {
|
|
130
|
+
type: 'wal:flush';
|
|
131
|
+
entries: number;
|
|
132
|
+
};
|
|
133
|
+
interface Migration {
|
|
134
|
+
version: number;
|
|
135
|
+
name: string;
|
|
136
|
+
up(db: unknown): Promise<void>;
|
|
137
|
+
down(db: unknown): Promise<void>;
|
|
138
|
+
}
|
|
139
|
+
interface GramoBaseConfig {
|
|
140
|
+
/** Bot token or array of tokens for pool rotation */
|
|
141
|
+
botToken: string | string[];
|
|
142
|
+
/** Primary storage channel ID */
|
|
143
|
+
channelId: string;
|
|
144
|
+
/** Optional separate channel for WAL entries */
|
|
145
|
+
walChannelId?: string | undefined;
|
|
146
|
+
/** Optional separate channel for collection indexes */
|
|
147
|
+
indexChannelId?: string | undefined;
|
|
148
|
+
/** AES-256 encryption key for data at rest */
|
|
149
|
+
encryptionKey?: string | undefined;
|
|
150
|
+
/** LRU cache byte limit (default: 64MB) */
|
|
151
|
+
cacheMaxBytes?: number | undefined;
|
|
152
|
+
/** LRU cache TTL in milliseconds (default: 60s) */
|
|
153
|
+
cacheTtlMs?: number | undefined;
|
|
154
|
+
/** Max concurrent requests per bot token (default: 25) */
|
|
155
|
+
concurrency?: number | undefined;
|
|
156
|
+
/** Webhook URL for realtime events (optional — falls back to polling) */
|
|
157
|
+
webhookUrl?: string | undefined;
|
|
158
|
+
/** Enable verbose debug logging */
|
|
159
|
+
debug?: boolean | undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface WorkerStats {
|
|
163
|
+
tokenIndex: number;
|
|
164
|
+
requestCount: number;
|
|
165
|
+
errorCount: number;
|
|
166
|
+
lastUsed: number;
|
|
167
|
+
rateLimitHits: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* BotWorkerPool manages a round-robin pool of Telegram bot tokens.
|
|
171
|
+
* Each token gets its own PQueue limited to 25 concurrent requests
|
|
172
|
+
* (safe under Telegram's 30 req/s flood limit with headroom).
|
|
173
|
+
* On 429 responses, the worker is cooled down and the next token takes over.
|
|
174
|
+
*/
|
|
175
|
+
declare class BotWorkerPool extends EventEmitter {
|
|
176
|
+
private bots;
|
|
177
|
+
private queues;
|
|
178
|
+
private stats;
|
|
179
|
+
private currentIndex;
|
|
180
|
+
private debug;
|
|
181
|
+
constructor(tokens: string[], concurrency?: number, debug?: boolean);
|
|
182
|
+
/**
|
|
183
|
+
* Execute a Telegram API call through the pool with automatic retry
|
|
184
|
+
* and token rotation on rate limits.
|
|
185
|
+
*/
|
|
186
|
+
execute<T>(fn: (bot: TelegramBot) => Promise<T>, priority?: number): Promise<T>;
|
|
187
|
+
/**
|
|
188
|
+
* Round-robin with recency bias — prefer the worker that was least recently used.
|
|
189
|
+
*/
|
|
190
|
+
private pickWorker;
|
|
191
|
+
private isFloodError;
|
|
192
|
+
private isRetryableError;
|
|
193
|
+
private extractRetryAfter;
|
|
194
|
+
getBot(index?: number): TelegramBot;
|
|
195
|
+
getStats(): WorkerStats[];
|
|
196
|
+
getQueueSizes(): number[];
|
|
197
|
+
private sleep;
|
|
198
|
+
destroy(): Promise<void>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export { type AuthConfig as A, BotWorkerPool as B, type CollectionConfig as C, type FileRecord as F, type GramoBaseEvent as G, type Lease as L, type Migration as M, type Session as S, type UploadOptions as U, type WorkerStats as W, type GramoBaseConfig as a, type ComparisonOperator as b, type Filter as c, type FindOptions as d, type GramoBaseDocument as e, type UpdateOperators as f, type User as g, type WalEntry as h, type WalOpType as i, type WithId as j };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { B as BotWorkerPool, e as GramoBaseDocument, i as WalOpType, h as WalEntry, C as CollectionConfig, j as WithId, c as Filter, d as FindOptions, f as UpdateOperators, A as AuthConfig, g as User, S as Session } from './BotWorkerPool-9ndHQt2g.js';
|
|
3
|
+
import EventEmitter from 'eventemitter3';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* HotCache is an in-memory LRU cache that sits in front of Telegram storage.
|
|
7
|
+
*
|
|
8
|
+
* All reads hit the cache first. On a miss, the caller fetches from Telegram
|
|
9
|
+
* and stores the result here. All writes invalidate or update the relevant key.
|
|
10
|
+
*
|
|
11
|
+
* The cache also stores the "collection index" — the full key→msgId map for
|
|
12
|
+
* each collection — so that single-document lookups are O(1) without scanning
|
|
13
|
+
* all messages.
|
|
14
|
+
*/
|
|
15
|
+
declare class HotCache extends EventEmitter {
|
|
16
|
+
private cache;
|
|
17
|
+
private collectionIndexes;
|
|
18
|
+
private stats;
|
|
19
|
+
constructor(maxBytes?: number, ttlMs?: number);
|
|
20
|
+
get<T>(collection: string, id: string): T | undefined;
|
|
21
|
+
set<T>(collection: string, id: string, data: T): void;
|
|
22
|
+
delete(collection: string, id: string): void;
|
|
23
|
+
invalidateCollection(collection: string): void;
|
|
24
|
+
getQuery<T>(queryHash: string): T[] | undefined;
|
|
25
|
+
setQuery<T>(queryHash: string, results: T[]): void;
|
|
26
|
+
invalidateQuery(collection: string): void;
|
|
27
|
+
getIndex(collection: string): Map<string, number> | undefined;
|
|
28
|
+
setIndex(collection: string, index: Map<string, number>): void;
|
|
29
|
+
updateIndexEntry(collection: string, id: string, msgId: number): void;
|
|
30
|
+
deleteIndexEntry(collection: string, id: string): void;
|
|
31
|
+
getMsgId(collection: string, id: string): number | undefined;
|
|
32
|
+
getMany<T>(collection: string, ids: string[]): Map<string, T>;
|
|
33
|
+
getStats(): {
|
|
34
|
+
size: number;
|
|
35
|
+
hitRate: number;
|
|
36
|
+
hits: number;
|
|
37
|
+
misses: number;
|
|
38
|
+
evictions: number;
|
|
39
|
+
};
|
|
40
|
+
clear(): void;
|
|
41
|
+
private docKey;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface IndexMessage {
|
|
45
|
+
collection: string;
|
|
46
|
+
entries: Record<string, number>;
|
|
47
|
+
walSeq: number;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
msgId?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* TelegramStorage handles the raw read/write operations against Telegram channels.
|
|
53
|
+
*
|
|
54
|
+
* Each collection gets a "collection index message" (pinned or findable by tag)
|
|
55
|
+
* that maps document IDs to their Telegram message IDs.
|
|
56
|
+
*
|
|
57
|
+
* Documents are stored as JSON message text, optionally AES-256 encrypted.
|
|
58
|
+
* Files are stored via sendDocument/sendPhoto and referenced by Telegram file_id.
|
|
59
|
+
*/
|
|
60
|
+
declare class TelegramStorage {
|
|
61
|
+
private pool;
|
|
62
|
+
private defaultChannelId;
|
|
63
|
+
private debug;
|
|
64
|
+
private encryptionKey;
|
|
65
|
+
private indexMsgIds;
|
|
66
|
+
constructor(pool: BotWorkerPool, defaultChannelId: string, encryptionKey?: string, debug?: boolean);
|
|
67
|
+
loadIndex(collection: string, channelId?: string): Promise<IndexMessage>;
|
|
68
|
+
saveIndex(index: IndexMessage, channelId?: string): Promise<void>;
|
|
69
|
+
writeDocument(doc: GramoBaseDocument, channelId?: string): Promise<number>;
|
|
70
|
+
readDocument(msgId: number, channelId?: string): Promise<GramoBaseDocument | null>;
|
|
71
|
+
deleteDocument(msgId: number, channelId?: string): Promise<void>;
|
|
72
|
+
updateDocument(msgId: number, doc: GramoBaseDocument, channelId?: string): Promise<number>;
|
|
73
|
+
private writeChunked;
|
|
74
|
+
private readChunked;
|
|
75
|
+
uploadFile(data: Buffer, fileName: string, mimeType: string, channelId?: string): Promise<{
|
|
76
|
+
fileId: string;
|
|
77
|
+
msgId: number;
|
|
78
|
+
}>;
|
|
79
|
+
getFileUrl(fileId: string): Promise<string>;
|
|
80
|
+
private encrypt;
|
|
81
|
+
private decrypt;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Write-Ahead Log backed by a Telegram channel.
|
|
86
|
+
*
|
|
87
|
+
* Every mutation is written to the WAL channel as a message BEFORE it is
|
|
88
|
+
* applied to the main index. On startup, the WAL is replayed to recover
|
|
89
|
+
* any operations that didn't complete during a crash.
|
|
90
|
+
*
|
|
91
|
+
* Format of each WAL message:
|
|
92
|
+
* { "__wal": true, seq: N, op: "INSERT"|..., collection, id, data, ts, checksum }
|
|
93
|
+
*/
|
|
94
|
+
declare class WriteAheadLog {
|
|
95
|
+
private pool;
|
|
96
|
+
private walChannelId;
|
|
97
|
+
private debug;
|
|
98
|
+
private seq;
|
|
99
|
+
private buffer;
|
|
100
|
+
private flushTimer;
|
|
101
|
+
private readonly FLUSH_INTERVAL_MS;
|
|
102
|
+
private readonly BUFFER_LIMIT;
|
|
103
|
+
constructor(pool: BotWorkerPool, walChannelId: string, debug?: boolean);
|
|
104
|
+
init(): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Append an entry to the WAL buffer. Flushes immediately if buffer is full.
|
|
107
|
+
*/
|
|
108
|
+
append(op: WalOpType, collection: string, id: string, data?: unknown): Promise<WalEntry>;
|
|
109
|
+
/**
|
|
110
|
+
* Flush buffered WAL entries to Telegram.
|
|
111
|
+
*/
|
|
112
|
+
flush(): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Replay all WAL entries since a given sequence number.
|
|
115
|
+
* Returns entries in order — the caller applies them to restore state.
|
|
116
|
+
*/
|
|
117
|
+
replay(sinceSeq?: number): Promise<WalEntry[]>;
|
|
118
|
+
private scheduleFlush;
|
|
119
|
+
private checksum;
|
|
120
|
+
private verifyChecksum;
|
|
121
|
+
private chunk;
|
|
122
|
+
getCurrentSeq(): number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type DocOf<T extends z.ZodType> = WithId<z.infer<T>>;
|
|
126
|
+
/**
|
|
127
|
+
* Collection<T> is the main ORM interface.
|
|
128
|
+
*
|
|
129
|
+
* It provides a MongoDB-like API and handles:
|
|
130
|
+
* - Schema validation via Zod
|
|
131
|
+
* - Hot-cache reads (LRU in-memory)
|
|
132
|
+
* - WAL writes before Telegram flush
|
|
133
|
+
* - Index management (id → msgId map, stored as a pinned message)
|
|
134
|
+
* - Filter/sort/skip/limit in memory after cache warm-up
|
|
135
|
+
*/
|
|
136
|
+
declare class Collection<T extends z.ZodType> {
|
|
137
|
+
private name;
|
|
138
|
+
private config;
|
|
139
|
+
private cache;
|
|
140
|
+
private storage;
|
|
141
|
+
private wal;
|
|
142
|
+
private channelId;
|
|
143
|
+
private indexLoaded;
|
|
144
|
+
constructor(name: string, config: CollectionConfig<T>, cache: HotCache, storage: TelegramStorage, wal: WriteAheadLog, defaultChannelId: string);
|
|
145
|
+
ensureIndexLoaded(): Promise<void>;
|
|
146
|
+
insertOne(data: z.infer<T>): Promise<DocOf<T>>;
|
|
147
|
+
insertMany(items: z.infer<T>[]): Promise<DocOf<T>[]>;
|
|
148
|
+
findById(id: string): Promise<DocOf<T> | null>;
|
|
149
|
+
findOne(filter?: Filter<z.infer<T>>): Promise<DocOf<T> | null>;
|
|
150
|
+
find(options?: FindOptions<z.infer<T>>): Promise<DocOf<T>[]>;
|
|
151
|
+
count(filter?: Filter<z.infer<T>>): Promise<number>;
|
|
152
|
+
updateOne(filter: Filter<z.infer<T>>, update: UpdateOperators<z.infer<T>>): Promise<DocOf<T> | null>;
|
|
153
|
+
updateMany(filter: Filter<z.infer<T>>, update: UpdateOperators<z.infer<T>>): Promise<DocOf<T>[]>;
|
|
154
|
+
findByIdAndUpdate(id: string, update: UpdateOperators<z.infer<T>>): Promise<DocOf<T> | null>;
|
|
155
|
+
private applyUpdate;
|
|
156
|
+
deleteOne(filter: Filter<z.infer<T>>): Promise<boolean>;
|
|
157
|
+
deleteMany(filter: Filter<z.infer<T>>): Promise<number>;
|
|
158
|
+
deleteById(id: string): Promise<boolean>;
|
|
159
|
+
private flushIndex;
|
|
160
|
+
private matchesFilter;
|
|
161
|
+
private applySort;
|
|
162
|
+
private applyProjection;
|
|
163
|
+
private hashQuery;
|
|
164
|
+
getName(): string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
declare const UserSchema: z.ZodObject<{
|
|
168
|
+
email: z.ZodString;
|
|
169
|
+
passwordHash: z.ZodString;
|
|
170
|
+
roles: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
171
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
172
|
+
createdAt: z.ZodString;
|
|
173
|
+
updatedAt: z.ZodString;
|
|
174
|
+
}, "strip", z.ZodTypeAny, {
|
|
175
|
+
email: string;
|
|
176
|
+
passwordHash: string;
|
|
177
|
+
roles: string[];
|
|
178
|
+
createdAt: string;
|
|
179
|
+
updatedAt: string;
|
|
180
|
+
metadata?: Record<string, unknown> | undefined;
|
|
181
|
+
}, {
|
|
182
|
+
email: string;
|
|
183
|
+
passwordHash: string;
|
|
184
|
+
createdAt: string;
|
|
185
|
+
updatedAt: string;
|
|
186
|
+
roles?: string[] | undefined;
|
|
187
|
+
metadata?: Record<string, unknown> | undefined;
|
|
188
|
+
}>;
|
|
189
|
+
declare class GramoBaseAuth {
|
|
190
|
+
private users;
|
|
191
|
+
private config;
|
|
192
|
+
private readonly DEFAULT_ROUNDS;
|
|
193
|
+
private readonly resolvedSecret;
|
|
194
|
+
constructor(users: Collection<typeof UserSchema>, config: AuthConfig);
|
|
195
|
+
register(email: string, password: string, roles?: string[], metadata?: Record<string, unknown>): Promise<{
|
|
196
|
+
user: User;
|
|
197
|
+
session: Session;
|
|
198
|
+
}>;
|
|
199
|
+
login(email: string, password: string): Promise<{
|
|
200
|
+
user: User;
|
|
201
|
+
session: Session;
|
|
202
|
+
}>;
|
|
203
|
+
verifyToken(token: string): Session;
|
|
204
|
+
requireRole(session: Session, role: string): void;
|
|
205
|
+
requireAnyRole(session: Session, roles: string[]): void;
|
|
206
|
+
changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void>;
|
|
207
|
+
resetPassword(userId: string, newPassword: string): Promise<void>;
|
|
208
|
+
getUserById(id: string): Promise<User | null>;
|
|
209
|
+
getUserByEmail(email: string): Promise<User | null>;
|
|
210
|
+
updateRoles(userId: string, roles: string[]): Promise<void>;
|
|
211
|
+
deleteUser(userId: string): Promise<void>;
|
|
212
|
+
private createSession;
|
|
213
|
+
private parseExpiry;
|
|
214
|
+
middleware(): (req: any, res: any, next: any) => any;
|
|
215
|
+
requireRoleMiddleware(role: string): (req: any, res: any, next: any) => void;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export { Collection as C, GramoBaseAuth as G };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { B as BotWorkerPool, e as GramoBaseDocument, i as WalOpType, h as WalEntry, C as CollectionConfig, j as WithId, c as Filter, d as FindOptions, f as UpdateOperators, A as AuthConfig, g as User, S as Session } from './BotWorkerPool-9ndHQt2g.cjs';
|
|
3
|
+
import EventEmitter from 'eventemitter3';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* HotCache is an in-memory LRU cache that sits in front of Telegram storage.
|
|
7
|
+
*
|
|
8
|
+
* All reads hit the cache first. On a miss, the caller fetches from Telegram
|
|
9
|
+
* and stores the result here. All writes invalidate or update the relevant key.
|
|
10
|
+
*
|
|
11
|
+
* The cache also stores the "collection index" — the full key→msgId map for
|
|
12
|
+
* each collection — so that single-document lookups are O(1) without scanning
|
|
13
|
+
* all messages.
|
|
14
|
+
*/
|
|
15
|
+
declare class HotCache extends EventEmitter {
|
|
16
|
+
private cache;
|
|
17
|
+
private collectionIndexes;
|
|
18
|
+
private stats;
|
|
19
|
+
constructor(maxBytes?: number, ttlMs?: number);
|
|
20
|
+
get<T>(collection: string, id: string): T | undefined;
|
|
21
|
+
set<T>(collection: string, id: string, data: T): void;
|
|
22
|
+
delete(collection: string, id: string): void;
|
|
23
|
+
invalidateCollection(collection: string): void;
|
|
24
|
+
getQuery<T>(queryHash: string): T[] | undefined;
|
|
25
|
+
setQuery<T>(queryHash: string, results: T[]): void;
|
|
26
|
+
invalidateQuery(collection: string): void;
|
|
27
|
+
getIndex(collection: string): Map<string, number> | undefined;
|
|
28
|
+
setIndex(collection: string, index: Map<string, number>): void;
|
|
29
|
+
updateIndexEntry(collection: string, id: string, msgId: number): void;
|
|
30
|
+
deleteIndexEntry(collection: string, id: string): void;
|
|
31
|
+
getMsgId(collection: string, id: string): number | undefined;
|
|
32
|
+
getMany<T>(collection: string, ids: string[]): Map<string, T>;
|
|
33
|
+
getStats(): {
|
|
34
|
+
size: number;
|
|
35
|
+
hitRate: number;
|
|
36
|
+
hits: number;
|
|
37
|
+
misses: number;
|
|
38
|
+
evictions: number;
|
|
39
|
+
};
|
|
40
|
+
clear(): void;
|
|
41
|
+
private docKey;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface IndexMessage {
|
|
45
|
+
collection: string;
|
|
46
|
+
entries: Record<string, number>;
|
|
47
|
+
walSeq: number;
|
|
48
|
+
updatedAt: string;
|
|
49
|
+
msgId?: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* TelegramStorage handles the raw read/write operations against Telegram channels.
|
|
53
|
+
*
|
|
54
|
+
* Each collection gets a "collection index message" (pinned or findable by tag)
|
|
55
|
+
* that maps document IDs to their Telegram message IDs.
|
|
56
|
+
*
|
|
57
|
+
* Documents are stored as JSON message text, optionally AES-256 encrypted.
|
|
58
|
+
* Files are stored via sendDocument/sendPhoto and referenced by Telegram file_id.
|
|
59
|
+
*/
|
|
60
|
+
declare class TelegramStorage {
|
|
61
|
+
private pool;
|
|
62
|
+
private defaultChannelId;
|
|
63
|
+
private debug;
|
|
64
|
+
private encryptionKey;
|
|
65
|
+
private indexMsgIds;
|
|
66
|
+
constructor(pool: BotWorkerPool, defaultChannelId: string, encryptionKey?: string, debug?: boolean);
|
|
67
|
+
loadIndex(collection: string, channelId?: string): Promise<IndexMessage>;
|
|
68
|
+
saveIndex(index: IndexMessage, channelId?: string): Promise<void>;
|
|
69
|
+
writeDocument(doc: GramoBaseDocument, channelId?: string): Promise<number>;
|
|
70
|
+
readDocument(msgId: number, channelId?: string): Promise<GramoBaseDocument | null>;
|
|
71
|
+
deleteDocument(msgId: number, channelId?: string): Promise<void>;
|
|
72
|
+
updateDocument(msgId: number, doc: GramoBaseDocument, channelId?: string): Promise<number>;
|
|
73
|
+
private writeChunked;
|
|
74
|
+
private readChunked;
|
|
75
|
+
uploadFile(data: Buffer, fileName: string, mimeType: string, channelId?: string): Promise<{
|
|
76
|
+
fileId: string;
|
|
77
|
+
msgId: number;
|
|
78
|
+
}>;
|
|
79
|
+
getFileUrl(fileId: string): Promise<string>;
|
|
80
|
+
private encrypt;
|
|
81
|
+
private decrypt;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Write-Ahead Log backed by a Telegram channel.
|
|
86
|
+
*
|
|
87
|
+
* Every mutation is written to the WAL channel as a message BEFORE it is
|
|
88
|
+
* applied to the main index. On startup, the WAL is replayed to recover
|
|
89
|
+
* any operations that didn't complete during a crash.
|
|
90
|
+
*
|
|
91
|
+
* Format of each WAL message:
|
|
92
|
+
* { "__wal": true, seq: N, op: "INSERT"|..., collection, id, data, ts, checksum }
|
|
93
|
+
*/
|
|
94
|
+
declare class WriteAheadLog {
|
|
95
|
+
private pool;
|
|
96
|
+
private walChannelId;
|
|
97
|
+
private debug;
|
|
98
|
+
private seq;
|
|
99
|
+
private buffer;
|
|
100
|
+
private flushTimer;
|
|
101
|
+
private readonly FLUSH_INTERVAL_MS;
|
|
102
|
+
private readonly BUFFER_LIMIT;
|
|
103
|
+
constructor(pool: BotWorkerPool, walChannelId: string, debug?: boolean);
|
|
104
|
+
init(): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Append an entry to the WAL buffer. Flushes immediately if buffer is full.
|
|
107
|
+
*/
|
|
108
|
+
append(op: WalOpType, collection: string, id: string, data?: unknown): Promise<WalEntry>;
|
|
109
|
+
/**
|
|
110
|
+
* Flush buffered WAL entries to Telegram.
|
|
111
|
+
*/
|
|
112
|
+
flush(): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Replay all WAL entries since a given sequence number.
|
|
115
|
+
* Returns entries in order — the caller applies them to restore state.
|
|
116
|
+
*/
|
|
117
|
+
replay(sinceSeq?: number): Promise<WalEntry[]>;
|
|
118
|
+
private scheduleFlush;
|
|
119
|
+
private checksum;
|
|
120
|
+
private verifyChecksum;
|
|
121
|
+
private chunk;
|
|
122
|
+
getCurrentSeq(): number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
type DocOf<T extends z.ZodType> = WithId<z.infer<T>>;
|
|
126
|
+
/**
|
|
127
|
+
* Collection<T> is the main ORM interface.
|
|
128
|
+
*
|
|
129
|
+
* It provides a MongoDB-like API and handles:
|
|
130
|
+
* - Schema validation via Zod
|
|
131
|
+
* - Hot-cache reads (LRU in-memory)
|
|
132
|
+
* - WAL writes before Telegram flush
|
|
133
|
+
* - Index management (id → msgId map, stored as a pinned message)
|
|
134
|
+
* - Filter/sort/skip/limit in memory after cache warm-up
|
|
135
|
+
*/
|
|
136
|
+
declare class Collection<T extends z.ZodType> {
|
|
137
|
+
private name;
|
|
138
|
+
private config;
|
|
139
|
+
private cache;
|
|
140
|
+
private storage;
|
|
141
|
+
private wal;
|
|
142
|
+
private channelId;
|
|
143
|
+
private indexLoaded;
|
|
144
|
+
constructor(name: string, config: CollectionConfig<T>, cache: HotCache, storage: TelegramStorage, wal: WriteAheadLog, defaultChannelId: string);
|
|
145
|
+
ensureIndexLoaded(): Promise<void>;
|
|
146
|
+
insertOne(data: z.infer<T>): Promise<DocOf<T>>;
|
|
147
|
+
insertMany(items: z.infer<T>[]): Promise<DocOf<T>[]>;
|
|
148
|
+
findById(id: string): Promise<DocOf<T> | null>;
|
|
149
|
+
findOne(filter?: Filter<z.infer<T>>): Promise<DocOf<T> | null>;
|
|
150
|
+
find(options?: FindOptions<z.infer<T>>): Promise<DocOf<T>[]>;
|
|
151
|
+
count(filter?: Filter<z.infer<T>>): Promise<number>;
|
|
152
|
+
updateOne(filter: Filter<z.infer<T>>, update: UpdateOperators<z.infer<T>>): Promise<DocOf<T> | null>;
|
|
153
|
+
updateMany(filter: Filter<z.infer<T>>, update: UpdateOperators<z.infer<T>>): Promise<DocOf<T>[]>;
|
|
154
|
+
findByIdAndUpdate(id: string, update: UpdateOperators<z.infer<T>>): Promise<DocOf<T> | null>;
|
|
155
|
+
private applyUpdate;
|
|
156
|
+
deleteOne(filter: Filter<z.infer<T>>): Promise<boolean>;
|
|
157
|
+
deleteMany(filter: Filter<z.infer<T>>): Promise<number>;
|
|
158
|
+
deleteById(id: string): Promise<boolean>;
|
|
159
|
+
private flushIndex;
|
|
160
|
+
private matchesFilter;
|
|
161
|
+
private applySort;
|
|
162
|
+
private applyProjection;
|
|
163
|
+
private hashQuery;
|
|
164
|
+
getName(): string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
declare const UserSchema: z.ZodObject<{
|
|
168
|
+
email: z.ZodString;
|
|
169
|
+
passwordHash: z.ZodString;
|
|
170
|
+
roles: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
171
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
172
|
+
createdAt: z.ZodString;
|
|
173
|
+
updatedAt: z.ZodString;
|
|
174
|
+
}, "strip", z.ZodTypeAny, {
|
|
175
|
+
email: string;
|
|
176
|
+
passwordHash: string;
|
|
177
|
+
roles: string[];
|
|
178
|
+
createdAt: string;
|
|
179
|
+
updatedAt: string;
|
|
180
|
+
metadata?: Record<string, unknown> | undefined;
|
|
181
|
+
}, {
|
|
182
|
+
email: string;
|
|
183
|
+
passwordHash: string;
|
|
184
|
+
createdAt: string;
|
|
185
|
+
updatedAt: string;
|
|
186
|
+
roles?: string[] | undefined;
|
|
187
|
+
metadata?: Record<string, unknown> | undefined;
|
|
188
|
+
}>;
|
|
189
|
+
declare class GramoBaseAuth {
|
|
190
|
+
private users;
|
|
191
|
+
private config;
|
|
192
|
+
private readonly DEFAULT_ROUNDS;
|
|
193
|
+
private readonly resolvedSecret;
|
|
194
|
+
constructor(users: Collection<typeof UserSchema>, config: AuthConfig);
|
|
195
|
+
register(email: string, password: string, roles?: string[], metadata?: Record<string, unknown>): Promise<{
|
|
196
|
+
user: User;
|
|
197
|
+
session: Session;
|
|
198
|
+
}>;
|
|
199
|
+
login(email: string, password: string): Promise<{
|
|
200
|
+
user: User;
|
|
201
|
+
session: Session;
|
|
202
|
+
}>;
|
|
203
|
+
verifyToken(token: string): Session;
|
|
204
|
+
requireRole(session: Session, role: string): void;
|
|
205
|
+
requireAnyRole(session: Session, roles: string[]): void;
|
|
206
|
+
changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void>;
|
|
207
|
+
resetPassword(userId: string, newPassword: string): Promise<void>;
|
|
208
|
+
getUserById(id: string): Promise<User | null>;
|
|
209
|
+
getUserByEmail(email: string): Promise<User | null>;
|
|
210
|
+
updateRoles(userId: string, roles: string[]): Promise<void>;
|
|
211
|
+
deleteUser(userId: string): Promise<void>;
|
|
212
|
+
private createSession;
|
|
213
|
+
private parseExpiry;
|
|
214
|
+
middleware(): (req: any, res: any, next: any) => any;
|
|
215
|
+
requireRoleMiddleware(role: string): (req: any, res: any, next: any) => void;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export { Collection as C, GramoBaseAuth as G };
|