gramobase 1.0.9 → 1.0.12
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 +92 -30
- 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/bin/gramobase.cjs +15 -9
- package/dist/bin/gramobase.cjs.map +1 -1
- package/dist/bin/gramobase.js +15 -9
- package/dist/bin/gramobase.js.map +1 -1
- 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 +2897 -0
- package/dist/studio/server.cjs.map +1 -0
- package/dist/studio/server.d.cts +5 -0
- package/dist/studio/server.d.ts +5 -0
- package/dist/studio/server.js +2866 -0
- package/dist/studio/server.js.map +1 -0
- package/package.json +26 -24
package/README.md
CHANGED
|
@@ -4,20 +4,47 @@
|
|
|
4
4
|
[](https://github.com/besaoct/gramobase/actions)
|
|
5
5
|
[](https://github.com/besaoct/gramobase/blob/main/LICENSE)
|
|
6
6
|
[](https://github.com/besaoct/gramobase/actions)
|
|
7
|
-
[](https://codecov.io/gh/besaoct/gramobase)
|
|
8
8
|
[](https://github.com/besaoct/gramobase/pulls)
|
|
9
9
|
|
|
10
10
|
**Telegram as a free, infinite, production-grade backend database.**
|
|
11
11
|
|
|
12
|
-
Every Telegram channel is a collection. Every message is a document. Zero infrastructure needed — all you need
|
|
12
|
+
Every Telegram channel is a collection. Every message is a document. Zero infrastructure needed — all you need a free Telegram account.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### 1. Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install gramobase
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 2. Setup
|
|
25
|
+
|
|
26
|
+
Initialize your project with the auto-detect wizard:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx gramobase init
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This interactive wizard walks you through setting up your backend. It features **Auto-Detect** technology and **Anti-Flood** scaling: it will ask you how many Bot Tokens you want to configure (for 30 req/s scaling per bot). Provide your tokens, and leave the Channel ID blank! By sending a message in your channel, `gramobase` will automatically fetch your hidden Telegram Channel ID for you and generate your `.env` and `gramobase.config.ts`.
|
|
33
|
+
|
|
34
|
+
**Prerequisites:**
|
|
35
|
+
1. Create a bot via [@BotFather](https://t.me/BotFather) on Telegram — takes 30 seconds
|
|
36
|
+
2. Create a private Telegram channel
|
|
37
|
+
3. Add your bot as an **Administrator** with full permissions to the channel
|
|
38
|
+
|
|
39
|
+
### 3. Usage
|
|
13
40
|
|
|
14
41
|
```ts
|
|
15
42
|
import { createClient } from 'gramobase';
|
|
16
43
|
import { z } from 'zod';
|
|
17
44
|
|
|
18
45
|
const db = await createClient({
|
|
19
|
-
botToken: process.env.
|
|
20
|
-
channelId: process.env.
|
|
46
|
+
botToken: process.env.GRAMOBASE_BOT_TOKEN_1!,
|
|
47
|
+
channelId: process.env.GRAMOBASE_CHANNEL_ID!,
|
|
21
48
|
}).connect();
|
|
22
49
|
|
|
23
50
|
const users = db.collection('users', {
|
|
@@ -28,6 +55,44 @@ await users.insertOne({ name: 'Aarav', email: 'aarav@example.com' });
|
|
|
28
55
|
const user = await users.findOne({ name: { $eq: 'Aarav' } });
|
|
29
56
|
```
|
|
30
57
|
|
|
58
|
+
> [!IMPORTANT]
|
|
59
|
+
> **Next.js & Hot-Reloading Environments:**
|
|
60
|
+
> In development environments that support hot-reloading (like Next.js), the module cache is frequently cleared, which can instantiate multiple database clients and trigger write lease lock collisions. To prevent this, choose one of the following methods to cache your client:
|
|
61
|
+
>
|
|
62
|
+
> **Option A: Built-in `global` config option (Recommended)**
|
|
63
|
+
> Pass `global: true` in your client config to automatically handle global caching inside the package:
|
|
64
|
+
>
|
|
65
|
+
> ```ts
|
|
66
|
+
> import { createClient } from 'gramobase';
|
|
67
|
+
>
|
|
68
|
+
> const client = createClient({
|
|
69
|
+
> botToken: process.env.GRAMOBASE_BOT_TOKEN_1!,
|
|
70
|
+
> channelId: process.env.GRAMOBASE_CHANNEL_ID!,
|
|
71
|
+
> global: true, // Automatically caches the client instance globally
|
|
72
|
+
> });
|
|
73
|
+
> const db = await client.connect();
|
|
74
|
+
> ```
|
|
75
|
+
>
|
|
76
|
+
> **Option B: Manual `globalThis` caching**
|
|
77
|
+
> Alternatively, you can manage caching manually on the global scope:
|
|
78
|
+
>
|
|
79
|
+
> ```ts
|
|
80
|
+
> import { createClient } from 'gramobase';
|
|
81
|
+
>
|
|
82
|
+
> const globalForDb = globalThis as unknown as { dbClient: any };
|
|
83
|
+
>
|
|
84
|
+
> export async function getDb() {
|
|
85
|
+
> if (!globalForDb.dbClient) {
|
|
86
|
+
> const client = createClient({
|
|
87
|
+
> botToken: process.env.GRAMOBASE_BOT_TOKEN_1!,
|
|
88
|
+
> channelId: process.env.GRAMOBASE_CHANNEL_ID!,
|
|
89
|
+
> });
|
|
90
|
+
> globalForDb.dbClient = await client.connect();
|
|
91
|
+
> }
|
|
92
|
+
> return globalForDb.dbClient;
|
|
93
|
+
> }
|
|
94
|
+
> ```
|
|
95
|
+
|
|
31
96
|
---
|
|
32
97
|
|
|
33
98
|
## Why gramobase?
|
|
@@ -45,12 +110,6 @@ const user = await users.findOne({ name: { $eq: 'Aarav' } });
|
|
|
45
110
|
|
|
46
111
|
---
|
|
47
112
|
|
|
48
|
-
## Installation
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npm install gramobase
|
|
52
|
-
```
|
|
53
|
-
|
|
54
113
|
### Running Tests
|
|
55
114
|
|
|
56
115
|
To run the suite of 40 unit tests checking the ORM, caching, queue/worker pooling, and authentication:
|
|
@@ -59,20 +118,6 @@ To run the suite of 40 unit tests checking the ORM, caching, queue/worker poolin
|
|
|
59
118
|
npm run test
|
|
60
119
|
```
|
|
61
120
|
|
|
62
|
-
### Setup
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
npx gramobase init
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
This interactive wizard walks you through setting up your backend.
|
|
69
|
-
It features **Auto-Detect** technology and **Anti-Flood** scaling: it will ask you how many Bot Tokens you want to configure (for 30 req/s scaling per bot). Provide your tokens, and leave the Channel ID blank! By sending a message in your channel, `gramobase` will automatically fetch your hidden Telegram Channel ID for you and generate your `.env` and `gramobase.config.ts`.
|
|
70
|
-
|
|
71
|
-
**Prerequisites:**
|
|
72
|
-
1. Create a bot via [@BotFather](https://t.me/BotFather) on Telegram — takes 30 seconds
|
|
73
|
-
2. Create a private Telegram channel
|
|
74
|
-
3. Add your bot as an **Administrator** with full permissions to the channel
|
|
75
|
-
|
|
76
121
|
---
|
|
77
122
|
|
|
78
123
|
## Core API
|
|
@@ -274,17 +319,17 @@ Developer API (ORM, Auth, Files, Realtime)
|
|
|
274
319
|
│
|
|
275
320
|
Telegram Bot API ─────────────────────────────────┐
|
|
276
321
|
│ │
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
322
|
+
Private Channel File Storage Realtime
|
|
323
|
+
(messages = docs, (sendDocument, (webhook +
|
|
324
|
+
pinned = registry) file_id refs) SSE bridge)
|
|
280
325
|
```
|
|
281
326
|
|
|
282
327
|
### Storage model
|
|
283
328
|
|
|
284
329
|
- Each collection maps to a private Telegram channel (or shares one via namespaced message tags)
|
|
285
|
-
- A **pinned
|
|
330
|
+
- A **pinned registry message** acts as a distributed write lock across processes and stores the master index mapping of collection names to their respective index message IDs (`{ collectionName → indexMsgId }`)
|
|
331
|
+
- Individual **index messages** (unpinned) store `{ id → msgId }` for O(1) document lookups
|
|
286
332
|
- The **Write-Ahead Log** channel stores operation logs for crash recovery
|
|
287
|
-
- A **registry message** acts as a distributed write lock across processes
|
|
288
333
|
|
|
289
334
|
### Limits
|
|
290
335
|
|
|
@@ -308,11 +353,27 @@ npx gramobase migrate # run pending migrations
|
|
|
308
353
|
npx gramobase migrate --rollback 1 # rollback last migration
|
|
309
354
|
npx gramobase migrate --status # show migration history
|
|
310
355
|
npx gramobase generate post --fields "title:string,views:number"
|
|
311
|
-
npx gramobase studio # open browser UI (
|
|
356
|
+
npx gramobase studio # open browser UI (see below)
|
|
357
|
+
npx gramobase studio --port 9000 # custom port
|
|
312
358
|
```
|
|
313
359
|
|
|
314
360
|
---
|
|
315
361
|
|
|
362
|
+
## gramobase Studio
|
|
363
|
+
|
|
364
|
+
`npx gramobase studio` spins up a local browser admin panel at **http://localhost:4242**. Zero extra dependencies — it reads your `.env` and starts a Node HTTP server backed by a real live `GramoBase` client.
|
|
365
|
+
|
|
366
|
+
**Features:**
|
|
367
|
+
- 🔍 **Collection Browser** — Paginated table view of every document in any collection
|
|
368
|
+
- 📋 **Sortable Columns** — Click any column header to sort ASC/DESC
|
|
369
|
+
- 🔎 **Filter Bar** — Filter with `field:value` syntax or free-text regex search
|
|
370
|
+
- 📄 **JSON Inspector** — Click any row to open a full syntax-highlighted document drawer
|
|
371
|
+
- 📡 **Realtime Feed** — Live SSE stream of all insert/update/delete/WAL events
|
|
372
|
+
- ⚡ **Stats Dashboard** — Cache hit rate, bytes used, worker pool status, token count
|
|
373
|
+
- 🤖 **Bot Info Panel** — Bot username, channel ID, token pool capacity
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
316
377
|
## Configuration
|
|
317
378
|
|
|
318
379
|
```ts
|
|
@@ -327,6 +388,7 @@ const db = createClient({
|
|
|
327
388
|
concurrency?: number, // max concurrent requests per token, default 25
|
|
328
389
|
webhookUrl?: string, // enables webhook mode for realtime
|
|
329
390
|
debug?: boolean,
|
|
391
|
+
global?: boolean, // auto-cache client globally (default: false)
|
|
330
392
|
});
|
|
331
393
|
```
|
|
332
394
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import TelegramBot from 'node-telegram-bot-api';
|
|
3
2
|
import EventEmitter from 'eventemitter3';
|
|
4
3
|
|
|
@@ -11,7 +10,11 @@ interface GramoBaseDocument {
|
|
|
11
10
|
[key: string]: unknown;
|
|
12
11
|
}
|
|
13
12
|
type WithId<T> = T & GramoBaseDocument;
|
|
14
|
-
interface
|
|
13
|
+
interface SchemaLike<Output = any> {
|
|
14
|
+
parse(data: unknown): Output;
|
|
15
|
+
}
|
|
16
|
+
type InferSchema<T extends SchemaLike> = ReturnType<T['parse']>;
|
|
17
|
+
interface CollectionConfig<T extends SchemaLike> {
|
|
15
18
|
schema: T;
|
|
16
19
|
/** Channel override — uses the default if omitted */
|
|
17
20
|
channelId?: string | undefined;
|
|
@@ -157,6 +160,8 @@ interface GramoBaseConfig {
|
|
|
157
160
|
webhookUrl?: string | undefined;
|
|
158
161
|
/** Enable verbose debug logging */
|
|
159
162
|
debug?: boolean | undefined;
|
|
163
|
+
/** Auto-cache client globally on globalThis in dev mode to prevent lease collisions in serverless/hot-reloading environments */
|
|
164
|
+
global?: boolean | undefined;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
interface WorkerStats {
|
|
@@ -198,4 +203,4 @@ declare class BotWorkerPool extends EventEmitter {
|
|
|
198
203
|
destroy(): Promise<void>;
|
|
199
204
|
}
|
|
200
205
|
|
|
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
|
|
206
|
+
export { type AuthConfig as A, BotWorkerPool as B, type CollectionConfig as C, type FileRecord as F, type GramoBaseEvent as G, type InferSchema as I, type Lease as L, type Migration as M, type SchemaLike 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 Session as f, type UpdateOperators as g, type User as h, type WalEntry as i, type WalOpType as j, type WithId as k };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import TelegramBot from 'node-telegram-bot-api';
|
|
3
2
|
import EventEmitter from 'eventemitter3';
|
|
4
3
|
|
|
@@ -11,7 +10,11 @@ interface GramoBaseDocument {
|
|
|
11
10
|
[key: string]: unknown;
|
|
12
11
|
}
|
|
13
12
|
type WithId<T> = T & GramoBaseDocument;
|
|
14
|
-
interface
|
|
13
|
+
interface SchemaLike<Output = any> {
|
|
14
|
+
parse(data: unknown): Output;
|
|
15
|
+
}
|
|
16
|
+
type InferSchema<T extends SchemaLike> = ReturnType<T['parse']>;
|
|
17
|
+
interface CollectionConfig<T extends SchemaLike> {
|
|
15
18
|
schema: T;
|
|
16
19
|
/** Channel override — uses the default if omitted */
|
|
17
20
|
channelId?: string | undefined;
|
|
@@ -157,6 +160,8 @@ interface GramoBaseConfig {
|
|
|
157
160
|
webhookUrl?: string | undefined;
|
|
158
161
|
/** Enable verbose debug logging */
|
|
159
162
|
debug?: boolean | undefined;
|
|
163
|
+
/** Auto-cache client globally on globalThis in dev mode to prevent lease collisions in serverless/hot-reloading environments */
|
|
164
|
+
global?: boolean | undefined;
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
interface WorkerStats {
|
|
@@ -198,4 +203,4 @@ declare class BotWorkerPool extends EventEmitter {
|
|
|
198
203
|
destroy(): Promise<void>;
|
|
199
204
|
}
|
|
200
205
|
|
|
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
|
|
206
|
+
export { type AuthConfig as A, BotWorkerPool as B, type CollectionConfig as C, type FileRecord as F, type GramoBaseEvent as G, type InferSchema as I, type Lease as L, type Migration as M, type SchemaLike 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 Session as f, type UpdateOperators as g, type User as h, type WalEntry as i, type WalOpType as j, type WithId as k };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { B as BotWorkerPool, e as GramoBaseDocument,
|
|
2
|
+
import { B as BotWorkerPool, L as Lease, e as GramoBaseDocument, j as WalOpType, i as WalEntry, S as SchemaLike, C as CollectionConfig, I as InferSchema, k as WithId, c as Filter, d as FindOptions, g as UpdateOperators, A as AuthConfig, h as User, f as Session } from './BotWorkerPool-h_8a20dt.js';
|
|
3
3
|
import EventEmitter from 'eventemitter3';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -41,6 +41,41 @@ declare class HotCache extends EventEmitter {
|
|
|
41
41
|
private docKey;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Registry uses a pinned Telegram message as a distributed lock.
|
|
46
|
+
*
|
|
47
|
+
* When a gramobase instance starts up, it reads the registry message.
|
|
48
|
+
* If no lease exists or the existing lease is expired, it writes a new
|
|
49
|
+
* lease with its own instanceId and begins sending heartbeats.
|
|
50
|
+
*
|
|
51
|
+
* This prevents multiple writer processes from corrupting the index
|
|
52
|
+
* (last-write-wins races on the pinned index message).
|
|
53
|
+
*
|
|
54
|
+
* Read-only operations are always permitted. Only index mutations
|
|
55
|
+
* require holding the write lease.
|
|
56
|
+
*/
|
|
57
|
+
declare class Registry {
|
|
58
|
+
private pool;
|
|
59
|
+
private channelId;
|
|
60
|
+
private debug;
|
|
61
|
+
private state;
|
|
62
|
+
private readonly instanceId;
|
|
63
|
+
constructor(pool: BotWorkerPool, channelId: string, debug?: boolean);
|
|
64
|
+
acquireWriteLease(options?: {
|
|
65
|
+
wait?: boolean;
|
|
66
|
+
}): Promise<Lease>;
|
|
67
|
+
releaseWriteLease(): Promise<void>;
|
|
68
|
+
forceRelease(): Promise<void>;
|
|
69
|
+
isWriteLeaseHeld(): Promise<boolean>;
|
|
70
|
+
private heartbeat;
|
|
71
|
+
private readRegistryMessage;
|
|
72
|
+
private writeRegistryMessage;
|
|
73
|
+
getCollectionIndexMsgId(collection: string): Promise<number | null>;
|
|
74
|
+
setCollectionIndexMsgId(collection: string, msgId: number): Promise<void>;
|
|
75
|
+
getInstanceId(): string;
|
|
76
|
+
getCurrentLease(): Lease | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
44
79
|
interface IndexMessage {
|
|
45
80
|
collection: string;
|
|
46
81
|
entries: Record<string, number>;
|
|
@@ -60,10 +95,12 @@ interface IndexMessage {
|
|
|
60
95
|
declare class TelegramStorage {
|
|
61
96
|
private pool;
|
|
62
97
|
private defaultChannelId;
|
|
98
|
+
private registry;
|
|
63
99
|
private debug;
|
|
64
100
|
private encryptionKey;
|
|
65
101
|
private indexMsgIds;
|
|
66
|
-
constructor(pool: BotWorkerPool, defaultChannelId: string, encryptionKey?: string, debug?: boolean);
|
|
102
|
+
constructor(pool: BotWorkerPool, defaultChannelId: string, registry: Registry, encryptionKey?: string, debug?: boolean);
|
|
103
|
+
private readRawMessageText;
|
|
67
104
|
loadIndex(collection: string, channelId?: string): Promise<IndexMessage>;
|
|
68
105
|
saveIndex(index: IndexMessage, channelId?: string): Promise<void>;
|
|
69
106
|
writeDocument(doc: GramoBaseDocument, channelId?: string): Promise<number>;
|
|
@@ -122,7 +159,7 @@ declare class WriteAheadLog {
|
|
|
122
159
|
getCurrentSeq(): number;
|
|
123
160
|
}
|
|
124
161
|
|
|
125
|
-
type DocOf<T extends
|
|
162
|
+
type DocOf<T extends SchemaLike> = WithId<InferSchema<T>>;
|
|
126
163
|
/**
|
|
127
164
|
* Collection<T> is the main ORM interface.
|
|
128
165
|
*
|
|
@@ -133,7 +170,7 @@ type DocOf<T extends z.ZodType> = WithId<z.infer<T>>;
|
|
|
133
170
|
* - Index management (id → msgId map, stored as a pinned message)
|
|
134
171
|
* - Filter/sort/skip/limit in memory after cache warm-up
|
|
135
172
|
*/
|
|
136
|
-
declare class Collection<T extends
|
|
173
|
+
declare class Collection<T extends SchemaLike> {
|
|
137
174
|
private name;
|
|
138
175
|
private config;
|
|
139
176
|
private cache;
|
|
@@ -143,18 +180,18 @@ declare class Collection<T extends z.ZodType> {
|
|
|
143
180
|
private indexLoaded;
|
|
144
181
|
constructor(name: string, config: CollectionConfig<T>, cache: HotCache, storage: TelegramStorage, wal: WriteAheadLog, defaultChannelId: string);
|
|
145
182
|
ensureIndexLoaded(): Promise<void>;
|
|
146
|
-
insertOne(data:
|
|
147
|
-
insertMany(items:
|
|
183
|
+
insertOne(data: InferSchema<T>): Promise<DocOf<T>>;
|
|
184
|
+
insertMany(items: InferSchema<T>[]): Promise<DocOf<T>[]>;
|
|
148
185
|
findById(id: string): Promise<DocOf<T> | null>;
|
|
149
|
-
findOne(filter?: Filter<
|
|
150
|
-
find(options?: FindOptions<
|
|
151
|
-
count(filter?: Filter<
|
|
152
|
-
updateOne(filter: Filter<
|
|
153
|
-
updateMany(filter: Filter<
|
|
154
|
-
findByIdAndUpdate(id: string, update: UpdateOperators<
|
|
186
|
+
findOne(filter?: Filter<InferSchema<T>>): Promise<DocOf<T> | null>;
|
|
187
|
+
find(options?: FindOptions<InferSchema<T>>): Promise<DocOf<T>[]>;
|
|
188
|
+
count(filter?: Filter<InferSchema<T>>): Promise<number>;
|
|
189
|
+
updateOne(filter: Filter<InferSchema<T>>, update: UpdateOperators<InferSchema<T>>): Promise<DocOf<T> | null>;
|
|
190
|
+
updateMany(filter: Filter<InferSchema<T>>, update: UpdateOperators<InferSchema<T>>): Promise<DocOf<T>[]>;
|
|
191
|
+
findByIdAndUpdate(id: string, update: UpdateOperators<InferSchema<T>>): Promise<DocOf<T> | null>;
|
|
155
192
|
private applyUpdate;
|
|
156
|
-
deleteOne(filter: Filter<
|
|
157
|
-
deleteMany(filter: Filter<
|
|
193
|
+
deleteOne(filter: Filter<InferSchema<T>>): Promise<boolean>;
|
|
194
|
+
deleteMany(filter: Filter<InferSchema<T>>): Promise<number>;
|
|
158
195
|
deleteById(id: string): Promise<boolean>;
|
|
159
196
|
private flushIndex;
|
|
160
197
|
private matchesFilter;
|
|
@@ -215,4 +252,4 @@ declare class GramoBaseAuth {
|
|
|
215
252
|
requireRoleMiddleware(role: string): (req: any, res: any, next: any) => void;
|
|
216
253
|
}
|
|
217
254
|
|
|
218
|
-
export { Collection as C, GramoBaseAuth as G };
|
|
255
|
+
export { Collection as C, GramoBaseAuth as G, Registry as R };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { B as BotWorkerPool, e as GramoBaseDocument,
|
|
2
|
+
import { B as BotWorkerPool, L as Lease, e as GramoBaseDocument, j as WalOpType, i as WalEntry, S as SchemaLike, C as CollectionConfig, I as InferSchema, k as WithId, c as Filter, d as FindOptions, g as UpdateOperators, A as AuthConfig, h as User, f as Session } from './BotWorkerPool-h_8a20dt.cjs';
|
|
3
3
|
import EventEmitter from 'eventemitter3';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -41,6 +41,41 @@ declare class HotCache extends EventEmitter {
|
|
|
41
41
|
private docKey;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Registry uses a pinned Telegram message as a distributed lock.
|
|
46
|
+
*
|
|
47
|
+
* When a gramobase instance starts up, it reads the registry message.
|
|
48
|
+
* If no lease exists or the existing lease is expired, it writes a new
|
|
49
|
+
* lease with its own instanceId and begins sending heartbeats.
|
|
50
|
+
*
|
|
51
|
+
* This prevents multiple writer processes from corrupting the index
|
|
52
|
+
* (last-write-wins races on the pinned index message).
|
|
53
|
+
*
|
|
54
|
+
* Read-only operations are always permitted. Only index mutations
|
|
55
|
+
* require holding the write lease.
|
|
56
|
+
*/
|
|
57
|
+
declare class Registry {
|
|
58
|
+
private pool;
|
|
59
|
+
private channelId;
|
|
60
|
+
private debug;
|
|
61
|
+
private state;
|
|
62
|
+
private readonly instanceId;
|
|
63
|
+
constructor(pool: BotWorkerPool, channelId: string, debug?: boolean);
|
|
64
|
+
acquireWriteLease(options?: {
|
|
65
|
+
wait?: boolean;
|
|
66
|
+
}): Promise<Lease>;
|
|
67
|
+
releaseWriteLease(): Promise<void>;
|
|
68
|
+
forceRelease(): Promise<void>;
|
|
69
|
+
isWriteLeaseHeld(): Promise<boolean>;
|
|
70
|
+
private heartbeat;
|
|
71
|
+
private readRegistryMessage;
|
|
72
|
+
private writeRegistryMessage;
|
|
73
|
+
getCollectionIndexMsgId(collection: string): Promise<number | null>;
|
|
74
|
+
setCollectionIndexMsgId(collection: string, msgId: number): Promise<void>;
|
|
75
|
+
getInstanceId(): string;
|
|
76
|
+
getCurrentLease(): Lease | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
44
79
|
interface IndexMessage {
|
|
45
80
|
collection: string;
|
|
46
81
|
entries: Record<string, number>;
|
|
@@ -60,10 +95,12 @@ interface IndexMessage {
|
|
|
60
95
|
declare class TelegramStorage {
|
|
61
96
|
private pool;
|
|
62
97
|
private defaultChannelId;
|
|
98
|
+
private registry;
|
|
63
99
|
private debug;
|
|
64
100
|
private encryptionKey;
|
|
65
101
|
private indexMsgIds;
|
|
66
|
-
constructor(pool: BotWorkerPool, defaultChannelId: string, encryptionKey?: string, debug?: boolean);
|
|
102
|
+
constructor(pool: BotWorkerPool, defaultChannelId: string, registry: Registry, encryptionKey?: string, debug?: boolean);
|
|
103
|
+
private readRawMessageText;
|
|
67
104
|
loadIndex(collection: string, channelId?: string): Promise<IndexMessage>;
|
|
68
105
|
saveIndex(index: IndexMessage, channelId?: string): Promise<void>;
|
|
69
106
|
writeDocument(doc: GramoBaseDocument, channelId?: string): Promise<number>;
|
|
@@ -122,7 +159,7 @@ declare class WriteAheadLog {
|
|
|
122
159
|
getCurrentSeq(): number;
|
|
123
160
|
}
|
|
124
161
|
|
|
125
|
-
type DocOf<T extends
|
|
162
|
+
type DocOf<T extends SchemaLike> = WithId<InferSchema<T>>;
|
|
126
163
|
/**
|
|
127
164
|
* Collection<T> is the main ORM interface.
|
|
128
165
|
*
|
|
@@ -133,7 +170,7 @@ type DocOf<T extends z.ZodType> = WithId<z.infer<T>>;
|
|
|
133
170
|
* - Index management (id → msgId map, stored as a pinned message)
|
|
134
171
|
* - Filter/sort/skip/limit in memory after cache warm-up
|
|
135
172
|
*/
|
|
136
|
-
declare class Collection<T extends
|
|
173
|
+
declare class Collection<T extends SchemaLike> {
|
|
137
174
|
private name;
|
|
138
175
|
private config;
|
|
139
176
|
private cache;
|
|
@@ -143,18 +180,18 @@ declare class Collection<T extends z.ZodType> {
|
|
|
143
180
|
private indexLoaded;
|
|
144
181
|
constructor(name: string, config: CollectionConfig<T>, cache: HotCache, storage: TelegramStorage, wal: WriteAheadLog, defaultChannelId: string);
|
|
145
182
|
ensureIndexLoaded(): Promise<void>;
|
|
146
|
-
insertOne(data:
|
|
147
|
-
insertMany(items:
|
|
183
|
+
insertOne(data: InferSchema<T>): Promise<DocOf<T>>;
|
|
184
|
+
insertMany(items: InferSchema<T>[]): Promise<DocOf<T>[]>;
|
|
148
185
|
findById(id: string): Promise<DocOf<T> | null>;
|
|
149
|
-
findOne(filter?: Filter<
|
|
150
|
-
find(options?: FindOptions<
|
|
151
|
-
count(filter?: Filter<
|
|
152
|
-
updateOne(filter: Filter<
|
|
153
|
-
updateMany(filter: Filter<
|
|
154
|
-
findByIdAndUpdate(id: string, update: UpdateOperators<
|
|
186
|
+
findOne(filter?: Filter<InferSchema<T>>): Promise<DocOf<T> | null>;
|
|
187
|
+
find(options?: FindOptions<InferSchema<T>>): Promise<DocOf<T>[]>;
|
|
188
|
+
count(filter?: Filter<InferSchema<T>>): Promise<number>;
|
|
189
|
+
updateOne(filter: Filter<InferSchema<T>>, update: UpdateOperators<InferSchema<T>>): Promise<DocOf<T> | null>;
|
|
190
|
+
updateMany(filter: Filter<InferSchema<T>>, update: UpdateOperators<InferSchema<T>>): Promise<DocOf<T>[]>;
|
|
191
|
+
findByIdAndUpdate(id: string, update: UpdateOperators<InferSchema<T>>): Promise<DocOf<T> | null>;
|
|
155
192
|
private applyUpdate;
|
|
156
|
-
deleteOne(filter: Filter<
|
|
157
|
-
deleteMany(filter: Filter<
|
|
193
|
+
deleteOne(filter: Filter<InferSchema<T>>): Promise<boolean>;
|
|
194
|
+
deleteMany(filter: Filter<InferSchema<T>>): Promise<number>;
|
|
158
195
|
deleteById(id: string): Promise<boolean>;
|
|
159
196
|
private flushIndex;
|
|
160
197
|
private matchesFilter;
|
|
@@ -215,4 +252,4 @@ declare class GramoBaseAuth {
|
|
|
215
252
|
requireRoleMiddleware(role: string): (req: any, res: any, next: any) => void;
|
|
216
253
|
}
|
|
217
254
|
|
|
218
|
-
export { Collection as C, GramoBaseAuth as G };
|
|
255
|
+
export { Collection as C, GramoBaseAuth as G, Registry as R };
|
package/dist/auth/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import 'zod';
|
|
2
|
-
import '../BotWorkerPool-
|
|
3
|
-
export { G as GramoBaseAuth } from '../GramoBaseAuth-
|
|
2
|
+
import '../BotWorkerPool-h_8a20dt.cjs';
|
|
3
|
+
export { G as GramoBaseAuth } from '../GramoBaseAuth-ObeOxqKj.cjs';
|
|
4
4
|
import 'node-telegram-bot-api';
|
|
5
5
|
import 'eventemitter3';
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import 'zod';
|
|
2
|
-
import '../BotWorkerPool-
|
|
3
|
-
export { G as GramoBaseAuth } from '../GramoBaseAuth-
|
|
2
|
+
import '../BotWorkerPool-h_8a20dt.js';
|
|
3
|
+
export { G as GramoBaseAuth } from '../GramoBaseAuth-DfRKq2yW.js';
|
|
4
4
|
import 'node-telegram-bot-api';
|
|
5
5
|
import 'eventemitter3';
|
package/dist/bin/gramobase.cjs
CHANGED
|
@@ -154,9 +154,8 @@ export default config;
|
|
|
154
154
|
${chalk__default.default.gray("\u2514\u2500")} gramobase/migrations/
|
|
155
155
|
|
|
156
156
|
${chalk__default.default.bold("Next steps:")}
|
|
157
|
-
${chalk__default.default.cyan("1.")}
|
|
158
|
-
${chalk__default.default.cyan("2.")}
|
|
159
|
-
${chalk__default.default.cyan("3.")} Import and use: ${chalk__default.default.gray("import { createClient } from 'gramobase'")}
|
|
157
|
+
${chalk__default.default.cyan("1.")} Run ${chalk__default.default.bold("npx gramobase migrate")} to initialize the database
|
|
158
|
+
${chalk__default.default.cyan("2.")} Import and use: ${chalk__default.default.gray("import { createClient } from 'gramobase'")}
|
|
160
159
|
`);
|
|
161
160
|
});
|
|
162
161
|
program.command("migrate").description("Run pending migrations").option("--rollback <steps>", "Rollback N migration steps", "0").option("--status", "Show migration status").action(async (opts) => {
|
|
@@ -229,18 +228,25 @@ export type ${capitalize(safeName)} = z.infer<typeof ${safeName}Schema>;
|
|
|
229
228
|
${chalk__default.default.green("\u2713")} Generated ${chalk__default.default.cyan(`gramobase/${safeName}.schema.ts`)}
|
|
230
229
|
`);
|
|
231
230
|
});
|
|
232
|
-
program.command("studio").description("Open the gramobase browser studio UI").option("--port <port>", "Port to listen on", "4242").action((opts) => {
|
|
231
|
+
program.command("studio").description("Open the gramobase browser studio UI").option("--port <port>", "Port to listen on", "4242").action(async (opts) => {
|
|
233
232
|
const port = parseInt(opts.port, 10);
|
|
234
233
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
235
234
|
console.error(chalk__default.default.red(" Error: Invalid port number"));
|
|
236
235
|
process.exit(1);
|
|
237
236
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
const spinner = ora__default.default("Starting gramobase studio...").start();
|
|
238
|
+
try {
|
|
239
|
+
const { startStudio } = await import(new URL("../../dist/studio/server.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('gramobase.cjs', document.baseURI).href))).href);
|
|
240
|
+
await startStudio(port, process.cwd());
|
|
241
|
+
spinner.succeed(chalk__default.default.green("gramobase studio is running!"));
|
|
242
|
+
console.log(`
|
|
243
|
+
${chalk__default.default.bold("Studio")} ${chalk__default.default.cyan(`http://localhost:${port}`)}
|
|
244
|
+
${chalk__default.default.gray("Press Ctrl+C to stop.")}
|
|
242
245
|
`);
|
|
243
|
-
|
|
246
|
+
} catch (e) {
|
|
247
|
+
spinner.fail(chalk__default.default.red("Failed to start studio: " + (e?.message || String(e))));
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
244
250
|
});
|
|
245
251
|
function capitalize(s) {
|
|
246
252
|
return s.charAt(0).toUpperCase() + s.slice(1);
|