@quereus/sync-coordinator 0.8.2 → 0.10.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.
Files changed (51) hide show
  1. package/README.md +12 -3
  2. package/dist/src/common/index.d.ts +1 -0
  3. package/dist/src/common/index.d.ts.map +1 -1
  4. package/dist/src/common/index.js +1 -0
  5. package/dist/src/common/index.js.map +1 -1
  6. package/dist/src/common/serialization.d.ts +28 -0
  7. package/dist/src/common/serialization.d.ts.map +1 -0
  8. package/dist/src/common/serialization.js +128 -0
  9. package/dist/src/common/serialization.js.map +1 -0
  10. package/dist/src/index.d.ts +1 -1
  11. package/dist/src/index.d.ts.map +1 -1
  12. package/dist/src/index.js +1 -1
  13. package/dist/src/index.js.map +1 -1
  14. package/dist/src/metrics/coordinator-metrics.d.ts +1 -0
  15. package/dist/src/metrics/coordinator-metrics.d.ts.map +1 -1
  16. package/dist/src/metrics/coordinator-metrics.js +2 -0
  17. package/dist/src/metrics/coordinator-metrics.js.map +1 -1
  18. package/dist/src/server/routes.d.ts.map +1 -1
  19. package/dist/src/server/routes.js +7 -31
  20. package/dist/src/server/routes.js.map +1 -1
  21. package/dist/src/server/websocket.d.ts +1 -0
  22. package/dist/src/server/websocket.d.ts.map +1 -1
  23. package/dist/src/server/websocket.js +74 -51
  24. package/dist/src/server/websocket.js.map +1 -1
  25. package/dist/src/service/coordinator-service.d.ts +10 -5
  26. package/dist/src/service/coordinator-service.d.ts.map +1 -1
  27. package/dist/src/service/coordinator-service.js +108 -41
  28. package/dist/src/service/coordinator-service.js.map +1 -1
  29. package/dist/src/service/index.d.ts +2 -2
  30. package/dist/src/service/index.d.ts.map +1 -1
  31. package/dist/src/service/index.js +1 -1
  32. package/dist/src/service/index.js.map +1 -1
  33. package/dist/src/service/s3-batch-store.d.ts +10 -6
  34. package/dist/src/service/s3-batch-store.d.ts.map +1 -1
  35. package/dist/src/service/s3-batch-store.js +42 -10
  36. package/dist/src/service/s3-batch-store.js.map +1 -1
  37. package/dist/src/service/s3-config.d.ts +10 -0
  38. package/dist/src/service/s3-config.d.ts.map +1 -1
  39. package/dist/src/service/s3-config.js +7 -0
  40. package/dist/src/service/s3-config.js.map +1 -1
  41. package/dist/src/service/s3-snapshot-store.d.ts +17 -6
  42. package/dist/src/service/s3-snapshot-store.d.ts.map +1 -1
  43. package/dist/src/service/s3-snapshot-store.js +64 -15
  44. package/dist/src/service/s3-snapshot-store.js.map +1 -1
  45. package/dist/src/service/store-manager.d.ts +32 -0
  46. package/dist/src/service/store-manager.d.ts.map +1 -1
  47. package/dist/src/service/store-manager.js +119 -8
  48. package/dist/src/service/store-manager.js.map +1 -1
  49. package/dist/src/service/types.d.ts +2 -1
  50. package/dist/src/service/types.d.ts.map +1 -1
  51. package/package.json +6 -6
package/README.md CHANGED
@@ -79,6 +79,10 @@ Enable S3 batch storage for durability and disaster recovery:
79
79
  | `S3_SECRET_ACCESS_KEY` | AWS secret key | — |
80
80
  | `S3_FORCE_PATH_STYLE` | Use path-style URLs (for MinIO) | `false` |
81
81
  | `S3_KEY_PREFIX` | Key prefix for all objects | — |
82
+ | `DISK_EVICTION_ENABLED` | Enable disk eviction for idle stores backed by S3 (auto-enabled with `S3_BUCKET`) | `true` if S3 set |
83
+ | `DISK_EVICTION_IDLE_MS` | Idle time (ms) before a closed store's local directory is deleted | `3600000` (1 hr) |
84
+
85
+ When S3 is configured, local LevelDB directories act as a write-back cache. After a store is closed (idle 5 min) and remains unused for `DISK_EVICTION_IDLE_MS`, its local directory is deleted — provided an S3 snapshot exists. On next access, the store is automatically restored from S3.
82
86
 
83
87
  #### Local Testing with MinIO
84
88
 
@@ -112,9 +116,9 @@ npx sync-coordinator --help
112
116
  |----------|--------|-------------|
113
117
  | `/sync/status` | GET | Health check and stats |
114
118
  | `/sync/metrics` | GET | Prometheus metrics |
115
- | `/sync/changes` | GET | Get changes since HLC |
116
- | `/sync/changes` | POST | Apply changes |
117
- | `/sync/snapshot` | GET | Stream full snapshot |
119
+ | `/sync/:databaseId/changes` | GET | Get changes since HLC |
120
+ | `/sync/:databaseId/changes` | POST | Apply changes |
121
+ | `/sync/:databaseId/snapshot` | GET | Stream full snapshot (NDJSON) |
118
122
  | `/sync/ws` | WS | WebSocket for real-time sync |
119
123
 
120
124
  ## Prometheus Metrics
@@ -128,10 +132,15 @@ sync_http_requests_total # HTTP requests by endpoint/status
128
132
  sync_changes_applied_total # Changes applied
