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,167 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var commander = require('commander');
|
|
5
|
+
var chalk = require('chalk');
|
|
6
|
+
var ora = require('ora');
|
|
7
|
+
var readline = require('readline');
|
|
8
|
+
var fs = require('fs');
|
|
9
|
+
var path = require('path');
|
|
10
|
+
|
|
11
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
var chalk__default = /*#__PURE__*/_interopDefault(chalk);
|
|
14
|
+
var ora__default = /*#__PURE__*/_interopDefault(ora);
|
|
15
|
+
|
|
16
|
+
// gramobase — Telegram as your free, infinite backend
|
|
17
|
+
|
|
18
|
+
var pkg = { version: "0.1.0" };
|
|
19
|
+
var program = new commander.Command();
|
|
20
|
+
program.name("gramobase").description(chalk__default.default.cyan("Telegram as your free, infinite backend database")).version(pkg.version);
|
|
21
|
+
program.command("init").description("Initialize a new gramobase project").option("--yes", "Skip prompts, use defaults").action(async (opts) => {
|
|
22
|
+
console.log("\n" + chalk__default.default.bold.cyan(" gramobase") + chalk__default.default.gray(" \u2014 Telegram backend\n"));
|
|
23
|
+
let botToken = "";
|
|
24
|
+
let channelId = "";
|
|
25
|
+
let encryptionKey = "";
|
|
26
|
+
if (!opts.yes) {
|
|
27
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
28
|
+
const ask = (q) => new Promise((res) => rl.question(q, res));
|
|
29
|
+
console.log(chalk__default.default.gray(" Get a bot token from @BotFather on Telegram\n"));
|
|
30
|
+
botToken = await ask(chalk__default.default.white(" Bot token: "));
|
|
31
|
+
channelId = await ask(chalk__default.default.white(" Channel ID (e.g. -100123456789): "));
|
|
32
|
+
encryptionKey = await ask(chalk__default.default.white(" Encryption key (optional, press enter to skip): "));
|
|
33
|
+
rl.close();
|
|
34
|
+
}
|
|
35
|
+
const spinner = ora__default.default("Setting up gramobase...").start();
|
|
36
|
+
const safeToken = botToken.trim();
|
|
37
|
+
const safeChannelId = channelId.trim();
|
|
38
|
+
const safeKey = encryptionKey.trim();
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const envPath = path.join(cwd, ".env");
|
|
41
|
+
const envContent = [
|
|
42
|
+
`GRAMOBASE_BOT_TOKEN=${safeToken}`,
|
|
43
|
+
`GRAMOBASE_CHANNEL_ID=${safeChannelId}`,
|
|
44
|
+
safeKey ? `GRAMOBASE_ENCRYPTION_KEY=${safeKey}` : "# GRAMOBASE_ENCRYPTION_KEY="
|
|
45
|
+
].join("\n");
|
|
46
|
+
fs.writeFileSync(envPath, envContent + "\n");
|
|
47
|
+
const configContent = `import { GramoBaseConfig } from 'gramobase';
|
|
48
|
+
|
|
49
|
+
const config: GramoBaseConfig = {
|
|
50
|
+
botToken: process.env.GRAMOBASE_BOT_TOKEN!,
|
|
51
|
+
channelId: process.env.GRAMOBASE_CHANNEL_ID!,
|
|
52
|
+
// encryptionKey: process.env.GRAMOBASE_ENCRYPTION_KEY,
|
|
53
|
+
cacheMaxBytes: 64 * 1024 * 1024, // 64MB hot cache
|
|
54
|
+
cacheTtlMs: 60_000,
|
|
55
|
+
concurrency: 25,
|
|
56
|
+
debug: process.env.NODE_ENV === 'development',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default config;
|
|
60
|
+
`;
|
|
61
|
+
fs.writeFileSync(path.join(cwd, "gramobase.config.ts"), configContent);
|
|
62
|
+
const migrationsDir = path.join(cwd, "gramobase", "migrations");
|
|
63
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
64
|
+
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
spinner.succeed(chalk__default.default.green("gramobase initialized!"));
|
|
67
|
+
console.log(`
|
|
68
|
+
${chalk__default.default.bold("Files created:")}
|
|
69
|
+
${chalk__default.default.gray("\u251C\u2500")} .env
|
|
70
|
+
${chalk__default.default.gray("\u2514\u2500")} gramobase.config.ts
|
|
71
|
+
${chalk__default.default.gray("\u2514\u2500")} gramobase/migrations/
|
|
72
|
+
|
|
73
|
+
${chalk__default.default.bold("Next steps:")}
|
|
74
|
+
${chalk__default.default.cyan("1.")} Add your bot token and channel ID to .env
|
|
75
|
+
${chalk__default.default.cyan("2.")} Run ${chalk__default.default.bold("gramobase migrate")} to initialize the database
|
|
76
|
+
${chalk__default.default.cyan("3.")} Import and use: ${chalk__default.default.gray("import { createClient } from 'gramobase'")}
|
|
77
|
+
`);
|
|
78
|
+
});
|
|
79
|
+
program.command("migrate").description("Run pending migrations").option("--rollback <steps>", "Rollback N migration steps", "0").option("--status", "Show migration status").action(async (opts) => {
|
|
80
|
+
const spinner = ora__default.default("Loading migrations...").start();
|
|
81
|
+
try {
|
|
82
|
+
const configPath = path.join(process.cwd(), "gramobase.config.ts");
|
|
83
|
+
spinner.text = "Connecting...";
|
|
84
|
+
spinner.succeed("Migration runner ready (run in your project after build)");
|
|
85
|
+
} catch (e) {
|
|
86
|
+
spinner.fail(chalk__default.default.red("Failed: " + (e instanceof Error ? e.message : "Unknown error")));
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
program.command("status").description("Show database and connection status").action(async () => {
|
|
90
|
+
const spinner = ora__default.default("Checking status...").start();
|
|
91
|
+
try {
|
|
92
|
+
const token = process.env["GRAMOBASE_BOT_TOKEN"];
|
|
93
|
+
const channelId = process.env["GRAMOBASE_CHANNEL_ID"];
|
|
94
|
+
if (!token || !channelId) {
|
|
95
|
+
spinner.fail(".env not found \u2014 run gramobase init first");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const res = await fetch(`https://api.telegram.org/bot${encodeURIComponent(token)}/getMe`);
|
|
99
|
+
const json = await res.json();
|
|
100
|
+
if (json.ok) {
|
|
101
|
+
spinner.succeed(chalk__default.default.green("Connected"));
|
|
102
|
+
console.log(`
|
|
103
|
+
${chalk__default.default.bold("Bot:")} ${chalk__default.default.cyan("@" + json.result.username)} (${json.result.first_name})
|
|
104
|
+
${chalk__default.default.bold("Channel:")} ${chalk__default.default.cyan(channelId)}
|
|
105
|
+
${chalk__default.default.bold("Status:")} ${chalk__default.default.green("\u25CF Online")}
|
|
106
|
+
`);
|
|
107
|
+
} else {
|
|
108
|
+
spinner.fail(chalk__default.default.red("Bot API error \u2014 check your token"));
|
|
109
|
+
}
|
|
110
|
+
} catch (e) {
|
|
111
|
+
spinner.fail(chalk__default.default.red("Connection failed"));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
program.command("generate <name>").description("Generate a typed collection schema").option("--fields <fields>", "Comma-separated fields (e.g. name:string,age:number)").action((name, opts) => {
|
|
115
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
116
|
+
if (!safeName || safeName !== name) {
|
|
117
|
+
console.error(chalk__default.default.red(" Error: Schema name must contain only letters, numbers, underscores, and hyphens"));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
const fields = opts.fields ? opts.fields.split(",").map((f) => f.trim()) : ["name:string"];
|
|
121
|
+
const schemaFields = fields.map((f) => {
|
|
122
|
+
const [fname, ftype] = f.split(":");
|
|
123
|
+
const safeFname = (fname ?? "field").replace(/[^a-zA-Z0-9_]/g, "");
|
|
124
|
+
const zodType = ftype === "number" ? "z.number()" : ftype === "boolean" ? "z.boolean()" : ftype === "date" ? "z.string()" : "z.string()";
|
|
125
|
+
return ` ${safeFname}: ${zodType},`;
|
|
126
|
+
}).join("\n");
|
|
127
|
+
const output = `import { z } from 'zod';
|
|
128
|
+
import { createClient } from 'gramobase';
|
|
129
|
+
|
|
130
|
+
export const ${safeName}Schema = z.object({
|
|
131
|
+
${schemaFields}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export type ${capitalize(safeName)} = z.infer<typeof ${safeName}Schema>;
|
|
135
|
+
|
|
136
|
+
// Usage:
|
|
137
|
+
// const db = createClient(config);
|
|
138
|
+
// const ${safeName}s = db.collection('${safeName}s', { schema: ${safeName}Schema });
|
|
139
|
+
// await ${safeName}s.insertOne({ ${fields.map((f) => (f.split(":")[0] ?? "field").replace(/[^a-zA-Z0-9_]/g, "") + ": ...").join(", ")} });
|
|
140
|
+
`;
|
|
141
|
+
const dir = path.join(process.cwd(), "gramobase");
|
|
142
|
+
const outPath = path.join(dir, `${safeName}.schema.ts`);
|
|
143
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
144
|
+
fs.writeFileSync(outPath, output);
|
|
145
|
+
console.log(`
|
|
146
|
+
${chalk__default.default.green("\u2713")} Generated ${chalk__default.default.cyan(`gramobase/${safeName}.schema.ts`)}
|
|
147
|
+
`);
|
|
148
|
+
});
|
|
149
|
+
program.command("studio").description("Open the gramobase browser studio UI").option("--port <port>", "Port to listen on", "4242").action((opts) => {
|
|
150
|
+
const port = parseInt(opts.port, 10);
|
|
151
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
152
|
+
console.error(chalk__default.default.red(" Error: Invalid port number"));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
console.log(`
|
|
156
|
+
${chalk__default.default.bold.cyan("gramobase studio")}
|
|
157
|
+
`);
|
|
158
|
+
console.log(` ${chalk__default.default.gray("Open")} ${chalk__default.default.cyan(`http://localhost:${port}`)} ${chalk__default.default.gray("in your browser")}
|
|
159
|
+
`);
|
|
160
|
+
console.log(chalk__default.default.yellow(" Studio UI coming in v0.2.0 \u2014 contribute at github.com/yourusername/gramobase\n"));
|
|
161
|
+
});
|
|
162
|
+
function capitalize(s) {
|
|
163
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
164
|
+
}
|
|
165
|
+
program.parse();
|
|
166
|
+
//# sourceMappingURL=gramobase.cjs.map
|
|
167
|
+
//# sourceMappingURL=gramobase.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../bin/gramobase.ts"],"names":["Command","chalk","createInterface","ora","join","writeFileSync","existsSync","mkdirSync"],"mappings":"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","file":"gramobase.cjs","sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { createInterface } from 'readline';\nimport { writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join, resolve, basename } from 'path';\n\nconst pkg = { version: '0.1.0' };\n\nconst program = new Command();\n\nprogram\n .name('gramobase')\n .description(chalk.cyan('Telegram as your free, infinite backend database'))\n .version(pkg.version);\n\n// ─── gramobase init ──────────────────────────────────────────────────────────\n\nprogram\n .command('init')\n .description('Initialize a new gramobase project')\n .option('--yes', 'Skip prompts, use defaults')\n .action(async (opts) => {\n console.log('\\n' + chalk.bold.cyan(' gramobase') + chalk.gray(' — Telegram backend\\n'));\n\n let botToken = '';\n let channelId = '';\n let encryptionKey = '';\n\n if (!opts.yes) {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string) => new Promise<string>((res) => rl.question(q, res));\n\n console.log(chalk.gray(' Get a bot token from @BotFather on Telegram\\n'));\n botToken = await ask(chalk.white(' Bot token: '));\n channelId = await ask(chalk.white(' Channel ID (e.g. -100123456789): '));\n encryptionKey = await ask(chalk.white(' Encryption key (optional, press enter to skip): '));\n rl.close();\n }\n\n const spinner = ora('Setting up gramobase...').start();\n\n // Sanitize inputs — only use basename of any path-like values\n const safeToken = botToken.trim();\n const safeChannelId = channelId.trim();\n const safeKey = encryptionKey.trim();\n\n // Create .env — never write tokens to paths derived from user input\n const cwd = process.cwd();\n const envPath = join(cwd, '.env');\n const envContent = [\n `GRAMOBASE_BOT_TOKEN=${safeToken}`,\n `GRAMOBASE_CHANNEL_ID=${safeChannelId}`,\n safeKey ? `GRAMOBASE_ENCRYPTION_KEY=${safeKey}` : '# GRAMOBASE_ENCRYPTION_KEY=',\n ].join('\\n');\n\n writeFileSync(envPath, envContent + '\\n');\n\n // Create gramobase.config.ts\n const configContent = `import { GramoBaseConfig } from 'gramobase';\n\nconst config: GramoBaseConfig = {\n botToken: process.env.GRAMOBASE_BOT_TOKEN!,\n channelId: process.env.GRAMOBASE_CHANNEL_ID!,\n // encryptionKey: process.env.GRAMOBASE_ENCRYPTION_KEY,\n cacheMaxBytes: 64 * 1024 * 1024, // 64MB hot cache\n cacheTtlMs: 60_000,\n concurrency: 25,\n debug: process.env.NODE_ENV === 'development',\n};\n\nexport default config;\n`;\n\n writeFileSync(join(cwd, 'gramobase.config.ts'), configContent);\n\n // Create migrations folder — path is hardcoded, not from user input\n const migrationsDir = join(cwd, 'gramobase', 'migrations');\n if (!existsSync(migrationsDir)) {\n mkdirSync(migrationsDir, { recursive: true });\n }\n\n spinner.succeed(chalk.green('gramobase initialized!'));\n\n console.log(`\n ${chalk.bold('Files created:')}\n ${chalk.gray('├─')} .env\n ${chalk.gray('└─')} gramobase.config.ts\n ${chalk.gray('└─')} gramobase/migrations/\n\n ${chalk.bold('Next steps:')}\n ${chalk.cyan('1.')} Add your bot token and channel ID to .env\n ${chalk.cyan('2.')} Run ${chalk.bold('gramobase migrate')} to initialize the database\n ${chalk.cyan('3.')} Import and use: ${chalk.gray(\"import { createClient } from 'gramobase'\")}\n`);\n });\n\n// ─── gramobase migrate ───────────────────────────────────────────────────────\n\nprogram\n .command('migrate')\n .description('Run pending migrations')\n .option('--rollback <steps>', 'Rollback N migration steps', '0')\n .option('--status', 'Show migration status')\n .action(async (opts) => {\n const spinner = ora('Loading migrations...').start();\n try {\n const configPath = join(process.cwd(), 'gramobase.config.ts');\n\n spinner.text = 'Connecting...';\n spinner.succeed('Migration runner ready (run in your project after build)');\n } catch (e: any) {\n spinner.fail(chalk.red('Failed: ' + (e instanceof Error ? e.message : 'Unknown error')));\n }\n });\n\n// ─── gramobase status ────────────────────────────────────────────────────────\n\nprogram\n .command('status')\n .description('Show database and connection status')\n .action(async () => {\n const spinner = ora('Checking status...').start();\n try {\n const token = process.env['GRAMOBASE_BOT_TOKEN'];\n const channelId = process.env['GRAMOBASE_CHANNEL_ID'];\n\n if (!token || !channelId) {\n spinner.fail('.env not found — run gramobase init first');\n return;\n }\n\n // Ping Telegram Bot API — token is from env, not user input in this context\n const res = await fetch(`https://api.telegram.org/bot${encodeURIComponent(token)}/getMe`);\n const json = await res.json() as any;\n\n if (json.ok) {\n spinner.succeed(chalk.green('Connected'));\n console.log(`\n ${chalk.bold('Bot:')} ${chalk.cyan('@' + json.result.username)} (${json.result.first_name})\n ${chalk.bold('Channel:')} ${chalk.cyan(channelId)}\n ${chalk.bold('Status:')} ${chalk.green('● Online')}\n`);\n } else {\n spinner.fail(chalk.red('Bot API error — check your token'));\n }\n } catch (e: any) {\n spinner.fail(chalk.red('Connection failed'));\n }\n });\n\n// ─── gramobase generate ──────────────────────────────────────────────────────\n\nprogram\n .command('generate <name>')\n .description('Generate a typed collection schema')\n .option('--fields <fields>', 'Comma-separated fields (e.g. name:string,age:number)')\n .action((name: string, opts) => {\n // Sanitize name — only allow alphanumeric + underscore/hyphen\n const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '');\n if (!safeName || safeName !== name) {\n console.error(chalk.red(' Error: Schema name must contain only letters, numbers, underscores, and hyphens'));\n process.exit(1);\n }\n\n const fields: string[] = opts.fields\n ? (opts.fields as string).split(',').map((f: string) => f.trim())\n : ['name:string'];\n\n const schemaFields = fields.map((f: string) => {\n const [fname, ftype] = f.split(':');\n // Sanitize field name\n const safeFname = (fname ?? 'field').replace(/[^a-zA-Z0-9_]/g, '');\n const zodType =\n ftype === 'number' ? 'z.number()' :\n ftype === 'boolean' ? 'z.boolean()' :\n ftype === 'date' ? 'z.string()' :\n 'z.string()';\n return ` ${safeFname}: ${zodType},`;\n }).join('\\n');\n\n const output = `import { z } from 'zod';\nimport { createClient } from 'gramobase';\n\nexport const ${safeName}Schema = z.object({\n${schemaFields}\n});\n\nexport type ${capitalize(safeName)} = z.infer<typeof ${safeName}Schema>;\n\n// Usage:\n// const db = createClient(config);\n// const ${safeName}s = db.collection('${safeName}s', { schema: ${safeName}Schema });\n// await ${safeName}s.insertOne({ ${fields.map((f: string) => (f.split(':')[0] ?? 'field').replace(/[^a-zA-Z0-9_]/g, '') + ': ...' ).join(', ')} });\n`;\n\n // Path is constructed from sanitized name only — no user-controlled path traversal\n const dir = join(process.cwd(), 'gramobase');\n const outPath = join(dir, `${safeName}.schema.ts`);\n\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n writeFileSync(outPath, output);\n\n console.log(`\\n ${chalk.green('✓')} Generated ${chalk.cyan(`gramobase/${safeName}.schema.ts`)}\\n`);\n });\n\n// ─── gramobase studio ────────────────────────────────────────────────────────\n\nprogram\n .command('studio')\n .description('Open the gramobase browser studio UI')\n .option('--port <port>', 'Port to listen on', '4242')\n .action((opts) => {\n // Validate port is numeric and in valid range\n const port = parseInt(opts.port as string, 10);\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(chalk.red(' Error: Invalid port number'));\n process.exit(1);\n }\n console.log(`\\n ${chalk.bold.cyan('gramobase studio')}\\n`);\n console.log(` ${chalk.gray('Open')} ${chalk.cyan(`http://localhost:${port}`)} ${chalk.gray('in your browser')}\\n`);\n console.log(chalk.yellow(' Studio UI coming in v0.2.0 — contribute at github.com/yourusername/gramobase\\n'));\n });\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nprogram.parse();\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { createInterface } from 'readline';
|
|
6
|
+
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
// gramobase — Telegram as your free, infinite backend
|
|
10
|
+
|
|
11
|
+
var pkg = { version: "0.1.0" };
|
|
12
|
+
var program = new Command();
|
|
13
|
+
program.name("gramobase").description(chalk.cyan("Telegram as your free, infinite backend database")).version(pkg.version);
|
|
14
|
+
program.command("init").description("Initialize a new gramobase project").option("--yes", "Skip prompts, use defaults").action(async (opts) => {
|
|
15
|
+
console.log("\n" + chalk.bold.cyan(" gramobase") + chalk.gray(" \u2014 Telegram backend\n"));
|
|
16
|
+
let botToken = "";
|
|
17
|
+
let channelId = "";
|
|
18
|
+
let encryptionKey = "";
|
|
19
|
+
if (!opts.yes) {
|
|
20
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
21
|
+
const ask = (q) => new Promise((res) => rl.question(q, res));
|
|
22
|
+
console.log(chalk.gray(" Get a bot token from @BotFather on Telegram\n"));
|
|
23
|
+
botToken = await ask(chalk.white(" Bot token: "));
|
|
24
|
+
channelId = await ask(chalk.white(" Channel ID (e.g. -100123456789): "));
|
|
25
|
+
encryptionKey = await ask(chalk.white(" Encryption key (optional, press enter to skip): "));
|
|
26
|
+
rl.close();
|
|
27
|
+
}
|
|
28
|
+
const spinner = ora("Setting up gramobase...").start();
|
|
29
|
+
const safeToken = botToken.trim();
|
|
30
|
+
const safeChannelId = channelId.trim();
|
|
31
|
+
const safeKey = encryptionKey.trim();
|
|
32
|
+
const cwd = process.cwd();
|
|
33
|
+
const envPath = join(cwd, ".env");
|
|
34
|
+
const envContent = [
|
|
35
|
+
`GRAMOBASE_BOT_TOKEN=${safeToken}`,
|
|
36
|
+
`GRAMOBASE_CHANNEL_ID=${safeChannelId}`,
|
|
37
|
+
safeKey ? `GRAMOBASE_ENCRYPTION_KEY=${safeKey}` : "# GRAMOBASE_ENCRYPTION_KEY="
|
|
38
|
+
].join("\n");
|
|
39
|
+
writeFileSync(envPath, envContent + "\n");
|
|
40
|
+
const configContent = `import { GramoBaseConfig } from 'gramobase';
|
|
41
|
+
|
|
42
|
+
const config: GramoBaseConfig = {
|
|
43
|
+
botToken: process.env.GRAMOBASE_BOT_TOKEN!,
|
|
44
|
+
channelId: process.env.GRAMOBASE_CHANNEL_ID!,
|
|
45
|
+
// encryptionKey: process.env.GRAMOBASE_ENCRYPTION_KEY,
|
|
46
|
+
cacheMaxBytes: 64 * 1024 * 1024, // 64MB hot cache
|
|
47
|
+
cacheTtlMs: 60_000,
|
|
48
|
+
concurrency: 25,
|
|
49
|
+
debug: process.env.NODE_ENV === 'development',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default config;
|
|
53
|
+
`;
|
|
54
|
+
writeFileSync(join(cwd, "gramobase.config.ts"), configContent);
|
|
55
|
+
const migrationsDir = join(cwd, "gramobase", "migrations");
|
|
56
|
+
if (!existsSync(migrationsDir)) {
|
|
57
|
+
mkdirSync(migrationsDir, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
spinner.succeed(chalk.green("gramobase initialized!"));
|
|
60
|
+
console.log(`
|
|
61
|
+
${chalk.bold("Files created:")}
|
|
62
|
+
${chalk.gray("\u251C\u2500")} .env
|
|
63
|
+
${chalk.gray("\u2514\u2500")} gramobase.config.ts
|
|
64
|
+
${chalk.gray("\u2514\u2500")} gramobase/migrations/
|
|
65
|
+
|
|
66
|
+
${chalk.bold("Next steps:")}
|
|
67
|
+
${chalk.cyan("1.")} Add your bot token and channel ID to .env
|
|
68
|
+
${chalk.cyan("2.")} Run ${chalk.bold("gramobase migrate")} to initialize the database
|
|
69
|
+
${chalk.cyan("3.")} Import and use: ${chalk.gray("import { createClient } from 'gramobase'")}
|
|
70
|
+
`);
|
|
71
|
+
});
|
|
72
|
+
program.command("migrate").description("Run pending migrations").option("--rollback <steps>", "Rollback N migration steps", "0").option("--status", "Show migration status").action(async (opts) => {
|
|
73
|
+
const spinner = ora("Loading migrations...").start();
|
|
74
|
+
try {
|
|
75
|
+
const configPath = join(process.cwd(), "gramobase.config.ts");
|
|
76
|
+
spinner.text = "Connecting...";
|
|
77
|
+
spinner.succeed("Migration runner ready (run in your project after build)");
|
|
78
|
+
} catch (e) {
|
|
79
|
+
spinner.fail(chalk.red("Failed: " + (e instanceof Error ? e.message : "Unknown error")));
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
program.command("status").description("Show database and connection status").action(async () => {
|
|
83
|
+
const spinner = ora("Checking status...").start();
|
|
84
|
+
try {
|
|
85
|
+
const token = process.env["GRAMOBASE_BOT_TOKEN"];
|
|
86
|
+
const channelId = process.env["GRAMOBASE_CHANNEL_ID"];
|
|
87
|
+
if (!token || !channelId) {
|
|
88
|
+
spinner.fail(".env not found \u2014 run gramobase init first");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const res = await fetch(`https://api.telegram.org/bot${encodeURIComponent(token)}/getMe`);
|
|
92
|
+
const json = await res.json();
|
|
93
|
+
if (json.ok) {
|
|
94
|
+
spinner.succeed(chalk.green("Connected"));
|
|
95
|
+
console.log(`
|
|
96
|
+
${chalk.bold("Bot:")} ${chalk.cyan("@" + json.result.username)} (${json.result.first_name})
|
|
97
|
+
${chalk.bold("Channel:")} ${chalk.cyan(channelId)}
|
|
98
|
+
${chalk.bold("Status:")} ${chalk.green("\u25CF Online")}
|
|
99
|
+
`);
|
|
100
|
+
} else {
|
|
101
|
+
spinner.fail(chalk.red("Bot API error \u2014 check your token"));
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
spinner.fail(chalk.red("Connection failed"));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
program.command("generate <name>").description("Generate a typed collection schema").option("--fields <fields>", "Comma-separated fields (e.g. name:string,age:number)").action((name, opts) => {
|
|
108
|
+
const safeName = name.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
109
|
+
if (!safeName || safeName !== name) {
|
|
110
|
+
console.error(chalk.red(" Error: Schema name must contain only letters, numbers, underscores, and hyphens"));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const fields = opts.fields ? opts.fields.split(",").map((f) => f.trim()) : ["name:string"];
|
|
114
|
+
const schemaFields = fields.map((f) => {
|
|
115
|
+
const [fname, ftype] = f.split(":");
|
|
116
|
+
const safeFname = (fname ?? "field").replace(/[^a-zA-Z0-9_]/g, "");
|
|
117
|
+
const zodType = ftype === "number" ? "z.number()" : ftype === "boolean" ? "z.boolean()" : ftype === "date" ? "z.string()" : "z.string()";
|
|
118
|
+
return ` ${safeFname}: ${zodType},`;
|
|
119
|
+
}).join("\n");
|
|
120
|
+
const output = `import { z } from 'zod';
|
|
121
|
+
import { createClient } from 'gramobase';
|
|
122
|
+
|
|
123
|
+
export const ${safeName}Schema = z.object({
|
|
124
|
+
${schemaFields}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export type ${capitalize(safeName)} = z.infer<typeof ${safeName}Schema>;
|
|
128
|
+
|
|
129
|
+
// Usage:
|
|
130
|
+
// const db = createClient(config);
|
|
131
|
+
// const ${safeName}s = db.collection('${safeName}s', { schema: ${safeName}Schema });
|
|
132
|
+
// await ${safeName}s.insertOne({ ${fields.map((f) => (f.split(":")[0] ?? "field").replace(/[^a-zA-Z0-9_]/g, "") + ": ...").join(", ")} });
|
|
133
|
+
`;
|
|
134
|
+
const dir = join(process.cwd(), "gramobase");
|
|
135
|
+
const outPath = join(dir, `${safeName}.schema.ts`);
|
|
136
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
137
|
+
writeFileSync(outPath, output);
|
|
138
|
+
console.log(`
|
|
139
|
+
${chalk.green("\u2713")} Generated ${chalk.cyan(`gramobase/${safeName}.schema.ts`)}
|
|
140
|
+
`);
|
|
141
|
+
});
|
|
142
|
+
program.command("studio").description("Open the gramobase browser studio UI").option("--port <port>", "Port to listen on", "4242").action((opts) => {
|
|
143
|
+
const port = parseInt(opts.port, 10);
|
|
144
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
145
|
+
console.error(chalk.red(" Error: Invalid port number"));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
console.log(`
|
|
149
|
+
${chalk.bold.cyan("gramobase studio")}
|
|
150
|
+
`);
|
|
151
|
+
console.log(` ${chalk.gray("Open")} ${chalk.cyan(`http://localhost:${port}`)} ${chalk.gray("in your browser")}
|
|
152
|
+
`);
|
|
153
|
+
console.log(chalk.yellow(" Studio UI coming in v0.2.0 \u2014 contribute at github.com/yourusername/gramobase\n"));
|
|
154
|
+
});
|
|
155
|
+
function capitalize(s) {
|
|
156
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
157
|
+
}
|
|
158
|
+
program.parse();
|
|
159
|
+
//# sourceMappingURL=gramobase.js.map
|
|
160
|
+
//# sourceMappingURL=gramobase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../bin/gramobase.ts"],"names":[],"mappings":";;;;;;;;;;AAQA,IAAM,GAAA,GAAM,EAAE,OAAA,EAAS,OAAA,EAAQ;AAE/B,IAAM,OAAA,GAAU,IAAI,OAAA,EAAQ;AAE5B,OAAA,CACG,IAAA,CAAK,WAAW,CAAA,CAChB,WAAA,CAAY,KAAA,CAAM,IAAA,CAAK,kDAAkD,CAAC,CAAA,CAC1E,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA;AAItB,OAAA,CACG,OAAA,CAAQ,MAAM,CAAA,CACd,WAAA,CAAY,oCAAoC,CAAA,CAChD,MAAA,CAAO,OAAA,EAAS,4BAA4B,CAAA,CAC5C,MAAA,CAAO,OAAO,IAAA,KAAS;AACtB,EAAA,OAAA,CAAQ,GAAA,CAAI,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,aAAa,CAAA,GAAI,KAAA,CAAM,IAAA,CAAK,4BAAuB,CAAC,CAAA;AAEvF,EAAA,IAAI,QAAA,GAAW,EAAA;AACf,EAAA,IAAI,SAAA,GAAY,EAAA;AAChB,EAAA,IAAI,aAAA,GAAgB,EAAA;AAEpB,EAAA,IAAI,CAAC,KAAK,GAAA,EAAK;AACb,IAAA,MAAM,EAAA,GAAK,gBAAgB,EAAE,KAAA,EAAO,QAAQ,KAAA,EAAO,MAAA,EAAQ,OAAA,CAAQ,MAAA,EAAQ,CAAA;AAC3E,IAAA,MAAM,GAAA,GAAM,CAAC,CAAA,KAAc,IAAI,OAAA,CAAgB,CAAC,GAAA,KAAQ,EAAA,CAAG,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA;AAE3E,IAAA,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,iDAAiD,CAAC,CAAA;AACzE,IAAA,QAAA,GAAW,MAAM,GAAA,CAAI,KAAA,CAAM,KAAA,CAAM,eAAe,CAAC,CAAA;AACjD,IAAA,SAAA,GAAY,MAAM,GAAA,CAAI,KAAA,CAAM,KAAA,CAAM,qCAAqC,CAAC,CAAA;AACxE,IAAA,aAAA,GAAgB,MAAM,GAAA,CAAI,KAAA,CAAM,KAAA,CAAM,oDAAoD,CAAC,CAAA;AAC3F,IAAA,EAAA,CAAG,KAAA,EAAM;AAAA,EACX;AAEA,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,yBAAyB,CAAA,CAAE,KAAA,EAAM;AAGrD,EAAA,MAAM,SAAA,GAAY,SAAS,IAAA,EAAK;AAChC,EAAA,MAAM,aAAA,GAAgB,UAAU,IAAA,EAAK;AACrC,EAAA,MAAM,OAAA,GAAU,cAAc,IAAA,EAAK;AAGnC,EAAA,MAAM,GAAA,GAAM,QAAQ,GAAA,EAAI;AACxB,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,EAAK,MAAM,CAAA;AAChC,EAAA,MAAM,UAAA,GAAa;AAAA,IACjB,uBAAuB,SAAS,CAAA,CAAA;AAAA,IAChC,wBAAwB,aAAa,CAAA,CAAA;AAAA,IACrC,OAAA,GAAU,CAAA,yBAAA,EAA4B,OAAO,CAAA,CAAA,GAAK;AAAA,GACpD,CAAE,KAAK,IAAI,CAAA;AAEX,EAAA,aAAA,CAAc,OAAA,EAAS,aAAa,IAAI,CAAA;AAGxC,EAAA,MAAM,aAAA,GAAgB,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA,CAAA;AAetB,EAAA,aAAA,CAAc,IAAA,CAAK,GAAA,EAAK,qBAAqB,CAAA,EAAG,aAAa,CAAA;AAG7D,EAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,GAAA,EAAK,WAAA,EAAa,YAAY,CAAA;AACzD,EAAA,IAAI,CAAC,UAAA,CAAW,aAAa,CAAA,EAAG;AAC9B,IAAA,SAAA,CAAU,aAAA,EAAe,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAAA,EAC9C;AAEA,EAAA,OAAA,CAAQ,OAAA,CAAQ,KAAA,CAAM,KAAA,CAAM,wBAAwB,CAAC,CAAA;AAErD,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,EAAA,EACZ,KAAA,CAAM,IAAA,CAAK,gBAAgB,CAAC;AAAA,EAAA,EAC5B,KAAA,CAAM,IAAA,CAAK,cAAI,CAAC,CAAA;AAAA,EAAA,EAChB,KAAA,CAAM,IAAA,CAAK,cAAI,CAAC,CAAA;AAAA,EAAA,EAChB,KAAA,CAAM,IAAA,CAAK,cAAI,CAAC,CAAA;;AAAA,EAAA,EAEhB,KAAA,CAAM,IAAA,CAAK,aAAa,CAAC;AAAA,EAAA,EACzB,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,EAAA,EAChB,KAAA,CAAM,KAAK,IAAI,CAAC,QAAQ,KAAA,CAAM,IAAA,CAAK,mBAAmB,CAAC,CAAA;AAAA,EAAA,EACvD,KAAA,CAAM,KAAK,IAAI,CAAC,oBAAoB,KAAA,CAAM,IAAA,CAAK,0CAA0C,CAAC;AAAA,CAC7F,CAAA;AACC,CAAC,CAAA;AAIH,OAAA,CACG,QAAQ,SAAS,CAAA,CACjB,WAAA,CAAY,wBAAwB,EACpC,MAAA,CAAO,oBAAA,EAAsB,4BAAA,EAA8B,GAAG,EAC9D,MAAA,CAAO,UAAA,EAAY,uBAAuB,CAAA,CAC1C,MAAA,CAAO,OAAO,IAAA,KAAS;AACtB,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,uBAAuB,CAAA,CAAE,KAAA,EAAM;AACnD,EAAA,IAAI;AACF,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,OAAA,CAAQ,GAAA,IAAO,qBAAqB,CAAA;AAE5D,IAAA,OAAA,CAAQ,IAAA,GAAO,eAAA;AACf,IAAA,OAAA,CAAQ,QAAQ,0DAA0D,CAAA;AAAA,EAC5E,SAAS,CAAA,EAAQ;AACf,IAAA,OAAA,CAAQ,IAAA,CAAK,MAAM,GAAA,CAAI,UAAA,IAAc,aAAa,KAAA,GAAQ,CAAA,CAAE,OAAA,GAAU,eAAA,CAAgB,CAAC,CAAA;AAAA,EACzF;AACF,CAAC,CAAA;AAIH,OAAA,CACG,QAAQ,QAAQ,CAAA,CAChB,YAAY,qCAAqC,CAAA,CACjD,OAAO,YAAY;AAClB,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,oBAAoB,CAAA,CAAE,KAAA,EAAM;AAChD,EAAA,IAAI;AACF,IAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,qBAAqB,CAAA;AAC/C,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,sBAAsB,CAAA;AAEpD,IAAA,IAAI,CAAC,KAAA,IAAS,CAAC,SAAA,EAAW;AACxB,MAAA,OAAA,CAAQ,KAAK,gDAA2C,CAAA;AACxD,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,MAAM,MAAM,KAAA,CAAM,+BAA+B,kBAAA,CAAmB,KAAK,CAAC,CAAA,MAAA,CAAQ,CAAA;AACxF,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAE5B,IAAA,IAAI,KAAK,EAAA,EAAI;AACX,MAAA,OAAA,CAAQ,OAAA,CAAQ,KAAA,CAAM,KAAA,CAAM,WAAW,CAAC,CAAA;AACxC,MAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,EAAA,EAChB,KAAA,CAAM,IAAA,CAAK,MAAM,CAAC,QAAQ,KAAA,CAAM,IAAA,CAAK,GAAA,GAAM,IAAA,CAAK,OAAO,QAAQ,CAAC,CAAA,EAAA,EAAK,IAAA,CAAK,OAAO,UAAU,CAAA;AAAA,EAAA,EAC3F,KAAA,CAAM,KAAK,UAAU,CAAC,IAAI,KAAA,CAAM,IAAA,CAAK,SAAS,CAAC;AAAA,EAAA,EAC/C,KAAA,CAAM,KAAK,SAAS,CAAC,KAAK,KAAA,CAAM,KAAA,CAAM,eAAU,CAAC;AAAA,CACpD,CAAA;AAAA,IACK,CAAA,MAAO;AACL,MAAA,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,uCAAkC,CAAC,CAAA;AAAA,IAC5D;AAAA,EACF,SAAS,CAAA,EAAQ;AACf,IAAA,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,mBAAmB,CAAC,CAAA;AAAA,EAC7C;AACF,CAAC,CAAA;AAIH,OAAA,CACG,OAAA,CAAQ,iBAAiB,CAAA,CACzB,WAAA,CAAY,oCAAoC,CAAA,CAChD,MAAA,CAAO,mBAAA,EAAqB,sDAAsD,CAAA,CAClF,MAAA,CAAO,CAAC,MAAc,IAAA,KAAS;AAE9B,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAA;AACnD,EAAA,IAAI,CAAC,QAAA,IAAY,QAAA,KAAa,IAAA,EAAM;AAClC,IAAA,OAAA,CAAQ,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,mFAAmF,CAAC,CAAA;AAC5G,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAEA,EAAA,MAAM,SAAmB,IAAA,CAAK,MAAA,GACzB,IAAA,CAAK,MAAA,CAAkB,MAAM,GAAG,CAAA,CAAE,GAAA,CAAI,CAAC,MAAc,CAAA,CAAE,IAAA,EAAM,CAAA,GAC9D,CAAC,aAAa,CAAA;AAElB,EAAA,MAAM,YAAA,GAAe,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,KAAc;AAC7C,IAAA,MAAM,CAAC,KAAA,EAAO,KAAK,CAAA,GAAI,CAAA,CAAE,MAAM,GAAG,CAAA;AAElC,IAAA,MAAM,SAAA,GAAA,CAAa,KAAA,IAAS,OAAA,EAAS,OAAA,CAAQ,kBAAkB,EAAE,CAAA;AACjE,IAAA,MAAM,OAAA,GACJ,UAAU,QAAA,GAAW,YAAA,GACrB,UAAU,SAAA,GAAY,aAAA,GACtB,KAAA,KAAU,MAAA,GAAS,YAAA,GACnB,YAAA;AACF,IAAA,OAAO,CAAA,EAAA,EAAK,SAAS,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA,CAAA;AAAA,EACnC,CAAC,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAEZ,EAAA,MAAM,MAAA,GAAS,CAAA;AAAA;;AAAA,aAAA,EAGJ,QAAQ,CAAA;AAAA,EACrB,YAAY;AAAA;;AAAA,YAAA,EAGA,UAAA,CAAW,QAAQ,CAAC,CAAA,kBAAA,EAAqB,QAAQ,CAAA;;AAAA;AAAA;AAAA,SAAA,EAIpD,QAAQ,CAAA,mBAAA,EAAsB,QAAQ,CAAA,cAAA,EAAiB,QAAQ,CAAA;AAAA,SAAA,EAC/D,QAAQ,iBAAiB,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,KAAA,CAAe,CAAA,CAAE,MAAM,GAAG,CAAA,CAAE,CAAC,CAAA,IAAK,OAAA,EAAS,QAAQ,gBAAA,EAAkB,EAAE,IAAI,OAAQ,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,CAAA;AAI3I,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,OAAA,CAAQ,GAAA,IAAO,WAAW,CAAA;AAC3C,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,EAAK,CAAA,EAAG,QAAQ,CAAA,UAAA,CAAY,CAAA;AAEjD,EAAA,IAAI,CAAC,WAAW,GAAG,CAAA,YAAa,GAAA,EAAK,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACxD,EAAA,aAAA,CAAc,SAAS,MAAM,CAAA;AAE7B,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,EAAA,EAAO,KAAA,CAAM,KAAA,CAAM,QAAG,CAAC,CAAA,WAAA,EAAc,MAAM,IAAA,CAAK,CAAA,UAAA,EAAa,QAAQ,CAAA,UAAA,CAAY,CAAC;AAAA,CAAI,CAAA;AACpG,CAAC,CAAA;AAIH,OAAA,CACG,OAAA,CAAQ,QAAQ,CAAA,CAChB,WAAA,CAAY,sCAAsC,CAAA,CAClD,MAAA,CAAO,eAAA,EAAiB,mBAAA,EAAqB,MAAM,CAAA,CACnD,MAAA,CAAO,CAAC,IAAA,KAAS;AAEhB,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,IAAA,CAAK,IAAA,EAAgB,EAAE,CAAA;AAC7C,EAAA,IAAI,MAAM,IAAI,CAAA,IAAK,IAAA,GAAO,CAAA,IAAK,OAAO,KAAA,EAAO;AAC3C,IAAA,OAAA,CAAQ,KAAA,CAAM,KAAA,CAAM,GAAA,CAAI,8BAA8B,CAAC,CAAA;AACvD,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AACA,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,EAAA,EAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,kBAAkB,CAAC;AAAA,CAAI,CAAA;AAC1D,EAAA,OAAA,CAAQ,IAAI,CAAA,EAAA,EAAK,KAAA,CAAM,IAAA,CAAK,MAAM,CAAC,CAAA,CAAA,EAAI,KAAA,CAAM,IAAA,CAAK,CAAA,iBAAA,EAAoB,IAAI,CAAA,CAAE,CAAC,IAAI,KAAA,CAAM,IAAA,CAAK,iBAAiB,CAAC;AAAA,CAAI,CAAA;AAClH,EAAA,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,MAAA,CAAO,uFAAkF,CAAC,CAAA;AAC9G,CAAC,CAAA;AAEH,SAAS,WAAW,CAAA,EAAmB;AACrC,EAAA,OAAO,CAAA,CAAE,OAAO,CAAC,CAAA,CAAE,aAAY,GAAI,CAAA,CAAE,MAAM,CAAC,CAAA;AAC9C;AAEA,OAAA,CAAQ,KAAA,EAAM","file":"gramobase.js","sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport { createInterface } from 'readline';\nimport { writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { join, resolve, basename } from 'path';\n\nconst pkg = { version: '0.1.0' };\n\nconst program = new Command();\n\nprogram\n .name('gramobase')\n .description(chalk.cyan('Telegram as your free, infinite backend database'))\n .version(pkg.version);\n\n// ─── gramobase init ──────────────────────────────────────────────────────────\n\nprogram\n .command('init')\n .description('Initialize a new gramobase project')\n .option('--yes', 'Skip prompts, use defaults')\n .action(async (opts) => {\n console.log('\\n' + chalk.bold.cyan(' gramobase') + chalk.gray(' — Telegram backend\\n'));\n\n let botToken = '';\n let channelId = '';\n let encryptionKey = '';\n\n if (!opts.yes) {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string) => new Promise<string>((res) => rl.question(q, res));\n\n console.log(chalk.gray(' Get a bot token from @BotFather on Telegram\\n'));\n botToken = await ask(chalk.white(' Bot token: '));\n channelId = await ask(chalk.white(' Channel ID (e.g. -100123456789): '));\n encryptionKey = await ask(chalk.white(' Encryption key (optional, press enter to skip): '));\n rl.close();\n }\n\n const spinner = ora('Setting up gramobase...').start();\n\n // Sanitize inputs — only use basename of any path-like values\n const safeToken = botToken.trim();\n const safeChannelId = channelId.trim();\n const safeKey = encryptionKey.trim();\n\n // Create .env — never write tokens to paths derived from user input\n const cwd = process.cwd();\n const envPath = join(cwd, '.env');\n const envContent = [\n `GRAMOBASE_BOT_TOKEN=${safeToken}`,\n `GRAMOBASE_CHANNEL_ID=${safeChannelId}`,\n safeKey ? `GRAMOBASE_ENCRYPTION_KEY=${safeKey}` : '# GRAMOBASE_ENCRYPTION_KEY=',\n ].join('\\n');\n\n writeFileSync(envPath, envContent + '\\n');\n\n // Create gramobase.config.ts\n const configContent = `import { GramoBaseConfig } from 'gramobase';\n\nconst config: GramoBaseConfig = {\n botToken: process.env.GRAMOBASE_BOT_TOKEN!,\n channelId: process.env.GRAMOBASE_CHANNEL_ID!,\n // encryptionKey: process.env.GRAMOBASE_ENCRYPTION_KEY,\n cacheMaxBytes: 64 * 1024 * 1024, // 64MB hot cache\n cacheTtlMs: 60_000,\n concurrency: 25,\n debug: process.env.NODE_ENV === 'development',\n};\n\nexport default config;\n`;\n\n writeFileSync(join(cwd, 'gramobase.config.ts'), configContent);\n\n // Create migrations folder — path is hardcoded, not from user input\n const migrationsDir = join(cwd, 'gramobase', 'migrations');\n if (!existsSync(migrationsDir)) {\n mkdirSync(migrationsDir, { recursive: true });\n }\n\n spinner.succeed(chalk.green('gramobase initialized!'));\n\n console.log(`\n ${chalk.bold('Files created:')}\n ${chalk.gray('├─')} .env\n ${chalk.gray('└─')} gramobase.config.ts\n ${chalk.gray('└─')} gramobase/migrations/\n\n ${chalk.bold('Next steps:')}\n ${chalk.cyan('1.')} Add your bot token and channel ID to .env\n ${chalk.cyan('2.')} Run ${chalk.bold('gramobase migrate')} to initialize the database\n ${chalk.cyan('3.')} Import and use: ${chalk.gray(\"import { createClient } from 'gramobase'\")}\n`);\n });\n\n// ─── gramobase migrate ───────────────────────────────────────────────────────\n\nprogram\n .command('migrate')\n .description('Run pending migrations')\n .option('--rollback <steps>', 'Rollback N migration steps', '0')\n .option('--status', 'Show migration status')\n .action(async (opts) => {\n const spinner = ora('Loading migrations...').start();\n try {\n const configPath = join(process.cwd(), 'gramobase.config.ts');\n\n spinner.text = 'Connecting...';\n spinner.succeed('Migration runner ready (run in your project after build)');\n } catch (e: any) {\n spinner.fail(chalk.red('Failed: ' + (e instanceof Error ? e.message : 'Unknown error')));\n }\n });\n\n// ─── gramobase status ────────────────────────────────────────────────────────\n\nprogram\n .command('status')\n .description('Show database and connection status')\n .action(async () => {\n const spinner = ora('Checking status...').start();\n try {\n const token = process.env['GRAMOBASE_BOT_TOKEN'];\n const channelId = process.env['GRAMOBASE_CHANNEL_ID'];\n\n if (!token || !channelId) {\n spinner.fail('.env not found — run gramobase init first');\n return;\n }\n\n // Ping Telegram Bot API — token is from env, not user input in this context\n const res = await fetch(`https://api.telegram.org/bot${encodeURIComponent(token)}/getMe`);\n const json = await res.json() as any;\n\n if (json.ok) {\n spinner.succeed(chalk.green('Connected'));\n console.log(`\n ${chalk.bold('Bot:')} ${chalk.cyan('@' + json.result.username)} (${json.result.first_name})\n ${chalk.bold('Channel:')} ${chalk.cyan(channelId)}\n ${chalk.bold('Status:')} ${chalk.green('● Online')}\n`);\n } else {\n spinner.fail(chalk.red('Bot API error — check your token'));\n }\n } catch (e: any) {\n spinner.fail(chalk.red('Connection failed'));\n }\n });\n\n// ─── gramobase generate ──────────────────────────────────────────────────────\n\nprogram\n .command('generate <name>')\n .description('Generate a typed collection schema')\n .option('--fields <fields>', 'Comma-separated fields (e.g. name:string,age:number)')\n .action((name: string, opts) => {\n // Sanitize name — only allow alphanumeric + underscore/hyphen\n const safeName = name.replace(/[^a-zA-Z0-9_-]/g, '');\n if (!safeName || safeName !== name) {\n console.error(chalk.red(' Error: Schema name must contain only letters, numbers, underscores, and hyphens'));\n process.exit(1);\n }\n\n const fields: string[] = opts.fields\n ? (opts.fields as string).split(',').map((f: string) => f.trim())\n : ['name:string'];\n\n const schemaFields = fields.map((f: string) => {\n const [fname, ftype] = f.split(':');\n // Sanitize field name\n const safeFname = (fname ?? 'field').replace(/[^a-zA-Z0-9_]/g, '');\n const zodType =\n ftype === 'number' ? 'z.number()' :\n ftype === 'boolean' ? 'z.boolean()' :\n ftype === 'date' ? 'z.string()' :\n 'z.string()';\n return ` ${safeFname}: ${zodType},`;\n }).join('\\n');\n\n const output = `import { z } from 'zod';\nimport { createClient } from 'gramobase';\n\nexport const ${safeName}Schema = z.object({\n${schemaFields}\n});\n\nexport type ${capitalize(safeName)} = z.infer<typeof ${safeName}Schema>;\n\n// Usage:\n// const db = createClient(config);\n// const ${safeName}s = db.collection('${safeName}s', { schema: ${safeName}Schema });\n// await ${safeName}s.insertOne({ ${fields.map((f: string) => (f.split(':')[0] ?? 'field').replace(/[^a-zA-Z0-9_]/g, '') + ': ...' ).join(', ')} });\n`;\n\n // Path is constructed from sanitized name only — no user-controlled path traversal\n const dir = join(process.cwd(), 'gramobase');\n const outPath = join(dir, `${safeName}.schema.ts`);\n\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n writeFileSync(outPath, output);\n\n console.log(`\\n ${chalk.green('✓')} Generated ${chalk.cyan(`gramobase/${safeName}.schema.ts`)}\\n`);\n });\n\n// ─── gramobase studio ────────────────────────────────────────────────────────\n\nprogram\n .command('studio')\n .description('Open the gramobase browser studio UI')\n .option('--port <port>', 'Port to listen on', '4242')\n .action((opts) => {\n // Validate port is numeric and in valid range\n const port = parseInt(opts.port as string, 10);\n if (isNaN(port) || port < 1 || port > 65535) {\n console.error(chalk.red(' Error: Invalid port number'));\n process.exit(1);\n }\n console.log(`\\n ${chalk.bold.cyan('gramobase studio')}\\n`);\n console.log(` ${chalk.gray('Open')} ${chalk.cyan(`http://localhost:${port}`)} ${chalk.gray('in your browser')}\\n`);\n console.log(chalk.yellow(' Studio UI coming in v0.2.0 — contribute at github.com/yourusername/gramobase\\n'));\n });\n\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nprogram.parse();\n"]}
|