@@ -14,7 +14,7 @@ use crate::engine::htap::HtapBridge;
1414use crate :: engine:: strict:: StrictEngine ;
1515use crate :: engine:: vector:: graph:: { HnswIndex , HnswParams } ;
1616use crate :: memory:: { EngineId , MemoryGovernor } ;
17- use crate :: storage:: engine:: { StorageEngine , WriteOp } ;
17+ use crate :: storage:: engine:: { StorageEngine , StorageEngineSync , WriteOp } ;
1818
1919/// Storage key constants.
2020pub ( crate ) const META_HNSW_COLLECTIONS : & [ u8 ] = b"meta:hnsw_collections" ;
@@ -31,7 +31,7 @@ pub(crate) const META_LAST_FLUSHED_MID: &[u8] = b"meta:last_flushed_mid";
3131///
3232/// Fully capable of vector search, graph traversal, and document CRUD
3333/// entirely offline. Optional sync to Origin via WebSocket.
34- pub struct NodeDbLite < S : StorageEngine > {
34+ pub struct NodeDbLite < S : StorageEngine + StorageEngineSync > {
3535 pub ( crate ) storage : Arc < S > ,
3636 /// Per-collection HNSW indices.
3737 pub ( crate ) hnsw_indices : Mutex < HashMap < String , HnswIndex > > ,
@@ -70,7 +70,25 @@ pub struct NodeDbLite<S: StorageEngine> {
7070 /// Arc-wrapped for sharing with the query engine's DDL handlers.
7171 pub ( crate ) timeseries : Arc < Mutex < crate :: engine:: timeseries:: engine:: TimeseriesEngine > > ,
7272 /// Array engine in-memory state (storage-agnostic; calls via NodeDbLite methods).
73- pub ( crate ) array_state : std:: sync:: Mutex < crate :: engine:: array:: engine:: ArrayEngineState > ,
73+ ///
74+ /// `Arc`-wrapped so it can be shared with [`crate::sync::array::LiteApplyEngine`]
75+ /// for the inbound receive path without borrowing `NodeDbLite`.
76+ pub ( crate ) array_state : Arc < std:: sync:: Mutex < crate :: engine:: array:: engine:: ArrayEngineState > > ,
77+ /// Stable per-replica identity + HLC generator for array CRDT sync.
78+ /// Used by the transport-layer wiring (Phase F+); held here so that
79+ /// the `NodeDbLite` constructor owns the lifetime.
80+ #[ allow( dead_code) ]
81+ pub ( crate ) array_replica : Arc < crate :: sync:: array:: ReplicaState > ,
82+ /// Per-array [`SchemaDoc`] registry (persisted Loro snapshots).
83+ pub ( crate ) array_schemas : Arc < crate :: sync:: array:: SchemaRegistry < S > > ,
84+ /// Array CRDT send path: op-log + pending queue emitters.
85+ pub ( crate ) array_outbound : Arc < crate :: sync:: array:: ArrayOutbound < S > > ,
86+ /// Array CRDT receive path: applies inbound wire messages from Origin.
87+ #[ allow( dead_code) ]
88+ pub ( crate ) array_inbound : Arc < crate :: sync:: array:: ArrayInbound < S > > ,
89+ /// Per-array last-seen HLC tracker for catch-up requests.
90+ #[ allow( dead_code) ]
91+ pub ( crate ) array_catchup : Arc < crate :: sync:: array:: CatchupTracker < S > > ,
7492 /// When `false`, KV operations go directly to redb, bypassing Loro.
7593 /// Other engines (vector, graph, document) are unaffected.
7694 pub ( crate ) sync_enabled : bool ,
@@ -97,7 +115,7 @@ pub(crate) struct KvWriteBuffer {
97115 pub overlay : HashMap < Vec < u8 > , Option < Vec < u8 > > > ,
98116}
99117
100- impl < S : StorageEngine > NodeDbLite < S > {
118+ impl < S : StorageEngine + StorageEngineSync > NodeDbLite < S > {
101119 /// Open or create a Lite database backed by the given storage engine.
102120 ///
103121 /// Memory budget and per-engine percentages are resolved from environment
@@ -271,6 +289,47 @@ impl<S: StorageEngine> NodeDbLite<S> {
271289
272290 let array_engine =
273291 crate :: engine:: array:: ArrayEngineState :: open ( & storage) . map_err ( NodeDbError :: storage) ?;
292+ let array_state = Arc :: new ( Mutex :: new ( array_engine) ) ;
293+
294+ // ── Array CRDT sync state ──────────────────────────────────────────────
295+ let array_replica = Arc :: new (
296+ crate :: sync:: array:: ReplicaState :: load_or_init ( & * storage)
297+ . map_err ( NodeDbError :: storage) ?,
298+ ) ;
299+ let array_schemas = Arc :: new (
300+ crate :: sync:: array:: SchemaRegistry :: load (
301+ Arc :: clone ( & storage) ,
302+ Arc :: clone ( & array_replica) ,
303+ )
304+ . map_err ( NodeDbError :: storage) ?,
305+ ) ;
306+ let array_op_log = Arc :: new ( crate :: sync:: array:: RedbOpLog :: new ( Arc :: clone ( & storage) ) ) ;
307+ let array_pending = Arc :: new ( crate :: sync:: array:: PendingQueue :: new ( Arc :: clone ( & storage) ) ) ;
308+ let array_outbound = Arc :: new ( crate :: sync:: array:: ArrayOutbound :: new (
309+ Arc :: clone ( & array_op_log) ,
310+ Arc :: clone ( & array_pending) ,
311+ Arc :: clone ( & array_schemas) ,
312+ Arc :: clone ( & array_replica) ,
313+ ) ) ;
314+
315+ // ── Array CRDT inbound receive path ───────────────────────────────────
316+ let array_apply_engine = Arc :: new ( crate :: sync:: array:: LiteApplyEngine :: new (
317+ Arc :: clone ( & storage) ,
318+ Arc :: clone ( & array_state) ,
319+ Arc :: clone ( & array_schemas) ,
320+ Arc :: clone ( array_outbound. op_log ( ) ) ,
321+ ) ) ;
322+ let array_inbound = Arc :: new ( crate :: sync:: array:: ArrayInbound :: new (
323+ array_apply_engine,
324+ Arc :: clone ( & array_schemas) ,
325+ Arc :: clone ( & array_replica) ,
326+ Arc :: clone ( array_outbound. pending ( ) ) ,
327+ Arc :: clone ( array_outbound. op_log ( ) ) ,
328+ ) ) ;
329+ let array_catchup = Arc :: new (
330+ crate :: sync:: array:: CatchupTracker :: load ( Arc :: clone ( & storage) )
331+ . map_err ( NodeDbError :: storage) ?,
332+ ) ;
274333
275334 let db = Self {
276335 storage,
@@ -288,7 +347,12 @@ impl<S: StorageEngine> NodeDbLite<S> {
288347 columnar,
289348 htap,
290349 timeseries,
291- array_state : Mutex :: new ( array_engine) ,
350+ array_state,
351+ array_replica,
352+ array_schemas,
353+ array_outbound,
354+ array_inbound,
355+ array_catchup,
292356 sync_enabled,
293357 kv_write_buf : Mutex :: new ( KvWriteBuffer {
294358 ops : Vec :: with_capacity ( 1024 ) ,
0 commit comments