129
133
  sync_changes_received_total # Changes received from clients
130
134
  sync_changes_rejected_total # Changes rejected during validation
135
+ sync_changes_broadcast_total # Changes broadcast to clients
136
+ sync_broadcast_errors_total # Broadcast send failures
137
+ sync_snapshot_requests_total # Snapshot requests
138
+ sync_snapshot_chunks_total # Snapshot chunks sent
131
139
  sync_auth_attempts_total # Authentication attempts
132
140
  sync_auth_failures_total # Authentication failures
133
141
  sync_apply_changes_duration_seconds # Apply operation duration histogram
134
142
  sync_get_changes_duration_seconds # Get changes duration histogram
143
+ sync_change_batch_size # Change batch size histogram
135
144
  ```
136
145
 
137
146
  ## Custom Hooks
@@ -2,4 +2,5 @@
2
2
  * Common utilities for sync-coordinator.
3
3
  */
4
4
  export { createLogger, serverLog, httpLog, wsLog, serviceLog, authLog, configLog, } from './logger.js';
5
+ export { serializeChangeSet, deserializeChangeSet, serializeSnapshotChunk, deserializeSnapshotChunk, } from './serialization.js';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/common/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,YAAY,EACZ,SAAS,EACT,OAAO,EACP,KAAK,EACL,UAAU,EACV,OAAO,EACP,SAAS,GACV,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/common/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,YAAY,EACZ,SAAS,EACT,OAAO,EACP,KAAK,EACL,UAAU,EACV,OAAO,EACP,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
@@ -2,4 +2,5 @@
2
2
  * Common utilities for sync-coordinator.
3
3
  */
4
4
  export { createLogger, serverLog, httpLog, wsLog, serviceLog, authLog, configLog, } from './logger.js';
5
+ export { serializeChangeSet, deserializeChangeSet, serializeSnapshotChunk, deserializeSnapshotChunk, } from './serialization.js';
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/common/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,YAAY,EACZ,SAAS,EACT,OAAO,EACP,KAAK,EACL,UAAU,EACV,OAAO,EACP,SAAS,GACV,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/common/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,YAAY,EACZ,SAAS,EACT,OAAO,EACP,KAAK,EACL,UAAU,EACV,OAAO,EACP,SAAS,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Shared serialization for JSON transport.
3
+ *
4
+ * Used by both the WebSocket handler and the HTTP routes
5
+ * for consistent wire format.
6
+ */
7
+ import { type ChangeSet, type SnapshotChunk } from '@quereus/sync';
8
+ /**
9
+ * Serialize a ChangeSet for JSON transport.
10
+ * Converts binary fields (siteId, HLCs) to base64 strings.
11
+ */
12
+ export declare function serializeChangeSet(cs: ChangeSet): object;
13
+ /**
14
+ * Serialize a SnapshotChunk for JSON transport.
15
+ * Converts binary fields (siteId, HLCs) to base64 strings.
16
+ */
17
+ export declare function serializeSnapshotChunk(chunk: SnapshotChunk): object;
18
+ /**
19
+ * Deserialize a SnapshotChunk from JSON transport format.
20
+ * Converts base64 strings back to binary fields (SiteId, HLC).
21
+ */
22
+ export declare function deserializeSnapshotChunk(obj: unknown): SnapshotChunk;
23
+ /**
24
+ * Deserialize a ChangeSet from JSON transport format.
25
+ * Converts base64 strings back to binary fields.
26
+ */
27
+ export declare function deserializeChangeSet(cs: unknown): ChangeSet;
28
+ //# sourceMappingURL=serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../../../src/common/serialization.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAKN,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,MAAM,eAAe,CAAC;AAEvB;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,CAcxD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAkCnE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAqCpE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,OAAO,GAAG,SAAS,CAe3D"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Shared serialization for JSON transport.
3
+ *
4
+ * Used by both the WebSocket handler and the HTTP routes
5
+ * for consistent wire format.
6
+ */
7
+ import { siteIdFromBase64, siteIdToBase64, deserializeHLC, serializeHLC, } from '@quereus/sync';
8
+ /**
9
+ * Serialize a ChangeSet for JSON transport.
10
+ * Converts binary fields (siteId, HLCs) to base64 strings.
11
+ */
12
+ export function serializeChangeSet(cs) {
13
+ return {
14
+ siteId: siteIdToBase64(cs.siteId),
15
+ transactionId: cs.transactionId,
16
+ hlc: Buffer.from(serializeHLC(cs.hlc)).toString('base64'),
17
+ changes: cs.changes.map(c => ({
18
+ ...c,
19
+ hlc: Buffer.from(serializeHLC(c.hlc)).toString('base64'),
20
+ })),
21
+ schemaMigrations: cs.schemaMigrations.map(m => ({
22
+ ...m,
23
+ hlc: Buffer.from(serializeHLC(m.hlc)).toString('base64'),
24
+ })),
25
+ };
26
+ }
27
+ /**
28
+ * Serialize a SnapshotChunk for JSON transport.
29
+ * Converts binary fields (siteId, HLCs) to base64 strings.
30
+ */
31
+ export function serializeSnapshotChunk(chunk) {
32
+ switch (chunk.type) {
33
+ case 'header':
34
+ return {
35
+ type: chunk.type,
36
+ siteId: siteIdToBase64(chunk.siteId),
37
+ hlc: Buffer.from(serializeHLC(chunk.hlc)).toString('base64'),
38
+ tableCount: chunk.tableCount,
39
+ migrationCount: chunk.migrationCount,
40
+ snapshotId: chunk.snapshotId,
41
+ };
42
+ case 'column-versions':
43
+ return {
44
+ type: chunk.type,
45
+ schema: chunk.schema,
46
+ table: chunk.table,
47
+ entries: chunk.entries.map(([key, hlc, value]) => [
48
+ key,
49
+ Buffer.from(serializeHLC(hlc)).toString('base64'),
50
+ value,
51
+ ]),
52
+ };
53
+ case 'schema-migration':
54
+ return {
55
+ type: chunk.type,
56
+ migration: {
57
+ ...chunk.migration,
58
+ hlc: Buffer.from(serializeHLC(chunk.migration.hlc)).toString('base64'),
59
+ },
60
+ };
61
+ // table-start, table-end, footer have no binary fields
62
+ default:
63
+ return chunk;
64
+ }
65
+ }
66
+ /**
67
+ * Deserialize a SnapshotChunk from JSON transport format.
68
+ * Converts base64 strings back to binary fields (SiteId, HLC).
69
+ */
70
+ export function deserializeSnapshotChunk(obj) {
71
+ const chunk = obj;
72
+ switch (chunk.type) {
73
+ case 'header':
74
+ return {
75
+ type: 'header',
76
+ siteId: siteIdFromBase64(chunk.siteId),
77
+ hlc: deserializeHLC(Buffer.from(chunk.hlc, 'base64')),
78
+ tableCount: chunk.tableCount,
79
+ migrationCount: chunk.migrationCount,
80
+ snapshotId: chunk.snapshotId,
81
+ };
82
+ case 'column-versions':
83
+ return {
84
+ type: 'column-versions',
85
+ schema: chunk.schema,
86
+ table: chunk.table,
87
+ entries: chunk.entries.map(([key, hlc, value]) => [
88
+ key,
89
+ deserializeHLC(Buffer.from(hlc, 'base64')),
90
+ value,
91
+ ]),
92
+ };
93
+ case 'schema-migration': {
94
+ const migration = chunk.migration;
95
+ return {
96
+ type: 'schema-migration',
97
+ migration: {
98
+ ...migration,
99
+ hlc: deserializeHLC(Buffer.from(migration.hlc, 'base64')),
100
+ },
101
+ };
102
+ }
103
+ // table-start, table-end, footer have no binary fields
104
+ default:
105
+ return chunk;
106
+ }
107
+ }
108
+ /**
109
+ * Deserialize a ChangeSet from JSON transport format.
110
+ * Converts base64 strings back to binary fields.
111
+ */
112
+ export function deserializeChangeSet(cs) {
113
+ const obj = cs;
114
+ return {
115
+ siteId: siteIdFromBase64(obj.siteId),
116
+ transactionId: obj.transactionId,
117
+ hlc: deserializeHLC(Buffer.from(obj.hlc, 'base64')),
118
+ changes: obj.changes.map(c => ({
119
+ ...c,
120
+ hlc: deserializeHLC(Buffer.from(c.hlc, 'base64')),
121
+ })),
122
+ schemaMigrations: (obj.schemaMigrations || []).map(m => ({
123
+ ...m,
124
+ hlc: deserializeHLC(Buffer.from(m.hlc, 'base64')),
125
+ })),
126
+ };
127
+ }
128
+ //# sourceMappingURL=serialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialization.js","sourceRoot":"","sources":["../../../src/common/serialization.ts"],"names":[],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * ```
15
15
  */
