@loro-dev/flock 4.5.0 → 4.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loro-dev/flock",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "TypeScript bindings for the Flock CRDT with mergeable export/import utilities.",
5
5
  "exports": {
6
6
  ".": {
package/src/index.ts CHANGED
@@ -680,6 +680,104 @@ function normalizeRawEventPayload(
680
680
  }
681
681
 
682
682
  const JSON_SERIALIZATION_ERROR = "Value is not JSON serializable";
683
+ const JSON_SERIALIZATION_WARNING_PREFIX = "[flock] JSON value normalized:";
684
+
685
+ function warnJsonNormalization(message: string): void {
686
+ try {
687
+ console.warn(`${JSON_SERIALIZATION_WARNING_PREFIX} ${message}`);
688
+ } catch {
689
+ // Ignore console failures so value normalization never becomes a write error.
690
+ }
691
+ }
692
+
693
+ function normalizeInvalidJsonValue(message: string): null {
694
+ warnJsonNormalization(message);
695
+ return null;
696
+ }
697
+
698
+ function normalizeJsonValue(
699
+ value: unknown,
700
+ seen: WeakSet<object>,
701
+ inObjectEntry = false,
702
+ ): unknown {
703
+ if (typeof value === "number" && !Number.isFinite(value)) {
704
+ return normalizeInvalidJsonValue("non-finite number stored as null");
705
+ }
706
+ if (value === undefined) {
707
+ return inObjectEntry ? undefined : null;
708
+ }
709
+ if (typeof value === "function" || typeof value === "symbol") {
710
+ warnJsonNormalization(`${typeof value} value omitted`);
711
+ return inObjectEntry ? undefined : null;
712
+ }
713
+ if (typeof value === "bigint") {
714
+ if (
715
+ value <= BigInt(Number.MAX_SAFE_INTEGER) &&
716
+ value >= BigInt(Number.MIN_SAFE_INTEGER)
717
+ ) {
718
+ return Number(value);
719
+ }
720
+ return normalizeInvalidJsonValue(
721
+ "bigint exceeds JavaScript safe integer range and was stored as null",
722
+ );
723
+ }
724
+ if (!value || typeof value !== "object") {
725
+ return value;
726
+ }
727
+
728
+ const toJson = (value as { toJSON?: unknown }).toJSON;
729
+ if (typeof toJson === "function") {
730
+ try {
731
+ return normalizeJsonValue(toJson.call(value), seen, inObjectEntry);
732
+ } catch {
733
+ return normalizeInvalidJsonValue("toJSON threw and value was stored as null");
734
+ }
735
+ }
736
+
737
+ if (seen.has(value)) {
738
+ return normalizeInvalidJsonValue("circular reference stored as null");
739
+ }
740
+ seen.add(value);
741
+ if (Array.isArray(value)) {
742
+ const result = value.map((item) => {
743
+ const normalized = normalizeJsonValue(item, seen);
744
+ return normalized === undefined ? null : normalized;
745
+ });
746
+ seen.delete(value);
747
+ return result;
748
+ }
749
+
750
+ let keys: string[];
751
+ try {
752
+ keys = Object.keys(value);
753
+ } catch {
754
+ seen.delete(value);
755
+ return normalizeInvalidJsonValue("object keys could not be read and value was stored as null");
756
+ }
757
+
758
+ const result: Record<string, unknown> = {};
759
+ for (const key of keys) {
760
+ let rawValue: unknown;
761
+ try {
762
+ rawValue = (value as Record<string, unknown>)[key];
763
+ } catch {
764
+ result[key] = normalizeInvalidJsonValue(
765
+ "object property could not be read and was stored as null",
766
+ );
767
+ continue;
768
+ }
769
+ const normalized = normalizeJsonValue(
770
+ rawValue,
771
+ seen,
772
+ true,
773
+ );
774
+ if (normalized !== undefined) {
775
+ result[key] = normalized;
776
+ }
777
+ }
778
+ seen.delete(value);
779
+ return result;
780
+ }
683
781
 
684
782
  function validateJsonPart(value: unknown): unknown {
685
783
  if (typeof value === "number" && !Number.isFinite(value)) {
@@ -697,6 +795,11 @@ function validateJsonPart(value: unknown): unknown {
697
795
  }
698
796
 
699
797
  function stringifyJson(value: unknown): string {
798
+ const normalized = normalizeJsonValue(value, new WeakSet<object>());
799
+ return JSON.stringify(normalized) ?? "null";
800
+ }
801
+
802
+ function stringifyStrictJson(value: unknown): string {
700
803
  const encoded = JSON.stringify(value, (_key, part) =>
701
804
  validateJsonPart(part),
702
805
  );
@@ -717,6 +820,13 @@ function cloneJson<T>(value: T): T {
717
820
  return JSON.parse(stringifyJson(value)) as T;
718
821
  }
719
822
 
823
+ function cloneStrictJson<T>(value: T): T {
824
+ if (value === undefined) {
825
+ return value;
826
+ }
827
+ return JSON.parse(stringifyStrictJson(value)) as T;
828
+ }
829
+
720
830
  function cloneStoredJson(value: Value | undefined): Value {
721
831
  return value === undefined ? null : cloneJson(value);
722
832
  }
@@ -725,7 +835,7 @@ function cloneKey(key: KeyPart[]): KeyPart[] {
725
835
  if (!Array.isArray(key)) {
726
836
  throw new TypeError("key must be an array");
727
837
  }
728
- return cloneJson(key) as KeyPart[];
838
+ return cloneStrictJson(key) as KeyPart[];
729
839
  }
730
840
 
731
841
  function parseKeyString(key: string): KeyPart[] {