@lark-sh/client 0.1.4 → 0.1.5

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.mjs CHANGED
@@ -5,17 +5,14 @@ var OnDisconnectAction = {
5
5
  DELETE: "d",
6
6
  CANCEL: "c"
7
7
  };
8
- var eventTypeFromShort = {
9
- v: "value",
10
- ca: "child_added",
11
- cc: "child_changed",
12
- cr: "child_removed"
13
- };
14
- var eventTypeToShort = {
15
- value: "v",
16
- child_added: "ca",
17
- child_changed: "cc",
18
- child_removed: "cr"
8
+ var ErrorCode = {
9
+ PERMISSION_DENIED: "permission_denied",
10
+ INVALID_DATA: "invalid_data",
11
+ NOT_FOUND: "not_found",
12
+ INVALID_PATH: "invalid_path",
13
+ INVALID_OPERATION: "invalid_operation",
14
+ INTERNAL_ERROR: "internal_error",
15
+ CONDITION_FAILED: "condition_failed"
19
16
  };
20
17
  var DEFAULT_COORDINATOR_URL = "https://db.lark.sh";
21
18
 
@@ -202,6 +199,91 @@ var MessageQueue = class {
202
199
  }
203
200
  };
204
201
 
202
+ // src/connection/PendingWriteManager.ts
203
+ var PendingWriteManager = class {
204
+ constructor() {
205
+ this.pending = /* @__PURE__ */ new Map();
206
+ this.counter = 0;
207
+ }
208
+ /**
209
+ * Generate a unique operation ID.
210
+ * Format: op_{timestamp}_{counter}_{random}
211
+ */
212
+ generateOid() {
213
+ this.counter++;
214
+ const random = Math.random().toString(36).slice(2, 8);
215
+ return `op_${Date.now()}_${this.counter}_${random}`;
216
+ }
217
+ /**
218
+ * Track a new pending write operation.
219
+ */
220
+ trackWrite(oid, operation, path, value) {
221
+ this.pending.set(oid, {
222
+ oid,
223
+ operation,
224
+ path,
225
+ value,
226
+ timestamp: Date.now()
227
+ });
228
+ }
229
+ /**
230
+ * Called when a write is acknowledged by the server.
231
+ * Removes the write from pending tracking.
232
+ */
233
+ onAck(oid) {
234
+ return this.pending.delete(oid);
235
+ }
236
+ /**
237
+ * Called when a write is rejected by the server.
238
+ * Removes the write from pending tracking.
239
+ */
240
+ onNack(oid) {
241
+ return this.pending.delete(oid);
242
+ }
243
+ /**
244
+ * Get all pending writes for retry on reconnect.
245
+ * Returns writes in order of their timestamps (oldest first).
246
+ */
247
+ getPendingWrites() {
248
+ const writes = Array.from(this.pending.values());
249
+ writes.sort((a, b) => a.timestamp - b.timestamp);
250
+ return writes;
251
+ }
252
+ /**
253
+ * Check if a specific operation is still pending.
254
+ */
255
+ isPending(oid) {
256
+ return this.pending.has(oid);
257
+ }
258
+ /**
259
+ * Get the number of pending writes.
260
+ */
261
+ get pendingCount() {
262
+ return this.pending.size;
263
+ }
264
+ /**
265
+ * Clear all pending writes (e.g., on disconnect when not retrying).
266
+ */
267
+ clear() {
268
+ this.pending.clear();
269
+ }
270
+ /**
271
+ * Remove writes older than a specified age (in milliseconds).
272
+ * Useful for cleaning up stale pending writes that may never be acked.
273
+ */
274
+ removeStale(maxAgeMs) {
275
+ const cutoff = Date.now() - maxAgeMs;
276
+ let removed = 0;
277
+ for (const [oid, write] of this.pending) {
278
+ if (write.timestamp < cutoff) {
279
+ this.pending.delete(oid);
280
+ removed++;
281
+ }
282
+ }
283
+ return removed;
284
+ }
285
+ };
286
+
205
287
  // src/utils/path.ts