16
16
  export { type CoordinatorConfig, type PartialCoordinatorConfig, type CorsConfig, type AuthConfig, type SyncSettings, type LoggingConfig, DEFAULT_CONFIG, loadConfig, loadConfigFile, loadEnvConfig, } from './config/index.js';
17
- export { type ClientIdentity, type ClientSession, type AuthContext, type SyncOperation, type RejectedChange, type ValidationResult, type CoordinatorHooks, CoordinatorService, type CoordinatorServiceOptions, StoreManager, type StoreEntry, type StoreManagerConfig, type StoreManagerHooks, type StoreContext, type S3StorageConfig, createS3Client, buildBatchKey, buildSnapshotKey, parseS3ConfigFromEnv, S3BatchStore, createS3BatchStore, type SyncBatch, type StoragePathResolver, S3SnapshotStore, createS3SnapshotStore, type SnapshotMetadata, type SnapshotScheduleConfig, } from './service/index.js';
17
+ export { type ClientIdentity, type ClientSession, type AuthContext, type SyncOperation, type RejectedChange, type ValidationResult, type CoordinatorHooks, CoordinatorService, type CoordinatorServiceOptions, StoreManager, type StoreEntry, type StoreManagerConfig, type StoreManagerHooks, type StoreContext, type S3StorageConfig, type StoragePathResolver, createS3Client, buildBatchKey, buildSnapshotKey, defaultStoragePathResolver, parseS3ConfigFromEnv, S3BatchStore, createS3BatchStore, type SyncBatch, S3SnapshotStore, createS3SnapshotStore, type SnapshotMetadata, type SnapshotScheduleConfig, } from './service/index.js';
18
18
  export { createCoordinatorServer, type CoordinatorServer, type CoordinatorServerOptions, registerRoutes, registerWebSocket, } from './server/index.js';
