diff --git a/.github/workflows/advisory-cron.yaml b/.github/workflows/advisory-cron.yaml
new file mode 100644
index 00000000..31bac5a3
--- /dev/null
+++ b/.github/workflows/advisory-cron.yaml
@@ -0,0 +1,17 @@
+name: Advisories
+on:
+ schedule:
+ - cron: '0 18 * * *'
+jobs:
+ cargo-deny:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ checks:
+ - advisories
+ - bans licenses sources
+ steps:
+ - uses: actions/checkout@v2
+ - uses: EmbarkStudios/cargo-deny-action@v1
+ with:
+ command: check ${{ matrix.checks }}
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 00000000..8519ac5e
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,177 @@
+name: CI
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+jobs:
+ fmt:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: 1.67.0
+ default: true
+ components: rustfmt
+ - uses: Swatinem/rust-cache@v1
+ - run: ./scripts/ci/fmt
+ shell: bash
+
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: 1.67.0
+ default: true
+ components: clippy
+ - uses: Swatinem/rust-cache@v1
+ - run: ./scripts/ci/lint
+ shell: bash
+
+ docs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: 1.67.0
+ default: true
+ - uses: Swatinem/rust-cache@v1
+ - name: Build rust docs
+ run: ./scripts/ci/rust-docs
+ shell: bash
+ - name: Install doxygen
+ run: sudo apt-get install -y doxygen
+ shell: bash
+
+ cargo-deny:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ checks:
+ - advisories
+ - bans licenses sources
+ continue-on-error: ${{ matrix.checks == 'advisories' }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: EmbarkStudios/cargo-deny-action@v1
+ with:
+ arguments: '--manifest-path ./rust/Cargo.toml'
+ command: check ${{ matrix.checks }}
+
+ wasm_tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install wasm-bindgen-cli
+ run: cargo install wasm-bindgen-cli wasm-opt
+ - name: Install wasm32 target
+ run: rustup target add wasm32-unknown-unknown
+ - name: run tests
+ run: ./scripts/ci/wasm_tests
+ deno_tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: denoland/setup-deno@v1
+ with:
+ deno-version: v1.x
+ - name: Install wasm-bindgen-cli
+ run: cargo install wasm-bindgen-cli wasm-opt
+ - name: Install wasm32 target
+ run: rustup target add wasm32-unknown-unknown
+ - name: run tests
+ run: ./scripts/ci/deno_tests
+
+ js_fmt:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: install
+ run: yarn global add prettier
+ - name: format
+ run: prettier -c javascript/.prettierrc javascript
+
+ js_tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install wasm-bindgen-cli
+ run: cargo install wasm-bindgen-cli wasm-opt
+ - name: Install wasm32 target
+ run: rustup target add wasm32-unknown-unknown
+ - name: run tests
+ run: ./scripts/ci/js_tests
+
+ cmake_build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: nightly-2023-01-26
+ default: true
+ - uses: Swatinem/rust-cache@v1
+ - name: Install CMocka
+ run: sudo apt-get install -y libcmocka-dev
+ - name: Install/update CMake
+ uses: jwlawson/actions-setup-cmake@v1.12
+ with:
+ cmake-version: latest
+ - name: Install rust-src
+ run: rustup component add rust-src
+ - name: Build and test C bindings
+ run: ./scripts/ci/cmake-build Release Static
+ shell: bash
+
+ linux:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ toolchain:
+ - 1.67.0
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: ${{ matrix.toolchain }}
+ default: true
+ - uses: Swatinem/rust-cache@v1
+ - run: ./scripts/ci/build-test
+ shell: bash
+
+ macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: 1.67.0
+ default: true
+ - uses: Swatinem/rust-cache@v1
+ - run: ./scripts/ci/build-test
+ shell: bash
+
+ windows:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: 1.67.0
+ default: true
+ - uses: Swatinem/rust-cache@v1
+ - run: ./scripts/ci/build-test
+ shell: bash
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
new file mode 100644
index 00000000..b501d526
--- /dev/null
+++ b/.github/workflows/docs.yaml
@@ -0,0 +1,52 @@
+on:
+ push:
+ branches:
+ - main
+
+name: Documentation
+
+jobs:
+ deploy-docs:
+ concurrency: deploy-docs
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Toolchain
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: stable
+ override: true
+
+ - name: Cache
+ uses: Swatinem/rust-cache@v1
+
+ - name: Clean docs dir
+ run: rm -rf docs
+ shell: bash
+
+ - name: Clean Rust docs dir
+ uses: actions-rs/cargo@v1
+ with:
+ command: clean
+ args: --manifest-path ./rust/Cargo.toml --doc
+
+ - name: Build Rust docs
+ uses: actions-rs/cargo@v1
+ with:
+ command: doc
+ args: --manifest-path ./rust/Cargo.toml --workspace --all-features --no-deps
+
+ - name: Move Rust docs
+ run: mkdir -p docs && mv rust/target/doc/* docs/.
+ shell: bash
+
+ - name: Configure root page
+ run: echo '' > docs/index.html
+
+ - name: Deploy docs
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./docs
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
new file mode 100644
index 00000000..762671ff
--- /dev/null
+++ b/.github/workflows/release.yaml
@@ -0,0 +1,214 @@
+name: Release
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ check_if_wasm_version_upgraded:
+ name: Check if WASM version has been upgraded
+ runs-on: ubuntu-latest
+ outputs:
+ wasm_version: ${{ steps.version-updated.outputs.current-package-version }}
+ wasm_has_updated: ${{ steps.version-updated.outputs.has-updated }}
+ steps:
+ - uses: JiPaix/package-json-updated-action@v1.0.5
+ id: version-updated
+ with:
+ path: rust/automerge-wasm/package.json
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ publish-wasm:
+ name: Publish WASM package
+ runs-on: ubuntu-latest
+ needs:
+ - check_if_wasm_version_upgraded
+ # We create release only if the version in the package.json has been upgraded
+ if: needs.check_if_wasm_version_upgraded.outputs.wasm_has_updated == 'true'
+ steps:
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '16.x'
+ registry-url: 'https://registry.npmjs.org'
+ - uses: denoland/setup-deno@v1
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ github.ref }}
+ - name: Get rid of local github workflows
+ run: rm -r .github/workflows
+ - name: Remove tmp_branch if it exists
+ run: git push origin :tmp_branch || true
+ - run: git checkout -b tmp_branch
+ - name: Install wasm-bindgen-cli
+ run: cargo install wasm-bindgen-cli wasm-opt
+ - name: Install wasm32 target
+ run: rustup target add wasm32-unknown-unknown
+ - name: run wasm js tests
+ id: wasm_js_tests
+ run: ./scripts/ci/wasm_tests
+ - name: run wasm deno tests
+ id: wasm_deno_tests
+ run: ./scripts/ci/deno_tests
+ - name: build release
+ id: build_release
+ run: |
+ npm --prefix $GITHUB_WORKSPACE/rust/automerge-wasm run release
+ - name: Collate deno release files
+ if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success'
+ run: |
+ mkdir $GITHUB_WORKSPACE/deno_wasm_dist
+ cp $GITHUB_WORKSPACE/rust/automerge-wasm/deno/* $GITHUB_WORKSPACE/deno_wasm_dist
+ cp $GITHUB_WORKSPACE/rust/automerge-wasm/index.d.ts $GITHUB_WORKSPACE/deno_wasm_dist
+ cp $GITHUB_WORKSPACE/rust/automerge-wasm/README.md $GITHUB_WORKSPACE/deno_wasm_dist
+ cp $GITHUB_WORKSPACE/rust/automerge-wasm/LICENSE $GITHUB_WORKSPACE/deno_wasm_dist
+ sed -i '1i /// ' $GITHUB_WORKSPACE/deno_wasm_dist/automerge_wasm.js
+ - name: Create npm release
+ if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success'
+ run: |
+ if [ "$(npm --prefix $GITHUB_WORKSPACE/rust/automerge-wasm show . version)" = "$VERSION" ]; then
+ echo "This version is already published"
+ exit 0
+ fi
+ EXTRA_ARGS="--access public"
+ if [[ $VERSION == *"alpha."* ]] || [[ $VERSION == *"beta."* ]] || [[ $VERSION == *"rc."* ]]; then
+ echo "Is pre-release version"
+ EXTRA_ARGS="$EXTRA_ARGS --tag next"
+ fi
+ if [ "$NODE_AUTH_TOKEN" = "" ]; then
+ echo "Can't publish on NPM, You need a NPM_TOKEN secret."
+ false
+ fi
+ npm publish $GITHUB_WORKSPACE/rust/automerge-wasm $EXTRA_ARGS
+ env:
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
+ VERSION: ${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }}
+ - name: Commit wasm deno release files
+ run: |
+ git config --global user.name "actions"
+ git config --global user.email actions@github.com
+ git add $GITHUB_WORKSPACE/deno_wasm_dist
+ git commit -am "Add deno release files"
+ git push origin tmp_branch
+ - name: Tag wasm release
+ if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success'
+ uses: softprops/action-gh-release@v1
+ with:
+ name: Automerge Wasm v${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }}
+ tag_name: js/automerge-wasm-${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }}
+ target_commitish: tmp_branch
+ generate_release_notes: false
+ draft: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Remove tmp_branch
+ run: git push origin :tmp_branch
+ check_if_js_version_upgraded:
+ name: Check if JS version has been upgraded
+ runs-on: ubuntu-latest
+ outputs:
+ js_version: ${{ steps.version-updated.outputs.current-package-version }}
+ js_has_updated: ${{ steps.version-updated.outputs.has-updated }}
+ steps:
+ - uses: JiPaix/package-json-updated-action@v1.0.5
+ id: version-updated
+ with:
+ path: javascript/package.json
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ publish-js:
+ name: Publish JS package
+ runs-on: ubuntu-latest
+ needs:
+ - check_if_js_version_upgraded
+ - check_if_wasm_version_upgraded
+ - publish-wasm
+ # We create release only if the version in the package.json has been upgraded and after the WASM release
+ if: |
+ (always() && ! cancelled()) &&
+ (needs.publish-wasm.result == 'success' || needs.publish-wasm.result == 'skipped') &&
+ needs.check_if_js_version_upgraded.outputs.js_has_updated == 'true'
+ steps:
+ - uses: actions/setup-node@v3
+ with:
+ node-version: '16.x'
+ registry-url: 'https://registry.npmjs.org'
+ - uses: denoland/setup-deno@v1
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ github.ref }}
+ - name: Get rid of local github workflows
+ run: rm -r .github/workflows
+ - name: Remove js_tmp_branch if it exists
+ run: git push origin :js_tmp_branch || true
+ - run: git checkout -b js_tmp_branch
+ - name: check js formatting
+ run: |
+ yarn global add prettier
+ prettier -c javascript/.prettierrc javascript
+ - name: run js tests
+ id: js_tests
+ run: |
+ cargo install wasm-bindgen-cli wasm-opt
+ rustup target add wasm32-unknown-unknown
+ ./scripts/ci/js_tests
+ - name: build js release
+ id: build_release
+ run: |
+ npm --prefix $GITHUB_WORKSPACE/javascript run build
+ - name: build js deno release
+ id: build_deno_release
+ run: |
+ VERSION=$WASM_VERSION npm --prefix $GITHUB_WORKSPACE/javascript run deno:build
+ env:
+ WASM_VERSION: ${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }}
+ - name: run deno tests
+ id: deno_tests
+ run: |
+ npm --prefix $GITHUB_WORKSPACE/javascript run deno:test
+ - name: Collate deno release files
+ if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success'
+ run: |
+ mkdir $GITHUB_WORKSPACE/deno_js_dist
+ cp $GITHUB_WORKSPACE/javascript/deno_dist/* $GITHUB_WORKSPACE/deno_js_dist
+ - name: Create npm release
+ if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success'
+ run: |
+ if [ "$(npm --prefix $GITHUB_WORKSPACE/javascript show . version)" = "$VERSION" ]; then
+ echo "This version is already published"
+ exit 0
+ fi
+ EXTRA_ARGS="--access public"
+ if [[ $VERSION == *"alpha."* ]] || [[ $VERSION == *"beta."* ]] || [[ $VERSION == *"rc."* ]]; then
+ echo "Is pre-release version"
+ EXTRA_ARGS="$EXTRA_ARGS --tag next"
+ fi
+ if [ "$NODE_AUTH_TOKEN" = "" ]; then
+ echo "Can't publish on NPM, You need a NPM_TOKEN secret."
+ false
+ fi
+ npm publish $GITHUB_WORKSPACE/javascript $EXTRA_ARGS
+ env:
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
+ VERSION: ${{ needs.check_if_js_version_upgraded.outputs.js_version }}
+ - name: Commit js deno release files
+ run: |
+ git config --global user.name "actions"
+ git config --global user.email actions@github.com
+ git add $GITHUB_WORKSPACE/deno_js_dist
+ git commit -am "Add deno js release files"
+ git push origin js_tmp_branch
+ - name: Tag JS release
+ if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success'
+ uses: softprops/action-gh-release@v1
+ with:
+ name: Automerge v${{ needs.check_if_js_version_upgraded.outputs.js_version }}
+ tag_name: js/automerge-${{ needs.check_if_js_version_upgraded.outputs.js_version }}
+ target_commitish: js_tmp_branch
+ generate_release_notes: false
+ draft: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Remove js_tmp_branch
+ run: git push origin :js_tmp_branch
diff --git a/README.md b/README.md
index d11e9d1c..ad174da4 100644
--- a/README.md
+++ b/README.md
@@ -42,9 +42,10 @@ In general we try and respect semver.
### JavaScript
-An alpha release of the javascript package is currently available as
-`@automerge/automerge@2.0.0-alpha.n` where `n` is an integer. We are gathering
-feedback on the API and looking to release a `2.0.0` in the next few weeks.
+A stable release of the javascript package is currently available as
+`@automerge/automerge@2.0.0` where. pre-release verisions of the `2.0.1` are
+available as `2.0.1-alpha.n`. `2.0.1*` packages are also available for Deno at
+https://deno.land/x/automerge
### Rust
@@ -52,7 +53,9 @@ The rust codebase is currently oriented around producing a performant backend
for the Javascript wrapper and as such the API for Rust code is low level and
not well documented. We will be returning to this over the next few months but
for now you will need to be comfortable reading the tests and asking questions
-to figure out how to use it.
+to figure out how to use it. If you are looking to build rust applications which
+use automerge you may want to look into
+[autosurgeon](https://github.com/alexjg/autosurgeon)
## Repository Organisation
@@ -109,9 +112,16 @@ brew install cmake node cmocka
# install yarn
npm install --global yarn
+# install javascript dependencies
+yarn --cwd ./javascript
+
# install rust dependencies
cargo install wasm-bindgen-cli wasm-opt cargo-deny
+# get nightly rust to produce optimized automerge-c builds
+rustup toolchain install nightly
+rustup component add rust-src --toolchain nightly
+
# add wasm target in addition to current architecture
rustup target add wasm32-unknown-unknown
diff --git a/deno_wasm_dist/LICENSE b/deno_wasm_dist/LICENSE
deleted file mode 100644
index 63b21502..00000000
--- a/deno_wasm_dist/LICENSE
+++ /dev/null
@@ -1,10 +0,0 @@
-MIT License
-
-Copyright 2022, Ink & Switch LLC
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
diff --git a/deno_wasm_dist/README.md b/deno_wasm_dist/README.md
deleted file mode 100644
index 20256313..00000000
--- a/deno_wasm_dist/README.md
+++ /dev/null
@@ -1,469 +0,0 @@
-## Automerge WASM Low Level Interface
-
-This package is a low level interface to the [automerge rust](https://github.com/automerge/automerge-rs/tree/experiment) CRDT. The api is intended to be as "close to the metal" as possible with only a few ease of use accommodations. This library is used as the underpinnings for the [Automerge JS wrapper](https://github.com/automerge/automerge-rs/tree/experiment/automerge-js) and can be used as is or as a basis for another higher level expression of a CRDT.
-
-All example code can be found in `test/readme.ts`
-
-### Why CRDT?
-
-CRDT stands for Conflict Free Replicated Data Type. It is a data structure that offers eventual consistency where multiple actors can write to the document independently and then these edits can be automatically merged together into a coherent document that, as much as possible, preserves the intent of the different writers. This allows for novel masterless application design where different components need not have a central coordinating server when altering application state.
-
-### Terminology
-
-The term Actor, Object Id and Heads are used through this documentation. Detailed explanations are in the glossary at the end of this readme. But the most basic definition would be...
-
-An Actor is a unique id that distinguishes a single writer to a document. It can be any hex string.
-
-An Object id uniquely identifies a Map, List or Text object within a document. It can be treated as an opaque string and can be used across documents. This id comes as a string in the form of `{number}@{actor}` - so `"10@aabbcc"` for example. The string `"_root"` or `"/"` can also be used to refer to the document root. These strings are durable and can be used on any descendant or copy of the document that generated them.
-
-Heads refers to a set of hashes that uniquely identifies a point in time in a document's history. Heads are useful for comparing documents state or retrieving past states from the document.
-
-### Automerge Scalar Types
-
-Automerge has many scalar types. Methods like `put()` and `insert()` take an optional data type parameter. Normally the type can be inferred but in some cases, such as telling the difference between int, uint and a counter, it cannot.
-
-These are puts without a data type
-
-```javascript
- import { create } from "@automerge/automerge-wasm"
-
- let doc = create()
- doc.put("/", "prop1", 100) // int
- doc.put("/", "prop2", 3.14) // f64
- doc.put("/", "prop3", "hello world")
- doc.put("/", "prop4", new Date())
- doc.put("/", "prop5", new Uint8Array([1,2,3]))
- doc.put("/", "prop6", true)
- doc.put("/", "prop7", null)
-```
-
-Put's with a data type and examples of all the supported data types.
-
-While int vs uint vs f64 matters little in javascript, Automerge is a cross platform library where these distinctions matter.
-
-```javascript
- import { create } from "@automerge/automerge-wasm"
-
- let doc = create()
- doc.put("/", "prop1", 100, "int")
- doc.put("/", "prop2", 100, "uint")
- doc.put("/", "prop3", 100.5, "f64")
- doc.put("/", "prop4", 100, "counter")
- doc.put("/", "prop5", 1647531707301, "timestamp")
- doc.put("/", "prop6", new Date(), "timestamp")
- doc.put("/", "prop7", "hello world", "str")
- doc.put("/", "prop8", new Uint8Array([1,2,3]), "bytes")
- doc.put("/", "prop9", true, "boolean")
- doc.put("/", "prop10", null, "null")
-```
-
-### Automerge Object Types
-
-Automerge WASM supports 3 object types. Maps, lists, and text. Maps are key value stores where the values can be any scalar type or any object type. Lists are numerically indexed sets of data that can hold any scalar or any object type.
-
-```javascript
- import { create } from "@automerge/automerge-wasm"
-
- let doc = create()
-
- // you can create an object by passing in the inital state - if blank pass in `{}`
- // the return value is the Object Id
- // these functions all return an object id
-
- let config = doc.putObject("/", "config", { align: "left", archived: false, cycles: [10, 19, 21] })
- let token = doc.putObject("/", "tokens", {})
-
- // lists can be made with javascript arrays
-
- let birds = doc.putObject("/", "birds", ["bluejay", "penguin", "puffin"])
- let bots = doc.putObject("/", "bots", [])
-
- // text is initialized with a string
-
- let notes = doc.putObject("/", "notes", "Hello world!")
-```
-
-You can access objects by passing the object id as the first parameter for a call.
-
-```javascript
- import { create } from "@automerge/automerge-wasm"
-
- let doc = create()
-
- let config = doc.putObject("/", "config", { align: "left", archived: false, cycles: [10, 19, 21] })
-
- doc.put(config, "align", "right")
-
- // Anywhere Object Ids are being used a path can also be used.
- // The following two statements are equivalent:
-
- // get the id then use it
-
- // get returns a single simple javascript value or undefined
- // getWithType returns an Array of the datatype plus basic type or null
-
- let id = doc.getWithType("/", "config")
- if (id && id[0] === 'map') {
- doc.put(id[1], "align", "right")
- }
-
- // use a path instead
-
- doc.put("/config", "align", "right")
-```
-
-Using the id directly is always faster (as it prevents the path to id conversion internally) so it is preferred for performance critical code.
-
-### Maps
-
-Maps are key/value stores. The root object is always a map. The keys are always strings. The values can be any scalar type or any object.
-
-```javascript
- let doc = create()
- let mymap = doc.putObject("_root", "mymap", { foo: "bar"})
- // make a new map with the foo key
-
- doc.put(mymap, "bytes", new Uint8Array([1,2,3]))
- // assign a byte array to key `bytes` of the mymap object
-
- let submap = doc.putObject(mymap, "sub", {})
- // make a new empty object and assign it to the key `sub` of mymap
-
- doc.keys(mymap) // returns ["bytes","foo","sub"]
- doc.materialize("_root") // returns { mymap: { bytes: new Uint8Array([1,2,3]), foo: "bar", sub: {}}}
-```
-
-### Lists
-
-Lists are index addressable sets of values. These values can be any scalar or object type. You can manipulate lists with `insert()`, `put()`, `insertObject()`, `putObject()`, `push()`, `pushObject()`, `splice()`, and `delete()`.
-
-```javascript
- let doc = create()
- let items = doc.putObject("_root", "items", [10,"box"])
- // init a new list with two elements
- doc.push(items, true) // push `true` to the end of the list
- doc.putObject(items, 0, { hello: "world" }) // overwrite the value 10 with an object with a key and value
- doc.delete(items, 1) // delete "box"
- doc.splice(items, 2, 0, ["bag", "brick"]) // splice in "bag" and "brick" at position 2
- doc.insert(items, 0, "bat") // insert "bat" to the beginning of the list
- doc.insertObject(items, 1, [1,2]) // insert a list with 2 values at pos 1
-
- doc.materialize(items) // returns [ "bat", [1,2], { hello : "world" }, true, "bag", "brick"]
- doc.length(items) // returns 6
-```
-
-### Text
-
-Text is a specialized list type intended for modifying a text document. The primary way to interact with a text document is via the `splice()` method. Spliced strings will be indexable by character (important to note for platforms that index by graphmeme cluster).
-
-```javascript
- let doc = create("aaaaaa")
- let notes = doc.putObject("_root", "notes", "Hello world")
- doc.splice(notes, 6, 5, "everyone")
-
- doc.text(notes) // returns "Hello everyone"
-```
-
-### Tables
-
-Automerge's Table type is currently not implemented.
-
-### Querying Data
-
-When querying maps use the `get()` method with the object in question and the property to query. This method returns a tuple with the data type and the data. The `keys()` method will return all the keys on the object. If you are interested in conflicted values from a merge use `getAll()` instead which returns an array of values instead of just the winner.
-
-```javascript
- let doc1 = create("aabbcc")
- doc1.put("_root", "key1", "val1")
- let key2 = doc1.putObject("_root", "key2", [])
-
- doc1.get("_root", "key1") // returns "val1"
- doc1.getWithType("_root", "key2") // returns ["list", "2@aabbcc"]
- doc1.keys("_root") // returns ["key1", "key2"]
-
- let doc2 = doc1.fork("ffaaff")
-
- // put a value concurrently
- doc1.put("_root","key3","doc1val")
- doc2.put("_root","key3","doc2val")
-
- doc1.merge(doc2)
-
- doc1.get("_root","key3") // returns "doc2val"
- doc1.getAll("_root","key3") // returns [[ "str", "doc1val"], ["str", "doc2val"]]
-```
-
-### Counters
-
-Counters are 64 bit ints that support the increment operation. Frequently different actors will want to increment or decrement a number and have all these coalesse into a merged value.
-
-```javascript
- let doc1 = create("aaaaaa")
- doc1.put("_root", "number", 0)
- doc1.put("_root", "total", 0, "counter")
-
- let doc2 = doc1.fork("bbbbbb")
- doc2.put("_root", "number", 10)
- doc2.increment("_root", "total", 11)
-
- doc1.put("_root", "number", 20)
- doc1.increment("_root", "total", 22)
-
- doc1.merge(doc2)
-
- doc1.materialize("_root") // returns { number: 10, total: 33 }
-```
-
-### Transactions
-
-Generally speaking you don't need to think about transactions when using Automerge. Normal edits queue up into an in-progress transaction. You can query the number of ops in the current transaction with `pendingOps()`. The transaction will commit automatically on certains calls such as `save()`, `saveIncremental()`, `fork()`, `merge()`, `getHeads()`, `applyChanges()`, `generateSyncMessage()`, and `receiveSyncMessage()`. When the transaction commits the heads of the document change. If you want to roll back all the in progress ops you can call `doc.rollback()`. If you want to manually commit a transaction in progress you can call `doc.commit()` with an optional commit message and timestamp.
-
-```javascript
- let doc = create()
-
- doc.put("_root", "key", "val1")
-
- doc.get("_root", "key") // returns "val1"
- doc.pendingOps() // returns 1
-
- doc.rollback()
-
- doc.get("_root", "key") // returns null
- doc.pendingOps() // returns 0
-
- doc.put("_root", "key", "val2")
-
- doc.pendingOps() // returns 1
-
- doc.commit("test commit 1")
-
- doc.get("_root", "key") // returns "val2"
- doc.pendingOps() // returns 0
-```
-
-### Viewing Old Versions of the Document
-
-All query functions can take an optional argument of `heads` which allow you to query a prior document state. Heads are a set of change hashes that uniquely identify a point in the document history. The `getHeads()` method can retrieve these at any point.
-
-```javascript
- let doc = create()
-
- doc.put("_root", "key", "val1")
- let heads1 = doc.getHeads()
-
- doc.put("_root", "key", "val2")
- let heads2 = doc.getHeads()
-
- doc.put("_root", "key", "val3")
-
- doc.get("_root","key") // returns "val3"
- doc.get("_root","key",heads2) // returns "val2"
- doc.get("_root","key",heads1) // returns "val1"
- doc.get("_root","key",[]) // returns undefined
-```
-
-This works for `get()`, `getAll()`, `keys()`, `length()`, `text()`, and `materialize()`
-
-Queries of old document states are not indexed internally and will be slower than normal access. If you need a fast indexed version of a document at a previous point in time you can create one with `doc.forkAt(heads, actor?)`
-
-### Forking and Merging
-
-You can `fork()` a document which makes an exact copy of it. This assigns a new actor so changes made to the fork can be merged back in with the original. The `forkAt()` takes a Heads, allowing you to fork off a document from a previous point in its history. These documents allocate new memory in WASM and need to be freed.
-
-The `merge()` command applies all changes in the argument doc into the calling doc. Therefore if doc a has 1000 changes that doc b lacks and doc b has only 10 changes that doc a lacks, `a.merge(b)` will be much faster than `b.merge(a)`.
-
-```javascript
- let doc1 = create()
- doc1.put("_root", "key1", "val1")
-
- let doc2 = doc1.fork()
-
- doc1.put("_root", "key2", "val2")
- doc2.put("_root", "key3", "val3")
-
- doc1.merge(doc2)
-
- doc1.materialize("_root") // returns { key1: "val1", key2: "val2", key3: "val3" }
- doc2.materialize("_root") // returns { key1: "val1", key3: "val3" }
-```
-
-Note that calling `a.merge(a)` will produce an unrecoverable error from the wasm-bindgen layer which (as of this writing) there is no workaround for.
-
-### Saving and Loading
-
-Calling `save()` converts the document to a compressed `Uint8Array()` that can be saved to durable storage. This format uses a columnar storage format that compresses away most of the Automerge metadata needed to manage the CRDT state, but does include all of the change history.
-
-If you wish to incrementally update a saved Automerge doc you can call `saveIncremental()` to get a `Uint8Array()` of bytes that can be appended to the file with all the new changes(). Note that the `saveIncremental()` bytes are not as compressed as the whole document save as each chunk has metadata information needed to parse it. It may make sense to periodically perform a new `save()` to get the smallest possible file footprint.
-
-The `load()` function takes a `Uint8Array()` of bytes produced in this way and constitutes a new document. The `loadIncremental()` method is available if you wish to consume the result of a `saveIncremental()` with an already instanciated document.
-
-```javascript
- import { create, load } from "@automerge/automerge-wasm"
-
- let doc1 = create()
-
- doc1.put("_root", "key1", "value1")
-
- let save1 = doc1.save()
-
- let doc2 = load(save1)
-
- doc2.materialize("_root") // returns { key1: "value1" }
-
- doc1.put("_root", "key2", "value2")
-
- let saveIncremental = doc1.saveIncremental()
-
- let save2 = doc1.save()
-
- let save3 = new Uint8Array([... save1, ... saveIncremental])
-
- // save2 has fewer bytes than save3 but contains the same ops
-
- doc2.loadIncremental(saveIncremental)
-
- let doc3 = load(save2)
-
- let doc4 = load(save3)
-
- doc1.materialize("_root") // returns { key1: "value1", key2: "value2" }
- doc2.materialize("_root") // returns { key1: "value1", key2: "value2" }
- doc3.materialize("_root") // returns { key1: "value1", key2: "value2" }
- doc4.materialize("_root") // returns { key1: "value1", key2: "value2" }
-```
-
-One interesting feature of automerge binary saves is that they can be concatenated together in any order and can still be loaded into a coherent merged document.
-
-```javascript
-import { load } from "@automerge/automerge-wasm"
-import * as fs from "fs"
-
-let file1 = fs.readFileSync("automerge_save_1");
-let file2 = fs.readFileSync("automerge_save_2");
-
-let docA = load(file1).merge(load(file2))
-let docB = load(Buffer.concat([ file1, file2 ]))
-
-assert.deepEqual(docA.materialize("/"), docB.materialize("/"))
-assert.equal(docA.save(), docB.save())
-```
-
-### Syncing
-
-When syncing a document the `generateSyncMessage()` and `receiveSyncMessage()` methods will produce and consume sync messages. A sync state object will need to be managed for the duration of the connection (created by the function `initSyncState()` and can be serialized to a Uint8Array() to preserve sync state with the `encodeSyncState()` and `decodeSyncState()` functions.
-
-A very simple sync implementation might look like this.
-
-```javascript
- import { encodeSyncState, decodeSyncState, initSyncState } from "@automerge/automerge-wasm"
-
- let states = {}
-
- function receiveMessageFromPeer(doc, peer_id, message) {
- let syncState = states[peer_id]
- doc.receiveMessage(syncState, message)
- let reply = doc.generateSyncMessage(syncState)
- if (reply) {
- sendMessage(peer_id, reply)
- }
- }
-
- function notifyPeerAboutUpdates(doc, peer_id) {
- let syncState = states[peer_id]
- let message = doc.generateSyncMessage(syncState)
- if (message) {
- sendMessage(peer_id, message)
- }
- }
-
- function onDisconnect(peer_id) {
- let state = states[peer_id]
- if (state) {
- saveSyncToStorage(peer_id, encodeSyncState(state))
- }
- delete states[peer_id]
- }
-
- function onConnect(peer_id) {
- let state = loadSyncFromStorage(peer_id)
- if (state) {
- states[peer_id] = decodeSyncState(state)
- } else {
- states[peer_id] = initSyncState()
- }
- }
-```
-
-### Glossary: Actors
-
-Some basic concepts you will need to know to better understand the api are Actors and Object Ids.
-
-Actors are ids that need to be unique to each process writing to a document. This is normally one actor per device. Or for a web app one actor per tab per browser would be needed. It can be a uuid, or a public key, or a certificate, as your application demands. All that matters is that its bytes are unique. Actors are always expressed in this api as a hex string.
-
-Methods that create new documents will generate random actors automatically - if you wish to supply your own it is always taken as an optional argument. This is true for the following functions.
-
-```javascript
- import { create, load } from "@automerge/automerge-wasm"
-
- let doc1 = create() // random actorid
- let doc2 = create("aabbccdd")
- let doc3 = doc1.fork() // random actorid
- let doc4 = doc2.fork("ccdd0011")
- let doc5 = load(doc3.save()) // random actorid
- let doc6 = load(doc4.save(), "00aabb11")
-
- let actor = doc1.getActor()
-```
-
-### Glossary: Object Id's
-
-Object Ids uniquely identify an object within a document. They are represented as strings in the format of `{counter}@{actor}`. The root object is a special case and can be referred to as `_root`. The counter is an ever increasing integer, starting at 1, that is always one higher than the highest counter seen in the document thus far. Object Id's do not change when the object is modified but they do if it is overwritten with a new object.
-
-```javascript
- let doc = create("aabbcc")
- let o1 = doc.putObject("_root", "o1", {})
- let o2 = doc.putObject("_root", "o2", {})
- doc.put(o1, "hello", "world")
-
- assert.deepEqual(doc.materialize("_root"), { "o1": { hello: "world" }, "o2": {} })
- assert.equal(o1, "1@aabbcc")
- assert.equal(o2, "2@aabbcc")
-
- let o1v2 = doc.putObject("_root", "o1", {})
-
- doc.put(o1, "a", "b") // modifying an overwritten object - does nothing
- doc.put(o1v2, "x", "y") // modifying the new "o1" object
-
- assert.deepEqual(doc.materialize("_root"), { "o1": { x: "y" }, "o2": {} })
-```
-
-### Appendix: Building
-
- The following steps should allow you to build the package
-
- ```
- $ rustup target add wasm32-unknown-unknown
- $ cargo install wasm-bindgen-cli
- $ cargo install wasm-opt
- $ yarn
- $ yarn release
- $ yarn pack
- ```
-
-### Appendix: WASM and Memory Allocation
-
-Allocated memory in rust will be freed automatically on platforms that support `FinalizationRegistry`.
-
-This is currently supported in [all major browsers and nodejs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry).
-
-On unsupported platforms you can free memory explicitly.
-
-```javascript
- import { create, initSyncState } from "@automerge/automerge-wasm"
-
- let doc = create()
- let sync = initSyncState()
-
- doc.free()
- sync.free()
-```
diff --git a/deno_wasm_dist/automerge_wasm.js b/deno_wasm_dist/automerge_wasm.js
deleted file mode 100644
index b987558d..00000000
--- a/deno_wasm_dist/automerge_wasm.js
+++ /dev/null
@@ -1,1663 +0,0 @@
-///
-
-
-const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
-
-cachedTextDecoder.decode();
-
-let cachedUint8Memory0 = new Uint8Array();
-
-function getUint8Memory0() {
- if (cachedUint8Memory0.byteLength === 0) {
- cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
- }
- return cachedUint8Memory0;
-}
-
-function getStringFromWasm0(ptr, len) {
- return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
-}
-
-const heap = new Array(32).fill(undefined);
-
-heap.push(undefined, null, true, false);
-
-let heap_next = heap.length;
-
-function addHeapObject(obj) {
- if (heap_next === heap.length) heap.push(heap.length + 1);
- const idx = heap_next;
- heap_next = heap[idx];
-
- heap[idx] = obj;
- return idx;
-}
-
-function getObject(idx) { return heap[idx]; }
-
-function dropObject(idx) {
- if (idx < 36) return;
- heap[idx] = heap_next;
- heap_next = idx;
-}
-
-function takeObject(idx) {
- const ret = getObject(idx);
- dropObject(idx);
- return ret;
-}
-
-let WASM_VECTOR_LEN = 0;
-
-const cachedTextEncoder = new TextEncoder('utf-8');
-
-const encodeString = function (arg, view) {
- return cachedTextEncoder.encodeInto(arg, view);
-};
-
-function passStringToWasm0(arg, malloc, realloc) {
-
- if (realloc === undefined) {
- const buf = cachedTextEncoder.encode(arg);
- const ptr = malloc(buf.length);
- getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
- WASM_VECTOR_LEN = buf.length;
- return ptr;
- }
-
- let len = arg.length;
- let ptr = malloc(len);
-
- const mem = getUint8Memory0();
-
- let offset = 0;
-
- for (; offset < len; offset++) {
- const code = arg.charCodeAt(offset);
- if (code > 0x7F) break;
- mem[ptr + offset] = code;
- }
-
- if (offset !== len) {
- if (offset !== 0) {
- arg = arg.slice(offset);
- }
- ptr = realloc(ptr, len, len = offset + arg.length * 3);
- const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
- const ret = encodeString(arg, view);
-
- offset += ret.written;
- }
-
- WASM_VECTOR_LEN = offset;
- return ptr;
-}
-
-function isLikeNone(x) {
- return x === undefined || x === null;
-}
-
-let cachedInt32Memory0 = new Int32Array();
-
-function getInt32Memory0() {
- if (cachedInt32Memory0.byteLength === 0) {
- cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
- }
- return cachedInt32Memory0;
-}
-
-let cachedFloat64Memory0 = new Float64Array();
-
-function getFloat64Memory0() {
- if (cachedFloat64Memory0.byteLength === 0) {
- cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer);
- }
- return cachedFloat64Memory0;
-}
-
-function debugString(val) {
- // primitive types
- const type = typeof val;
- if (type == 'number' || type == 'boolean' || val == null) {
- return `${val}`;
- }
- if (type == 'string') {
- return `"${val}"`;
- }
- if (type == 'symbol') {
- const description = val.description;
- if (description == null) {
- return 'Symbol';
- } else {
- return `Symbol(${description})`;
- }
- }
- if (type == 'function') {
- const name = val.name;
- if (typeof name == 'string' && name.length > 0) {
- return `Function(${name})`;
- } else {
- return 'Function';
- }
- }
- // objects
- if (Array.isArray(val)) {
- const length = val.length;
- let debug = '[';
- if (length > 0) {
- debug += debugString(val[0]);
- }
- for(let i = 1; i < length; i++) {
- debug += ', ' + debugString(val[i]);
- }
- debug += ']';
- return debug;
- }
- // Test for built-in
- const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
- let className;
- if (builtInMatches.length > 1) {
- className = builtInMatches[1];
- } else {
- // Failed to match the standard '[object ClassName]'
- return toString.call(val);
- }
- if (className == 'Object') {
- // we're a user defined class or Object
- // JSON.stringify avoids problems with cycles, and is generally much
- // easier than looping through ownProperties of `val`.
- try {
- return 'Object(' + JSON.stringify(val) + ')';
- } catch (_) {
- return 'Object';
- }
- }
- // errors
- if (val instanceof Error) {
- return `${val.name}: ${val.message}\n${val.stack}`;
- }
- // TODO we could test for more things here, like `Set`s and `Map`s.
- return className;
-}
-
-function _assertClass(instance, klass) {
- if (!(instance instanceof klass)) {
- throw new Error(`expected instance of ${klass.name}`);
- }
- return instance.ptr;
-}
-/**
-* @param {boolean} text_v2
-* @param {string | undefined} actor
-* @returns {Automerge}
-*/
-export function create(text_v2, actor) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- wasm.create(retptr, text_v2, ptr0, len0);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return Automerge.__wrap(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {Uint8Array} data
-* @param {boolean} text_v2
-* @param {string | undefined} actor
-* @returns {Automerge}
-*/
-export function load(data, text_v2, actor) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- wasm.load(retptr, addHeapObject(data), text_v2, ptr0, len0);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return Automerge.__wrap(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {any} change
-* @returns {Uint8Array}
-*/
-export function encodeChange(change) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.encodeChange(retptr, addHeapObject(change));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {Uint8Array} change
-* @returns {any}
-*/
-export function decodeChange(change) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.decodeChange(retptr, addHeapObject(change));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @returns {SyncState}
-*/
-export function initSyncState() {
- const ret = wasm.initSyncState();
- return SyncState.__wrap(ret);
-}
-
-/**
-* @param {any} state
-* @returns {SyncState}
-*/
-export function importSyncState(state) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.importSyncState(retptr, addHeapObject(state));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return SyncState.__wrap(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {SyncState} state
-* @returns {any}
-*/
-export function exportSyncState(state) {
- _assertClass(state, SyncState);
- const ret = wasm.exportSyncState(state.ptr);
- return takeObject(ret);
-}
-
-/**
-* @param {any} message
-* @returns {Uint8Array}
-*/
-export function encodeSyncMessage(message) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.encodeSyncMessage(retptr, addHeapObject(message));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {Uint8Array} msg
-* @returns {any}
-*/
-export function decodeSyncMessage(msg) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.decodeSyncMessage(retptr, addHeapObject(msg));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-/**
-* @param {SyncState} state
-* @returns {Uint8Array}
-*/
-export function encodeSyncState(state) {
- _assertClass(state, SyncState);
- const ret = wasm.encodeSyncState(state.ptr);
- return takeObject(ret);
-}
-
-/**
-* @param {Uint8Array} data
-* @returns {SyncState}
-*/
-export function decodeSyncState(data) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.decodeSyncState(retptr, addHeapObject(data));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return SyncState.__wrap(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
-}
-
-function handleError(f, args) {
- try {
- return f.apply(this, args);
- } catch (e) {
- wasm.__wbindgen_exn_store(addHeapObject(e));
- }
-}
-
-function getArrayU8FromWasm0(ptr, len) {
- return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
-}
-/**
-* How text is represented in materialized objects on the JS side
-*/
-export const TextRepresentation = Object.freeze({
-/**
-* As an array of characters and objects
-*/
-Array:0,"0":"Array",
-/**
-* As a single JS string
-*/
-String:1,"1":"String", });
-
-const AutomergeFinalization = new FinalizationRegistry(ptr => wasm.__wbg_automerge_free(ptr));
-/**
-*/
-export class Automerge {
-
- static __wrap(ptr) {
- const obj = Object.create(Automerge.prototype);
- obj.ptr = ptr;
- AutomergeFinalization.register(obj, obj.ptr, obj);
- return obj;
- }
-
- __destroy_into_raw() {
- const ptr = this.ptr;
- this.ptr = 0;
- AutomergeFinalization.unregister(this);
- return ptr;
- }
-
- free() {
- const ptr = this.__destroy_into_raw();
- wasm.__wbg_automerge_free(ptr);
- }
- /**
- * @param {string | undefined} actor
- * @param {number} text_rep
- * @returns {Automerge}
- */
- static new(actor, text_rep) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- wasm.automerge_new(retptr, ptr0, len0, text_rep);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return Automerge.__wrap(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {string | undefined} actor
- * @returns {Automerge}
- */
- clone(actor) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- wasm.automerge_clone(retptr, this.ptr, ptr0, len0);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return Automerge.__wrap(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {string | undefined} actor
- * @param {any} heads
- * @returns {Automerge}
- */
- fork(actor, heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- wasm.automerge_fork(retptr, this.ptr, ptr0, len0, addHeapObject(heads));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return Automerge.__wrap(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @returns {any}
- */
- pendingOps() {
- const ret = wasm.automerge_pendingOps(this.ptr);
- return takeObject(ret);
- }
- /**
- * @param {string | undefined} message
- * @param {number | undefined} time
- * @returns {any}
- */
- commit(message, time) {
- var ptr0 = isLikeNone(message) ? 0 : passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- const ret = wasm.automerge_commit(this.ptr, ptr0, len0, !isLikeNone(time), isLikeNone(time) ? 0 : time);
- return takeObject(ret);
- }
- /**
- * @param {Automerge} other
- * @returns {Array}
- */
- merge(other) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- _assertClass(other, Automerge);
- wasm.automerge_merge(retptr, this.ptr, other.ptr);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @returns {number}
- */
- rollback() {
- const ret = wasm.automerge_rollback(this.ptr);
- return ret;
- }
- /**
- * @param {any} obj
- * @param {Array | undefined} heads
- * @returns {Array}
- */
- keys(obj, heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_keys(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {Array | undefined} heads
- * @returns {string}
- */
- text(obj, heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_text(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- var r3 = getInt32Memory0()[retptr / 4 + 3];
- var ptr0 = r0;
- var len0 = r1;
- if (r3) {
- ptr0 = 0; len0 = 0;
- throw takeObject(r2);
- }
- return getStringFromWasm0(ptr0, len0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- wasm.__wbindgen_free(ptr0, len0);
- }
- }
- /**
- * @param {any} obj
- * @param {number} start
- * @param {number} delete_count
- * @param {any} text
- */
- splice(obj, start, delete_count, text) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_splice(retptr, this.ptr, addHeapObject(obj), start, delete_count, addHeapObject(text));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} value
- * @param {any} datatype
- */
- push(obj, value, datatype) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_push(retptr, this.ptr, addHeapObject(obj), addHeapObject(value), addHeapObject(datatype));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} value
- * @returns {string | undefined}
- */
- pushObject(obj, value) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_pushObject(retptr, this.ptr, addHeapObject(obj), addHeapObject(value));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- var r3 = getInt32Memory0()[retptr / 4 + 3];
- if (r3) {
- throw takeObject(r2);
- }
- let v0;
- if (r0 !== 0) {
- v0 = getStringFromWasm0(r0, r1).slice();
- wasm.__wbindgen_free(r0, r1 * 1);
- }
- return v0;
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {number} index
- * @param {any} value
- * @param {any} datatype
- */
- insert(obj, index, value, datatype) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_insert(retptr, this.ptr, addHeapObject(obj), index, addHeapObject(value), addHeapObject(datatype));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {number} index
- * @param {any} value
- * @returns {string | undefined}
- */
- insertObject(obj, index, value) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_insertObject(retptr, this.ptr, addHeapObject(obj), index, addHeapObject(value));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- var r3 = getInt32Memory0()[retptr / 4 + 3];
- if (r3) {
- throw takeObject(r2);
- }
- let v0;
- if (r0 !== 0) {
- v0 = getStringFromWasm0(r0, r1).slice();
- wasm.__wbindgen_free(r0, r1 * 1);
- }
- return v0;
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} prop
- * @param {any} value
- * @param {any} datatype
- */
- put(obj, prop, value, datatype) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_put(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value), addHeapObject(datatype));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} prop
- * @param {any} value
- * @returns {any}
- */
- putObject(obj, prop, value) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_putObject(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} prop
- * @param {any} value
- */
- increment(obj, prop, value) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_increment(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} prop
- * @param {Array | undefined} heads
- * @returns {any}
- */
- get(obj, prop, heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_get(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), isLikeNone(heads) ? 0 : addHeapObject(heads));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} prop
- * @param {Array | undefined} heads
- * @returns {any}
- */
- getWithType(obj, prop, heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_getWithType(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), isLikeNone(heads) ? 0 : addHeapObject(heads));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} arg
- * @param {Array | undefined} heads
- * @returns {Array}
- */
- getAll(obj, arg, heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_getAll(retptr, this.ptr, addHeapObject(obj), addHeapObject(arg), isLikeNone(heads) ? 0 : addHeapObject(heads));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} enable
- * @returns {any}
- */
- enableFreeze(enable) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_enableFreeze(retptr, this.ptr, addHeapObject(enable));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} enable
- * @returns {any}
- */
- enablePatches(enable) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_enablePatches(retptr, this.ptr, addHeapObject(enable));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} datatype
- * @param {any} _function
- */
- registerDatatype(datatype, _function) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_registerDatatype(retptr, this.ptr, addHeapObject(datatype), addHeapObject(_function));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} object
- * @param {any} meta
- * @param {any} callback
- * @returns {any}
- */
- applyPatches(object, meta, callback) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_applyPatches(retptr, this.ptr, addHeapObject(object), addHeapObject(meta), addHeapObject(callback));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @returns {Array}
- */
- popPatches() {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_popPatches(retptr, this.ptr);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {Array | undefined} heads
- * @returns {number}
- */
- length(obj, heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_length(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads));
- var r0 = getFloat64Memory0()[retptr / 8 + 0];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- var r3 = getInt32Memory0()[retptr / 4 + 3];
- if (r3) {
- throw takeObject(r2);
- }
- return r0;
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {any} prop
- */
- delete(obj, prop) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_delete(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @returns {Uint8Array}
- */
- save() {
- const ret = wasm.automerge_save(this.ptr);
- return takeObject(ret);
- }
- /**
- * @returns {Uint8Array}
- */
- saveIncremental() {
- const ret = wasm.automerge_saveIncremental(this.ptr);
- return takeObject(ret);
- }
- /**
- * @param {Uint8Array} data
- * @returns {number}
- */
- loadIncremental(data) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_loadIncremental(retptr, this.ptr, addHeapObject(data));
- var r0 = getFloat64Memory0()[retptr / 8 + 0];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- var r3 = getInt32Memory0()[retptr / 4 + 3];
- if (r3) {
- throw takeObject(r2);
- }
- return r0;
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} changes
- */
- applyChanges(changes) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_applyChanges(retptr, this.ptr, addHeapObject(changes));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} have_deps
- * @returns {Array}
- */
- getChanges(have_deps) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_getChanges(retptr, this.ptr, addHeapObject(have_deps));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} hash
- * @returns {any}
- */
- getChangeByHash(hash) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_getChangeByHash(retptr, this.ptr, addHeapObject(hash));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {Automerge} other
- * @returns {Array}
- */
- getChangesAdded(other) {
- _assertClass(other, Automerge);
- const ret = wasm.automerge_getChangesAdded(this.ptr, other.ptr);
- return takeObject(ret);
- }
- /**
- * @returns {Array}
- */
- getHeads() {
- const ret = wasm.automerge_getHeads(this.ptr);
- return takeObject(ret);
- }
- /**
- * @returns {string}
- */
- getActorId() {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_getActorId(retptr, this.ptr);
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- return getStringFromWasm0(r0, r1);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- wasm.__wbindgen_free(r0, r1);
- }
- }
- /**
- * @returns {any}
- */
- getLastLocalChange() {
- const ret = wasm.automerge_getLastLocalChange(this.ptr);
- return takeObject(ret);
- }
- /**
- */
- dump() {
- wasm.automerge_dump(this.ptr);
- }
- /**
- * @param {Array | undefined} heads
- * @returns {Array}
- */
- getMissingDeps(heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_getMissingDeps(retptr, this.ptr, isLikeNone(heads) ? 0 : addHeapObject(heads));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {SyncState} state
- * @param {Uint8Array} message
- */
- receiveSyncMessage(state, message) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- _assertClass(state, SyncState);
- wasm.automerge_receiveSyncMessage(retptr, this.ptr, state.ptr, addHeapObject(message));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {SyncState} state
- * @returns {any}
- */
- generateSyncMessage(state) {
- _assertClass(state, SyncState);
- const ret = wasm.automerge_generateSyncMessage(this.ptr, state.ptr);
- return takeObject(ret);
- }
- /**
- * @param {any} meta
- * @returns {any}
- */
- toJS(meta) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_toJS(retptr, this.ptr, addHeapObject(meta));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} obj
- * @param {Array | undefined} heads
- * @param {any} meta
- * @returns {any}
- */
- materialize(obj, heads, meta) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.automerge_materialize(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads), addHeapObject(meta));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- var r2 = getInt32Memory0()[retptr / 4 + 2];
- if (r2) {
- throw takeObject(r1);
- }
- return takeObject(r0);
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {string | undefined} message
- * @param {number | undefined} time
- * @returns {any}
- */
- emptyChange(message, time) {
- var ptr0 = isLikeNone(message) ? 0 : passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- const ret = wasm.automerge_emptyChange(this.ptr, ptr0, len0, !isLikeNone(time), isLikeNone(time) ? 0 : time);
- return takeObject(ret);
- }
-}
-
-const SyncStateFinalization = new FinalizationRegistry(ptr => wasm.__wbg_syncstate_free(ptr));
-/**
-*/
-export class SyncState {
-
- static __wrap(ptr) {
- const obj = Object.create(SyncState.prototype);
- obj.ptr = ptr;
- SyncStateFinalization.register(obj, obj.ptr, obj);
- return obj;
- }
-
- __destroy_into_raw() {
- const ptr = this.ptr;
- this.ptr = 0;
- SyncStateFinalization.unregister(this);
- return ptr;
- }
-
- free() {
- const ptr = this.__destroy_into_raw();
- wasm.__wbg_syncstate_free(ptr);
- }
- /**
- * @returns {any}
- */
- get sharedHeads() {
- const ret = wasm.syncstate_sharedHeads(this.ptr);
- return takeObject(ret);
- }
- /**
- * @returns {any}
- */
- get lastSentHeads() {
- const ret = wasm.syncstate_lastSentHeads(this.ptr);
- return takeObject(ret);
- }
- /**
- * @param {any} heads
- */
- set lastSentHeads(heads) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.syncstate_set_lastSentHeads(retptr, this.ptr, addHeapObject(heads));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @param {any} hashes
- */
- set sentHashes(hashes) {
- try {
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
- wasm.syncstate_set_sentHashes(retptr, this.ptr, addHeapObject(hashes));
- var r0 = getInt32Memory0()[retptr / 4 + 0];
- var r1 = getInt32Memory0()[retptr / 4 + 1];
- if (r1) {
- throw takeObject(r0);
- }
- } finally {
- wasm.__wbindgen_add_to_stack_pointer(16);
- }
- }
- /**
- * @returns {SyncState}
- */
- clone() {
- const ret = wasm.syncstate_clone(this.ptr);
- return SyncState.__wrap(ret);
- }
-}
-
-const imports = {
- __wbindgen_placeholder__: {
- __wbindgen_string_new: function(arg0, arg1) {
- const ret = getStringFromWasm0(arg0, arg1);
- return addHeapObject(ret);
- },
- __wbindgen_object_drop_ref: function(arg0) {
- takeObject(arg0);
- },
- __wbindgen_object_clone_ref: function(arg0) {
- const ret = getObject(arg0);
- return addHeapObject(ret);
- },
- __wbindgen_is_undefined: function(arg0) {
- const ret = getObject(arg0) === undefined;
- return ret;
- },
- __wbindgen_string_get: function(arg0, arg1) {
- const obj = getObject(arg1);
- const ret = typeof(obj) === 'string' ? obj : undefined;
- var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- var len0 = WASM_VECTOR_LEN;
- getInt32Memory0()[arg0 / 4 + 1] = len0;
- getInt32Memory0()[arg0 / 4 + 0] = ptr0;
- },
- __wbindgen_number_new: function(arg0) {
- const ret = arg0;
- return addHeapObject(ret);
- },
- __wbindgen_is_function: function(arg0) {
- const ret = typeof(getObject(arg0)) === 'function';
- return ret;
- },
- __wbindgen_number_get: function(arg0, arg1) {
- const obj = getObject(arg1);
- const ret = typeof(obj) === 'number' ? obj : undefined;
- getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
- getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
- },
- __wbindgen_is_string: function(arg0) {
- const ret = typeof(getObject(arg0)) === 'string';
- return ret;
- },
- __wbindgen_is_null: function(arg0) {
- const ret = getObject(arg0) === null;
- return ret;
- },
- __wbindgen_boolean_get: function(arg0) {
- const v = getObject(arg0);
- const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
- return ret;
- },
- __wbindgen_json_serialize: function(arg0, arg1) {
- const obj = getObject(arg1);
- const ret = JSON.stringify(obj === undefined ? null : obj);
- const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- const len0 = WASM_VECTOR_LEN;
- getInt32Memory0()[arg0 / 4 + 1] = len0;
- getInt32Memory0()[arg0 / 4 + 0] = ptr0;
- },
- __wbindgen_error_new: function(arg0, arg1) {
- const ret = new Error(getStringFromWasm0(arg0, arg1));
- return addHeapObject(ret);
- },
- __wbg_new_abda76e883ba8a5f: function() {
- const ret = new Error();
- return addHeapObject(ret);
- },
- __wbg_stack_658279fe44541cf6: function(arg0, arg1) {
- const ret = getObject(arg1).stack;
- const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- const len0 = WASM_VECTOR_LEN;
- getInt32Memory0()[arg0 / 4 + 1] = len0;
- getInt32Memory0()[arg0 / 4 + 0] = ptr0;
- },
- __wbg_error_f851667af71bcfc6: function(arg0, arg1) {
- try {
- console.error(getStringFromWasm0(arg0, arg1));
- } finally {
- wasm.__wbindgen_free(arg0, arg1);
- }
- },
- __wbindgen_bigint_from_i64: function(arg0) {
- const ret = arg0;
- return addHeapObject(ret);
- },
- __wbindgen_bigint_from_u64: function(arg0) {
- const ret = BigInt.asUintN(64, arg0);
- return addHeapObject(ret);
- },
- __wbindgen_is_object: function(arg0) {
- const val = getObject(arg0);
- const ret = typeof(val) === 'object' && val !== null;
- return ret;
- },
- __wbindgen_jsval_loose_eq: function(arg0, arg1) {
- const ret = getObject(arg0) == getObject(arg1);
- return ret;
- },
- __wbg_String_91fba7ded13ba54c: function(arg0, arg1) {
- const ret = String(getObject(arg1));
- const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- const len0 = WASM_VECTOR_LEN;
- getInt32Memory0()[arg0 / 4 + 1] = len0;
- getInt32Memory0()[arg0 / 4 + 0] = ptr0;
- },
- __wbg_set_20cbc34131e76824: function(arg0, arg1, arg2) {
- getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
- },
- __wbg_crypto_e1d53a1d73fb10b8: function(arg0) {
- const ret = getObject(arg0).crypto;
- return addHeapObject(ret);
- },
- __wbg_process_038c26bf42b093f8: function(arg0) {
- const ret = getObject(arg0).process;
- return addHeapObject(ret);
- },
- __wbg_versions_ab37218d2f0b24a8: function(arg0) {
- const ret = getObject(arg0).versions;
- return addHeapObject(ret);
- },
- __wbg_node_080f4b19d15bc1fe: function(arg0) {
- const ret = getObject(arg0).node;
- return addHeapObject(ret);
- },
- __wbg_msCrypto_6e7d3e1f92610cbb: function(arg0) {
- const ret = getObject(arg0).msCrypto;
- return addHeapObject(ret);
- },
- __wbg_require_78a3dcfbdba9cbce: function() { return handleError(function () {
- const ret = module.require;
- return addHeapObject(ret);
- }, arguments) },
- __wbg_getRandomValues_805f1c3d65988a5a: function() { return handleError(function (arg0, arg1) {
- getObject(arg0).getRandomValues(getObject(arg1));
- }, arguments) },
- __wbg_randomFillSync_6894564c2c334c42: function() { return handleError(function (arg0, arg1, arg2) {
- getObject(arg0).randomFillSync(getArrayU8FromWasm0(arg1, arg2));
- }, arguments) },
- __wbg_log_4b5638ad60bdc54a: function(arg0) {
- console.log(getObject(arg0));
- },
- __wbg_log_89ca282a8a49b121: function(arg0, arg1) {
- console.log(getObject(arg0), getObject(arg1));
- },
- __wbg_get_57245cc7d7c7619d: function(arg0, arg1) {
- const ret = getObject(arg0)[arg1 >>> 0];
- return addHeapObject(ret);
- },
- __wbg_length_6e3bbe7c8bd4dbd8: function(arg0) {
- const ret = getObject(arg0).length;
- return ret;
- },
- __wbg_new_1d9a920c6bfc44a8: function() {
- const ret = new Array();
- return addHeapObject(ret);
- },
- __wbg_newnoargs_b5b063fc6c2f0376: function(arg0, arg1) {
- const ret = new Function(getStringFromWasm0(arg0, arg1));
- return addHeapObject(ret);
- },
- __wbg_next_579e583d33566a86: function(arg0) {
- const ret = getObject(arg0).next;
- return addHeapObject(ret);
- },
- __wbg_next_aaef7c8aa5e212ac: function() { return handleError(function (arg0) {
- const ret = getObject(arg0).next();
- return addHeapObject(ret);
- }, arguments) },
- __wbg_done_1b73b0672e15f234: function(arg0) {
- const ret = getObject(arg0).done;
- return ret;
- },
- __wbg_value_1ccc36bc03462d71: function(arg0) {
- const ret = getObject(arg0).value;
- return addHeapObject(ret);
- },
- __wbg_iterator_6f9d4f28845f426c: function() {
- const ret = Symbol.iterator;
- return addHeapObject(ret);
- },
- __wbg_get_765201544a2b6869: function() { return handleError(function (arg0, arg1) {
- const ret = Reflect.get(getObject(arg0), getObject(arg1));
- return addHeapObject(ret);
- }, arguments) },
- __wbg_call_97ae9d8645dc388b: function() { return handleError(function (arg0, arg1) {
- const ret = getObject(arg0).call(getObject(arg1));
- return addHeapObject(ret);
- }, arguments) },
- __wbg_new_0b9bfdd97583284e: function() {
- const ret = new Object();
- return addHeapObject(ret);
- },
- __wbg_length_f2ab5db52e68a619: function(arg0) {
- const ret = getObject(arg0).length;
- return ret;
- },
- __wbg_self_6d479506f72c6a71: function() { return handleError(function () {
- const ret = self.self;
- return addHeapObject(ret);
- }, arguments) },
- __wbg_window_f2557cc78490aceb: function() { return handleError(function () {
- const ret = window.window;
- return addHeapObject(ret);
- }, arguments) },
- __wbg_globalThis_7f206bda628d5286: function() { return handleError(function () {
- const ret = globalThis.globalThis;
- return addHeapObject(ret);
- }, arguments) },
- __wbg_global_ba75c50d1cf384f4: function() { return handleError(function () {
- const ret = global.global;
- return addHeapObject(ret);
- }, arguments) },
- __wbg_set_a68214f35c417fa9: function(arg0, arg1, arg2) {
- getObject(arg0)[arg1 >>> 0] = takeObject(arg2);
- },
- __wbg_from_7ce3cb27cb258569: function(arg0) {
- const ret = Array.from(getObject(arg0));
- return addHeapObject(ret);
- },
- __wbg_isArray_27c46c67f498e15d: function(arg0) {
- const ret = Array.isArray(getObject(arg0));
- return ret;
- },
- __wbg_push_740e4b286702d964: function(arg0, arg1) {
- const ret = getObject(arg0).push(getObject(arg1));
- return ret;
- },
- __wbg_unshift_1bf718f5eb23ad8a: function(arg0, arg1) {
- const ret = getObject(arg0).unshift(getObject(arg1));
- return ret;
- },
- __wbg_instanceof_ArrayBuffer_e5e48f4762c5610b: function(arg0) {
- let result;
- try {
- result = getObject(arg0) instanceof ArrayBuffer;
- } catch {
- result = false;
- }
- const ret = result;
- return ret;
- },
- __wbg_new_8d2af00bc1e329ee: function(arg0, arg1) {
- const ret = new Error(getStringFromWasm0(arg0, arg1));
- return addHeapObject(ret);
- },
- __wbg_call_168da88779e35f61: function() { return handleError(function (arg0, arg1, arg2) {
- const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
- return addHeapObject(ret);
- }, arguments) },
- __wbg_call_e1f72c051cdab859: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
- const ret = getObject(arg0).call(getObject(arg1), getObject(arg2), getObject(arg3), getObject(arg4));
- return addHeapObject(ret);
- }, arguments) },
- __wbg_instanceof_Date_b979044f17219415: function(arg0) {
- let result;
- try {
- result = getObject(arg0) instanceof Date;
- } catch {
- result = false;
- }
- const ret = result;
- return ret;
- },
- __wbg_getTime_cb82adb2556ed13e: function(arg0) {
- const ret = getObject(arg0).getTime();
- return ret;
- },
- __wbg_new_c8631234f931e1c4: function(arg0) {
- const ret = new Date(getObject(arg0));
- return addHeapObject(ret);
- },
- __wbg_instanceof_Object_595a1007518cbea3: function(arg0) {
- let result;
- try {
- result = getObject(arg0) instanceof Object;
- } catch {
- result = false;
- }
- const ret = result;
- return ret;
- },
- __wbg_assign_e3deabdbb7f0913d: function(arg0, arg1) {
- const ret = Object.assign(getObject(arg0), getObject(arg1));
- return addHeapObject(ret);
- },
- __wbg_defineProperty_e47dcaf04849e02c: function(arg0, arg1, arg2) {
- const ret = Object.defineProperty(getObject(arg0), getObject(arg1), getObject(arg2));
- return addHeapObject(ret);
- },
- __wbg_entries_65a76a413fc91037: function(arg0) {
- const ret = Object.entries(getObject(arg0));
- return addHeapObject(ret);
- },
- __wbg_freeze_863b0fb5229a1aa6: function(arg0) {
- const ret = Object.freeze(getObject(arg0));
- return addHeapObject(ret);
- },
- __wbg_keys_0702294afaeb6044: function(arg0) {
- const ret = Object.keys(getObject(arg0));
- return addHeapObject(ret);
- },
- __wbg_values_f72d246067c121fe: function(arg0) {
- const ret = Object.values(getObject(arg0));
- return addHeapObject(ret);
- },
- __wbg_concat_783dc3b16a989c3a: function(arg0, arg1) {
- const ret = getObject(arg0).concat(getObject(arg1));
- return addHeapObject(ret);
- },
- __wbg_slice_283900b9d91a5de8: function(arg0, arg1, arg2) {
- const ret = getObject(arg0).slice(arg1 >>> 0, arg2 >>> 0);
- return addHeapObject(ret);
- },
- __wbg_for_5dcca67bf52b18ca: function(arg0, arg1) {
- const ret = Symbol.for(getStringFromWasm0(arg0, arg1));
- return addHeapObject(ret);
- },
- __wbg_toString_1f0448acb8520180: function(arg0) {
- const ret = getObject(arg0).toString();
- return addHeapObject(ret);
- },
- __wbg_buffer_3f3d764d4747d564: function(arg0) {
- const ret = getObject(arg0).buffer;
- return addHeapObject(ret);
- },
- __wbg_newwithbyteoffsetandlength_d9aa266703cb98be: function(arg0, arg1, arg2) {
- const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
- return addHeapObject(ret);
- },
- __wbg_new_8c3f0052272a457a: function(arg0) {
- const ret = new Uint8Array(getObject(arg0));
- return addHeapObject(ret);
- },
- __wbg_set_83db9690f9353e79: function(arg0, arg1, arg2) {
- getObject(arg0).set(getObject(arg1), arg2 >>> 0);
- },
- __wbg_length_9e1ae1900cb0fbd5: function(arg0) {
- const ret = getObject(arg0).length;
- return ret;
- },
- __wbg_instanceof_Uint8Array_971eeda69eb75003: function(arg0) {
- let result;
- try {
- result = getObject(arg0) instanceof Uint8Array;
- } catch {
- result = false;
- }
- const ret = result;
- return ret;
- },
- __wbg_newwithlength_f5933855e4f48a19: function(arg0) {
- const ret = new Uint8Array(arg0 >>> 0);
- return addHeapObject(ret);
- },
- __wbg_subarray_58ad4efbb5bcb886: function(arg0, arg1, arg2) {
- const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
- return addHeapObject(ret);
- },
- __wbg_apply_75f7334893eef4ad: function() { return handleError(function (arg0, arg1, arg2) {
- const ret = Reflect.apply(getObject(arg0), getObject(arg1), getObject(arg2));
- return addHeapObject(ret);
- }, arguments) },
- __wbg_deleteProperty_424563545efc9635: function() { return handleError(function (arg0, arg1) {
- const ret = Reflect.deleteProperty(getObject(arg0), getObject(arg1));
- return ret;
- }, arguments) },
- __wbg_ownKeys_bf24e1178641d9f0: function() { return handleError(function (arg0) {
- const ret = Reflect.ownKeys(getObject(arg0));
- return addHeapObject(ret);
- }, arguments) },
- __wbg_set_bf3f89b92d5a34bf: function() { return handleError(function (arg0, arg1, arg2) {
- const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
- return ret;
- }, arguments) },
- __wbindgen_debug_string: function(arg0, arg1) {
- const ret = debugString(getObject(arg1));
- const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
- const len0 = WASM_VECTOR_LEN;
- getInt32Memory0()[arg0 / 4 + 1] = len0;
- getInt32Memory0()[arg0 / 4 + 0] = ptr0;
- },
- __wbindgen_throw: function(arg0, arg1) {
- throw new Error(getStringFromWasm0(arg0, arg1));
- },
- __wbindgen_memory: function() {
- const ret = wasm.memory;
- return addHeapObject(ret);
- },
- },
-
-};
-
-const wasm_url = new URL('automerge_wasm_bg.wasm', import.meta.url);
-let wasmCode = '';
-switch (wasm_url.protocol) {
- case 'file:':
- wasmCode = await Deno.readFile(wasm_url);
- break
- case 'https:':
- case 'http:':
- wasmCode = await (await fetch(wasm_url)).arrayBuffer();
- break
- default:
- throw new Error(`Unsupported protocol: ${wasm_url.protocol}`);
-}
-
-const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)).instance;
-const wasm = wasmInstance.exports;
-
diff --git a/deno_wasm_dist/automerge_wasm_bg.wasm b/deno_wasm_dist/automerge_wasm_bg.wasm
deleted file mode 100644
index 94444630..00000000
Binary files a/deno_wasm_dist/automerge_wasm_bg.wasm and /dev/null differ
diff --git a/deno_wasm_dist/index.d.ts b/deno_wasm_dist/index.d.ts
deleted file mode 100644
index 29586b47..00000000
--- a/deno_wasm_dist/index.d.ts
+++ /dev/null
@@ -1,232 +0,0 @@
-export type Actor = string;
-export type ObjID = string;
-export type Change = Uint8Array;
-export type SyncMessage = Uint8Array;
-export type Prop = string | number;
-export type Hash = string;
-export type Heads = Hash[];
-export type Value = string | number | boolean | null | Date | Uint8Array
-export type MaterializeValue = { [key:string]: MaterializeValue } | Array | Value
-export type ObjType = string | Array | { [key: string]: ObjType | Value }
-export type FullValue =
- ["str", string] |
- ["int", number] |
- ["uint", number] |
- ["f64", number] |
- ["boolean", boolean] |
- ["timestamp", Date] |
- ["counter", number] |
- ["bytes", Uint8Array] |
- ["null", null] |
- ["map", ObjID] |
- ["list", ObjID] |
- ["text", ObjID] |
- ["table", ObjID]
-
-export type FullValueWithId =
- ["str", string, ObjID ] |
- ["int", number, ObjID ] |
- ["uint", number, ObjID ] |
- ["f64", number, ObjID ] |
- ["boolean", boolean, ObjID ] |
- ["timestamp", Date, ObjID ] |
- ["counter", number, ObjID ] |
- ["bytes", Uint8Array, ObjID ] |
- ["null", null, ObjID ] |
- ["map", ObjID ] |
- ["list", ObjID] |
- ["text", ObjID] |
- ["table", ObjID]
-
-export enum ObjTypeName {
- list = "list",
- map = "map",
- table = "table",
- text = "text",
-}
-
-export type Datatype =
- "boolean" |
- "str" |
- "int" |
- "uint" |
- "f64" |
- "null" |
- "timestamp" |
- "counter" |
- "bytes" |
- "map" |
- "text" |
- "list";
-
-export type SyncHave = {
- lastSync: Heads,
- bloom: Uint8Array,
-}
-
-export type DecodedSyncMessage = {
- heads: Heads,
- need: Heads,
- have: SyncHave[]
- changes: Change[]
-}
-
-export type DecodedChange = {
- actor: Actor,
- seq: number
- startOp: number,
- time: number,
- message: string | null,
- deps: Heads,
- hash: Hash,
- ops: Op[]
-}
-
-type PartialBy = Omit & Partial>
-export type ChangeToEncode = PartialBy
-
-export type Op = {
- action: string,
- obj: ObjID,
- key: string,
- value?: string | number | boolean,
- datatype?: string,
- pred: string[],
-}
-
-export type Patch = PutPatch | DelPatch | SplicePatch | IncPatch;
-
-export type PutPatch = {
- action: 'put'
- path: Prop[],
- value: Value
- conflict: boolean
-}
-
-export type IncPatch = {
- action: 'inc'
- path: Prop[],
- value: number
-}
-
-export type DelPatch = {
- action: 'del'
- path: Prop[],
- length?: number,
-}
-
-export type SplicePatch = {
- action: 'splice'
- path: Prop[],
- values: Value[],
-}
-
-export function encodeChange(change: ChangeToEncode): Change;
-export function create(text_v2: boolean, actor?: Actor): Automerge;
-export function load(data: Uint8Array, text_v2: boolean, actor?: Actor): Automerge;
-export function decodeChange(change: Change): DecodedChange;
-export function initSyncState(): SyncState;
-export function encodeSyncMessage(message: DecodedSyncMessage): SyncMessage;
-export function decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage;
-export function encodeSyncState(state: SyncState): Uint8Array;
-export function decodeSyncState(data: Uint8Array): SyncState;
-export function exportSyncState(state: SyncState): JsSyncState;
-export function importSyncState(state: JsSyncState): SyncState;
-
-export interface API {
- create(text_v2: boolean, actor?: Actor): Automerge;
- load(data: Uint8Array, text_v2: boolean, actor?: Actor): Automerge;
- encodeChange(change: ChangeToEncode): Change;
- decodeChange(change: Change): DecodedChange;
- initSyncState(): SyncState;
- encodeSyncMessage(message: DecodedSyncMessage): SyncMessage;
- decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage;
- encodeSyncState(state: SyncState): Uint8Array;
- decodeSyncState(data: Uint8Array): SyncState;
- exportSyncState(state: SyncState): JsSyncState;
- importSyncState(state: JsSyncState): SyncState;
-}
-
-export class Automerge {
- // change state
- put(obj: ObjID, prop: Prop, value: Value, datatype?: Datatype): void;
- putObject(obj: ObjID, prop: Prop, value: ObjType): ObjID;
- insert(obj: ObjID, index: number, value: Value, datatype?: Datatype): void;
- insertObject(obj: ObjID, index: number, value: ObjType): ObjID;
- push(obj: ObjID, value: Value, datatype?: Datatype): void;
- pushObject(obj: ObjID, value: ObjType): ObjID;
- splice(obj: ObjID, start: number, delete_count: number, text?: string | Array): ObjID[] | undefined;
- increment(obj: ObjID, prop: Prop, value: number): void;
- delete(obj: ObjID, prop: Prop): void;
-
- // returns a single value - if there is a conflict return the winner
- get(obj: ObjID, prop: Prop, heads?: Heads): Value | undefined;
- getWithType(obj: ObjID, prop: Prop, heads?: Heads): FullValue | null;
- // return all values in case of a conflict
- getAll(obj: ObjID, arg: Prop, heads?: Heads): FullValueWithId[];
- keys(obj: ObjID, heads?: Heads): string[];
- text(obj: ObjID, heads?: Heads): string;
- length(obj: ObjID, heads?: Heads): number;
- materialize(obj?: ObjID, heads?: Heads, metadata?: unknown): MaterializeValue;
- toJS(): MaterializeValue;
-
- // transactions
- commit(message?: string, time?: number): Hash | null;
- emptyChange(message?: string, time?: number): Hash;
- merge(other: Automerge): Heads;
- getActorId(): Actor;
- pendingOps(): number;
- rollback(): number;
-
- // patches
- enablePatches(enable: boolean): boolean;
- enableFreeze(enable: boolean): boolean;
- registerDatatype(datatype: string, callback: Function): void;
- popPatches(): Patch[];
-
- // save and load to local store
- save(): Uint8Array;
- saveIncremental(): Uint8Array;
- loadIncremental(data: Uint8Array): number;
-
- // sync over network
- receiveSyncMessage(state: SyncState, message: SyncMessage): void;
- generateSyncMessage(state: SyncState): SyncMessage | null;
-
- // low level change functions
- applyChanges(changes: Change[]): void;
- getChanges(have_deps: Heads): Change[];
- getChangeByHash(hash: Hash): Change | null;
- getChangesAdded(other: Automerge): Change[];
- getHeads(): Heads;
- getLastLocalChange(): Change | null;
- getMissingDeps(heads?: Heads): Heads;
-
- // memory management
- free(): void; // only needed if weak-refs are unsupported
- clone(actor?: string): Automerge; // TODO - remove, this is dangerous
- fork(actor?: string, heads?: Heads): Automerge;
-
- // dump internal state to console.log - for debugging
- dump(): void;
-
- // experimental api can go here
- applyPatches(obj: Doc, meta?: unknown, callback?: (patch: Array, before: Doc, after: Doc) => void): Doc;
-}
-
-export interface JsSyncState {
- sharedHeads: Heads;
- lastSentHeads: Heads;
- theirHeads: Heads | undefined;
- theirHeed: Heads | undefined;
- theirHave: SyncHave[] | undefined;
- sentHashes: Heads;
-}
-
-export class SyncState {
- free(): void;
- clone(): SyncState;
- lastSentHeads: Heads;
- sentHashes: Heads;
- readonly sharedHeads: Heads;
-}
diff --git a/flake.nix b/flake.nix
index 4f9ba1fe..37835738 100644
--- a/flake.nix
+++ b/flake.nix
@@ -54,6 +54,7 @@
nodejs
yarn
+ deno
# c deps
cmake
diff --git a/javascript/.denoifyrc.json b/javascript/.denoifyrc.json
new file mode 100644
index 00000000..9453a31f
--- /dev/null
+++ b/javascript/.denoifyrc.json
@@ -0,0 +1,3 @@
+{
+ "replacer": "scripts/denoify-replacer.mjs"
+}
diff --git a/javascript/.eslintrc.cjs b/javascript/.eslintrc.cjs
index 5d11eb94..88776271 100644
--- a/javascript/.eslintrc.cjs
+++ b/javascript/.eslintrc.cjs
@@ -3,4 +3,13 @@ module.exports = {
parser: "@typescript-eslint/parser",
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
+ rules: {
+ "@typescript-eslint/no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^_",
+ varsIgnorePattern: "^_",
+ },
+ ],
+ },
}
diff --git a/javascript/.gitignore b/javascript/.gitignore
index ab4ec70d..f98d9db2 100644
--- a/javascript/.gitignore
+++ b/javascript/.gitignore
@@ -3,3 +3,4 @@
dist
docs/
.vim
+deno_dist/
diff --git a/javascript/.prettierignore b/javascript/.prettierignore
index c2dcd4bb..6ab2f796 100644
--- a/javascript/.prettierignore
+++ b/javascript/.prettierignore
@@ -1,3 +1,4 @@
e2e/verdacciodb
dist
docs
+deno_dist
diff --git a/javascript/config/cjs.json b/javascript/config/cjs.json
index fc500311..0b135067 100644
--- a/javascript/config/cjs.json
+++ b/javascript/config/cjs.json
@@ -1,6 +1,11 @@
{
"extends": "../tsconfig.json",
- "exclude": ["../dist/**/*", "../node_modules", "../test/**/*"],
+ "exclude": [
+ "../dist/**/*",
+ "../node_modules",
+ "../test/**/*",
+ "../src/**/*.deno.ts"
+ ],
"compilerOptions": {
"outDir": "../dist/cjs"
}
diff --git a/javascript/config/declonly.json b/javascript/config/declonly.json
index df615930..7c1df687 100644
--- a/javascript/config/declonly.json
+++ b/javascript/config/declonly.json
@@ -1,6 +1,11 @@
{
"extends": "../tsconfig.json",
- "exclude": ["../dist/**/*", "../node_modules", "../test/**/*"],
+ "exclude": [
+ "../dist/**/*",
+ "../node_modules",
+ "../test/**/*",
+ "../src/**/*.deno.ts"
+ ],
"emitDeclarationOnly": true,
"compilerOptions": {
"outDir": "../dist"
diff --git a/javascript/config/mjs.json b/javascript/config/mjs.json
index 2ee7a8b8..ecf3ce36 100644
--- a/javascript/config/mjs.json
+++ b/javascript/config/mjs.json
@@ -1,6 +1,11 @@
{
"extends": "../tsconfig.json",
- "exclude": ["../dist/**/*", "../node_modules", "../test/**/*"],
+ "exclude": [
+ "../dist/**/*",
+ "../node_modules",
+ "../test/**/*",
+ "../src/**/*.deno.ts"
+ ],
"compilerOptions": {
"target": "es6",
"module": "es6",
diff --git a/javascript/deno-tests/deno.ts b/javascript/deno-tests/deno.ts
new file mode 100644
index 00000000..fc0a4dad
--- /dev/null
+++ b/javascript/deno-tests/deno.ts
@@ -0,0 +1,10 @@
+import * as Automerge from "../deno_dist/index.ts"
+
+Deno.test("It should create, clone and free", () => {
+ let doc1 = Automerge.init()
+ let doc2 = Automerge.clone(doc1)
+
+ // this is only needed if weakrefs are not supported
+ Automerge.free(doc1)
+ Automerge.free(doc2)
+})
diff --git a/javascript/examples/create-react-app/yarn.lock b/javascript/examples/create-react-app/yarn.lock
index d6e5d93f..ec83af3b 100644
--- a/javascript/examples/create-react-app/yarn.lock
+++ b/javascript/examples/create-react-app/yarn.lock
@@ -5845,9 +5845,9 @@ json-stable-stringify-without-jsonify@^1.0.1:
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
json5@^1.0.1:
- version "1.0.1"
- resolved "http://localhost:4873/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
- integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
+ integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
dependencies:
minimist "^1.2.0"
@@ -6165,9 +6165,9 @@ minimatch@^5.0.1:
brace-expansion "^2.0.1"
minimist@^1.2.0, minimist@^1.2.6:
- version "1.2.6"
- resolved "http://localhost:4873/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
- integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
+ integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
mkdirp@~0.5.1:
version "0.5.6"
diff --git a/javascript/package.json b/javascript/package.json
index 53cc6fdc..79309907 100644
--- a/javascript/package.json
+++ b/javascript/package.json
@@ -4,7 +4,7 @@
"Orion Henry ",
"Martin Kleppmann"
],
- "version": "2.0.1-alpha.3",
+ "version": "2.0.2",
"description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm",
"homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript",
"repository": "github:automerge/automerge-rs",
@@ -25,6 +25,8 @@
"lint": "eslint src",
"build": "tsc -p config/mjs.json && tsc -p config/cjs.json && tsc -p config/declonly.json --emitDeclarationOnly",
"test": "ts-mocha test/*.ts",
+ "deno:build": "denoify && node ./scripts/deno-prefixer.mjs",
+ "deno:test": "deno test ./deno-tests/deno.ts --allow-read --allow-net",
"watch-docs": "typedoc src/index.ts --watch --readme none"
},
"devDependencies": {
@@ -33,6 +35,7 @@
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5.46.0",
"@typescript-eslint/parser": "^5.46.0",
+ "denoify": "^1.4.5",
"eslint": "^8.29.0",
"fast-sha256": "^1.3.0",
"mocha": "^10.2.0",
@@ -44,7 +47,7 @@
"typescript": "^4.9.4"
},
"dependencies": {
- "@automerge/automerge-wasm": "0.1.21",
+ "@automerge/automerge-wasm": "0.1.25",
"uuid": "^9.0.0"
}
}
diff --git a/javascript/scripts/deno-prefixer.mjs b/javascript/scripts/deno-prefixer.mjs
new file mode 100644
index 00000000..28544102
--- /dev/null
+++ b/javascript/scripts/deno-prefixer.mjs
@@ -0,0 +1,9 @@
+import * as fs from "fs"
+
+const files = ["./deno_dist/proxies.ts"]
+for (const filepath of files) {
+ const data = fs.readFileSync(filepath)
+ fs.writeFileSync(filepath, "// @ts-nocheck \n" + data)
+
+ console.log('Prepended "// @ts-nocheck" to ' + filepath)
+}
diff --git a/javascript/scripts/denoify-replacer.mjs b/javascript/scripts/denoify-replacer.mjs
new file mode 100644
index 00000000..e183ba0d
--- /dev/null
+++ b/javascript/scripts/denoify-replacer.mjs
@@ -0,0 +1,42 @@
+// @denoify-ignore
+
+import { makeThisModuleAnExecutableReplacer } from "denoify"
+// import { assert } from "tsafe";
+// import * as path from "path";
+
+makeThisModuleAnExecutableReplacer(
+ async ({ parsedImportExportStatement, destDirPath, version }) => {
+ version = process.env.VERSION || version
+
+ switch (parsedImportExportStatement.parsedArgument.nodeModuleName) {
+ case "@automerge/automerge-wasm":
+ {
+ const moduleRoot =
+ process.env.ROOT_MODULE ||
+ `https://deno.land/x/automerge_wasm@${version}`
+ /*
+ *We expect not to run against statements like
+ *import(..).then(...)
+ *or
+ *export * from "..."
+ *in our code.
+ */
+ if (
+ !parsedImportExportStatement.isAsyncImport &&
+ (parsedImportExportStatement.statementType === "import" ||
+ parsedImportExportStatement.statementType === "export")
+ ) {
+ if (parsedImportExportStatement.isTypeOnly) {
+ return `${parsedImportExportStatement.statementType} type ${parsedImportExportStatement.target} from "${moduleRoot}/index.d.ts";`
+ } else {
+ return `${parsedImportExportStatement.statementType} ${parsedImportExportStatement.target} from "${moduleRoot}/automerge_wasm.js";`
+ }
+ }
+ }
+ break
+ }
+
+ //The replacer should return undefined when we want to let denoify replace the statement
+ return undefined
+ }
+)
diff --git a/javascript/src/conflicts.ts b/javascript/src/conflicts.ts
new file mode 100644
index 00000000..52af23e1
--- /dev/null
+++ b/javascript/src/conflicts.ts
@@ -0,0 +1,100 @@
+import { Counter, type AutomergeValue } from "./types"
+import { Text } from "./text"
+import { type AutomergeValue as UnstableAutomergeValue } from "./unstable_types"
+import { type Target, Text1Target, Text2Target } from "./proxies"
+import { mapProxy, listProxy, ValueType } from "./proxies"
+import type { Prop, ObjID } from "@automerge/automerge-wasm"
+import { Automerge } from "@automerge/automerge-wasm"
+
+export type ConflictsF = { [key: string]: ValueType }
+export type Conflicts = ConflictsF
+export type UnstableConflicts = ConflictsF
+
+export function stableConflictAt(
+ context: Automerge,
+ objectId: ObjID,
+ prop: Prop
+): Conflicts | undefined {
+ return conflictAt(
+ context,
+ objectId,
+ prop,
+ true,
+ (context: Automerge, conflictId: ObjID): AutomergeValue => {
+ return new Text(context.text(conflictId))
+ }
+ )
+}
+
+export function unstableConflictAt(
+ context: Automerge,
+ objectId: ObjID,
+ prop: Prop
+): UnstableConflicts | undefined {
+ return conflictAt(
+ context,
+ objectId,
+ prop,
+ true,
+ (context: Automerge, conflictId: ObjID): UnstableAutomergeValue => {
+ return context.text(conflictId)
+ }
+ )
+}
+
+function conflictAt(
+ context: Automerge,
+ objectId: ObjID,
+ prop: Prop,
+ textV2: boolean,
+ handleText: (a: Automerge, conflictId: ObjID) => ValueType
+): ConflictsF | undefined {
+ const values = context.getAll(objectId, prop)
+ if (values.length <= 1) {
+ return
+ }
+ const result: ConflictsF = {}
+ for (const fullVal of values) {
+ switch (fullVal[0]) {
+ case "map":
+ result[fullVal[1]] = mapProxy(
+ context,
+ fullVal[1],
+ textV2,
+ [prop],
+ true
+ )
+ break
+ case "list":
+ result[fullVal[1]] = listProxy(
+ context,
+ fullVal[1],
+ textV2,
+ [prop],
+ true
+ )
+ break
+ case "text":
+ result[fullVal[1]] = handleText(context, fullVal[1] as ObjID)
+ break
+ case "str":
+ case "uint":
+ case "int":
+ case "f64":
+ case "boolean":
+ case "bytes":
+ case "null":
+ result[fullVal[2]] = fullVal[1] as ValueType
+ break
+ case "counter":
+ result[fullVal[2]] = new Counter(fullVal[1]) as ValueType
+ break
+ case "timestamp":
+ result[fullVal[2]] = new Date(fullVal[1]) as ValueType
+ break
+ default:
+ throw RangeError(`datatype ${fullVal[0]} unimplemented`)
+ }
+ }
+ return result
+}
diff --git a/javascript/src/constants.ts b/javascript/src/constants.ts
index d3bd8138..7b714772 100644
--- a/javascript/src/constants.ts
+++ b/javascript/src/constants.ts
@@ -2,7 +2,7 @@
export const STATE = Symbol.for("_am_meta") // symbol used to hide application metadata on automerge objects
export const TRACE = Symbol.for("_am_trace") // used for debugging
-export const OBJECT_ID = Symbol.for("_am_objectId") // synbol used to hide the object id on automerge objects
+export const OBJECT_ID = Symbol.for("_am_objectId") // symbol used to hide the object id on automerge objects
export const IS_PROXY = Symbol.for("_am_isProxy") // symbol used to test if the document is a proxy object
export const UINT = Symbol.for("_am_uint")
diff --git a/javascript/src/counter.ts b/javascript/src/counter.ts
index 6b9ad277..88adb840 100644
--- a/javascript/src/counter.ts
+++ b/javascript/src/counter.ts
@@ -1,4 +1,4 @@
-import { Automerge, ObjID, Prop } from "@automerge/automerge-wasm"
+import { Automerge, type ObjID, type Prop } from "@automerge/automerge-wasm"
import { COUNTER } from "./constants"
/**
* The most basic CRDT: an integer value that can be changed only by
@@ -100,7 +100,7 @@ export function getWriteableCounter(
path: Prop[],
objectId: ObjID,
key: Prop
-) {
+): WriteableCounter {
return new WriteableCounter(value, context, path, objectId, key)
}
diff --git a/javascript/src/internal_state.ts b/javascript/src/internal_state.ts
index 92ab648e..f3da49b1 100644
--- a/javascript/src/internal_state.ts
+++ b/javascript/src/internal_state.ts
@@ -1,8 +1,8 @@
-import { ObjID, Heads, Automerge } from "@automerge/automerge-wasm"
+import { type ObjID, type Heads, Automerge } from "@automerge/automerge-wasm"
import { STATE, OBJECT_ID, TRACE, IS_PROXY } from "./constants"
-import { type Doc, PatchCallback } from "./types"
+import type { Doc, PatchCallback } from "./types"
export interface InternalState {
handle: Automerge
diff --git a/javascript/src/low_level.ts b/javascript/src/low_level.ts
index 94ac63db..f44f3a32 100644
--- a/javascript/src/low_level.ts
+++ b/javascript/src/low_level.ts
@@ -1,20 +1,21 @@
import {
+ type API,
Automerge,
- Change,
- DecodedChange,
- Actor,
+ type Change,
+ type DecodedChange,
+ type Actor,
SyncState,
- SyncMessage,
- JsSyncState,
- DecodedSyncMessage,
- ChangeToEncode,
+ type SyncMessage,
+ type JsSyncState,
+ type DecodedSyncMessage,
+ type ChangeToEncode,
} from "@automerge/automerge-wasm"
-export { ChangeToEncode } from "@automerge/automerge-wasm"
-import { API } from "@automerge/automerge-wasm"
+export type { ChangeToEncode } from "@automerge/automerge-wasm"
export function UseApi(api: API) {
for (const k in api) {
- ApiHandler[k] = api[k]
+ // eslint-disable-next-line @typescript-eslint/no-extra-semi,@typescript-eslint/no-explicit-any
+ ;(ApiHandler as any)[k] = (api as any)[k]
}
}
diff --git a/javascript/src/numbers.ts b/javascript/src/numbers.ts
index d52a36c5..7ad95998 100644
--- a/javascript/src/numbers.ts
+++ b/javascript/src/numbers.ts
@@ -1,4 +1,4 @@
-// Convience classes to allow users to stricly specify the number type they want
+// Convenience classes to allow users to strictly specify the number type they want
import { INT, UINT, F64 } from "./constants"
diff --git a/javascript/src/proxies.ts b/javascript/src/proxies.ts
index 3fb3a825..54a8dd71 100644
--- a/javascript/src/proxies.ts
+++ b/javascript/src/proxies.ts
@@ -1,13 +1,18 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
import { Text } from "./text"
-import { Automerge, Heads, ObjID } from "@automerge/automerge-wasm"
-import { Prop } from "@automerge/automerge-wasm"
import {
- AutomergeValue,
- ScalarValue,
- MapValue,
- ListValue,
- TextValue,
-} from "./types"
+ Automerge,
+ type Heads,
+ type ObjID,
+ type Prop,
+} from "@automerge/automerge-wasm"
+
+import type { AutomergeValue, ScalarValue, MapValue, ListValue } from "./types"
+import {
+ type AutomergeValue as UnstableAutomergeValue,
+ MapValue as UnstableMapValue,
+ ListValue as UnstableListValue,
+} from "./unstable_types"
import { Counter, getWriteableCounter } from "./counter"
import {
STATE,
@@ -21,19 +26,38 @@ import {
} from "./constants"
import { RawString } from "./raw_string"
-type Target = {
+type TargetCommon = {
context: Automerge
objectId: ObjID
path: Array
readonly: boolean
heads?: Array
- cache: {}
+ cache: object
trace?: any
frozen: boolean
- textV2: boolean
}
-function parseListIndex(key) {
+export type Text2Target = TargetCommon & { textV2: true }
+export type Text1Target = TargetCommon & { textV2: false }
+export type Target = Text1Target | Text2Target
+
+export type ValueType = T extends Text2Target
+ ? UnstableAutomergeValue
+ : T extends Text1Target
+ ? AutomergeValue
+ : never
+type MapValueType = T extends Text2Target
+ ? UnstableMapValue
+ : T extends Text1Target
+ ? MapValue
+ : never
+type ListValueType = T extends Text2Target
+ ? UnstableListValue
+ : T extends Text1Target
+ ? ListValue
+ : never
+
+function parseListIndex(key: any) {
if (typeof key === "string" && /^[0-9]+$/.test(key)) key = parseInt(key, 10)
if (typeof key !== "number") {
return key
@@ -44,7 +68,10 @@ function parseListIndex(key) {
return key
}
-function valueAt(target: Target, prop: Prop): AutomergeValue | undefined {
+function valueAt(
+ target: T,
+ prop: Prop
+): ValueType | undefined {
const { context, objectId, path, readonly, heads, textV2 } = target
const value = context.getWithType(objectId, prop, heads)
if (value === null) {
@@ -56,7 +83,7 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined {
case undefined:
return
case "map":
- return mapProxy(
+ return mapProxy(
context,
val as ObjID,
textV2,
@@ -65,7 +92,7 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined {
heads
)
case "list":
- return listProxy(
+ return listProxy(
context,
val as ObjID,
textV2,
@@ -75,7 +102,7 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined {
)
case "text":
if (textV2) {
- return context.text(val as ObjID, heads)
+ return context.text(val as ObjID, heads) as ValueType
} else {
return textProxy(
context,
@@ -83,29 +110,36 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined {
[...path, prop],
readonly,
heads
- )
+ ) as unknown as ValueType
}
case "str":
- return val
+ return val as ValueType
case "uint":
- return val
+ return val as ValueType
case "int":
- return val
+ return val as ValueType
case "f64":
- return val
+ return val as ValueType
case "boolean":
- return val
+ return val as ValueType
case "null":
- return null
+ return null as ValueType
case "bytes":
- return val
+ return val as ValueType
case "timestamp":
- return val
+ return val as ValueType
case "counter": {
if (readonly) {
- return new Counter(val as number)
+ return new Counter(val as number) as ValueType
} else {
- return getWriteableCounter(val as number, context, path, objectId, prop)
+ const counter: Counter = getWriteableCounter(
+ val as number,
+ context,
+ path,
+ objectId,
+ prop
+ )
+ return counter as ValueType
}
}
default:
@@ -113,7 +147,21 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined {
}
}
-function import_value(value: any, textV2: boolean) {
+type ImportedValue =
+ | [null, "null"]
+ | [number, "uint"]
+ | [number, "int"]
+ | [number, "f64"]
+ | [number, "counter"]
+ | [number, "timestamp"]
+ | [string, "str"]
+ | [Text | string, "text"]
+ | [Uint8Array, "bytes"]
+ | [Array, "list"]
+ | [Record, "map"]
+ | [boolean, "boolean"]
+
+function import_value(value: any, textV2: boolean): ImportedValue {
switch (typeof value) {
case "object":
if (value == null) {
@@ -165,7 +213,10 @@ function import_value(value: any, textV2: boolean) {
}
const MapHandler = {
- get(target: Target, key): AutomergeValue | { handle: Automerge } {
+ get(
+ target: T,
+ key: any
+ ): ValueType | ObjID | boolean | { handle: Automerge } {
const { context, objectId, cache } = target
if (key === Symbol.toStringTag) {
return target[Symbol.toStringTag]
@@ -180,7 +231,7 @@ const MapHandler = {
return cache[key]
},
- set(target: Target, key, val) {
+ set(target: Target, key: any, val: any) {
const { context, objectId, path, readonly, frozen, textV2 } = target
target.cache = {} // reset cache on set
if (val && val[OBJECT_ID]) {
@@ -216,8 +267,10 @@ const MapHandler = {
}
case "text": {
if (textV2) {
+ assertString(value)
context.putObject(objectId, key, value)
} else {
+ assertText(value)
const text = context.putObject(objectId, key, "")
const proxyText = textProxy(context, text, [...path, key], readonly)
for (let i = 0; i < value.length; i++) {
@@ -246,7 +299,7 @@ const MapHandler = {
return true
},
- deleteProperty(target: Target, key) {
+ deleteProperty(target: Target, key: any) {
const { context, objectId, readonly } = target
target.cache = {} // reset cache on delete
if (readonly) {
@@ -256,12 +309,12 @@ const MapHandler = {
return true
},
- has(target: Target, key) {
+ has(target: Target, key: any) {
const value = this.get(target, key)
return value !== undefined
},
- getOwnPropertyDescriptor(target: Target, key) {
+ getOwnPropertyDescriptor(target: Target, key: any) {
// const { context, objectId } = target
const value = this.get(target, key)
if (typeof value !== "undefined") {
@@ -282,11 +335,20 @@ const MapHandler = {
}
const ListHandler = {
- get(target: Target, index) {
+ get(
+ target: T,
+ index: any
+ ):
+ | ValueType
+ | boolean
+ | ObjID
+ | { handle: Automerge }
+ | number
+ | ((_: any) => boolean) {
const { context, objectId, heads } = target
index = parseListIndex(index)
if (index === Symbol.hasInstance) {
- return instance => {
+ return (instance: any) => {
return Array.isArray(instance)
}
}
@@ -299,13 +361,13 @@ const ListHandler = {
if (index === STATE) return { handle: context }
if (index === "length") return context.length(objectId, heads)
if (typeof index === "number") {
- return valueAt(target, index)
+ return valueAt(target, index) as ValueType
} else {
return listMethods(target)[index]
}
},
- set(target: Target, index, val) {
+ set(target: Target, index: any, val: any) {
const { context, objectId, path, readonly, frozen, textV2 } = target
index = parseListIndex(index)
if (val && val[OBJECT_ID]) {
@@ -329,7 +391,7 @@ const ListHandler = {
}
switch (datatype) {
case "list": {
- let list
+ let list: ObjID
if (index >= context.length(objectId)) {
list = context.insertObject(objectId, index, [])
} else {
@@ -347,13 +409,15 @@ const ListHandler = {
}
case "text": {
if (textV2) {
+ assertString(value)
if (index >= context.length(objectId)) {
context.insertObject(objectId, index, value)
} else {
context.putObject(objectId, index, value)
}
} else {
- let text
+ let text: ObjID
+ assertText(value)
if (index >= context.length(objectId)) {
text = context.insertObject(objectId, index, "")
} else {
@@ -365,7 +429,7 @@ const ListHandler = {
break
}
case "map": {
- let map
+ let map: ObjID
if (index >= context.length(objectId)) {
map = context.insertObject(objectId, index, {})
} else {
@@ -393,7 +457,7 @@ const ListHandler = {
return true
},
- deleteProperty(target: Target, index) {
+ deleteProperty(target: Target, index: any) {
const { context, objectId } = target
index = parseListIndex(index)
const elem = context.get(objectId, index)
@@ -406,7 +470,7 @@ const ListHandler = {
return true
},
- has(target: Target, index) {
+ has(target: Target, index: any) {
const { context, objectId, heads } = target
index = parseListIndex(index)
if (typeof index === "number") {
@@ -415,7 +479,7 @@ const ListHandler = {
return index === "length"
},
- getOwnPropertyDescriptor(target: Target, index) {
+ getOwnPropertyDescriptor(target: Target, index: any) {
const { context, objectId, heads } = target
if (index === "length")
@@ -429,7 +493,7 @@ const ListHandler = {
return { configurable: true, enumerable: true, value }
},
- getPrototypeOf(target) {
+ getPrototypeOf(target: Target) {
return Object.getPrototypeOf(target)
},
ownKeys(/*target*/): string[] {
@@ -471,14 +535,14 @@ const TextHandler = Object.assign({}, ListHandler, {
},
})
-export function mapProxy(
+export function mapProxy(
context: Automerge,
objectId: ObjID,
textV2: boolean,
path?: Prop[],
readonly?: boolean,
heads?: Heads
-): MapValue {
+): MapValueType {
const target: Target = {
context,
objectId,
@@ -491,19 +555,19 @@ export function mapProxy(
}
const proxied = {}
Object.assign(proxied, target)
- let result = new Proxy(proxied, MapHandler)
+ const result = new Proxy(proxied, MapHandler)
// conversion through unknown is necessary because the types are so different
- return result as unknown as MapValue
+ return result as unknown as MapValueType
}
-export function listProxy(
+export function listProxy(
context: Automerge,
objectId: ObjID,
textV2: boolean,
path?: Prop[],
readonly?: boolean,
heads?: Heads
-): ListValue {
+): ListValueType {
const target: Target = {
context,
objectId,
@@ -516,17 +580,22 @@ export function listProxy(
}
const proxied = []
Object.assign(proxied, target)
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return new Proxy(proxied, ListHandler) as unknown as ListValue
}
+interface TextProxy extends Text {
+ splice: (index: any, del: any, ...vals: any[]) => void
+}
+
export function textProxy(
context: Automerge,
objectId: ObjID,
path?: Prop[],
readonly?: boolean,
heads?: Heads
-): TextValue {
+): TextProxy {
const target: Target = {
context,
objectId,
@@ -537,7 +606,9 @@ export function textProxy(
cache: {},
textV2: false,
}
- return new Proxy(target, TextHandler) as unknown as TextValue
+ const proxied = {}
+ Object.assign(proxied, target)
+ return new Proxy(proxied, TextHandler) as unknown as TextProxy
}
export function rootProxy(
@@ -549,10 +620,10 @@ export function rootProxy(
return mapProxy(context, "_root", textV2, [], !!readonly)
}
-function listMethods(target: Target) {
+function listMethods(target: T) {
const { context, objectId, path, readonly, frozen, heads, textV2 } = target
const methods = {
- deleteAt(index, numDelete) {
+ deleteAt(index: number, numDelete: number) {
if (typeof numDelete === "number") {
context.splice(objectId, index, numDelete)
} else {
@@ -567,8 +638,20 @@ function listMethods(target: Target) {
start = parseListIndex(start || 0)
end = parseListIndex(end || length)
for (let i = start; i < Math.min(end, length); i++) {
- if (datatype === "text" || datatype === "list" || datatype === "map") {
+ if (datatype === "list" || datatype === "map") {
context.putObject(objectId, i, value)
+ } else if (datatype === "text") {
+ if (textV2) {
+ assertString(value)
+ context.putObject(objectId, i, value)
+ } else {
+ assertText(value)
+ const text = context.putObject(objectId, i, "")
+ const proxyText = textProxy(context, text, [...path, i], readonly)
+ for (let i = 0; i < value.length; i++) {
+ proxyText[i] = value.get(i)
+ }
+ }
} else {
context.put(objectId, i, value, datatype)
}
@@ -576,7 +659,7 @@ function listMethods(target: Target) {
return this
},
- indexOf(o, start = 0) {
+ indexOf(o: any, start = 0) {
const length = context.length(objectId)
for (let i = start; i < length; i++) {
const value = context.getWithType(objectId, i, heads)
@@ -587,7 +670,7 @@ function listMethods(target: Target) {
return -1
},
- insertAt(index, ...values) {
+ insertAt(index: number, ...values: any[]) {
this.splice(index, 0, ...values)
return this
},
@@ -602,7 +685,7 @@ function listMethods(target: Target) {
return last
},
- push(...values) {
+ push(...values: any[]) {
const len = context.length(objectId)
this.splice(len, 0, ...values)
return context.length(objectId)
@@ -615,7 +698,7 @@ function listMethods(target: Target) {
return first
},
- splice(index, del, ...vals) {
+ splice(index: any, del: any, ...vals: any[]) {
index = parseListIndex(index)
del = parseListIndex(del)
for (const val of vals) {
@@ -633,9 +716,9 @@ function listMethods(target: Target) {
"Sequence object cannot be modified outside of a change block"
)
}
- const result: AutomergeValue[] = []
+ const result: ValueType[] = []
for (let i = 0; i < del; i++) {
- const value = valueAt(target, index)
+ const value = valueAt(target, index)
if (value !== undefined) {
result.push(value)
}
@@ -658,6 +741,7 @@ function listMethods(target: Target) {
}
case "text": {
if (textV2) {
+ assertString(value)
context.insertObject(objectId, index, value)
} else {
const text = context.insertObject(objectId, index, "")
@@ -693,7 +777,7 @@ function listMethods(target: Target) {
return result
},
- unshift(...values) {
+ unshift(...values: any) {
this.splice(0, 0, ...values)
return context.length(objectId)
},
@@ -744,11 +828,11 @@ function listMethods(target: Target) {
return iterator
},
- toArray(): AutomergeValue[] {
- const list: AutomergeValue = []
- let value
+ toArray(): ValueType[] {
+ const list: Array> = []
+ let value: ValueType | undefined
do {
- value = valueAt(target, list.length)
+ value = valueAt(target, list.length)
if (value !== undefined) {
list.push(value)
}
@@ -757,7 +841,7 @@ function listMethods(target: Target) {
return list
},
- map(f: (AutomergeValue, number) => T): T[] {
+ map(f: (_a: ValueType, _n: number) => U): U[] {
return this.toArray().map(f)
},
@@ -769,24 +853,26 @@ function listMethods(target: Target) {
return this.toArray().toLocaleString()
},
- forEach(f: (AutomergeValue, number) => undefined) {
+ forEach(f: (_a: ValueType, _n: number) => undefined) {
return this.toArray().forEach(f)
},
// todo: real concat function is different
- concat(other: AutomergeValue[]): AutomergeValue[] {
+ concat(other: ValueType[]): ValueType[] {
return this.toArray().concat(other)
},
- every(f: (AutomergeValue, number) => boolean): boolean {
+ every(f: (_a: ValueType, _n: number) => boolean): boolean {
return this.toArray().every(f)
},
- filter(f: (AutomergeValue, number) => boolean): AutomergeValue[] {
+ filter(f: (_a: ValueType, _n: number) => boolean): ValueType[] {
return this.toArray().filter(f)
},
- find(f: (AutomergeValue, number) => boolean): AutomergeValue | undefined {
+ find(
+ f: (_a: ValueType, _n: number) => boolean
+ ): ValueType | undefined {
let index = 0
for (const v of this) {
if (f(v, index)) {
@@ -796,7 +882,7 @@ function listMethods(target: Target) {
}
},
- findIndex(f: (AutomergeValue, number) => boolean): number {
+ findIndex(f: (_a: ValueType, _n: number) => boolean): number {
let index = 0
for (const v of this) {
if (f(v, index)) {
@@ -807,7 +893,7 @@ function listMethods(target: Target) {
return -1
},
- includes(elem: AutomergeValue): boolean {
+ includes(elem: ValueType): boolean {
return this.find(e => e === elem) !== undefined
},
@@ -815,29 +901,30 @@ function listMethods(target: Target) {
return this.toArray().join(sep)
},
- // todo: remove the any
- reduce(f: (any, AutomergeValue) => T, initalValue?: T): T | undefined {
- return this.toArray().reduce(f, initalValue)
+ reduce(
+ f: (acc: U, currentValue: ValueType) => U,
+ initialValue: U
+ ): U | undefined {
+ return this.toArray().reduce(f, initialValue)
},
- // todo: remove the any
- reduceRight(
- f: (any, AutomergeValue) => T,
- initalValue?: T
- ): T | undefined {
- return this.toArray().reduceRight(f, initalValue)
+ reduceRight(
+ f: (acc: U, item: ValueType) => U,
+ initialValue: U
+ ): U | undefined {
+ return this.toArray().reduceRight(f, initialValue)
},
- lastIndexOf(search: AutomergeValue, fromIndex = +Infinity): number {
+ lastIndexOf(search: ValueType, fromIndex = +Infinity): number {
// this can be faster
return this.toArray().lastIndexOf(search, fromIndex)
},
- slice(index?: number, num?: number): AutomergeValue[] {
+ slice(index?: number, num?: number): ValueType[] {
return this.toArray().slice(index, num)
},
- some(f: (AutomergeValue, number) => boolean): boolean {
+ some(f: (v: ValueType, i: number) => boolean): boolean {
let index = 0
for (const v of this) {
if (f(v, index)) {
@@ -864,7 +951,7 @@ function listMethods(target: Target) {
function textMethods(target: Target) {
const { context, objectId, heads } = target
const methods = {
- set(index: number, value) {
+ set(index: number, value: any) {
return (this[index] = value)
},
get(index: number): AutomergeValue {
@@ -897,10 +984,22 @@ function textMethods(target: Target) {
toJSON(): string {
return this.toString()
},
- indexOf(o, start = 0) {
+ indexOf(o: any, start = 0) {
const text = context.text(objectId)
return text.indexOf(o, start)
},
}
return methods
}
+
+function assertText(value: Text | string): asserts value is Text {
+ if (!(value instanceof Text)) {
+ throw new Error("value was not a Text instance")
+ }
+}
+
+function assertString(value: Text | string): asserts value is string {
+ if (typeof value !== "string") {
+ throw new Error("value was not a string")
+ }
+}
diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts
index c52d0a4c..e83b127f 100644
--- a/javascript/src/stable.ts
+++ b/javascript/src/stable.ts
@@ -1,55 +1,72 @@
/** @hidden **/
export { /** @hidden */ uuid } from "./uuid"
-import { rootProxy, listProxy, mapProxy, textProxy } from "./proxies"
+import { rootProxy } from "./proxies"
import { STATE } from "./constants"
-import { AutomergeValue, Counter, Doc, PatchCallback } from "./types"
-export {
- AutomergeValue,
+import {
+ type AutomergeValue,
Counter,
- Doc,
+ type Doc,
+ type PatchCallback,
+} from "./types"
+export {
+ type AutomergeValue,
+ Counter,
+ type Doc,
Int,
Uint,
Float64,
- Patch,
- PatchCallback,
- ScalarValue,
- Text,
+ type Patch,
+ type PatchCallback,
+ type ScalarValue,
} from "./types"
import { Text } from "./text"
+export { Text } from "./text"
-import { type API } from "@automerge/automerge-wasm"
-export {
- PutPatch,
- DelPatch,
- SplicePatch,
- IncPatch,
- SyncMessage,
-} from "@automerge/automerge-wasm"
-import { ApiHandler, ChangeToEncode, UseApi } from "./low_level"
-
-import {
+import type {
+ API as WasmAPI,
Actor as ActorId,
Prop,
ObjID,
Change,
DecodedChange,
Heads,
- Automerge,
MaterializeValue,
-} from "@automerge/automerge-wasm"
-import {
- JsSyncState as SyncState,
+ JsSyncState,
SyncMessage,
DecodedSyncMessage,
} from "@automerge/automerge-wasm"
+export type {
+ PutPatch,
+ DelPatch,
+ SpliceTextPatch,
+ InsertPatch,
+ IncPatch,
+ SyncMessage,
+} from "@automerge/automerge-wasm"
+
+/** @hidden **/
+type API = WasmAPI
+
+const SyncStateSymbol = Symbol("_syncstate")
+
+/**
+ * An opaque type tracking the state of sync with a remote peer
+ */
+type SyncState = JsSyncState & { _opaque: typeof SyncStateSymbol }
+
+import { ApiHandler, type ChangeToEncode, UseApi } from "./low_level"
+
+import { Automerge } from "@automerge/automerge-wasm"
import { RawString } from "./raw_string"
import { _state, _is_proxy, _trace, _obj } from "./internal_state"
+import { stableConflictAt } from "./conflicts"
+
/** Options passed to {@link change}, and {@link emptyChange}
* @typeParam T - The type of value contained in the document
*/
@@ -67,13 +84,36 @@ export type ChangeOptions = {
*/
export type ApplyOptions = { patchCallback?: PatchCallback }
+/**
+ * A List is an extended Array that adds the two helper methods `deleteAt` and `insertAt`.
+ */
+export interface List extends Array {
+ insertAt(index: number, ...args: T[]): List
+ deleteAt(index: number, numDelete?: number): List
+}
+
+/**
+ * To extend an arbitrary type, we have to turn any arrays that are part of the type's definition into Lists.
+ * So we recurse through the properties of T, turning any Arrays we find into Lists.
+ */
+export type Extend =
+ // is it an array? make it a list (we recursively extend the type of the array's elements as well)
+ T extends Array
+ ? List>
+ : // is it an object? recursively extend all of its properties
+ // eslint-disable-next-line @typescript-eslint/ban-types
+ T extends Object
+ ? { [P in keyof T]: Extend }
+ : // otherwise leave the type alone
+ T
+
/**
* Function which is called by {@link change} when making changes to a `Doc`
* @typeParam T - The type of value contained in the document
*
* This function may mutate `doc`
*/
-export type ChangeFn = (doc: T) => void
+export type ChangeFn = (doc: Extend) => void
/** @hidden **/
export interface State {
@@ -132,11 +172,12 @@ export function init(_opts?: ActorId | InitOptions): Doc {
const handle = ApiHandler.create(opts.enableTextV2 || false, opts.actor)
handle.enablePatches(true)
handle.enableFreeze(!!opts.freeze)
- handle.registerDatatype("counter", (n: any) => new Counter(n))
- let textV2 = opts.enableTextV2 || false
+ handle.registerDatatype("counter", (n: number) => new Counter(n))
+ const textV2 = opts.enableTextV2 || false
if (textV2) {
handle.registerDatatype("str", (n: string) => new RawString(n))
} else {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
handle.registerDatatype("text", (n: any) => new Text(n))
}
const doc = handle.materialize("/", undefined, {
@@ -200,7 +241,7 @@ export function clone(
// `change` uses the presence of state.heads to determine if we are in a view
// set it to undefined to indicate that this is a full fat document
- const { heads: oldHeads, ...stateSansHeads } = state
+ const { heads: _oldHeads, ...stateSansHeads } = state
return handle.applyPatches(doc, { ...stateSansHeads, handle })
}
@@ -264,7 +305,7 @@ export function from>(
* @example A change with a message and a timestamp
*
* ```
- * doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => {
+ * doc1 = automerge.change(doc1, {message: "add another value", time: 1640995200}, d => {
* d.key2 = "value2"
* })
* ```
@@ -275,7 +316,7 @@ export function from>(
* let patchCallback = patch => {
* patchedPath = patch.path
* }
- * doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => {
+ * doc1 = automerge.change(doc1, {message, "add another value", time: 1640995200, patchCallback}, d => {
* d.key2 = "value2"
* })
* assert.equal(patchedPath, ["key2"])
@@ -339,7 +380,7 @@ function _change(
try {
state.heads = heads
const root: T = rootProxy(state.handle, state.textV2)
- callback(root)
+ callback(root as Extend)
if (state.handle.pendingOps() === 0) {
state.heads = undefined
return doc
@@ -537,62 +578,6 @@ export function getActorId(doc: Doc): ActorId {
*/
type Conflicts = { [key: string]: AutomergeValue }
-function conflictAt(
- context: Automerge,
- objectId: ObjID,
- prop: Prop,
- textV2: boolean
-): Conflicts | undefined {
- const values = context.getAll(objectId, prop)
- if (values.length <= 1) {
- return
- }
- const result: Conflicts = {}
- for (const fullVal of values) {
- switch (fullVal[0]) {
- case "map":
- result[fullVal[1]] = mapProxy(context, fullVal[1], textV2, [prop], true)
- break
- case "list":
- result[fullVal[1]] = listProxy(
- context,
- fullVal[1],
- textV2,
- [prop],
- true
- )
- break
- case "text":
- if (textV2) {
- result[fullVal[1]] = context.text(fullVal[1])
- } else {
- result[fullVal[1]] = textProxy(context, objectId, [prop], true)
- }
- break
- //case "table":
- //case "cursor":
- case "str":
- case "uint":
- case "int":
- case "f64":
- case "boolean":
- case "bytes":
- case "null":
- result[fullVal[2]] = fullVal[1]
- break
- case "counter":
- result[fullVal[2]] = new Counter(fullVal[1])
- break
- case "timestamp":
- result[fullVal[2]] = new Date(fullVal[1])
- break
- default:
- throw RangeError(`datatype ${fullVal[0]} unimplemented`)
- }
- }
- return result
-}
-
/**
* Get the conflicts associated with a property
*
@@ -642,9 +627,12 @@ export function getConflicts(
prop: Prop
): Conflicts | undefined {
const state = _state(doc, false)
+ if (state.textV2) {
+ throw new Error("use unstable.getConflicts for an unstable document")
+ }
const objectId = _obj(doc)
if (objectId != null) {
- return conflictAt(state.handle, objectId, prop, state.textV2)
+ return stableConflictAt(state.handle, objectId, prop)
} else {
return undefined
}
@@ -668,6 +656,7 @@ export function getLastLocalChange(doc: Doc): Change | undefined {
* This is useful to determine if something is actually an automerge document,
* if `doc` is not an automerge document this will return null.
*/
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getObjectId(doc: any, prop?: Prop): ObjID | null {
if (prop) {
const state = _state(doc, false)
@@ -794,7 +783,7 @@ export function decodeSyncState(state: Uint8Array): SyncState {
const sync = ApiHandler.decodeSyncState(state)
const result = ApiHandler.exportSyncState(sync)
sync.free()
- return result
+ return result as SyncState
}
/**
@@ -815,7 +804,7 @@ export function generateSyncMessage(
const state = _state(doc)
const syncState = ApiHandler.importSyncState(inState)
const message = state.handle.generateSyncMessage(syncState)
- const outState = ApiHandler.exportSyncState(syncState)
+ const outState = ApiHandler.exportSyncState(syncState) as SyncState
return [outState, message]
}
@@ -857,7 +846,7 @@ export function receiveSyncMessage(
}
const heads = state.handle.getHeads()
state.handle.receiveSyncMessage(syncState, message)
- const outSyncState = ApiHandler.exportSyncState(syncState)
+ const outSyncState = ApiHandler.exportSyncState(syncState) as SyncState
return [
progressDocument(doc, heads, opts.patchCallback || state.patchCallback),
outSyncState,
@@ -874,7 +863,7 @@ export function receiveSyncMessage(
* @group sync
*/
export function initSyncState(): SyncState {
- return ApiHandler.exportSyncState(ApiHandler.initSyncState())
+ return ApiHandler.exportSyncState(ApiHandler.initSyncState()) as SyncState
}
/** @hidden */
diff --git a/javascript/src/text.ts b/javascript/src/text.ts
index bb0a868d..b01bd7db 100644
--- a/javascript/src/text.ts
+++ b/javascript/src/text.ts
@@ -1,10 +1,15 @@
-import { Value } from "@automerge/automerge-wasm"
+import type { Value } from "@automerge/automerge-wasm"
import { TEXT, STATE } from "./constants"
+import type { InternalState } from "./internal_state"
export class Text {
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
elems: Array
str: string | undefined
- spans: Array | undefined
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
+ spans: Array | undefined;
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
+ [STATE]?: InternalState
constructor(text?: string | string[] | Value[]) {
if (typeof text === "string") {
@@ -23,6 +28,7 @@ export class Text {
return this.elems.length
}
+ //eslint-disable-next-line @typescript-eslint/no-explicit-any
get(index: number): any {
return this.elems[index]
}
@@ -71,7 +77,7 @@ export class Text {
* For example, the value `['a', 'b', {x: 3}, 'c', 'd']` has spans:
* `=> ['ab', {x: 3}, 'cd']`
*/
- toSpans(): Array {
+ toSpans(): Array {
if (!this.spans) {
this.spans = []
let chars = ""
@@ -116,7 +122,7 @@ export class Text {
/**
* Inserts new list items `values` starting at position `index`.
*/
- insertAt(index: number, ...values: Array) {
+ insertAt(index: number, ...values: Array) {
if (this[STATE]) {
throw new RangeError(
"object cannot be modified outside of a change block"
@@ -138,7 +144,7 @@ export class Text {
this.elems.splice(index, numDelete)
}
- map(callback: (e: Value | Object) => T) {
+ map(callback: (e: Value | object) => T) {
this.elems.map(callback)
}
@@ -208,7 +214,7 @@ export class Text {
new Text(this.elems.slice(start, end))
}
- some(test: (Value) => boolean): boolean {
+ some(test: (arg: Value) => boolean): boolean {
return this.elems.some(test)
}
diff --git a/javascript/src/types.ts b/javascript/src/types.ts
index e3cb81f8..beb5cf70 100644
--- a/javascript/src/types.ts
+++ b/javascript/src/types.ts
@@ -1,4 +1,5 @@
export { Text } from "./text"
+import { Text } from "./text"
export { Counter } from "./counter"
export { Int, Uint, Float64 } from "./numbers"
@@ -10,9 +11,9 @@ export type AutomergeValue =
| ScalarValue
| { [key: string]: AutomergeValue }
| Array
+ | Text
export type MapValue = { [key: string]: AutomergeValue }
export type ListValue = Array
-export type TextValue = Array
export type ScalarValue =
| string
| number
diff --git a/javascript/src/unstable.ts b/javascript/src/unstable.ts
index 3ee18dbc..7c73afb9 100644
--- a/javascript/src/unstable.ts
+++ b/javascript/src/unstable.ts
@@ -22,9 +22,9 @@
* This leads to the following differences from `stable`:
*
* * There is no `unstable.Text` class, all strings are text objects
- * * Reading strings in a `future` document is the same as reading any other
+ * * Reading strings in an `unstable` document is the same as reading any other
* javascript string
- * * To modify strings in a `future` document use {@link splice}
+ * * To modify strings in an `unstable` document use {@link splice}
* * The {@link AutomergeValue} type does not include the {@link Text}
* class but the {@link RawString} class is included in the {@link ScalarValue}
* type
@@ -35,34 +35,29 @@
*
* @module
*/
-import { Counter } from "./types"
-export { Counter, Doc, Int, Uint, Float64, Patch, PatchCallback } from "./types"
+export {
+ Counter,
+ type Doc,
+ Int,
+ Uint,
+ Float64,
+ type Patch,
+ type PatchCallback,
+ type AutomergeValue,
+ type ScalarValue,
+} from "./unstable_types"
import type { PatchCallback } from "./stable"
-export type AutomergeValue =
- | ScalarValue
- | { [key: string]: AutomergeValue }
- | Array
-export type MapValue = { [key: string]: AutomergeValue }
-export type ListValue = Array
-export type ScalarValue =
- | string
- | number
- | null
- | boolean
- | Date
- | Counter
- | Uint8Array
- | RawString
+import { type UnstableConflicts as Conflicts } from "./conflicts"
+import { unstableConflictAt } from "./conflicts"
-export type Conflicts = { [key: string]: AutomergeValue }
-
-export {
+export type {
PutPatch,
DelPatch,
- SplicePatch,
+ SpliceTextPatch,
+ InsertPatch,
IncPatch,
SyncMessage,
} from "@automerge/automerge-wasm"
@@ -116,7 +111,6 @@ export { RawString } from "./raw_string"
export const getBackend = stable.getBackend
import { _is_proxy, _state, _obj } from "./internal_state"
-import { RawString } from "./raw_string"
/**
* Create a new automerge document
@@ -128,7 +122,7 @@ import { RawString } from "./raw_string"
* random actor ID
*/
export function init(_opts?: ActorId | InitOptions): Doc {
- let opts = importOpts(_opts)
+ const opts = importOpts(_opts)
opts.enableTextV2 = true
return stable.init(opts)
}
@@ -152,7 +146,7 @@ export function clone(
doc: Doc,
_opts?: ActorId | InitOptions
): Doc {
- let opts = importOpts(_opts)
+ const opts = importOpts(_opts)
opts.enableTextV2 = true
return stable.clone(doc, opts)
}
@@ -287,6 +281,14 @@ export function getConflicts(
doc: Doc,
prop: stable.Prop
): Conflicts | undefined {
- // this function only exists to get the types to line up with future.AutomergeValue
- return stable.getConflicts(doc, prop)
+ const state = _state(doc, false)
+ if (!state.textV2) {
+ throw new Error("use getConflicts for a stable document")
+ }
+ const objectId = _obj(doc)
+ if (objectId != null) {
+ return unstableConflictAt(state.handle, objectId, prop)
+ } else {
+ return undefined
+ }
}
diff --git a/javascript/src/unstable_types.ts b/javascript/src/unstable_types.ts
new file mode 100644
index 00000000..071e2cc4
--- /dev/null
+++ b/javascript/src/unstable_types.ts
@@ -0,0 +1,30 @@
+import { Counter } from "./types"
+
+export {
+ Counter,
+ type Doc,
+ Int,
+ Uint,
+ Float64,
+ type Patch,
+ type PatchCallback,
+} from "./types"
+
+import { RawString } from "./raw_string"
+export { RawString } from "./raw_string"
+
+export type AutomergeValue =
+ | ScalarValue
+ | { [key: string]: AutomergeValue }
+ | Array
+export type MapValue = { [key: string]: AutomergeValue }
+export type ListValue = Array
+export type ScalarValue =
+ | string
+ | number
+ | null
+ | boolean
+ | Date
+ | Counter
+ | Uint8Array
+ | RawString
diff --git a/javascript/src/uuid.deno.ts b/javascript/src/uuid.deno.ts
new file mode 100644
index 00000000..04c9b93d
--- /dev/null
+++ b/javascript/src/uuid.deno.ts
@@ -0,0 +1,26 @@
+import * as v4 from "https://deno.land/x/uuid@v0.1.2/mod.ts"
+
+// this file is a deno only port of the uuid module
+
+function defaultFactory() {
+ return v4.uuid().replace(/-/g, "")
+}
+
+let factory = defaultFactory
+
+interface UUIDFactory extends Function {
+ setFactory(f: typeof factory): void
+ reset(): void
+}
+
+export const uuid: UUIDFactory = () => {
+ return factory()
+}
+
+uuid.setFactory = newFactory => {
+ factory = newFactory
+}
+
+uuid.reset = () => {
+ factory = defaultFactory
+}
diff --git a/javascript/test/basic_test.ts b/javascript/test/basic_test.ts
index 90e7a99d..e34484c4 100644
--- a/javascript/test/basic_test.ts
+++ b/javascript/test/basic_test.ts
@@ -58,6 +58,22 @@ describe("Automerge", () => {
})
})
+ it("should be able to insert and delete a large number of properties", () => {
+ let doc = Automerge.init()
+
+ doc = Automerge.change(doc, doc => {
+ doc["k1"] = true
+ })
+
+ for (let idx = 1; idx <= 200; idx++) {
+ doc = Automerge.change(doc, doc => {
+ delete doc["k" + idx]
+ doc["k" + (idx + 1)] = true
+ assert(Object.keys(doc).length == 1)
+ })
+ }
+ })
+
it("can detect an automerge doc with isAutomerge()", () => {
const doc1 = Automerge.from({ sub: { object: true } })
assert(Automerge.isAutomerge(doc1))
@@ -267,7 +283,6 @@ describe("Automerge", () => {
})
assert.deepEqual(doc5, { list: [2, 1, 9, 10, 3, 11, 12] })
let doc6 = Automerge.change(doc5, d => {
- // @ts-ignore
d.list.insertAt(3, 100, 101)
})
assert.deepEqual(doc6, { list: [2, 1, 9, 100, 101, 10, 3, 11, 12] })
diff --git a/javascript/test/legacy_tests.ts b/javascript/test/legacy_tests.ts
index a423b51f..8c2e552e 100644
--- a/javascript/test/legacy_tests.ts
+++ b/javascript/test/legacy_tests.ts
@@ -461,12 +461,12 @@ describe("Automerge", () => {
s1 = Automerge.change(s1, "set foo", doc => {
doc.foo = "bar"
})
- let deleted
+ let deleted: any
s1 = Automerge.change(s1, "del foo", doc => {
deleted = delete doc.foo
})
assert.strictEqual(deleted, true)
- let deleted2
+ let deleted2: any
assert.doesNotThrow(() => {
s1 = Automerge.change(s1, "del baz", doc => {
deleted2 = delete doc.baz
@@ -515,7 +515,7 @@ describe("Automerge", () => {
s1 = Automerge.change(s1, doc => {
doc.nested = {}
})
- let id = Automerge.getObjectId(s1.nested)
+ Automerge.getObjectId(s1.nested)
assert.strictEqual(
OPID_PATTERN.test(Automerge.getObjectId(s1.nested)!),
true
@@ -975,6 +975,7 @@ describe("Automerge", () => {
it("should allow adding and removing list elements in the same change callback", () => {
let s1 = Automerge.change(
Automerge.init<{ noodles: Array }>(),
+ // @ts-ignore
doc => (doc.noodles = [])
)
s1 = Automerge.change(s1, doc => {
@@ -1848,9 +1849,8 @@ describe("Automerge", () => {
})
assert.deepStrictEqual(patches, [
{ action: "put", path: ["birds"], value: [] },
- { action: "insert", path: ["birds", 0], values: [""] },
+ { action: "insert", path: ["birds", 0], values: ["", ""] },
{ action: "splice", path: ["birds", 0, 0], value: "Goldfinch" },
- { action: "insert", path: ["birds", 1], values: [""] },
{ action: "splice", path: ["birds", 1, 0], value: "Chaffinch" },
])
})
diff --git a/javascript/test/stable_unstable_interop.ts b/javascript/test/stable_unstable_interop.ts
index 2f58c256..dc57f338 100644
--- a/javascript/test/stable_unstable_interop.ts
+++ b/javascript/test/stable_unstable_interop.ts
@@ -38,4 +38,62 @@ describe("stable/unstable interop", () => {
stableDoc = unstable.merge(stableDoc, unstableDoc)
assert.deepStrictEqual(stableDoc.text, "abc")
})
+
+ it("should show conflicts on text objects", () => {
+ let doc1 = stable.from({ text: new stable.Text("abc") }, "bb")
+ let doc2 = stable.from({ text: new stable.Text("def") }, "aa")
+ doc1 = stable.merge(doc1, doc2)
+ let conflicts = stable.getConflicts(doc1, "text")!
+ assert.equal(conflicts["1@bb"]!.toString(), "abc")
+ assert.equal(conflicts["1@aa"]!.toString(), "def")
+
+ let unstableDoc = unstable.init()
+ unstableDoc = unstable.merge(unstableDoc, doc1)
+ let conflicts2 = unstable.getConflicts(unstableDoc, "text")!
+ assert.equal(conflicts2["1@bb"]!.toString(), "abc")
+ assert.equal(conflicts2["1@aa"]!.toString(), "def")
+ })
+
+ it("should allow filling a list with text in stable", () => {
+ let doc = stable.from<{ list: Array }>({
+ list: [null, null, null],
+ })
+ doc = stable.change(doc, doc => {
+ doc.list.fill(new stable.Text("abc"), 0, 3)
+ })
+ assert.deepStrictEqual(doc.list, [
+ new stable.Text("abc"),
+ new stable.Text("abc"),
+ new stable.Text("abc"),
+ ])
+ })
+
+ it("should allow filling a list with text in unstable", () => {
+ let doc = unstable.from<{ list: Array }>({
+ list: [null, null, null],
+ })
+ doc = stable.change(doc, doc => {
+ doc.list.fill("abc", 0, 3)
+ })
+ assert.deepStrictEqual(doc.list, ["abc", "abc", "abc"])
+ })
+
+ it("should allow splicing text into a list on stable", () => {
+ let doc = stable.from<{ list: Array }>({ list: [] })
+ doc = stable.change(doc, doc => {
+ doc.list.splice(0, 0, new stable.Text("abc"), new stable.Text("def"))
+ })
+ assert.deepStrictEqual(doc.list, [
+ new stable.Text("abc"),
+ new stable.Text("def"),
+ ])
+ })
+
+ it("should allow splicing text into a list on unstable", () => {
+ let doc = unstable.from<{ list: Array }>({ list: [] })
+ doc = unstable.change(doc, doc => {
+ doc.list.splice(0, 0, "abc", "def")
+ })
+ assert.deepStrictEqual(doc.list, ["abc", "def"])
+ })
})
diff --git a/javascript/tsconfig.json b/javascript/tsconfig.json
index c6684ca0..628aea8e 100644
--- a/javascript/tsconfig.json
+++ b/javascript/tsconfig.json
@@ -15,5 +15,5 @@
"outDir": "./dist"
},
"include": ["src/**/*", "test/**/*"],
- "exclude": ["./dist/**/*", "./node_modules"]
+ "exclude": ["./dist/**/*", "./node_modules", "./src/**/*.deno.ts"]
}
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 938100cf..5d29fc9f 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -10,13 +10,8 @@ members = [
resolver = "2"
[profile.release]
-debug = true
lto = true
-opt-level = 3
+codegen-units = 1
[profile.bench]
-debug = true
-
-[profile.release.package.automerge-wasm]
-debug = false
-opt-level = 3
+debug = true
\ No newline at end of file
diff --git a/rust/automerge-c/.clang-format b/rust/automerge-c/.clang-format
new file mode 100644
index 00000000..dbf16c21
--- /dev/null
+++ b/rust/automerge-c/.clang-format
@@ -0,0 +1,250 @@
+---
+Language: Cpp
+# BasedOnStyle: Chromium
+AccessModifierOffset: -1
+AlignAfterOpenBracket: Align
+AlignArrayOfStructures: None
+AlignConsecutiveAssignments:
+ Enabled: false
+ AcrossEmptyLines: false
+ AcrossComments: false
+ AlignCompound: false
+ PadOperators: true
+AlignConsecutiveBitFields:
+ Enabled: false
+ AcrossEmptyLines: false
+ AcrossComments: false
+ AlignCompound: false
+ PadOperators: false
+AlignConsecutiveDeclarations:
+ Enabled: false
+ AcrossEmptyLines: false
+ AcrossComments: false
+ AlignCompound: false
+ PadOperators: false
+AlignConsecutiveMacros:
+ Enabled: false
+ AcrossEmptyLines: false
+ AcrossComments: false
+ AlignCompound: false
+ PadOperators: false
+AlignEscapedNewlines: Left
+AlignOperands: Align
+AlignTrailingComments: true
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+ - __capability
+BinPackArguments: true
+BinPackParameters: false
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: Never
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ BeforeLambdaBody: false
+ BeforeWhile: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: Always
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 120
+CommentPragmas: '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat: false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: NextLine
+BasedOnStyle: ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IfMacros:
+ - KJ_IF_MAYBE
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^'
+ Priority: 2
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '^<.*\.h>'
+ Priority: 1
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '^<.*'
+ Priority: 2
+ SortPriority: 0
+ CaseSensitive: false
+ - Regex: '.*'
+ Priority: 3
+ SortPriority: 0
+ CaseSensitive: false
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: true
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequiresClause: true
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+InsertBraces: false
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Never
+ObjCBlockIndentWidth: 2
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Left
+PPIndentWidth: -1
+RawStringFormats:
+ - Language: Cpp
+ Delimiters:
+ - cc
+ - CC
+ - cpp
+ - Cpp
+ - CPP
+ - 'c++'
+ - 'C++'
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ - Language: TextProto
+ Delimiters:
+ - pb
+ - PB
+ - proto
+ - PROTO
+ EnclosingFunctions:
+ - EqualsProto
+ - EquivToProto
+ - PARSE_PARTIAL_TEXT_PROTO
+ - PARSE_TEST_PROTO
+ - PARSE_TEXT_PROTO
+ - ParseTextOrDie
+ - ParseTextProtoOrDie
+ - ParseTestProto
+ - ParsePartialTestProto
+ CanonicalDelimiter: pb
+ BasedOnStyle: google
+ReferenceAlignment: Pointer
+ReflowComments: true
+RemoveBracesLLVM: false
+RequiresClausePosition: OwnLine
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes: CaseSensitive
+SortJavaStaticImport: Before
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeParensOptions:
+ AfterControlStatements: true
+ AfterForeachMacros: true
+ AfterFunctionDefinitionName: false
+ AfterFunctionDeclarationName: false
+ AfterIfMacros: true
+ AfterOverloadedOperator: false
+ AfterRequiresInClause: false
+ AfterRequiresInExpression: false
+ BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+ Minimum: 1
+ Maximum: -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard: Auto
+StatementAttributeLikeMacros:
+ - Q_EMIT
+StatementMacros:
+ - Q_UNUSED
+ - QT_REQUIRE_VERSION
+TabWidth: 8
+UseCRLF: false
+UseTab: Never
+WhitespaceSensitiveMacros:
+ - STRINGIZE
+ - PP_STRINGIZE
+ - BOOST_PP_STRINGIZE
+ - NS_SWIFT_NAME
+ - CF_SWIFT_NAME
+...
+
diff --git a/rust/automerge-c/.gitignore b/rust/automerge-c/.gitignore
index f04de582..14d74973 100644
--- a/rust/automerge-c/.gitignore
+++ b/rust/automerge-c/.gitignore
@@ -1,10 +1,10 @@
automerge
automerge.h
automerge.o
-*.cmake
+build/
+CMakeCache.txt
CMakeFiles
+CMakePresets.json
Makefile
DartConfiguration.tcl
-config.h
-CMakeCache.txt
-Cargo
+out/
diff --git a/rust/automerge-c/CMakeLists.txt b/rust/automerge-c/CMakeLists.txt
index 1b68669a..0c35eebd 100644
--- a/rust/automerge-c/CMakeLists.txt
+++ b/rust/automerge-c/CMakeLists.txt
@@ -1,97 +1,297 @@
-cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
-set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
+project(automerge-c VERSION 0.1.0
+ LANGUAGES C
+ DESCRIPTION "C bindings for the Automerge Rust library.")
-# Parse the library name, project name and project version out of Cargo's TOML file.
-set(CARGO_LIB_SECTION OFF)
+set(LIBRARY_NAME "automerge")
-set(LIBRARY_NAME "")
-
-set(CARGO_PKG_SECTION OFF)
-
-set(CARGO_PKG_NAME "")
-
-set(CARGO_PKG_VERSION "")
-
-file(READ Cargo.toml TOML_STRING)
-
-string(REPLACE ";" "\\\\;" TOML_STRING "${TOML_STRING}")
-
-string(REPLACE "\n" ";" TOML_LINES "${TOML_STRING}")
-
-foreach(TOML_LINE IN ITEMS ${TOML_LINES})
- string(REGEX MATCH "^\\[(lib|package)\\]$" _ ${TOML_LINE})
-
- if(CMAKE_MATCH_1 STREQUAL "lib")
- set(CARGO_LIB_SECTION ON)
-
- set(CARGO_PKG_SECTION OFF)
- elseif(CMAKE_MATCH_1 STREQUAL "package")
- set(CARGO_LIB_SECTION OFF)
-
- set(CARGO_PKG_SECTION ON)
- endif()
-
- string(REGEX MATCH "^name += +\"([^\"]+)\"$" _ ${TOML_LINE})
-
- if(CMAKE_MATCH_1 AND (CARGO_LIB_SECTION AND NOT CARGO_PKG_SECTION))
- set(LIBRARY_NAME "${CMAKE_MATCH_1}")
- elseif(CMAKE_MATCH_1 AND (NOT CARGO_LIB_SECTION AND CARGO_PKG_SECTION))
- set(CARGO_PKG_NAME "${CMAKE_MATCH_1}")
- endif()
-
- string(REGEX MATCH "^version += +\"([^\"]+)\"$" _ ${TOML_LINE})
-
- if(CMAKE_MATCH_1 AND CARGO_PKG_SECTION)
- set(CARGO_PKG_VERSION "${CMAKE_MATCH_1}")
- endif()
-
- if(LIBRARY_NAME AND (CARGO_PKG_NAME AND CARGO_PKG_VERSION))
- break()
- endif()
-endforeach()
-
-project(${CARGO_PKG_NAME} VERSION 0.0.1 LANGUAGES C DESCRIPTION "C bindings for the Automerge Rust backend.")
-
-include(CTest)
+set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
option(BUILD_SHARED_LIBS "Enable the choice of a shared or static library.")
+include(CTest)
+
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
+set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
+
string(MAKE_C_IDENTIFIER ${PROJECT_NAME} SYMBOL_PREFIX)
string(TOUPPER ${SYMBOL_PREFIX} SYMBOL_PREFIX)
-set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/Cargo/target")
+set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/Cargo/target")
-set(CBINDGEN_INCLUDEDIR "${CARGO_TARGET_DIR}/${CMAKE_INSTALL_INCLUDEDIR}")
+set(CBINDGEN_INCLUDEDIR "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}")
set(CBINDGEN_TARGET_DIR "${CBINDGEN_INCLUDEDIR}/${PROJECT_NAME}")
-add_subdirectory(src)
+find_program (
+ CARGO_CMD
+ "cargo"
+ PATHS "$ENV{CARGO_HOME}/bin"
+ DOC "The Cargo command"
+)
-# Generate and install the configuration header.
+if(NOT CARGO_CMD)
+ message(FATAL_ERROR "Cargo (Rust package manager) not found! "
+ "Please install it and/or set the CARGO_HOME "
+ "environment variable to its path.")
+endif()
+
+string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER)
+
+# In order to build with -Z build-std, we need to pass target explicitly.
+# https://doc.rust-lang.org/cargo/reference/unstable.html#build-std
+execute_process (
+ COMMAND rustc -vV
+ OUTPUT_VARIABLE RUSTC_VERSION
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+string(REGEX REPLACE ".*host: ([^ \n]*).*" "\\1"
+ CARGO_TARGET
+ ${RUSTC_VERSION}
+)
+
+if(BUILD_TYPE_LOWER STREQUAL debug)
+ set(CARGO_BUILD_TYPE "debug")
+
+ set(CARGO_FLAG --target=${CARGO_TARGET})
+else()
+ set(CARGO_BUILD_TYPE "release")
+
+ if (NOT RUSTC_VERSION MATCHES "nightly")
+ set(RUSTUP_TOOLCHAIN nightly)
+ endif()
+
+ set(RUSTFLAGS -C\ panic=abort)
+
+ set(CARGO_FLAG -Z build-std=std,panic_abort --release --target=${CARGO_TARGET})
+endif()
+
+set(CARGO_FEATURES "")
+
+set(CARGO_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_TARGET}/${CARGO_BUILD_TYPE}")
+
+set(BINDINGS_NAME "${LIBRARY_NAME}_core")
+
+configure_file(
+ ${CMAKE_MODULE_PATH}/Cargo.toml.in
+ ${CMAKE_SOURCE_DIR}/Cargo.toml
+ @ONLY
+ NEWLINE_STYLE LF
+)
+
+set(INCLUDE_GUARD_PREFIX "${SYMBOL_PREFIX}")
+
+configure_file(
+ ${CMAKE_MODULE_PATH}/cbindgen.toml.in
+ ${CMAKE_SOURCE_DIR}/cbindgen.toml
+ @ONLY
+ NEWLINE_STYLE LF
+)
+
+set(CARGO_OUTPUT
+ ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
+ ${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}
+)
+
+# \note cbindgen's naming behavior isn't fully configurable and it ignores
+# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252).
+add_custom_command(
+ OUTPUT
+ ${CARGO_OUTPUT}
+ COMMAND
+ # \note cbindgen won't regenerate its output header file after it's been removed but it will after its
+ # configuration file has been updated.
+ ${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file-touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml
+ COMMAND
+ ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} RUSTUP_TOOLCHAIN=${RUSTUP_TOOLCHAIN} RUSTFLAGS=${RUSTFLAGS} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES}
+ COMMAND
+ # Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase".
+ ${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
+ COMMAND
+ # Compensate for cbindgen ignoring `std:mem::size_of()` calls.
+ ${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
+ MAIN_DEPENDENCY
+ src/lib.rs
+ DEPENDS
+ src/actor_id.rs
+ src/byte_span.rs
+ src/change.rs
+ src/doc.rs
+ src/doc/list.rs
+ src/doc/map.rs
+ src/doc/utils.rs
+ src/index.rs
+ src/item.rs
+ src/items.rs
+ src/obj.rs
+ src/result.rs
+ src/sync.rs
+ src/sync/have.rs
+ src/sync/message.rs
+ src/sync/state.rs
+ ${CMAKE_SOURCE_DIR}/build.rs
+ ${CMAKE_MODULE_PATH}/Cargo.toml.in
+ ${CMAKE_MODULE_PATH}/cbindgen.toml.in
+ WORKING_DIRECTORY
+ ${CMAKE_SOURCE_DIR}
+ COMMENT
+ "Producing the bindings' artifacts with Cargo..."
+ VERBATIM
+)
+
+add_custom_target(${BINDINGS_NAME}_artifacts ALL
+ DEPENDS ${CARGO_OUTPUT}
+)
+
+add_library(${BINDINGS_NAME} STATIC IMPORTED GLOBAL)
+
+target_include_directories(${BINDINGS_NAME} INTERFACE "${CBINDGEN_INCLUDEDIR}")
+
+set_target_properties(
+ ${BINDINGS_NAME}
+ PROPERTIES
+ # \note Cargo writes a debug build into a nested directory instead of
+ # decorating its name.
+ DEBUG_POSTFIX ""
+ DEFINE_SYMBOL ""
+ IMPORTED_IMPLIB ""
+ IMPORTED_LOCATION "${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}"
+ IMPORTED_NO_SONAME "TRUE"
+ IMPORTED_SONAME ""
+ LINKER_LANGUAGE C
+ PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
+ SOVERSION "${PROJECT_VERSION_MAJOR}"
+ VERSION "${PROJECT_VERSION}"
+ # \note Cargo exports all of the symbols automatically.
+ WINDOWS_EXPORT_ALL_SYMBOLS "TRUE"
+)
+
+target_compile_definitions(${BINDINGS_NAME} INTERFACE $)
+
+set(UTILS_SUBDIR "utils")
+
+add_custom_command(
+ OUTPUT
+ ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h
+ ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c
+ COMMAND
+ ${CMAKE_COMMAND} -DPROJECT_NAME=${PROJECT_NAME} -DLIBRARY_NAME=${LIBRARY_NAME} -DSUBDIR=${UTILS_SUBDIR} -P ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c
+ MAIN_DEPENDENCY
+ ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
+ DEPENDS
+ ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake
+ WORKING_DIRECTORY
+ ${CMAKE_SOURCE_DIR}
+ COMMENT
+ "Generating the enum string functions with CMake..."
+ VERBATIM
+)
+
+add_custom_target(${LIBRARY_NAME}_utilities
+ DEPENDS ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h
+ ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c
+)
+
+add_library(${LIBRARY_NAME})
+
+target_compile_features(${LIBRARY_NAME} PRIVATE c_std_99)
+
+set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
+
+set(THREADS_PREFER_PTHREAD_FLAG TRUE)
+
+find_package(Threads REQUIRED)
+
+set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS})
+
+if(WIN32)
+ list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32)
+else()
+ list(APPEND LIBRARY_DEPENDENCIES m)
+endif()
+
+target_link_libraries(${LIBRARY_NAME}
+ PUBLIC ${BINDINGS_NAME}
+ ${LIBRARY_DEPENDENCIES}
+)
+
+# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't
+# contain a non-existent path so its build-time include directory
+# must be specified for all of its dependent targets instead.
+target_include_directories(${LIBRARY_NAME}
+ PUBLIC "$"
+ "$"
+)
+
+add_dependencies(${LIBRARY_NAME} ${BINDINGS_NAME}_artifacts)
+
+# Generate the configuration header.
math(EXPR INTEGER_PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR} * 100000")
math(EXPR INTEGER_PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR} * 100")
math(EXPR INTEGER_PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
-math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + ${INTEGER_PROJECT_VERSION_MINOR} + ${INTEGER_PROJECT_VERSION_PATCH}")
+math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + \
+ ${INTEGER_PROJECT_VERSION_MINOR} + \
+ ${INTEGER_PROJECT_VERSION_PATCH}")
configure_file(
${CMAKE_MODULE_PATH}/config.h.in
- config.h
+ ${CBINDGEN_TARGET_DIR}/config.h
@ONLY
NEWLINE_STYLE LF
)
+target_sources(${LIBRARY_NAME}
+ PRIVATE
+ src/${UTILS_SUBDIR}/result.c
+ src/${UTILS_SUBDIR}/stack_callback_data.c
+ src/${UTILS_SUBDIR}/stack.c
+ src/${UTILS_SUBDIR}/string.c
+ ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c
+ PUBLIC
+ FILE_SET api TYPE HEADERS
+ BASE_DIRS
+ ${CBINDGEN_INCLUDEDIR}
+ ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}
+ FILES
+ ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
+ ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h
+ ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/result.h
+ ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack_callback_data.h
+ ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack.h
+ ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/string.h
+ INTERFACE
+ FILE_SET config TYPE HEADERS
+ BASE_DIRS
+ ${CBINDGEN_INCLUDEDIR}
+ FILES
+ ${CBINDGEN_TARGET_DIR}/config.h
+)
+
install(
- FILES ${CMAKE_BINARY_DIR}/config.h
- DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
+ TARGETS ${LIBRARY_NAME}
+ EXPORT ${PROJECT_NAME}-config
+ FILE_SET api
+ FILE_SET config
+)
+
+# \note Install the Cargo-built core bindings to enable direct linkage.
+install(
+ FILES $
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}
+)
+
+install(EXPORT ${PROJECT_NAME}-config
+ FILE ${PROJECT_NAME}-config.cmake
+ NAMESPACE "${PROJECT_NAME}::"
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LIB}
)
if(BUILD_TESTING)
@@ -100,42 +300,6 @@ if(BUILD_TESTING)
enable_testing()
endif()
+add_subdirectory(docs)
+
add_subdirectory(examples EXCLUDE_FROM_ALL)
-
-# Generate and install .cmake files
-set(PROJECT_CONFIG_NAME "${PROJECT_NAME}-config")
-
-set(PROJECT_CONFIG_VERSION_NAME "${PROJECT_CONFIG_NAME}-version")
-
-write_basic_package_version_file(
- ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY ExactVersion
-)
-
-# The namespace label starts with the title-cased library name.
-string(SUBSTRING ${LIBRARY_NAME} 0 1 NS_FIRST)
-
-string(SUBSTRING ${LIBRARY_NAME} 1 -1 NS_REST)
-
-string(TOUPPER ${NS_FIRST} NS_FIRST)
-
-string(TOLOWER ${NS_REST} NS_REST)
-
-string(CONCAT NAMESPACE ${NS_FIRST} ${NS_REST} "::")
-
-# \note CMake doesn't automate the exporting of an imported library's targets
-# so the package configuration script must do it.
-configure_package_config_file(
- ${CMAKE_MODULE_PATH}/${PROJECT_CONFIG_NAME}.cmake.in
- ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake
- INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
-)
-
-install(
- FILES
- ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake
- ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake
- DESTINATION
- ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
-)
diff --git a/rust/automerge-c/Cargo.toml b/rust/automerge-c/Cargo.toml
index d039e460..95a3a29c 100644
--- a/rust/automerge-c/Cargo.toml
+++ b/rust/automerge-c/Cargo.toml
@@ -7,8 +7,8 @@ license = "MIT"
rust-version = "1.57.0"
[lib]
-name = "automerge"
-crate-type = ["cdylib", "staticlib"]
+name = "automerge_core"
+crate-type = ["staticlib"]
bench = false
doc = false
diff --git a/rust/automerge-c/README.md b/rust/automerge-c/README.md
index a9f097e2..1fbca3df 100644
--- a/rust/automerge-c/README.md
+++ b/rust/automerge-c/README.md
@@ -1,22 +1,29 @@
-automerge-c exposes an API to C that can either be used directly or as a basis
-for other language bindings that have good support for calling into C functions.
+# Overview
-# Building
+automerge-c exposes a C API that can either be used directly or as the basis
+for other language bindings that have good support for calling C functions.
-See the main README for instructions on getting your environment set up, then
-you can use `./scripts/ci/cmake-build Release static` to build automerge-c.
+# Installing
-It will output two files:
+See the main README for instructions on getting your environment set up and then
+you can build the automerge-c library and install its constituent files within
+a root directory of your choosing (e.g. "/usr/local") like so:
+```shell
+cmake -E make_directory automerge-c/build
+cmake -S automerge-c -B automerge-c/build
+cmake --build automerge-c/build
+cmake --install automerge-c/build --prefix "/usr/local"
+```
+Installation is important because the name, location and structure of CMake's
+out-of-source build subdirectory is subject to change based on the platform and
+the release version; generated headers like `automerge-c/config.h` and
+`automerge-c/utils/enum_string.h` are only sure to be found within their
+installed locations.
-- ./build/Cargo/target/include/automerge-c/automerge.h
-- ./build/Cargo/target/release/libautomerge.a
-
-To use these in your application you must arrange for your C compiler to find
-these files, either by moving them to the right location on your computer, or
-by configuring the compiler to reference these directories.
-
-- `export LDFLAGS=-L./build/Cargo/target/release -lautomerge`
-- `export CFLAGS=-I./build/Cargo/target/include`
+It's not obvious because they are versioned but the `Cargo.toml` and
+`cbindgen.toml` configuration files are also generated in order to ensure that
+the project name, project version and library name that they contain match those
+specified within the top-level `CMakeLists.txt` file.
If you'd like to cross compile the library for different platforms you can do so
using [cross](https://github.com/cross-rs/cross). For example:
@@ -25,134 +32,176 @@ using [cross](https://github.com/cross-rs/cross). For example:
This will output a shared library in the directory `rust/target/aarch64-unknown-linux-gnu/release/`.
-You can replace `aarch64-unknown-linux-gnu` with any [cross supported targets](https://github.com/cross-rs/cross#supported-targets). The targets below are known to work, though other targets are expected to work too:
+You can replace `aarch64-unknown-linux-gnu` with any
+[cross supported targets](https://github.com/cross-rs/cross#supported-targets).
+The targets below are known to work, though other targets are expected to work
+too:
- `x86_64-apple-darwin`
- `aarch64-apple-darwin`
- `x86_64-unknown-linux-gnu`
- `aarch64-unknown-linux-gnu`
-As a caveat, the header file is currently 32/64-bit dependant. You can re-use it
-for all 64-bit architectures, but you must generate a specific header for 32-bit
-targets.
+As a caveat, CMake generates the `automerge.h` header file in terms of the
+processor architecture of the computer on which it was built so, for example,
+don't use a header generated for a 64-bit processor if your target is a 32-bit
+processor.
# Usage
-For full reference, read through `automerge.h`, or to get started quickly look
-at the
+You can build and view the C API's HTML reference documentation like so:
+```shell
+cmake -E make_directory automerge-c/build
+cmake -S automerge-c -B automerge-c/build
+cmake --build automerge-c/build --target automerge_docs
+firefox automerge-c/build/src/html/index.html
+```
+
+To get started quickly, look at the
[examples](https://github.com/automerge/automerge-rs/tree/main/rust/automerge-c/examples).
-Almost all operations in automerge-c act on an AMdoc struct which you can get
-from `AMcreate()` or `AMload()`. Operations on a given doc are not thread safe
-so you must use a mutex or similar to avoid calling more than one function with
-the same AMdoc pointer concurrently.
+Almost all operations in automerge-c act on an Automerge document
+(`AMdoc` struct) which is structurally similar to a JSON document.
-As with all functions that either allocate memory, or could fail if given
-invalid input, `AMcreate()` returns an `AMresult`. The `AMresult` contains the
-returned doc (or error message), and must be freed with `AMfree()` after you are
-done to avoid leaking memory.
+You can get a document by calling either `AMcreate()` or `AMload()`. Operations
+on a given document are not thread-safe so you must use a mutex or similar to
+avoid calling more than one function on the same one concurrently.
+A C API function that could succeed or fail returns a result (`AMresult` struct)
+containing a status code (`AMstatus` enum) and either a sequence of at least one
+item (`AMitem` struct) or a read-only view onto a UTF-8 error message string
+(`AMbyteSpan` struct).
+An item contains up to three components: an index within its parent object
+(`AMbyteSpan` struct or `size_t`), a unique identifier (`AMobjId` struct) and a
+value.
+The result of a successful function call that doesn't produce any values will
+contain a single item that is void (`AM_VAL_TYPE_VOID`).
+A returned result **must** be passed to `AMresultFree()` once the item(s) or
+error message it contains is no longer needed in order to avoid a memory leak.
```
-#include
#include
+#include
+#include
+#include
int main(int argc, char** argv) {
AMresult *docResult = AMcreate(NULL);
if (AMresultStatus(docResult) != AM_STATUS_OK) {
- printf("failed to create doc: %s", AMerrorMessage(docResult).src);
+ char* const err_msg = AMstrdup(AMresultError(docResult), NULL);
+ printf("failed to create doc: %s", err_msg);
+ free(err_msg);
goto cleanup;
}
- AMdoc *doc = AMresultValue(docResult).doc;
+ AMdoc *doc;
+ AMitemToDoc(AMresultItem(docResult), &doc);
// useful code goes here!
cleanup:
- AMfree(docResult);
+ AMresultFree(docResult);
}
```
-If you are writing code in C directly, you can use the `AMpush()` helper
-function to reduce the boilerplate of error handling and freeing for you (see
-examples/quickstart.c).
+If you are writing an application in C, the `AMstackItem()`, `AMstackItems()`
+and `AMstackResult()` functions enable the lifetimes of anonymous results to be
+centrally managed and allow the same validation logic to be reused without
+relying upon the `goto` statement (see examples/quickstart.c).
If you are wrapping automerge-c in another language, particularly one that has a
-garbage collector, you can call `AMfree` within a finalizer to ensure that memory
-is reclaimed when it is no longer needed.
+garbage collector, you can call the `AMresultFree()` function within a finalizer
+to ensure that memory is reclaimed when it is no longer needed.
-An AMdoc wraps an automerge document which are very similar to JSON documents.
-Automerge documents consist of a mutable root, which is always a map from string
-keys to values. Values can have the following types:
+Automerge documents consist of a mutable root which is always a map from string
+keys to values. A value can be one of the following types:
- A number of type double / int64_t / uint64_t
-- An explicit true / false / nul
-- An immutable utf-8 string (AMbyteSpan)
-- An immutable array of arbitrary bytes (AMbyteSpan)
-- A mutable map from string keys to values (AMmap)
-- A mutable list of values (AMlist)
-- A mutable string (AMtext)
+- An explicit true / false / null
+- An immutable UTF-8 string (`AMbyteSpan`).
+- An immutable array of arbitrary bytes (`AMbyteSpan`).
+- A mutable map from string keys to values.
+- A mutable list of values.
+- A mutable UTF-8 string.
-If you read from a location in the document with no value a value with
-`.tag == AM_VALUE_VOID` will be returned, but you cannot write such a value explicitly.
+If you read from a location in the document with no value, an item with type
+`AM_VAL_TYPE_VOID` will be returned, but you cannot write such a value
+explicitly.
-Under the hood, automerge references mutable objects by the internal object id,
-and `AM_ROOT` is always the object id of the root value.
+Under the hood, automerge references a mutable object by its object identifier
+where `AM_ROOT` signifies a document's root map object.
-There is a function to put each type of value into either a map or a list, and a
-function to read the current value from a list. As (in general) collaborators
+There are functions to put each type of value into either a map or a list, and
+functions to read the current or a historical value from a map or a list. As (in general) collaborators
may edit the document at any time, you cannot guarantee that the type of the
-value at a given part of the document will stay the same. As a result reading
-from the document will return an `AMvalue` union that you can inspect to
-determine its type.
+value at a given part of the document will stay the same. As a result, reading
+from the document will return an `AMitem` struct that you can inspect to
+determine the type of value that it contains.
Strings in automerge-c are represented using an `AMbyteSpan` which contains a
-pointer and a length. Strings must be valid utf-8 and may contain null bytes.
-As a convenience you can use `AMstr()` to get the representation of a
-null-terminated C string as an `AMbyteSpan`.
+pointer and a length. Strings must be valid UTF-8 and may contain NUL (`0`)
+characters.
+For your convenience, you can call `AMstr()` to get the `AMbyteSpan` struct
+equivalent of a null-terminated byte string or `AMstrdup()` to get the
+representation of an `AMbyteSpan` struct as a null-terminated byte string
+wherein its NUL characters have been removed/replaced as you choose.
Putting all of that together, to read and write from the root of the document
you can do this:
```
-#include
#include
+#include
+#include
+#include
int main(int argc, char** argv) {
// ...previous example...
- AMdoc *doc = AMresultValue(docResult).doc;
+ AMdoc *doc;
+ AMitemToDoc(AMresultItem(docResult), &doc);
AMresult *putResult = AMmapPutStr(doc, AM_ROOT, AMstr("key"), AMstr("value"));
if (AMresultStatus(putResult) != AM_STATUS_OK) {
- printf("failed to put: %s", AMerrorMessage(putResult).src);
+ char* const err_msg = AMstrdup(AMresultError(putResult), NULL);
+ printf("failed to put: %s", err_msg);
+ free(err_msg);
goto cleanup;
}
AMresult *getResult = AMmapGet(doc, AM_ROOT, AMstr("key"), NULL);
if (AMresultStatus(getResult) != AM_STATUS_OK) {
- printf("failed to get: %s", AMerrorMessage(getResult).src);
+ char* const err_msg = AMstrdup(AMresultError(putResult), NULL);
+ printf("failed to get: %s", err_msg);
+ free(err_msg);
goto cleanup;
}
- AMvalue got = AMresultValue(getResult);
- if (got.tag != AM_VALUE_STR) {
+ AMbyteSpan got;
+ if (AMitemToStr(AMresultItem(getResult), &got)) {
+ char* const c_str = AMstrdup(got, NULL);
+ printf("Got %zu-character string \"%s\"", got.count, c_str);
+ free(c_str);
+ } else {
printf("expected to read a string!");
goto cleanup;
}
- printf("Got %zu-character string `%s`", got.str.count, got.str.src);
cleanup:
- AMfree(getResult);
- AMfree(putResult);
- AMfree(docResult);
+ AMresultFree(getResult);
+ AMresultFree(putResult);
+ AMresultFree(docResult);
}
```
-Functions that do not return an `AMresult` (for example `AMmapItemValue()`) do
-not allocate memory, but continue to reference memory that was previously
-allocated. It's thus important to keep the original `AMresult` alive (in this
-case the one returned by `AMmapRange()`) until after you are done with the return
-values of these functions.
+Functions that do not return an `AMresult` (for example `AMitemKey()`) do
+not allocate memory but rather reference memory that was previously
+allocated. It's therefore important to keep the original `AMresult` alive (in
+this case the one returned by `AMmapRange()`) until after you are finished with
+the items that it contains. However, the memory for an individual `AMitem` can
+be shared with a new `AMresult` by calling `AMitemResult()` on it. In other
+words, a select group of items can be filtered out of a collection and only each
+one's corresponding `AMresult` must be kept alive from that point forward; the
+originating collection's `AMresult` can be safely freed.
Beyond that, good luck!
diff --git a/rust/automerge-c/cbindgen.toml b/rust/automerge-c/cbindgen.toml
index ada7f48d..21eaaadd 100644
--- a/rust/automerge-c/cbindgen.toml
+++ b/rust/automerge-c/cbindgen.toml
@@ -1,7 +1,7 @@
after_includes = """\n
/**
* \\defgroup enumerations Public Enumerations
- Symbolic names for integer constants.
+ * Symbolic names for integer constants.
*/
/**
@@ -12,21 +12,23 @@ after_includes = """\n
#define AM_ROOT NULL
/**
- * \\memberof AMchangeHash
+ * \\memberof AMdoc
* \\def AM_CHANGE_HASH_SIZE
* \\brief The count of bytes in a change hash.
*/
#define AM_CHANGE_HASH_SIZE 32
"""
-autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+autogen_warning = """
+/**
+ * \\file
+ * \\brief All constants, functions and types in the core Automerge C API.
+ *
+ * \\warning This file is auto-generated by cbindgen.
+ */
+"""
documentation = true
documentation_style = "doxy"
-header = """
-/** \\file
- * All constants, functions and types in the Automerge library's C API.
- */
- """
-include_guard = "AUTOMERGE_H"
+include_guard = "AUTOMERGE_C_H"
includes = []
language = "C"
line_length = 140
diff --git a/rust/automerge-c/cmake/Cargo.toml.in b/rust/automerge-c/cmake/Cargo.toml.in
new file mode 100644
index 00000000..781e2fef
--- /dev/null
+++ b/rust/automerge-c/cmake/Cargo.toml.in
@@ -0,0 +1,22 @@
+[package]
+name = "@PROJECT_NAME@"
+version = "@PROJECT_VERSION@"
+authors = ["Orion Henry ", "Jason Kankiewicz "]
+edition = "2021"
+license = "MIT"
+rust-version = "1.57.0"
+
+[lib]
+name = "@BINDINGS_NAME@"
+crate-type = ["staticlib"]
+bench = false
+doc = false
+
+[dependencies]
+@LIBRARY_NAME@ = { path = "../@LIBRARY_NAME@" }
+hex = "^0.4.3"
+libc = "^0.2"
+smol_str = "^0.1.21"
+
+[build-dependencies]
+cbindgen = "^0.24"
diff --git a/rust/automerge-c/cmake/cbindgen.toml.in b/rust/automerge-c/cmake/cbindgen.toml.in
new file mode 100644
index 00000000..5122b75c
--- /dev/null
+++ b/rust/automerge-c/cmake/cbindgen.toml.in
@@ -0,0 +1,48 @@
+after_includes = """\n
+/**
+ * \\defgroup enumerations Public Enumerations
+ * Symbolic names for integer constants.
+ */
+
+/**
+ * \\memberof AMdoc
+ * \\def AM_ROOT
+ * \\brief The root object of a document.
+ */
+#define AM_ROOT NULL
+
+/**
+ * \\memberof AMdoc
+ * \\def AM_CHANGE_HASH_SIZE
+ * \\brief The count of bytes in a change hash.
+ */
+#define AM_CHANGE_HASH_SIZE 32
+"""
+autogen_warning = """
+/**
+ * \\file
+ * \\brief All constants, functions and types in the core Automerge C API.
+ *
+ * \\warning This file is auto-generated by cbindgen.
+ */
+"""
+documentation = true
+documentation_style = "doxy"
+include_guard = "@INCLUDE_GUARD_PREFIX@_H"
+includes = []
+language = "C"
+line_length = 140
+no_includes = true
+style = "both"
+sys_includes = ["stdbool.h", "stddef.h", "stdint.h", "time.h"]
+usize_is_size_t = true
+
+[enum]
+derive_const_casts = true
+enum_class = true
+must_use = "MUST_USE_ENUM"
+prefix_with_name = true
+rename_variants = "ScreamingSnakeCase"
+
+[export]
+item_types = ["constants", "enums", "functions", "opaque", "structs", "typedefs"]
diff --git a/rust/automerge-c/cmake/config.h.in b/rust/automerge-c/cmake/config.h.in
index 44ba5213..40482cb9 100644
--- a/rust/automerge-c/cmake/config.h.in
+++ b/rust/automerge-c/cmake/config.h.in
@@ -1,14 +1,35 @@
-#ifndef @SYMBOL_PREFIX@_CONFIG_H
-#define @SYMBOL_PREFIX@_CONFIG_H
-
-/* This header is auto-generated by CMake. */
+#ifndef @INCLUDE_GUARD_PREFIX@_CONFIG_H
+#define @INCLUDE_GUARD_PREFIX@_CONFIG_H
+/**
+ * \file
+ * \brief Configuration pararameters defined by the build system.
+ *
+ * \warning This file is auto-generated by CMake.
+ */
+/**
+ * \def @SYMBOL_PREFIX@_VERSION
+ * \brief Denotes a semantic version of the form {MAJOR}{MINOR}{PATCH} as three,
+ * two-digit decimal numbers without leading zeros (e.g. 100 is 0.1.0).
+ */
#define @SYMBOL_PREFIX@_VERSION @INTEGER_PROJECT_VERSION@
+/**
+ * \def @SYMBOL_PREFIX@_MAJOR_VERSION
+ * \brief Denotes a semantic major version as a decimal number.
+ */
#define @SYMBOL_PREFIX@_MAJOR_VERSION (@SYMBOL_PREFIX@_VERSION / 100000)
+/**
+ * \def @SYMBOL_PREFIX@_MINOR_VERSION
+ * \brief Denotes a semantic minor version as a decimal number.
+ */
#define @SYMBOL_PREFIX@_MINOR_VERSION ((@SYMBOL_PREFIX@_VERSION / 100) % 1000)
+/**
+ * \def @SYMBOL_PREFIX@_PATCH_VERSION
+ * \brief Denotes a semantic patch version as a decimal number.
+ */
#define @SYMBOL_PREFIX@_PATCH_VERSION (@SYMBOL_PREFIX@_VERSION % 100)
-#endif /* @SYMBOL_PREFIX@_CONFIG_H */
+#endif /* @INCLUDE_GUARD_PREFIX@_CONFIG_H */
diff --git a/rust/automerge-c/cmake/enum-string-functions-gen.cmake b/rust/automerge-c/cmake/enum-string-functions-gen.cmake
new file mode 100644
index 00000000..77080e8d
--- /dev/null
+++ b/rust/automerge-c/cmake/enum-string-functions-gen.cmake
@@ -0,0 +1,183 @@
+# This CMake script is used to generate a header and a source file for utility
+# functions that convert the tags of generated enum types into strings and
+# strings into the tags of generated enum types.
+cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
+
+# Seeks the starting line of the source enum's declaration.
+macro(seek_enum_mode)
+ if (line MATCHES "^(typedef[ \t]+)?enum ")
+ string(REGEX REPLACE "^enum ([0-9a-zA-Z_]+).*$" "\\1" enum_name "${line}")
+ set(mode "read_tags")
+ endif()
+endmacro()
+
+# Scans the input for the current enum's tags.
+macro(read_tags_mode)
+ if(line MATCHES "^}")
+ set(mode "generate")
+ elseif(line MATCHES "^[A-Z0-9_]+.*$")
+ string(REGEX REPLACE "^([A-Za-z0-9_]+).*$" "\\1" tmp "${line}")
+ list(APPEND enum_tags "${tmp}")
+ endif()
+endmacro()
+
+macro(write_header_file)
+ # Generate a to-string function declaration.
+ list(APPEND header_body
+ "/**\n"
+ " * \\ingroup enumerations\n"
+ " * \\brief Gets the string representation of an `${enum_name}` enum tag.\n"
+ " *\n"
+ " * \\param[in] tag An `${enum_name}` enum tag.\n"
+ " * \\return A null-terminated byte string.\n"
+ " */\n"
+ "char const* ${enum_name}ToString(${enum_name} const tag)\;\n"
+ "\n")
+ # Generate a from-string function declaration.
+ list(APPEND header_body
+ "/**\n"
+ " * \\ingroup enumerations\n"
+ " * \\brief Gets an `${enum_name}` enum tag from its string representation.\n"
+ " *\n"
+ " * \\param[out] dest An `${enum_name}` enum tag pointer.\n"
+ " * \\param[in] src A null-terminated byte string.\n"
+ " * \\return `true` if \\p src matches the string representation of an\n"
+ " * `${enum_name}` enum tag, `false` otherwise.\n"
+ " */\n"
+ "bool ${enum_name}FromString(${enum_name}* dest, char const* const src)\;\n"
+ "\n")
+endmacro()
+
+macro(write_source_file)
+ # Generate a to-string function implementation.
+ list(APPEND source_body
+ "char const* ${enum_name}ToString(${enum_name} const tag) {\n"
+ " switch (tag) {\n"
+ " default:\n"
+ " return \"???\"\;\n")
+ foreach(label IN LISTS enum_tags)
+ list(APPEND source_body
+ " case ${label}:\n"
+ " return \"${label}\"\;\n")
+ endforeach()
+ list(APPEND source_body
+ " }\n"
+ "}\n"
+ "\n")
+ # Generate a from-string function implementation.
+ list(APPEND source_body
+ "bool ${enum_name}FromString(${enum_name}* dest, char const* const src) {\n")
+ foreach(label IN LISTS enum_tags)
+ list(APPEND source_body
+ " if (!strcmp(src, \"${label}\")) {\n"
+ " *dest = ${label}\;\n"
+ " return true\;\n"
+ " }\n")
+ endforeach()
+ list(APPEND source_body
+ " return false\;\n"
+ "}\n"
+ "\n")
+endmacro()
+
+function(main)
+ set(header_body "")
+ # File header and includes.
+ list(APPEND header_body
+ "#ifndef ${include_guard}\n"
+ "#define ${include_guard}\n"
+ "/**\n"
+ " * \\file\n"
+ " * \\brief Utility functions for converting enum tags into null-terminated\n"
+ " * byte strings and vice versa.\n"
+ " *\n"
+ " * \\warning This file is auto-generated by CMake.\n"
+ " */\n"
+ "\n"
+ "#include \n"
+ "\n"
+ "#include <${library_include}>\n"
+ "\n")
+ set(source_body "")
+ # File includes.
+ list(APPEND source_body
+ "/** \\warning This file is auto-generated by CMake. */\n"
+ "\n"
+ "#include \"stdio.h\"\n"
+ "#include \"string.h\"\n"
+ "\n"
+ "#include <${header_include}>\n"
+ "\n")
+ set(enum_name "")
+ set(enum_tags "")
+ set(mode "seek_enum")
+ file(STRINGS "${input_path}" lines)
+ foreach(line IN LISTS lines)
+ string(REGEX REPLACE "^(.+)(//.*)?" "\\1" line "${line}")
+ string(STRIP "${line}" line)
+ if(mode STREQUAL "seek_enum")
+ seek_enum_mode()
+ elseif(mode STREQUAL "read_tags")
+ read_tags_mode()
+ else()
+ # The end of the enum declaration was reached.
+ if(NOT enum_name)
+ # The end of the file was reached.
+ return()
+ endif()
+ if(NOT enum_tags)
+ message(FATAL_ERROR "No tags found for `${enum_name}`.")
+ endif()
+ string(TOLOWER "${enum_name}" output_stem_prefix)
+ string(CONCAT output_stem "${output_stem_prefix}" "_string")
+ cmake_path(REPLACE_EXTENSION output_stem "h" OUTPUT_VARIABLE output_header_basename)
+ write_header_file()
+ write_source_file()
+ set(enum_name "")
+ set(enum_tags "")
+ set(mode "seek_enum")
+ endif()
+ endforeach()
+ # File footer.
+ list(APPEND header_body
+ "#endif /* ${include_guard} */\n")
+ message(STATUS "Generating header file \"${output_header_path}\"...")
+ file(WRITE "${output_header_path}" ${header_body})
+ message(STATUS "Generating source file \"${output_source_path}\"...")
+ file(WRITE "${output_source_path}" ${source_body})
+endfunction()
+
+if(NOT DEFINED PROJECT_NAME)
+ message(FATAL_ERROR "Variable PROJECT_NAME is not defined.")
+elseif(NOT DEFINED LIBRARY_NAME)
+ message(FATAL_ERROR "Variable LIBRARY_NAME is not defined.")
+elseif(NOT DEFINED SUBDIR)
+ message(FATAL_ERROR "Variable SUBDIR is not defined.")
+elseif(${CMAKE_ARGC} LESS 9)
+ message(FATAL_ERROR "Too few arguments.")
+elseif(${CMAKE_ARGC} GREATER 10)
+ message(FATAL_ERROR "Too many arguments.")
+elseif(NOT EXISTS ${CMAKE_ARGV5})
+ message(FATAL_ERROR "Input header \"${CMAKE_ARGV7}\" not found.")
+endif()
+cmake_path(CONVERT "${CMAKE_ARGV7}" TO_CMAKE_PATH_LIST input_path NORMALIZE)
+cmake_path(CONVERT "${CMAKE_ARGV8}" TO_CMAKE_PATH_LIST output_header_path NORMALIZE)
+cmake_path(CONVERT "${CMAKE_ARGV9}" TO_CMAKE_PATH_LIST output_source_path NORMALIZE)
+string(TOLOWER "${PROJECT_NAME}" project_root)
+cmake_path(CONVERT "${SUBDIR}" TO_CMAKE_PATH_LIST project_subdir NORMALIZE)
+string(TOLOWER "${project_subdir}" project_subdir)
+string(TOLOWER "${LIBRARY_NAME}" library_stem)
+cmake_path(REPLACE_EXTENSION library_stem "h" OUTPUT_VARIABLE library_basename)
+string(JOIN "/" library_include "${project_root}" "${library_basename}")
+string(TOUPPER "${PROJECT_NAME}" project_name_upper)
+string(TOUPPER "${project_subdir}" include_guard_infix)
+string(REGEX REPLACE "/" "_" include_guard_infix "${include_guard_infix}")
+string(REGEX REPLACE "-" "_" include_guard_prefix "${project_name_upper}")
+string(JOIN "_" include_guard_prefix "${include_guard_prefix}" "${include_guard_infix}")
+string(JOIN "/" output_header_prefix "${project_root}" "${project_subdir}")
+cmake_path(GET output_header_path STEM output_header_stem)
+string(TOUPPER "${output_header_stem}" include_guard_stem)
+string(JOIN "_" include_guard "${include_guard_prefix}" "${include_guard_stem}" "H")
+cmake_path(GET output_header_path FILENAME output_header_basename)
+string(JOIN "/" header_include "${output_header_prefix}" "${output_header_basename}")
+main()
diff --git a/rust/automerge-c/cmake/file_regex_replace.cmake b/rust/automerge-c/cmake/file-regex-replace.cmake
similarity index 87%
rename from rust/automerge-c/cmake/file_regex_replace.cmake
rename to rust/automerge-c/cmake/file-regex-replace.cmake
index 27306458..09005bc2 100644
--- a/rust/automerge-c/cmake/file_regex_replace.cmake
+++ b/rust/automerge-c/cmake/file-regex-replace.cmake
@@ -1,4 +1,6 @@
-cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
+# This CMake script is used to perform string substitutions within a generated
+# file.
+cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
if(NOT DEFINED MATCH_REGEX)
message(FATAL_ERROR "Variable \"MATCH_REGEX\" is not defined.")
diff --git a/rust/automerge-c/cmake/file_touch.cmake b/rust/automerge-c/cmake/file-touch.cmake
similarity index 82%
rename from rust/automerge-c/cmake/file_touch.cmake
rename to rust/automerge-c/cmake/file-touch.cmake
index 087d59b6..2c196755 100644
--- a/rust/automerge-c/cmake/file_touch.cmake
+++ b/rust/automerge-c/cmake/file-touch.cmake
@@ -1,4 +1,6 @@
-cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
+# This CMake script is used to force Cargo to regenerate the header file for the
+# core bindings after the out-of-source build directory has been cleaned.
+cmake_minimum_required(VERSION 3.23 FATAL_ERROR)
if(NOT DEFINED CONDITION)
message(FATAL_ERROR "Variable \"CONDITION\" is not defined.")
diff --git a/rust/automerge-c/docs/CMakeLists.txt b/rust/automerge-c/docs/CMakeLists.txt
new file mode 100644
index 00000000..1d94c872
--- /dev/null
+++ b/rust/automerge-c/docs/CMakeLists.txt
@@ -0,0 +1,35 @@
+find_package(Doxygen OPTIONAL_COMPONENTS dot)
+
+if(DOXYGEN_FOUND)
+ set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>")
+
+ set(DOXYGEN_GENERATE_LATEX YES)
+
+ set(DOXYGEN_PDF_HYPERLINKS YES)
+
+ set(DOXYGEN_PROJECT_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/img/brandmark.png")
+
+ set(DOXYGEN_SORT_BRIEF_DOCS YES)
+
+ set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md")
+
+ doxygen_add_docs(
+ ${LIBRARY_NAME}_docs
+ "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
+ "${CBINDGEN_TARGET_DIR}/config.h"
+ "${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h"
+ "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/result.h"
+ "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack_callback_data.h"
+ "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack.h"
+ "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/string.h"
+ "${CMAKE_SOURCE_DIR}/README.md"
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+ COMMENT "Producing documentation with Doxygen..."
+ )
+
+ # \note A Doxygen input file isn't a file-level dependency so the Doxygen
+ # command must instead depend upon a target that either outputs the
+ # file or depends upon it also or it will just output an error message
+ # when it can't be found.
+ add_dependencies(${LIBRARY_NAME}_docs ${BINDINGS_NAME}_artifacts ${LIBRARY_NAME}_utilities)
+endif()
diff --git a/rust/automerge-c/img/brandmark.png b/rust/automerge-c/docs/img/brandmark.png
similarity index 100%
rename from rust/automerge-c/img/brandmark.png
rename to rust/automerge-c/docs/img/brandmark.png
diff --git a/rust/automerge-c/examples/CMakeLists.txt b/rust/automerge-c/examples/CMakeLists.txt
index 3395124c..f080237b 100644
--- a/rust/automerge-c/examples/CMakeLists.txt
+++ b/rust/automerge-c/examples/CMakeLists.txt
@@ -1,41 +1,39 @@
-cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
-
add_executable(
- example_quickstart
+ ${LIBRARY_NAME}_quickstart
quickstart.c
)
-set_target_properties(example_quickstart PROPERTIES LINKER_LANGUAGE C)
+set_target_properties(${LIBRARY_NAME}_quickstart PROPERTIES LINKER_LANGUAGE C)
# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't
# contain a non-existent path so its build-time include directory
# must be specified for all of its dependent targets instead.
target_include_directories(
- example_quickstart
+ ${LIBRARY_NAME}_quickstart
PRIVATE "$"
)
-target_link_libraries(example_quickstart PRIVATE ${LIBRARY_NAME})
+target_link_libraries(${LIBRARY_NAME}_quickstart PRIVATE ${LIBRARY_NAME})
-add_dependencies(example_quickstart ${LIBRARY_NAME}_artifacts)
+add_dependencies(${LIBRARY_NAME}_quickstart ${BINDINGS_NAME}_artifacts)
if(BUILD_SHARED_LIBS AND WIN32)
add_custom_command(
- TARGET example_quickstart
+ TARGET ${LIBRARY_NAME}_quickstart
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}
- ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}
COMMENT "Copying the DLL built by Cargo into the examples directory..."
VERBATIM
)
endif()
add_custom_command(
- TARGET example_quickstart
+ TARGET ${LIBRARY_NAME}_quickstart
POST_BUILD
COMMAND
- example_quickstart
+ ${LIBRARY_NAME}_quickstart
COMMENT
"Running the example quickstart..."
VERBATIM
diff --git a/rust/automerge-c/examples/README.md b/rust/automerge-c/examples/README.md
index 17aa2227..17e69412 100644
--- a/rust/automerge-c/examples/README.md
+++ b/rust/automerge-c/examples/README.md
@@ -5,5 +5,5 @@
```shell
cmake -E make_directory automerge-c/build
cmake -S automerge-c -B automerge-c/build
-cmake --build automerge-c/build --target example_quickstart
+cmake --build automerge-c/build --target automerge_quickstart
```
diff --git a/rust/automerge-c/examples/quickstart.c b/rust/automerge-c/examples/quickstart.c
index bc418511..ab6769ef 100644
--- a/rust/automerge-c/examples/quickstart.c
+++ b/rust/automerge-c/examples/quickstart.c
@@ -3,152 +3,127 @@
#include
#include
+#include
+#include
+#include
+#include
-static void abort_cb(AMresultStack**, uint8_t);
+static bool abort_cb(AMstack**, void*);
/**
* \brief Based on https://automerge.github.io/docs/quickstart
*/
int main(int argc, char** argv) {
- AMresultStack* stack = NULL;
- AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc;
- AMobjId const* const cards = AMpush(&stack,
- AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST),
- AM_VALUE_OBJ_ID,
- abort_cb).obj_id;
- AMobjId const* const card1 = AMpush(&stack,
- AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP),
- AM_VALUE_OBJ_ID,
- abort_cb).obj_id;
- AMfree(AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure")));
- AMfree(AMmapPutBool(doc1, card1, AMstr("done"), false));
- AMobjId const* const card2 = AMpush(&stack,
- AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP),
- AM_VALUE_OBJ_ID,
- abort_cb).obj_id;
- AMfree(AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell")));
- AMfree(AMmapPutBool(doc1, card2, AMstr("done"), false));
- AMfree(AMcommit(doc1, AMstr("Add card"), NULL));
+ AMstack* stack = NULL;
+ AMdoc* doc1;
+ AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1);
+ AMobjId const* const cards =
+ AMitemObjId(AMstackItem(&stack, AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST), abort_cb,
+ AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
+ AMobjId const* const card1 =
+ AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb,
+ AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
+ AMstackItem(NULL, AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure")), abort_cb,
+ AMexpect(AM_VAL_TYPE_VOID));
+ AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID));
+ AMobjId const* const card2 =
+ AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb,
+ AMexpect(AM_VAL_TYPE_OBJ_TYPE)));
+ AMstackItem(NULL, AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell")), abort_cb,
+ AMexpect(AM_VAL_TYPE_VOID));
+ AMstackItem(NULL, AMmapPutBool(doc1, card2, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID));
+ AMstackItem(NULL, AMcommit(doc1, AMstr("Add card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
- AMdoc* doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc;
- AMfree(AMmerge(doc2, doc1));
+ AMdoc* doc2;
+ AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2);
+ AMstackItem(NULL, AMmerge(doc2, doc1), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
- AMbyteSpan const binary = AMpush(&stack, AMsave(doc1), AM_VALUE_BYTES, abort_cb).bytes;
- doc2 = AMpush(&stack, AMload(binary.src, binary.count), AM_VALUE_DOC, abort_cb).doc;
+ AMbyteSpan binary;
+ AMitemToBytes(AMstackItem(&stack, AMsave(doc1), abort_cb, AMexpect(AM_VAL_TYPE_BYTES)), &binary);
+ AMitemToDoc(AMstackItem(&stack, AMload(binary.src, binary.count), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2);
- AMfree(AMmapPutBool(doc1, card1, AMstr("done"), true));
- AMfree(AMcommit(doc1, AMstr("Mark card as done"), NULL));
+ AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), true), abort_cb, AMexpect(AM_VAL_TYPE_VOID));
+ AMstackItem(NULL, AMcommit(doc1, AMstr("Mark card as done"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
- AMfree(AMlistDelete(doc2, cards, 0));
- AMfree(AMcommit(doc2, AMstr("Delete card"), NULL));
+ AMstackItem(NULL, AMlistDelete(doc2, cards, 0), abort_cb, AMexpect(AM_VAL_TYPE_VOID));
+ AMstackItem(NULL, AMcommit(doc2, AMstr("Delete card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
- AMfree(AMmerge(doc1, doc2));
+ AMstackItem(NULL, AMmerge(doc1, doc2), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH));
- AMchanges changes = AMpush(&stack, AMgetChanges(doc1, NULL), AM_VALUE_CHANGES, abort_cb).changes;
- AMchange const* change = NULL;
- while ((change = AMchangesNext(&changes, 1)) != NULL) {
- AMbyteSpan const change_hash = AMchangeHash(change);
- AMchangeHashes const heads = AMpush(&stack,
- AMchangeHashesInit(&change_hash, 1),
- AM_VALUE_CHANGE_HASHES,
- abort_cb).change_hashes;
- AMbyteSpan const msg = AMchangeMessage(change);
- char* const c_msg = calloc(1, msg.count + 1);
- strncpy(c_msg, msg.src, msg.count);
- printf("%s %ld\n", c_msg, AMobjSize(doc1, cards, &heads));
+ AMitems changes = AMstackItems(&stack, AMgetChanges(doc1, NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE));
+ AMitem* item = NULL;
+ while ((item = AMitemsNext(&changes, 1)) != NULL) {
+ AMchange const* change;
+ AMitemToChange(item, &change);
+ AMitems const heads = AMstackItems(&stack, AMitemFromChangeHash(AMchangeHash(change)), abort_cb,
+ AMexpect(AM_VAL_TYPE_CHANGE_HASH));
+ char* const c_msg = AMstrdup(AMchangeMessage(change), NULL);
+ printf("%s %zu\n", c_msg, AMobjSize(doc1, cards, &heads));
free(c_msg);
}
- AMfreeStack(&stack);
+ AMstackFree(&stack);
}
-static char const* discriminant_suffix(AMvalueVariant const);
-
/**
- * \brief Prints an error message to `stderr`, deallocates all results in the
- * given stack and exits.
+ * \brief Examines the result at the top of the given stack and, if it's
+ * invalid, prints an error message to `stderr`, deallocates all results
+ * in the stack and exits.
*
- * \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct.
- * \param[in] discriminant An `AMvalueVariant` enum tag.
- * \pre \p stack` != NULL`.
- * \post `*stack == NULL`.
+ * \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
+ * \param[in] data A pointer to an owned `AMstackCallbackData` struct or `NULL`.
+ * \return `true` if the top `AMresult` in \p stack is valid, `false` otherwise.
+ * \pre \p stack `!= NULL`.
*/
-static void abort_cb(AMresultStack** stack, uint8_t discriminant) {
+static bool abort_cb(AMstack** stack, void* data) {
static char buffer[512] = {0};
char const* suffix = NULL;
if (!stack) {
suffix = "Stack*";
- }
- else if (!*stack) {
+ } else if (!*stack) {
suffix = "Stack";
- }
- else if (!(*stack)->result) {
+ } else if (!(*stack)->result) {
suffix = "";
}
if (suffix) {
- fprintf(stderr, "Null `AMresult%s*`.", suffix);
- AMfreeStack(stack);
+ fprintf(stderr, "Null `AMresult%s*`.\n", suffix);
+ AMstackFree(stack);
exit(EXIT_FAILURE);
- return;
+ return false;
}
AMstatus const status = AMresultStatus((*stack)->result);
switch (status) {
- case AM_STATUS_ERROR: strcpy(buffer, "Error"); break;
- case AM_STATUS_INVALID_RESULT: strcpy(buffer, "Invalid result"); break;
- case AM_STATUS_OK: break;
- default: sprintf(buffer, "Unknown `AMstatus` tag %d", status);
+ case AM_STATUS_ERROR:
+ strcpy(buffer, "Error");
+ break;
+ case AM_STATUS_INVALID_RESULT:
+ strcpy(buffer, "Invalid result");
+ break;
+ case AM_STATUS_OK:
+ break;
+ default:
+ sprintf(buffer, "Unknown `AMstatus` tag %d", status);
}
if (buffer[0]) {
- AMbyteSpan const msg = AMerrorMessage((*stack)->result);
- char* const c_msg = calloc(1, msg.count + 1);
- strncpy(c_msg, msg.src, msg.count);
- fprintf(stderr, "%s; %s.", buffer, c_msg);
+ char* const c_msg = AMstrdup(AMresultError((*stack)->result), NULL);
+ fprintf(stderr, "%s; %s.\n", buffer, c_msg);
free(c_msg);
- AMfreeStack(stack);
+ AMstackFree(stack);
exit(EXIT_FAILURE);
- return;
+ return false;
}
- AMvalue const value = AMresultValue((*stack)->result);
- fprintf(stderr, "Unexpected tag `AM_VALUE_%s` (%d); expected `AM_VALUE_%s`.",
- discriminant_suffix(value.tag),
- value.tag,
- discriminant_suffix(discriminant));
- AMfreeStack(stack);
- exit(EXIT_FAILURE);
-}
-
-/**
- * \brief Gets the suffix for a discriminant's corresponding string
- * representation.
- *
- * \param[in] discriminant An `AMvalueVariant` enum tag.
- * \return A UTF-8 string.
- */
-static char const* discriminant_suffix(AMvalueVariant const discriminant) {
- char const* suffix = NULL;
- switch (discriminant) {
- case AM_VALUE_ACTOR_ID: suffix = "ACTOR_ID"; break;
- case AM_VALUE_BOOLEAN: suffix = "BOOLEAN"; break;
- case AM_VALUE_BYTES: suffix = "BYTES"; break;
- case AM_VALUE_CHANGE_HASHES: suffix = "CHANGE_HASHES"; break;
- case AM_VALUE_CHANGES: suffix = "CHANGES"; break;
- case AM_VALUE_COUNTER: suffix = "COUNTER"; break;
- case AM_VALUE_DOC: suffix = "DOC"; break;
- case AM_VALUE_F64: suffix = "F64"; break;
- case AM_VALUE_INT: suffix = "INT"; break;
- case AM_VALUE_LIST_ITEMS: suffix = "LIST_ITEMS"; break;
- case AM_VALUE_MAP_ITEMS: suffix = "MAP_ITEMS"; break;
- case AM_VALUE_NULL: suffix = "NULL"; break;
- case AM_VALUE_OBJ_ID: suffix = "OBJ_ID"; break;
- case AM_VALUE_OBJ_ITEMS: suffix = "OBJ_ITEMS"; break;
- case AM_VALUE_STR: suffix = "STR"; break;
- case AM_VALUE_STRS: suffix = "STRINGS"; break;
- case AM_VALUE_SYNC_MESSAGE: suffix = "SYNC_MESSAGE"; break;
- case AM_VALUE_SYNC_STATE: suffix = "SYNC_STATE"; break;
- case AM_VALUE_TIMESTAMP: suffix = "TIMESTAMP"; break;
- case AM_VALUE_UINT: suffix = "UINT"; break;
- case AM_VALUE_VOID: suffix = "VOID"; break;
- default: suffix = "...";
+ if (data) {
+ AMstackCallbackData* sc_data = (AMstackCallbackData*)data;
+ AMvalType const tag = AMitemValType(AMresultItem((*stack)->result));
+ if (tag != sc_data->bitmask) {
+ fprintf(stderr, "Unexpected tag `%s` (%d) instead of `%s` at %s:%d.\n", AMvalTypeToString(tag), tag,
+ AMvalTypeToString(sc_data->bitmask), sc_data->file, sc_data->line);
+ free(sc_data);
+ AMstackFree(stack);
+ exit(EXIT_FAILURE);
+ return false;
+ }
}
- return suffix;
+ free(data);
+ return true;
}
diff --git a/rust/automerge-c/include/automerge-c/utils/result.h b/rust/automerge-c/include/automerge-c/utils/result.h
new file mode 100644
index 00000000..ab8a2f93
--- /dev/null
+++ b/rust/automerge-c/include/automerge-c/utils/result.h
@@ -0,0 +1,30 @@
+#ifndef AUTOMERGE_C_UTILS_RESULT_H
+#define AUTOMERGE_C_UTILS_RESULT_H
+/**
+ * \file
+ * \brief Utility functions for use with `AMresult` structs.
+ */
+
+#include
+
+#include
+
+/**
+ * \brief Transfers the items within an arbitrary list of results into a
+ * new result in their order of specification.
+ * \param[in] count The count of subsequent arguments.
+ * \param[in] ... A \p count list of arguments, each of which is a pointer to
+ * an `AMresult` struct whose items will be transferred out of it
+ * and which is subsequently freed.
+ * \return A pointer to an `AMresult` struct or `NULL`.
+ * \pre `∀𝑥 ∈` \p ... `, AMresultStatus(𝑥) == AM_STATUS_OK`
+ * \post `(∃𝑥 ∈` \p ... `, AMresultStatus(𝑥) != AM_STATUS_OK) -> NULL`
+ * \attention All `AMresult` struct pointer arguments are passed to
+ * `AMresultFree()` regardless of success; use `AMresultCat()`
+ * instead if you wish to pass them to `AMresultFree()` yourself.
+ * \warning The returned `AMresult` struct pointer must be passed to
+ * `AMresultFree()` in order to avoid a memory leak.
+ */
+AMresult* AMresultFrom(int count, ...);
+
+#endif /* AUTOMERGE_C_UTILS_RESULT_H */
diff --git a/rust/automerge-c/include/automerge-c/utils/stack.h b/rust/automerge-c/include/automerge-c/utils/stack.h
new file mode 100644
index 00000000..a8e9fd08
--- /dev/null
+++ b/rust/automerge-c/include/automerge-c/utils/stack.h
@@ -0,0 +1,130 @@
+#ifndef AUTOMERGE_C_UTILS_STACK_H
+#define AUTOMERGE_C_UTILS_STACK_H
+/**
+ * \file
+ * \brief Utility data structures and functions for hiding `AMresult` structs,
+ * managing their lifetimes, and automatically applying custom
+ * validation logic to the `AMitem` structs that they contain.
+ *
+ * \note The `AMstack` struct and its related functions drastically reduce the
+ * need for boilerplate code and/or `goto` statement usage within a C
+ * application but a higher-level programming language offers even better
+ * ways to do the same things.
+ */
+
+#include
+
+/**
+ * \struct AMstack
+ * \brief A node in a singly-linked list of result pointers.
+ */
+typedef struct AMstack {
+ /** A result to be deallocated. */
+ AMresult* result;
+ /** The previous node in the singly-linked list or `NULL`. */
+ struct AMstack* prev;
+} AMstack;
+
+/**
+ * \memberof AMstack
+ * \brief The prototype of a function that examines the result at the top of
+ * the given stack in terms of some arbitrary data.
+ *
+ * \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
+ * \param[in] data A pointer to arbitrary data or `NULL`.
+ * \return `true` if the top `AMresult` struct in \p stack is valid, `false`
+ * otherwise.
+ * \pre \p stack `!= NULL`.
+ */
+typedef bool (*AMstackCallback)(AMstack** stack, void* data);
+
+/**
+ * \memberof AMstack
+ * \brief Deallocates the storage for a stack of results.
+ *
+ * \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
+ * \pre \p stack `!= NULL`
+ * \post `*stack == NULL`
+ */
+void AMstackFree(AMstack** stack);
+
+/**
+ * \memberof AMstack
+ * \brief Gets a result from the stack after removing it.
+ *
+ * \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
+ * \param[in] result A pointer to the `AMresult` to be popped or `NULL` to
+ * select the top result in \p stack.
+ * \return A pointer to an `AMresult` struct or `NULL`.
+ * \pre \p stack `!= NULL`
+ * \warning The returned `AMresult` struct pointer must be passed to
+ * `AMresultFree()` in order to avoid a memory leak.
+ */
+AMresult* AMstackPop(AMstack** stack, AMresult const* result);
+
+/**
+ * \memberof AMstack
+ * \brief Pushes the given result onto the given stack, calls the given
+ * callback with the given data to validate it and then either gets the
+ * result if it's valid or gets `NULL` instead.
+ *
+ * \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
+ * \param[in] result A pointer to an `AMresult` struct.
+ * \param[in] callback A pointer to a function with the same signature as
+ * `AMstackCallback()` or `NULL`.
+ * \param[in] data A pointer to arbitrary data or `NULL` which is passed to
+ * \p callback.
+ * \return \p result or `NULL`.
+ * \warning If \p stack `== NULL` then \p result is deallocated in order to
+ * avoid a memory leak.
+ */
+AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data);
+
+/**
+ * \memberof AMstack
+ * \brief Pushes the given result onto the given stack, calls the given
+ * callback with the given data to validate it and then either gets the
+ * first item in the sequence of items within that result if it's valid
+ * or gets `NULL` instead.
+ *
+ * \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
+ * \param[in] result A pointer to an `AMresult` struct.
+ * \param[in] callback A pointer to a function with the same signature as
+ * `AMstackCallback()` or `NULL`.
+ * \param[in] data A pointer to arbitrary data or `NULL` which is passed to
+ * \p callback.
+ * \return A pointer to an `AMitem` struct or `NULL`.
+ * \warning If \p stack `== NULL` then \p result is deallocated in order to
+ * avoid a memory leak.
+ */
+AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data);
+
+/**
+ * \memberof AMstack
+ * \brief Pushes the given result onto the given stack, calls the given
+ * callback with the given data to validate it and then either gets an
+ * `AMitems` struct over the sequence of items within that result if it's
+ * valid or gets an empty `AMitems` instead.
+ *
+ * \param[in,out] stack A pointer to a pointer to an `AMstack` struct.
+ * \param[in] result A pointer to an `AMresult` struct.
+ * \param[in] callback A pointer to a function with the same signature as
+ * `AMstackCallback()` or `NULL`.
+ * \param[in] data A pointer to arbitrary data or `NULL` which is passed to
+ * \p callback.
+ * \return An `AMitems` struct.
+ * \warning If \p stack `== NULL` then \p result is deallocated immediately
+ * in order to avoid a memory leak.
+ */
+AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data);
+
+/**
+ * \memberof AMstack
+ * \brief Gets the count of results that have been pushed onto the stack.
+ *
+ * \param[in,out] stack A pointer to an `AMstack` struct.
+ * \return A 64-bit unsigned integer.
+ */
+size_t AMstackSize(AMstack const* const stack);
+
+#endif /* AUTOMERGE_C_UTILS_STACK_H */
diff --git a/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h b/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h
new file mode 100644
index 00000000..6f9f1edb
--- /dev/null
+++ b/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h
@@ -0,0 +1,53 @@
+#ifndef AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H
+#define AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H
+/**
+ * \file
+ * \brief Utility data structures, functions and macros for supplying
+ * parameters to the custom validation logic applied to `AMitem`
+ * structs.
+ */
+
+#include
+
+/**
+ * \struct AMstackCallbackData
+ * \brief A data structure for passing the parameters of an item value test
+ * to an implementation of the `AMstackCallback` function prototype.
+ */
+typedef struct {
+ /** A bitmask of `AMvalType` tags. */
+ AMvalType bitmask;
+ /** A null-terminated file path string. */
+ char const* file;
+ /** The ordinal number of a line within a file. */
+ int line;
+} AMstackCallbackData;
+
+/**
+ * \memberof AMstackCallbackData
+ * \brief Allocates a new `AMstackCallbackData` struct and initializes its
+ * members from their corresponding arguments.
+ *
+ * \param[in] bitmask A bitmask of `AMvalType` tags.
+ * \param[in] file A null-terminated file path string.
+ * \param[in] line The ordinal number of a line within a file.
+ * \return A pointer to a disowned `AMstackCallbackData` struct.
+ * \warning The returned pointer must be passed to `free()` to avoid a memory
+ * leak.
+ */
+AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line);
+
+/**
+ * \memberof AMstackCallbackData
+ * \def AMexpect
+ * \brief Allocates a new `AMstackCallbackData` struct and initializes it from
+ * an `AMvalueType` bitmask.
+ *
+ * \param[in] bitmask A bitmask of `AMvalType` tags.
+ * \return A pointer to a disowned `AMstackCallbackData` struct.
+ * \warning The returned pointer must be passed to `free()` to avoid a memory
+ * leak.
+ */
+#define AMexpect(bitmask) AMstackCallbackDataInit(bitmask, __FILE__, __LINE__)
+
+#endif /* AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H */
diff --git a/rust/automerge-c/include/automerge-c/utils/string.h b/rust/automerge-c/include/automerge-c/utils/string.h
new file mode 100644
index 00000000..4d61c2e9
--- /dev/null
+++ b/rust/automerge-c/include/automerge-c/utils/string.h
@@ -0,0 +1,29 @@
+#ifndef AUTOMERGE_C_UTILS_STRING_H
+#define AUTOMERGE_C_UTILS_STRING_H
+/**
+ * \file
+ * \brief Utility functions for use with `AMbyteSpan` structs that provide
+ * UTF-8 string views.
+ */
+
+#include
+
+/**
+ * \memberof AMbyteSpan
+ * \brief Returns a pointer to a null-terminated byte string which is a
+ * duplicate of the given UTF-8 string view except for the substitution
+ * of its NUL (0) characters with the specified null-terminated byte
+ * string.
+ *
+ * \param[in] str A UTF-8 string view as an `AMbyteSpan` struct.
+ * \param[in] nul A null-terminated byte string to substitute for NUL characters
+ * or `NULL` to substitute `"\\0"` for NUL characters.
+ * \return A disowned null-terminated byte string.
+ * \pre \p str.src `!= NULL`
+ * \pre \p str.count `<= sizeof(`\p str.src `)`
+ * \warning The returned pointer must be passed to `free()` to avoid a memory
+ * leak.
+ */
+char* AMstrdup(AMbyteSpan const str, char const* nul);
+
+#endif /* AUTOMERGE_C_UTILS_STRING_H */
diff --git a/rust/automerge-c/src/CMakeLists.txt b/rust/automerge-c/src/CMakeLists.txt
deleted file mode 100644
index e02c0a96..00000000
--- a/rust/automerge-c/src/CMakeLists.txt
+++ /dev/null
@@ -1,250 +0,0 @@
-cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
-
-find_program (
- CARGO_CMD
- "cargo"
- PATHS "$ENV{CARGO_HOME}/bin"
- DOC "The Cargo command"
-)
-
-if(NOT CARGO_CMD)
- message(FATAL_ERROR "Cargo (Rust package manager) not found! Install it and/or set the CARGO_HOME environment variable.")
-endif()
-
-string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER)
-
-if(BUILD_TYPE_LOWER STREQUAL debug)
- set(CARGO_BUILD_TYPE "debug")
-
- set(CARGO_FLAG "")
-else()
- set(CARGO_BUILD_TYPE "release")
-
- set(CARGO_FLAG "--release")
-endif()
-
-set(CARGO_FEATURES "")
-
-set(CARGO_CURRENT_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}")
-
-set(
- CARGO_OUTPUT
- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
- ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}
- ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}
-)
-
-if(WIN32)
- # \note The basename of an import library output by Cargo is the filename
- # of its corresponding shared library.
- list(APPEND CARGO_OUTPUT ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX})
-endif()
-
-add_custom_command(
- OUTPUT
- ${CARGO_OUTPUT}
- COMMAND
- # \note cbindgen won't regenerate its output header file after it's
- # been removed but it will after its configuration file has been
- # updated.
- ${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file_touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml
- COMMAND
- ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES}
- MAIN_DEPENDENCY
- lib.rs
- DEPENDS
- actor_id.rs
- byte_span.rs
- change_hashes.rs
- change.rs
- changes.rs
- doc.rs
- doc/list.rs
- doc/list/item.rs
- doc/list/items.rs
- doc/map.rs
- doc/map/item.rs
- doc/map/items.rs
- doc/utils.rs
- obj.rs
- obj/item.rs
- obj/items.rs
- result.rs
- result_stack.rs
- strs.rs
- sync.rs
- sync/have.rs
- sync/haves.rs
- sync/message.rs
- sync/state.rs
- ${CMAKE_SOURCE_DIR}/build.rs
- ${CMAKE_SOURCE_DIR}/Cargo.toml
- ${CMAKE_SOURCE_DIR}/cbindgen.toml
- WORKING_DIRECTORY
- ${CMAKE_SOURCE_DIR}
- COMMENT
- "Producing the library artifacts with Cargo..."
- VERBATIM
-)
-
-add_custom_target(
- ${LIBRARY_NAME}_artifacts ALL
- DEPENDS ${CARGO_OUTPUT}
-)
-
-# \note cbindgen's naming behavior isn't fully configurable and it ignores
-# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252).
-add_custom_command(
- TARGET ${LIBRARY_NAME}_artifacts
- POST_BUILD
- COMMAND
- # Compensate for cbindgen's variant struct naming.
- ${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+_[^_]+\)_Body -DREPLACE_EXPR=AM\\1 -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
- COMMAND
- # Compensate for cbindgen's union tag enum type naming.
- ${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+\)_Tag -DREPLACE_EXPR=AM\\1Variant -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
- COMMAND
- # Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase".
- ${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
- COMMAND
- # Compensate for cbindgen ignoring `std:mem::size_of()` calls.
- ${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h
- WORKING_DIRECTORY
- ${CMAKE_SOURCE_DIR}
- COMMENT
- "Compensating for cbindgen deficits..."
- VERBATIM
-)
-
-if(BUILD_SHARED_LIBS)
- if(WIN32)
- set(LIBRARY_DESTINATION "${CMAKE_INSTALL_BINDIR}")
- else()
- set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}")
- endif()
-
- set(LIBRARY_DEFINE_SYMBOL "${SYMBOL_PREFIX}_EXPORTS")
-
- # \note The basename of an import library output by Cargo is the filename
- # of its corresponding shared library.
- set(LIBRARY_IMPLIB "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}")
-
- set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}")
-
- set(LIBRARY_NO_SONAME "${WIN32}")
-
- set(LIBRARY_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}")
-
- set(LIBRARY_TYPE "SHARED")
-else()
- set(LIBRARY_DEFINE_SYMBOL "")
-
- set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}")
-
- set(LIBRARY_IMPLIB "")
-
- set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}")
-
- set(LIBRARY_NO_SONAME "TRUE")
-
- set(LIBRARY_SONAME "")
-
- set(LIBRARY_TYPE "STATIC")
-endif()
-
-add_library(${LIBRARY_NAME} ${LIBRARY_TYPE} IMPORTED GLOBAL)
-
-set_target_properties(
- ${LIBRARY_NAME}
- PROPERTIES
- # \note Cargo writes a debug build into a nested directory instead of
- # decorating its name.
- DEBUG_POSTFIX ""
- DEFINE_SYMBOL "${LIBRARY_DEFINE_SYMBOL}"
- IMPORTED_IMPLIB "${LIBRARY_IMPLIB}"
- IMPORTED_LOCATION "${LIBRARY_LOCATION}"
- IMPORTED_NO_SONAME "${LIBRARY_NO_SONAME}"
- IMPORTED_SONAME "${LIBRARY_SONAME}"
- LINKER_LANGUAGE C
- PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
- SOVERSION "${PROJECT_VERSION_MAJOR}"
- VERSION "${PROJECT_VERSION}"
- # \note Cargo exports all of the symbols automatically.
- WINDOWS_EXPORT_ALL_SYMBOLS "TRUE"
-)
-
-target_compile_definitions(${LIBRARY_NAME} INTERFACE $)
-
-target_include_directories(
- ${LIBRARY_NAME}
- INTERFACE
- "$"
-)
-
-set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
-
-set(THREADS_PREFER_PTHREAD_FLAG TRUE)
-
-find_package(Threads REQUIRED)
-
-set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS})
-
-if(WIN32)
- list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32)
-else()
- list(APPEND LIBRARY_DEPENDENCIES m)
-endif()
-
-target_link_libraries(${LIBRARY_NAME} INTERFACE ${LIBRARY_DEPENDENCIES})
-
-install(
- FILES $
- TYPE LIB
- # \note The basename of an import library output by Cargo is the filename
- # of its corresponding shared library.
- RENAME "${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}"
- OPTIONAL
-)
-
-set(LIBRARY_FILE_NAME "${CMAKE_${LIBRARY_TYPE}_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_${LIBRARY_TYPE}_LIBRARY_SUFFIX}")
-
-install(
- FILES $
- RENAME "${LIBRARY_FILE_NAME}"
- DESTINATION ${LIBRARY_DESTINATION}
-)
-
-install(
- FILES $
- DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}
-)
-
-find_package(Doxygen OPTIONAL_COMPONENTS dot)
-
-if(DOXYGEN_FOUND)
- set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>")
-
- set(DOXYGEN_GENERATE_LATEX YES)
-
- set(DOXYGEN_PDF_HYPERLINKS YES)
-
- set(DOXYGEN_PROJECT_LOGO "${CMAKE_SOURCE_DIR}/img/brandmark.png")
-
- set(DOXYGEN_SORT_BRIEF_DOCS YES)
-
- set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md")
-
- doxygen_add_docs(
- ${LIBRARY_NAME}_docs
- "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h"
- "${CMAKE_SOURCE_DIR}/README.md"
- USE_STAMP_FILE
- WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
- COMMENT "Producing documentation with Doxygen..."
- )
-
- # \note A Doxygen input file isn't a file-level dependency so the Doxygen
- # command must instead depend upon a target that outputs the file or
- # it will just output an error message when it can't be found.
- add_dependencies(${LIBRARY_NAME}_docs ${LIBRARY_NAME}_artifacts)
-endif()
diff --git a/rust/automerge-c/src/actor_id.rs b/rust/automerge-c/src/actor_id.rs
index bc86d5ef..5a28959e 100644
--- a/rust/automerge-c/src/actor_id.rs
+++ b/rust/automerge-c/src/actor_id.rs
@@ -1,4 +1,5 @@
use automerge as am;
+use libc::c_int;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::str::FromStr;
@@ -11,7 +12,7 @@ macro_rules! to_actor_id {
let handle = $handle.as_ref();
match handle {
Some(b) => b,
- None => return AMresult::err("Invalid AMactorId pointer").into(),
+ None => return AMresult::error("Invalid `AMactorId*`").into(),
}
}};
}
@@ -57,11 +58,11 @@ impl AsRef for AMactorId {
}
/// \memberof AMactorId
-/// \brief Gets the value of an actor identifier as a sequence of bytes.
+/// \brief Gets the value of an actor identifier as an array of bytes.
///
/// \param[in] actor_id A pointer to an `AMactorId` struct.
-/// \pre \p actor_id `!= NULL`.
-/// \return An `AMbyteSpan` struct.
+/// \return An `AMbyteSpan` struct for an array of bytes.
+/// \pre \p actor_id `!= NULL`
/// \internal
///
/// # Safety
@@ -82,8 +83,8 @@ pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpa
/// \return `-1` if \p actor_id1 `<` \p actor_id2, `0` if
/// \p actor_id1 `==` \p actor_id2 and `1` if
/// \p actor_id1 `>` \p actor_id2.
-/// \pre \p actor_id1 `!= NULL`.
-/// \pre \p actor_id2 `!= NULL`.
+/// \pre \p actor_id1 `!= NULL`
+/// \pre \p actor_id2 `!= NULL`
/// \internal
///
/// #Safety
@@ -93,7 +94,7 @@ pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpa
pub unsafe extern "C" fn AMactorIdCmp(
actor_id1: *const AMactorId,
actor_id2: *const AMactorId,
-) -> isize {
+) -> c_int {
match (actor_id1.as_ref(), actor_id2.as_ref()) {
(Some(actor_id1), Some(actor_id2)) => match actor_id1.as_ref().cmp(actor_id2.as_ref()) {
Ordering::Less => -1,
@@ -101,65 +102,69 @@ pub unsafe extern "C" fn AMactorIdCmp(
Ordering::Greater => 1,
},
(None, Some(_)) => -1,
- (Some(_), None) => 1,
(None, None) => 0,
+ (Some(_), None) => 1,
}
}
/// \memberof AMactorId
-/// \brief Allocates a new actor identifier and initializes it with a random
-/// UUID.
+/// \brief Allocates a new actor identifier and initializes it from a random
+/// UUID value.
///
-/// \return A pointer to an `AMresult` struct containing a pointer to an
-/// `AMactorId` struct.
-/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
-/// in order to prevent a memory leak.
+/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item.
+/// \warning The returned `AMresult` struct pointer must be passed to
+/// `AMresultFree()` in order to avoid a memory leak.
#[no_mangle]
pub unsafe extern "C" fn AMactorIdInit() -> *mut AMresult {
to_result(Ok::(am::ActorId::random()))
}
/// \memberof AMactorId
-/// \brief Allocates a new actor identifier and initializes it from a sequence
-/// of bytes.
+/// \brief Allocates a new actor identifier and initializes it from an array of
+/// bytes value.
///
-/// \param[in] src A pointer to a contiguous sequence of bytes.
-/// \param[in] count The number of bytes to copy from \p src.
-/// \pre `0 <` \p count `<= sizeof(`\p src`)`.
-/// \return A pointer to an `AMresult` struct containing a pointer to an
-/// `AMactorId` struct.
-/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
-/// in order to prevent a memory leak.
+/// \param[in] src A pointer to an array of bytes.
+/// \param[in] count The count of bytes to copy from the array pointed to by
+/// \p src.
+/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item.
+/// \pre \p src `!= NULL`
+/// \pre `sizeof(`\p src `) > 0`
+/// \pre \p count `<= sizeof(`\p src `)`
+/// \warning The returned `AMresult` struct pointer must be passed to
+/// `AMresultFree()` in order to avoid a memory leak.
/// \internal
///
/// # Safety
-/// src must be a byte array of size `>= count`
+/// src must be a byte array of length `>= count`
#[no_mangle]
-pub unsafe extern "C" fn AMactorIdInitBytes(src: *const u8, count: usize) -> *mut AMresult {
- let slice = std::slice::from_raw_parts(src, count);
- to_result(Ok::(am::ActorId::from(
- slice,
- )))
+pub unsafe extern "C" fn AMactorIdFromBytes(src: *const u8, count: usize) -> *mut AMresult {
+ if !src.is_null() {
+ let value = std::slice::from_raw_parts(src, count);
+ to_result(Ok::(am::ActorId::from(
+ value,
+ )))
+ } else {
+ AMresult::error("Invalid uint8_t*").into()
+ }
}
/// \memberof AMactorId
/// \brief Allocates a new actor identifier and initializes it from a
-/// hexadecimal string.
+/// hexadecimal UTF-8 string view value.
///
-/// \param[in] hex_str A UTF-8 string view as an `AMbyteSpan` struct.
-/// \return A pointer to an `AMresult` struct containing a pointer to an
-/// `AMactorId` struct.
-/// \warning The returned `AMresult` struct must be deallocated with `AMfree()`
-/// in order to prevent a memory leak.
+/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct.
+/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item.
+/// \warning The returned `AMresult` struct pointer must be passed to
+/// `AMresultFree()` in order to avoid a memory leak.
/// \internal
///
/// # Safety
/// hex_str must be a valid pointer to an AMbyteSpan
#[no_mangle]
-pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult {
+pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult {
use am::AutomergeError::InvalidActorId;
- to_result(match (&hex_str).try_into() {
+ to_result(match (&value).try_into() {
Ok(s) => match am::ActorId::from_str(s) {
Ok(actor_id) => Ok(actor_id),
Err(_) => Err(InvalidActorId(String::from(s))),
@@ -169,11 +174,12 @@ pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult
}
/// \memberof AMactorId
-/// \brief Gets the value of an actor identifier as a hexadecimal string.
+/// \brief Gets the value of an actor identifier as a UTF-8 hexadecimal string
+/// view.
///
/// \param[in] actor_id A pointer to an `AMactorId` struct.
-/// \pre \p actor_id `!= NULL`.
/// \return A UTF-8 string view as an `AMbyteSpan` struct.
+/// \pre \p actor_id `!= NULL`
/// \internal
///
/// # Safety
diff --git a/rust/automerge-c/src/byte_span.rs b/rust/automerge-c/src/byte_span.rs
index fd4c3ca0..5855cfc7 100644
--- a/rust/automerge-c/src/byte_span.rs
+++ b/rust/automerge-c/src/byte_span.rs
@@ -1,14 +1,17 @@
use automerge as am;
-use libc::strlen;
+use std::cmp::Ordering;
use std::convert::TryFrom;
use std::os::raw::c_char;
+use libc::{c_int, strlen};
+use smol_str::SmolStr;
+
macro_rules! to_str {
- ($span:expr) => {{
- let result: Result<&str, am::AutomergeError> = (&$span).try_into();
+ ($byte_span:expr) => {{
+ let result: Result<&str, am::AutomergeError> = (&$byte_span).try_into();
match result {
Ok(s) => s,
- Err(e) => return AMresult::err(&e.to_string()).into(),
+ Err(e) => return AMresult::error(&e.to_string()).into(),
}
}};
}
@@ -17,16 +20,17 @@ pub(crate) use to_str;
/// \struct AMbyteSpan
/// \installed_headerfile
-/// \brief A view onto a contiguous sequence of bytes.
+/// \brief A view onto an array of bytes.
#[repr(C)]
pub struct AMbyteSpan {
- /// A pointer to an array of bytes.
- /// \attention NEVER CALL `free()` ON \p src!
- /// \warning \p src is only valid until the `AMfree()` function is called
- /// on the `AMresult` struct that stores the array of bytes to
- /// which it points.
+ /// A pointer to the first byte of an array of bytes.
+ /// \warning \p src is only valid until the array of bytes to which it
+ /// points is freed.
+ /// \note If the `AMbyteSpan` came from within an `AMitem` struct then
+ /// \p src will be freed when the pointer to the `AMresult` struct
+ /// containing the `AMitem` struct is passed to `AMresultFree()`.
pub src: *const u8,
- /// The number of bytes in the array.
+ /// The count of bytes in the array.
pub count: usize,
}
@@ -52,9 +56,7 @@ impl PartialEq for AMbyteSpan {
} else if self.src == other.src {
return true;
}
- let slice = unsafe { std::slice::from_raw_parts(self.src, self.count) };
- let other_slice = unsafe { std::slice::from_raw_parts(other.src, other.count) };
- slice == other_slice
+ <&[u8]>::from(self) == <&[u8]>::from(other)
}
}
@@ -72,10 +74,15 @@ impl From<&am::ActorId> for AMbyteSpan {
impl From<&mut am::ActorId> for AMbyteSpan {
fn from(actor: &mut am::ActorId) -> Self {
- let slice = actor.to_bytes();
+ actor.as_ref().into()
+ }
+}
+
+impl From<&am::ChangeHash> for AMbyteSpan {
+ fn from(change_hash: &am::ChangeHash) -> Self {
Self {
- src: slice.as_ptr(),
- count: slice.len(),
+ src: change_hash.0.as_ptr(),
+ count: change_hash.0.len(),
}
}
}
@@ -93,12 +100,9 @@ impl From<*const c_char> for AMbyteSpan {
}
}
-impl From<&am::ChangeHash> for AMbyteSpan {
- fn from(change_hash: &am::ChangeHash) -> Self {
- Self {
- src: change_hash.0.as_ptr(),
- count: change_hash.0.len(),
- }
+impl From<&SmolStr> for AMbyteSpan {
+ fn from(smol_str: &SmolStr) -> Self {
+ smol_str.as_bytes().into()
}
}
@@ -111,13 +115,39 @@ impl From<&[u8]> for AMbyteSpan {
}
}
+impl From<&AMbyteSpan> for &[u8] {
+ fn from(byte_span: &AMbyteSpan) -> Self {
+ unsafe { std::slice::from_raw_parts(byte_span.src, byte_span.count) }
+ }
+}
+
+impl From<&AMbyteSpan> for Vec {
+ fn from(byte_span: &AMbyteSpan) -> Self {
+ <&[u8]>::from(byte_span).to_vec()
+ }
+}
+
+impl TryFrom<&AMbyteSpan> for am::ChangeHash {
+ type Error = am::AutomergeError;
+
+ fn try_from(byte_span: &AMbyteSpan) -> Result {
+ use am::AutomergeError::InvalidChangeHashBytes;
+
+ let slice: &[u8] = byte_span.into();
+ match slice.try_into() {
+ Ok(change_hash) => Ok(change_hash),
+ Err(e) => Err(InvalidChangeHashBytes(e)),
+ }
+ }
+}
+
impl TryFrom<&AMbyteSpan> for &str {
type Error = am::AutomergeError;
- fn try_from(span: &AMbyteSpan) -> Result {
+ fn try_from(byte_span: &AMbyteSpan) -> Result {
use am::AutomergeError::InvalidCharacter;
- let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) };
+ let slice = byte_span.into();
match std::str::from_utf8(slice) {
Ok(str_) => Ok(str_),
Err(e) => Err(InvalidCharacter(e.valid_up_to())),
@@ -125,17 +155,69 @@ impl TryFrom<&AMbyteSpan> for &str {
}
}
-/// \brief Creates an AMbyteSpan from a pointer + length
+/// \memberof AMbyteSpan
+/// \brief Creates a view onto an array of bytes.
///
-/// \param[in] src A pointer to a span of bytes
-/// \param[in] count The number of bytes in the span
-/// \return An `AMbyteSpan` struct
+/// \param[in] src A pointer to an array of bytes or `NULL`.
+/// \param[in] count The count of bytes to view from the array pointed to by
+/// \p src.
+/// \return An `AMbyteSpan` struct.
+/// \pre \p count `<= sizeof(`\p src `)`
+/// \post `(`\p src `== NULL) -> (AMbyteSpan){NULL, 0}`
/// \internal
///
/// #Safety
-/// AMbytes does not retain the underlying storage, so you must discard the
-/// return value before freeing the bytes.
+/// src must be a byte array of length `>= count` or `std::ptr::null()`
#[no_mangle]
pub unsafe extern "C" fn AMbytes(src: *const u8, count: usize) -> AMbyteSpan {
- AMbyteSpan { src, count }
+ AMbyteSpan {
+ src,
+ count: if src.is_null() { 0 } else { count },
+ }
+}
+
+/// \memberof AMbyteSpan
+/// \brief Creates a view onto a C string.
+///
+/// \param[in] c_str A null-terminated byte string or `NULL`.
+/// \return An `AMbyteSpan` struct.
+/// \pre Each byte in \p c_str encodes one UTF-8 character.
+/// \internal
+///
+/// #Safety
+/// c_str must be a null-terminated array of `std::os::raw::c_char` or `std::ptr::null()`.
+#[no_mangle]
+pub unsafe extern "C" fn AMstr(c_str: *const c_char) -> AMbyteSpan {
+ c_str.into()
+}
+
+/// \memberof AMbyteSpan
+/// \brief Compares two UTF-8 string views lexicographically.
+///
+/// \param[in] lhs A UTF-8 string view as an `AMbyteSpan` struct.
+/// \param[in] rhs A UTF-8 string view as an `AMbyteSpan` struct.
+/// \return Negative value if \p lhs appears before \p rhs in lexicographical order.
+/// Zero if \p lhs and \p rhs compare equal.
+/// Positive value if \p lhs appears after \p rhs in lexicographical order.
+/// \pre \p lhs.src `!= NULL`
+/// \pre \p lhs.count `<= sizeof(`\p lhs.src `)`
+/// \pre \p rhs.src `!= NULL`
+/// \pre \p rhs.count `<= sizeof(`\p rhs.src `)`
+/// \internal
+///
+/// #Safety
+/// lhs.src must be a byte array of length >= lhs.count
+/// rhs.src must be a a byte array of length >= rhs.count
+#[no_mangle]
+pub unsafe extern "C" fn AMstrCmp(lhs: AMbyteSpan, rhs: AMbyteSpan) -> c_int {
+ match (<&str>::try_from(&lhs), <&str>::try_from(&rhs)) {
+ (Ok(lhs), Ok(rhs)) => match lhs.cmp(rhs) {
+ Ordering::Less => -1,
+ Ordering::Equal => 0,
+ Ordering::Greater => 1,
+ },
+ (Err(_), Ok(_)) => -1,
+ (Err(_), Err(_)) => 0,
+ (Ok(_), Err(_)) => 1,
+ }
}
diff --git a/rust/automerge-c/src/change.rs b/rust/automerge-c/src/change.rs
index d64a2635..8529ed94 100644
--- a/rust/automerge-c/src/change.rs
+++ b/rust/automerge-c/src/change.rs
@@ -2,7 +2,6 @@ use automerge as am;
use std::cell::RefCell;
use crate::byte_span::AMbyteSpan;
-use crate::change_hashes::AMchangeHashes;
use crate::result::{to_result, AMresult};
macro_rules! to_change {
@@ -10,7 +9,7 @@ macro_rules! to_change {
let handle = $handle.as_ref();
match handle {
Some(b) => b,
- None => return AMresult::err("Invalid AMchange pointer").into(),
+ None => return AMresult::error("Invalid `AMchange*`").into(),
}
}};
}
@@ -21,14 +20,14 @@ macro_rules! to_change {
#[derive(Eq, PartialEq)]
pub struct AMchange {
body: *mut am::Change,
- changehash: RefCell