@fatagnus/dink-sync 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -0
- package/dist/client/attachment.d.ts +225 -0
- package/dist/client/attachment.d.ts.map +1 -0
- package/dist/client/attachment.js +402 -0
- package/dist/client/attachment.js.map +1 -0
- package/dist/client/binary-encoding.d.ts +45 -0
- package/dist/client/binary-encoding.d.ts.map +1 -0
- package/dist/client/binary-encoding.js +90 -0
- package/dist/client/binary-encoding.js.map +1 -0
- package/dist/client/collection.d.ts +10 -0
- package/dist/client/collection.d.ts.map +1 -0
- package/dist/client/collection.js +924 -0
- package/dist/client/collection.js.map +1 -0
- package/dist/client/compression.d.ts +56 -0
- package/dist/client/compression.d.ts.map +1 -0
- package/dist/client/compression.js +173 -0
- package/dist/client/compression.js.map +1 -0
- package/dist/client/crdt/index.d.ts +2 -0
- package/dist/client/crdt/index.d.ts.map +1 -0
- package/dist/client/crdt/index.js +2 -0
- package/dist/client/crdt/index.js.map +1 -0
- package/dist/client/crdt/yjs-doc.d.ts +88 -0
- package/dist/client/crdt/yjs-doc.d.ts.map +1 -0
- package/dist/client/crdt/yjs-doc.js +123 -0
- package/dist/client/crdt/yjs-doc.js.map +1 -0
- package/dist/client/index.d.ts +66 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +233 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/mock-transport.d.ts +155 -0
- package/dist/client/mock-transport.d.ts.map +1 -0
- package/dist/client/mock-transport.js +292 -0
- package/dist/client/mock-transport.js.map +1 -0
- package/dist/client/network-detector.d.ts +65 -0
- package/dist/client/network-detector.d.ts.map +1 -0
- package/dist/client/network-detector.js +147 -0
- package/dist/client/network-detector.js.map +1 -0
- package/dist/client/provisioning.d.ts +126 -0
- package/dist/client/provisioning.d.ts.map +1 -0
- package/dist/client/provisioning.js +125 -0
- package/dist/client/provisioning.js.map +1 -0
- package/dist/client/signal.d.ts +13 -0
- package/dist/client/signal.d.ts.map +1 -0
- package/dist/client/signal.js +27 -0
- package/dist/client/signal.js.map +1 -0
- package/dist/client/sync-engine.d.ts +298 -0
- package/dist/client/sync-engine.d.ts.map +1 -0
- package/dist/client/sync-engine.js +904 -0
- package/dist/client/sync-engine.js.map +1 -0
- package/dist/client/synced-edge.d.ts +109 -0
- package/dist/client/synced-edge.d.ts.map +1 -0
- package/dist/client/synced-edge.js +179 -0
- package/dist/client/synced-edge.js.map +1 -0
- package/dist/client/synced-offline-edge-types.d.ts +540 -0
- package/dist/client/synced-offline-edge-types.d.ts.map +1 -0
- package/dist/client/synced-offline-edge-types.js +10 -0
- package/dist/client/synced-offline-edge-types.js.map +1 -0
- package/dist/client/synced-offline-edge.d.ts +54 -0
- package/dist/client/synced-offline-edge.d.ts.map +1 -0
- package/dist/client/synced-offline-edge.js +731 -0
- package/dist/client/synced-offline-edge.js.map +1 -0
- package/dist/client/transport.d.ts +202 -0
- package/dist/client/transport.d.ts.map +1 -0
- package/dist/client/transport.js +409 -0
- package/dist/client/transport.js.map +1 -0
- package/dist/client/types.d.ts +622 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +60 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/validation.d.ts +61 -0
- package/dist/client/validation.d.ts.map +1 -0
- package/dist/client/validation.js +57 -0
- package/dist/client/validation.js.map +1 -0
- package/dist/client/versioning.d.ts +134 -0
- package/dist/client/versioning.d.ts.map +1 -0
- package/dist/client/versioning.js +304 -0
- package/dist/client/versioning.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/persistence/encryption.d.ts +114 -0
- package/dist/persistence/encryption.d.ts.map +1 -0
- package/dist/persistence/encryption.js +286 -0
- package/dist/persistence/encryption.js.map +1 -0
- package/dist/persistence/index.d.ts +21 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +20 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/memory.d.ts +32 -0
- package/dist/persistence/memory.d.ts.map +1 -0
- package/dist/persistence/memory.js +57 -0
- package/dist/persistence/memory.js.map +1 -0
- package/dist/persistence/migrations.d.ts +106 -0
- package/dist/persistence/migrations.d.ts.map +1 -0
- package/dist/persistence/migrations.js +176 -0
- package/dist/persistence/migrations.js.map +1 -0
- package/dist/persistence/pending-queue.d.ts +109 -0
- package/dist/persistence/pending-queue.d.ts.map +1 -0
- package/dist/persistence/pending-queue.js +249 -0
- package/dist/persistence/pending-queue.js.map +1 -0
- package/dist/persistence/pglite.d.ts +72 -0
- package/dist/persistence/pglite.d.ts.map +1 -0
- package/dist/persistence/pglite.js +126 -0
- package/dist/persistence/pglite.js.map +1 -0
- package/dist/persistence/quota-manager.d.ts +134 -0
- package/dist/persistence/quota-manager.d.ts.map +1 -0
- package/dist/persistence/quota-manager.js +242 -0
- package/dist/persistence/quota-manager.js.map +1 -0
- package/dist/persistence/types.d.ts +54 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +2 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/react/OfflineEdgeProvider.d.ts +91 -0
- package/dist/react/OfflineEdgeProvider.d.ts.map +1 -0
- package/dist/react/OfflineEdgeProvider.js +127 -0
- package/dist/react/OfflineEdgeProvider.js.map +1 -0
- package/dist/react/SyncedOfflineEdgeProvider.d.ts +105 -0
- package/dist/react/SyncedOfflineEdgeProvider.d.ts.map +1 -0
- package/dist/react/SyncedOfflineEdgeProvider.js +138 -0
- package/dist/react/SyncedOfflineEdgeProvider.js.map +1 -0
- package/dist/react/index.d.ts +50 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +51 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/useCollection.d.ts +77 -0
- package/dist/react/useCollection.d.ts.map +1 -0
- package/dist/react/useCollection.js +113 -0
- package/dist/react/useCollection.js.map +1 -0
- package/dist/react/useCollectionSyncMode.d.ts +61 -0
- package/dist/react/useCollectionSyncMode.d.ts.map +1 -0
- package/dist/react/useCollectionSyncMode.js +93 -0
- package/dist/react/useCollectionSyncMode.js.map +1 -0
- package/dist/react/useConnectionState.d.ts +44 -0
- package/dist/react/useConnectionState.d.ts.map +1 -0
- package/dist/react/useConnectionState.js +46 -0
- package/dist/react/useConnectionState.js.map +1 -0
- package/dist/react/useDocumentSyncStatus.d.ts +72 -0
- package/dist/react/useDocumentSyncStatus.d.ts.map +1 -0
- package/dist/react/useDocumentSyncStatus.js +110 -0
- package/dist/react/useDocumentSyncStatus.js.map +1 -0
- package/dist/react/useOfflineEdge.d.ts +58 -0
- package/dist/react/useOfflineEdge.d.ts.map +1 -0
- package/dist/react/useOfflineEdge.js +54 -0
- package/dist/react/useOfflineEdge.js.map +1 -0
- package/dist/react/usePendingChanges.d.ts +67 -0
- package/dist/react/usePendingChanges.d.ts.map +1 -0
- package/dist/react/usePendingChanges.js +90 -0
- package/dist/react/usePendingChanges.js.map +1 -0
- package/dist/react/useRejectedDocuments.d.ts +112 -0
- package/dist/react/useRejectedDocuments.d.ts.map +1 -0
- package/dist/react/useRejectedDocuments.js +213 -0
- package/dist/react/useRejectedDocuments.js.map +1 -0
- package/dist/react/useSyncControls.d.ts +96 -0
- package/dist/react/useSyncControls.d.ts.map +1 -0
- package/dist/react/useSyncControls.js +112 -0
- package/dist/react/useSyncControls.js.map +1 -0
- package/dist/react/useSyncProgress.d.ts +78 -0
- package/dist/react/useSyncProgress.d.ts.map +1 -0
- package/dist/react/useSyncProgress.js +90 -0
- package/dist/react/useSyncProgress.js.map +1 -0
- package/dist/react/useSyncRejected.d.ts +47 -0
- package/dist/react/useSyncRejected.d.ts.map +1 -0
- package/dist/react/useSyncRejected.js +55 -0
- package/dist/react/useSyncRejected.js.map +1 -0
- package/dist/react/useSyncStatus.d.ts +56 -0
- package/dist/react/useSyncStatus.d.ts.map +1 -0
- package/dist/react/useSyncStatus.js +59 -0
- package/dist/react/useSyncStatus.js.map +1 -0
- package/dist/react/useSyncedOfflineEdge.d.ts +69 -0
- package/dist/react/useSyncedOfflineEdge.d.ts.map +1 -0
- package/dist/react/useSyncedOfflineEdge.js +65 -0
- package/dist/react/useSyncedOfflineEdge.js.map +1 -0
- package/dist/service-worker/index.d.ts +7 -0
- package/dist/service-worker/index.d.ts.map +1 -0
- package/dist/service-worker/index.js +7 -0
- package/dist/service-worker/index.js.map +1 -0
- package/dist/service-worker/sync-worker.d.ts +230 -0
- package/dist/service-worker/sync-worker.d.ts.map +1 -0
- package/dist/service-worker/sync-worker.js +471 -0
- package/dist/service-worker/sync-worker.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +95 -0
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# @fatagnus/dink-sync
|
|
2
|
+
|
|
3
|
+
Offline-first sync SDK for Dink edge platform with Effect.ts.
|
|
4
|
+
|
|
5
|
+
This SDK powers the **Offline-First Edge** type - edges that work offline and automatically sync when connectivity is restored.
|
|
6
|
+
|
|
7
|
+
| Edge Type | SDK | Use Case |
|
|
8
|
+
|-----------|-----|----------|
|
|
9
|
+
| **Lite Edge** | `@fatagnus/dink-sdk` | Always-connected IoT, RPC services |
|
|
10
|
+
| **Offline-First Edge** | ✅ This SDK | Offline data with auto-sync |
|
|
11
|
+
| **Full Edge** | Not yet available | Local NATS services (planned) |
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @fatagnus/dink-sync
|
|
17
|
+
# or
|
|
18
|
+
pnpm add @fatagnus/dink-sync
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Offline-first**: Queue changes locally and sync when online
|
|
24
|
+
- **CRDT-based**: Conflict-free replicated data types for automatic conflict resolution
|
|
25
|
+
- **Real-time sync**: Bidirectional sync with dinkd server via NATS
|
|
26
|
+
- **React hooks**: Ready-to-use hooks for React applications
|
|
27
|
+
- **Persistence**: Pluggable persistence providers (memory, PGlite)
|
|
28
|
+
- **Type-safe**: Full TypeScript support with strict typing
|
|
29
|
+
- **Typed Client Factory**: Generate type-safe collection access from Convex schema
|
|
30
|
+
|
|
31
|
+
## Quick Start — ⭐ Typed Client (STRONGLY PREFERRED)
|
|
32
|
+
|
|
33
|
+
**Always use typed clients** generated from your Convex schema. This is the only recommended approach for production applications.
|
|
34
|
+
|
|
35
|
+
> ⚠️ Do not use the low-level sync engine or generic collection APIs in production. They should only be used for quick prototyping.
|
|
36
|
+
|
|
37
|
+
### 1. Define Your Schema
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// convex/schema.ts
|
|
41
|
+
import { defineSchema, defineTable } from "convex/server";
|
|
42
|
+
import { v } from "convex/values";
|
|
43
|
+
|
|
44
|
+
export default defineSchema({
|
|
45
|
+
tasks: defineTable({
|
|
46
|
+
text: v.string(),
|
|
47
|
+
isCompleted: v.boolean(),
|
|
48
|
+
priority: v.optional(v.number()),
|
|
49
|
+
}),
|
|
50
|
+
users: defineTable({
|
|
51
|
+
name: v.string(),
|
|
52
|
+
email: v.string(),
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. Generate Typed Client
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Generate TypeScript types, validators, and typed client
|
|
61
|
+
dink codegen --convex-schema ./convex --convex-output ./src/generated --zod
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Use the Typed Client
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { offlineEdge } from '@fatagnus/dink-sync';
|
|
68
|
+
import { PGlitePersistence } from '@fatagnus/dink-sync/persistence';
|
|
69
|
+
import { createTypedClient } from './generated';
|
|
70
|
+
|
|
71
|
+
// Initialize the offline edge client
|
|
72
|
+
const edge = offlineEdge.create({
|
|
73
|
+
persistence: new PGlitePersistence(),
|
|
74
|
+
config: {
|
|
75
|
+
serverUrl: 'nats://localhost:4222',
|
|
76
|
+
apiKey: 'your-edge-api-key',
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
await edge.init();
|
|
80
|
+
|
|
81
|
+
// Create the typed client - this is the recommended way!
|
|
82
|
+
const db = createTypedClient(edge.get());
|
|
83
|
+
|
|
84
|
+
// Type-safe collection access with full IDE autocompletion
|
|
85
|
+
const task = await db.tasks.insert({
|
|
86
|
+
text: 'Buy milk',
|
|
87
|
+
isCompleted: false,
|
|
88
|
+
});
|
|
89
|
+
console.log(`Created task: ${task.id}`);
|
|
90
|
+
|
|
91
|
+
// List all tasks
|
|
92
|
+
const allTasks = await db.tasks.list();
|
|
93
|
+
|
|
94
|
+
// Update a task
|
|
95
|
+
const updated = await db.tasks.update(task.id, { isCompleted: true });
|
|
96
|
+
|
|
97
|
+
// Delete a task
|
|
98
|
+
await db.tasks.delete(task.id);
|
|
99
|
+
|
|
100
|
+
// Validation is automatic!
|
|
101
|
+
try {
|
|
102
|
+
await db.tasks.insert({ text: '', isCompleted: false });
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error('Validation failed:', err.message); // "text is required"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Why Typed Client?
|
|
109
|
+
|
|
110
|
+
| Feature | Typed Client | Generic Collection |
|
|
111
|
+
|---------|--------------|--------------------|
|
|
112
|
+
| Type Safety | ✅ Compile-time checks | ⚠️ Runtime only |
|
|
113
|
+
| IDE Support | ✅ Full autocompletion | ⚠️ Limited |
|
|
114
|
+
| Validation | ✅ Automatic | ❌ Manual setup |
|
|
115
|
+
| Refactoring | ✅ Safe renames | ⚠️ String-based |
|
|
116
|
+
|
|
117
|
+
## Alternative: Low-Level Sync Engine ⚠️ Advanced/Prototyping Only
|
|
118
|
+
|
|
119
|
+
For advanced use cases or quick prototyping, you can use the sync engine directly. **Migrate to typed clients before production:**
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { createSyncEngine, NatsTransport } from '@fatagnus/dink-sync/client';
|
|
123
|
+
|
|
124
|
+
// Create transport and engine
|
|
125
|
+
const transport = new NatsTransport({
|
|
126
|
+
serverUrl: 'nats://localhost:4222',
|
|
127
|
+
apiKey: 'your-edge-api-key',
|
|
128
|
+
appId: 'your-app-id',
|
|
129
|
+
edgeId: 'your-edge-id',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const engine = createSyncEngine({
|
|
133
|
+
serverUrl: 'nats://localhost:4222',
|
|
134
|
+
apiKey: 'your-edge-api-key',
|
|
135
|
+
transport,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Connect and register documents
|
|
139
|
+
await engine.connect();
|
|
140
|
+
const actor = await engine.registerDocument('tasks', 'task-1');
|
|
141
|
+
|
|
142
|
+
// Queue changes
|
|
143
|
+
actor.queueChange(new Uint8Array([1, 2, 3]));
|
|
144
|
+
|
|
145
|
+
// Listen for sync events
|
|
146
|
+
engine.onSyncComplete((event) => {
|
|
147
|
+
console.log(`Synced ${event.documentId}`);
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## React Integration
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { OfflineEdgeProvider, useCollection } from '@fatagnus/dink-sync/react';
|
|
155
|
+
|
|
156
|
+
function App() {
|
|
157
|
+
return (
|
|
158
|
+
<OfflineEdgeProvider config={{ serverUrl, apiKey, edgeId }}>
|
|
159
|
+
<TaskList />
|
|
160
|
+
</OfflineEdgeProvider>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function TaskList() {
|
|
165
|
+
const { items, insert, update, delete: remove } = useCollection('tasks');
|
|
166
|
+
// ...
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Testing
|
|
171
|
+
|
|
172
|
+
### Unit Tests
|
|
173
|
+
|
|
174
|
+
Run unit tests (uses mock transport):
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pnpm test
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### E2E Tests
|
|
181
|
+
|
|
182
|
+
E2E tests use testcontainers to spin up a real dinkd server.
|
|
183
|
+
|
|
184
|
+
### Browser Tests
|
|
185
|
+
|
|
186
|
+
Browser tests verify PGlite persistence with IndexedDB in a real browser environment.
|
|
187
|
+
|
|
188
|
+
#### Prerequisites
|
|
189
|
+
|
|
190
|
+
1. **Docker**: Install and ensure Docker daemon is running
|
|
191
|
+
|
|
192
|
+
2. **dinkd image**: Build the dinkd Docker image from the project root:
|
|
193
|
+
```bash
|
|
194
|
+
# From the project root directory
|
|
195
|
+
docker build -t dinkd:latest .
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Running E2E Tests
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pnpm test:e2e
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### Running Browser Tests
|
|
205
|
+
|
|
206
|
+
**Prerequisites:**
|
|
207
|
+
|
|
208
|
+
1. **Playwright browsers**: Install Chromium for browser testing:
|
|
209
|
+
```bash
|
|
210
|
+
npx playwright install chromium
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Run browser tests:**
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pnpm test:browser
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Browser test coverage:**
|
|
220
|
+
|
|
221
|
+
- Data persistence with IndexedDB backend
|
|
222
|
+
- Data persistence across page reloads/sessions
|
|
223
|
+
- Large dataset handling (1000+ documents)
|
|
224
|
+
- Concurrent read/write operations
|
|
225
|
+
- Binary data integrity
|
|
226
|
+
- Special character handling in document IDs
|
|
227
|
+
|
|
228
|
+
#### E2E Test Coverage
|
|
229
|
+
|
|
230
|
+
The E2E tests verify:
|
|
231
|
+
|
|
232
|
+
- Edge connection to real dinkd server
|
|
233
|
+
- Document registration and sync operations
|
|
234
|
+
- External update subscription
|
|
235
|
+
- Concurrent sync from multiple edges
|
|
236
|
+
- Reconnection handling
|
|
237
|
+
- Error handling and event emission
|
|
238
|
+
|
|
239
|
+
#### Test Environment Variables
|
|
240
|
+
|
|
241
|
+
| Variable | Description | Default |
|
|
242
|
+
|----------|-------------|--------|
|
|
243
|
+
| `DINK_TEST_IMAGE` | Docker image for dinkd | `dinkd:latest` |
|
|
244
|
+
|
|
245
|
+
#### Troubleshooting Browser Tests
|
|
246
|
+
|
|
247
|
+
**Playwright not installed:**
|
|
248
|
+
- Run `npx playwright install chromium` to install the browser
|
|
249
|
+
|
|
250
|
+
**Tests timing out:**
|
|
251
|
+
- Large dataset tests may take longer; timeout is set to 60 seconds
|
|
252
|
+
- Consider running browser tests separately from unit tests
|
|
253
|
+
|
|
254
|
+
**IndexedDB errors:**
|
|
255
|
+
- Ensure you're running in a supported browser (Chromium)
|
|
256
|
+
- Check for quota limits in browser settings
|
|
257
|
+
|
|
258
|
+
#### Troubleshooting E2E Tests
|
|
259
|
+
|
|
260
|
+
**Container startup timeout**:
|
|
261
|
+
- Ensure Docker is running
|
|
262
|
+
- Check that the `dinkd:latest` image exists: `docker images | grep dinkd`
|
|
263
|
+
- Increase startup timeout in test configuration if needed
|
|
264
|
+
|
|
265
|
+
**Connection refused**:
|
|
266
|
+
- Container may not be fully started
|
|
267
|
+
- Check container logs for errors
|
|
268
|
+
|
|
269
|
+
**Port conflicts**:
|
|
270
|
+
- Testcontainers automatically maps to random ports, but conflicts can occur
|
|
271
|
+
- Stop any running dinkd containers manually if needed
|
|
272
|
+
|
|
273
|
+
## API Reference
|
|
274
|
+
|
|
275
|
+
### SyncEngine
|
|
276
|
+
|
|
277
|
+
- `connect()`: Connect to dinkd server
|
|
278
|
+
- `disconnect()`: Disconnect from server
|
|
279
|
+
- `destroy()`: Clean up all resources
|
|
280
|
+
- `registerDocument(collection, docId)`: Register a document for sync
|
|
281
|
+
- `unregisterDocument(collection, docId)`: Unregister a document
|
|
282
|
+
- `getConnectionState()`: Get current connection state
|
|
283
|
+
- `onStateChange(callback)`: Subscribe to connection state changes
|
|
284
|
+
- `onSyncStarted(callback)`: Subscribe to sync start events
|
|
285
|
+
- `onSyncComplete(callback)`: Subscribe to sync completion events
|
|
286
|
+
- `onSyncError(callback)`: Subscribe to sync error events
|
|
287
|
+
- `onSyncRejected(callback)`: Subscribe to sync rejection events
|
|
288
|
+
- `goOffline()`: Manually force offline mode
|
|
289
|
+
- `goOnline()`: Exit manual offline mode
|
|
290
|
+
- `discardLocalChanges(collection, docId)`: Discard pending changes for a document
|
|
291
|
+
- `forcePush(collection, docId)`: Force push local state to server
|
|
292
|
+
|
|
293
|
+
### DocumentActor
|
|
294
|
+
|
|
295
|
+
- `queueChange(delta)`: Queue a change for sync
|
|
296
|
+
- `hasPendingChanges()`: Check if there are pending changes
|
|
297
|
+
- `onPendingChange(callback)`: Subscribe to pending state changes
|
|
298
|
+
- `getStateVector()`: Get current state vector
|
|
299
|
+
- `setStateVector(vector)`: Set state vector
|
|
300
|
+
- `applyExternalUpdate(update)`: Apply an external update
|
|
301
|
+
- `onExternalUpdate(callback)`: Subscribe to external updates
|
|
302
|
+
|
|
303
|
+
### ConnectionState
|
|
304
|
+
|
|
305
|
+
- `Offline`: Not connected to server
|
|
306
|
+
- `Connecting`: Connection in progress
|
|
307
|
+
- `Online`: Connected and ready
|
|
308
|
+
- `Reconnecting`: Connection lost, attempting to reconnect
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
Apache-2.0
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attachment support for offline-first sync.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Separate storage for binary attachments
|
|
6
|
+
* - Chunked upload for large files
|
|
7
|
+
* - Upload progress tracking
|
|
8
|
+
* - On-demand vs eager download policies
|
|
9
|
+
* - LRU caching with eviction
|
|
10
|
+
*/
|
|
11
|
+
import type { PersistenceProvider } from '../persistence/types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Download policy for attachments.
|
|
14
|
+
*/
|
|
15
|
+
export declare const DownloadPolicy: {
|
|
16
|
+
/** Download attachment data immediately when fetched */
|
|
17
|
+
readonly Eager: "eager";
|
|
18
|
+
/** Only download metadata; data loaded on explicit request */
|
|
19
|
+
readonly OnDemand: "on-demand";
|
|
20
|
+
};
|
|
21
|
+
export type DownloadPolicy = typeof DownloadPolicy[keyof typeof DownloadPolicy];
|
|
22
|
+
/**
|
|
23
|
+
* Metadata for an attachment.
|
|
24
|
+
*/
|
|
25
|
+
export interface AttachmentMetadata {
|
|
26
|
+
/** Unique attachment ID */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Original filename */
|
|
29
|
+
filename: string;
|
|
30
|
+
/** MIME type of the file */
|
|
31
|
+
mimeType: string;
|
|
32
|
+
/** Size in bytes */
|
|
33
|
+
size: number;
|
|
34
|
+
/** Checksum for integrity verification */
|
|
35
|
+
checksum: string;
|
|
36
|
+
/** Timestamp when the attachment was created */
|
|
37
|
+
createdAt: number;
|
|
38
|
+
/** ID of the document this attachment belongs to */
|
|
39
|
+
documentId: string;
|
|
40
|
+
/** Collection the document belongs to */
|
|
41
|
+
collection: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Upload progress information.
|
|
45
|
+
*/
|
|
46
|
+
export interface UploadProgress {
|
|
47
|
+
/** Percentage complete (0-100) */
|
|
48
|
+
percent: number;
|
|
49
|
+
/** Bytes uploaded so far */
|
|
50
|
+
uploadedBytes: number;
|
|
51
|
+
/** Total bytes to upload */
|
|
52
|
+
totalBytes: number;
|
|
53
|
+
/** Number of chunks completed */
|
|
54
|
+
chunksCompleted: number;
|
|
55
|
+
/** Total number of chunks */
|
|
56
|
+
totalChunks: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Options for uploading an attachment.
|
|
60
|
+
*/
|
|
61
|
+
export interface UploadOptions {
|
|
62
|
+
/** Original filename */
|
|
63
|
+
filename: string;
|
|
64
|
+
/** MIME type */
|
|
65
|
+
mimeType: string;
|
|
66
|
+
/** Binary data to upload */
|
|
67
|
+
data: Uint8Array;
|
|
68
|
+
/** Document ID to associate with */
|
|
69
|
+
documentId: string;
|
|
70
|
+
/** Collection name */
|
|
71
|
+
collection: string;
|
|
72
|
+
/** Progress callback */
|
|
73
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Result of an upload operation.
|
|
77
|
+
*/
|
|
78
|
+
export interface UploadResult {
|
|
79
|
+
/** Attachment metadata */
|
|
80
|
+
metadata: AttachmentMetadata;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Configuration for AttachmentManager.
|
|
84
|
+
*/
|
|
85
|
+
export interface AttachmentConfig {
|
|
86
|
+
/** Chunk size for large file uploads (default: 256KB) */
|
|
87
|
+
chunkSize?: number;
|
|
88
|
+
/** Maximum cache size in bytes (default: 50MB) */
|
|
89
|
+
maxCacheSize?: number;
|
|
90
|
+
/** Download policy (default: Eager) */
|
|
91
|
+
downloadPolicy?: DownloadPolicy;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Cache statistics.
|
|
95
|
+
*/
|
|
96
|
+
export interface CacheStats {
|
|
97
|
+
/** Number of cache hits */
|
|
98
|
+
hits: number;
|
|
99
|
+
/** Number of cache misses */
|
|
100
|
+
misses: number;
|
|
101
|
+
/** Number of evictions */
|
|
102
|
+
evictions: number;
|
|
103
|
+
/** Current cache size in bytes */
|
|
104
|
+
currentSize: number;
|
|
105
|
+
/** IDs of cached attachments */
|
|
106
|
+
cachedIds: string[];
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* An attachment with lazy-loadable data.
|
|
110
|
+
*/
|
|
111
|
+
export interface Attachment {
|
|
112
|
+
/** Attachment metadata */
|
|
113
|
+
metadata: AttachmentMetadata;
|
|
114
|
+
/** Binary data (may be undefined if not loaded) */
|
|
115
|
+
data: Uint8Array | undefined;
|
|
116
|
+
/** Check if data has been loaded */
|
|
117
|
+
isDataLoaded(): boolean;
|
|
118
|
+
/** Load the data (no-op if already loaded) */
|
|
119
|
+
loadData(): Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Stored attachment with metadata and data.
|
|
123
|
+
*/
|
|
124
|
+
interface StoredAttachment {
|
|
125
|
+
metadata: AttachmentMetadata;
|
|
126
|
+
data: Uint8Array;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Low-level storage for attachments.
|
|
130
|
+
* Stores metadata and data separately for efficient access.
|
|
131
|
+
*/
|
|
132
|
+
export declare class AttachmentStorage {
|
|
133
|
+
private readonly persistence;
|
|
134
|
+
constructor(persistence: PersistenceProvider);
|
|
135
|
+
/**
|
|
136
|
+
* Save an attachment.
|
|
137
|
+
*/
|
|
138
|
+
save(metadata: AttachmentMetadata, data: Uint8Array): Promise<void>;
|
|
139
|
+
/**
|
|
140
|
+
* Load an attachment with its data.
|
|
141
|
+
*/
|
|
142
|
+
load(attachmentId: string): Promise<StoredAttachment | undefined>;
|
|
143
|
+
/**
|
|
144
|
+
* Load just the data for an attachment.
|
|
145
|
+
*/
|
|
146
|
+
loadData(attachmentId: string): Promise<Uint8Array | undefined>;
|
|
147
|
+
/**
|
|
148
|
+
* Get metadata for an attachment without loading data.
|
|
149
|
+
*/
|
|
150
|
+
getMetadata(attachmentId: string): Promise<AttachmentMetadata | undefined>;
|
|
151
|
+
/**
|
|
152
|
+
* List all attachments for a document.
|
|
153
|
+
*/
|
|
154
|
+
listByDocument(collection: string, documentId: string): Promise<AttachmentMetadata[]>;
|
|
155
|
+
/**
|
|
156
|
+
* Delete an attachment.
|
|
157
|
+
*/
|
|
158
|
+
delete(attachmentId: string): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Update the document-to-attachment index.
|
|
161
|
+
*/
|
|
162
|
+
private updateIndex;
|
|
163
|
+
/**
|
|
164
|
+
* Remove an attachment from the document index.
|
|
165
|
+
*/
|
|
166
|
+
private removeFromIndex;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Manages attachments with caching, chunked upload, and progress tracking.
|
|
170
|
+
*/
|
|
171
|
+
export declare class AttachmentManager {
|
|
172
|
+
private readonly storage;
|
|
173
|
+
private readonly chunkSize;
|
|
174
|
+
private readonly maxCacheSize;
|
|
175
|
+
private readonly downloadPolicy;
|
|
176
|
+
private readonly cache;
|
|
177
|
+
private currentCacheSize;
|
|
178
|
+
private cacheHits;
|
|
179
|
+
private cacheMisses;
|
|
180
|
+
private cacheEvictions;
|
|
181
|
+
constructor(storage: AttachmentStorage, config?: AttachmentConfig);
|
|
182
|
+
/**
|
|
183
|
+
* Upload an attachment.
|
|
184
|
+
*/
|
|
185
|
+
upload(options: UploadOptions): Promise<UploadResult>;
|
|
186
|
+
/**
|
|
187
|
+
* Get an attachment by ID.
|
|
188
|
+
*/
|
|
189
|
+
get(attachmentId: string): Promise<Attachment | undefined>;
|
|
190
|
+
/**
|
|
191
|
+
* Split data into chunks.
|
|
192
|
+
*/
|
|
193
|
+
splitIntoChunks(data: Uint8Array): Uint8Array[];
|
|
194
|
+
/**
|
|
195
|
+
* Reassemble chunks into original data.
|
|
196
|
+
*/
|
|
197
|
+
reassembleChunks(chunks: Uint8Array[]): Uint8Array;
|
|
198
|
+
/**
|
|
199
|
+
* Calculate checksum for data.
|
|
200
|
+
* Uses a simple hash in browser environments.
|
|
201
|
+
*/
|
|
202
|
+
calculateChecksum(data: Uint8Array): Promise<string>;
|
|
203
|
+
/**
|
|
204
|
+
* Get cache statistics.
|
|
205
|
+
*/
|
|
206
|
+
getCacheStats(): CacheStats;
|
|
207
|
+
/**
|
|
208
|
+
* Clear the cache.
|
|
209
|
+
*/
|
|
210
|
+
clearCache(): void;
|
|
211
|
+
/**
|
|
212
|
+
* Add an attachment to the cache, evicting old entries if needed.
|
|
213
|
+
*/
|
|
214
|
+
private addToCache;
|
|
215
|
+
/**
|
|
216
|
+
* Evict the least recently used cache entry.
|
|
217
|
+
*/
|
|
218
|
+
private evictLRU;
|
|
219
|
+
/**
|
|
220
|
+
* Create an Attachment object with lazy loading support.
|
|
221
|
+
*/
|
|
222
|
+
private createAttachment;
|
|
223
|
+
}
|
|
224
|
+
export {};
|
|
225
|
+
//# sourceMappingURL=attachment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../src/client/attachment.ts"],"names":[],"mappings":"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"}
|