19
19
  export { type CoordinatorMetrics, type CounterMetric, type GaugeMetric, type HistogramMetric, MetricsRegistry, globalRegistry, createCoordinatorMetrics, } from './metrics/index.js';
20
20
  export { createLogger } from './common/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,cAAc,EACd,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAEjB,KAAK,eAAe,EACpB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACxB,eAAe,EACf,qBAAqB,EACrB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,cAAc,EACd,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,eAAe,EACf,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,cAAc,EACd,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAEjB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,0BAA0B,EAC1B,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAClB,KAAK,SAAS,EACd,eAAe,EACf,qBAAqB,EACrB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,GAC5B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,cAAc,EACd,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,eAAe,EACf,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/src/index.js CHANGED
@@ -16,7 +16,7 @@
16
16
  // Configuration
17
17
  export { DEFAULT_CONFIG, loadConfig, loadConfigFile, loadEnvConfig, } from './config/index.js';
18
18
  // Service layer
19
- export { CoordinatorService, StoreManager, createS3Client, buildBatchKey, buildSnapshotKey, parseS3ConfigFromEnv, S3BatchStore, createS3BatchStore, S3SnapshotStore, createS3SnapshotStore, } from './service/index.js';
19
+ export { CoordinatorService, StoreManager, createS3Client, buildBatchKey, buildSnapshotKey, defaultStoragePathResolver, parseS3ConfigFromEnv, S3BatchStore, createS3BatchStore, S3SnapshotStore, createS3SnapshotStore, } from './service/index.js';
20
20
  // Server
21
21
  export { createCoordinatorServer, registerRoutes, registerWebSocket, } from './server/index.js';
