state-sync-log 0.9.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 (41) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/LICENSE +21 -0
  3. package/README.md +277 -0
  4. package/dist/state-sync-log.esm.js +1339 -0
  5. package/dist/state-sync-log.esm.mjs +1339 -0
  6. package/dist/state-sync-log.umd.js +1343 -0
  7. package/dist/types/ClientId.d.ts +1 -0
  8. package/dist/types/SortedTxEntry.d.ts +44 -0
  9. package/dist/types/StateCalculator.d.ts +141 -0
  10. package/dist/types/TxRecord.d.ts +14 -0
  11. package/dist/types/checkpointUtils.d.ts +15 -0
  12. package/dist/types/checkpoints.d.ts +62 -0
  13. package/dist/types/clientState.d.ts +19 -0
  14. package/dist/types/createStateSyncLog.d.ts +97 -0
  15. package/dist/types/draft.d.ts +69 -0
  16. package/dist/types/error.d.ts +4 -0
  17. package/dist/types/index.d.ts +4 -0
  18. package/dist/types/json.d.ts +23 -0
  19. package/dist/types/operations.d.ts +64 -0
  20. package/dist/types/reconcile.d.ts +7 -0
  21. package/dist/types/txLog.d.ts +32 -0
  22. package/dist/types/txTimestamp.d.ts +27 -0
  23. package/dist/types/utils.d.ts +23 -0
  24. package/package.json +94 -0
  25. package/src/ClientId.ts +1 -0
  26. package/src/SortedTxEntry.ts +83 -0
  27. package/src/StateCalculator.ts +407 -0
  28. package/src/TxRecord.ts +15 -0
  29. package/src/checkpointUtils.ts +44 -0
  30. package/src/checkpoints.ts +208 -0
  31. package/src/clientState.ts +37 -0
  32. package/src/createStateSyncLog.ts +330 -0
  33. package/src/draft.ts +288 -0
  34. package/src/error.ts +12 -0
  35. package/src/index.ts +8 -0
  36. package/src/json.ts +25 -0
  37. package/src/operations.ts +157 -0
  38. package/src/reconcile.ts +124 -0
  39. package/src/txLog.ts +208 -0
  40. package/src/txTimestamp.ts +56 -0
  41. package/src/utils.ts +55 -0
