@korajs/sync 0.3.2 → 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.d.cts CHANGED
@@ -1,6 +1,32 @@
1
- import { S as SyncMessage, a as SerializedOperation, W as WireFormat, b as SyncTransport, T as TransportOptions, c as TransportMessageHandler, d as TransportCloseHandler, e as TransportErrorHandler, Q as QueueStorage, f as SyncConfig, g as SyncStatusInfo, h as SyncState } from './transport-B5EFsr5F.cjs';
2
- export { A as AcknowledgmentMessage, E as ErrorMessage, H as HandshakeMessage, i as HandshakeResponseMessage, O as OperationBatchMessage, j as SYNC_STATES, k as SYNC_STATUSES, l as SyncScopeContext, m as SyncStatus, n as isAcknowledgmentMessage, o as isErrorMessage, p as isHandshakeMessage, q as isHandshakeResponseMessage, r as isOperationBatchMessage, s as isSyncMessage } from './transport-B5EFsr5F.cjs';
3
- import { VersionVector, Operation, KoraEventEmitter, TimeSource, ConnectionQuality } from '@korajs/core';
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.cjs';
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.cjs';
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
- export { type ApplyResult, type ChaosConfig, ChaosTransport, ConnectionMonitor, type ConnectionMonitorConfig, HttpLongPollingTransport, type HttpLongPollingTransportOptions, JsonMessageSerializer, type MessageSerializer, NegotiatedMessageSerializer, type OutboundBatch, OutboundQueue, ProtobufMessageSerializer, QueueStorage, type ReconnectionConfig, ReconnectionManager, SerializedOperation, SyncConfig, SyncEngine, type SyncEngineOptions, SyncMessage, SyncState, SyncStatusInfo, type SyncStore, SyncTransport, TransportCloseHandler, TransportErrorHandler, TransportMessageHandler, TransportOptions, type WebSocketConstructor, type WebSocketLike, WebSocketTransport, type WebSocketTransportOptions, WireFormat, versionVectorToWire, wireToVersionVector };
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 };