From f356569a4b20dbad8ba772e004f73b1e88a0803e Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 22 Oct 2026 16:01:37 +0000 Subject: [PATCH] chore: prepare React Native bindings 1.17.1 --- .github/workflows/release.yml | 88 ++++++++++ README.md | 13 +- loro-rs/Cargo.lock | 22 +-- loro-rs/Cargo.toml | 4 +- package.json | 8 +- scripts/verify-release.py | 40 +++++ src/NativeLoroReactNative.ts | 2 +- src/generated/loro-ffi.ts | 19 +++ src/generated/loro.ts | 299 +++++++++++++++++++++++++++++++++- src/index.tsx | 1 - 10 files changed, 470 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 scripts/verify-release.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..562ddea --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,88 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: read + +jobs: + native: + strategy: + fail-fast: false + matrix: + include: + - platform: android + runner: ubuntu-latest + - platform: ios + runner: macos-15 + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-js + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + workspaces: loro-rs + - name: Install Android targets + if: matrix.platform == 'android' + run: | + cargo install cargo-ndk --locked + rustup target add aarch64-linux-android armv7-linux-androideabi + - name: Install iOS targets + if: matrix.platform == 'ios' + run: rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios + - name: Build native library and regenerate bindings + run: | + corepack yarn ubrn build ${{ matrix.platform }} --release --config ubrn.config.yaml --and-generate + bash scripts/add-extension.sh + - name: Collect Android files + if: matrix.platform == 'android' + run: tar -czf native-android.tgz android + - name: Collect iOS and generated binding files + if: matrix.platform == 'ios' + run: tar -czf native-ios.tgz build ios cpp src *.podspec + - uses: actions/upload-artifact@v4 + with: + name: native-${{ matrix.platform }} + path: native-${{ matrix.platform }}.tgz + if-no-files-found: error + compression-level: 0 + + publish: + needs: native + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup-js + - uses: actions/download-artifact@v4 + with: + pattern: native-* + merge-multiple: true + - name: Assemble and check package + run: | + tar -xzf native-android.tgz + tar -xzf native-ios.tgz + corepack yarn typecheck + corepack yarn prepare + npm pack --ignore-scripts --json > pack.json + python3 scripts/verify-release.py "$(node -p "require('./pack.json')[0].filename")" "$GITHUB_REF_NAME" + - uses: actions/upload-artifact@v4 + with: + name: npm-package + path: loro-react-native-*.tgz + if-no-files-found: error + - uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + - name: Publish verified tarball + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + npm install --global npm@11 + npm publish "$(node -p "require('./pack.json')[0].filename")" --access public --provenance diff --git a/README.md b/README.md index 4460673..17cf759 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,18 @@ You can find an editor demo [here](https://github.com/Leeeon233/loro-react-nativ ## Dev +### Release 1.17.1 + +Uses Loro core and FFI 1.17.1, including `exportPendingUpdates()` for saving +received updates waiting for dependencies. Save these bytes with a consistent +snapshot and restore the snapshot first. Clear older saved pending bytes when +the method returns `undefined`. + +Version tags run `.github/workflows/release.yml`: build release libraries for +iOS and Android, regenerate bindings, check TypeScript, and verify the npm +tarball contains both platforms before publishing. npm publishing requires a +trusted publisher for that workflow or the repository's `NPM_TOKEN` secret. + Follow the [getting started](https://jhugman.github.io/uniffi-bindgen-react-native/guides/pre-installation.html) to set up the environment. ### IOS @@ -64,4 +76,3 @@ MIT --- Made with [uniffi-bindgen-react-native](https://github.com/jhugman/uniffi-bindgen-react-native) - diff --git a/loro-rs/Cargo.lock b/loro-rs/Cargo.lock index 88c70d9..864d213 100644 --- a/loro-rs/Cargo.lock +++ b/loro-rs/Cargo.lock @@ -726,9 +726,9 @@ dependencies = [ [[package]] name = "loro" -version = "1.13.7" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec673c3332ede11cc58d5d88de55a01ba17cf28b0bd0feb66ca1c66fb757ad" +checksum = "410c939fe3bee34b8ed61f7aea93201fb136629f685ffa557039c4d9952f9988" dependencies = [ "enum-as-inner 0.6.1", "generic-btree", @@ -742,9 +742,9 @@ dependencies = [ [[package]] name = "loro-common" -version = "1.13.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b51042936156a1d537df8a38938ec8ed24b45de14530af933c1d02de39c9e056" +checksum = "bd3f7afbafc4db85aefe9620e7e99fa49c974477563904f226ae96fc9674eade" dependencies = [ "arbitrary", "enum-as-inner 0.6.1", @@ -772,9 +772,9 @@ dependencies = [ [[package]] name = "loro-ffi" -version = "1.13.7" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2739c8e12e40732ad2e47ae3a8799cd5c2301dba7ba0bd05a0aa33af9b6685c2" +checksum = "4ce775fdd5df81cdb6e362c11b8176482acac4d9ca3078be8fc97e01d147a93c" dependencies = [ "loro", "serde_json", @@ -783,9 +783,9 @@ dependencies = [ [[package]] name = "loro-internal" -version = "1.13.7" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b635801310e2047f74be15f58988f2f78e7874e77aa08643fe9e6eec356f6848" +checksum = "7e533c1688430aa4fcdd933f00861e331c169a4158ee6d27d054e0558892d447" dependencies = [ "append-only-bytes", "arref", @@ -830,9 +830,9 @@ dependencies = [ [[package]] name = "loro-kv-store" -version = "1.13.7" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b8d6d2c95abf67d7f50a60171759d693206796d7f8f03f3fe68e531c88794" +checksum = "c7331ebe6902effeb0c3d52aba9e30fb0118bc926faed6f3848487aab40c1a4e" dependencies = [ "bytes", "ensure-cov", @@ -858,7 +858,7 @@ dependencies = [ [[package]] name = "loro-rs" -version = "1.13.2" +version = "1.17.1" dependencies = [ "loro-ffi", "uniffi", diff --git a/loro-rs/Cargo.toml b/loro-rs/Cargo.toml index 046ca57..f56dca4 100644 --- a/loro-rs/Cargo.toml +++ b/loro-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "loro-rs" -version = "1.13.2" +version = "1.17.1" edition = "2021" [lib] @@ -11,7 +11,7 @@ name = "uniffi-bindgen" path = "src/uniffi-bindgen.rs" [dependencies] -loro-ffi = { version = "1.13.7" } +loro-ffi = { version = "1.17.1" } uniffi = { version = "0.31.1" } [build-dependencies] diff --git a/package.json b/package.json index 0c0a484..43e2d49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loro-react-native", - "version": "1.13.2", + "version": "1.17.1", "description": "React native binding for Loro", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", @@ -54,14 +54,14 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/Leeeon233/loro-react-native.git" + "url": "git+https://github.com/loro-dev/loro-react-native.git" }, "author": "Leon Zhao (https://github.com/Leeeon233)", "license": "MIT", "bugs": { - "url": "https://github.com/Leeeon233/loro-react-native/issues" + "url": "https://github.com/loro-dev/loro-react-native/issues" }, - "homepage": "https://github.com/Leeeon233/loro-react-native#readme", + "homepage": "https://github.com/loro-dev/loro-react-native#readme", "publishConfig": { "registry": "https://registry.npmjs.org/" }, diff --git a/scripts/verify-release.py b/scripts/verify-release.py new file mode 100644 index 0000000..f672a8a --- /dev/null +++ b/scripts/verify-release.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Reject a release tarball missing either platform's native libraries.""" + +import json +from pathlib import Path +import sys +import tarfile +import tomllib + + +def main(): + tarball, tag = sys.argv[1:] + with tarfile.open(tarball, "r:gz") as archive: + names = set(archive.getnames()) + package = json.load(archive.extractfile("package/package.json")) + assert tag == "v" + package["version"], "tag and package version differ" + required = { + "package/build/LoroFfiFramework.xcframework/Info.plist", + "package/android/src/main/jniLibs/arm64-v8a/libloro_rs.a", + "package/android/src/main/jniLibs/armeabi-v7a/libloro_rs.a", + "package/cpp/generated/loro.cpp", + "package/cpp/generated/loro.hpp", + "package/src/generated/loro.ts", + "package/LoroReactNative.podspec", + "package/" + package["main"].removeprefix("./"), + "package/" + package["types"].removeprefix("./"), + } + missing = required - names + assert not missing, "missing release files: " + ", ".join(sorted(missing)) + bindings = archive.extractfile("package/src/generated/loro.ts").read() + assert b"exportPendingUpdates(" in bindings, "pending-update API missing" + lock = tomllib.loads(Path("loro-rs/Cargo.lock").read_text()) + for name in ("loro", "loro-ffi", "loro-internal"): + versions = {p["version"] for p in lock["package"] if p["name"] == name} + assert versions == {package["version"]}, f"unexpected {name} versions: {versions}" + print(f"Verified {package['name']} {package['version']} with iOS and Android libraries.") + + +if __name__ == "__main__": + main() diff --git a/src/NativeLoroReactNative.ts b/src/NativeLoroReactNative.ts index 46b212e..c8df76e 100644 --- a/src/NativeLoroReactNative.ts +++ b/src/NativeLoroReactNative.ts @@ -7,4 +7,4 @@ export interface Spec extends TurboModule { cleanupRustCrate(): boolean; } -export default TurboModuleRegistry.getEnforcing('LoroReactNative'); \ No newline at end of file +export default TurboModuleRegistry.getEnforcing('LoroReactNative'); diff --git a/src/generated/loro-ffi.ts b/src/generated/loro-ffi.ts index 86b3f6a..b1c7ef7 100644 --- a/src/generated/loro-ffi.ts +++ b/src/generated/loro-ffi.ts @@ -562,6 +562,10 @@ interface NativeModuleInterface { endVv: bigint, uniffi_out_err: UniffiRustCallStatus ): Uint8Array; + ubrn_uniffi_loro_ffi_fn_method_lorodoc_export_pending_updates( + ptr: bigint, + uniffi_out_err: UniffiRustCallStatus + ): Uint8Array; ubrn_uniffi_loro_ffi_fn_method_lorodoc_export_shallow_snapshot( ptr: bigint, frontiers: bigint, @@ -870,6 +874,18 @@ interface NativeModuleInterface { f: bigint, uniffi_out_err: UniffiRustCallStatus ): void; + ubrn_uniffi_loro_ffi_fn_method_lorodoc_try_export_json_updates( + ptr: bigint, + startVv: bigint, + endVv: bigint, + uniffi_out_err: UniffiRustCallStatus + ): Uint8Array; + ubrn_uniffi_loro_ffi_fn_method_lorodoc_try_export_json_updates_without_peer_compression( + ptr: bigint, + startVv: bigint, + endVv: bigint, + uniffi_out_err: UniffiRustCallStatus + ): Uint8Array; ubrn_uniffi_loro_ffi_fn_method_lorodoc_try_get_counter( ptr: bigint, id: bigint, @@ -2315,6 +2331,7 @@ interface NativeModuleInterface { ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_json_in_id_span(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_json_updates(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_json_updates_without_peer_compression(): number; + ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_pending_updates(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_shallow_snapshot(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_snapshot(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_snapshot_at(): number; @@ -2379,6 +2396,8 @@ interface NativeModuleInterface { ubrn_uniffi_loro_ffi_checksum_method_lorodoc_subscribe_pre_commit(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_subscribe_root(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_travel_change_ancestors(): number; + ubrn_uniffi_loro_ffi_checksum_method_lorodoc_try_export_json_updates(): number; + ubrn_uniffi_loro_ffi_checksum_method_lorodoc_try_export_json_updates_without_peer_compression(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_try_get_counter(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_try_get_list(): number; ubrn_uniffi_loro_ffi_checksum_method_lorodoc_try_get_map(): number; diff --git a/src/generated/loro.ts b/src/generated/loro.ts index bff4458..df1837b 100644 --- a/src/generated/loro.ts +++ b/src/generated/loro.ts @@ -1674,6 +1674,7 @@ export enum CannotFindRelativePosition_Tags { ContainerDeleted = 'ContainerDeleted', HistoryCleared = 'HistoryCleared', IdNotFound = 'IdNotFound', + HistoryUnreadable = 'HistoryUnreadable', } export const CannotFindRelativePosition = (() => { class ContainerDeleted extends UniffiError { @@ -1742,6 +1743,28 @@ export const CannotFindRelativePosition = (() => { return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 3; } } + class HistoryUnreadable extends UniffiError { + /** + * @private + * This field is private and should not be used. + */ + readonly [uniffiTypeNameSymbol]: string = 'CannotFindRelativePosition'; + /** + * @private + * This field is private and should not be used. + */ + readonly [variantOrdinalSymbol] = 4; + + readonly tag = CannotFindRelativePosition_Tags.HistoryUnreadable; + + constructor(message: string) { + super('CannotFindRelativePosition', 'HistoryUnreadable', message); + } + + static instanceOf(e: any): e is HistoryUnreadable { + return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 4; + } + } // Utility function which does not rely on instanceof. function instanceOf(e: any): e is CannotFindRelativePosition { @@ -1751,6 +1774,7 @@ export const CannotFindRelativePosition = (() => { ContainerDeleted, HistoryCleared, IdNotFound, + HistoryUnreadable, instanceOf, }; })(); @@ -1785,6 +1809,11 @@ const FfiConverterTypeCannotFindRelativePosition = (() => { FfiConverterString.read(from) ); + case 4: + return new CannotFindRelativePosition.HistoryUnreadable( + FfiConverterString.read(from) + ); + default: throw new UniffiInternalError.UnexpectedEnumCase(); } @@ -1805,6 +1834,7 @@ const FfiConverterTypeCannotFindRelativePosition = (() => { export enum ChangeTravelError_Tags { TargetIdNotFound = 'TargetIdNotFound', TargetVersionNotIncluded = 'TargetVersionNotIncluded', + HistoryUnreadable = 'HistoryUnreadable', } export const ChangeTravelError = (() => { class TargetIdNotFound extends UniffiError { @@ -1851,6 +1881,28 @@ export const ChangeTravelError = (() => { return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 2; } } + class HistoryUnreadable extends UniffiError { + /** + * @private + * This field is private and should not be used. + */ + readonly [uniffiTypeNameSymbol]: string = 'ChangeTravelError'; + /** + * @private + * This field is private and should not be used. + */ + readonly [variantOrdinalSymbol] = 3; + + readonly tag = ChangeTravelError_Tags.HistoryUnreadable; + + constructor(message: string) { + super('ChangeTravelError', 'HistoryUnreadable', message); + } + + static instanceOf(e: any): e is HistoryUnreadable { + return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 3; + } + } // Utility function which does not rely on instanceof. function instanceOf(e: any): e is ChangeTravelError { @@ -1859,6 +1911,7 @@ export const ChangeTravelError = (() => { return { TargetIdNotFound, TargetVersionNotIncluded, + HistoryUnreadable, instanceOf, }; })(); @@ -1885,6 +1938,11 @@ const FfiConverterTypeChangeTravelError = (() => { FfiConverterString.read(from) ); + case 3: + return new ChangeTravelError.HistoryUnreadable( + FfiConverterString.read(from) + ); + default: throw new UniffiInternalError.UnexpectedEnumCase(); } @@ -3592,6 +3650,8 @@ export enum LoroEncodeError_Tags { FrontiersNotFound = 'FrontiersNotFound', ShallowSnapshotIncompatibleWithOldFormat = 'ShallowSnapshotIncompatibleWithOldFormat', UnknownContainer = 'UnknownContainer', + InternalError = 'InternalError', + DecodeError = 'DecodeError', } export const LoroEncodeError = (() => { class FrontiersNotFound extends UniffiError { @@ -3665,6 +3725,50 @@ export const LoroEncodeError = (() => { return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 3; } } + class InternalError extends UniffiError { + /** + * @private + * This field is private and should not be used. + */ + readonly [uniffiTypeNameSymbol]: string = 'LoroEncodeError'; + /** + * @private + * This field is private and should not be used. + */ + readonly [variantOrdinalSymbol] = 4; + + readonly tag = LoroEncodeError_Tags.InternalError; + + constructor(message: string) { + super('LoroEncodeError', 'InternalError', message); + } + + static instanceOf(e: any): e is InternalError { + return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 4; + } + } + class DecodeError extends UniffiError { + /** + * @private + * This field is private and should not be used. + */ + readonly [uniffiTypeNameSymbol]: string = 'LoroEncodeError'; + /** + * @private + * This field is private and should not be used. + */ + readonly [variantOrdinalSymbol] = 5; + + readonly tag = LoroEncodeError_Tags.DecodeError; + + constructor(message: string) { + super('LoroEncodeError', 'DecodeError', message); + } + + static instanceOf(e: any): e is DecodeError { + return instanceOf(e) && (e as any)[variantOrdinalSymbol] === 5; + } + } // Utility function which does not rely on instanceof. function instanceOf(e: any): e is LoroEncodeError { @@ -3674,6 +3778,8 @@ export const LoroEncodeError = (() => { FrontiersNotFound, ShallowSnapshotIncompatibleWithOldFormat, UnknownContainer, + InternalError, + DecodeError, instanceOf, }; })(); @@ -3705,6 +3811,14 @@ const FfiConverterTypeLoroEncodeError = (() => { FfiConverterString.read(from) ); + case 4: + return new LoroEncodeError.InternalError( + FfiConverterString.read(from) + ); + + case 5: + return new LoroEncodeError.DecodeError(FfiConverterString.read(from)); + default: throw new UniffiInternalError.UnexpectedEnumCase(); } @@ -9444,9 +9558,37 @@ export interface LoroDocLike { startVv: VersionVectorLike, endVv: VersionVectorLike ): string; + /** + * Export imported remote updates waiting for missing causal dependencies. + * + * These are received remote operations, not uncommitted local transactions. + * Returns native nil/None when none remain pending, not empty bytes, or bytes + * in the ordinary updates format. The bytes preserve the pending operations, + * not necessarily the original blob. + * + * This is read-only: it does not consume pending updates, commit local edits, + * emit events, or advance the applied version vector/frontiers. Ordinary + * snapshot/update exports exclude these updates; snapshot export still + * commits local edits as usual. + * + * Prevent mutations/imports between exporting a snapshot and these bytes: + * the calls are not an atomic pair. Save or replace both as one consistent + * checkpoint, only after both exports succeed. nil/None must clear any older + * pending record; an export error must not replace the previous checkpoint. + * Encoding failures, including `LoroEncodeError.InternalError`, are thrown + * with the core's error message rather than returned as absent bytes. + * + * Restore the snapshot first, then pass any pending bytes to ordinary `import`. + * They remain pending until the real missing dependencies arrive. This API + * does not fetch or invent those dependencies, establish that the document + * is synced, or make advancing a transport cursor or compaction safe. + */ + exportPendingUpdates() /*throws*/ : ArrayBuffer | undefined; exportShallowSnapshot(frontiers: FrontiersLike) /*throws*/ : ArrayBuffer; /** * Export the current state and history of the document. + * Remote updates waiting for missing dependencies are excluded; save them + * separately with `export_pending_updates`. This export commits local edits. */ exportSnapshot() /*throws*/ : ArrayBuffer; exportSnapshotAt(frontiers: FrontiersLike) /*throws*/ : ArrayBuffer; @@ -9454,7 +9596,9 @@ export interface LoroDocLike { frontiers: FrontiersLike | undefined ) /*throws*/ : ArrayBuffer; /** - * Export all the ops not included in the given `VersionVector` + * Export all applied ops not included in the given `VersionVector`. + * Remote updates waiting for missing dependencies are excluded; save them + * separately with `export_pending_updates`. */ exportUpdates(vv: VersionVectorLike) /*throws*/ : ArrayBuffer; exportUpdatesInRange(spans: Array) /*throws*/ : ArrayBuffer; @@ -9653,7 +9797,8 @@ export interface LoroDocLike { hasContainer(id: ContainerId): boolean; hasHistoryCache(): boolean; /** - * Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`]. + * Import a snapshot, ordinary updates, or bytes from `export_pending_updates`. + * Pending updates still wait for their actual missing causal dependencies. */ import_(bytes: ArrayBuffer) /*throws*/ : ImportStatus; /** @@ -9664,7 +9809,8 @@ export interface LoroDocLike { importBatch(bytes: Array) /*throws*/ : ImportStatus; importJsonUpdates(json: string) /*throws*/ : ImportStatus; /** - * Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`]. + * Import a snapshot, ordinary updates, or bytes from `export_pending_updates`. + * Pending updates still wait for their actual missing causal dependencies. * * It marks the import with a custom `origin` string. It can be used to track the import source * in the generated events. @@ -9902,6 +10048,20 @@ export interface LoroDocLike { ids: Array, f: ChangeAncestorsTraveler ) /*throws*/ : void; + /** + * Export JSON updates, reporting unreadable history as LoroError. + */ + tryExportJsonUpdates( + startVv: VersionVectorLike, + endVv: VersionVectorLike + ) /*throws*/ : string; + /** + * Export JSON updates without peer compression, reporting unreadable history as LoroError. + */ + tryExportJsonUpdatesWithoutPeerCompression( + startVv: VersionVectorLike, + endVv: VersionVectorLike + ) /*throws*/ : string; /** * Try to get a [LoroCounter] by container id. * @@ -10388,6 +10548,48 @@ export class LoroDoc extends UniffiAbstractObject implements LoroDocLike { ); } + /** + * Export imported remote updates waiting for missing causal dependencies. + * + * These are received remote operations, not uncommitted local transactions. + * Returns native nil/None when none remain pending, not empty bytes, or bytes + * in the ordinary updates format. The bytes preserve the pending operations, + * not necessarily the original blob. + * + * This is read-only: it does not consume pending updates, commit local edits, + * emit events, or advance the applied version vector/frontiers. Ordinary + * snapshot/update exports exclude these updates; snapshot export still + * commits local edits as usual. + * + * Prevent mutations/imports between exporting a snapshot and these bytes: + * the calls are not an atomic pair. Save or replace both as one consistent + * checkpoint, only after both exports succeed. nil/None must clear any older + * pending record; an export error must not replace the previous checkpoint. + * Encoding failures, including `LoroEncodeError.InternalError`, are thrown + * with the core's error message rather than returned as absent bytes. + * + * Restore the snapshot first, then pass any pending bytes to ordinary `import`. + * They remain pending until the real missing dependencies arrive. This API + * does not fetch or invent those dependencies, establish that the document + * is synced, or make advancing a transport cursor or compaction safe. + */ + exportPendingUpdates(): ArrayBuffer | undefined /*throws*/ { + return FfiConverterOptionalArrayBuffer.lift( + uniffiCaller.rustCallWithError( + /*liftError:*/ FfiConverterTypeLoroEncodeError.lift.bind( + FfiConverterTypeLoroEncodeError + ), + /*caller:*/ (callStatus) => { + return nativeModule().ubrn_uniffi_loro_ffi_fn_method_lorodoc_export_pending_updates( + uniffiTypeLoroDocObjectFactory.clonePointer(this), + callStatus + ); + }, + /*liftString:*/ FfiConverterString.lift + ) + ); + } + exportShallowSnapshot(frontiers: FrontiersLike): ArrayBuffer /*throws*/ { return FfiConverterArrayBuffer.lift( uniffiCaller.rustCallWithError( @@ -10408,6 +10610,8 @@ export class LoroDoc extends UniffiAbstractObject implements LoroDocLike { /** * Export the current state and history of the document. + * Remote updates waiting for missing dependencies are excluded; save them + * separately with `export_pending_updates`. This export commits local edits. */ exportSnapshot(): ArrayBuffer /*throws*/ { return FfiConverterArrayBuffer.lift( @@ -10465,7 +10669,9 @@ export class LoroDoc extends UniffiAbstractObject implements LoroDocLike { } /** - * Export all the ops not included in the given `VersionVector` + * Export all applied ops not included in the given `VersionVector`. + * Remote updates waiting for missing dependencies are excluded; save them + * separately with `export_pending_updates`. */ exportUpdates(vv: VersionVectorLike): ArrayBuffer /*throws*/ { return FfiConverterArrayBuffer.lift( @@ -11047,7 +11253,8 @@ export class LoroDoc extends UniffiAbstractObject implements LoroDocLike { } /** - * Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`]. + * Import a snapshot, ordinary updates, or bytes from `export_pending_updates`. + * Pending updates still wait for their actual missing causal dependencies. */ import_(bytes: ArrayBuffer): ImportStatus /*throws*/ { return FfiConverterTypeImportStatus.lift( @@ -11109,7 +11316,8 @@ export class LoroDoc extends UniffiAbstractObject implements LoroDocLike { } /** - * Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`]. + * Import a snapshot, ordinary updates, or bytes from `export_pending_updates`. + * Pending updates still wait for their actual missing causal dependencies. * * It marks the import with a custom `origin` string. It can be used to track the import source * in the generated events. @@ -11766,6 +11974,56 @@ export class LoroDoc extends UniffiAbstractObject implements LoroDocLike { ); } + /** + * Export JSON updates, reporting unreadable history as LoroError. + */ + tryExportJsonUpdates( + startVv: VersionVectorLike, + endVv: VersionVectorLike + ): string /*throws*/ { + return FfiConverterString.lift( + uniffiCaller.rustCallWithError( + /*liftError:*/ FfiConverterTypeLoroError.lift.bind( + FfiConverterTypeLoroError + ), + /*caller:*/ (callStatus) => { + return nativeModule().ubrn_uniffi_loro_ffi_fn_method_lorodoc_try_export_json_updates( + uniffiTypeLoroDocObjectFactory.clonePointer(this), + FfiConverterTypeVersionVector.lower(startVv), + FfiConverterTypeVersionVector.lower(endVv), + callStatus + ); + }, + /*liftString:*/ FfiConverterString.lift + ) + ); + } + + /** + * Export JSON updates without peer compression, reporting unreadable history as LoroError. + */ + tryExportJsonUpdatesWithoutPeerCompression( + startVv: VersionVectorLike, + endVv: VersionVectorLike + ): string /*throws*/ { + return FfiConverterString.lift( + uniffiCaller.rustCallWithError( + /*liftError:*/ FfiConverterTypeLoroError.lift.bind( + FfiConverterTypeLoroError + ), + /*caller:*/ (callStatus) => { + return nativeModule().ubrn_uniffi_loro_ffi_fn_method_lorodoc_try_export_json_updates_without_peer_compression( + uniffiTypeLoroDocObjectFactory.clonePointer(this), + FfiConverterTypeVersionVector.lower(startVv), + FfiConverterTypeVersionVector.lower(endVv), + callStatus + ); + }, + /*liftString:*/ FfiConverterString.lift + ) + ); + } + /** * Try to get a [LoroCounter] by container id. * @@ -19680,6 +19938,11 @@ const FfiConverterTypeVersionVector = new FfiConverterObject( uniffiTypeVersionVectorObjectFactory ); +// FfiConverter for ArrayBuffer | undefined +const FfiConverterOptionalArrayBuffer = new FfiConverterOptional( + FfiConverterArrayBuffer +); + // FfiConverter for /*f64*/number | undefined const FfiConverterOptionalFloat64 = new FfiConverterOptional( FfiConverterFloat64 @@ -20542,6 +20805,14 @@ function uniffiEnsureInitialized() { 'uniffi_loro_ffi_checksum_method_lorodoc_export_json_updates_without_peer_compression' ); } + if ( + nativeModule().ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_pending_updates() !== + 11778 + ) { + throw new UniffiInternalError.ApiChecksumMismatch( + 'uniffi_loro_ffi_checksum_method_lorodoc_export_pending_updates' + ); + } if ( nativeModule().ubrn_uniffi_loro_ffi_checksum_method_lorodoc_export_shallow_snapshot() !== 39593 @@ -21053,6 +21324,22 @@ function uniffiEnsureInitialized() { 'uniffi_loro_ffi_checksum_method_lorodoc_travel_change_ancestors' ); } + if ( + nativeModule().ubrn_uniffi_loro_ffi_checksum_method_lorodoc_try_export_json_updates() !== + 16562 + ) { + throw new UniffiInternalError.ApiChecksumMismatch( + 'uniffi_loro_ffi_checksum_method_lorodoc_try_export_json_updates' + ); + } + if ( + nativeModule().ubrn_uniffi_loro_ffi_checksum_method_lorodoc_try_export_json_updates_without_peer_compression() !== + 7325 + ) { + throw new UniffiInternalError.ApiChecksumMismatch( + 'uniffi_loro_ffi_checksum_method_lorodoc_try_export_json_updates_without_peer_compression' + ); + } if ( nativeModule().ubrn_uniffi_loro_ffi_checksum_method_lorodoc_try_get_counter() !== 9399 diff --git a/src/index.tsx b/src/index.tsx index 50f8f42..19af0e1 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -38,4 +38,3 @@ export async function uniffiInitAsync() { export default { loro, }; -