@@ -0,0 +1 @@
1
+ export type ClientId = string;
@@ -0,0 +1,44 @@
1
+ import { TxRecord } from './TxRecord';
2
+ import { TxTimestamp, TxTimestampKey } from './txTimestamp';
3
+ import type * as Y from "yjs";
4
+ /**
5
+ * A cached tx entry with lazy parsing and optional tx caching.
6
+ * The timestamp is parsed on first access and cached.
7
+ * The tx record can be fetched lazily and cached.
8
+ */
9
+ export declare class SortedTxEntry {
10
+ readonly txTimestampKey: TxTimestampKey;
11
+ private readonly _yTx;
12
+ private _txTimestamp?;
13
+ private _originalTxTimestampKey?;
14
+ private _originalTxTimestamp?;
15
+ private _txRecord?;
16
+ constructor(txTimestampKey: TxTimestampKey, _yTx: Y.Map<TxRecord>);
17
+ /**
18
+ * Gets the parsed timestamp, lazily parsing and caching on first access.
19
+ */
20
+ get txTimestamp(): TxTimestamp;
21
+ /**
22
+ * Gets the original tx timestamp key, lazily and caching on first access.
23
+ */
24
+ get originalTxTimestampKey(): TxTimestampKey | null;
25
+ /**
26
+ * Gets the parsed original tx timestamp, lazily parsing and caching on first access.
27
+ */
28
+ get originalTxTimestamp(): TxTimestamp | null;
29
+ /**
30
+ * Gets the logical (deduplicated) tx timestamp key.
31
+ * This is the original tx key if it exists, otherwise the physical key.
32
+ */
33
+ get dedupTxTimestampKey(): TxTimestampKey;
34
+ /**
35
+ * Gets the logical (deduplicated) parsed tx timestamp.
36
+ * This is the original tx timestamp if it exists, otherwise the physical timestamp.
37
+ */
38
+ get dedupTxTimestamp(): TxTimestamp;
39
+ /**
40
+ * Gets the tx record, lazily fetching and caching on first access.
41
+ * Returns undefined if the tx doesn't exist.
42
+ */
43
+ get txRecord(): TxRecord;
44
+ }
@@ -0,0 +1,141 @@
1
+ import { CheckpointRecord, ClientWatermarks } from './checkpoints';
2
+ import { JSONObject } from './json';
3
+ import { Op, ValidateFn } from './operations';
4
+ import { SortedTxEntry } from './SortedTxEntry';
5
+ import { TxRecord } from './TxRecord';
6
+ import { TxTimestamp, TxTimestampKey } from './txTimestamp';
7
+ import type * as Y from "yjs";
8
+ /**
9
+ * Checks if a transaction is covered by the checkpoint watermarks.
10
+ */
11
+ export declare function isTransactionInCheckpoint(ts: TxTimestamp, watermarks: ClientWatermarks): boolean;
12
+ /**
13
+ * StateCalculator encapsulates the sorted transaction cache and state calculation logic.
14
+ *
15
+ * It maintains:
16
+ * - A sorted array of transaction entries
17
+ * - A map for O(1) lookup
18
+ * - A tracking index indicating up to which tx the state has been calculated
19
+ * - The cached calculated state
20
+ * - The base checkpoint
21
+ *
22
+ * The tracking index is invalidated (set to null) when:
23
+ * - A transaction is inserted before the already-calculated slice
24
+ * - A transaction is deleted from the already-calculated slice
25
+ * - The base checkpoint changes
26
+ */
27
+ export declare class StateCalculator {
28
+ /** Sorted tx cache (ALL active/future txs, kept sorted by timestamp) */
29
+ private sortedTxs;
30
+ /** O(1) existence check and lookup */
31
+ private sortedTxsMap;
32
+ /**
33
+ * Index of the last transaction applied to cachedState.
34
+ * - null: state needs full recalculation from checkpoint
35
+ * - -1: no transactions have been applied yet (state === checkpoint state)
36
+ * - >= 0: transactions up to and including this index have been applied
37
+ */
38
+ private lastAppliedIndex;
39
+ /** The cached calculated state */
40
+ private cachedState;
41
+ /** The base checkpoint to calculate state from */
42
+ private baseCheckpoint;
43
+ /**
44
+ * Applied dedup keys - tracks which LOGICAL txs have been applied.
45
+ * This is the originalTxKey (or physical key if no original) for each applied tx.
46
+ * Used to properly deduplicate re-emits.
47
+ */
48
+ private appliedTxKeys;
49
+ /** Max clock seen from any transaction (for Lamport clock updates) */
50
+ private maxSeenClock;
51
+ /** Validation function (optional) */
52
+ private validateFn?;
53
+ constructor(validateFn?: ValidateFn<JSONObject>);
54
+ /**
55
+ * Sets the base checkpoint. Invalidates cached state if checkpoint changed.
56
+ * @returns true if the checkpoint changed
57
+ */
58
+ setBaseCheckpoint(checkpoint: CheckpointRecord | null): boolean;
59
+ /**
60
+ * Gets the current base checkpoint.
61
+ */
62
+ getBaseCheckpoint(): CheckpointRecord | null;
63
+ /**
64
+ * Clears all transactions and rebuilds from yTx map.
65
+ * This is used when the checkpoint changes and we need a fresh start.
66
+ */
67
+ rebuildFromYjs(yTx: Y.Map<TxRecord>): void;
68
+ /**
69
+ * Inserts a transaction into the sorted cache.
70
+ * Invalidates cached state if the transaction was inserted before the calculated slice.
71
+ *
72
+ * @returns true if this caused invalidation (out-of-order insert)
73
+ */
74
+ insertTx(key: TxTimestampKey, yTx: Y.Map<TxRecord>): boolean;
75
+ /**
76
+ * Removes multiple transactions from the sorted cache.
77
+ * @returns the number of keys that were actually removed
78
+ */
79
+ removeTxs(keys: readonly TxTimestampKey[]): number;
80
+ /**
81
+ * Checks if a transaction key exists in the cache.
82
+ */
83
+ hasTx(key: TxTimestampKey): boolean;
84
+ /**
85
+ * Gets a transaction entry by key.
86
+ */
87
+ getTx(key: TxTimestampKey): SortedTxEntry | undefined;
88
+ /**
89
+ * Gets all sorted transaction entries.
90
+ */
91
+ getSortedTxs(): readonly SortedTxEntry[];
92
+ /**
93
+ * Gets the number of transactions in the cache.
94
+ */
95
+ get txCount(): number;
96
+ /**
97
+ * Returns true if the state needs full recalculation.
98
+ */
99
+ needsFullRecalculation(): boolean;
100
+ /**
101
+ * Invalidates the cached state, forcing a full recalculation on next calculateState().
102
+ * Note: cachedState is kept so computeReconcileOps can diff old vs new state.
103
+ */
104
+ invalidate(): void;
105
+ /**
106
+ * Calculates and returns the current state, along with a lazy getter for ops that changed from the previous state.
107
+ *
108
+ * - If lastAppliedIndex is null: full recalculation from checkpoint
109
+ * - If lastAppliedIndex >= -1: incremental apply from lastAppliedIndex + 1
110
+ */
111
+ calculateState(): {
112
+ state: JSONObject;
113
+ getAppliedOps: () => readonly Op[];
114
+ };
115
+ /**
116
+ * Full recalculation of state from the base checkpoint.
117
+ */
118
+ private fullRecalculation;
119
+ /**
120
+ * Incremental apply of transactions from lastAppliedIndex + 1.
121
+ * @param returnOps If true, collects applied transactions (to lazy compute ops). If false, skips collection.
122
+ */
123
+ private incrementalApply;
124
+ /**
125
+ * Gets the max seen clock (for Lamport clock updates).
126
+ */
127
+ getMaxSeenClock(): number;
128
+ /**
129
+ * Gets the current cached state without recalculating.
130
+ * Returns null if state has never been calculated.
131
+ */
132
+ getCachedState(): JSONObject | null;
133
+ /**
134
+ * Gets the last applied timestamp.
135
+ */
136
+ getLastAppliedTs(): TxTimestamp | null;
137
+ /**
138
+ * Gets the last applied index (for debugging/tracking).
139
+ */
140
+ getLastAppliedIndex(): number | null;
141
+ }
@@ -0,0 +1,14 @@
1
+ import { Op } from './operations';
2
+ import { TxTimestampKey } from './txTimestamp';
3
+ /**
4
+ * The immutable record stored in the Log.
5
+ */
6
+ export type TxRecord = {
7
+ ops: readonly Op[];
8
+ /**
9
+ * If this is a re-emit of a missed transaction, this field holds the
10
+ * ORIGINAL key. Used for deduplication to prevent applying the same logical
11
+ * action twice.
12
+ */
13
+ originalTxKey?: TxTimestampKey;
14
+ };
@@ -0,0 +1,15 @@
1
+ import { CheckpointRecord } from './checkpoints';
2
+ import * as Y from "yjs";
3
+ /**
4
+ * Determines the finalized epoch and its canonical checkpoint in a single pass.
5
+ *
6
+ * Policy A: The finalized epoch is the most recent epoch with a checkpoint.
7
+ * Canonical checkpoint: The checkpoint with highest txCount for that epoch
8
+ * (tie-break: lowest clientId alphabetically).
9
+ *
10
+ * Returns { finalizedEpoch: -1, checkpoint: null } if no checkpoints exist.
11
+ */
12
+ export declare function getFinalizedEpochAndCheckpoint(yCheckpoint: Y.Map<CheckpointRecord>): {
13
+ finalizedEpoch: number;
14
+ checkpoint: CheckpointRecord | null;
15
+ };
@@ -0,0 +1,62 @@
1
+ import { ClientId } from './ClientId';
2
+ import { ClientState } from './clientState';
3
+ import { JSONObject } from './json';
4
+ import { TxRecord } from './TxRecord';
5
+ import * as Y from "yjs";
6
+ /**
7
+ * Watermarking for deduplication and pruning.
8
+ * - maxClock: All txs from this client with clock <= maxClock are FINALIZED.
9
+ * - maxWallClock: The last time we saw this client active (for pruning).
10
+ */
11
+ type ClientWatermark = Readonly<{
12
+ maxClock: number;
13
+ maxWallClock: number;
14
+ }>;
15
+ /**
16
+ * Watermarks for all clients.
17
+ */
18
+ export type ClientWatermarks = Record<ClientId, ClientWatermark>;
19
+ /**
20
+ * A snapshot of the state at the end of a specific epoch.
21
+ */
22
+ export type CheckpointRecord = {
23
+ state: JSONObject;
24
+ watermarks: ClientWatermarks;
25
+ txCount: number;
26
+ minWallClock: number;
27
+ };
28
+ /**
29
+ * Unique ID for a checkpoint.
30
+ * Format: `${epoch};${txCount};${clientId}`
31
+ */
32
+ export type CheckpointKey = string;
33
+ /**
34
+ * Data extracted from a checkpoint key.
35
+ */
36
+ export type CheckpointKeyData = {
37
+ epoch: number;
38
+ txCount: number;
39
+ clientId: ClientId;
40
+ };
41
+ /**
42
+ * Helper to parse checkpoint keys.
43
+ * Checkpoint keys have format: `${epoch};${txCount};${clientId}`
44
+ * Throws if key is malformed.
45
+ */
46
+ export declare function parseCheckpointKey(key: CheckpointKey): CheckpointKeyData;
47
+ /**
48
+ * Called periodically (e.g. by a server or leader client) to finalize the epoch.
49
+ */
50
+ export declare function createCheckpoint(yTx: Y.Map<TxRecord>, yCheckpoint: Y.Map<CheckpointRecord>, clientState: ClientState, activeEpoch: number, currentState: JSONObject, myClientId: string): void;
51
+ /**
52
+ * Garbage collects old checkpoints.
53
+ * Should be called periodically to prevent unbounded growth of yCheckpoint.
54
+ *
55
+ * Keeps only the canonical checkpoint for the finalized epoch.
56
+ * Everything else is deleted (old epochs + non-canonical).
57
+ *
58
+ * Note: The active epoch never has checkpoints - creating a checkpoint
59
+ * for an epoch immediately makes it finalized.
60
+ */
61
+ export declare function pruneCheckpoints(yCheckpoint: Y.Map<CheckpointRecord>, finalizedEpoch: number): void;
62
+ export {};
@@ -0,0 +1,19 @@
1
+ import { JSONObject } from './json';
2
+ import { ValidateFn } from './operations';
3
+ import { StateCalculator } from './StateCalculator';
4
+ /**
5
+ * Client-side state including clocks and calculator for state management
6
+ */
7
+ export interface ClientState {
8
+ localClock: number;
9
+ cachedFinalizedEpoch: number | null;
10
+ stateCalculator: StateCalculator;
11
+ /**
12
+ * Timestamp retention window in milliseconds.
13
+ */
14
+ retentionWindowMs: number;
15
+ }
16
+ /**
17
+ * Factory to create an initial ClientState
18
+ */
19
+ export declare function createClientState(validateFn: ValidateFn<JSONObject> | undefined, retentionWindowMs: number): ClientState;
@@ -0,0 +1,97 @@
1
+ import { JSONObject } from './json';
2
+ import { Op } from './operations';
3
+ import { SortedTxEntry } from './SortedTxEntry';
4
+ import * as Y from "yjs";
5
+ export declare const getSortedTxsSymbol: unique symbol;
6
+ export interface StateSyncLogOptions<State extends JSONObject> {
7
+ /**
8
+ * The Y.js Document to bind to.
9
+ */
10
+ yDoc: Y.Doc;
11
+ /**
12
+ * Name for the txs Y.Map.
13
+ * Default: "state-sync-log-tx"
14
+ */
15
+ yTxMapName?: string;
16
+ /**
17
+ * Name for the checkpoint Y.Map.
18
+ * Default: "state-sync-log-checkpoint"
19
+ */
20
+ yCheckpointMapName?: string;
21
+ /**
22
+ * Unique identifier for this client.
23
+ * If omitted, a random UUID (nanoid) will be generated.
24
+ * NOTE: If you need to resume a session (keep local clock/watermark), you MUST provide a stable ID.
25
+ * MUST NOT contain semicolons.
26
+ */
27
+ clientId?: string;
28
+ /**
29
+ * Origin tag for Y.js txs created by this library.
30
+ */
31
+ yjsOrigin?: unknown;
32
+ /**
33
+ * Optional validation function.
34
+ * Runs after each tx's ops are applied.
35
+ * If it returns false, the tx is rejected (state reverts).
36
+ * MUST be deterministic and consistent across all clients.
37
+ */
38
+ validate?: (state: State) => boolean;
39
+ /**
40
+ * Timestamp retention window in milliseconds.
41
+ * Txs older than this window are considered "Ancient" and pruned.
42
+ *
43
+ * Default: Infinity (No pruning).
44
+ * Recommended: 14 days (1209600000 ms).
45
+ */
46
+ retentionWindowMs: number | undefined;
47
+ }
48
+ export interface StateSyncLogController<State extends JSONObject> {
49
+ /**
50
+ * Returns the current state.
51
+ */
52
+ getState(): State;
53
+ /**
54
+ * Subscribes to state changes.
55
+ */
56
+ subscribe(callback: (newState: State, getAppliedOps: () => readonly Op[]) => void): () => void;
57
+ /**
58
+ * Emits a new tx (list of operations) to the log.
59
+ */
60
+ emit(ops: Op[]): void;
61
+ /**
62
+ * Reconciles the current state with the target state.
63
+ */
64
+ reconcileState(targetState: State): void;
65
+ /**
66
+ * Manually triggers epoch compaction (Checkpointing).
67
+ */
68
+ compact(): void;
69
+ /**
70
+ * Cleans up observers and releases memory.
71
+ */
72
+ dispose(): void;
73
+ /**
74
+ * Returns the current active epoch number.
75
+ */
76
+ getActiveEpoch(): number;
77
+ /**
78
+ * Returns the number of txs currently in the active epoch.
79
+ */
80
+ getActiveEpochTxCount(): number;
81
+ /**
82
+ * Returns the wallClock timestamp of the first tx in the active epoch.
83
+ */
84
+ getActiveEpochStartTime(): number | undefined;
85
+ /**
86
+ * Returns true if the log is completely empty.
87
+ */
88
+ isLogEmpty(): boolean;
89
+ /**
90
+ * Internal/Testing: Returns all txs currently in the log, sorted.
91
+ */
92
+ [getSortedTxsSymbol](): readonly SortedTxEntry[];
93
+ }
94
+ /**
95
+ * Creates a StateSyncLog controller.
96
+ */
97
+ export declare function createStateSyncLog<State extends JSONObject>(options: StateSyncLogOptions<State>): StateSyncLogController<State>;
@@ -0,0 +1,69 @@
1
+ import { JSONObject, JSONValue, Path } from './json';
2
+ import { Op, ValidateFn } from './operations';
3
+ import { TxRecord } from './TxRecord';
4
+ /**
5
+ * A draft context for copy-on-write immutable updates.
6
+ *
7
+ * This provides Immer/Mutative-like semantics without the proxy overhead:
8
+ * - The original base state is never mutated
9
+ * - Objects are cloned lazily on first mutation (copy-on-write)
10
+ * - Once an object is cloned ("owned"), it can be mutated directly
11
+ * - If no changes are made, the original reference is preserved (structural sharing)
12
+ */
13
+ export interface DraftContext<T extends JSONObject> {
14
+ /** The current root (may be the original base or a cloned version) */
15
+ root: T;
16
+ /** The original base state (never mutated) */
17
+ base: T;
18
+ /** Set of objects that have been cloned and are safe to mutate */
19
+ ownedObjects: WeakSet<object>;
20
+ /** Optimization: fast check if root is already owned */
21
+ isRootOwned: boolean;
22
+ }
23
+ /**
24
+ * Creates a new draft context from a base state.
25
+ * The base state will never be mutated.
26
+ */
27
+ export declare function createDraft<T extends JSONObject>(base: T): DraftContext<T>;
28
+ /**
29
+ * Checks if the draft was modified (root !== base).
30
+ */
31
+ export declare function isDraftModified<T extends JSONObject>(ctx: DraftContext<T>): boolean;
32
+ /**
33
+ * Applies a single "set" operation to the draft with copy-on-write.
34
+ */
35
+ export declare function draftSet<T extends JSONObject>(ctx: DraftContext<T>, path: Path, key: string, value: JSONValue): void;
36
+ /**
37
+ * Applies a single "delete" operation to the draft with copy-on-write.
38
+ */
39
+ export declare function draftDelete<T extends JSONObject>(ctx: DraftContext<T>, path: Path, key: string): void;
40
+ /**
41
+ * Applies a single "splice" operation to the draft with copy-on-write.
42
+ */
43
+ export declare function draftSplice<T extends JSONObject>(ctx: DraftContext<T>, path: Path, index: number, deleteCount: number, inserts: readonly JSONValue[]): void;
44
+ /**
45
+ * Applies a single "addToSet" operation to the draft with copy-on-write.
46
+ */
47
+ export declare function draftAddToSet<T extends JSONObject>(ctx: DraftContext<T>, path: Path, value: JSONValue): void;
48
+ /**
49
+ * Applies a single "deleteFromSet" operation to the draft with copy-on-write.
50
+ */
51
+ export declare function draftDeleteFromSet<T extends JSONObject>(ctx: DraftContext<T>, path: Path, value: JSONValue): void;
52
+ /**
53
+ * Applies a single operation to the draft with copy-on-write.
54
+ */
55
+ export declare function applyOpToDraft<T extends JSONObject>(ctx: DraftContext<T>, op: Op): void;
56
+ /**
57
+ * Applies a single transaction to a base state immutably.
58
+ *
59
+ * Key benefits over Mutative/Immer:
60
+ * - No proxy overhead - direct object access and copy-on-write cloning
61
+ * - Structural sharing - unchanged subtrees keep their references
62
+ * - Zero-copy on failure - if validation fails, returns original base unchanged
63
+ *
64
+ * @param base - The base state (never mutated)
65
+ * @param tx - The transaction to apply
66
+ * @param validateFn - Optional validation function
67
+ * @returns The final state (if valid) or the original base (if invalid or empty)
68
+ */
69
+ export declare function applyTxImmutable<T extends JSONObject>(base: T, tx: Pick<TxRecord, "ops">, validateFn?: ValidateFn<T>): T;
@@ -0,0 +1,4 @@
1
+ export declare class StateSyncLogError extends Error {
2
+ constructor(msg: string);
3
+ }
4
+ export declare function failure(message: string): never;
@@ -0,0 +1,4 @@
1
+ export type { CheckpointKey, CheckpointRecord } from './checkpoints';
2
+ export { createStateSyncLog, type StateSyncLogController, type StateSyncLogOptions, } from './createStateSyncLog';
3
+ export type { JSONObject, JSONValue, Path } from './json';
4
+ export { type ApplyOpsOptions, applyOps, type Op, type ValidateFn } from './operations';
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A unique path to a value within the JSON document.
3
+ * Resolution fails if any segment is missing or type mismatch occurs.
4
+ */
5
+ export type Path = readonly (string | number)[];
6
+ /**
7
+ * A JSON primitive.
8
+ */
9
+ export type JSONPrimitive = null | boolean | number | string;
10
+ /**
11
+ * A JSON record.
12
+ */
13
+ export type JSONRecord = {
14
+ [k: string]: JSONValue;
15
+ };
16
+ /**
17
+ * A JSON object.
18
+ */
19
+ export type JSONObject = JSONRecord | JSONValue[];
20
+ /**
21
+ * A JSON value.
22
+ */
23
+ export type JSONValue = JSONPrimitive | JSONObject;
@@ -0,0 +1,64 @@
1
+ import { JSONObject, JSONValue, Path } from './json';
2
+ /**
3
+ * Supported operations.
4
+ * Applied sequentially within a tx.
5
+ */
6
+ export type Op = {
7
+ kind: "set";
8
+ path: Path;
9
+ key: string;
10
+ value: JSONValue;
11
+ } | {
12
+ kind: "delete";
13
+ path: Path;
14
+ key: string;
15
+ } | {
16
+ kind: "splice";
17
+ path: Path;
18
+ index: number;
19
+ deleteCount: number;
20
+ inserts: JSONValue[];
21
+ } | {
22
+ kind: "addToSet";
23
+ path: Path;
24
+ value: JSONValue;
25
+ } | {
26
+ kind: "deleteFromSet";
27
+ path: Path;
28
+ value: JSONValue;
29
+ };
30
+ /**
31
+ * Validation function type.
32
+ *
33
+ * Rules:
34
+ * - Validation MUST depend only on candidateState (and deterministic code).
35
+ * - Validation runs once per tx, after all ops apply.
36
+ * - If validation fails, the x is rejected (state reverts to previous).
37
+ * - If no validator is provided, validation defaults to true.
38
+ *
39
+ * IMPORTANT: Validation outcome is **derived local state** and MUST NOT be replicated.
40
+ * All clients MUST use the same validation logic to ensure consistency.
41
+ */
42
+ export type ValidateFn<State extends JSONObject> = (candidateState: State) => boolean;
43
+ /**
44
+ * Options for applyOps.
45
+ */
46
+ export interface ApplyOpsOptions {
47
+ /**
48
+ * Whether to deep clone values before inserting them into the target.
49
+ * - `true` (default): Values are cloned to prevent aliasing between the ops and target.
50
+ * - `false`: Values are used directly for better performance. Only use this if you
51
+ * guarantee the op values won't be mutated after application.
52
+ */
53
+ cloneValues?: boolean;
54
+ }
55
+ /**
56
+ * Applies a list of operations to a mutable target object.
57
+ * Use this to synchronize an external mutable state (e.g., MobX store)
58
+ * with the operations received via subscribe().
59
+ *
60
+ * @param ops - The list of operations to apply.
61
+ * @param target - The mutable object to modify.
62
+ * @param options - Optional settings for controlling cloning behavior.
63
+ */
64
+ export declare function applyOps(ops: readonly Op[], target: JSONObject, options?: ApplyOpsOptions): void;
@@ -0,0 +1,7 @@
1
+ import { JSONValue } from './json';
2
+ import { Op } from './operations';
3
+ /**
4
+ * Reconciles the current state with the target state by computing and emitting
5
+ * the minimal set of operations needed to transform currentState into targetState.
6
+ */
7
+ export declare function computeReconcileOps(currentState: JSONValue, targetState: JSONValue): Op[];
@@ -0,0 +1,32 @@
1
+ import { CheckpointRecord } from './checkpoints';
2
+ import { ClientState } from './clientState';
3
+ import { JSONObject } from './json';
4
+ import { Op } from './operations';
5
+ import { TxRecord } from './TxRecord';
6
+ import { TxTimestampKey } from './txTimestamp';
7
+ import * as Y from "yjs";
8
+ /**
9
+ * Changes to transaction keys from a Y.YMapEvent.
10
+ * - added: keys that were added
11
+ * - deleted: keys that were deleted
12
+ */
13
+ export type TxKeyChanges = {
14
+ added: readonly TxTimestampKey[];
15
+ deleted: readonly TxTimestampKey[];
16
+ };
17
+ /**
18
+ * Appends a new transaction to the log.
19
+ *
20
+ * @param originalKey - Optional reference to the original transaction key for re-emits.
21
+ * Used by syncLog to preserve transactions missed by checkpoints.
22
+ */
23
+ export declare function appendTx(ops: readonly Op[], yTx: Y.Map<TxRecord>, activeEpoch: number, myClientId: string, clientState: ClientState, originalKey?: TxTimestampKey): TxTimestampKey;
24
+ /**
25
+ * The primary update function that maintains current state.
26
+ *
27
+ * @param txChanges - Changes from Y.YMapEvent. If undefined (first run), performs a full scan.
28
+ */
29
+ export declare function updateState(doc: Y.Doc, yTx: Y.Map<TxRecord>, yCheckpoint: Y.Map<CheckpointRecord>, myClientId: string, clientState: ClientState, txChanges: TxKeyChanges | undefined): {
30
+ state: JSONObject;
31
+ getAppliedOps: () => readonly Op[];
32
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Parsed tx timestamp components.
3
+ */
4
+ export type TxTimestamp = {
5
+ epoch: number;
6
+ clock: number;
7
+ clientId: string;
8
+ wallClock: number;
9
+ };
10
+ /**
11
+ * Unique tx ID (Composite Key).
12
+ */
13
+ export type TxTimestampKey = string;
14
+ /**
15
+ * Converts a timestamp object to a TransactionTimestampKey string.
16
+ */
17
+ export declare function txTimestampToKey(ts: TxTimestamp): TxTimestampKey;
18
+ /**
19
+ * Helper to parse tx timestamp keys.
20
+ * Throws if key is malformed.
21
+ */
22
+ export declare function parseTxTimestampKey(key: TxTimestampKey): TxTimestamp;
23
+ /**
24
+ * Compares two tx timestamps for deterministic ordering.
25
+ * Sort order: epoch (asc) → clock (asc) → clientId (asc)
26
+ */
27
+ export declare function compareTxTimestamps(a: TxTimestamp, b: TxTimestamp): number;
@@ -0,0 +1,23 @@
1
+ import { JSONValue } from './json';
2
+ /**
3
+ * Deep equality check for JSONValues.
4
+ * Used for addToSet / deleteFromSet operations.
5
+ */
6
+ export declare function deepEqual(a: JSONValue, b: JSONValue): boolean;
7
+ /**
8
+ * Generates a unique ID using nanoid.
9
+ */
10
+ export declare function generateID(): string;
11
+ /**
12
+ * Checks if a value is an object (typeof === "object" && !== null).
13
+ */
14
+ export declare function isObject(value: unknown): value is object;
15
+ /**
16
+ * Deep clones a JSON-serializable value.
17
+ * Optimized: primitives are returned as-is.
18
+ */
19
+ export declare function deepClone<T>(value: T): T;
20
+ /**
21
+ * Creates a lazy memoized getter.
22
+ */
23
+ export declare function lazy<T>(fn: () => T): () => T;