206
288
  function normalizePath(path) {
207
289
  if (!path || path === "/") return "/";
@@ -356,6 +438,8 @@ var DataCache = class {
356
438
  *
357
439
  * E.g., if /boxes is cached and /boxes/5 changes:
358
440
  * - Update the /boxes cache to reflect the new /boxes/5 value
441
+ *
442
+ * If an ancestor is null, it creates a new object to hold the child.
359
443
  */
360
444
  updateChild(path, value) {
361
445
  const normalized = normalizePath(path);
@@ -365,22 +449,17 @@ var DataCache = class {
365
449
  const ancestorPath = "/" + segments.slice(0, i).join("/");
366
450
  if (this.cache.has(ancestorPath)) {
367
451
  const ancestorValue = this.cache.get(ancestorPath);
368
- if (ancestorValue !== null && typeof ancestorValue === "object") {
369
- const childKey = segments[i];
370
- const remainingPath = "/" + segments.slice(i).join("/");
371
- const updatedAncestor = this.deepClone(ancestorValue);
372
- setValueAtPath(updatedAncestor, remainingPath, value);
373
- this.cache.set(ancestorPath, updatedAncestor);
374
- }
452
+ const baseValue = ancestorValue !== null && typeof ancestorValue === "object" ? this.deepClone(ancestorValue) : {};
453
+ const remainingPath = "/" + segments.slice(i).join("/");
454
+ setValueAtPath(baseValue, remainingPath, value);
455
+ this.cache.set(ancestorPath, baseValue);
375
456
  }
376
457
  }
377
458
  if (this.cache.has("/") && normalized !== "/") {
378
459
  const rootValue = this.cache.get("/");
379
- if (rootValue !== null && typeof rootValue === "object") {
380
- const updatedRoot = this.deepClone(rootValue);
381
- setValueAtPath(updatedRoot, normalized, value);
382
- this.cache.set("/", updatedRoot);
383
- }
460
+ const baseValue = rootValue !== null && typeof rootValue === "object" ? this.deepClone(rootValue) : {};
461
+ setValueAtPath(baseValue, normalized, value);
462
+ this.cache.set("/", baseValue);
384
463
  }
385
464
  }
386
465
  /**
@@ -433,10 +512,15 @@ var DataCache = class {
433
512
  // src/connection/SubscriptionManager.ts
434
513
  var SubscriptionManager = class {
435
514
  constructor() {
436
- // path -> eventType -> array of subscriptions
515
+ // subscriptionPath -> eventType -> array of subscriptions
437
516
  this.subscriptions = /* @__PURE__ */ new Map();
517
+ // Ordered child keys for each subscription path (for ordered queries)
518
+ // subscriptionPath -> ordered array of child keys
519
+ this.orderedChildren = /* @__PURE__ */ new Map();
438
520
  // Callback to send subscribe message to server
439
521
  this.sendSubscribe = null;
522
+ // Query params for each subscription path
523
+ this.queryParams = /* @__PURE__ */ new Map();
440
524
  // Callback to send unsubscribe message to server
441
525
  this.sendUnsubscribe = null;
442
526
  // Callback to create DataSnapshot from event data
@@ -455,7 +539,7 @@ var SubscriptionManager = class {
455
539
  * Subscribe to events at a path.
456
540
  * Returns an unsubscribe function.
457
541
  */
458
- subscribe(path, eventType, callback) {
542
+ subscribe(path, eventType, callback, queryParams) {
459
543
  const normalizedPath = path;
460
544
  const isFirstForPath = !this.subscriptions.has(normalizedPath);
461
545
  const existingEventTypes = this.getEventTypesForPath(normalizedPath);
@@ -468,14 +552,17 @@ var SubscriptionManager = class {
468
552
  pathSubs.set(eventType, []);
469
553
  }
470
554
  const eventSubs = pathSubs.get(eventType);
555
+ if (isFirstForPath && queryParams) {
556
+ this.queryParams.set(normalizedPath, queryParams);
557
+ }
471
558
  const unsubscribe = () => {
472
559
  this.unsubscribeCallback(normalizedPath, eventType, callback);
473
560
  };
474
561
  eventSubs.push({ callback, unsubscribe });
475
562
  if (isFirstForPath || isFirstForEventType) {
476
563
  const allEventTypes = this.getEventTypesForPath(normalizedPath);
477
- const shortEventTypes = allEventTypes.map((et) => eventTypeToShort[et]);
478
- this.sendSubscribe?.(normalizedPath, shortEventTypes).catch((err) => {
564
+ const storedQueryParams = this.queryParams.get(normalizedPath);
565
+ this.sendSubscribe?.(normalizedPath, allEventTypes, storedQueryParams).catch((err) => {
479
566
  console.error("Failed to subscribe:", err);
480
567
  });
481
568
  }
@@ -498,6 +585,9 @@ var SubscriptionManager = class {
498
585
  }
499
586
  if (pathSubs.size === 0) {
500
587
  this.subscriptions.delete(path);
588
+ this.orderedChildren.delete(path);
589
+ this.queryParams.delete(path);
590
+ this.cache.deleteTree(path);
501
591
  this.sendUnsubscribe?.(path).catch((err) => {
502
592
  console.error("Failed to unsubscribe:", err);
503
593
  });
@@ -513,13 +603,16 @@ var SubscriptionManager = class {
513
603
  pathSubs.delete(eventType);
514
604
  if (pathSubs.size === 0) {
515
605
  this.subscriptions.delete(normalizedPath);
606
+ this.orderedChildren.delete(normalizedPath);
607
+ this.queryParams.delete(normalizedPath);
608
+ this.cache.deleteTree(normalizedPath);
516
609
  this.sendUnsubscribe?.(normalizedPath).catch((err) => {
517
610
  console.error("Failed to unsubscribe:", err);
518
611
  });
519
612
  } else {
520
613
  const remainingEventTypes = this.getEventTypesForPath(normalizedPath);
521
- const shortEventTypes = remainingEventTypes.map((et) => eventTypeToShort[et]);
522
- this.sendSubscribe?.(normalizedPath, shortEventTypes).catch((err) => {
614
+ const storedQueryParams = this.queryParams.get(normalizedPath);
615
+ this.sendSubscribe?.(normalizedPath, remainingEventTypes, storedQueryParams).catch((err) => {
523
616
  console.error("Failed to update subscription:", err);
524
617
  });
525
618
  }
@@ -531,49 +624,347 @@ var SubscriptionManager = class {
531
624
  const normalizedPath = path;
532
625
  if (!this.subscriptions.has(normalizedPath)) return;
533
626
  this.subscriptions.delete(normalizedPath);
627
+ this.orderedChildren.delete(normalizedPath);
628
+ this.queryParams.delete(normalizedPath);
629
+ this.cache.deleteTree(normalizedPath);
534
630
  this.sendUnsubscribe?.(normalizedPath).catch((err) => {
535
631
  console.error("Failed to unsubscribe:", err);
536
632
  });
537
633
  }
538
634
  /**
539
635
  * Handle an incoming event message from the server.
636
+ * Server sends 'put' or 'patch' events; we generate child_* events client-side.
540
637
  */
541
638
  handleEvent(message) {
542
- const eventType = eventTypeFromShort[message.ev];
543
- if (!eventType) {
639
+ if (message.ev === "put") {
640
+ this.handlePutEvent(message);
641
+ } else if (message.ev === "patch") {
642
+ this.handlePatchEvent(message);
643
+ } else {
544
644
  console.warn("Unknown event type:", message.ev);
545
- return;
546
645
  }
547
- const path = message.p;
548
- const pathSubs = this.subscriptions.get(path);
646
+ }
647
+ /**
648
+ * Handle a 'put' event - single path change.
649
+ */
650
+ handlePutEvent(message) {
651
+ const subscriptionPath = message.sp;
652
+ const relativePath = message.p;
653
+ const value = message.v;
654
+ const isVolatile = message.x ?? false;
655
+ const serverTimestamp = message.ts;
656
+ const afterKey = message.ak;
657
+ const orderedKeys = message.k;
658
+ const pathSubs = this.subscriptions.get(subscriptionPath);
549
659
  if (!pathSubs) return;
550
- const eventSubs = pathSubs.get(eventType);
551
- if (!eventSubs || eventSubs.length === 0) return;
552
- let snapshotPath;
553
- let snapshotValue;
554
- if (eventType === "value") {
555
- snapshotPath = path;
556
- snapshotValue = message.v;
557
- this.cache.set(path, snapshotValue);
558
- } else if (eventType === "child_added" || eventType === "child_changed") {
559
- snapshotPath = path === "/" ? `/${message.k}` : `${path}/${message.k}`;
560
- snapshotValue = message.v;
561
- this.cache.updateChild(snapshotPath, snapshotValue);
562
- } else if (eventType === "child_removed") {
563
- snapshotPath = path === "/" ? `/${message.k}` : `${path}/${message.k}`;
564
- snapshotValue = message.v;
565
- this.cache.removeChild(snapshotPath);
660
+ const absolutePath = relativePath === "/" ? subscriptionPath : joinPath(subscriptionPath, relativePath);
661
+ const previousOrder = this.orderedChildren.get(subscriptionPath) ?? [];
662
+ const previousChildSet = new Set(previousOrder);
663
+ if (value !== void 0) {
664
+ if (relativePath === "/") {
665
+ this.cache.set(subscriptionPath, value);
666
+ } else if (value === null) {
667
+ this.cache.removeChild(absolutePath);
668
+ } else {
669
+ this.cache.updateChild(absolutePath, value);
670
+ }
671
+ }
672
+ if (relativePath === "/") {
673
+ if (orderedKeys) {
674
+ this.orderedChildren.set(subscriptionPath, [...orderedKeys]);
675
+ } else if (value && typeof value === "object" && value !== null) {
676
+ this.orderedChildren.set(subscriptionPath, Object.keys(value));
677
+ } else {
678
+ this.orderedChildren.set(subscriptionPath, []);
679
+ }
566
680
  } else {
567
- snapshotPath = path === "/" ? `/${message.k}` : `${path}/${message.k}`;
568
- snapshotValue = message.v;
681
+ const segments = relativePath.split("/").filter((s) => s.length > 0);
682
+ if (segments.length > 0) {
683
+ const childKey = segments[0];
684
+ const currentOrder2 = this.orderedChildren.get(subscriptionPath) ?? [];
685
+ if (value === null) {
686
+ const newOrder = currentOrder2.filter((k) => k !== childKey);
687
+ this.orderedChildren.set(subscriptionPath, newOrder);
688
+ } else if (!previousChildSet.has(childKey)) {
689
+ const newOrder = [...currentOrder2];
690
+ this.insertAfterKey(newOrder, childKey, afterKey);
691
+ this.orderedChildren.set(subscriptionPath, newOrder);
692
+ }
693
+ }
569
694
  }
570
- const snapshot = this.createSnapshot?.(snapshotPath, snapshotValue, message.x ?? false);
571
- if (!snapshot) return;
572
- for (const entry of eventSubs) {
573
- try {
574
- entry.callback(snapshot, void 0);
575
- } catch (err) {
576
- console.error("Error in subscription callback:", err);
695
+ const currentOrder = this.orderedChildren.get(subscriptionPath) ?? [];
696
+ const currentChildSet = new Set(currentOrder);
697
+ this.fireCallbacks(
698
+ subscriptionPath,
699
+ pathSubs,
700
+ relativePath,
701
+ value,
702
+ previousOrder,
703
+ currentOrder,
704
+ previousChildSet,
705
+ currentChildSet,
706
+ afterKey,
707
+ isVolatile,
708
+ serverTimestamp
709
+ );
710
+ }
711
+ /**
712
+ * Handle a 'patch' event - multi-path change and/or moves.
713
+ */
714
+ handlePatchEvent(message) {
715
+ const subscriptionPath = message.sp;
716
+ const basePath = message.p;
717
+ const patches = message.v;
718
+ const moves = message.mv;
719
+ const orderedKeys = message.k;
720
+ const isVolatile = message.x ?? false;
721
+ const serverTimestamp = message.ts;
722
+ const pathSubs = this.subscriptions.get(subscriptionPath);
723
+ if (!pathSubs) return;
724
+ const previousOrder = this.orderedChildren.get(subscriptionPath) ?? [];
725
+ const previousChildSet = new Set(previousOrder);
726
+ const affectedChildren = /* @__PURE__ */ new Set();
727
+ if (patches) {
728
+ for (const [relativePath, value] of Object.entries(patches)) {
729
+ const fullRelativePath = basePath === "/" ? "/" + relativePath : joinPath(basePath, relativePath);
730
+ const absolutePath = joinPath(subscriptionPath, fullRelativePath);
731
+ const segments = fullRelativePath.split("/").filter((s) => s.length > 0);
732
+ if (segments.length > 0) {
733
+ affectedChildren.add(segments[0]);
734
+ }
735
+ if (value === null) {
736
+ this.cache.removeChild(absolutePath);
737
+ } else {
738
+ this.cache.updateChild(absolutePath, value);
739
+ }
740
+ }
741
+ }
742
+ if (moves && moves.length > 0) {
743
+ this.handleMoves(subscriptionPath, pathSubs, moves, isVolatile, serverTimestamp);
744
+ }
745
+ if (orderedKeys) {
746
+ this.orderedChildren.set(subscriptionPath, [...orderedKeys]);
747
+ }
748
+ const currentOrder = this.orderedChildren.get(subscriptionPath) ?? [];
749
+ const currentChildSet = new Set(currentOrder);
750
+ const valueSubs = pathSubs.get("value");
751
+ if (valueSubs && valueSubs.length > 0) {
752
+ const fullValue = this.cache.get(subscriptionPath).value;
753
+ const snapshot = this.createSnapshot?.(subscriptionPath, fullValue, isVolatile, serverTimestamp);
754
+ if (snapshot) {
755
+ for (const entry of valueSubs) {
756
+ try {
757
+ entry.callback(snapshot, void 0);
758
+ } catch (err) {
759
+ console.error("Error in value subscription callback:", err);
760
+ }
761
+ }
762
+ }
763
+ }
764
+ if (patches && affectedChildren.size > 0) {
765
+ const childAddedSubs = pathSubs.get("child_added") ?? [];
766
+ const childChangedSubs = pathSubs.get("child_changed") ?? [];
767
+ const childRemovedSubs = pathSubs.get("child_removed") ?? [];
768
+ for (const childKey of affectedChildren) {
769
+ const wasPresent = previousChildSet.has(childKey);
770
+ const isPresent = currentChildSet.has(childKey);
771
+ if (!wasPresent && isPresent) {
772
+ const prevKey = this.getPreviousChildKey(currentOrder, childKey);
773
+ this.fireChildAdded(subscriptionPath, childKey, childAddedSubs, prevKey, isVolatile, serverTimestamp);
774
+ } else if (wasPresent && !isPresent) {
775
+ this.fireChildRemoved(subscriptionPath, childKey, childRemovedSubs, isVolatile, serverTimestamp);
776
+ } else if (wasPresent && isPresent) {
777
+ const prevKey = this.getPreviousChildKey(currentOrder, childKey);
778
+ this.fireChildChanged(subscriptionPath, childKey, childChangedSubs, prevKey, isVolatile, serverTimestamp);
779
+ }
780
+ }
781
+ }
782
+ }
783
+ /**
784
+ * Handle moves array - update order and fire child_moved events.
785
+ */
786
+ handleMoves(subscriptionPath, pathSubs, moves, isVolatile, serverTimestamp) {
787
+ const childMovedSubs = pathSubs.get("child_moved") ?? [];
788
+ const currentOrder = this.orderedChildren.get(subscriptionPath) ?? [];
789
+ for (const move of moves) {
790
+ const { k: childKey, ak: afterKey } = move;
791
+ const idx = currentOrder.indexOf(childKey);
792
+ if (idx !== -1) {
793
+ currentOrder.splice(idx, 1);
794
+ }
795
+ this.insertAfterKey(currentOrder, childKey, afterKey);
796
+ if (childMovedSubs.length > 0) {
797
+ const previousChildKey = afterKey === "" ? null : afterKey;
798
+ this.fireChildMoved(subscriptionPath, childKey, childMovedSubs, previousChildKey, isVolatile, serverTimestamp);
799
+ }
800
+ }
801
+ this.orderedChildren.set(subscriptionPath, currentOrder);
802
+ }
803
+ /**
804
+ * Insert a key into an ordered array after a specific key.
805
+ * If afterKey is empty string or undefined, insert at beginning.
806
+ * If afterKey is not found, append at end.
807
+ */
808
+ insertAfterKey(order, key, afterKey) {
809
+ if (afterKey === "" || afterKey === void 0) {
810
+ order.unshift(key);
811
+ } else {
812
+ const afterIdx = order.indexOf(afterKey);
813
+ if (afterIdx === -1) {
814
+ order.push(key);
815
+ } else {
816
+ order.splice(afterIdx + 1, 0, key);
817
+ }
818
+ }
819
+ }
820
+ /**
821
+ * Get the previous sibling key for a given key in the ordered array.
822
+ */
823
+ getPreviousChildKey(order, key) {
824
+ const idx = order.indexOf(key);
825
+ if (idx <= 0) return null;
826
+ return order[idx - 1];
827
+ }
828
+ /**
829
+ * Fire callbacks for subscribed event types.
830
+ */
831
+ fireCallbacks(subscriptionPath, pathSubs, relativePath, value, previousOrder, currentOrder, previousChildSet, currentChildSet, afterKey, isVolatile, serverTimestamp) {
832
+ const valueSubs = pathSubs.get("value");
833
+ if (valueSubs && valueSubs.length > 0) {
834
+ const fullValue = this.cache.get(subscriptionPath).value;
835
+ const snapshot = this.createSnapshot?.(subscriptionPath, fullValue, isVolatile, serverTimestamp);
836
+ if (snapshot) {
837
+ for (const entry of valueSubs) {
838
+ try {
839
+ entry.callback(snapshot, void 0);
840
+ } catch (err) {
841
+ console.error("Error in value subscription callback:", err);
842
+ }
843
+ }
844
+ }
845
+ }
846
+ this.fireChildEvents(
847
+ subscriptionPath,
848
+ pathSubs,
849
+ relativePath,
850
+ value,
851
+ previousOrder,
852
+ currentOrder,
853
+ previousChildSet,
854
+ currentChildSet,
855
+ afterKey,
856
+ isVolatile,
857
+ serverTimestamp
858
+ );
859
+ }
860
+ /**
861
+ * Generate and fire child_added, child_changed, child_removed events.
862
+ * child_moved is handled separately via handleMoves().
863
+ */
864
+ fireChildEvents(subscriptionPath, pathSubs, relativePath, value, previousOrder, currentOrder, previousChildSet, currentChildSet, afterKey, isVolatile, serverTimestamp) {
865
+ const childAddedSubs = pathSubs.get("child_added") ?? [];
866
+ const childChangedSubs = pathSubs.get("child_changed") ?? [];
867
+ const childRemovedSubs = pathSubs.get("child_removed") ?? [];
868
+ if (childAddedSubs.length === 0 && childChangedSubs.length === 0 && childRemovedSubs.length === 0) {
869
+ return;
870
+ }
871
+ if (relativePath === "/") {
872
+ for (const key of currentOrder) {
873
+ if (!previousChildSet.has(key)) {
874
+ const prevKey = this.getPreviousChildKey(currentOrder, key);
875
+ this.fireChildAdded(subscriptionPath, key, childAddedSubs, prevKey, isVolatile, serverTimestamp);
876
+ }
877
+ }
878
+ for (const key of previousOrder) {
879
+ if (!currentChildSet.has(key)) {
880
+ this.fireChildRemoved(subscriptionPath, key, childRemovedSubs, isVolatile, serverTimestamp);
881
+ }
882
+ }
883
+ } else {
884
+ const segments = relativePath.split("/").filter((s) => s.length > 0);
885
+ if (segments.length === 0) return;
886
+ const childKey = segments[0];
887
+ if (value === null) {
888
+ if (previousChildSet.has(childKey) && !currentChildSet.has(childKey)) {
889
+ this.fireChildRemoved(subscriptionPath, childKey, childRemovedSubs, isVolatile, serverTimestamp);
890
+ }
891
+ } else if (!previousChildSet.has(childKey)) {
892
+ const prevKey = afterKey !== void 0 ? afterKey === "" ? null : afterKey : this.getPreviousChildKey(currentOrder, childKey);
893
+ this.fireChildAdded(subscriptionPath, childKey, childAddedSubs, prevKey, isVolatile, serverTimestamp);
894
+ } else {
895
+ const prevKey = this.getPreviousChildKey(currentOrder, childKey);
896
+ this.fireChildChanged(subscriptionPath, childKey, childChangedSubs, prevKey, isVolatile, serverTimestamp);
897
+ }
898
+ }
899
+ }
900
+ /**
901
+ * Fire child_added callbacks for a child key.
902
+ */
903
+ fireChildAdded(subscriptionPath, childKey, subs, previousChildKey, isVolatile, serverTimestamp) {
904
+ if (subs.length === 0) return;
905
+ const childPath = joinPath(subscriptionPath, childKey);
906
+ const childValue = this.cache.get(childPath).value;
907
+ const snapshot = this.createSnapshot?.(childPath, childValue, isVolatile, serverTimestamp);
908
+ if (snapshot) {
909
+ for (const entry of subs) {
910
+ try {
911
+ entry.callback(snapshot, previousChildKey);
912
+ } catch (err) {
913
+ console.error("Error in child_added subscription callback:", err);
914
+ }
915
+ }
916
+ }
917
+ }
918
+ /**
919
+ * Fire child_changed callbacks for a child key.
920
+ */
921
+ fireChildChanged(subscriptionPath, childKey, subs, previousChildKey, isVolatile, serverTimestamp) {
922
+ if (subs.length === 0) return;
923
+ const childPath = joinPath(subscriptionPath, childKey);
924
+ const childValue = this.cache.get(childPath).value;
925
+ const snapshot = this.createSnapshot?.(childPath, childValue, isVolatile, serverTimestamp);
926
+ if (snapshot) {
927
+ for (const entry of subs) {
928
+ try {
929
+ entry.callback(snapshot, previousChildKey);
930
+ } catch (err) {
931
+ console.error("Error in child_changed subscription callback:", err);
932
+ }
933
+ }
934
+ }
935
+ }
936
+ /**
937
+ * Fire child_removed callbacks for a child key.
938
+ */
939
+ fireChildRemoved(subscriptionPath, childKey, subs, isVolatile, serverTimestamp) {
940
+ if (subs.length === 0) return;
941
+ const childPath = joinPath(subscriptionPath, childKey);
942
+ const snapshot = this.createSnapshot?.(childPath, null, isVolatile, serverTimestamp);
943
+ if (snapshot) {
944
+ for (const entry of subs) {
945
+ try {
946
+ entry.callback(snapshot, void 0);
947
+ } catch (err) {
948
+ console.error("Error in child_removed subscription callback:", err);
949
+ }
950
+ }
951
+ }
952
+ }
953
+ /**
954
+ * Fire child_moved callbacks for a child key.
955
+ */
956
+ fireChildMoved(subscriptionPath, childKey, subs, previousChildKey, isVolatile, serverTimestamp) {
957
+ if (subs.length === 0) return;
958
+ const childPath = joinPath(subscriptionPath, childKey);
959
+ const childValue = this.cache.get(childPath).value;
960
+ const snapshot = this.createSnapshot?.(childPath, childValue, isVolatile, serverTimestamp);
961
+ if (snapshot) {
962
+ for (const entry of subs) {
963
+ try {
964
+ entry.callback(snapshot, previousChildKey);
965
+ } catch (err) {
966
+ console.error("Error in child_moved subscription callback:", err);
967
+ }
577
968
  }
578
969
  }
579
970
  }
@@ -590,6 +981,8 @@ var SubscriptionManager = class {
590
981
  */
591
982
  clear() {
592
983
  this.subscriptions.clear();
984
+ this.orderedChildren.clear();
985
+ this.queryParams.clear();
593
986
  this.cache.clear();
594
987
  }
595
988
  /**
@@ -833,6 +1226,7 @@ function generatePushId() {
833
1226
  }
834
1227
 
835
1228
  // src/DatabaseReference.ts
1229
+ var DEFAULT_MAX_RETRIES = 25;
836
1230
  var DatabaseReference = class _DatabaseReference {
837
1231
  constructor(db, path, query = {}) {
838
1232
  this._db = db;
@@ -886,9 +1280,40 @@ var DatabaseReference = class _DatabaseReference {
886
1280
  }
887
1281
  /**
888
1282
  * Update specific children at this location without overwriting other children.
1283
+ *
1284
+ * Also supports Firebase-style multi-path updates when keys look like paths
1285
+ * (start with '/'). In this mode, each path is written atomically as a transaction.
1286
+ *
1287
+ * @example
1288
+ * ```javascript
1289
+ * // Normal update (merge at single path)
1290
+ * await ref.update({ score: 10, name: 'Riley' });
1291
+ *
1292
+ * // Multi-path update (atomic writes to multiple paths)
1293
+ * await db.ref().update({
1294
+ * '/users/alice/score': 100,
1295
+ * '/users/bob/score': 200,
1296
+ * '/leaderboard/alice': null // null = delete
1297
+ * });
1298
+ * ```
889
1299
  */
890
1300
  async update(values) {
891
- await this._db._sendUpdate(this._path, values);
1301
+ const hasPathKeys = Object.keys(values).some((key) => key.startsWith("/"));
1302
+ if (hasPathKeys) {
1303
+ const ops = [];
1304
+ for (const [key, value] of Object.entries(values)) {
1305
+ const fullPath = key.startsWith("/") ? joinPath(this._path, key) : joinPath(this._path, key);
1306
+ const normalizedPath = normalizePath(fullPath) || "/";
1307
+ if (value === null) {
1308
+ ops.push({ o: "d", p: normalizedPath });
1309
+ } else {
1310
+ ops.push({ o: "s", p: normalizedPath, v: value });
1311
+ }
1312
+ }
1313
+ await this._db._sendTransaction(ops);
1314
+ } else {
1315
+ await this._db._sendUpdate(this._path, values);
1316
+ }
892
1317
  }
893
1318
  /**
894
1319
  * Remove the data at this location.
@@ -928,6 +1353,64 @@ var DatabaseReference = class _DatabaseReference {
928
1353
  const snapshot = await this.once();
929
1354
  await this.setWithPriority(snapshot.val(), priority);
930
1355
  }
1356
+ /**
1357
+ * Atomically modify the data at this location using optimistic concurrency.
1358
+ *
1359
+ * The update function receives the current value and should return the new
1360
+ * value. If the data changed on the server before the write could be
1361
+ * committed, the function is called again with the new value, and the
1362
+ * process repeats until successful or the maximum retries are exceeded.
1363
+ *
1364
+ * @example
1365
+ * ```javascript
1366
+ * // Increment a counter atomically
1367
+ * const result = await ref.transaction(currentValue => {
1368
+ * return (currentValue || 0) + 1;
1369
+ * });
1370
+ * console.log('New value:', result.snapshot.val());
1371
+ * ```
1372
+ *
1373
+ * @param updateFunction - Function that receives current value and returns new value.
1374
+ * Return undefined to abort the transaction.
1375
+ * @param maxRetries - Maximum number of retries (default: 25)
1376
+ * @returns TransactionResult with committed status and final snapshot
1377
+ */
1378
+ async transaction(updateFunction, maxRetries = DEFAULT_MAX_RETRIES) {
1379
+ let retries = 0;
1380
+ while (retries < maxRetries) {
1381
+ const currentSnapshot = await this.once();
1382
+ const currentValue = currentSnapshot.val();
1383
+ const newValue = updateFunction(currentValue);
1384
+ if (newValue === void 0) {
1385
+ return {
1386
+ committed: false,
1387
+ snapshot: currentSnapshot
1388
+ };
1389
+ }
1390
+ const ops = [
1391
+ { o: "c", p: this._path, v: currentValue },
1392
+ { o: "s", p: this._path, v: newValue }
1393
+ ];
1394
+ try {
1395
+ await this._db._sendTransaction(ops);
1396
+ const finalSnapshot = await this.once();
1397
+ return {
1398
+ committed: true,
1399
+ snapshot: finalSnapshot
1400
+ };
1401
+ } catch (error) {
1402
+ if (error instanceof LarkError && error.code === ErrorCode.CONDITION_FAILED) {
1403
+ retries++;
1404
+ continue;
1405
+ }
1406
+ throw error;
1407
+ }
1408
+ }
1409
+ throw new LarkError(
1410
+ "max_retries_exceeded",
1411
+ `Transaction failed after ${maxRetries} retries`
1412
+ );
1413
+ }
931
1414
  // ============================================
932
1415
  // Read Operations
933
1416
  // ============================================
@@ -948,7 +1431,7 @@ var DatabaseReference = class _DatabaseReference {
948
1431
  * Returns an unsubscribe function.
949
1432
  */
950
1433
  on(eventType, callback) {
951
- return this._db._subscribe(this._path, eventType, callback);
1434
+ return this._db._subscribe(this._path, eventType, callback, this._buildQueryParams());
952
1435
  }
953
1436
  /**
954
1437
  * Unsubscribe from events.
@@ -994,10 +1477,8 @@ var DatabaseReference = class _DatabaseReference {
994
1477
  }
995
1478
  /**
996
1479
  * Order results by a child key.
997
- * NOTE: Phase 2 - not yet implemented on server.
998
1480
  */
999
1481
  orderByChild(path) {
1000
- console.warn("orderByChild() is not yet implemented");
1001
1482
  return new _DatabaseReference(this._db, this._path, {
1002
1483
  ...this._query,
1003
1484
  orderBy: "child",
@@ -1006,10 +1487,8 @@ var DatabaseReference = class _DatabaseReference {
1006
1487
  }
1007
1488
  /**
1008
1489
  * Order results by value.
1009
- * NOTE: Phase 2 - not yet implemented on server.
1010
1490
  */
1011
1491
  orderByValue() {
1012
- console.warn("orderByValue() is not yet implemented");
1013
1492
  return new _DatabaseReference(this._db, this._path, {
1014
1493
  ...this._query,
1015
1494
  orderBy: "value"
@@ -1035,10 +1514,8 @@ var DatabaseReference = class _DatabaseReference {
1035
1514
  }
1036
1515
  /**
1037
1516
  * Start at a specific value/key.
1038
- * NOTE: Phase 2 - not yet implemented on server.
1039
1517
  */
1040
1518
  startAt(value, key) {
1041
- console.warn("startAt() is not yet implemented");
1042
1519
  return new _DatabaseReference(this._db, this._path, {
1043
1520
  ...this._query,
1044
1521
  startAt: { value, key }
@@ -1046,10 +1523,8 @@ var DatabaseReference = class _DatabaseReference {
1046
1523
  }
1047
1524
  /**
1048
1525
  * End at a specific value/key.
1049
- * NOTE: Phase 2 - not yet implemented on server.
1050
1526
  */
1051
1527
  endAt(value, key) {
1052
- console.warn("endAt() is not yet implemented");
1053
1528
  return new _DatabaseReference(this._db, this._path, {
1054
1529
  ...this._query,
1055
1530
  endAt: { value, key }
@@ -1057,10 +1532,8 @@ var DatabaseReference = class _DatabaseReference {
1057
1532
  }
1058
1533
  /**
1059
1534
  * Filter to items equal to a specific value.
1060
- * NOTE: Phase 2 - not yet implemented on server.
1061
1535
  */
1062
1536
  equalTo(value, key) {
1063
- console.warn("equalTo() is not yet implemented");
1064
1537
  return new _DatabaseReference(this._db, this._path, {
1065
1538
  ...this._query,
1066
1539
  equalTo: { value, key }
@@ -1076,18 +1549,48 @@ var DatabaseReference = class _DatabaseReference {
1076
1549
  const params = {};
1077
1550
  let hasParams = false;
1078
1551
  if (this._query.orderBy === "key") {
1079
- params.ob = "k";
1552
+ params.orderBy = "key";
1080
1553
  hasParams = true;
1081
1554
  } else if (this._query.orderBy === "priority") {
1082
- params.ob = "p";
1555
+ params.orderBy = "priority";
1556
+ hasParams = true;
1557
+ } else if (this._query.orderBy === "child") {
1558
+ params.orderBy = "child";
1559
+ if (this._query.orderByChildPath) {
1560
+ params.orderByChild = this._query.orderByChildPath;
1561
+ }
1562
+ hasParams = true;
1563
+ } else if (this._query.orderBy === "value") {
1564
+ params.orderBy = "value";
1083
1565
  hasParams = true;
1084
1566
  }
1085
1567
  if (this._query.limitToFirst !== void 0) {
1086
- params.lf = this._query.limitToFirst;
1568
+ params.limitToFirst = this._query.limitToFirst;
1087
1569
  hasParams = true;
1088
1570
  }
1089
1571
  if (this._query.limitToLast !== void 0) {
1090
- params.ll = this._query.limitToLast;
1572
+ params.limitToLast = this._query.limitToLast;
1573
+ hasParams = true;
1574
+ }
1575
+ if (this._query.startAt !== void 0) {
1576
+ params.startAt = this._query.startAt.value;
1577
+ if (this._query.startAt.key !== void 0) {
1578
+ params.startAtKey = this._query.startAt.key;
1579
+ }
1580
+ hasParams = true;
1581
+ }
1582
+ if (this._query.endAt !== void 0) {
1583
+ params.endAt = this._query.endAt.value;
1584
+ if (this._query.endAt.key !== void 0) {
1585
+ params.endAtKey = this._query.endAt.key;
1586
+ }
1587
+ hasParams = true;
1588
+ }
1589
+ if (this._query.equalTo !== void 0) {
1590
+ params.equalTo = this._query.equalTo.value;
1591
+ if (this._query.equalTo.key !== void 0) {
1592
+ params.equalToKey = this._query.equalTo.key;
1593
+ }
1091
1594
  hasParams = true;
1092
1595
  }
1093
1596
  return hasParams ? params : void 0;
@@ -1113,6 +1616,7 @@ var DataSnapshot = class _DataSnapshot {
1113
1616
  this._db = db;
1114
1617
  this._volatile = options.volatile ?? false;
1115
1618
  this._priority = options.priority ?? null;
1619
+ this._serverTimestamp = options.serverTimestamp ?? null;
1116
1620
  }
1117
1621
  /**
1118
1622
  * Get a DatabaseReference for the location of this snapshot.
@@ -1145,7 +1649,8 @@ var DataSnapshot = class _DataSnapshot {
1145
1649
  const childPath = joinPath(this._path, path);
1146
1650
  const childData = getValueAtPath(this._data, path);
1147
1651
  return new _DataSnapshot(childData, childPath, this._db, {
1148
- volatile: this._volatile
1652
+ volatile: this._volatile,
1653
+ serverTimestamp: this._serverTimestamp
1149
1654
  });
1150
1655
  }
1151
1656
  /**
@@ -1201,6 +1706,15 @@ var DataSnapshot = class _DataSnapshot {
1201
1706
  isVolatile() {
1202
1707
  return this._volatile;
1203
1708
  }
1709
+ /**
1710
+ * Get the server timestamp for this snapshot (milliseconds since Unix epoch).
1711
+ * Only present on volatile value events. Use deltas between timestamps for
1712
+ * interpolation rather than absolute times to avoid clock sync issues.
1713
+ * This is a Lark extension not present in Firebase.
1714
+ */
1715
+ getServerTimestamp() {
1716
+ return this._serverTimestamp;
1717
+ }
1204
1718
  /**
1205
1719
  * Export the snapshot data as JSON (alias for val()).
1206
1720
  */
@@ -1242,6 +1756,7 @@ var LarkDatabase = class {
1242
1756
  this.errorCallbacks = /* @__PURE__ */ new Set();
1243
1757
  this.messageQueue = new MessageQueue();
1244
1758
  this.subscriptionManager = new SubscriptionManager();
1759
+ this.pendingWrites = new PendingWriteManager();
1245
1760
  }
1246
1761
  // ============================================
1247
1762
  // Connection State
@@ -1275,6 +1790,26 @@ var LarkDatabase = class {
1275
1790
  get volatilePaths() {
1276
1791
  return this._volatilePaths;
1277
1792
  }
1793
+ /**
1794
+ * Check if there are any pending writes waiting for acknowledgment.
1795
+ * Useful for showing "saving..." indicators in UI.
1796
+ */
1797
+ hasPendingWrites() {
1798
+ return this.pendingWrites.pendingCount > 0;
1799
+ }
1800
+ /**
1801
+ * Get the number of pending writes waiting for acknowledgment.
1802
+ */
1803
+ getPendingWriteCount() {
1804
+ return this.pendingWrites.pendingCount;
1805
+ }
1806
+ /**
1807
+ * Clear all pending writes.
1808
+ * Call this if you don't want to retry writes on reconnect.
1809
+ */
1810
+ clearPendingWrites() {
1811
+ this.pendingWrites.clear();
1812
+ }
1278
1813
  // ============================================
1279
1814
  // Connection Management
1280
1815
  // ============================================
@@ -1384,6 +1919,96 @@ var LarkDatabase = class {
1384
1919
  return new DatabaseReference(this, path);
1385
1920
  }
1386
1921
  // ============================================
1922
+ // Transactions
1923
+ // ============================================
1924
+ /**
1925
+ * Execute an atomic transaction with multiple operations.
1926
+ *
1927
+ * Supports two syntaxes:
1928
+ *
1929
+ * **Object syntax** (like Firebase multi-path update):
1930
+ * ```javascript
1931
+ * await db.transaction({
1932
+ * '/users/alice/name': 'Alice',
1933
+ * '/users/alice/score': 100,
1934
+ * '/temp/data': null // null = delete
1935
+ * });
1936
+ * ```
1937
+ *
1938
+ * **Array syntax** (explicit operations):
1939
+ * ```javascript
1940
+ * await db.transaction([
1941
+ * { op: 'set', path: '/users/alice/name', value: 'Alice' },
1942
+ * { op: 'update', path: '/metadata', value: { lastUpdated: '...' } },
1943
+ * { op: 'delete', path: '/temp/data' },
1944
+ * { op: 'condition', path: '/counter', value: 5 } // CAS check
1945
+ * ]);
1946
+ * ```
1947
+ *
1948
+ * All operations are atomic: either all succeed or all fail.
1949
+ * Conditions are checked first; if any fail, the transaction is rejected
1950
+ * with error code 'condition_failed'.
1951
+ */
1952
+ async transaction(operations) {
1953
+ let txOps;
1954
+ if (Array.isArray(operations)) {
1955
+ txOps = operations.map((op) => this.convertToTxOp(op));
1956
+ } else {
1957
+ txOps = this.convertObjectToTxOps(operations);
1958
+ }
1959
+ await this._sendTransaction(txOps);
1960
+ }
1961
+ /**
1962
+ * Convert a public TransactionOp to wire format TxOperation.
1963
+ */
1964
+ convertToTxOp(op) {
1965
+ const path = normalizePath(op.path) || "/";
1966
+ switch (op.op) {
1967
+ case "set":
1968
+ return { o: "s", p: path, v: op.value };
1969
+ case "update":
1970
+ return { o: "u", p: path, v: op.value };
1971
+ case "delete":
1972
+ return { o: "d", p: path };
1973
+ case "condition":
1974
+ return { o: "c", p: path, v: op.value };
1975
+ default:
1976
+ throw new Error(`Unknown transaction operation: ${op.op}`);
1977
+ }
1978
+ }
1979
+ /**
1980
+ * Convert object syntax to wire format TxOperations.
1981
+ * Each path becomes a set operation, null values become deletes.
1982
+ */
1983
+ convertObjectToTxOps(obj) {
1984
+ const ops = [];
1985
+ for (const [path, value] of Object.entries(obj)) {
1986
+ const normalizedPath = normalizePath(path) || "/";
1987
+ if (value === null) {
1988
+ ops.push({ o: "d", p: normalizedPath });
1989
+ } else {
1990
+ ops.push({ o: "s", p: normalizedPath, v: value });
1991
+ }
1992
+ }
1993
+ return ops;
1994
+ }
1995
+ /**
1996
+ * @internal Send a transaction to the server.
1997
+ */
1998
+ async _sendTransaction(ops) {
1999
+ const requestId = this.messageQueue.nextRequestId();
2000
+ const oid = this.pendingWrites.generateOid();
2001
+ this.pendingWrites.trackWrite(oid, "transaction", "/", ops);
2002
+ const message = {
2003
+ o: "tx",
2004
+ ops,
2005
+ r: requestId,
2006
+ oid
2007
+ };
2008
+ this.send(message);
2009
+ await this.messageQueue.registerRequest(requestId);
2010
+ }
2011
+ // ============================================
1387
2012
  // Connection Events
1388
2013
  // ============================================
1389
2014
  /**
@@ -1425,6 +2050,11 @@ var LarkDatabase = class {
1425
2050
  this.ws?.send(JSON.stringify({ o: "po" }));
1426
2051
  return;
1427
2052
  }
2053
+ if (isAckMessage(message) && message.oid) {
2054
+ this.pendingWrites.onAck(message.oid);
2055
+ } else if (isNackMessage(message) && message.oid) {
2056
+ this.pendingWrites.onNack(message.oid);
2057
+ }
1428
2058
  if (this.messageQueue.handleMessage(message)) {
1429
2059
  return;
1430
2060
  }
@@ -1457,11 +2087,15 @@ var LarkDatabase = class {
1457
2087
  */
1458
2088
  async _sendSet(path, value, priority) {
1459
2089
  const requestId = this.messageQueue.nextRequestId();
2090
+ const oid = this.pendingWrites.generateOid();
2091
+ const normalizedPath = normalizePath(path) || "/";
2092
+ this.pendingWrites.trackWrite(oid, "set", normalizedPath, value);
1460
2093
  const message = {
1461
2094
  o: "s",
1462
- p: normalizePath(path) || "/",
2095
+ p: normalizedPath,
1463
2096
  v: value,
1464
- r: requestId
2097
+ r: requestId,
2098
+ oid
1465
2099
  };
1466
2100
  if (priority !== void 0) {
1467
2101
  message.y = priority;
@@ -1474,11 +2108,15 @@ var LarkDatabase = class {
1474
2108
  */
1475
2109
  async _sendUpdate(path, values) {
1476
2110
  const requestId = this.messageQueue.nextRequestId();
2111
+ const oid = this.pendingWrites.generateOid();
2112
+ const normalizedPath = normalizePath(path) || "/";
2113
+ this.pendingWrites.trackWrite(oid, "update", normalizedPath, values);
1477
2114
  const message = {
1478
2115
  o: "u",
1479
- p: normalizePath(path) || "/",
2116
+ p: normalizedPath,
1480
2117
  v: values,
1481
- r: requestId
2118
+ r: requestId,
2119
+ oid
1482
2120
  };
1483
2121
  this.send(message);
1484
2122
  await this.messageQueue.registerRequest(requestId);
@@ -1488,10 +2126,14 @@ var LarkDatabase = class {
1488
2126
  */
1489
2127
  async _sendDelete(path) {
1490
2128
  const requestId = this.messageQueue.nextRequestId();
2129
+ const oid = this.pendingWrites.generateOid();
2130
+ const normalizedPath = normalizePath(path) || "/";
2131
+ this.pendingWrites.trackWrite(oid, "delete", normalizedPath);
1491
2132
  const message = {
1492
2133
  o: "d",
1493
- p: normalizePath(path) || "/",
1494
- r: requestId
2134
+ p: normalizedPath,
2135
+ r: requestId,
2136
+ oid
1495
2137
  };
1496
2138
  this.send(message);
1497
2139
  await this.messageQueue.registerRequest(requestId);
@@ -1501,11 +2143,15 @@ var LarkDatabase = class {
1501
2143
  */
1502
2144
  async _sendPush(path, value) {
1503
2145
  const requestId = this.messageQueue.nextRequestId();
2146
+ const oid = this.pendingWrites.generateOid();
2147
+ const normalizedPath = normalizePath(path) || "/";
2148
+ this.pendingWrites.trackWrite(oid, "push", normalizedPath, value);
1504
2149
  const message = {
1505
2150
  o: "p",
1506
- p: normalizePath(path) || "/",
2151
+ p: normalizedPath,
1507
2152
  v: value,
1508
- r: requestId
2153
+ r: requestId,
2154
+ oid
1509
2155
  };
1510
2156
  this.send(message);
1511
2157
  const key = await this.messageQueue.registerRequest(requestId);
@@ -1535,11 +2181,10 @@ var LarkDatabase = class {
1535
2181
  const message = {
1536
2182
  o: "o",
1537
2183
  p: normalizedPath,
1538
- r: requestId
2184
+ r: requestId,
2185
+ // Spread query params at top level (not nested in 'q')
2186
+ ...query
1539
2187
  };
1540
- if (query) {
1541
- message.q = query;
1542
- }
1543
2188
  this.send(message);
1544
2189
  const value = await this.messageQueue.registerRequest(requestId);
1545
2190
  return new DataSnapshot(value, path, this);
@@ -1567,13 +2212,14 @@ var LarkDatabase = class {
1567
2212
  /**
1568
2213
  * @internal Send a subscribe message to server.
1569
2214
  */
1570
- async sendSubscribeMessage(path, eventTypes) {
2215
+ async sendSubscribeMessage(path, eventTypes, queryParams) {
1571
2216
  const requestId = this.messageQueue.nextRequestId();
1572
2217
  const message = {
1573
2218
  o: "sb",
1574
2219
  p: normalizePath(path) || "/",
1575
2220
  e: eventTypes,
1576
- r: requestId
2221
+ r: requestId,
2222
+ ...queryParams
1577
2223
  };
1578
2224
  this.send(message);
1579
2225
  await this.messageQueue.registerRequest(requestId);
@@ -1594,8 +2240,11 @@ var LarkDatabase = class {
1594
2240
  /**
1595
2241
  * @internal Create a DataSnapshot from event data.
1596
2242
  */
1597
- createSnapshot(path, value, volatile) {
1598
- return new DataSnapshot(value, path, this, { volatile });
2243
+ createSnapshot(path, value, volatile, serverTimestamp) {
2244
+ return new DataSnapshot(value, path, this, {
2245
+ volatile,
2246
+ serverTimestamp: serverTimestamp ?? null
2247
+ });
1599
2248
  }
1600
2249
  // ============================================
1601
2250
  // Internal: Subscription Management
@@ -1603,8 +2252,8 @@ var LarkDatabase = class {
1603
2252
  /**
1604
2253
  * @internal Subscribe to events at a path.
1605
2254
  */
1606
- _subscribe(path, eventType, callback) {
1607
- return this.subscriptionManager.subscribe(path, eventType, callback);
2255
+ _subscribe(path, eventType, callback, queryParams) {
2256
+ return this.subscriptionManager.subscribe(path, eventType, callback, queryParams);
1608
2257
  }
1609
2258
  /**
1610
2259
  * @internal Unsubscribe from a specific event type at a path.
@@ -1640,6 +2289,7 @@ export {
1640
2289
  LarkDatabase,
1641
2290
  LarkError,
1642
2291
  OnDisconnect,
2292
+ PendingWriteManager,
1643
2293
  generatePushId,
1644
2294
  isVolatilePath
1645
2295
  };