vecforge 0.2.0__py3-none-any.whl
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.
- vecforge/__init__.py +59 -0
- vecforge/cli/__init__.py +3 -0
- vecforge/cli/main.py +197 -0
- vecforge/core/__init__.py +3 -0
- vecforge/core/bm25.py +187 -0
- vecforge/core/embedder.py +152 -0
- vecforge/core/indexer.py +196 -0
- vecforge/core/reranker.py +120 -0
- vecforge/core/storage.py +493 -0
- vecforge/core/vault.py +760 -0
- vecforge/exceptions.py +164 -0
- vecforge/ingest/__init__.py +3 -0
- vecforge/ingest/dispatcher.py +181 -0
- vecforge/ingest/document.py +237 -0
- vecforge/search/__init__.py +3 -0
- vecforge/search/cascade.py +186 -0
- vecforge/search/filters.py +146 -0
- vecforge/search/hybrid.py +146 -0
- vecforge/security/__init__.py +3 -0
- vecforge/security/audit.py +169 -0
- vecforge/security/encryption.py +84 -0
- vecforge/security/namespaces.py +127 -0
- vecforge/security/rbac.py +172 -0
- vecforge/security/snapshots.py +135 -0
- vecforge/server/__init__.py +3 -0
- vecforge/server/app.py +54 -0
- vecforge/server/routes.py +215 -0
- vecforge-0.2.0.dist-info/METADATA +302 -0
- vecforge-0.2.0.dist-info/RECORD +34 -0
- vecforge-0.2.0.dist-info/WHEEL +5 -0
- vecforge-0.2.0.dist-info/entry_points.txt +2 -0
- vecforge-0.2.0.dist-info/licenses/LICENSE +45 -0
- vecforge-0.2.0.dist-info/licenses/NOTICE +14 -0
- vecforge-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# VecForge — Universal Local-First Vector Database
|
|
2
|
+
# Copyright (c) 2026 Suneel Bose K · ArcGX TechLabs Private Limited
|
|
3
|
+
# Built by Suneel Bose K (Founder & CEO, ArcGX TechLabs)
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Business Source License 1.1 (BSL 1.1)
|
|
6
|
+
# Free for personal, research, open-source, and non-commercial use.
|
|
7
|
+
# Commercial use requires a separate license from ArcGX TechLabs.
|
|
8
|
+
# See LICENSE file in the project root or contact: suneelbose@arcgx.in
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
SQLCipher AES-256 key management for VecForge.
|
|
12
|
+
|
|
13
|
+
Handles encryption key validation, SQLCipher PRAGMA setup, and
|
|
14
|
+
graceful fallback when SQLCipher C library is not available.
|
|
15
|
+
|
|
16
|
+
Built by Suneel Bose K · ArcGX TechLabs Private Limited.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import warnings
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_sqlcipher_available() -> bool:
|
|
28
|
+
"""Check if SQLCipher is available on this system.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
True if sqlcipher3 can be imported, False otherwise.
|
|
32
|
+
|
|
33
|
+
Performance:
|
|
34
|
+
Time: O(1)
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
import sqlcipher3 # noqa: F401
|
|
38
|
+
|
|
39
|
+
return True
|
|
40
|
+
except ImportError:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def validate_encryption_key(key: str | None) -> str | None:
|
|
45
|
+
"""Validate and normalize an encryption key.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
key: User-provided encryption key or None.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Validated key string, or None if no encryption.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If key is empty or too short.
|
|
55
|
+
|
|
56
|
+
Performance:
|
|
57
|
+
Time: O(1)
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> validate_encryption_key("my-secure-key-here")
|
|
61
|
+
'my-secure-key-here'
|
|
62
|
+
>>> validate_encryption_key("")
|
|
63
|
+
ValueError: Encryption key must be at least 8 characters...
|
|
64
|
+
"""
|
|
65
|
+
if key is None:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
if not key or len(key) < 8:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
"Encryption key must be at least 8 characters long.\n"
|
|
71
|
+
"Use a strong key: os.environ['VECFORGE_KEY']\n"
|
|
72
|
+
"VecForge by Suneel Bose K · ArcGX TechLabs — docs: vecforge.arcgx.in"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if not check_sqlcipher_available():
|
|
76
|
+
warnings.warn(
|
|
77
|
+
"SQLCipher is not installed — data will NOT be encrypted.\n"
|
|
78
|
+
"Install sqlcipher3 for AES-256 encryption: pip install sqlcipher3\n"
|
|
79
|
+
"VecForge by Suneel Bose K · ArcGX TechLabs",
|
|
80
|
+
UserWarning,
|
|
81
|
+
stacklevel=2,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return key
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# VecForge — Universal Local-First Vector Database
|
|
2
|
+
# Copyright (c) 2026 Suneel Bose K · ArcGX TechLabs Private Limited
|
|
3
|
+
# Built by Suneel Bose K (Founder & CEO, ArcGX TechLabs)
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Business Source License 1.1 (BSL 1.1)
|
|
6
|
+
# Free for personal, research, open-source, and non-commercial use.
|
|
7
|
+
# Commercial use requires a separate license from ArcGX TechLabs.
|
|
8
|
+
# See LICENSE file in the project root or contact: suneelbose@arcgx.in
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Multi-tenant namespace isolation for VecForge.
|
|
12
|
+
|
|
13
|
+
Ensures that data operations are always scoped to a namespace,
|
|
14
|
+
preventing cross-tenant data leaks.
|
|
15
|
+
|
|
16
|
+
Built by Suneel Bose K · ArcGX TechLabs Private Limited.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
|
|
23
|
+
from vecforge.core.storage import StorageBackend
|
|
24
|
+
from vecforge.exceptions import NamespaceNotFoundError
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class NamespaceManager:
|
|
30
|
+
"""Multi-tenant namespace manager.
|
|
31
|
+
|
|
32
|
+
Every data operation flows through the namespace manager to
|
|
33
|
+
guarantee tenant isolation. The 'default' namespace always exists.
|
|
34
|
+
|
|
35
|
+
Built by Suneel Bose K · ArcGX TechLabs Private Limited.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
storage: StorageBackend instance.
|
|
39
|
+
|
|
40
|
+
Performance:
|
|
41
|
+
Namespace check: O(1) with cached list
|
|
42
|
+
Create/list: O(K) where K = number of namespaces
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> nsm = NamespaceManager(storage)
|
|
46
|
+
>>> nsm.create("ward_7")
|
|
47
|
+
>>> nsm.validate("ward_7") # OK
|
|
48
|
+
>>> nsm.validate("ward_99") # raises NamespaceNotFoundError
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, storage: StorageBackend) -> None:
|
|
52
|
+
self._storage = storage
|
|
53
|
+
self._cache: set[str] | None = None
|
|
54
|
+
|
|
55
|
+
def _refresh_cache(self) -> None:
|
|
56
|
+
"""Refresh the namespace cache from storage.
|
|
57
|
+
|
|
58
|
+
Performance:
|
|
59
|
+
Time: O(K) where K = number of namespaces
|
|
60
|
+
"""
|
|
61
|
+
self._cache = set(self._storage.list_namespaces())
|
|
62
|
+
|
|
63
|
+
def validate(self, namespace: str) -> None:
|
|
64
|
+
"""Validate that a namespace exists.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
namespace: Namespace name to validate.
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
NamespaceNotFoundError: If namespace does not exist.
|
|
71
|
+
|
|
72
|
+
Performance:
|
|
73
|
+
Time: O(1) with cache
|
|
74
|
+
"""
|
|
75
|
+
if self._cache is None:
|
|
76
|
+
self._refresh_cache()
|
|
77
|
+
|
|
78
|
+
assert self._cache is not None
|
|
79
|
+
if namespace not in self._cache:
|
|
80
|
+
# why: Refresh cache in case namespace was created elsewhere
|
|
81
|
+
self._refresh_cache()
|
|
82
|
+
if namespace not in self._cache:
|
|
83
|
+
raise NamespaceNotFoundError(namespace, sorted(self._cache))
|
|
84
|
+
|
|
85
|
+
def create(self, name: str) -> None:
|
|
86
|
+
"""Create a new namespace.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
name: Namespace name.
|
|
90
|
+
|
|
91
|
+
Performance:
|
|
92
|
+
Time: O(1)
|
|
93
|
+
"""
|
|
94
|
+
self._storage.create_namespace(name)
|
|
95
|
+
if self._cache is not None:
|
|
96
|
+
self._cache.add(name)
|
|
97
|
+
logger.info("Created namespace: %s", name)
|
|
98
|
+
|
|
99
|
+
def list_all(self) -> list[str]:
|
|
100
|
+
"""List all namespace names.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Sorted list of namespace names.
|
|
104
|
+
|
|
105
|
+
Performance:
|
|
106
|
+
Time: O(K log K)
|
|
107
|
+
"""
|
|
108
|
+
self._refresh_cache()
|
|
109
|
+
assert self._cache is not None
|
|
110
|
+
return sorted(self._cache)
|
|
111
|
+
|
|
112
|
+
def exists(self, namespace: str) -> bool:
|
|
113
|
+
"""Check if a namespace exists.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
namespace: Namespace name.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
True if namespace exists.
|
|
120
|
+
|
|
121
|
+
Performance:
|
|
122
|
+
Time: O(1) with cache
|
|
123
|
+
"""
|
|
124
|
+
if self._cache is None:
|
|
125
|
+
self._refresh_cache()
|
|
126
|
+
assert self._cache is not None
|
|
127
|
+
return namespace in self._cache
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# VecForge — Universal Local-First Vector Database
|
|
2
|
+
# Copyright (c) 2026 Suneel Bose K · ArcGX TechLabs Private Limited
|
|
3
|
+
# Built by Suneel Bose K (Founder & CEO, ArcGX TechLabs)
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Business Source License 1.1 (BSL 1.1)
|
|
6
|
+
# Free for personal, research, open-source, and non-commercial use.
|
|
7
|
+
# Commercial use requires a separate license from ArcGX TechLabs.
|
|
8
|
+
# See LICENSE file in the project root or contact: suneelbose@arcgx.in
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Role-based access control (RBAC) for VecForge.
|
|
12
|
+
|
|
13
|
+
Provides API key → role mapping with permission enforcement.
|
|
14
|
+
Every write operation must check permissions before executing.
|
|
15
|
+
|
|
16
|
+
Built by Suneel Bose K · ArcGX TechLabs Private Limited.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
|
|
23
|
+
from vecforge.exceptions import VecForgePermissionError
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
# security: Role hierarchy — higher roles inherit all lower permissions
|
|
28
|
+
_ROLE_PERMISSIONS: dict[str, set[str]] = {
|
|
29
|
+
"admin": {"read", "write", "delete", "create_namespace", "manage_keys", "backup"},
|
|
30
|
+
"read-write": {"read", "write", "delete"},
|
|
31
|
+
"read-only": {"read"},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# why: Default role when no API key is provided — full access for local use
|
|
35
|
+
_DEFAULT_ROLE = "admin"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RBACManager:
|
|
39
|
+
"""Role-based access control manager.
|
|
40
|
+
|
|
41
|
+
Maps API keys to roles and enforces permission checks. When no
|
|
42
|
+
API key is provided, defaults to admin role (local-first trust).
|
|
43
|
+
|
|
44
|
+
Built by Suneel Bose K · ArcGX TechLabs Private Limited.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
api_key: Current user's API key. None = local admin.
|
|
48
|
+
key_roles: Mapping of API key → role name.
|
|
49
|
+
|
|
50
|
+
Performance:
|
|
51
|
+
Permission check: O(1)
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
>>> rbac = RBACManager(api_key="key123", key_roles={"key123": "read-only"})
|
|
55
|
+
>>> rbac.require("read") # OK
|
|
56
|
+
>>> rbac.require("write") # raises VecForgePermissionError
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
api_key: str | None = None,
|
|
62
|
+
key_roles: dict[str, str] | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
self._api_key = api_key
|
|
65
|
+
self._key_roles = key_roles or {}
|
|
66
|
+
self._current_role = self._resolve_role()
|
|
67
|
+
|
|
68
|
+
def _resolve_role(self) -> str:
|
|
69
|
+
"""Resolve the current user's role from their API key.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Role name string.
|
|
73
|
+
|
|
74
|
+
Performance:
|
|
75
|
+
Time: O(1)
|
|
76
|
+
"""
|
|
77
|
+
if self._api_key is None:
|
|
78
|
+
# why: No API key = local use = admin privileges
|
|
79
|
+
return _DEFAULT_ROLE
|
|
80
|
+
|
|
81
|
+
role = self._key_roles.get(self._api_key, "read-only")
|
|
82
|
+
if role not in _ROLE_PERMISSIONS:
|
|
83
|
+
logger.warning(
|
|
84
|
+
"Unknown role '%s' for key '%s' — defaulting to read-only",
|
|
85
|
+
role,
|
|
86
|
+
self._api_key[:8] + "...",
|
|
87
|
+
)
|
|
88
|
+
role = "read-only"
|
|
89
|
+
|
|
90
|
+
return role
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def current_role(self) -> str:
|
|
94
|
+
"""Return the current user's role.
|
|
95
|
+
|
|
96
|
+
Performance:
|
|
97
|
+
Time: O(1)
|
|
98
|
+
"""
|
|
99
|
+
return self._current_role
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def key_id(self) -> str:
|
|
103
|
+
"""Return a safe identifier for the current key (for audit logs).
|
|
104
|
+
|
|
105
|
+
Performance:
|
|
106
|
+
Time: O(1)
|
|
107
|
+
"""
|
|
108
|
+
if self._api_key is None:
|
|
109
|
+
return "local-admin"
|
|
110
|
+
# security: Never log full API keys
|
|
111
|
+
return self._api_key[:8] + "..." if len(self._api_key) > 8 else "***"
|
|
112
|
+
|
|
113
|
+
def require(self, permission: str) -> None:
|
|
114
|
+
"""Check if current role has the required permission.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
permission: Required permission (read, write, delete, etc.).
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
VecForgePermissionError: If current role lacks the permission.
|
|
121
|
+
|
|
122
|
+
Performance:
|
|
123
|
+
Time: O(1)
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
>>> rbac.require("write") # raises if read-only
|
|
127
|
+
"""
|
|
128
|
+
permissions = _ROLE_PERMISSIONS.get(self._current_role, set())
|
|
129
|
+
if permission not in permissions:
|
|
130
|
+
raise VecForgePermissionError(permission, self._current_role)
|
|
131
|
+
|
|
132
|
+
def has_permission(self, permission: str) -> bool:
|
|
133
|
+
"""Check if current role has a permission without raising.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
permission: Permission to check.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if permission is granted, False otherwise.
|
|
140
|
+
|
|
141
|
+
Performance:
|
|
142
|
+
Time: O(1)
|
|
143
|
+
"""
|
|
144
|
+
permissions = _ROLE_PERMISSIONS.get(self._current_role, set())
|
|
145
|
+
return permission in permissions
|
|
146
|
+
|
|
147
|
+
def register_key(self, api_key: str, role: str) -> None:
|
|
148
|
+
"""Register a new API key with a role.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
api_key: The API key to register.
|
|
152
|
+
role: Role to assign (admin, read-write, read-only).
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
VecForgePermissionError: If current user is not admin.
|
|
156
|
+
ValueError: If role is not valid.
|
|
157
|
+
|
|
158
|
+
Performance:
|
|
159
|
+
Time: O(1)
|
|
160
|
+
"""
|
|
161
|
+
# security: Only admins can register new keys
|
|
162
|
+
self.require("manage_keys")
|
|
163
|
+
|
|
164
|
+
if role not in _ROLE_PERMISSIONS:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"Invalid role '{role}'.\n"
|
|
167
|
+
f"Valid roles: {', '.join(_ROLE_PERMISSIONS.keys())}\n"
|
|
168
|
+
f"VecForge by Suneel Bose K · ArcGX TechLabs"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
self._key_roles[api_key] = role
|
|
172
|
+
logger.info("Registered key %s... with role '%s'", api_key[:8], role)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# VecForge — Universal Local-First Vector Database
|
|
2
|
+
# Copyright (c) 2026 Suneel Bose K · ArcGX TechLabs Private Limited
|
|
3
|
+
# Built by Suneel Bose K (Founder & CEO, ArcGX TechLabs)
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Business Source License 1.1 (BSL 1.1)
|
|
6
|
+
# Free for personal, research, open-source, and non-commercial use.
|
|
7
|
+
# Commercial use requires a separate license from ArcGX TechLabs.
|
|
8
|
+
# See LICENSE file in the project root or contact: suneelbose@arcgx.in
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Backup and restore snapshots for VecForge vaults.
|
|
12
|
+
|
|
13
|
+
Creates complete vault snapshots preserving encryption state and
|
|
14
|
+
all data. Supports point-in-time recovery.
|
|
15
|
+
|
|
16
|
+
Built by Suneel Bose K · ArcGX TechLabs Private Limited.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
import shutil
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SnapshotManager:
|
|
30
|
+
"""Vault backup and restore manager.
|
|
31
|
+
|
|
32
|
+
Creates full snapshots of the vault database file. Encryption
|
|
33
|
+
state is preserved — encrypted vaults remain encrypted in snapshots.
|
|
34
|
+
|
|
35
|
+
Built by Suneel Bose K · ArcGX TechLabs Private Limited.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
vault_path: Path to the vault database file.
|
|
39
|
+
|
|
40
|
+
Performance:
|
|
41
|
+
Snapshot: O(file_size) — full file copy
|
|
42
|
+
Restore: O(file_size) — full file copy
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> snap = SnapshotManager("my_vault.db")
|
|
46
|
+
>>> snap.create_snapshot("backups/")
|
|
47
|
+
'backups/my_vault_20240101_120000.db'
|
|
48
|
+
>>> snap.restore_snapshot("backups/my_vault_20240101_120000.db")
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, vault_path: str) -> None:
|
|
52
|
+
self._vault_path = Path(vault_path)
|
|
53
|
+
|
|
54
|
+
def create_snapshot(self, backup_dir: str) -> str:
|
|
55
|
+
"""Create a timestamped snapshot of the vault.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
backup_dir: Directory to store the snapshot file.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Path to the created snapshot file.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
FileNotFoundError: If vault file does not exist.
|
|
65
|
+
|
|
66
|
+
Performance:
|
|
67
|
+
Time: O(file_size)
|
|
68
|
+
"""
|
|
69
|
+
if not self._vault_path.exists():
|
|
70
|
+
raise FileNotFoundError(
|
|
71
|
+
f"Vault file not found: {self._vault_path}\n"
|
|
72
|
+
f"VecForge by Suneel Bose K · ArcGX TechLabs"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
backup_path = Path(backup_dir)
|
|
76
|
+
backup_path.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
|
|
78
|
+
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
|
79
|
+
stem = self._vault_path.stem
|
|
80
|
+
suffix = self._vault_path.suffix
|
|
81
|
+
snapshot_name = f"{stem}_{timestamp}{suffix}"
|
|
82
|
+
snapshot_path = backup_path / snapshot_name
|
|
83
|
+
|
|
84
|
+
shutil.copy2(str(self._vault_path), str(snapshot_path))
|
|
85
|
+
logger.info("Snapshot created: %s", snapshot_path)
|
|
86
|
+
|
|
87
|
+
return str(snapshot_path)
|
|
88
|
+
|
|
89
|
+
def restore_snapshot(self, snapshot_path: str) -> None:
|
|
90
|
+
"""Restore vault from a snapshot.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
snapshot_path: Path to the snapshot file to restore.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
FileNotFoundError: If snapshot file does not exist.
|
|
97
|
+
|
|
98
|
+
Performance:
|
|
99
|
+
Time: O(file_size)
|
|
100
|
+
"""
|
|
101
|
+
source = Path(snapshot_path)
|
|
102
|
+
if not source.exists():
|
|
103
|
+
raise FileNotFoundError(
|
|
104
|
+
f"Snapshot file not found: {snapshot_path}\n"
|
|
105
|
+
f"VecForge by Suneel Bose K · ArcGX TechLabs"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
shutil.copy2(str(source), str(self._vault_path))
|
|
109
|
+
logger.info("Vault restored from: %s", snapshot_path)
|
|
110
|
+
|
|
111
|
+
def list_snapshots(self, backup_dir: str) -> list[str]:
|
|
112
|
+
"""List available snapshots in a backup directory.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
backup_dir: Directory containing snapshots.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Sorted list of snapshot file paths (newest first).
|
|
119
|
+
|
|
120
|
+
Performance:
|
|
121
|
+
Time: O(S log S) where S = number of snapshots
|
|
122
|
+
"""
|
|
123
|
+
backup_path = Path(backup_dir)
|
|
124
|
+
if not backup_path.exists():
|
|
125
|
+
return []
|
|
126
|
+
|
|
127
|
+
stem = self._vault_path.stem
|
|
128
|
+
suffix = self._vault_path.suffix
|
|
129
|
+
pattern = f"{stem}_*{suffix}"
|
|
130
|
+
|
|
131
|
+
snapshots = sorted(
|
|
132
|
+
[str(p) for p in backup_path.glob(pattern)],
|
|
133
|
+
reverse=True,
|
|
134
|
+
)
|
|
135
|
+
return snapshots
|
vecforge/server/app.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# VecForge — Universal Local-First Vector Database
|
|
2
|
+
# Copyright (c) 2026 Suneel Bose K · ArcGX TechLabs Private Limited
|
|
3
|
+
# Built by Suneel Bose K (Founder & CEO, ArcGX TechLabs)
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Business Source License 1.1 (BSL 1.1)
|
|
6
|
+
# Free for personal, research, open-source, and non-commercial use.
|
|
7
|
+
# Commercial use requires a separate license from ArcGX TechLabs.
|
|
8
|
+
# See LICENSE file in the project root or contact: suneelbose@arcgx.in
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
FastAPI REST server for VecForge.
|
|
12
|
+
|
|
13
|
+
Provides a REST API for vault operations. Designed for local-first
|
|
14
|
+
deployment — no cloud dependency required.
|
|
15
|
+
|
|
16
|
+
Built by Suneel Bose K · ArcGX TechLabs Private Limited.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from fastapi import FastAPI
|
|
22
|
+
|
|
23
|
+
from vecforge.server.routes import create_router
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_app(vault_path: str = ":memory:") -> FastAPI:
|
|
27
|
+
"""Create the FastAPI application.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
vault_path: Path to the vault database.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
FastAPI application instance.
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> app = create_app("my_vault.db")
|
|
37
|
+
>>> # Run with: uvicorn vecforge.server.app:app
|
|
38
|
+
"""
|
|
39
|
+
app = FastAPI(
|
|
40
|
+
title="VecForge API",
|
|
41
|
+
description=(
|
|
42
|
+
"VecForge — Universal Local-First Vector Database REST API.\n\n"
|
|
43
|
+
"Built by Suneel Bose K · ArcGX TechLabs Private Limited.\n"
|
|
44
|
+
"Licensed under BSL 1.1."
|
|
45
|
+
),
|
|
46
|
+
version="0.2.0",
|
|
47
|
+
docs_url="/docs",
|
|
48
|
+
redoc_url="/redoc",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
router = create_router(vault_path)
|
|
52
|
+
app.include_router(router)
|
|
53
|
+
|
|
54
|
+
return app
|