@takyonic/cli 1.0.2
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 +37 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +107 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +98 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +77 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/pull.d.ts +3 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +100 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +3 -0
- package/dist/commands/push.d.ts.map +1 -0
- package/dist/commands/push.js +135 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/dsl-to-json.d.ts +28 -0
- package/dist/dsl-to-json.d.ts.map +1 -0
- package/dist/dsl-to-json.js +69 -0
- package/dist/dsl-to-json.js.map +1 -0
- package/dist/generator.d.ts +19 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +161 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +36 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +234 -0
- package/dist/parser.js.map +1 -0
- package/dist/type-mapper.d.ts +17 -0
- package/dist/type-mapper.d.ts.map +1 -0
- package/dist/type-mapper.js +90 -0
- package/dist/type-mapper.js.map +1 -0
- package/generated/index.ts +89 -0
- package/package.json +33 -0
- package/src/commands/generate.ts +78 -0
- package/src/commands/init.ts +63 -0
- package/src/commands/inspect.ts +98 -0
- package/src/commands/pull.ts +68 -0
- package/src/commands/push.ts +106 -0
- package/src/dsl-to-json.ts +99 -0
- package/src/generator.ts +178 -0
- package/src/index.ts +32 -0
- package/src/parser.ts +285 -0
- package/src/test-bulk.ts +38 -0
- package/src/test-load.ts +28 -0
- package/src/type-mapper.ts +90 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Type Mapping Utilities
|
|
4
|
+
* Maps between Takyonic DSL types and PostgreSQL types
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.dslTypeToPostgres = dslTypeToPostgres;
|
|
8
|
+
exports.postgresTypeToDsl = postgresTypeToDsl;
|
|
9
|
+
exports.isNullableType = isNullableType;
|
|
10
|
+
/**
|
|
11
|
+
* Map Takyonic DSL type to PostgreSQL type
|
|
12
|
+
*/
|
|
13
|
+
function dslTypeToPostgres(dslType) {
|
|
14
|
+
const normalized = dslType.toLowerCase().trim();
|
|
15
|
+
switch (normalized) {
|
|
16
|
+
case 'string':
|
|
17
|
+
return 'text';
|
|
18
|
+
case 'int':
|
|
19
|
+
case 'integer':
|
|
20
|
+
return 'integer';
|
|
21
|
+
case 'float':
|
|
22
|
+
case 'double':
|
|
23
|
+
return 'double precision';
|
|
24
|
+
case 'json':
|
|
25
|
+
return 'jsonb';
|
|
26
|
+
case 'bool':
|
|
27
|
+
case 'boolean':
|
|
28
|
+
return 'boolean';
|
|
29
|
+
case 'timestamp':
|
|
30
|
+
return 'timestamp';
|
|
31
|
+
case 'date':
|
|
32
|
+
return 'date';
|
|
33
|
+
case 'uuid':
|
|
34
|
+
return 'uuid';
|
|
35
|
+
default:
|
|
36
|
+
// Return as-is for unknown types (might be custom PostgreSQL types)
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Map PostgreSQL type to Takyonic DSL type
|
|
42
|
+
*/
|
|
43
|
+
function postgresTypeToDsl(postgresType) {
|
|
44
|
+
const normalized = postgresType.toLowerCase().trim();
|
|
45
|
+
switch (normalized) {
|
|
46
|
+
case 'text':
|
|
47
|
+
case 'varchar':
|
|
48
|
+
case 'character varying':
|
|
49
|
+
case 'char':
|
|
50
|
+
return 'string';
|
|
51
|
+
case 'integer':
|
|
52
|
+
case 'int':
|
|
53
|
+
case 'int4':
|
|
54
|
+
return 'int';
|
|
55
|
+
case 'bigint':
|
|
56
|
+
case 'int8':
|
|
57
|
+
return 'int';
|
|
58
|
+
case 'double precision':
|
|
59
|
+
case 'real':
|
|
60
|
+
case 'float':
|
|
61
|
+
return 'float';
|
|
62
|
+
case 'jsonb':
|
|
63
|
+
case 'json':
|
|
64
|
+
return 'json';
|
|
65
|
+
case 'boolean':
|
|
66
|
+
case 'bool':
|
|
67
|
+
return 'bool';
|
|
68
|
+
case 'timestamp':
|
|
69
|
+
case 'timestamp without time zone':
|
|
70
|
+
case 'timestamp with time zone':
|
|
71
|
+
return 'timestamp';
|
|
72
|
+
case 'date':
|
|
73
|
+
return 'date';
|
|
74
|
+
case 'uuid':
|
|
75
|
+
return 'uuid';
|
|
76
|
+
case 'numeric':
|
|
77
|
+
case 'decimal':
|
|
78
|
+
return 'float';
|
|
79
|
+
default:
|
|
80
|
+
// Return as-is for unknown types
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if a type is nullable based on PostgreSQL type information
|
|
86
|
+
*/
|
|
87
|
+
function isNullableType(postgresType, isNullable) {
|
|
88
|
+
return isNullable;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=type-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-mapper.js","sourceRoot":"","sources":["../src/type-mapper.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAKH,8CA2BC;AAKD,8CAyCC;AAKD,wCAEC;AAnFD;;GAEG;AACH,SAAgB,iBAAiB,CAAC,OAAe;IAC/C,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAEhD,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC;QAChB,KAAK,KAAK,CAAC;QACX,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,OAAO,CAAC;QACb,KAAK,QAAQ;YACX,OAAO,kBAAkB,CAAC;QAC5B,KAAK,MAAM;YACT,OAAO,OAAO,CAAC;QACjB,KAAK,MAAM,CAAC;QACZ,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,WAAW;YACd,OAAO,WAAW,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB;YACE,oEAAoE;YACpE,OAAO,UAAU,CAAC;IACtB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,iBAAiB,CAAC,YAAoB;IACpD,MAAM,UAAU,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAErD,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,MAAM,CAAC;QACZ,KAAK,SAAS,CAAC;QACf,KAAK,mBAAmB,CAAC;QACzB,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC;QAClB,KAAK,SAAS,CAAC;QACf,KAAK,KAAK,CAAC;QACX,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf,KAAK,QAAQ,CAAC;QACd,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf,KAAK,kBAAkB,CAAC;QACxB,KAAK,MAAM,CAAC;QACZ,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,SAAS,CAAC;QACf,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,WAAW,CAAC;QACjB,KAAK,6BAA6B,CAAC;QACnC,KAAK,0BAA0B;YAC7B,OAAO,WAAW,CAAC;QACrB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,MAAM;YACT,OAAO,MAAM,CAAC;QAChB,KAAK,SAAS,CAAC;QACf,KAAK,SAAS;YACZ,OAAO,OAAO,CAAC;QACjB;YACE,iCAAiC;YACjC,OAAO,UAAU,CAAC;IACtB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,YAAoB,EAAE,UAAmB;IACtE,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-generated by Takyonic CLI
|
|
3
|
+
*
|
|
4
|
+
* This file contains TypeScript interfaces for your database schema.
|
|
5
|
+
* Use these types with the @takyonic/sdk package for type-safe queries.
|
|
6
|
+
*
|
|
7
|
+
* Do not edit this file manually - regenerate with: takyonic-cli generate
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { TakyonicClient } from '@takyonic/sdk';
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Table Interfaces
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
export interface Products {
|
|
17
|
+
id: string;
|
|
18
|
+
data: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Users {
|
|
22
|
+
id: string;
|
|
23
|
+
data: Record<string, unknown>;
|
|
24
|
+
created_at: Date;
|
|
25
|
+
updated_at: Date;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Logs {
|
|
29
|
+
id: string;
|
|
30
|
+
event: string;
|
|
31
|
+
payload: Record<string, unknown>;
|
|
32
|
+
severity: number;
|
|
33
|
+
status: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Type Utilities
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Union type of all table names in the schema
|
|
42
|
+
*/
|
|
43
|
+
export type TableName = 'products' | 'users' | 'logs';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Mapping from table names to their interface types
|
|
47
|
+
*/
|
|
48
|
+
export interface TableTypes {
|
|
49
|
+
products: Products;
|
|
50
|
+
users: Users;
|
|
51
|
+
logs: Logs;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Helper type to get the interface type for a table name
|
|
56
|
+
*/
|
|
57
|
+
export type TableType<T extends TableName> = TableTypes[T];
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Usage Examples
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Example usage with @takyonic/sdk:
|
|
65
|
+
*
|
|
66
|
+
* ```typescript
|
|
67
|
+
* import { TakyonicClient } from '@takyonic/sdk';
|
|
68
|
+
* import type { Products } from './generated';
|
|
69
|
+
*
|
|
70
|
+
* const db = new TakyonicClient({
|
|
71
|
+
* endpoint: 'http://localhost:8080',
|
|
72
|
+
* token: process.env.TAKYONIC_API_KEY!
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* // Type-safe queries
|
|
76
|
+
* const records = await db.table<Products>('products').get();
|
|
77
|
+
*
|
|
78
|
+
* // Type-safe inserts
|
|
79
|
+
* await db.table<Products>('products').insert({
|
|
80
|
+
* id: 'new-id',
|
|
81
|
+
* // ... other fields
|
|
82
|
+
* });
|
|
83
|
+
*
|
|
84
|
+
* // Type-safe search with filters
|
|
85
|
+
* const filtered = await db.table<Products>('products')
|
|
86
|
+
* .where('id', '=', 'some-id')
|
|
87
|
+
* .get();
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@takyonic/cli",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Takyonic CLI - Developer tools for Takyonic Smart Cache Proxy",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"takyonic": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "ts-node src/index.ts",
|
|
12
|
+
"dev": "ts-node src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"takyonic",
|
|
16
|
+
"postgresql",
|
|
17
|
+
"cache",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@takyonic/sdk": "^1.0.2",
|
|
24
|
+
"chalk": "^4.1.2",
|
|
25
|
+
"commander": "^11.1.0",
|
|
26
|
+
"dotenv": "^16.3.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.10.0",
|
|
30
|
+
"ts-node": "^10.9.2",
|
|
31
|
+
"typescript": "^5.3.3"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { generateTypeScript } from '../generator';
|
|
6
|
+
import { parseTakyonicSchema, ParseError } from '../parser';
|
|
7
|
+
import { dslToJson, TableSchema } from '../dsl-to-json';
|
|
8
|
+
|
|
9
|
+
export const generateCommand = new Command('generate')
|
|
10
|
+
.description('Generate TypeScript client code from .takyonic schema file')
|
|
11
|
+
.action(async () => {
|
|
12
|
+
// Determine input file (.takyonic in project root)
|
|
13
|
+
const inputFile = path.join(process.cwd(), '.takyonic');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(inputFile)) {
|
|
16
|
+
console.error(chalk.red(`Error: ${inputFile} not found`));
|
|
17
|
+
console.error(chalk.yellow('Please run "takyonic init" to create a .takyonic file'));
|
|
18
|
+
console.error(chalk.yellow('Or run "takyonic pull" to fetch schema from server'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
console.log(chalk.blue('⚡ Takyonic: Reading schema from .takyonic file...\n'));
|
|
24
|
+
|
|
25
|
+
// Read and parse DSL file
|
|
26
|
+
const fileContent = fs.readFileSync(inputFile, 'utf-8');
|
|
27
|
+
const dslSchema = parseTakyonicSchema(fileContent);
|
|
28
|
+
|
|
29
|
+
if (!dslSchema.tables || dslSchema.tables.length === 0) {
|
|
30
|
+
console.log(chalk.yellow('No tables found in schema'));
|
|
31
|
+
console.log(chalk.yellow('Cannot generate code without tables.'));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Convert DSL to JSON format for generator
|
|
36
|
+
const schema = dslToJson(dslSchema);
|
|
37
|
+
|
|
38
|
+
console.log(chalk.blue('⚡ Takyonic: Generating TypeScript code...\n'));
|
|
39
|
+
|
|
40
|
+
// Generate TypeScript code
|
|
41
|
+
const generatedCode = generateTypeScript(schema);
|
|
42
|
+
|
|
43
|
+
// Determine output directory (cli/generated)
|
|
44
|
+
const outputDir = path.join(process.cwd(), 'generated');
|
|
45
|
+
const outputFile = path.join(outputDir, 'index.ts');
|
|
46
|
+
|
|
47
|
+
// Create directory if it doesn't exist
|
|
48
|
+
if (!fs.existsSync(outputDir)) {
|
|
49
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
50
|
+
console.log(chalk.green(`Created directory: ${outputDir}`));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Write file
|
|
54
|
+
fs.writeFileSync(outputFile, generatedCode, 'utf-8');
|
|
55
|
+
|
|
56
|
+
console.log(chalk.green(`✓ Generated TypeScript client code`));
|
|
57
|
+
console.log(chalk.gray(` Output: ${outputFile}`));
|
|
58
|
+
console.log(chalk.gray(` Tables: ${schema.length}`));
|
|
59
|
+
const totalColumns = schema.reduce((sum, table) => sum + table.columns.length, 0);
|
|
60
|
+
console.log(chalk.gray(` Columns: ${totalColumns}\n`));
|
|
61
|
+
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(chalk.red('⚡ Takyonic: Error generating code:'));
|
|
64
|
+
if (error instanceof ParseError) {
|
|
65
|
+
console.error(chalk.red(`Parse error: ${error.message}`));
|
|
66
|
+
if (error.line) {
|
|
67
|
+
console.error(chalk.yellow(` Line: ${error.line}`));
|
|
68
|
+
}
|
|
69
|
+
console.error(chalk.yellow('Please check your .takyonic file syntax'));
|
|
70
|
+
} else if (error instanceof Error) {
|
|
71
|
+
console.error(chalk.red(error.message));
|
|
72
|
+
} else {
|
|
73
|
+
console.error(chalk.red(String(error)));
|
|
74
|
+
}
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_TAKYONIC_FILE = `// Takyonic Core Configuration
|
|
7
|
+
engine {
|
|
8
|
+
provider = "takyonic"
|
|
9
|
+
db = env("DATABASE_URL")
|
|
10
|
+
cache = "in-memory"
|
|
11
|
+
port = 8080
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Takyonic Data Definitions
|
|
15
|
+
table products {
|
|
16
|
+
id string @primary
|
|
17
|
+
name string
|
|
18
|
+
price int @default(0)
|
|
19
|
+
data json
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
table orders {
|
|
23
|
+
id string @primary
|
|
24
|
+
amount float
|
|
25
|
+
status string @default("pending")
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
export const initCommand = new Command('init')
|
|
30
|
+
.description('Initialize a new Takyonic project with a .takyonic schema file')
|
|
31
|
+
.action(async () => {
|
|
32
|
+
const takyonicFile = path.join(process.cwd(), '.takyonic');
|
|
33
|
+
|
|
34
|
+
// Check if .takyonic file already exists
|
|
35
|
+
if (fs.existsSync(takyonicFile)) {
|
|
36
|
+
console.error(chalk.red('Error: .takyonic file already exists'));
|
|
37
|
+
console.error(chalk.yellow(` File: ${takyonicFile}`));
|
|
38
|
+
console.error(chalk.yellow(' Remove it first or use a different directory\n'));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Write default .takyonic file
|
|
44
|
+
fs.writeFileSync(takyonicFile, DEFAULT_TAKYONIC_FILE, 'utf-8');
|
|
45
|
+
|
|
46
|
+
console.log(chalk.green('✓ Takyonic project initialized'));
|
|
47
|
+
console.log(chalk.gray(` Created: ${takyonicFile}\n`));
|
|
48
|
+
console.log(chalk.blue('⚡ Next steps:'));
|
|
49
|
+
console.log(chalk.gray(' 1. Edit .takyonic to define your tables'));
|
|
50
|
+
console.log(chalk.gray(' 2. Run "takyonic push" to sync schema to server'));
|
|
51
|
+
console.log(chalk.gray(' 3. Run "takyonic generate" to create TypeScript client\n'));
|
|
52
|
+
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(chalk.red('⚡ Takyonic: Error initializing project:'));
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
console.error(chalk.red(error.message));
|
|
57
|
+
} else {
|
|
58
|
+
console.error(chalk.red(String(error)));
|
|
59
|
+
}
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
interface ColumnSchema {
|
|
4
|
+
name: string;
|
|
5
|
+
data_type: string;
|
|
6
|
+
nullable: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface TableSchema {
|
|
10
|
+
name: string;
|
|
11
|
+
columns: ColumnSchema[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function inspectCommand() {
|
|
15
|
+
const takyonicUrl = process.env.TAKYONIC_URL || 'http://localhost:8080';
|
|
16
|
+
const apiKey = process.env.TAKYONIC_API_KEY;
|
|
17
|
+
|
|
18
|
+
if (!apiKey) {
|
|
19
|
+
console.error(chalk.red('Error: TAKYONIC_API_KEY environment variable is not set'));
|
|
20
|
+
console.error(chalk.yellow('Please set TAKYONIC_API_KEY to your API key'));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
console.log(chalk.blue('⚡ Takyonic: Fetching schema from server...\n'));
|
|
26
|
+
|
|
27
|
+
const response = await fetch(`${takyonicUrl}/v1/schema`, {
|
|
28
|
+
headers: {
|
|
29
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
if (response.status === 404) {
|
|
35
|
+
console.error(chalk.red('Error: Server endpoint not found'));
|
|
36
|
+
console.error(chalk.yellow('Please ensure Takyonic server is running and accessible'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
const errorText = await response.text();
|
|
40
|
+
console.error(chalk.red(`Error: Server returned status ${response.status}`));
|
|
41
|
+
console.error(chalk.red(errorText));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const schema = (await response.json()) as TableSchema[];
|
|
46
|
+
|
|
47
|
+
if (schema.length === 0) {
|
|
48
|
+
console.log(chalk.yellow('No tables found in the database'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Display schema
|
|
53
|
+
console.log(chalk.bold.cyan('Database Schema:\n'));
|
|
54
|
+
|
|
55
|
+
for (const table of schema) {
|
|
56
|
+
console.log(chalk.bold.magenta(`Table: ${table.name}`));
|
|
57
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
58
|
+
|
|
59
|
+
// Table header
|
|
60
|
+
console.log(
|
|
61
|
+
chalk.bold(
|
|
62
|
+
`${'Column Name'.padEnd(30)} ${'Data Type'.padEnd(20)} ${'Nullable'.padEnd(10)}`
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
console.log(chalk.gray('─'.repeat(80)));
|
|
66
|
+
|
|
67
|
+
// Columns
|
|
68
|
+
for (const col of table.columns) {
|
|
69
|
+
const nullable = col.nullable
|
|
70
|
+
? chalk.yellow('YES')
|
|
71
|
+
: chalk.green('NO');
|
|
72
|
+
console.log(
|
|
73
|
+
`${col.name.padEnd(30)} ${col.data_type.padEnd(20)} ${nullable}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log('');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const totalColumns = schema.reduce((sum, table) => sum + table.columns.length, 0);
|
|
81
|
+
console.log(chalk.gray(`Total: ${schema.length} table(s), ${totalColumns} column(s)\n`));
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(chalk.red('⚡ Takyonic: Error fetching schema:'));
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
if (error.message.includes('fetch')) {
|
|
87
|
+
console.error(chalk.red('Failed to connect to Takyonic server'));
|
|
88
|
+
console.error(chalk.yellow(`Please ensure the server is running at ${takyonicUrl}`));
|
|
89
|
+
console.error(chalk.yellow('You can set TAKYONIC_URL environment variable to specify a different URL'));
|
|
90
|
+
} else {
|
|
91
|
+
console.error(chalk.red(error.message));
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
console.error(chalk.red(String(error)));
|
|
95
|
+
}
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { TakyonicClient, AuthenticationError, NetworkError } from '@takyonic/sdk';
|
|
6
|
+
|
|
7
|
+
export const pullCommand = new Command('pull')
|
|
8
|
+
.description('Pull schema from Takyonic server and save to .takyonic file')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
const takyonicUrl = process.env.TAKYONIC_URL || 'http://localhost:8080';
|
|
11
|
+
const apiKey = process.env.TAKYONIC_API_KEY;
|
|
12
|
+
|
|
13
|
+
if (!apiKey) {
|
|
14
|
+
console.error(chalk.red('Error: TAKYONIC_API_KEY environment variable is not set'));
|
|
15
|
+
console.error(chalk.yellow('Please set TAKYONIC_API_KEY to your API key'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
console.log(chalk.blue('⚡ Takyonic: Pulling schema from server...\n'));
|
|
21
|
+
|
|
22
|
+
// Use SDK to fetch pre-formatted DSL from server
|
|
23
|
+
const client = new TakyonicClient({
|
|
24
|
+
endpoint: takyonicUrl,
|
|
25
|
+
token: apiKey,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Fetch the DSL string directly from the Rust backend
|
|
29
|
+
// This is the authoritative, pre-formatted schema
|
|
30
|
+
const dslContent = await client.schemaDsl();
|
|
31
|
+
|
|
32
|
+
if (!dslContent || dslContent.trim().length === 0) {
|
|
33
|
+
console.log(chalk.yellow('No schema found on the server'));
|
|
34
|
+
console.log(chalk.yellow('Cannot save empty schema.'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Determine output file (.takyonic in project root)
|
|
39
|
+
const outputFile = path.join(process.cwd(), '.takyonic');
|
|
40
|
+
|
|
41
|
+
// Write the pre-formatted DSL directly to file
|
|
42
|
+
fs.writeFileSync(outputFile, dslContent, 'utf-8');
|
|
43
|
+
|
|
44
|
+
// Count tables for display
|
|
45
|
+
const tableCount = (dslContent.match(/^table\s+\w+\s*\{/gm) || []).length;
|
|
46
|
+
|
|
47
|
+
console.log(chalk.green(`✓ Schema pulled successfully`));
|
|
48
|
+
console.log(chalk.gray(` Output: ${outputFile}`));
|
|
49
|
+
console.log(chalk.gray(` Tables: ${tableCount}\n`));
|
|
50
|
+
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(chalk.red('⚡ Takyonic: Error pulling schema:'));
|
|
53
|
+
|
|
54
|
+
if (error instanceof AuthenticationError) {
|
|
55
|
+
console.error(chalk.red('Error: Unauthorized'));
|
|
56
|
+
console.error(chalk.yellow('Please check your TAKYONIC_API_KEY'));
|
|
57
|
+
} else if (error instanceof NetworkError) {
|
|
58
|
+
console.error(chalk.red('Failed to connect to Takyonic server'));
|
|
59
|
+
console.error(chalk.yellow(`Please ensure the server is running at ${takyonicUrl}`));
|
|
60
|
+
console.error(chalk.yellow('You can set TAKYONIC_URL environment variable to specify a different URL'));
|
|
61
|
+
} else if (error instanceof Error) {
|
|
62
|
+
console.error(chalk.red(error.message));
|
|
63
|
+
} else {
|
|
64
|
+
console.error(chalk.red(String(error)));
|
|
65
|
+
}
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { AdminClient, AuthenticationError, ValidationError, NetworkError } from '@takyonic/sdk';
|
|
6
|
+
import { parseTakyonicSchema, ParseError } from '../parser';
|
|
7
|
+
import { dslToJson } from '../dsl-to-json';
|
|
8
|
+
|
|
9
|
+
export const pushCommand = new Command('push')
|
|
10
|
+
.description('Push schema from .takyonic file to Takyonic server')
|
|
11
|
+
.action(async () => {
|
|
12
|
+
const takyonicUrl = process.env.TAKYONIC_URL || 'http://localhost:8080';
|
|
13
|
+
const adminSecret = process.env.TAKYONIC_ADMIN_SECRET;
|
|
14
|
+
|
|
15
|
+
if (!adminSecret) {
|
|
16
|
+
console.error(chalk.red('Error: TAKYONIC_ADMIN_SECRET environment variable is not set'));
|
|
17
|
+
console.error(chalk.yellow('Please set TAKYONIC_ADMIN_SECRET to your admin secret'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Determine input file (.takyonic in project root)
|
|
22
|
+
const inputFile = path.join(process.cwd(), '.takyonic');
|
|
23
|
+
|
|
24
|
+
if (!fs.existsSync(inputFile)) {
|
|
25
|
+
console.error(chalk.red(`Error: ${inputFile} not found`));
|
|
26
|
+
console.error(chalk.yellow('Please run "takyonic init" to create a .takyonic file'));
|
|
27
|
+
console.error(chalk.yellow('Or run "takyonic pull" to fetch schema from server'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
console.log(chalk.blue('⚡ Takyonic: Reading schema from .takyonic file...\n'));
|
|
33
|
+
|
|
34
|
+
// Read and parse DSL file
|
|
35
|
+
const fileContent = fs.readFileSync(inputFile, 'utf-8');
|
|
36
|
+
const dslSchema = parseTakyonicSchema(fileContent);
|
|
37
|
+
|
|
38
|
+
if (!dslSchema.tables || dslSchema.tables.length === 0) {
|
|
39
|
+
console.error(chalk.red('Error: No tables found in schema'));
|
|
40
|
+
console.error(chalk.yellow('Please define at least one table in your .takyonic file'));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Convert DSL to JSON format for server
|
|
45
|
+
const schema = dslToJson(dslSchema);
|
|
46
|
+
|
|
47
|
+
console.log(chalk.blue('⚡ Takyonic Engine: Synchronizing schema...\n'));
|
|
48
|
+
console.log(chalk.gray(` Tables: ${schema.length}\n`));
|
|
49
|
+
|
|
50
|
+
// Use AdminClient from SDK to push schema
|
|
51
|
+
const admin = new AdminClient({
|
|
52
|
+
endpoint: takyonicUrl,
|
|
53
|
+
adminSecret: adminSecret,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const result = await admin.migrate(schema);
|
|
57
|
+
|
|
58
|
+
console.log(chalk.green(`✓ Migration completed successfully`));
|
|
59
|
+
console.log(chalk.gray(` Server: ${takyonicUrl}`));
|
|
60
|
+
|
|
61
|
+
if (result.tables_created && result.tables_created.length > 0) {
|
|
62
|
+
console.log(chalk.green(` Tables created: ${result.tables_created.join(', ')}`));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (result.columns_added && Object.keys(result.columns_added).length > 0) {
|
|
66
|
+
for (const [table, columns] of Object.entries(result.columns_added)) {
|
|
67
|
+
console.log(chalk.yellow(` Columns added to ${table}: ${columns.join(', ')}`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (result.tables_updated && result.tables_updated.length > 0) {
|
|
72
|
+
console.log(chalk.gray(` Tables updated: ${result.tables_updated.join(', ')}`));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (result.message) {
|
|
76
|
+
console.log(chalk.gray(` ${result.message}\n`));
|
|
77
|
+
} else {
|
|
78
|
+
console.log(); // New line
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(chalk.red('⚡ Takyonic: Error pushing schema:'));
|
|
83
|
+
|
|
84
|
+
if (error instanceof ParseError) {
|
|
85
|
+
console.error(chalk.red(`Parse error: ${error.message}`));
|
|
86
|
+
if (error.line) {
|
|
87
|
+
console.error(chalk.yellow(` Line: ${error.line}`));
|
|
88
|
+
}
|
|
89
|
+
console.error(chalk.yellow('Please check your .takyonic file syntax'));
|
|
90
|
+
} else if (error instanceof AuthenticationError) {
|
|
91
|
+
console.error(chalk.red('Error: Unauthorized'));
|
|
92
|
+
console.error(chalk.yellow('Please check your TAKYONIC_ADMIN_SECRET'));
|
|
93
|
+
} else if (error instanceof ValidationError) {
|
|
94
|
+
console.error(chalk.red(`Validation error: ${error.message}`));
|
|
95
|
+
} else if (error instanceof NetworkError) {
|
|
96
|
+
console.error(chalk.red('Failed to connect to Takyonic server'));
|
|
97
|
+
console.error(chalk.yellow(`Please ensure the server is running at ${takyonicUrl}`));
|
|
98
|
+
console.error(chalk.yellow('You can set TAKYONIC_URL environment variable to specify a different URL'));
|
|
99
|
+
} else if (error instanceof Error) {
|
|
100
|
+
console.error(chalk.red(error.message));
|
|
101
|
+
} else {
|
|
102
|
+
console.error(chalk.red(String(error)));
|
|
103
|
+
}
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
});
|