sqlite-sync-core 0.1.0__tar.gz
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.
- sqlite_sync_core-0.1.0/LICENSE +46 -0
- sqlite_sync_core-0.1.0/PKG-INFO +326 -0
- sqlite_sync_core-0.1.0/README.md +297 -0
- sqlite_sync_core-0.1.0/pyproject.toml +54 -0
- sqlite_sync_core-0.1.0/setup.cfg +4 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/__init__.py +24 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/audit/__init__.py +19 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/audit/import_log.py +159 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/bundle/__init__.py +26 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/bundle/format.py +97 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/bundle/generate.py +200 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/bundle/validate.py +205 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/capture/__init__.py +17 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/capture/change_capture.py +90 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/config.py +48 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/db/__init__.py +38 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/db/connection.py +164 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/db/migrations.py +232 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/db/schema.py +183 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/db/triggers.py +362 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/engine.py +541 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/errors.py +176 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/import_apply/__init__.py +43 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/import_apply/apply.py +185 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/import_apply/conflict.py +223 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/import_apply/dedup.py +67 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/import_apply/ordering.py +70 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/invariants.py +193 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/log/__init__.py +47 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/log/operations.py +350 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/log/vector_clock.py +218 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/network/client.py +79 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/network/protocol.py +22 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/network/server.py +43 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/utils/__init__.py +4 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/utils/hashing.py +114 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/utils/msgpack_codec.py +158 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync/utils/uuid7.py +117 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync_core.egg-info/PKG-INFO +326 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync_core.egg-info/SOURCES.txt +46 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync_core.egg-info/dependency_links.txt +1 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync_core.egg-info/requires.txt +6 -0
- sqlite_sync_core-0.1.0/src/sqlite_sync_core.egg-info/top_level.txt +1 -0
- sqlite_sync_core-0.1.0/tests/test_conflicts.py +307 -0
- sqlite_sync_core-0.1.0/tests/test_determinism.py +224 -0
- sqlite_sync_core-0.1.0/tests/test_idempotency.py +189 -0
- sqlite_sync_core-0.1.0/tests/test_invariants.py +171 -0
- sqlite_sync_core-0.1.0/tests/test_minimal.py +3 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# SQLite Sync Core License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 VisionQuantech, India
|
|
4
|
+
|
|
5
|
+
## Dual License
|
|
6
|
+
|
|
7
|
+
This software is available under two license options:
|
|
8
|
+
|
|
9
|
+
### 1. Open Source License (AGPL-3.0)
|
|
10
|
+
|
|
11
|
+
For **non-commercial** and **open-source projects**, this software is licensed under the [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.html).
|
|
12
|
+
|
|
13
|
+
You may use, modify, and distribute this software freely, provided that:
|
|
14
|
+
|
|
15
|
+
- Your project is also open source under a compatible license
|
|
16
|
+
- You include this license notice
|
|
17
|
+
- Any modifications are also open sourced under AGPL-3.0
|
|
18
|
+
- Network use counts as distribution (SaaS must open source)
|
|
19
|
+
|
|
20
|
+
### 2. Commercial License
|
|
21
|
+
|
|
22
|
+
For **commercial use**, **proprietary software**, or **closed-source projects**, you must purchase a commercial license.
|
|
23
|
+
|
|
24
|
+
**Commercial use includes:**
|
|
25
|
+
|
|
26
|
+
- Using this library in proprietary/closed-source software
|
|
27
|
+
- Using this library in SaaS products without open-sourcing
|
|
28
|
+
- Any use where you cannot comply with AGPL-3.0 terms
|
|
29
|
+
|
|
30
|
+
**Contact for licensing:**
|
|
31
|
+
|
|
32
|
+
- Email: <shivaysinghrajput@proton.me>
|
|
33
|
+
- Email: <shivaysinghrajput@outlook.com>
|
|
34
|
+
- Email: <vbs.visionquanteh@proton.me>
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
**AGPL-3.0 Summary:**
|
|
39
|
+
|
|
40
|
+
- ✅ Free for personal projects
|
|
41
|
+
- ✅ Free for open-source projects (with AGPL-3.0 compatible license)
|
|
42
|
+
- ✅ Free for educational/research use
|
|
43
|
+
- ❌ NOT free for closed-source commercial products
|
|
44
|
+
- ❌ NOT free for proprietary SaaS without open-sourcing
|
|
45
|
+
|
|
46
|
+
For the full AGPL-3.0 license text, see: <https://www.gnu.org/licenses/agpl-3.0.html>
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlite-sync-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Universal SQLite Synchronization Core - A dependency-grade, local-first, offline-first SQLite synchronization primitive
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/shivay00001/sqlite-sync-core
|
|
7
|
+
Project-URL: Documentation, https://github.com/shivay00001/sqlite-sync-core#readme
|
|
8
|
+
Project-URL: Repository, https://github.com/shivay00001/sqlite-sync-core.git
|
|
9
|
+
Project-URL: Issues, https://github.com/shivay00001/sqlite-sync-core/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Database
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: msgpack>=1.0.0
|
|
24
|
+
Requires-Dist: websockets>=12.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Universal SQLite Synchronization Core
|
|
31
|
+
|
|
32
|
+
[](https://www.python.org/downloads/)
|
|
33
|
+
[](https://www.gnu.org/licenses/agpl-3.0)
|
|
34
|
+
[](https://github.com/shivay00001/sqlite-sync-core)
|
|
35
|
+
|
|
36
|
+
**A product of [VisionQuantech](https://github.com/shivay00001), India 🇮🇳**
|
|
37
|
+
|
|
38
|
+
**A dependency-grade, local-first, offline-first SQLite synchronization primitive.**
|
|
39
|
+
|
|
40
|
+
Captures database changes as structured operations, packages them into self-contained bundles, and applies them deterministically across disconnected devices.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- 🔒 **Append-only log** – Operations are immutable history
|
|
47
|
+
- 🕐 **Vector clocks** – Causality tracking across devices
|
|
48
|
+
- ⚔️ **Conflict detection** – Never auto-merges, preserves conflicts
|
|
49
|
+
- 🔄 **Deterministic replay** – Same operations = same result everywhere
|
|
50
|
+
- 📦 **Transport agnostic** – Bundles work over USB, email, Bluetooth, anything
|
|
51
|
+
- 🚫 **Zero infrastructure** – No servers, no cloud, no network required
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
### From PyPI
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install sqlite-sync-core
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### From GitHub (Development)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/shivay00001/sqlite-sync-core.git
|
|
67
|
+
cd sqlite-sync-core
|
|
68
|
+
pip install -e .
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Real-Time Network Sync
|
|
74
|
+
|
|
75
|
+
SQLite Sync Core now supports real-time synchronization over WebSockets.
|
|
76
|
+
|
|
77
|
+
### Start the Reference Server
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
python -m sqlite_sync.network.server
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Connect a Client
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from sqlite_sync import SyncEngine
|
|
87
|
+
from sqlite_sync.network.client import SyncClient
|
|
88
|
+
|
|
89
|
+
engine = SyncEngine("my_app.db")
|
|
90
|
+
engine.initialize()
|
|
91
|
+
|
|
92
|
+
client = SyncClient(engine, "ws://localhost:8765")
|
|
93
|
+
await client.connect()
|
|
94
|
+
|
|
95
|
+
# Listen for remote changes
|
|
96
|
+
asyncio.create_task(client.listen())
|
|
97
|
+
|
|
98
|
+
# Send local changes
|
|
99
|
+
ops = engine.get_new_operations()
|
|
100
|
+
for op in ops:
|
|
101
|
+
await client.send_operation(op)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Integration Examples
|
|
107
|
+
|
|
108
|
+
See the `examples/` directory for full integration samples:
|
|
109
|
+
|
|
110
|
+
- `basic_usage.py`: Simple CLI setup.
|
|
111
|
+
- `network_sync.py`: Real-time sync between two peers.
|
|
112
|
+
- `desktop_example.py`: Coming soon (GUI integration).
|
|
113
|
+
|
|
114
|
+
### Requirements
|
|
115
|
+
|
|
116
|
+
- Python 3.11+
|
|
117
|
+
- `msgpack` (auto-installed)
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Quick Start
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from sqlite_sync import SyncEngine
|
|
125
|
+
|
|
126
|
+
# Initialize sync-enabled database
|
|
127
|
+
engine = SyncEngine("my_database.db")
|
|
128
|
+
engine.initialize()
|
|
129
|
+
|
|
130
|
+
# Create a user table
|
|
131
|
+
engine.connection.execute("""
|
|
132
|
+
CREATE TABLE todos (
|
|
133
|
+
id INTEGER PRIMARY KEY,
|
|
134
|
+
title TEXT NOT NULL,
|
|
135
|
+
done INTEGER DEFAULT 0
|
|
136
|
+
)
|
|
137
|
+
""")
|
|
138
|
+
|
|
139
|
+
# Enable sync for the table
|
|
140
|
+
engine.enable_sync_for_table("todos")
|
|
141
|
+
|
|
142
|
+
# Now any INSERT/UPDATE/DELETE is automatically captured!
|
|
143
|
+
engine.connection.execute("INSERT INTO todos (title) VALUES ('Buy milk')")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Syncing Between Devices
|
|
149
|
+
|
|
150
|
+
### Device A: Generate a bundle
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from sqlite_sync import SyncEngine
|
|
154
|
+
|
|
155
|
+
engine_a = SyncEngine("device_a.db")
|
|
156
|
+
engine_a.initialize()
|
|
157
|
+
|
|
158
|
+
# Generate bundle for Device B
|
|
159
|
+
bundle_path = engine_a.generate_bundle(
|
|
160
|
+
peer_device_id=device_b_id, # 16-byte UUID
|
|
161
|
+
output_path="sync_bundle.db"
|
|
162
|
+
)
|
|
163
|
+
# Send bundle_path to Device B (USB, email, cloud, etc.)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Device B: Import the bundle
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
engine_b = SyncEngine("device_b.db")
|
|
170
|
+
engine_b.initialize()
|
|
171
|
+
|
|
172
|
+
# Import received bundle
|
|
173
|
+
result = engine_b.import_bundle("sync_bundle.db")
|
|
174
|
+
|
|
175
|
+
print(f"Applied: {result.applied_count}")
|
|
176
|
+
print(f"Conflicts: {result.conflict_count}")
|
|
177
|
+
print(f"Duplicates: {result.duplicate_count}")
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Handling Conflicts
|
|
183
|
+
|
|
184
|
+
Conflicts occur when two devices modify the same row independently.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# Get all unresolved conflicts
|
|
188
|
+
conflicts = engine.get_unresolved_conflicts()
|
|
189
|
+
|
|
190
|
+
for conflict in conflicts:
|
|
191
|
+
print(f"Table: {conflict.table_name}")
|
|
192
|
+
print(f"Row PK: {conflict.row_pk}")
|
|
193
|
+
print(f"Local op: {conflict.local_op_id.hex()}")
|
|
194
|
+
print(f"Remote op: {conflict.remote_op_id.hex()}")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
> **Note:** This library intentionally does NOT auto-resolve conflicts.
|
|
198
|
+
> You must implement your own resolution strategy.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Core Invariants
|
|
203
|
+
|
|
204
|
+
| # | Invariant | Description |
|
|
205
|
+
|---|-----------|-------------|
|
|
206
|
+
| 1 | **Append-only** | `sync_operations` only grows, never modified |
|
|
207
|
+
| 2 | **Causal consistency** | Vector clocks ensure happens-before |
|
|
208
|
+
| 3 | **Deterministic ordering** | Same operations always sort identically |
|
|
209
|
+
| 4 | **Explicit conflicts** | Concurrent writes preserved as records |
|
|
210
|
+
| 5 | **Idempotent import** | Same bundle × N imports = same result |
|
|
211
|
+
| 6 | **Transport independence** | Bundles are self-contained SQLite files |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## API Reference
|
|
216
|
+
|
|
217
|
+
### `SyncEngine`
|
|
218
|
+
|
|
219
|
+
| Method | Description |
|
|
220
|
+
|--------|-------------|
|
|
221
|
+
| `initialize()` | Initialize sync tables, returns device ID |
|
|
222
|
+
| `enable_sync_for_table(name)` | Install triggers for a table |
|
|
223
|
+
| `generate_bundle(peer_id, path)` | Create bundle for peer |
|
|
224
|
+
| `import_bundle(path)` | Import bundle, returns `ImportResult` |
|
|
225
|
+
| `get_unresolved_conflicts()` | Get all pending conflicts |
|
|
226
|
+
| `get_vector_clock()` | Get current vector clock |
|
|
227
|
+
| `close()` | Close database connection |
|
|
228
|
+
|
|
229
|
+
### `ImportResult`
|
|
230
|
+
|
|
231
|
+
| Field | Type | Description |
|
|
232
|
+
|-------|------|-------------|
|
|
233
|
+
| `bundle_id` | bytes | UUID of imported bundle |
|
|
234
|
+
| `source_device_id` | bytes | Device that created bundle |
|
|
235
|
+
| `total_operations` | int | Total ops in bundle |
|
|
236
|
+
| `applied_count` | int | Successfully applied |
|
|
237
|
+
| `conflict_count` | int | Conflicts detected |
|
|
238
|
+
| `duplicate_count` | int | Already had these ops |
|
|
239
|
+
| `skipped` | bool | True if bundle already imported |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Project Structure
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
sqlite_sync_core/
|
|
247
|
+
├── src/sqlite_sync/
|
|
248
|
+
│ ├── engine.py # Main SyncEngine class
|
|
249
|
+
│ ├── config.py # Configuration constants
|
|
250
|
+
│ ├── errors.py # Exception classes
|
|
251
|
+
│ ├── invariants.py # Core invariant enforcement
|
|
252
|
+
│ ├── db/ # Database layer
|
|
253
|
+
│ │ ├── schema.py # Table definitions
|
|
254
|
+
│ │ ├── migrations.py # Initialization
|
|
255
|
+
│ │ ├── triggers.py # Change capture triggers
|
|
256
|
+
│ │ └── connection.py # Connection management
|
|
257
|
+
│ ├── bundle/ # Bundle operations
|
|
258
|
+
│ │ ├── generate.py # Bundle creation
|
|
259
|
+
│ │ ├── validate.py # Bundle validation
|
|
260
|
+
│ │ └── format.py # Bundle metadata
|
|
261
|
+
│ ├── import_apply/ # Import pipeline
|
|
262
|
+
│ │ ├── apply.py # Apply operations
|
|
263
|
+
│ │ ├── conflict.py # Conflict detection
|
|
264
|
+
│ │ ├── ordering.py # Deterministic sort
|
|
265
|
+
│ │ └── dedup.py # Deduplication
|
|
266
|
+
│ ├── log/ # Operation log
|
|
267
|
+
│ │ ├── operations.py # SyncOperation dataclass
|
|
268
|
+
│ │ └── vector_clock.py# Vector clock logic
|
|
269
|
+
│ └── utils/ # Utilities
|
|
270
|
+
│ ├── uuid7.py # UUID v7 generation
|
|
271
|
+
│ ├── hashing.py # SHA-256 utilities
|
|
272
|
+
│ └── msgpack_codec.py# Serialization
|
|
273
|
+
├── tests/ # Test suite
|
|
274
|
+
├── pyproject.toml # Package config
|
|
275
|
+
└── README.md
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Running Tests
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# Using the custom test runner (no pytest required)
|
|
284
|
+
python run_verification.py
|
|
285
|
+
|
|
286
|
+
# Or with pytest
|
|
287
|
+
pip install pytest
|
|
288
|
+
pytest tests/ -v
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
**Dual License Model:**
|
|
296
|
+
|
|
297
|
+
| Use Case | License | Cost |
|
|
298
|
+
|----------|---------|------|
|
|
299
|
+
| Personal projects | AGPL-3.0 | **Free** |
|
|
300
|
+
| Open-source projects | AGPL-3.0 | **Free** |
|
|
301
|
+
| Educational/Research | AGPL-3.0 | **Free** |
|
|
302
|
+
| Commercial / Proprietary | Commercial License | **Paid** |
|
|
303
|
+
|
|
304
|
+
> **Commercial use** (closed-source, SaaS, proprietary) requires a paid license.
|
|
305
|
+
>
|
|
306
|
+
> **Contact for licensing:**
|
|
307
|
+
>
|
|
308
|
+
> - <shivaysinghrajput@proton.me>
|
|
309
|
+
> - <shivaysinghrajput@outlook.com>
|
|
310
|
+
> - <vbs.visionquanteh@proton.me>
|
|
311
|
+
|
|
312
|
+
See [LICENSE](./LICENSE) for full terms.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Contributing
|
|
317
|
+
|
|
318
|
+
1. Fork the repository
|
|
319
|
+
2. Create a feature branch
|
|
320
|
+
3. Make your changes
|
|
321
|
+
4. Run the test suite
|
|
322
|
+
5. Submit a pull request
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
**Built with ❤️ for offline-first applications**
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Universal SQLite Synchronization Core
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://www.gnu.org/licenses/agpl-3.0)
|
|
5
|
+
[](https://github.com/shivay00001/sqlite-sync-core)
|
|
6
|
+
|
|
7
|
+
**A product of [VisionQuantech](https://github.com/shivay00001), India 🇮🇳**
|
|
8
|
+
|
|
9
|
+
**A dependency-grade, local-first, offline-first SQLite synchronization primitive.**
|
|
10
|
+
|
|
11
|
+
Captures database changes as structured operations, packages them into self-contained bundles, and applies them deterministically across disconnected devices.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- 🔒 **Append-only log** – Operations are immutable history
|
|
18
|
+
- 🕐 **Vector clocks** – Causality tracking across devices
|
|
19
|
+
- ⚔️ **Conflict detection** – Never auto-merges, preserves conflicts
|
|
20
|
+
- 🔄 **Deterministic replay** – Same operations = same result everywhere
|
|
21
|
+
- 📦 **Transport agnostic** – Bundles work over USB, email, Bluetooth, anything
|
|
22
|
+
- 🚫 **Zero infrastructure** – No servers, no cloud, no network required
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### From PyPI
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install sqlite-sync-core
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### From GitHub (Development)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git clone https://github.com/shivay00001/sqlite-sync-core.git
|
|
38
|
+
cd sqlite-sync-core
|
|
39
|
+
pip install -e .
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Real-Time Network Sync
|
|
45
|
+
|
|
46
|
+
SQLite Sync Core now supports real-time synchronization over WebSockets.
|
|
47
|
+
|
|
48
|
+
### Start the Reference Server
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python -m sqlite_sync.network.server
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Connect a Client
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from sqlite_sync import SyncEngine
|
|
58
|
+
from sqlite_sync.network.client import SyncClient
|
|
59
|
+
|
|
60
|
+
engine = SyncEngine("my_app.db")
|
|
61
|
+
engine.initialize()
|
|
62
|
+
|
|
63
|
+
client = SyncClient(engine, "ws://localhost:8765")
|
|
64
|
+
await client.connect()
|
|
65
|
+
|
|
66
|
+
# Listen for remote changes
|
|
67
|
+
asyncio.create_task(client.listen())
|
|
68
|
+
|
|
69
|
+
# Send local changes
|
|
70
|
+
ops = engine.get_new_operations()
|
|
71
|
+
for op in ops:
|
|
72
|
+
await client.send_operation(op)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Integration Examples
|
|
78
|
+
|
|
79
|
+
See the `examples/` directory for full integration samples:
|
|
80
|
+
|
|
81
|
+
- `basic_usage.py`: Simple CLI setup.
|
|
82
|
+
- `network_sync.py`: Real-time sync between two peers.
|
|
83
|
+
- `desktop_example.py`: Coming soon (GUI integration).
|
|
84
|
+
|
|
85
|
+
### Requirements
|
|
86
|
+
|
|
87
|
+
- Python 3.11+
|
|
88
|
+
- `msgpack` (auto-installed)
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Quick Start
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from sqlite_sync import SyncEngine
|
|
96
|
+
|
|
97
|
+
# Initialize sync-enabled database
|
|
98
|
+
engine = SyncEngine("my_database.db")
|
|
99
|
+
engine.initialize()
|
|
100
|
+
|
|
101
|
+
# Create a user table
|
|
102
|
+
engine.connection.execute("""
|
|
103
|
+
CREATE TABLE todos (
|
|
104
|
+
id INTEGER PRIMARY KEY,
|
|
105
|
+
title TEXT NOT NULL,
|
|
106
|
+
done INTEGER DEFAULT 0
|
|
107
|
+
)
|
|
108
|
+
""")
|
|
109
|
+
|
|
110
|
+
# Enable sync for the table
|
|
111
|
+
engine.enable_sync_for_table("todos")
|
|
112
|
+
|
|
113
|
+
# Now any INSERT/UPDATE/DELETE is automatically captured!
|
|
114
|
+
engine.connection.execute("INSERT INTO todos (title) VALUES ('Buy milk')")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Syncing Between Devices
|
|
120
|
+
|
|
121
|
+
### Device A: Generate a bundle
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from sqlite_sync import SyncEngine
|
|
125
|
+
|
|
126
|
+
engine_a = SyncEngine("device_a.db")
|
|
127
|
+
engine_a.initialize()
|
|
128
|
+
|
|
129
|
+
# Generate bundle for Device B
|
|
130
|
+
bundle_path = engine_a.generate_bundle(
|
|
131
|
+
peer_device_id=device_b_id, # 16-byte UUID
|
|
132
|
+
output_path="sync_bundle.db"
|
|
133
|
+
)
|
|
134
|
+
# Send bundle_path to Device B (USB, email, cloud, etc.)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Device B: Import the bundle
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
engine_b = SyncEngine("device_b.db")
|
|
141
|
+
engine_b.initialize()
|
|
142
|
+
|
|
143
|
+
# Import received bundle
|
|
144
|
+
result = engine_b.import_bundle("sync_bundle.db")
|
|
145
|
+
|
|
146
|
+
print(f"Applied: {result.applied_count}")
|
|
147
|
+
print(f"Conflicts: {result.conflict_count}")
|
|
148
|
+
print(f"Duplicates: {result.duplicate_count}")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Handling Conflicts
|
|
154
|
+
|
|
155
|
+
Conflicts occur when two devices modify the same row independently.
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
# Get all unresolved conflicts
|
|
159
|
+
conflicts = engine.get_unresolved_conflicts()
|
|
160
|
+
|
|
161
|
+
for conflict in conflicts:
|
|
162
|
+
print(f"Table: {conflict.table_name}")
|
|
163
|
+
print(f"Row PK: {conflict.row_pk}")
|
|
164
|
+
print(f"Local op: {conflict.local_op_id.hex()}")
|
|
165
|
+
print(f"Remote op: {conflict.remote_op_id.hex()}")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> **Note:** This library intentionally does NOT auto-resolve conflicts.
|
|
169
|
+
> You must implement your own resolution strategy.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Core Invariants
|
|
174
|
+
|
|
175
|
+
| # | Invariant | Description |
|
|
176
|
+
|---|-----------|-------------|
|
|
177
|
+
| 1 | **Append-only** | `sync_operations` only grows, never modified |
|
|
178
|
+
| 2 | **Causal consistency** | Vector clocks ensure happens-before |
|
|
179
|
+
| 3 | **Deterministic ordering** | Same operations always sort identically |
|
|
180
|
+
| 4 | **Explicit conflicts** | Concurrent writes preserved as records |
|
|
181
|
+
| 5 | **Idempotent import** | Same bundle × N imports = same result |
|
|
182
|
+
| 6 | **Transport independence** | Bundles are self-contained SQLite files |
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## API Reference
|
|
187
|
+
|
|
188
|
+
### `SyncEngine`
|
|
189
|
+
|
|
190
|
+
| Method | Description |
|
|
191
|
+
|--------|-------------|
|
|
192
|
+
| `initialize()` | Initialize sync tables, returns device ID |
|
|
193
|
+
| `enable_sync_for_table(name)` | Install triggers for a table |
|
|
194
|
+
| `generate_bundle(peer_id, path)` | Create bundle for peer |
|
|
195
|
+
| `import_bundle(path)` | Import bundle, returns `ImportResult` |
|
|
196
|
+
| `get_unresolved_conflicts()` | Get all pending conflicts |
|
|
197
|
+
| `get_vector_clock()` | Get current vector clock |
|
|
198
|
+
| `close()` | Close database connection |
|
|
199
|
+
|
|
200
|
+
### `ImportResult`
|
|
201
|
+
|
|
202
|
+
| Field | Type | Description |
|
|
203
|
+
|-------|------|-------------|
|
|
204
|
+
| `bundle_id` | bytes | UUID of imported bundle |
|
|
205
|
+
| `source_device_id` | bytes | Device that created bundle |
|
|
206
|
+
| `total_operations` | int | Total ops in bundle |
|
|
207
|
+
| `applied_count` | int | Successfully applied |
|
|
208
|
+
| `conflict_count` | int | Conflicts detected |
|
|
209
|
+
| `duplicate_count` | int | Already had these ops |
|
|
210
|
+
| `skipped` | bool | True if bundle already imported |
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Project Structure
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
sqlite_sync_core/
|
|
218
|
+
├── src/sqlite_sync/
|
|
219
|
+
│ ├── engine.py # Main SyncEngine class
|
|
220
|
+
│ ├── config.py # Configuration constants
|
|
221
|
+
│ ├── errors.py # Exception classes
|
|
222
|
+
│ ├── invariants.py # Core invariant enforcement
|
|
223
|
+
│ ├── db/ # Database layer
|
|
224
|
+
│ │ ├── schema.py # Table definitions
|
|
225
|
+
│ │ ├── migrations.py # Initialization
|
|
226
|
+
│ │ ├── triggers.py # Change capture triggers
|
|
227
|
+
│ │ └── connection.py # Connection management
|
|
228
|
+
│ ├── bundle/ # Bundle operations
|
|
229
|
+
│ │ ├── generate.py # Bundle creation
|
|
230
|
+
│ │ ├── validate.py # Bundle validation
|
|
231
|
+
│ │ └── format.py # Bundle metadata
|
|
232
|
+
│ ├── import_apply/ # Import pipeline
|
|
233
|
+
│ │ ├── apply.py # Apply operations
|
|
234
|
+
│ │ ├── conflict.py # Conflict detection
|
|
235
|
+
│ │ ├── ordering.py # Deterministic sort
|
|
236
|
+
│ │ └── dedup.py # Deduplication
|
|
237
|
+
│ ├── log/ # Operation log
|
|
238
|
+
│ │ ├── operations.py # SyncOperation dataclass
|
|
239
|
+
│ │ └── vector_clock.py# Vector clock logic
|
|
240
|
+
│ └── utils/ # Utilities
|
|
241
|
+
│ ├── uuid7.py # UUID v7 generation
|
|
242
|
+
│ ├── hashing.py # SHA-256 utilities
|
|
243
|
+
│ └── msgpack_codec.py# Serialization
|
|
244
|
+
├── tests/ # Test suite
|
|
245
|
+
├── pyproject.toml # Package config
|
|
246
|
+
└── README.md
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Running Tests
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Using the custom test runner (no pytest required)
|
|
255
|
+
python run_verification.py
|
|
256
|
+
|
|
257
|
+
# Or with pytest
|
|
258
|
+
pip install pytest
|
|
259
|
+
pytest tests/ -v
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
**Dual License Model:**
|
|
267
|
+
|
|
268
|
+
| Use Case | License | Cost |
|
|
269
|
+
|----------|---------|------|
|
|
270
|
+
| Personal projects | AGPL-3.0 | **Free** |
|
|
271
|
+
| Open-source projects | AGPL-3.0 | **Free** |
|
|
272
|
+
| Educational/Research | AGPL-3.0 | **Free** |
|
|
273
|
+
| Commercial / Proprietary | Commercial License | **Paid** |
|
|
274
|
+
|
|
275
|
+
> **Commercial use** (closed-source, SaaS, proprietary) requires a paid license.
|
|
276
|
+
>
|
|
277
|
+
> **Contact for licensing:**
|
|
278
|
+
>
|
|
279
|
+
> - <shivaysinghrajput@proton.me>
|
|
280
|
+
> - <shivaysinghrajput@outlook.com>
|
|
281
|
+
> - <vbs.visionquanteh@proton.me>
|
|
282
|
+
|
|
283
|
+
See [LICENSE](./LICENSE) for full terms.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Contributing
|
|
288
|
+
|
|
289
|
+
1. Fork the repository
|
|
290
|
+
2. Create a feature branch
|
|
291
|
+
3. Make your changes
|
|
292
|
+
4. Run the test suite
|
|
293
|
+
5. Submit a pull request
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
**Built with ❤️ for offline-first applications**
|