axiodb 16.2.1 → 17.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/README.md CHANGED
@@ -341,7 +341,7 @@ The rule: the `-e AXIODB_TLS_CERT_PATH=...` value must always match the *right-h
341
341
 
342
342
  ## 💻 AxioDB CLI — Command Line Interface
343
343
 
344
- A Go-based CLI tool for interacting with AxioDB servers via the TCP protocol. Supports all database operations with both single-command and interactive REPL modes.
344
+ A Go-based CLI tool for interacting with AxioDB servers via the TCP protocol. Supports all database operations with both single-command and interactive REPL modes. Export and import databases via the HTTP API.
345
345
 
346
346
  ### Quick Install
347
347
 
@@ -399,14 +399,27 @@ axiodb -c axiodb://127.0.0.1:27019 -u admin -p admin connect
399
399
  axiodb -c axiodb://127.0.0.1:27019 --tls --tls-cert ./cert.pem connect
400
400
  ```
401
401
 
402
+ **Export database (via HTTP API):**
403
+ ```bash
404
+ axiodb export mydb --http-host localhost --http-port 27018 -u admin -p secret
405
+ # Saves mydb.tar.gz in current directory
406
+ ```
407
+
408
+ **Import database (via HTTP API):**
409
+ ```bash
410
+ axiodb import ./backups/mydb.tar.gz --http-host localhost --http-port 27018 -u admin -p secret
411
+ # Tab completes .tar.gz file paths
412
+ ```
413
+
402
414
  ### Features
403
415
 
404
- - **All 21 TCP commands:** database, collection, document CRUD, aggregation, indexing
416
+ - **All 23 commands:** database, collection, document CRUD, aggregation, indexing, export, import
405
417
  - **Interactive REPL:** MongoDB shell syntax (`use`, `show dbs`, `db.coll.find()`)
418
+ - **Export & Import:** backup/restore databases via HTTP API, tab-completes file paths
406
419
  - **TLS support:** `--tls`, `--tls-cert`, `--tls-skip-verify`
407
- - **Auth support:** `-u` / `-p` flags
420
+ - **Auth support:** `-u` / `-p` flags (TCP and HTTP)
408
421
  - **JSON output:** `--output json` for scripting
409
- - **Tab autocomplete** in REPL mode
422
+ - **Tab autocomplete** in REPL mode and file path completion
410
423
 
411
424
  👉 **[Full CLI Documentation](https://github.com/nexoral/AxioDB/tree/main/cli)**
412
425
 
package/cli/VERSION CHANGED
@@ -1 +1 @@
1
- 16.2.1
1
+ 17.0.0
@@ -0,0 +1,66 @@
1
+ package cmd
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+
7
+ "github.com/nexoral/axiodb-cli/internal/config"
8
+ "github.com/spf13/cobra"
9
+ )
10
+
11
+ var exportCmd = &cobra.Command{
12
+ Use: "export <dbname>",
13
+ Short: "Export a database to a .tar.gz file via HTTP",
14
+ Long: "Export an AxioDB database to a .tar.gz archive. Uses the HTTP API (port 27018), not TCP.",
15
+ Args: cobra.ExactArgs(1),
16
+ RunE: func(cmd *cobra.Command, args []string) error {
17
+ dbName := args[0]
18
+ cfg, err := config.FromFlags(cmd)
19
+ if err != nil {
20
+ return err
21
+ }
22
+
23
+ if cfg.Username == "" || cfg.Password == "" {
24
+ return fmt.Errorf("username and password required for HTTP auth (-u, -p)")
25
+ }
26
+
27
+ client := cfg.NewHTTPClient()
28
+
29
+ fmt.Fprintf(os.Stderr, "Logging in to %s:%d...\n", cfg.HTTPHost, cfg.HTTPPort)
30
+ if err := client.Login(cfg.Username, cfg.Password); err != nil {
31
+ return err
32
+ }
33
+ defer client.Logout()
34
+
35
+ fmt.Fprintf(os.Stderr, "Exporting %s...\n", dbName)
36
+ filename, size, err := client.Export(dbName)
37
+ if err != nil {
38
+ return err
39
+ }
40
+
41
+ fmt.Fprintf(os.Stderr, "Exported %s (%s)\n", filename, formatBytes(size))
42
+ return nil
43
+ },
44
+ }
45
+
46
+ func formatBytes(bytes int64) string {
47
+ const (
48
+ KB = 1024
49
+ MB = 1024 * KB
50
+ GB = 1024 * MB
51
+ )
52
+ switch {
53
+ case bytes >= GB:
54
+ return fmt.Sprintf("%.1f GB", float64(bytes)/float64(GB))
55
+ case bytes >= MB:
56
+ return fmt.Sprintf("%.1f MB", float64(bytes)/float64(MB))
57
+ case bytes >= KB:
58
+ return fmt.Sprintf("%.1f KB", float64(bytes)/float64(KB))
59
+ default:
60
+ return fmt.Sprintf("%d B", bytes)
61
+ }
62
+ }
63
+
64
+ func init() {
65
+ rootCmd.AddCommand(exportCmd)
66
+ }
@@ -0,0 +1,84 @@
1
+ package cmd
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+
9
+ "github.com/nexoral/axiodb-cli/internal/config"
10
+ "github.com/spf13/cobra"
11
+ )
12
+
13
+ var importCmd = &cobra.Command{
14
+ Use: "import <file>",
15
+ Short: "Import a database from a .tar.gz file via HTTP",
16
+ Long: "Import an AxioDB database from a .tar.gz archive. Uses the HTTP API (port 27018), not TCP.",
17
+ Args: cobra.ExactArgs(1),
18
+ ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
19
+ dir := "."
20
+ prefix := toComplete
21
+
22
+ if idx := strings.LastIndex(toComplete, "/"); idx >= 0 {
23
+ dir = toComplete[:idx+1]
24
+ prefix = toComplete[idx+1:]
25
+ }
26
+
27
+ entries, err := os.ReadDir(dir)
28
+ if err != nil {
29
+ return nil, cobra.ShellCompDirectiveDefault
30
+ }
31
+
32
+ var completions []string
33
+ for _, entry := range entries {
34
+ if entry.IsDir() {
35
+ continue
36
+ }
37
+ name := entry.Name()
38
+ if strings.HasPrefix(name, prefix) && strings.HasSuffix(name, ".tar.gz") {
39
+ completions = append(completions, filepath.Join(dir, name))
40
+ }
41
+ }
42
+ return completions, cobra.ShellCompDirectiveNoFileComp
43
+ },
44
+ RunE: func(cmd *cobra.Command, args []string) error {
45
+ filePath := args[0]
46
+ cfg, err := config.FromFlags(cmd)
47
+ if err != nil {
48
+ return err
49
+ }
50
+
51
+ if cfg.Username == "" || cfg.Password == "" {
52
+ return fmt.Errorf("username and password required for HTTP auth (-u, -p)")
53
+ }
54
+
55
+ info, err := os.Stat(filePath)
56
+ if err != nil {
57
+ return fmt.Errorf("file not found: %s", filePath)
58
+ }
59
+ if info.IsDir() {
60
+ return fmt.Errorf("not a file: %s", filePath)
61
+ }
62
+
63
+ client := cfg.NewHTTPClient()
64
+
65
+ fmt.Fprintf(os.Stderr, "Logging in to %s:%d...\n", cfg.HTTPHost, cfg.HTTPPort)
66
+ if err := client.Login(cfg.Username, cfg.Password); err != nil {
67
+ return err
68
+ }
69
+ defer client.Logout()
70
+
71
+ fmt.Fprintf(os.Stderr, "Importing %s...\n", filepath.Base(filePath))
72
+ dbName, err := client.Import(filePath)
73
+ if err != nil {
74
+ return err
75
+ }
76
+
77
+ fmt.Fprintf(os.Stderr, "Imported database: %s\n", dbName)
78
+ return nil
79
+ },
80
+ }
81
+
82
+ func init() {
83
+ rootCmd.AddCommand(importCmd)
84
+ }
package/cli/cmd/root.go CHANGED
@@ -33,4 +33,6 @@ func init() {
33
33
  rootCmd.PersistentFlags().Int("timeout", 30, "Request timeout in seconds")
34
34
  rootCmd.PersistentFlags().String("db", "", "Database name")
35
35
  rootCmd.PersistentFlags().String("collection", "", "Collection name")
36
+ rootCmd.PersistentFlags().String("http-host", "localhost", "HTTP server host (for export/import)")
37
+ rootCmd.PersistentFlags().Int("http-port", 27018, "HTTP server port (for export/import)")
36
38
  }
@@ -6,6 +6,7 @@ import (
6
6
  "strings"
7
7
  "time"
8
8
 
9
+ "github.com/nexoral/axiodb-cli/pkg/httpclient"
9
10
  "github.com/nexoral/axiodb-cli/pkg/protocol"
10
11
  "github.com/spf13/cobra"
11
12
  )
@@ -23,6 +24,8 @@ type Config struct {
23
24
  Timeout int
24
25
  DB string
25
26
  Collection string
27
+ HTTPHost string
28
+ HTTPPort int
26
29
  }
27
30
 
28
31
  func FromFlags(cmd *cobra.Command) (*Config, error) {
@@ -40,6 +43,8 @@ func FromFlags(cmd *cobra.Command) (*Config, error) {
40
43
  cfg.Timeout, _ = cmd.Flags().GetInt("timeout")
41
44
  cfg.DB, _ = cmd.Flags().GetString("db")
42
45
  cfg.Collection, _ = cmd.Flags().GetString("collection")
46
+ cfg.HTTPHost, _ = cmd.Flags().GetString("http-host")
47
+ cfg.HTTPPort, _ = cmd.Flags().GetInt("http-port")
43
48
 
44
49
  if cfg.ConnString != "" {
45
50
  host, port, err := parseConnectionString(cfg.ConnString)
@@ -109,3 +114,7 @@ func GetCollectionContext(cmd *cobra.Command) string {
109
114
  coll, _ := cmd.Flags().GetString("collection")
110
115
  return coll
111
116
  }
117
+
118
+ func (c *Config) NewHTTPClient() *httpclient.HTTPClient {
119
+ return httpclient.New(c.HTTPHost, c.HTTPPort, time.Duration(c.Timeout)*time.Second)
120
+ }
@@ -0,0 +1,160 @@
1
+ package httpclient
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "fmt"
7
+ "io"
8
+ "mime/multipart"
9
+ "net/http"
10
+ "net/http/cookiejar"
11
+ "os"
12
+ "path/filepath"
13
+ "strings"
14
+ "time"
15
+ )
16
+
17
+ type HTTPClient struct {
18
+ baseURL string
19
+ httpClient *http.Client
20
+ }
21
+
22
+ func New(host string, port int, timeout time.Duration) *HTTPClient {
23
+ jar, _ := cookiejar.New(nil)
24
+ return &HTTPClient{
25
+ baseURL: fmt.Sprintf("http://%s:%d", host, port),
26
+ httpClient: &http.Client{
27
+ Timeout: timeout,
28
+ Jar: jar,
29
+ },
30
+ }
31
+ }
32
+
33
+ func (c *HTTPClient) Login(username, password string) error {
34
+ body, _ := json.Marshal(map[string]string{
35
+ "username": username,
36
+ "password": password,
37
+ })
38
+
39
+ resp, err := c.httpClient.Post(c.baseURL+"/api/auth/login", "application/json", bytes.NewReader(body))
40
+ if err != nil {
41
+ return fmt.Errorf("login request failed: %w", err)
42
+ }
43
+ defer resp.Body.Close()
44
+
45
+ if resp.StatusCode == http.StatusOK {
46
+ return nil
47
+ }
48
+
49
+ var errResp struct {
50
+ Message string `json:"message"`
51
+ }
52
+ json.NewDecoder(resp.Body).Decode(&errResp)
53
+ msg := errResp.Message
54
+ if msg == "" {
55
+ msg = resp.Status
56
+ }
57
+ return fmt.Errorf("login failed: %s", msg)
58
+ }
59
+
60
+ func (c *HTTPClient) Logout() error {
61
+ resp, err := c.httpClient.Post(c.baseURL+"/api/auth/logout", "application/json", nil)
62
+ if err != nil {
63
+ return err
64
+ }
65
+ defer resp.Body.Close()
66
+ return nil
67
+ }
68
+
69
+ func (c *HTTPClient) Export(dbName string) (string, int64, error) {
70
+ url := fmt.Sprintf("%s/api/db/export-database/?dbName=%s", c.baseURL, dbName)
71
+ resp, err := c.httpClient.Get(url)
72
+ if err != nil {
73
+ return "", 0, fmt.Errorf("export request failed: %w", err)
74
+ }
75
+ defer resp.Body.Close()
76
+
77
+ if resp.StatusCode != http.StatusOK {
78
+ var errResp struct {
79
+ Message string `json:"message"`
80
+ }
81
+ json.NewDecoder(resp.Body).Decode(&errResp)
82
+ msg := errResp.Message
83
+ if msg == "" {
84
+ msg = resp.Status
85
+ }
86
+ return "", 0, fmt.Errorf("export failed: %s", msg)
87
+ }
88
+
89
+ filename := dbName + ".tar.gz"
90
+ if cd := resp.Header.Get("Content-Disposition"); cd != "" {
91
+ if idx := strings.Index(cd, "filename="); idx >= 0 {
92
+ name := cd[idx+9:]
93
+ name = strings.Trim(name, "\"")
94
+ if name != "" {
95
+ filename = filepath.Base(name)
96
+ }
97
+ }
98
+ }
99
+
100
+ out, err := os.Create(filename)
101
+ if err != nil {
102
+ return "", 0, fmt.Errorf("create file: %w", err)
103
+ }
104
+ defer out.Close()
105
+
106
+ written, err := io.Copy(out, resp.Body)
107
+ if err != nil {
108
+ os.Remove(filename)
109
+ return "", 0, fmt.Errorf("write file: %w", err)
110
+ }
111
+
112
+ return filename, written, nil
113
+ }
114
+
115
+ func (c *HTTPClient) Import(filePath string) (string, error) {
116
+ file, err := os.Open(filePath)
117
+ if err != nil {
118
+ return "", fmt.Errorf("open file: %w", err)
119
+ }
120
+ defer file.Close()
121
+
122
+ var body bytes.Buffer
123
+ writer := multipart.NewWriter(&body)
124
+ part, err := writer.CreateFormFile("file", filepath.Base(filePath))
125
+ if err != nil {
126
+ return "", fmt.Errorf("create form file: %w", err)
127
+ }
128
+ if _, err := io.Copy(part, file); err != nil {
129
+ return "", fmt.Errorf("write form data: %w", err)
130
+ }
131
+ writer.Close()
132
+
133
+ resp, err := c.httpClient.Post(
134
+ c.baseURL+"/api/db/import-database/",
135
+ writer.FormDataContentType(),
136
+ &body,
137
+ )
138
+ if err != nil {
139
+ return "", fmt.Errorf("import request failed: %w", err)
140
+ }
141
+ defer resp.Body.Close()
142
+
143
+ var result struct {
144
+ Message string `json:"message"`
145
+ Database string `json:"database"`
146
+ }
147
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
148
+ return "", fmt.Errorf("parse response: %w", err)
149
+ }
150
+
151
+ if resp.StatusCode != http.StatusOK {
152
+ msg := result.Message
153
+ if msg == "" {
154
+ msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
155
+ }
156
+ return "", fmt.Errorf("import failed: %s", msg)
157
+ }
158
+
159
+ return result.Database, nil
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axiodb",
3
- "version": "16.2.1",
3
+ "version": "17.0.0",
4
4
  "description": "The Pure JavaScript Alternative to SQLite. Embedded NoSQL database for Node.js with MongoDB-style queries, zero native dependencies, built-in InMemoryCache, and web GUI. Perfect for desktop apps, CLI tools, and embedded systems. No compilation, no platform issues—pure JavaScript from npm install to production.",
5
5
  "main": "./lib/config/DB.js",
6
6
  "types": "./lib/config/DB.d.ts",