@korajs/sync 0.3.1 → 0.4.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/dist/index.cjs +1506 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +515 -4
- package/dist/index.d.ts +515 -4
- package/dist/index.js +1471 -49
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/transport-B-BIguq9.d.cts +385 -0
- package/dist/transport-B-BIguq9.d.ts +385 -0
- package/package.json +3 -3
- package/dist/transport-B5EFsr5F.d.cts +0 -215
- package/dist/transport-B5EFsr5F.d.ts +0 -215
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
|
-
import { S as
|
|
2
|
-
export { A as AcknowledgmentMessage, E as ErrorMessage, H as HandshakeMessage,
|
|
3
|
-
import { VersionVector,
|
|
1
|
+
import { S as SyncScopeMap, a as SyncMessage, b as SerializedOperation, W as WireFormat, c as SyncTransport, T as TransportOptions, d as TransportMessageHandler, e as TransportCloseHandler, f as TransportErrorHandler, g as SyncEncryptionConfig, V as VersionedKey, Q as QueueStorage, h as SyncState, i as SyncStatusInfo, j as SyncConfig } from './transport-B-BIguq9.js';
|
|
2
|
+
export { A as AcknowledgmentMessage, k as AwarenessStateWire, l as AwarenessUpdateMessage, E as EncryptedPayload, m as ErrorMessage, H as HandshakeMessage, n as HandshakeResponseMessage, I as InvalidScopeError, O as OperationBatchMessage, o as SYNC_STATES, p as SYNC_STATUSES, q as ScopeViolationError, r as SyncEncryptionAlgorithm, s as SyncScopeContext, t as SyncStatus, u as isAcknowledgmentMessage, v as isAwarenessUpdateMessage, w as isErrorMessage, x as isHandshakeMessage, y as isHandshakeResponseMessage, z as isOperationBatchMessage, B as isSyncMessage } from './transport-B-BIguq9.js';
|
|
3
|
+
import { Operation, VersionVector, TimeSource, SyncError, KoraEventEmitter, ConnectionQuality } from '@korajs/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check whether an operation matches the given scope map.
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* - No scope map configured: operation is always in scope.
|
|
10
|
+
* - Collection not present in scope map: operation is out of scope.
|
|
11
|
+
* - Empty scope for a collection `{}`: no field restrictions, operation is in scope.
|
|
12
|
+
* - Scope has field/value pairs: all must match in the operation's data snapshot.
|
|
13
|
+
*
|
|
14
|
+
* For updates, the snapshot is built by merging `previousData` and `data` (data wins),
|
|
15
|
+
* which represents the record's state after the operation is applied.
|
|
16
|
+
*
|
|
17
|
+
* @param op - The operation to check
|
|
18
|
+
* @param scopeMap - Per-collection scope filters, or undefined for no filtering
|
|
19
|
+
* @returns true if the operation is within scope
|
|
20
|
+
*/
|
|
21
|
+
declare function operationMatchesScope(op: Operation, scopeMap: SyncScopeMap | undefined): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Filter operations to only those matching the given scope map.
|
|
24
|
+
*
|
|
25
|
+
* @param operations - Array of operations to filter
|
|
26
|
+
* @param scopeMap - Per-collection scope filters
|
|
27
|
+
* @returns Operations that match the scope
|
|
28
|
+
*/
|
|
29
|
+
declare function filterOperationsByScope(operations: Operation[], scopeMap: SyncScopeMap | undefined): Operation[];
|
|
4
30
|
|
|
5
31
|
/**
|
|
6
32
|
* Result of applying a remote operation to the store.
|
|
@@ -229,6 +255,358 @@ declare class ChaosTransport implements SyncTransport {
|
|
|
229
255
|
private flushReorderBuffer;
|
|
230
256
|
}
|
|
231
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Configuration for the SyncMetricsCollector.
|
|
260
|
+
*/
|
|
261
|
+
interface MetricsCollectorConfig {
|
|
262
|
+
/** Number of RTT samples to keep for percentile calculations. Defaults to 100. */
|
|
263
|
+
rttWindowSize?: number;
|
|
264
|
+
/** Number of bandwidth samples to keep. Defaults to 20. */
|
|
265
|
+
bandwidthWindowSize?: number;
|
|
266
|
+
/** Interval in ms between periodic diagnostics emissions. Defaults to 5000. */
|
|
267
|
+
diagnosticsInterval?: number;
|
|
268
|
+
/** Injectable time source for deterministic testing. */
|
|
269
|
+
timeSource?: TimeSource;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Thrown when encryption of operation data fails.
|
|
274
|
+
*/
|
|
275
|
+
declare class EncryptionError extends SyncError {
|
|
276
|
+
constructor(message: string, context?: Record<string, unknown>);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Thrown when decryption of operation data fails.
|
|
280
|
+
* This typically indicates a wrong key, tampered ciphertext, or corrupted data.
|
|
281
|
+
*/
|
|
282
|
+
declare class DecryptionError extends SyncError {
|
|
283
|
+
constructor(message: string, context?: Record<string, unknown>);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Encrypts and decrypts operation `data` and `previousData` fields for
|
|
287
|
+
* end-to-end encryption in the sync layer.
|
|
288
|
+
*
|
|
289
|
+
* **Design principles:**
|
|
290
|
+
* - Only `data` and `previousData` are encrypted. Metadata (id, nodeId,
|
|
291
|
+
* collection, timestamps, causalDeps, etc.) stays in cleartext so the
|
|
292
|
+
* server can route, deduplicate, and order operations.
|
|
293
|
+
* - Each field encryption uses a unique random IV (12 bytes for AES-GCM),
|
|
294
|
+
* ensuring that encrypting the same data twice produces different ciphertext.
|
|
295
|
+
* - Key rotation is supported via versioned keys. The key version is embedded
|
|
296
|
+
* in the {@link EncryptedPayload} so the decryptor can select the correct key.
|
|
297
|
+
* - Unencrypted fields pass through during decryption (backward compatibility).
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* const encryptor = await SyncEncryptor.create({
|
|
302
|
+
* enabled: true,
|
|
303
|
+
* key: 'user-passphrase'
|
|
304
|
+
* })
|
|
305
|
+
*
|
|
306
|
+
* // Encrypt before sending
|
|
307
|
+
* const encrypted = await encryptor.encryptOperation(operation)
|
|
308
|
+
*
|
|
309
|
+
* // Decrypt after receiving
|
|
310
|
+
* const decrypted = await encryptor.decryptOperation(encrypted)
|
|
311
|
+
* ```
|
|
312
|
+
*/
|
|
313
|
+
declare class SyncEncryptor {
|
|
314
|
+
/**
|
|
315
|
+
* Map of key version -> VersionedKey. The current (latest) version is used
|
|
316
|
+
* for encryption. All versions are available for decryption (key rotation).
|
|
317
|
+
*/
|
|
318
|
+
private readonly keys;
|
|
319
|
+
/** The current key version used for encryption. */
|
|
320
|
+
private currentVersion;
|
|
321
|
+
private constructor();
|
|
322
|
+
/**
|
|
323
|
+
* Creates a SyncEncryptor from a {@link SyncEncryptionConfig}.
|
|
324
|
+
*
|
|
325
|
+
* Derives the encryption key from the passphrase using PBKDF2. The key
|
|
326
|
+
* derivation is async because it uses the Web Crypto API.
|
|
327
|
+
*
|
|
328
|
+
* @param config - Encryption configuration with passphrase
|
|
329
|
+
* @param salt - Optional salt for deterministic key derivation (mainly for testing).
|
|
330
|
+
* If omitted, a random salt is generated.
|
|
331
|
+
* @returns A configured SyncEncryptor instance
|
|
332
|
+
* @throws {EncryptionError} If configuration is invalid
|
|
333
|
+
* @throws {KeyDerivationError} If key derivation fails
|
|
334
|
+
*/
|
|
335
|
+
static create(config: SyncEncryptionConfig, salt?: Uint8Array): Promise<SyncEncryptor>;
|
|
336
|
+
/**
|
|
337
|
+
* Creates a SyncEncryptor from pre-derived versioned keys.
|
|
338
|
+
*
|
|
339
|
+
* Use this when you need to support multiple key versions for key rotation,
|
|
340
|
+
* or when you have already derived the keys externally.
|
|
341
|
+
*
|
|
342
|
+
* @param versionedKeys - Array of versioned keys. The highest version is used for encryption.
|
|
343
|
+
* @returns A configured SyncEncryptor instance
|
|
344
|
+
* @throws {EncryptionError} If no keys are provided
|
|
345
|
+
*/
|
|
346
|
+
static fromKeys(versionedKeys: VersionedKey[]): SyncEncryptor;
|
|
347
|
+
/**
|
|
348
|
+
* Add a new key version for key rotation.
|
|
349
|
+
*
|
|
350
|
+
* After adding, the new key becomes the current version used for encryption
|
|
351
|
+
* if its version number is higher than the current version. Previously-versioned
|
|
352
|
+
* keys remain available for decrypting older operations.
|
|
353
|
+
*
|
|
354
|
+
* @param key - The new versioned key to add
|
|
355
|
+
* @throws {EncryptionError} If the key version already exists
|
|
356
|
+
*/
|
|
357
|
+
addKey(key: VersionedKey): void;
|
|
358
|
+
/**
|
|
359
|
+
* Get the current encryption key version number.
|
|
360
|
+
*/
|
|
361
|
+
getCurrentKeyVersion(): number;
|
|
362
|
+
/**
|
|
363
|
+
* Encrypt an operation's `data` and `previousData` fields.
|
|
364
|
+
*
|
|
365
|
+
* Returns a new Operation with encrypted field values. The original
|
|
366
|
+
* operation is not mutated (operations are immutable).
|
|
367
|
+
*
|
|
368
|
+
* Fields that are null (e.g., delete operations) remain null.
|
|
369
|
+
*
|
|
370
|
+
* @param operation - The operation to encrypt
|
|
371
|
+
* @returns A new operation with encrypted data fields
|
|
372
|
+
* @throws {EncryptionError} If encryption fails
|
|
373
|
+
*/
|
|
374
|
+
encryptOperation(operation: Operation): Promise<Operation>;
|
|
375
|
+
/**
|
|
376
|
+
* Decrypt an operation's `data` and `previousData` fields.
|
|
377
|
+
*
|
|
378
|
+
* Returns a new Operation with the original field values restored.
|
|
379
|
+
* The original operation is not mutated.
|
|
380
|
+
*
|
|
381
|
+
* If a field is null or not encrypted (no marker), it passes through unchanged.
|
|
382
|
+
* This enables mixed plaintext/encrypted operations during migration.
|
|
383
|
+
*
|
|
384
|
+
* @param operation - The operation to decrypt
|
|
385
|
+
* @returns A new operation with decrypted data fields
|
|
386
|
+
* @throws {DecryptionError} If decryption fails (wrong key, tampered data, unsupported version)
|
|
387
|
+
*/
|
|
388
|
+
decryptOperation(operation: Operation): Promise<Operation>;
|
|
389
|
+
/**
|
|
390
|
+
* Encrypt a serialized operation's `data` and `previousData` fields.
|
|
391
|
+
*
|
|
392
|
+
* Same as {@link encryptOperation} but works with the wire-format
|
|
393
|
+
* {@link SerializedOperation} type used by the serializer.
|
|
394
|
+
*
|
|
395
|
+
* @param serialized - The serialized operation to encrypt
|
|
396
|
+
* @returns A new serialized operation with encrypted data fields
|
|
397
|
+
* @throws {EncryptionError} If encryption fails
|
|
398
|
+
*/
|
|
399
|
+
encryptSerializedOperation(serialized: SerializedOperation): Promise<SerializedOperation>;
|
|
400
|
+
/**
|
|
401
|
+
* Decrypt a serialized operation's `data` and `previousData` fields.
|
|
402
|
+
*
|
|
403
|
+
* Same as {@link decryptOperation} but works with the wire-format
|
|
404
|
+
* {@link SerializedOperation} type used by the serializer.
|
|
405
|
+
*
|
|
406
|
+
* @param serialized - The serialized operation to decrypt
|
|
407
|
+
* @returns A new serialized operation with decrypted data fields
|
|
408
|
+
* @throws {DecryptionError} If decryption fails
|
|
409
|
+
*/
|
|
410
|
+
decryptSerializedOperation(serialized: SerializedOperation): Promise<SerializedOperation>;
|
|
411
|
+
/**
|
|
412
|
+
* Encrypt a batch of operations in parallel.
|
|
413
|
+
*
|
|
414
|
+
* @param operations - Operations to encrypt
|
|
415
|
+
* @returns New operations with encrypted data fields
|
|
416
|
+
*/
|
|
417
|
+
encryptBatch(operations: Operation[]): Promise<Operation[]>;
|
|
418
|
+
/**
|
|
419
|
+
* Decrypt a batch of operations in parallel.
|
|
420
|
+
*
|
|
421
|
+
* @param operations - Operations to decrypt
|
|
422
|
+
* @returns New operations with decrypted data fields
|
|
423
|
+
*/
|
|
424
|
+
decryptBatch(operations: Operation[]): Promise<Operation[]>;
|
|
425
|
+
/**
|
|
426
|
+
* Check if a field value contains an encrypted payload.
|
|
427
|
+
*
|
|
428
|
+
* @param field - An operation's `data` or `previousData` field
|
|
429
|
+
* @returns true if the field contains an encrypted payload
|
|
430
|
+
*/
|
|
431
|
+
static isEncryptedPayload(field: Record<string, unknown> | null): boolean;
|
|
432
|
+
private encryptField;
|
|
433
|
+
private decryptField;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Check if a field value contains an encrypted payload.
|
|
437
|
+
*
|
|
438
|
+
* @param field - An operation's `data` or `previousData` field
|
|
439
|
+
* @returns true if the field contains an encrypted payload
|
|
440
|
+
*/
|
|
441
|
+
declare function isEncryptedPayload(field: Record<string, unknown> | null): boolean;
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Cursor position within a richtext field.
|
|
445
|
+
* Uses Yjs-compatible anchor/head positions for editor interop.
|
|
446
|
+
*/
|
|
447
|
+
interface AwarenessCursor {
|
|
448
|
+
/** Collection containing the record being edited */
|
|
449
|
+
collection: string;
|
|
450
|
+
/** ID of the record being edited */
|
|
451
|
+
recordId: string;
|
|
452
|
+
/** Field name of the richtext field */
|
|
453
|
+
field: string;
|
|
454
|
+
/** Cursor anchor position in Y.Text (start of selection) */
|
|
455
|
+
anchor: number;
|
|
456
|
+
/** Cursor head position in Y.Text (end of selection, same as anchor if no selection) */
|
|
457
|
+
head: number;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* User identity information for presence display.
|
|
461
|
+
*/
|
|
462
|
+
interface AwarenessUser {
|
|
463
|
+
/** Display name of the user */
|
|
464
|
+
name: string;
|
|
465
|
+
/** Hex color for cursor/selection rendering (e.g. '#ff0000') */
|
|
466
|
+
color: string;
|
|
467
|
+
/** Optional avatar URL */
|
|
468
|
+
avatar?: string;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Per-client awareness state. Ephemeral -- not persisted, only shared with connected peers.
|
|
472
|
+
* Compatible with Yjs awareness protocol format for interop with existing editors.
|
|
473
|
+
*/
|
|
474
|
+
interface AwarenessState {
|
|
475
|
+
/** User identity information */
|
|
476
|
+
user: AwarenessUser;
|
|
477
|
+
/** Current cursor position, if any */
|
|
478
|
+
cursor?: AwarenessCursor;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Internal awareness message format used between AwarenessManager and transport layer.
|
|
482
|
+
*/
|
|
483
|
+
interface AwarenessMessage {
|
|
484
|
+
type: 'awareness';
|
|
485
|
+
/** Client ID of the sender */
|
|
486
|
+
clientId: number;
|
|
487
|
+
/** All known awareness states. null value means removal. */
|
|
488
|
+
states: Record<number, AwarenessState | null>;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Describes a change in awareness states.
|
|
492
|
+
* Emitted when remote clients update or remove their presence.
|
|
493
|
+
*/
|
|
494
|
+
interface AwarenessChange {
|
|
495
|
+
/** Client IDs whose states were added */
|
|
496
|
+
added: number[];
|
|
497
|
+
/** Client IDs whose states were updated */
|
|
498
|
+
updated: number[];
|
|
499
|
+
/** Client IDs whose states were removed */
|
|
500
|
+
removed: number[];
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Cursor information for rendering in editors.
|
|
504
|
+
* This is the developer-facing type -- simplified from internal awareness state.
|
|
505
|
+
* Editor-agnostic: provides data that can be rendered by TipTap, ProseMirror, Quill, etc.
|
|
506
|
+
*/
|
|
507
|
+
interface CursorInfo {
|
|
508
|
+
/** Unique client ID */
|
|
509
|
+
clientId: number;
|
|
510
|
+
/** User display name */
|
|
511
|
+
userName: string;
|
|
512
|
+
/** Hex color for cursor rendering */
|
|
513
|
+
color: string;
|
|
514
|
+
/** Cursor anchor position (start of selection) */
|
|
515
|
+
anchor: number;
|
|
516
|
+
/** Cursor head position (end of selection) */
|
|
517
|
+
head: number;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Callback for awareness change events.
|
|
522
|
+
*/
|
|
523
|
+
type AwarenessChangeListener = (change: AwarenessChange) => void;
|
|
524
|
+
/**
|
|
525
|
+
* Manages collaborative awareness state (cursor positions, user presence).
|
|
526
|
+
*
|
|
527
|
+
* Awareness is ephemeral -- states are never persisted, only shared with
|
|
528
|
+
* currently connected peers via the sync transport. This implements a
|
|
529
|
+
* lightweight protocol compatible with Yjs awareness semantics.
|
|
530
|
+
*
|
|
531
|
+
* Each AwarenessManager has a unique client ID and tracks both its own
|
|
532
|
+
* local state and all remote clients' states.
|
|
533
|
+
*/
|
|
534
|
+
declare class AwarenessManager {
|
|
535
|
+
/** Unique client ID for this instance */
|
|
536
|
+
readonly clientId: number;
|
|
537
|
+
private localState;
|
|
538
|
+
private readonly remoteStates;
|
|
539
|
+
private readonly listeners;
|
|
540
|
+
private readonly emitter;
|
|
541
|
+
private readonly timeoutMs;
|
|
542
|
+
private readonly lastUpdated;
|
|
543
|
+
private cleanupTimer;
|
|
544
|
+
private sendHandler;
|
|
545
|
+
private destroyed;
|
|
546
|
+
constructor(options?: {
|
|
547
|
+
clientId?: number;
|
|
548
|
+
emitter?: KoraEventEmitter;
|
|
549
|
+
timeoutMs?: number;
|
|
550
|
+
});
|
|
551
|
+
/**
|
|
552
|
+
* Set the local user's awareness state and broadcast to peers.
|
|
553
|
+
*
|
|
554
|
+
* @param state - The awareness state to share. Pass null to clear presence.
|
|
555
|
+
*/
|
|
556
|
+
setLocalState(state: AwarenessState | null): void;
|
|
557
|
+
/**
|
|
558
|
+
* Get the local awareness state.
|
|
559
|
+
*/
|
|
560
|
+
getLocalState(): AwarenessState | null;
|
|
561
|
+
/**
|
|
562
|
+
* Get all known awareness states (local + remote).
|
|
563
|
+
* Returns a new Map on each call.
|
|
564
|
+
*/
|
|
565
|
+
getStates(): Map<number, AwarenessState>;
|
|
566
|
+
/**
|
|
567
|
+
* Handle an incoming awareness message from the transport.
|
|
568
|
+
* This processes remote state updates and notifies listeners.
|
|
569
|
+
*/
|
|
570
|
+
handleRemoteMessage(message: AwarenessMessage): void;
|
|
571
|
+
/**
|
|
572
|
+
* Remove a specific remote client's awareness state.
|
|
573
|
+
* Called when the server notifies that a client has disconnected.
|
|
574
|
+
*/
|
|
575
|
+
removeClient(clientId: number): void;
|
|
576
|
+
/**
|
|
577
|
+
* Register a handler for sending awareness messages through the transport.
|
|
578
|
+
* The sync engine calls this to wire outgoing awareness messages to the transport.
|
|
579
|
+
*/
|
|
580
|
+
onSend(handler: (message: AwarenessMessage) => void): void;
|
|
581
|
+
/**
|
|
582
|
+
* Register a listener for awareness state changes.
|
|
583
|
+
* Returns an unsubscribe function.
|
|
584
|
+
*/
|
|
585
|
+
on(_event: 'change', listener: AwarenessChangeListener): () => void;
|
|
586
|
+
/**
|
|
587
|
+
* Remove a specific change listener.
|
|
588
|
+
*/
|
|
589
|
+
off(_event: 'change', listener: AwarenessChangeListener): void;
|
|
590
|
+
/**
|
|
591
|
+
* Start the cleanup timer that removes stale remote states.
|
|
592
|
+
* Called when the sync engine transitions to streaming state.
|
|
593
|
+
*/
|
|
594
|
+
startCleanupTimer(): void;
|
|
595
|
+
/**
|
|
596
|
+
* Stop the cleanup timer.
|
|
597
|
+
*/
|
|
598
|
+
stopCleanupTimer(): void;
|
|
599
|
+
/**
|
|
600
|
+
* Clean up all resources. After calling destroy(), the manager
|
|
601
|
+
* will no longer send or receive awareness updates.
|
|
602
|
+
* Broadcasts removal of local state before shutting down.
|
|
603
|
+
*/
|
|
604
|
+
destroy(): void;
|
|
605
|
+
private cleanupStaleStates;
|
|
606
|
+
private notifyListeners;
|
|
607
|
+
private emitAwarenessEvent;
|
|
608
|
+
}
|
|
609
|
+
|
|
232
610
|
/**
|
|
233
611
|
* A batch of operations taken from the queue for sending.
|
|
234
612
|
*/
|
|
@@ -317,6 +695,32 @@ interface SyncEngineOptions {
|
|
|
317
695
|
emitter?: KoraEventEmitter;
|
|
318
696
|
/** Queue storage for persistent outbound queue. Defaults to in-memory. */
|
|
319
697
|
queueStorage?: QueueStorage;
|
|
698
|
+
/**
|
|
699
|
+
* Optional encryptor for end-to-end encryption.
|
|
700
|
+
* When provided, `data` and `previousData` fields of operations are encrypted
|
|
701
|
+
* before sending and decrypted after receiving. The server never sees plaintext data.
|
|
702
|
+
*/
|
|
703
|
+
encryptor?: SyncEncryptor;
|
|
704
|
+
/** Optional configuration for the metrics collector. */
|
|
705
|
+
metricsConfig?: MetricsCollectorConfig;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Diagnostics snapshot for debugging and support.
|
|
709
|
+
*/
|
|
710
|
+
interface SyncDiagnostics {
|
|
711
|
+
state: SyncState;
|
|
712
|
+
status: SyncStatusInfo;
|
|
713
|
+
nodeId: string;
|
|
714
|
+
url: string;
|
|
715
|
+
schemaVersion: number;
|
|
716
|
+
lastSyncedAt: number | null;
|
|
717
|
+
lastSuccessfulPush: number | null;
|
|
718
|
+
lastSuccessfulPull: number | null;
|
|
719
|
+
conflicts: number;
|
|
720
|
+
pendingOperations: number;
|
|
721
|
+
hasInFlightBatch: boolean;
|
|
722
|
+
reconnecting: boolean;
|
|
723
|
+
timestamp: number;
|
|
320
724
|
}
|
|
321
725
|
/**
|
|
322
726
|
* Core sync orchestrator. Manages the sync lifecycle:
|
|
@@ -334,13 +738,25 @@ declare class SyncEngine {
|
|
|
334
738
|
private readonly emitter;
|
|
335
739
|
private readonly outboundQueue;
|
|
336
740
|
private readonly batchSize;
|
|
741
|
+
private readonly encryptor;
|
|
742
|
+
private readonly awarenessManager;
|
|
743
|
+
private readonly metricsCollector;
|
|
337
744
|
private remoteVector;
|
|
338
745
|
private lastSyncedAt;
|
|
746
|
+
private lastSuccessfulPush;
|
|
747
|
+
private lastSuccessfulPull;
|
|
748
|
+
private conflictCount;
|
|
339
749
|
private currentBatch;
|
|
340
750
|
private reconnecting;
|
|
341
751
|
private deltaBatchesReceived;
|
|
342
752
|
private deltaReceiveComplete;
|
|
343
753
|
private deltaSendComplete;
|
|
754
|
+
/**
|
|
755
|
+
* The effective scope for this sync session.
|
|
756
|
+
* Starts as the configured scopeMap. After handshake, may be replaced
|
|
757
|
+
* with the server-accepted scope (server is authoritative).
|
|
758
|
+
*/
|
|
759
|
+
private activeScope;
|
|
344
760
|
constructor(options: SyncEngineOptions);
|
|
345
761
|
/**
|
|
346
762
|
* Start the sync engine: connect → handshake → delta exchange → streaming.
|
|
@@ -354,6 +770,9 @@ declare class SyncEngine {
|
|
|
354
770
|
/**
|
|
355
771
|
* Push a local operation to the outbound queue.
|
|
356
772
|
* If streaming, flushes immediately.
|
|
773
|
+
*
|
|
774
|
+
* Operations outside the configured sync scope are silently skipped
|
|
775
|
+
* because they should remain local-only and not be sent to the server.
|
|
357
776
|
*/
|
|
358
777
|
pushOperation(op: Operation): Promise<void>;
|
|
359
778
|
/**
|
|
@@ -367,6 +786,21 @@ declare class SyncEngine {
|
|
|
367
786
|
* Get the current developer-facing sync status.
|
|
368
787
|
*/
|
|
369
788
|
getStatus(): SyncStatusInfo;
|
|
789
|
+
/**
|
|
790
|
+
* Record a merge conflict. Called by the merge-aware sync store
|
|
791
|
+
* to increment the conflict counter for status reporting.
|
|
792
|
+
*/
|
|
793
|
+
recordConflict(): void;
|
|
794
|
+
/**
|
|
795
|
+
* Force an immediate reconnection attempt. If the engine is disconnected
|
|
796
|
+
* or in error state, restarts the sync. If already connected, no-op.
|
|
797
|
+
*/
|
|
798
|
+
retryNow(): Promise<void>;
|
|
799
|
+
/**
|
|
800
|
+
* Export a diagnostics snapshot for debugging and support tickets.
|
|
801
|
+
* Contains connection state, timing info, and queue metrics.
|
|
802
|
+
*/
|
|
803
|
+
exportDiagnostics(): SyncDiagnostics;
|
|
370
804
|
/**
|
|
371
805
|
* Get the current internal state (for testing).
|
|
372
806
|
*/
|
|
@@ -375,6 +809,29 @@ declare class SyncEngine {
|
|
|
375
809
|
* Get the outbound queue (for testing).
|
|
376
810
|
*/
|
|
377
811
|
getOutboundQueue(): OutboundQueue;
|
|
812
|
+
/**
|
|
813
|
+
* Update the sync scope map. Takes effect on the next connection attempt.
|
|
814
|
+
*
|
|
815
|
+
* When the scope changes (e.g., user switches organization), call this method
|
|
816
|
+
* then reconnect. The new scope will be sent in the handshake, and the server
|
|
817
|
+
* will send back data matching the new scope.
|
|
818
|
+
*
|
|
819
|
+
* Data that no longer matches the new scope is NOT deleted locally.
|
|
820
|
+
* It simply stops being synced.
|
|
821
|
+
*
|
|
822
|
+
* @param scopeMap - New per-collection scope filters, or undefined to remove scope
|
|
823
|
+
*/
|
|
824
|
+
updateScope(scopeMap: SyncScopeMap | undefined): void;
|
|
825
|
+
/**
|
|
826
|
+
* Get the currently active scope map. Returns undefined if no scope is configured.
|
|
827
|
+
*/
|
|
828
|
+
getActiveScope(): SyncScopeMap | undefined;
|
|
829
|
+
/**
|
|
830
|
+
* Get the awareness manager for collaborative presence.
|
|
831
|
+
* Use this to set local presence, observe remote collaborators,
|
|
832
|
+
* and track cursor positions in richtext fields.
|
|
833
|
+
*/
|
|
834
|
+
getAwarenessManager(): AwarenessManager;
|
|
378
835
|
private handleMessage;
|
|
379
836
|
private handleHandshakeResponse;
|
|
380
837
|
private sendDelta;
|
|
@@ -384,6 +841,7 @@ declare class SyncEngine {
|
|
|
384
841
|
private handleError;
|
|
385
842
|
private checkDeltaComplete;
|
|
386
843
|
private flushQueue;
|
|
844
|
+
private handleAwarenessUpdate;
|
|
387
845
|
private handleTransportClose;
|
|
388
846
|
private handleTransportError;
|
|
389
847
|
private transitionTo;
|
|
@@ -518,4 +976,57 @@ declare class ReconnectionManager {
|
|
|
518
976
|
private wait;
|
|
519
977
|
}
|
|
520
978
|
|
|
521
|
-
|
|
979
|
+
/**
|
|
980
|
+
* Thrown when key derivation fails.
|
|
981
|
+
*/
|
|
982
|
+
declare class KeyDerivationError extends SyncError {
|
|
983
|
+
constructor(message: string, context?: Record<string, unknown>);
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Generates a cryptographically random salt for PBKDF2 key derivation.
|
|
987
|
+
*
|
|
988
|
+
* @returns A random 32-byte Uint8Array
|
|
989
|
+
* @throws {KeyDerivationError} If crypto.getRandomValues is unavailable
|
|
990
|
+
*/
|
|
991
|
+
declare function generateSalt(): Uint8Array;
|
|
992
|
+
/**
|
|
993
|
+
* Derives an AES-256-GCM encryption key from a passphrase using PBKDF2.
|
|
994
|
+
*
|
|
995
|
+
* Uses PBKDF2 with SHA-256 and 600,000 iterations (OWASP-recommended minimum)
|
|
996
|
+
* to derive a 256-bit key. The derived key is deterministic: the same passphrase
|
|
997
|
+
* and salt always produce the same key.
|
|
998
|
+
*
|
|
999
|
+
* @param passphrase - The user's passphrase (must be non-empty)
|
|
1000
|
+
* @param salt - Optional salt bytes. If omitted, a random 32-byte salt is generated.
|
|
1001
|
+
* @returns The derived CryptoKey and the salt used
|
|
1002
|
+
* @throws {KeyDerivationError} If the passphrase is empty, crypto is unavailable, or derivation fails
|
|
1003
|
+
*
|
|
1004
|
+
* @example
|
|
1005
|
+
* ```typescript
|
|
1006
|
+
* // First time: derive key with a new salt
|
|
1007
|
+
* const { key, salt } = await deriveKey('my-secure-passphrase')
|
|
1008
|
+
*
|
|
1009
|
+
* // Later: re-derive the same key using the stored salt
|
|
1010
|
+
* const { key: sameKey } = await deriveKey('my-secure-passphrase', salt)
|
|
1011
|
+
* ```
|
|
1012
|
+
*/
|
|
1013
|
+
declare function deriveKey(passphrase: string, salt?: Uint8Array): Promise<{
|
|
1014
|
+
key: CryptoKey;
|
|
1015
|
+
salt: Uint8Array;
|
|
1016
|
+
}>;
|
|
1017
|
+
/**
|
|
1018
|
+
* Derives a versioned encryption key from a passphrase.
|
|
1019
|
+
*
|
|
1020
|
+
* Wraps {@link deriveKey} with a version number for key rotation support.
|
|
1021
|
+
* When the passphrase changes, create a new versioned key with a higher
|
|
1022
|
+
* version number.
|
|
1023
|
+
*
|
|
1024
|
+
* @param passphrase - The user's passphrase
|
|
1025
|
+
* @param version - Key version number (must be a positive integer)
|
|
1026
|
+
* @param salt - Optional salt bytes. If omitted, a random salt is generated.
|
|
1027
|
+
* @returns A {@link VersionedKey} containing the key, version, and salt
|
|
1028
|
+
* @throws {KeyDerivationError} If parameters are invalid or derivation fails
|
|
1029
|
+
*/
|
|
1030
|
+
declare function deriveVersionedKey(passphrase: string, version: number, salt?: Uint8Array): Promise<VersionedKey>;
|
|
1031
|
+
|
|
1032
|
+
export { type ApplyResult, type AwarenessChange, type AwarenessCursor, AwarenessManager, type AwarenessMessage, type AwarenessState, type AwarenessUser, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, type CursorInfo, DecryptionError, EncryptionError, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, KeyDerivationError, type MessageSerializer, NegotiatedMessageSerializer, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, SerializedOperation, SyncConfig, type SyncDiagnostics, SyncEncryptionConfig, SyncEncryptor, SyncEngine, type SyncEngineOptions, SyncMessage, SyncScopeMap, SyncState, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, VersionedKey, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, deriveKey, deriveVersionedKey, filterOperationsByScope, generateSalt, isEncryptedPayload, operationMatchesScope, versionVectorToWire, wireToVersionVector };
|