@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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +111 -1
package/package.json
CHANGED
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
|
|
838
|
+
return cloneStrictJson(key) as KeyPart[];
|
|
729
839
|
}
|
|
730
840
|
|
|
731
841
|
function parseKeyString(key: string): KeyPart[] {
|