22
22
  // Metrics
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,gBAAgB;AAChB,OAAO,EAOL,cAAc,EACd,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,gBAAgB;AAChB,OAAO,EAQL,kBAAkB,EAElB,YAAY,EAOZ,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAGlB,eAAe,EACf,qBAAqB,GAGtB,MAAM,oBAAoB,CAAC;AAE5B,SAAS;AACT,OAAO,EACL,uBAAuB,EAGvB,cAAc,EACd,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,UAAU;AACV,OAAO,EAKL,eAAe,EACf,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,gBAAgB;AAChB,OAAO,EAOL,cAAc,EACd,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,gBAAgB;AAChB,OAAO,EAQL,kBAAkB,EAElB,YAAY,EAQZ,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,0BAA0B,EAC1B,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,EAElB,eAAe,EACf,qBAAqB,GAGtB,MAAM,oBAAoB,CAAC;AAE5B,SAAS;AACT,OAAO,EACL,uBAAuB,EAGvB,cAAc,EACd,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAE3B,UAAU;AACV,OAAO,EAKL,eAAe,EACf,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAE5B,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
@@ -14,6 +14,7 @@ export declare function createCoordinatorMetrics(registry?: MetricsRegistry): {
14
14
  changesReceivedTotal: import("./types.js").CounterMetric;
15
15
  changesRejectedTotal: import("./types.js").CounterMetric;
16
16
  changesBroadcastTotal: import("./types.js").CounterMetric;
17
+ broadcastErrorsTotal: import("./types.js").CounterMetric;
17
18
  snapshotRequestsTotal: import("./types.js").CounterMetric;
18
19
  snapshotChunksTotal: import("./types.js").CounterMetric;
19
20
  applyChangesDuration: import("./types.js").HistogramMetric;
@@ -1 +1 @@
1
- {"version":3,"file":"coordinator-metrics.d.ts","sourceRoot":"","sources":["../../../src/metrics/coordinator-metrics.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrE;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,GAAE,eAAgC;;;;;;;;;;;;;;;;;EAkHlF;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"coordinator-metrics.d.ts","sourceRoot":"","sources":["../../../src/metrics/coordinator-metrics.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrE;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,GAAE,eAAgC;;;;;;;;;;;;;;;;;;EAwHlF;AAED,MAAM,MAAM,kBAAkB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
@@ -18,6 +18,7 @@ export function createCoordinatorMetrics(registry = globalRegistry) {
18
18
  const changesReceivedTotal = registry.registerCounter('sync_changes_received_total', 'Total number of changes received from clients');
19
19
  const changesRejectedTotal = registry.registerCounter('sync_changes_rejected_total', 'Total number of changes rejected during validation');
20
20
  const changesBroadcastTotal = registry.registerCounter('sync_changes_broadcast_total', 'Total number of changes broadcast to clients');
21
+ const broadcastErrorsTotal = registry.registerCounter('sync_broadcast_errors_total', 'Total broadcast send failures');
21
22
  // Snapshot metrics
22
23
  const snapshotRequestsTotal = registry.registerCounter('sync_snapshot_requests_total', 'Total snapshot requests');
23
24
  const snapshotChunksTotal = registry.registerCounter('sync_snapshot_chunks_total', 'Total snapshot chunks sent');
@@ -40,6 +41,7 @@ export function createCoordinatorMetrics(registry = globalRegistry) {
40
41
  changesReceivedTotal,
41
42
  changesRejectedTotal,
42
43
  changesBroadcastTotal,
44
+ broadcastErrorsTotal,
43
45
  // Snapshots
44
46
  snapshotRequestsTotal,
45
47
  snapshotChunksTotal,
@@ -1 +1 @@
1
- {"version":3,"file":"coordinator-metrics.js","sourceRoot":"","sources":["../../../src/metrics/coordinator-metrics.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAwB,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAA4B,cAAc;IACjF,qBAAqB;IACrB,MAAM,mBAAmB,GAAG,QAAQ,CAAC,aAAa,CAChD,mCAAmC,EACnC,gDAAgD,CACjD,CAAC;IAEF,MAAM,kBAAkB,GAAG,QAAQ,CAAC,eAAe,CACjD,kCAAkC,EAClC,2CAA2C,CAC5C,CAAC;IAEF,uBAAuB;IACvB,MAAM,iBAAiB,GAAG,QAAQ,CAAC,eAAe,CAChD,0BAA0B,EAC1B,4CAA4C,CAC7C,CAAC;IAEF,MAAM,mBAAmB,GAAG,QAAQ,CAAC,iBAAiB,CACpD,oCAAoC,EACpC,kCAAkC,CACnC,CAAC;IAEF,yBAAyB;IACzB,MAAM,mBAAmB,GAAG,QAAQ,CAAC,eAAe,CAClD,4BAA4B,EAC5B,iCAAiC,CAClC,CAAC;IAEF,MAAM,oBAAoB,GAAG,QAAQ,CAAC,eAAe,CACnD,6BAA6B,EAC7B,+CAA+C,CAChD,CAAC;IAEF,MAAM,oBAAoB,GAAG,QAAQ,CAAC,eAAe,CACnD,6BAA6B,EAC7B,oDAAoD,CACrD,CAAC;IAEF,MAAM,qBAAqB,GAAG,QAAQ,CAAC,eAAe,CACpD,8BAA8B,EAC9B,8CAA8C,CAC/C,CAAC;IAEF,mBAAmB;IACnB,MAAM,qBAAqB,GAAG,QAAQ,CAAC,eAAe,CACpD,8BAA8B,EAC9B,yBAAyB,CAC1B,CAAC;IAEF,MAAM,mBAAmB,GAAG,QAAQ,CAAC,eAAe,CAClD,4BAA4B,EAC5B,4BAA4B,CAC7B,CAAC;IAEF,sBAAsB;IACtB,MAAM,oBAAoB,GAAG,QAAQ,CAAC,iBAAiB,CACrD,qCAAqC,EACrC,kCAAkC,CACnC,CAAC;IAEF,MAAM,kBAAkB,GAAG,QAAQ,CAAC,iBAAiB,CACnD,mCAAmC,EACnC,uCAAuC,CACxC,CAAC;IAEF,MAAM,eAAe,GAAG,QAAQ,CAAC,iBAAiB,CAChD,wBAAwB,EACxB,8BAA8B,EAC9B,oBAAoB,CACrB,CAAC;IAEF,eAAe;IACf,MAAM,iBAAiB,GAAG,QAAQ,CAAC,eAAe,CAChD,0BAA0B,EAC1B,+BAA+B,CAChC,CAAC;IAEF,MAAM,iBAAiB,GAAG,QAAQ,CAAC,eAAe,CAChD,0BAA0B,EAC1B,+BAA+B,CAChC,CAAC;IAEF,OAAO;QACL,aAAa;QACb,mBAAmB;QACnB,kBAAkB;QAElB,OAAO;QACP,iBAAiB;QACjB,mBAAmB;QAEnB,kBAAkB;QAClB,mBAAmB;QACnB,oBAAoB;QACpB,oBAAoB;QACpB,qBAAqB;QAErB,YAAY;QACZ,qBAAqB;QACrB,mBAAmB;QAEnB,cAAc;QACd,oBAAoB;QACpB,kBAAkB;QAClB,eAAe;QAEf,OAAO;QACP,iBAAiB;QACjB,iBAAiB;QAEjB,qBAAqB;QACrB,QAAQ;KACT,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"coordinator-metrics.js","sourceRoot":"","sources":["../../../src/metrics/coordinator-metrics.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,cAAc,EAAwB,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAA4B,cAAc;IACjF,qBAAqB;IACrB,MAAM,mBAAmB,GAAG,QAAQ,CAAC,aAAa,CAChD,mCAAmC,EACnC,gDAAgD,CACjD,CAAC;IAEF,MAAM,kBAAkB,GAAG,QAAQ,CAAC,eAAe,CACjD,kCAAkC,EAClC,2CAA2C,CAC5C,CAAC;IAEF,uBAAuB;IACvB,MAAM,iBAAiB,GAAG,QAAQ,CAAC,eAAe,CAChD,0BAA0B,EAC1B,4CAA4C,CAC7C,CAAC;IAEF,MAAM,mBAAmB,GAAG,QAAQ,CAAC,iBAAiB,CACpD,oCAAoC,EACpC,kCAAkC,CACnC,CAAC;IAEF,yBAAyB;IACzB,MAAM,mBAAmB,GAAG,QAAQ,CAAC,eAAe,CAClD,4BAA4B,EAC5B,iCAAiC,CAClC,CAAC;IAEF,MAAM,oBAAoB,GAAG,QAAQ,CAAC,eAAe,CACnD,6BAA6B,EAC7B,+CAA+C,CAChD,CAAC;IAEF,MAAM,oBAAoB,GAAG,QAAQ,CAAC,eAAe,CACnD,6BAA6B,EAC7B,oDAAoD,CACrD,CAAC;IAEF,MAAM,qBAAqB,GAAG,QAAQ,CAAC,eAAe,CACpD,8BAA8B,EAC9B,8CAA8C,CAC/C,CAAC;IAEF,MAAM,oBAAoB,GAAG,QAAQ,CAAC,eAAe,CACnD,6BAA6B,EAC7B,+BAA+B,CAChC,CAAC;IAEF,mBAAmB;IACnB,MAAM,qBAAqB,GAAG,QAAQ,CAAC,eAAe,CACpD,8BAA8B,EAC9B,yBAAyB,CAC1B,CAAC;IAEF,MAAM,mBAAmB,GAAG,QAAQ,CAAC,eAAe,CAClD,4BAA4B,EAC5B,4BAA4B,CAC7B,CAAC;IAEF,sBAAsB;IACtB,MAAM,oBAAoB,GAAG,QAAQ,CAAC,iBAAiB,CACrD,qCAAqC,EACrC,kCAAkC,CACnC,CAAC;IAEF,MAAM,kBAAkB,GAAG,QAAQ,CAAC,iBAAiB,CACnD,mCAAmC,EACnC,uCAAuC,CACxC,CAAC;IAEF,MAAM,eAAe,GAAG,QAAQ,CAAC,iBAAiB,CAChD,wBAAwB,EACxB,8BAA8B,EAC9B,oBAAoB,CACrB,CAAC;IAEF,eAAe;IACf,MAAM,iBAAiB,GAAG,QAAQ,CAAC,eAAe,CAChD,0BAA0B,EAC1B,+BAA+B,CAChC,CAAC;IAEF,MAAM,iBAAiB,GAAG,QAAQ,CAAC,eAAe,CAChD,0BAA0B,EAC1B,+BAA+B,CAChC,CAAC;IAEF,OAAO;QACL,aAAa;QACb,mBAAmB;QACnB,kBAAkB;QAElB,OAAO;QACP,iBAAiB;QACjB,mBAAmB;QAEnB,kBAAkB;QAClB,mBAAmB;QACnB,oBAAoB;QACpB,oBAAoB;QACpB,qBAAqB;QACrB,oBAAoB;QAEpB,YAAY;QACZ,qBAAqB;QACrB,mBAAmB;QAEnB,cAAc;QACd,oBAAoB;QACpB,kBAAkB;QAClB,eAAe;QAEf,OAAO;QACP,iBAAiB;QACjB,iBAAiB;QAEjB,qBAAqB;QACrB,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/server/routes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAE7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAI5E;;GAEG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,MAAM,GACf,IAAI,CAoLN"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/server/routes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAgC,MAAM,SAAS,CAAC;AAE7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAK5E;;GAEG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,MAAM,GACf,IAAI,CA0JN"}
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * HTTP routes for sync operations.
3
3
  */
4
- import { siteIdFromBase64, siteIdToBase64, deserializeHLC, serializeHLC } from '@quereus/sync';
4
+ import { siteIdFromBase64, deserializeHLC } from '@quereus/sync';
5
5
  import { httpLog } from '../common/logger.js';
6
+ import { serializeChangeSet, deserializeChangeSet, serializeSnapshotChunk } from '../common/serialization.js';
6
7
  /**
7
8
  * Register sync HTTP routes.
8
9
  */
@@ -84,20 +85,7 @@ export function registerRoutes(app, service, basePath) {
84
85
  sinceHLC = deserializeHLC(hlcBytes);
85
86
  }
86
87
  const changes = await service.getChangesSince(databaseId, client, sinceHLC);
87
- // Serialize HLCs in response for JSON transport
88
- const serializedChanges = changes.map(cs => ({
89
- ...cs,
90
- siteId: siteIdToBase64(cs.siteId),
91
- hlc: Buffer.from(serializeHLC(cs.hlc)).toString('base64'),
92
- changes: cs.changes.map(c => ({
93
- ...c,
94
- hlc: Buffer.from(serializeHLC(c.hlc)).toString('base64'),
95
- })),
96
- schemaMigrations: cs.schemaMigrations.map(m => ({
97
- ...m,
98
- hlc: Buffer.from(serializeHLC(m.hlc)).toString('base64'),
99
- })),
100
- }));
88
+ const serializedChanges = changes.map(cs => serializeChangeSet(cs));
101
89
  return reply.send({ ok: true, data: { changes: serializedChanges } });
102
90
  }
103
91
  catch (err) {
@@ -120,20 +108,7 @@ export function registerRoutes(app, service, basePath) {
120
108
  if (!body.changes || !Array.isArray(body.changes)) {
121
109
  return errorResponse(reply, 'INVALID_BODY', 'Request body must contain changes array');
122
110
  }
123
- // Deserialize HLCs from JSON transport format
124
- const changes = body.changes.map((cs) => ({
125
- siteId: siteIdFromBase64(cs.siteId),
126
- transactionId: cs.transactionId,
127
- hlc: deserializeHLC(Buffer.from(cs.hlc, 'base64')),
128
- changes: cs.changes.map(c => ({
129
- ...c,
130
- hlc: deserializeHLC(Buffer.from(c.hlc, 'base64')),
131
- })),
132
- schemaMigrations: (cs.schemaMigrations || []).map(m => ({
133
- ...m,
134
- hlc: deserializeHLC(Buffer.from(m.hlc, 'base64')),
135
- })),
136
- }));
111
+ const changes = body.changes.map(cs => deserializeChangeSet(cs));
137
112
  const result = await service.applyChanges(databaseId, client, changes);
138
113
  return reply.send({ ok: true, data: result });
139
114
  }
@@ -157,7 +132,7 @@ export function registerRoutes(app, service, basePath) {
157
132
  reply.raw.setHeader('Content-Type', 'application/x-ndjson');
158
133
  reply.raw.setHeader('Transfer-Encoding', 'chunked');
159
134
  for await (const chunk of service.getSnapshotStream(databaseId, client)) {
160
- const serialized = JSON.stringify(chunk) + '\n';
135
+ const serialized = JSON.stringify(serializeSnapshotChunk(chunk)) + '\n';
161
136
  reply.raw.write(serialized);
162
137
  }
163
138
  reply.raw.end();
@@ -165,7 +140,8 @@ export function registerRoutes(app, service, basePath) {
165
140
  catch (err) {
166
141
  const message = err instanceof Error ? err.message : 'Failed to get snapshot';
167
142
  httpLog('GET /%s/snapshot error: %s', databaseId, message);
168
- // Can't send error response if we've started streaming
143
+ // Write an error chunk so NDJSON clients can detect the failure
144
+ reply.raw.write(JSON.stringify({ error: message }) + '\n');
169
145
  reply.raw.end();
170
146
  }
171
147
  });
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/server/routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/server/routes.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  * WebSocket handler for real-time sync.
3
3
  */
4
4
  import type { FastifyInstance } from 'fastify';
5
+ import '@fastify/websocket';
5
6
  import type { CoordinatorService } from '../service/coordinator-service.js';
6
7
  /**
7
8
  * Register WebSocket handler.
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../../src/server/websocket.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAW/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAoD5E;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,MAAM,GACf,IAAI,CAgJN"}
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../../src/server/websocket.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAE/D,OAAO,oBAAoB,CAAC;AAS5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAoD5E;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,kBAAkB,EAC3B,QAAQ,EAAE,MAAM,GACf,IAAI,CAsMN"}
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * WebSocket handler for real-time sync.
3
3
  */
4
- import { siteIdFromBase64, siteIdToBase64, deserializeHLC, serializeHLC, } from '@quereus/sync';
5
- import { wsLog } from '../common/logger.js';
4
+ import '@fastify/websocket';
5
+ import { siteIdFromBase64, siteIdToBase64, deserializeHLC, } from '@quereus/sync';
6
+ import { wsLog, serializeChangeSet, deserializeChangeSet, serializeSnapshotChunk } from '../common/index.js';
6
7
  // ============================================================================
7
8
  // WebSocket Handler
8
9
  // ============================================================================
@@ -13,6 +14,7 @@ export function registerWebSocket(app, service, basePath) {
13
14
  app.get(`${basePath}/ws`, { websocket: true }, (socket, request) => {
14
15
  wsLog('New WebSocket connection from %s', request.ip);
15
16
  let session = null;
17
+ let socketClosed = false;
16
18
  const sendError = (code, message) => {
17
19
  socket.send(JSON.stringify({ type: 'error', code, message }));
18
20
  };
@@ -36,6 +38,9 @@ export function registerWebSocket(app, service, basePath) {
36
38
  case 'get_snapshot':
37
39
  await handleGetSnapshot();
38
40
  break;
41
+ case 'resume_snapshot':
42
+ await handleResumeSnapshot(message);
43
+ break;
39
44
  case 'ping':
40
45
  sendMessage({ type: 'pong' });
41
46
  break;
@@ -50,6 +55,7 @@ export function registerWebSocket(app, service, basePath) {
50
55
  }
51
56
  });
52
57
  socket.on('close', () => {
58
+ socketClosed = true;
53
59
  wsLog('WebSocket closed: %s', session?.connectionId?.slice(0, 8) || 'no-session');
54
60
  if (session) {
55
61
  service.unregisterSession(session.connectionId);
@@ -79,6 +85,14 @@ export function registerWebSocket(app, service, basePath) {
79
85
  };
80
86
  const identity = await service.authenticate(authContext);
81
87
  session = await service.registerSession(msg.databaseId, socket, identity, authContext);
88
+ // If socket closed during registration, the close handler couldn't
89
+ // call unregisterSession because `session` wasn't assigned yet.
90
+ // Clean up now that we have the connectionId.
91
+ if (socketClosed) {
92
+ service.unregisterSession(session.connectionId);
93
+ session = null;
94
+ return;
95
+ }
82
96
  const serverSiteId = await service.getSiteId(msg.databaseId, identity);
83
97
  sendMessage({
84
98
  type: 'handshake_ack',
@@ -89,6 +103,12 @@ export function registerWebSocket(app, service, basePath) {
89
103
  wsLog('Handshake complete: %s (db: %s)', session.connectionId.slice(0, 8), msg.databaseId);
90
104
  }
91
105
  catch (err) {
106
+ // If registerSession succeeded but a subsequent step threw,
107
+ // clean up the registered session to release the store reference.
108
+ if (session) {
109
+ service.unregisterSession(session.connectionId);
110
+ session = null;
111
+ }
92
112
  const errMsg = err instanceof Error ? err.message : 'Authentication failed';
93
113
  sendError('AUTH_FAILED', errMsg);
94
114
  socket.close(4001, 'Authentication failed');
@@ -99,70 +119,73 @@ export function registerWebSocket(app, service, basePath) {
99
119
  sendError('NOT_AUTHENTICATED', 'Must handshake first');
100
120
  return;
101
121
  }
102
- let sinceHLC;
103
- if (msg.sinceHLC) {
104
- sinceHLC = deserializeHLC(Buffer.from(msg.sinceHLC, 'base64'));
122
+ try {
123
+ let sinceHLC;
124
+ if (msg.sinceHLC) {
125
+ sinceHLC = deserializeHLC(Buffer.from(msg.sinceHLC, 'base64'));
126
+ }
127
+ const changes = await service.getChangesSince(session.databaseId, session.identity, sinceHLC);
128
+ // Serialize for JSON transport
129
+ const serializedChanges = changes.map(cs => serializeChangeSet(cs));
130
+ sendMessage({ type: 'changes', changeSets: serializedChanges });
131
+ }
132
+ catch (err) {
133
+ const msg2 = err instanceof Error ? err.message : 'Failed to get changes';
134
+ wsLog('get_changes error: %s', msg2);
135
+ sendError('GET_CHANGES_ERROR', msg2);
105
136
  }
106
- const changes = await service.getChangesSince(session.databaseId, session.identity, sinceHLC);
107
- // Serialize for JSON transport
108
- const serializedChanges = changes.map(cs => serializeChangeSet(cs));
109
- sendMessage({ type: 'changes', changeSets: serializedChanges });
110
137
  }
111
138
  async function handleApplyChanges(msg) {
112
139
  if (!session) {
113
140
  sendError('NOT_AUTHENTICATED', 'Must handshake first');
114
141
  return;
115
142
  }
116
- // Deserialize from JSON transport
117
- const changes = msg.changes.map(cs => deserializeChangeSet(cs));
118
- const result = await service.applyChanges(session.databaseId, session.identity, changes);
119
- sendMessage({ type: 'apply_result', ...result });
143
+ try {
144
+ // Deserialize from JSON transport
145
+ const changes = msg.changes.map(cs => deserializeChangeSet(cs));
146
+ const result = await service.applyChanges(session.databaseId, session.identity, changes);
147
+ sendMessage({ type: 'apply_result', ...result });
148
+ }
149
+ catch (err) {
150
+ const msg2 = err instanceof Error ? err.message : 'Failed to apply changes';
151
+ wsLog('apply_changes error: %s', msg2);
152
+ sendError('APPLY_CHANGES_ERROR', msg2);
153
+ }
120
154
  }
121
155
  async function handleGetSnapshot() {
122
156
  if (!session) {
123
157
  sendError('NOT_AUTHENTICATED', 'Must handshake first');
124
158
  return;
125
159
  }
126
- // Stream snapshot chunks
127
- for await (const chunk of service.getSnapshotStream(session.databaseId, session.identity)) {
128
- sendMessage({ ...chunk, type: 'snapshot_chunk' });
160
+ try {
161
+ for await (const chunk of service.getSnapshotStream(session.databaseId, session.identity)) {
162
+ sendMessage({ type: 'snapshot_chunk', chunk: serializeSnapshotChunk(chunk) });
163
+ }
164
+ sendMessage({ type: 'snapshot_complete' });
165
+ }
166
+ catch (err) {
167
+ const msg = err instanceof Error ? err.message : 'Snapshot streaming failed';
168
+ wsLog('get_snapshot error: %s', msg);
169
+ sendError('SNAPSHOT_ERROR', msg);
170
+ }
171
+ }
172
+ async function handleResumeSnapshot(msg) {
173
+ if (!session) {
174
+ sendError('NOT_AUTHENTICATED', 'Must handshake first');
175
+ return;
176
+ }
177
+ try {
178
+ for await (const chunk of service.resumeSnapshotStream(session.databaseId, session.identity, msg.checkpoint)) {
179
+ sendMessage({ type: 'snapshot_chunk', chunk: serializeSnapshotChunk(chunk) });
180
+ }
181
+ sendMessage({ type: 'snapshot_complete' });
182
+ }
183
+ catch (err) {
184
+ const msg2 = err instanceof Error ? err.message : 'Snapshot resume failed';
185
+ wsLog('resume_snapshot error: %s', msg2);
186
+ sendError('SNAPSHOT_ERROR', msg2);
129
187
  }
130
- sendMessage({ type: 'snapshot_complete' });
131
188
  }
132
189
  });
133
190
  }
134
- // ============================================================================
135
- // Serialization Helpers
136
- // ============================================================================
137
- function serializeChangeSet(cs) {
138
- return {
139
- siteId: siteIdToBase64(cs.siteId),
140
- transactionId: cs.transactionId,
141
- hlc: Buffer.from(serializeHLC(cs.hlc)).toString('base64'),
142
- changes: cs.changes.map(c => ({
143
- ...c,
144
- hlc: Buffer.from(serializeHLC(c.hlc)).toString('base64'),
145
- })),
146
- schemaMigrations: cs.schemaMigrations.map(m => ({
147
- ...m,
148
- hlc: Buffer.from(serializeHLC(m.hlc)).toString('base64'),
149
- })),
150
- };
151
- }
152
- function deserializeChangeSet(cs) {
153
- const obj = cs;
154
- return {
155
- siteId: siteIdFromBase64(obj.siteId),
156
- transactionId: obj.transactionId,
157
- hlc: deserializeHLC(Buffer.from(obj.hlc, 'base64')),
158
- changes: obj.changes.map(c => ({
159
- ...c,
160
- hlc: deserializeHLC(Buffer.from(c.hlc, 'base64')),
161
- })),
162
- schemaMigrations: (obj.schemaMigrations || []).map(m => ({
163
- ...m,
164
- hlc: deserializeHLC(Buffer.from(m.hlc, 'base64')),
165
- })),
166
- };
167
- }
168
191
  //# sourceMappingURL=websocket.js.map