@loro-dev/flock 4.4.5 → 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/README.md +9 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/_moon_flock.ts +1773 -2053
- package/src/index.ts +300 -29
- package/src/moonbit.d.ts +6 -0
package/src/index.ts
CHANGED
|
@@ -258,6 +258,20 @@ function normalizePeerId(peerId?: string): string {
|
|
|
258
258
|
return peerId;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
const SYNC_TXN_CALLBACK_ERROR = "Flock.txn callback must be synchronous";
|
|
262
|
+
|
|
263
|
+
function isAsyncCallback(callback: Function): boolean {
|
|
264
|
+
return callback.constructor?.name === "AsyncFunction";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
268
|
+
return (
|
|
269
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
270
|
+
value !== null &&
|
|
271
|
+
typeof (value as PromiseLike<unknown>).then === "function"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
261
275
|
type EncodableVersionVectorEntry = {
|
|
262
276
|
peer: string;
|
|
263
277
|
peerBytes: Uint8Array;
|
|
@@ -523,6 +537,16 @@ function normalizePruneBefore(
|
|
|
523
537
|
return pruneTombstonesBefore;
|
|
524
538
|
}
|
|
525
539
|
|
|
540
|
+
function normalizeMutationNow(now?: number | null): number | undefined {
|
|
541
|
+
if (now === undefined || now === null) {
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
if (typeof now !== "number" || !Number.isFinite(now)) {
|
|
545
|
+
throw new TypeError("now must be a finite number");
|
|
546
|
+
}
|
|
547
|
+
return now;
|
|
548
|
+
}
|
|
549
|
+
|
|
526
550
|
function decodeVersionVectorFromRaw(raw: unknown): VersionVector {
|
|
527
551
|
if (raw === null || typeof raw !== "object") {
|
|
528
552
|
return {};
|
|
@@ -554,13 +578,34 @@ function decodeVersionVectorFromRaw(raw: unknown): VersionVector {
|
|
|
554
578
|
}
|
|
555
579
|
|
|
556
580
|
function encodeBound(bound?: ScanBound): Record<string, unknown> | undefined {
|
|
557
|
-
if (
|
|
581
|
+
if (bound === undefined) {
|
|
558
582
|
return undefined;
|
|
559
583
|
}
|
|
584
|
+
if (!bound || typeof bound !== "object") {
|
|
585
|
+
throw new TypeError("scan bound must be an object");
|
|
586
|
+
}
|
|
560
587
|
if (bound.kind === "unbounded") {
|
|
561
588
|
return { kind: "unbounded" };
|
|
562
589
|
}
|
|
563
|
-
|
|
590
|
+
if (bound.kind !== "inclusive" && bound.kind !== "exclusive") {
|
|
591
|
+
throw new TypeError(
|
|
592
|
+
"scan bound kind must be inclusive, exclusive, or unbounded",
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
if (!Array.isArray(bound.key)) {
|
|
596
|
+
throw new TypeError("scan bound key must be a key array");
|
|
597
|
+
}
|
|
598
|
+
return { kind: bound.kind, key: cloneKey(bound.key) };
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function normalizeScanPrefix(prefix: unknown): KeyPart[] | undefined {
|
|
602
|
+
if (prefix === undefined) {
|
|
603
|
+
return undefined;
|
|
604
|
+
}
|
|
605
|
+
if (!Array.isArray(prefix)) {
|
|
606
|
+
throw new TypeError("scan prefix must be a key array");
|
|
607
|
+
}
|
|
608
|
+
return cloneKey(prefix as KeyPart[]);
|
|
564
609
|
}
|
|
565
610
|
|
|
566
611
|
function decodeEntryInfo(raw: unknown): EntryInfo | undefined {
|
|
@@ -634,18 +679,163 @@ function normalizeRawEventPayload(
|
|
|
634
679
|
return result;
|
|
635
680
|
}
|
|
636
681
|
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
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
|
+
}
|
|
781
|
+
|
|
782
|
+
function validateJsonPart(value: unknown): unknown {
|
|
783
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
784
|
+
throw new TypeError(JSON_SERIALIZATION_ERROR);
|
|
785
|
+
}
|
|
786
|
+
if (
|
|
787
|
+
value === undefined ||
|
|
788
|
+
typeof value === "function" ||
|
|
789
|
+
typeof value === "symbol" ||
|
|
790
|
+
typeof value === "bigint"
|
|
791
|
+
) {
|
|
792
|
+
throw new TypeError(JSON_SERIALIZATION_ERROR);
|
|
793
|
+
}
|
|
794
|
+
return value;
|
|
795
|
+
}
|
|
796
|
+
|
|
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 {
|
|
803
|
+
const encoded = JSON.stringify(value, (_key, part) =>
|
|
804
|
+
validateJsonPart(part),
|
|
805
|
+
);
|
|
806
|
+
if (encoded === undefined) {
|
|
807
|
+
throw new TypeError(JSON_SERIALIZATION_ERROR);
|
|
808
|
+
}
|
|
809
|
+
return encoded;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function stringifyStoredJson(value: Value | undefined): string {
|
|
813
|
+
return value === undefined ? "null" : stringifyJson(value);
|
|
814
|
+
}
|
|
640
815
|
|
|
641
816
|
function cloneJson<T>(value: T): T {
|
|
642
817
|
if (value === undefined) {
|
|
643
818
|
return value;
|
|
644
819
|
}
|
|
645
|
-
|
|
646
|
-
|
|
820
|
+
return JSON.parse(stringifyJson(value)) as T;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function cloneStrictJson<T>(value: T): T {
|
|
824
|
+
if (value === undefined) {
|
|
825
|
+
return value;
|
|
647
826
|
}
|
|
648
|
-
return JSON.parse(
|
|
827
|
+
return JSON.parse(stringifyStrictJson(value)) as T;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function cloneStoredJson(value: Value | undefined): Value {
|
|
831
|
+
return value === undefined ? null : cloneJson(value);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function cloneKey(key: KeyPart[]): KeyPart[] {
|
|
835
|
+
if (!Array.isArray(key)) {
|
|
836
|
+
throw new TypeError("key must be an array");
|
|
837
|
+
}
|
|
838
|
+
return cloneStrictJson(key) as KeyPart[];
|
|
649
839
|
}
|
|
650
840
|
|
|
651
841
|
function parseKeyString(key: string): KeyPart[] {
|
|
@@ -725,8 +915,11 @@ function createExportPayload(record: ExportRecord): ExportPayload {
|
|
|
725
915
|
return payload;
|
|
726
916
|
}
|
|
727
917
|
|
|
728
|
-
function createPutPayload(
|
|
729
|
-
|
|
918
|
+
function createPutPayload(
|
|
919
|
+
value: Value | undefined,
|
|
920
|
+
metadata?: MetadataMap,
|
|
921
|
+
): ExportPayload {
|
|
922
|
+
const payload: ExportPayload = { data: cloneStoredJson(value) };
|
|
730
923
|
const cleanMetadata = cloneMetadata(metadata);
|
|
731
924
|
if (cleanMetadata !== undefined) {
|
|
732
925
|
payload.metadata = cleanMetadata;
|
|
@@ -804,6 +997,21 @@ function normalizeImportDecision(
|
|
|
804
997
|
return { accept: true };
|
|
805
998
|
}
|
|
806
999
|
|
|
1000
|
+
function payloadFromImportDecision(
|
|
1001
|
+
decision: ImportDecision,
|
|
1002
|
+
): ImportPayload | undefined {
|
|
1003
|
+
if (!decision || typeof decision !== "object") {
|
|
1004
|
+
return undefined;
|
|
1005
|
+
}
|
|
1006
|
+
if ("accept" in decision) {
|
|
1007
|
+
return undefined;
|
|
1008
|
+
}
|
|
1009
|
+
if ("data" in decision || "metadata" in decision) {
|
|
1010
|
+
return decision as ImportPayload;
|
|
1011
|
+
}
|
|
1012
|
+
return undefined;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
807
1015
|
function decodeImportReport(raw: unknown): ImportReport {
|
|
808
1016
|
if (!raw || typeof raw !== "object") {
|
|
809
1017
|
return { accepted: 0, skipped: [] };
|
|
@@ -828,10 +1036,31 @@ function cloneBundle(bundle: ExportBundle): ExportBundle {
|
|
|
828
1036
|
return next;
|
|
829
1037
|
}
|
|
830
1038
|
|
|
1039
|
+
function isVersionVectorEntry(value: unknown): value is VersionVectorEntry {
|
|
1040
|
+
return (
|
|
1041
|
+
typeof value === "object" &&
|
|
1042
|
+
value !== null &&
|
|
1043
|
+
typeof (value as VersionVectorEntry).physicalTime === "number" &&
|
|
1044
|
+
Number.isFinite((value as VersionVectorEntry).physicalTime) &&
|
|
1045
|
+
typeof (value as VersionVectorEntry).logicalCounter === "number" &&
|
|
1046
|
+
Number.isFinite((value as VersionVectorEntry).logicalCounter)
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function isVersionVectorLike(value: unknown): value is VersionVector {
|
|
1051
|
+
return (
|
|
1052
|
+
typeof value === "object" &&
|
|
1053
|
+
value !== null &&
|
|
1054
|
+
Object.keys(value).length > 0 &&
|
|
1055
|
+
Object.values(value).every(isVersionVectorEntry)
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
831
1059
|
function isExportOptions(value: unknown): value is ExportOptions {
|
|
832
1060
|
return (
|
|
833
1061
|
typeof value === "object" &&
|
|
834
1062
|
value !== null &&
|
|
1063
|
+
!isVersionVectorLike(value) &&
|
|
835
1064
|
(Object.prototype.hasOwnProperty.call(value, "hooks") ||
|
|
836
1065
|
Object.prototype.hasOwnProperty.call(value, "from") ||
|
|
837
1066
|
Object.prototype.hasOwnProperty.call(value, "pruneTombstonesBefore") ||
|
|
@@ -894,34 +1123,50 @@ export class Flock {
|
|
|
894
1123
|
|
|
895
1124
|
private putWithMetaInternal(
|
|
896
1125
|
key: KeyPart[],
|
|
897
|
-
value: Value,
|
|
1126
|
+
value: Value | undefined,
|
|
898
1127
|
metadata?: MetadataMap,
|
|
899
1128
|
now?: number,
|
|
1129
|
+
): void {
|
|
1130
|
+
this.putWithMetaPrepared(
|
|
1131
|
+
cloneKey(key),
|
|
1132
|
+
value,
|
|
1133
|
+
metadata,
|
|
1134
|
+
normalizeMutationNow(now),
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
private putWithMetaPrepared(
|
|
1139
|
+
cleanKey: KeyPart[],
|
|
1140
|
+
value: Value | undefined,
|
|
1141
|
+
metadata: MetadataMap | undefined,
|
|
1142
|
+
cleanNow: number | undefined,
|
|
900
1143
|
): void {
|
|
901
1144
|
const metadataClone = cloneMetadata(metadata);
|
|
902
1145
|
put_with_meta_ffi(
|
|
903
1146
|
this.inner,
|
|
904
|
-
|
|
905
|
-
|
|
1147
|
+
cleanKey,
|
|
1148
|
+
stringifyStoredJson(value),
|
|
906
1149
|
metadataClone,
|
|
907
|
-
|
|
1150
|
+
cleanNow,
|
|
908
1151
|
);
|
|
909
1152
|
}
|
|
910
1153
|
|
|
911
1154
|
private async putWithMetaWithHooks(
|
|
912
1155
|
key: KeyPart[],
|
|
913
|
-
value: Value,
|
|
1156
|
+
value: Value | undefined,
|
|
914
1157
|
options: PutWithMetaOptions,
|
|
915
1158
|
): Promise<void> {
|
|
1159
|
+
const cleanKey = cloneKey(key);
|
|
1160
|
+
const cleanNow = normalizeMutationNow(options.now);
|
|
916
1161
|
const basePayload = createPutPayload(value, options.metadata);
|
|
917
1162
|
const transform = options.hooks?.transform;
|
|
918
1163
|
if (!transform) {
|
|
919
|
-
this.
|
|
1164
|
+
this.putWithMetaPrepared(cleanKey, value, options.metadata, cleanNow);
|
|
920
1165
|
return;
|
|
921
1166
|
}
|
|
922
1167
|
const workingPayload = clonePayload(basePayload);
|
|
923
1168
|
const transformed = await transform(
|
|
924
|
-
{ key:
|
|
1169
|
+
{ key: cleanKey.slice(), now: cleanNow },
|
|
925
1170
|
workingPayload,
|
|
926
1171
|
);
|
|
927
1172
|
const finalPayload = mergePayload(
|
|
@@ -932,11 +1177,11 @@ export class Flock {
|
|
|
932
1177
|
if (finalValue === undefined) {
|
|
933
1178
|
throw new TypeError("putWithMeta requires a data value");
|
|
934
1179
|
}
|
|
935
|
-
this.
|
|
936
|
-
|
|
1180
|
+
this.putWithMetaPrepared(
|
|
1181
|
+
cleanKey,
|
|
937
1182
|
finalValue,
|
|
938
1183
|
finalPayload.metadata,
|
|
939
|
-
|
|
1184
|
+
cleanNow,
|
|
940
1185
|
);
|
|
941
1186
|
}
|
|
942
1187
|
|
|
@@ -946,13 +1191,18 @@ export class Flock {
|
|
|
946
1191
|
* @param value
|
|
947
1192
|
* @param now
|
|
948
1193
|
*/
|
|
949
|
-
put(key: KeyPart[], value: Value, now?: number): void {
|
|
950
|
-
put_json_ffi(
|
|
1194
|
+
put(key: KeyPart[], value: Value | undefined, now?: number): void {
|
|
1195
|
+
put_json_ffi(
|
|
1196
|
+
this.inner,
|
|
1197
|
+
cloneKey(key),
|
|
1198
|
+
stringifyStoredJson(value),
|
|
1199
|
+
normalizeMutationNow(now),
|
|
1200
|
+
);
|
|
951
1201
|
}
|
|
952
1202
|
|
|
953
1203
|
putWithMeta(
|
|
954
1204
|
key: KeyPart[],
|
|
955
|
-
value: Value,
|
|
1205
|
+
value: Value | undefined,
|
|
956
1206
|
options?: PutWithMetaOptions,
|
|
957
1207
|
): void | Promise<void> {
|
|
958
1208
|
const opts = options ?? {};
|
|
@@ -962,7 +1212,7 @@ export class Flock {
|
|
|
962
1212
|
this.putWithMetaInternal(key, value, opts.metadata, opts.now);
|
|
963
1213
|
}
|
|
964
1214
|
|
|
965
|
-
set(key: KeyPart[], value: Value, now?: number): void {
|
|
1215
|
+
set(key: KeyPart[], value: Value | undefined, now?: number): void {
|
|
966
1216
|
this.put(key, value, now);
|
|
967
1217
|
}
|
|
968
1218
|
|
|
@@ -972,7 +1222,7 @@ export class Flock {
|
|
|
972
1222
|
* @param now
|
|
973
1223
|
*/
|
|
974
1224
|
delete(key: KeyPart[], now?: number): void {
|
|
975
|
-
delete_ffi(this.inner, key, now);
|
|
1225
|
+
delete_ffi(this.inner, cloneKey(key), normalizeMutationNow(now));
|
|
976
1226
|
}
|
|
977
1227
|
|
|
978
1228
|
get(key: KeyPart[]): Value | undefined {
|
|
@@ -1119,7 +1369,10 @@ export class Flock {
|
|
|
1119
1369
|
delete working.entries[key];
|
|
1120
1370
|
continue;
|
|
1121
1371
|
}
|
|
1122
|
-
working.entries[key] = buildRecord(
|
|
1372
|
+
working.entries[key] = buildRecord(
|
|
1373
|
+
record.c,
|
|
1374
|
+
mergePayload(basePayload, payloadFromImportDecision(decision)),
|
|
1375
|
+
);
|
|
1123
1376
|
}
|
|
1124
1377
|
}
|
|
1125
1378
|
const coreReport = this.importJsonInternal(working);
|
|
@@ -1176,18 +1429,26 @@ export class Flock {
|
|
|
1176
1429
|
}
|
|
1177
1430
|
|
|
1178
1431
|
putMvr(key: KeyPart[], value: Value, now?: number): void {
|
|
1179
|
-
put_mvr_ffi(
|
|
1432
|
+
put_mvr_ffi(
|
|
1433
|
+
this.inner,
|
|
1434
|
+
cloneKey(key),
|
|
1435
|
+
stringifyJson(value),
|
|
1436
|
+
normalizeMutationNow(now),
|
|
1437
|
+
);
|
|
1180
1438
|
}
|
|
1181
1439
|
|
|
1182
1440
|
getMvr(key: KeyPart[]): Value[] {
|
|
1183
|
-
const raw = get_mvr_ffi(this.inner, key);
|
|
1441
|
+
const raw = get_mvr_ffi(this.inner, cloneKey(key));
|
|
1184
1442
|
return Array.isArray(raw) ? (raw as Value[]) : [];
|
|
1185
1443
|
}
|
|
1186
1444
|
|
|
1187
1445
|
scan(options: ScanOptions = {}): ScanRow[] {
|
|
1446
|
+
if (!options || typeof options !== "object") {
|
|
1447
|
+
throw new TypeError("scan options must be an object");
|
|
1448
|
+
}
|
|
1188
1449
|
const start = encodeBound(options.start);
|
|
1189
1450
|
const end = encodeBound(options.end);
|
|
1190
|
-
const prefix = options.prefix
|
|
1451
|
+
const prefix = normalizeScanPrefix(options.prefix);
|
|
1191
1452
|
const rows = scan_ffi(this.inner, start, end, prefix) as
|
|
1192
1453
|
| RawScanRow[]
|
|
1193
1454
|
| undefined;
|
|
@@ -1337,9 +1598,19 @@ export class Flock {
|
|
|
1337
1598
|
"Cannot start transaction while autoDebounceCommit is active",
|
|
1338
1599
|
);
|
|
1339
1600
|
}
|
|
1601
|
+
if (isAsyncCallback(callback)) {
|
|
1602
|
+
throw new TypeError(SYNC_TXN_CALLBACK_ERROR);
|
|
1603
|
+
}
|
|
1340
1604
|
txn_begin_ffi(this.inner);
|
|
1341
1605
|
try {
|
|
1342
1606
|
const result = callback();
|
|
1607
|
+
if (isThenable(result)) {
|
|
1608
|
+
void Promise.resolve(result).catch(() => {});
|
|
1609
|
+
if (is_in_txn_ffi(this.inner)) {
|
|
1610
|
+
txn_rollback_ffi(this.inner);
|
|
1611
|
+
}
|
|
1612
|
+
throw new TypeError(SYNC_TXN_CALLBACK_ERROR);
|
|
1613
|
+
}
|
|
1343
1614
|
txn_commit_ffi(this.inner);
|
|
1344
1615
|
return result;
|
|
1345
1616
|
} catch (e) {
|
package/src/moonbit.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export type Int64 = bigint & { [__brand]: "Int64" };
|
|
|
47
47
|
*/
|
|
48
48
|
export type UInt64 = bigint & { [__brand]: "UInt64" };
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Backed directly by `bigint`.
|
|
52
|
+
* Valid `V128` values are in the unsigned 128-bit range `[0, 2^128)`.
|
|
53
|
+
*/
|
|
54
|
+
export type V128 = bigint & { [__brand]: "V128" };
|
|
55
|
+
|
|
50
56
|
export type String = string;
|
|
51
57
|
|
|
52
58
|
export type Bytes = Uint8Array;
|