@quereus/sync-coordinator 0.3.5 → 0.3.7

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 (82) hide show
  1. package/README.md +2 -2
  2. package/dist/src/bin/sync-coordinator.d.ts +6 -0
  3. package/dist/src/bin/sync-coordinator.d.ts.map +1 -0
  4. package/dist/src/bin/sync-coordinator.js +85 -0
  5. package/dist/src/bin/sync-coordinator.js.map +1 -0
  6. package/dist/src/common/index.d.ts +5 -0
  7. package/dist/src/common/index.d.ts.map +1 -0
  8. package/dist/src/common/index.js +5 -0
  9. package/dist/src/common/index.js.map +1 -0
  10. package/dist/src/common/logger.d.ts +20 -0
  11. package/dist/src/common/logger.d.ts.map +1 -0
  12. package/dist/src/common/logger.js +24 -0
  13. package/dist/src/common/logger.js.map +1 -0
  14. package/dist/src/config/index.d.ts +6 -0
  15. package/dist/src/config/index.d.ts.map +1 -0
  16. package/dist/src/config/index.js +6 -0
  17. package/dist/src/config/index.js.map +1 -0
  18. package/dist/src/config/loader.d.ts +27 -0
  19. package/dist/src/config/loader.d.ts.map +1 -0
  20. package/dist/src/config/loader.js +144 -0
  21. package/dist/src/config/loader.js.map +1 -0
  22. package/dist/src/config/types.d.ts +74 -0
  23. package/dist/src/config/types.d.ts.map +1 -0
  24. package/dist/src/config/types.js +27 -0
  25. package/dist/src/config/types.js.map +1 -0
  26. package/dist/src/index.d.ts +21 -0
  27. package/dist/src/index.d.ts.map +1 -0
  28. package/dist/src/index.js +26 -0
  29. package/dist/src/index.js.map +1 -0
  30. package/dist/src/metrics/coordinator-metrics.d.ts +27 -0
  31. package/dist/src/metrics/coordinator-metrics.d.ts.map +1 -0
  32. package/dist/src/metrics/coordinator-metrics.js +57 -0
  33. package/dist/src/metrics/coordinator-metrics.js.map +1 -0
  34. package/dist/src/metrics/index.d.ts +7 -0
  35. package/dist/src/metrics/index.d.ts.map +1 -0
  36. package/dist/src/metrics/index.js +7 -0
  37. package/dist/src/metrics/index.js.map +1 -0
  38. package/dist/src/metrics/registry.d.ts +53 -0
  39. package/dist/src/metrics/registry.d.ts.map +1 -0
  40. package/dist/src/metrics/registry.js +158 -0
  41. package/dist/src/metrics/registry.js.map +1 -0
  42. package/dist/src/metrics/types.d.ts +62 -0
  43. package/dist/src/metrics/types.d.ts.map +1 -0
  44. package/dist/src/metrics/types.js +16 -0
  45. package/dist/src/metrics/types.js.map +1 -0
  46. package/dist/src/server/index.d.ts +7 -0
  47. package/dist/src/server/index.d.ts.map +1 -0
  48. package/dist/src/server/index.js +7 -0
  49. package/dist/src/server/index.js.map +1 -0
  50. package/dist/src/server/routes.d.ts +10 -0
  51. package/dist/src/server/routes.d.ts.map +1 -0
  52. package/dist/src/server/routes.js +174 -0
  53. package/dist/src/server/routes.js.map +1 -0
  54. package/dist/src/server/server.d.ts +34 -0
  55. package/dist/src/server/server.d.ts.map +1 -0
  56. package/dist/src/server/server.js +57 -0
  57. package/dist/src/server/server.js.map +1 -0
  58. package/dist/src/server/websocket.d.ts +10 -0
  59. package/dist/src/server/websocket.d.ts.map +1 -0
  60. package/dist/src/server/websocket.js +167 -0
  61. package/dist/src/server/websocket.js.map +1 -0
  62. package/dist/src/service/coordinator-service.d.ts +122 -0
  63. package/dist/src/service/coordinator-service.d.ts.map +1 -0
  64. package/dist/src/service/coordinator-service.js +436 -0
  65. package/dist/src/service/coordinator-service.js.map +1 -0
  66. package/dist/src/service/database-ids.d.ts +58 -0
  67. package/dist/src/service/database-ids.d.ts.map +1 -0
  68. package/dist/src/service/database-ids.js +128 -0
  69. package/dist/src/service/database-ids.js.map +1 -0
  70. package/dist/src/service/index.d.ts +8 -0
  71. package/dist/src/service/index.d.ts.map +1 -0
  72. package/dist/src/service/index.js +7 -0
  73. package/dist/src/service/index.js.map +1 -0
  74. package/dist/src/service/store-manager.d.ts +85 -0
  75. package/dist/src/service/store-manager.d.ts.map +1 -0
  76. package/dist/src/service/store-manager.js +194 -0
  77. package/dist/src/service/store-manager.js.map +1 -0
  78. package/dist/src/service/types.d.ts +146 -0
  79. package/dist/src/service/types.d.ts.map +1 -0
  80. package/dist/src/service/types.js +5 -0
  81. package/dist/src/service/types.js.map +1 -0
  82. package/package.json +4 -3
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Service layer exports.
3
+ */
4
+ export { CoordinatorService, } from './coordinator-service.js';
5
+ export { StoreManager, } from './store-manager.js';
6
+ export { isValidDatabaseId, parseDatabaseId, formatDatabaseId, } from './database-ids.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/service/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAYH,OAAO,EACL,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,YAAY,GAGb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * StoreManager - Multi-tenant LevelDB store management
3
+ *
4
+ * Manages lazy loading and cleanup of per-database LevelDB stores.
5
+ * Each database gets its own isolated store, opened on-demand and
6
+ * closed after idle timeout.
7
+ */
8
+ import { StoreEventEmitter } from '@quereus/store';
9
+ import { LevelDBStore } from '@quereus/plugin-leveldb';
10
+ import { type SyncManager } from '@quereus/sync';
11
+ export interface StoreEntry {
12
+ databaseId: string;
13
+ store: LevelDBStore;
14
+ syncManager: SyncManager;
15
+ storeEvents: StoreEventEmitter;
16
+ refCount: number;
17
+ lastAccess: number;
18
+ }
19
+ export interface StoreManagerConfig {
20
+ /** Base directory for all database stores */
21
+ dataDir: string;
22
+ /** Maximum number of stores to keep open (LRU eviction) */
23
+ maxOpenStores: number;
24
+ /** Idle timeout in ms before closing a store with refCount=0 */
25
+ idleTimeoutMs: number;
26
+ /** Interval for cleanup checks */
27
+ cleanupIntervalMs: number;
28
+ /** Sync config passed to createSyncModule */
29
+ syncConfig?: {
30
+ tombstoneTTL?: number;
31
+ batchSize?: number;
32
+ };
33
+ }
34
+ /**
35
+ * Manages multiple LevelDB stores for multi-tenant sync.
36
+ */
37
+ export declare class StoreManager {
38
+ private readonly config;
39
+ private readonly stores;
40
+ private cleanupTimer;
41
+ private shutdownPromise;
42
+ constructor(config?: Partial<StoreManagerConfig>);
43
+ /**
44
+ * Start the store manager (begins cleanup interval).
45
+ */
46
+ start(): void;
47
+ /**
48
+ * Get or open a store for a database. Increments refCount.
49
+ */
50
+ acquire(databaseId: string): Promise<StoreEntry>;
51
+ /**
52
+ * Release a store reference. Decrements refCount.
53
+ */
54
+ release(databaseId: string): void;
55
+ /**
56
+ * Check if a store is currently open.
57
+ */
58
+ isOpen(databaseId: string): boolean;
59
+ /**
60
+ * Get an open store without acquiring (for read-only checks).
61
+ */
62
+ get(databaseId: string): StoreEntry | undefined;
63
+ /**
64
+ * Get count of open stores.
65
+ */
66
+ get openCount(): number;
67
+ /**
68
+ * Shutdown all stores.
69
+ */
70
+ shutdown(): Promise<void>;
71
+ private openStore;
72
+ /**
73
+ * Cleanup idle stores with refCount=0 past timeout.
74
+ */
75
+ private cleanup;
76
+ /**
77
+ * Evict least recently used store (with refCount=0).
78
+ */
79
+ private evictLRU;
80
+ /**
81
+ * Close a specific store.
82
+ */
83
+ private closeStore;
84
+ }
85
+ //# sourceMappingURL=store-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-manager.d.ts","sourceRoot":"","sources":["../../../src/service/store-manager.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AAIvB,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,UAAU,CAAC,EAAE;QACX,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AASD;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IACxD,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,eAAe,CAA8B;gBAEzC,MAAM,GAAE,OAAO,CAAC,kBAAkB,CAAM;IAIpD;;OAEG;IACH,KAAK,IAAI,IAAI;IAMb;;OAEG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAsBtD;;OAEG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IASjC;;OAEG;IACH,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAInC;;OAEG;IACH,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI/C;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YA0BjB,SAAS;IA2BvB;;OAEG;YACW,OAAO;IAmBrB;;OAEG;YACW,QAAQ;IAoBtB;;OAEG;YACW,UAAU;CAYzB"}
@@ -0,0 +1,194 @@
1
+ /**
2
+ * StoreManager - Multi-tenant LevelDB store management
3
+ *
4
+ * Manages lazy loading and cleanup of per-database LevelDB stores.
5
+ * Each database gets its own isolated store, opened on-demand and
6
+ * closed after idle timeout.
7
+ */
8
+ import { join } from 'node:path';
9
+ import { StoreEventEmitter } from '@quereus/store';
10
+ import { LevelDBStore } from '@quereus/plugin-leveldb';
11
+ import { createSyncModule, } from '@quereus/sync';
12
+ import { serviceLog } from '../common/logger.js';
13
+ import { getDatabaseStoragePath, parseDatabaseId } from './database-ids.js';
14
+ const DEFAULT_CONFIG = {
15
+ dataDir: './data',
16
+ maxOpenStores: 100,
17
+ idleTimeoutMs: 5 * 60 * 1000, // 5 minutes
18
+ cleanupIntervalMs: 30 * 1000, // 30 seconds
19
+ };
20
+ /**
21
+ * Manages multiple LevelDB stores for multi-tenant sync.
22
+ */
23
+ export class StoreManager {
24
+ config;
25
+ stores = new Map();
26
+ cleanupTimer = null;
27
+ shutdownPromise = null;
28
+ constructor(config = {}) {
29
+ this.config = { ...DEFAULT_CONFIG, ...config };
30
+ }
31
+ /**
32
+ * Start the store manager (begins cleanup interval).
33
+ */
34
+ start() {
35
+ if (this.cleanupTimer)
36
+ return;
37
+ this.cleanupTimer = setInterval(() => this.cleanup(), this.config.cleanupIntervalMs);
38
+ serviceLog('StoreManager started with dataDir: %s', this.config.dataDir);
39
+ }
40
+ /**
41
+ * Get or open a store for a database. Increments refCount.
42
+ */
43
+ async acquire(databaseId) {
44
+ // Check if already open
45
+ let entry = this.stores.get(databaseId);
46
+ if (entry) {
47
+ entry.refCount++;
48
+ entry.lastAccess = Date.now();
49
+ serviceLog('Store acquired (cached): %s, refCount=%d', databaseId, entry.refCount);
50
+ return entry;
51
+ }
52
+ // Check if we need to evict before opening new
53
+ if (this.stores.size >= this.config.maxOpenStores) {
54
+ await this.evictLRU();
55
+ }
56
+ // Open new store
57
+ entry = await this.openStore(databaseId);
58
+ this.stores.set(databaseId, entry);
59
+ serviceLog('Store acquired (opened): %s', databaseId);
60
+ return entry;
61
+ }
62
+ /**
63
+ * Release a store reference. Decrements refCount.
64
+ */
65
+ release(databaseId) {
66
+ const entry = this.stores.get(databaseId);
67
+ if (!entry)
68
+ return;
69
+ entry.refCount = Math.max(0, entry.refCount - 1);
70
+ entry.lastAccess = Date.now();
71
+ serviceLog('Store released: %s, refCount=%d', databaseId, entry.refCount);
72
+ }
73
+ /**
74
+ * Check if a store is currently open.
75
+ */
76
+ isOpen(databaseId) {
77
+ return this.stores.has(databaseId);
78
+ }
79
+ /**
80
+ * Get an open store without acquiring (for read-only checks).
81
+ */
82
+ get(databaseId) {
83
+ return this.stores.get(databaseId);
84
+ }
85
+ /**
86
+ * Get count of open stores.
87
+ */
88
+ get openCount() {
89
+ return this.stores.size;
90
+ }
91
+ /**
92
+ * Shutdown all stores.
93
+ */
94
+ async shutdown() {
95
+ if (this.shutdownPromise)
96
+ return this.shutdownPromise;
97
+ this.shutdownPromise = (async () => {
98
+ if (this.cleanupTimer) {
99
+ clearInterval(this.cleanupTimer);
100
+ this.cleanupTimer = null;
101
+ }
102
+ const closePromises = Array.from(this.stores.entries()).map(async ([id, entry]) => {
103
+ try {
104
+ await entry.store.close();
105
+ serviceLog('Store closed: %s', id);
106
+ }
107
+ catch (err) {
108
+ serviceLog('Error closing store %s: %O', id, err);
109
+ }
110
+ });
111
+ await Promise.all(closePromises);
112
+ this.stores.clear();
113
+ serviceLog('StoreManager shutdown complete');
114
+ })();
115
+ return this.shutdownPromise;
116
+ }
117
+ async openStore(databaseId) {
118
+ // Validate database ID format
119
+ parseDatabaseId(databaseId);
120
+ const storagePath = getDatabaseStoragePath(databaseId);
121
+ const fullPath = join(this.config.dataDir, storagePath);
122
+ serviceLog('Opening store at: %s', fullPath);
123
+ const store = await LevelDBStore.open({
124
+ path: fullPath,
125
+ createIfMissing: true,
126
+ });
127
+ const storeEvents = new StoreEventEmitter();
128
+ const { syncManager } = await createSyncModule(store, storeEvents, this.config.syncConfig);
129
+ return {
130
+ databaseId,
131
+ store,
132
+ syncManager,
133
+ storeEvents,
134
+ refCount: 1,
135
+ lastAccess: Date.now(),
136
+ };
137
+ }
138
+ /**
139
+ * Cleanup idle stores with refCount=0 past timeout.
140
+ */
141
+ async cleanup() {
142
+ const now = Date.now();
143
+ const toClose = [];
144
+ for (const [id, entry] of this.stores) {
145
+ if (entry.refCount === 0 && now - entry.lastAccess > this.config.idleTimeoutMs) {
146
+ toClose.push(id);
147
+ }
148
+ }
149
+ for (const id of toClose) {
150
+ await this.closeStore(id);
151
+ }
152
+ if (toClose.length > 0) {
153
+ serviceLog('Cleanup: closed %d idle stores', toClose.length);
154
+ }
155
+ }
156
+ /**
157
+ * Evict least recently used store (with refCount=0).
158
+ */
159
+ async evictLRU() {
160
+ let oldest = null;
161
+ for (const [id, entry] of this.stores) {
162
+ // Only evict stores with no active references
163
+ if (entry.refCount === 0) {
164
+ if (!oldest || entry.lastAccess < oldest.lastAccess) {
165
+ oldest = { id, lastAccess: entry.lastAccess };
166
+ }
167
+ }
168
+ }
169
+ if (oldest) {
170
+ await this.closeStore(oldest.id);
171
+ serviceLog('Evicted LRU store: %s', oldest.id);
172
+ }
173
+ else {
174
+ serviceLog('Warning: Cannot evict, all stores have active references');
175
+ }
176
+ }
177
+ /**
178
+ * Close a specific store.
179
+ */
180
+ async closeStore(databaseId) {
181
+ const entry = this.stores.get(databaseId);
182
+ if (!entry)
183
+ return;
184
+ try {
185
+ await entry.store.close();
186
+ this.stores.delete(databaseId);
187
+ serviceLog('Store closed: %s', databaseId);
188
+ }
189
+ catch (err) {
190
+ serviceLog('Error closing store %s: %O', databaseId, err);
191
+ }
192
+ }
193
+ }
194
+ //# sourceMappingURL=store-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-manager.js","sourceRoot":"","sources":["../../../src/service/store-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Service layer types - hooks, sessions, and operations.
3
+ */
4
+ import type { WebSocket } from 'ws';
5
+ import type { FastifyRequest } from 'fastify';
6
+ import type { SiteId, HLC, ChangeSet, ApplyResult } from '@quereus/sync';
7
+ /**
8
+ * Authenticated client identity.
9
+ * Extend this interface for custom auth data.
10
+ */
11
+ export interface ClientIdentity {
12
+ /** Client's site ID for sync */
13
+ siteId: SiteId;
14
+ /** Optional user ID from authentication */
15
+ userId?: string;
16
+ /** Optional additional metadata */
17
+ metadata?: Record<string, unknown>;
18
+ }
19
+ /**
20
+ * Active WebSocket client session.
21
+ */
22
+ export interface ClientSession {
23
+ /** Unique connection identifier */
24
+ connectionId: string;
25
+ /** Database ID for multi-tenant routing (e.g., 'a1-s42') */
26
+ databaseId: string;
27
+ /** Client's replica site ID */
28
+ siteId: SiteId;
29
+ /** Authenticated identity */
30
+ identity: ClientIdentity;
31
+ /** Last HLC client synced to */
32
+ lastSyncHLC: HLC | undefined;
33
+ /** Connection timestamp */
34
+ connectedAt: number;
35
+ /** The WebSocket connection */
36
+ socket: WebSocket;
37
+ }
38
+ /**
39
+ * Context provided to authentication hook.
40
+ */
41
+ export interface AuthContext {
42
+ /** Database ID for multi-tenant routing (e.g., 'a1-s42') */
43
+ databaseId: string;
44
+ /** Authorization header value */
45
+ token?: string;
46
+ /** Client-provided site ID */
47
+ siteId?: SiteId;
48
+ /** Raw site ID string from header */
49
+ siteIdRaw?: string;
50
+ /** Original HTTP request (if available) */
51
+ request?: FastifyRequest;
52
+ /** WebSocket connection (if WebSocket auth) */
53
+ socket?: WebSocket;
54
+ }
55
+ /**
56
+ * Sync operations that can be authorized.
57
+ */
58
+ export type SyncOperation = {
59
+ type: 'get_changes';
60
+ sinceHLC?: HLC;
61
+ } | {
62
+ type: 'apply_changes';
63
+ changeCount: number;
64
+ } | {
65
+ type: 'get_snapshot';
66
+ } | {
67
+ type: 'resume_snapshot';
68
+ };
69
+ /**
70
+ * A change that was rejected during validation.
71
+ */
72
+ export interface RejectedChange {
73
+ /** The rejected change */
74
+ change: ChangeSet;
75
+ /** Reason for rejection */
76
+ reason: string;
77
+ /** Error code for programmatic handling */
78
+ code?: string;
79
+ }
80
+ /**
81
+ * Result of change validation.
82
+ */
83
+ export interface ValidationResult {
84
+ /** Changes approved for application */
85
+ approved: ChangeSet[];
86
+ /** Changes that were rejected */
87
+ rejected: RejectedChange[];
88
+ }
89
+ /**
90
+ * Coordinator service hooks for customization.
91
+ * All hooks are optional; defaults allow all operations.
92
+ */
93
+ export interface CoordinatorHooks {
94
+ /**
95
+ * Authenticate an incoming request/connection.
96
+ * Called before any sync operation.
97
+ *
98
+ * @param context - Auth context with token and request info
99
+ * @returns Client identity on success
100
+ * @throws Error to reject authentication
101
+ */
102
+ onAuthenticate?(context: AuthContext): Promise<ClientIdentity>;
103
+ /**
104
+ * Authorize a specific operation for a client.
105
+ * Called after authentication, before executing the operation.
106
+ *
107
+ * @param client - Authenticated client identity
108
+ * @param operation - The operation being requested
109
+ * @returns true to allow, false to deny
110
+ */
111
+ onAuthorize?(client: ClientIdentity, operation: SyncOperation): Promise<boolean>;
112
+ /**
113
+ * Validate changes before applying them.
114
+ * Can modify, filter, or reject changes.
115
+ *
116
+ * @param client - Authenticated client identity
117
+ * @param changes - Changes to validate
118
+ * @returns Approved and rejected changes
119
+ */
120
+ onBeforeApplyChanges?(client: ClientIdentity, changes: ChangeSet[]): Promise<ValidationResult>;
121
+ /**
122
+ * Called after changes are successfully applied.
123
+ * Useful for logging, metrics, or triggering side effects.
124
+ *
125
+ * @param client - Authenticated client identity
126
+ * @param changes - Changes that were applied
127
+ * @param result - Result of the apply operation
128
+ */
129
+ onAfterApplyChanges?(client: ClientIdentity, changes: ChangeSet[], result: ApplyResult): void;
130
+ /**
131
+ * Called when a WebSocket client connects.
132
+ * Return false to reject the connection.
133
+ *
134
+ * @param client - Authenticated client identity
135
+ * @param socket - The WebSocket connection
136
+ * @returns true to accept, false to reject
137
+ */
138
+ onClientConnect?(client: ClientIdentity, socket: WebSocket): Promise<boolean>;
139
+ /**
140
+ * Called when a WebSocket client disconnects.
141
+ *
142
+ * @param client - Client identity of disconnected client
143
+ */
144
+ onClientDisconnect?(client: ClientIdentity): void;
145
+ }
146
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/service/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Service layer types - hooks, sessions, and operations.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/service/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quereus/sync-coordinator",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "type": "module",
5
5
  "description": "Standalone coordinator backend for Quereus Sync - production-ready sync server",
6
6
  "keywords": [
@@ -45,9 +45,10 @@
45
45
  "dependencies": {
46
46
  "@fastify/cors": "^10.0.2",
47
47
  "@fastify/websocket": "^11.0.2",
48
- "@quereus/plugin-store": "*",
49
- "@quereus/plugin-sync": "*",
48
+ "@quereus/plugin-leveldb": "workspace:^",
50
49
  "@quereus/quereus": "*",
50
+ "@quereus/store": "*",
51
+ "@quereus/sync": "*",
51
52
  "commander": "^14.0.0",
52
53
  "debug": "^4.4.0",
53
54
  "fastify": "^5.2.1"