From 2a4cda9dac8f011661f0a660f7bd7783a3423c69 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 11:15:54 +0800 Subject: [PATCH 1/2] chore(spm): switch xcframework releases to cargo-swift --- .github/workflows/release-xcframework.yml | 185 ++++- ...d523ff78c5fe2ceded45599bc3f0a4ee1e79.json} | 38 +- ...69897522d063cacaa6fed530f3064cfe16d4.json} | 38 +- ...16c82acf52885045a166b8305e123b9bfe9e.json} | 38 +- ...4ca7cfe57f8cb48730f477ab564f02844d0b2.json | 22 - AGENTS.md | 14 +- Package.swift | 12 +- README.md | 47 +- Sources/TaskChampionFFI/TaskChampionFFI.swift | 56 +- Sources/TaskChampionFFI/TaskChampionFFIFFI.h | 755 ------------------ .../TaskChampionFFIFFI.modulemap | 7 - ffi/src/convert.rs | 1 - ffi/src/replica_ops.rs | 90 +-- ffi/src/task_ops.rs | 4 +- ffi/src/types.rs | 9 - ffi/tests/round_trip.rs | 46 +- scripts/build_xcframework.sh | 175 ---- scripts/package_cargo_swift.sh | 79 ++ scripts/use_local_xcframework.sh | 68 ++ src/replica.rs | 23 - src/storage/columns.rs | 9 +- src/storage/external.rs | 31 - src/storage/mod.rs | 18 - src/storage/pgwire/mod.rs | 17 - src/storage/pgwire/row.rs | 5 - src/storage/powersync/inner.rs | 14 - src/storage/powersync/mod.rs | 63 -- src/storage/sql_ops.rs | 1 - src/task/task.rs | 27 - src/taskdb/mod.rs | 5 - 30 files changed, 442 insertions(+), 1455 deletions(-) rename .sqlx/{query-7136097d13f6ab68ba12f15e1f5d5a0c71941ee298264a57a7c944ba63cc3269.json => query-0db9f5fe7a74e466e9df0c4c9a0ad523ff78c5fe2ceded45599bc3f0a4ee1e79.json} (71%) rename .sqlx/{query-b07ff48fe7f2449e25a0c802bb8560f2ec2a10089ad84085918a06f717ec6783.json => query-240f7d7591d413d991976d92a2f369897522d063cacaa6fed530f3064cfe16d4.json} (70%) rename .sqlx/{query-363e9e0aa3cbfac7e1bf277bfafd93e2a39b116cb12d476893f7a2c92fc59cee.json => query-6d791f260498f5a6d742ebee8f6e16c82acf52885045a166b8305e123b9bfe9e.json} (70%) delete mode 100644 .sqlx/query-97840db2fb51d1923d7d56d018a4ca7cfe57f8cb48730f477ab564f02844d0b2.json delete mode 100644 Sources/TaskChampionFFI/TaskChampionFFIFFI.h delete mode 100644 Sources/TaskChampionFFI/TaskChampionFFIFFI.modulemap delete mode 100755 scripts/build_xcframework.sh create mode 100755 scripts/package_cargo_swift.sh create mode 100755 scripts/use_local_xcframework.sh diff --git a/.github/workflows/release-xcframework.yml b/.github/workflows/release-xcframework.yml index 5eaea4382..68f21da2a 100644 --- a/.github/workflows/release-xcframework.yml +++ b/.github/workflows/release-xcframework.yml @@ -1,10 +1,13 @@ name: Release XCFramework on: + push: + branches: + - main workflow_dispatch: inputs: version: - description: "Version tag (e.g. v3.0.2-guion.19). Leave empty to auto-bump." + description: "Version tag (e.g. v3.0.2-guion.19)." required: false type: string @@ -17,7 +20,7 @@ concurrency: jobs: release: - runs-on: macos-15 + runs-on: macos-14 steps: - name: Generate release bot token @@ -30,7 +33,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: token: ${{ steps.app-token.outputs.token }} - ref: main + ref: ${{ github.ref }} fetch-depth: 0 fetch-tags: true @@ -39,7 +42,17 @@ jobs: env: INPUT_VERSION: ${{ inputs.version }} run: | - if [ -n "$INPUT_VERSION" ]; then + set -euo pipefail + if [ "${GITHUB_EVENT_NAME}" = "push" ]; then + LATEST=$(git tag --list 'v*-guion.*' --sort=-v:refname | grep -Ev '(dynamic|snapshot)' | head -1) + if [ -z "$LATEST" ]; then + echo "ERROR: no formal v*-guion.* tag found for snapshot base" >&2 + exit 1 + fi + DATE="$(date -u +%Y%m%d%H%M%S)" + SHORT_SHA="${GITHUB_SHA::7}" + echo "tag=${LATEST}-snapshot.${DATE}.${SHORT_SHA}" >> "$GITHUB_OUTPUT" + elif [ -n "$INPUT_VERSION" ]; then if [[ ! "$INPUT_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?$ ]]; then echo "ERROR: version must match vMAJOR.MINOR.PATCH[-suffix] (e.g. v3.0.2-guion.19)" >&2 exit 1 @@ -47,7 +60,7 @@ jobs: echo "tag=${INPUT_VERSION}" >> "$GITHUB_OUTPUT" else # Auto-bump: find latest v*-guion.* tag and increment - LATEST=$(git tag --list 'v*-guion.*' --sort=-v:refname | head -1) + LATEST=$(git tag --list 'v*-guion.*' --sort=-v:refname | grep -Ev '(dynamic|snapshot)' | head -1) if [ -z "$LATEST" ]; then echo "ERROR: no existing v*-guion.* tags found — provide version manually" >&2 exit 1 @@ -63,6 +76,18 @@ jobs: echo "Auto-bumped: ${LATEST} → ${PREFIX}.${NEXT}" fi + - name: Determine linkage + id: linkage + env: + VERSION_TAG: ${{ steps.version.outputs.tag }} + run: | + set -euo pipefail + if [[ "$VERSION_TAG" == *snapshot* || "$VERSION_TAG" == *dynamic* ]]; then + echo "linkage=dynamic" >> "$GITHUB_OUTPUT" + else + echo "linkage=static" >> "$GITHUB_OUTPUT" + fi + - name: Install Rust toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1 (stable) with: @@ -71,10 +96,12 @@ jobs: - name: Select Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '16.0' + xcode-version: '16.2' - - name: Show Xcode SDKs + - name: Show tool versions run: | + rustc --version + cargo --version xcodebuild -version xcodebuild -showsdks @@ -91,52 +118,104 @@ jobs: - name: Cache cargo build artifacts uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: - path: target - key: ${{ runner.os }}-cargo-target-${{ hashFiles('**/Cargo.lock', 'scripts/build_xcframework.sh', '.github/workflows/release-xcframework.yml') }} + path: | + target + ffi/target + key: ${{ runner.os }}-cargo-swift-target-${{ hashFiles('**/Cargo.lock', 'ffi/Cargo.toml', 'ffi/uniffi.toml', 'scripts/package_cargo_swift.sh', '.github/workflows/release-xcframework.yml') }} + restore-keys: | + ${{ runner.os }}-cargo-swift-target- - - name: Build XCFramework and generate Swift bindings - timeout-minutes: 30 - run: ./scripts/build_xcframework.sh + - name: Install cargo-swift + run: | + set -euo pipefail + cargo install cargo-swift@0.11.1 --locked + cargo swift --version + + - name: Package framework with cargo-swift + timeout-minutes: 40 + env: + TASKCHAMPION_FFI_LINKAGE: ${{ steps.linkage.outputs.linkage }} + run: | + set -euo pipefail + ./scripts/package_cargo_swift.sh "${RUNNER_TEMP}/cargo-swift-output" + rm -rf "$GITHUB_WORKSPACE/ffi/TaskChampionFFI" + cp -R "${RUNNER_TEMP}/cargo-swift-output/TaskChampionFFI" "$GITHUB_WORKSPACE/ffi/TaskChampionFFI" + cp "${RUNNER_TEMP}/cargo-swift-output/TaskChampionFFI/Sources/TaskChampionFFI/taskchampion_ffi.swift" \ + "$GITHUB_WORKSPACE/Sources/TaskChampionFFI/TaskChampionFFI.swift" - - name: Validate iOS archive has no bundled SQLite + - name: Inspect cargo-swift output + working-directory: ffi + env: + LINKAGE: ${{ steps.linkage.outputs.linkage }} run: | set -euo pipefail - ios_lib="target/aarch64-apple-ios/release/libtaskchampion_ffi.a" + package_dir="TaskChampionFFI" + xcframework="${package_dir}/TaskChampionCore.xcframework" + expected="@rpath/TaskChampionCore.framework/TaskChampionCore" - if ar -t "$ios_lib" | grep -q 'sqlite3\.o$'; then - echo "ERROR: bundled sqlite3.o found in $ios_lib" >&2 + if [ ! -d "$xcframework" ]; then + echo "ERROR: cargo-swift did not create ${xcframework}" >&2 + find "$package_dir" -maxdepth 4 -print | sort >&2 exit 1 fi - echo "No bundled sqlite3.o found in $ios_lib" + legacy_module="TaskChampionFFI""FFI" + if grep -R -n "$legacy_module" "$package_dir"; then + echo "ERROR: generated package still references the legacy doubled-FFI module" >&2 + exit 1 + fi - - name: Validate Swift bindings were generated - run: | - [ -s Sources/TaskChampionFFI/TaskChampionFFI.swift ] || { - echo "ERROR: Swift bindings file is missing or empty" >&2; exit 1 - } + { + echo "## Package.swift" + sed -n '1,220p' "$package_dir/Package.swift" + echo + echo "## Output tree" + find "$package_dir" -maxdepth 6 -print | sort + echo + echo "## Binary files" + find "$xcframework" -type f -perm -111 -print | sort + echo + echo "## Framework bundles" + find "$xcframework" -name '*.framework' -type d -print | sort + } | tee cargo-swift-report.txt + + while IFS= read -r binary; do + echo "## file ${binary}" | tee -a cargo-swift-report.txt + file "$binary" | tee -a cargo-swift-report.txt + if [ "$LINKAGE" = "dynamic" ]; then + install_name="$(otool -D "$binary" | tail -n 1)" + echo "$install_name" | tee -a cargo-swift-report.txt + if [ "$install_name" != "$expected" ]; then + echo "ERROR: unexpected install name for $binary: $install_name" >&2 + exit 1 + fi + fi + done < <(find "$xcframework" -type f -perm -111 -print | sort) - name: Zip XCFramework run: | - zip -r TaskChampionFFIFFI.xcframework.zip TaskChampionFFIFFI.xcframework - [ -s TaskChampionFFIFFI.xcframework.zip ] || { echo "ERROR: zip archive is empty or missing"; exit 1; } + set -euo pipefail + rm -rf TaskChampionCore.xcframework TaskChampionCore.xcframework.zip + cp -R ffi/TaskChampionFFI/TaskChampionCore.xcframework TaskChampionCore.xcframework + zip -r TaskChampionCore.xcframework.zip TaskChampionCore.xcframework + [ -s TaskChampionCore.xcframework.zip ] || { echo "ERROR: zip archive is empty or missing"; exit 1; } - - name: Compute SHA-256 checksum + - name: Compute SwiftPM checksum id: checksum run: | - shasum -a 256 TaskChampionFFIFFI.xcframework.zip > checksum.txt - CHECKSUM=$(awk '{print $1}' checksum.txt) + CHECKSUM=$(swift package compute-checksum TaskChampionCore.xcframework.zip) if [ ${#CHECKSUM} -ne 64 ]; then echo "ERROR: unexpected checksum length (${#CHECKSUM}): '${CHECKSUM}'" >&2 exit 1 fi echo "checksum=${CHECKSUM}" >> "$GITHUB_OUTPUT" - # Update Package.swift and commit BEFORE tagging — so the tag - # includes the correct URL + checksum. No more chicken-and-egg. + # Update Package.swift and commit BEFORE tagging so the tag includes + # the correct URL + checksum. - name: Update Package.swift with release URL and checksum env: - RELEASE_URL: https://github.com/GuionAI/taskchampion/releases/download/${{ steps.version.outputs.tag }}/TaskChampionFFIFFI.xcframework.zip + VERSION_TAG: ${{ steps.version.outputs.tag }} + RELEASE_URL: https://github.com/GuionAI/taskchampion/releases/download/${{ steps.version.outputs.tag }}/TaskChampionCore.xcframework.zip RELEASE_CHECKSUM: ${{ steps.checksum.outputs.checksum }} run: | python3 -c " @@ -176,21 +255,45 @@ jobs: git config user.name "guion-release-bot[bot]" git config user.email "guion-release-bot[bot]@users.noreply.github.com" git add Package.swift Sources/TaskChampionFFI/TaskChampionFFI.swift - git diff --cached --quiet && { echo "No changes — skipping commit"; } || { + if git diff --cached --quiet; then + echo "No changes — skipping commit" + else git commit -m "chore(spm): update Package.swift and Swift bindings for ${VERSION_TAG} release" - } - git push origin main + fi + git push origin "HEAD:${GITHUB_REF_NAME}" - # gh release create owns the tag via --target main, making the tag - # atomic with the release. If this step fails, no tag exists and the - # workflow is safely re-runnable. + # Move the tag after the release commit so SwiftPM sees the matching + # Package.swift URL and checksum when pinning this version. - name: Create GitHub Release and upload XCFramework zip env: GH_TOKEN: ${{ steps.app-token.outputs.token }} VERSION_TAG: ${{ steps.version.outputs.tag }} run: | - gh release create "$VERSION_TAG" \ - TaskChampionFFIFFI.xcframework.zip \ - --target main \ - --generate-notes \ - --title "$VERSION_TAG" + git tag -f "$VERSION_TAG" HEAD + git push origin "refs/tags/${VERSION_TAG}" --force + + if gh release view "$VERSION_TAG" >/dev/null 2>&1; then + gh release upload "$VERSION_TAG" \ + TaskChampionCore.xcframework.zip \ + --clobber + if [[ "$VERSION_TAG" == *snapshot* ]]; then + gh release edit "$VERSION_TAG" \ + --title "$VERSION_TAG" \ + --prerelease + else + gh release edit "$VERSION_TAG" \ + --title "$VERSION_TAG" + fi + else + release_args=( + "$VERSION_TAG" + TaskChampionCore.xcframework.zip + --target "$(git rev-parse HEAD)" + --generate-notes + --title "$VERSION_TAG" + ) + if [[ "$VERSION_TAG" == *snapshot* ]]; then + release_args+=(--prerelease) + fi + gh release create "${release_args[@]}" + fi diff --git a/.sqlx/query-7136097d13f6ab68ba12f15e1f5d5a0c71941ee298264a57a7c944ba63cc3269.json b/.sqlx/query-0db9f5fe7a74e466e9df0c4c9a0ad523ff78c5fe2ceded45599bc3f0a4ee1e79.json similarity index 71% rename from .sqlx/query-7136097d13f6ab68ba12f15e1f5d5a0c71941ee298264a57a7c944ba63cc3269.json rename to .sqlx/query-0db9f5fe7a74e466e9df0c4c9a0ad523ff78c5fe2ceded45599bc3f0a4ee1e79.json index 67ea18187..aade18f2d 100644 --- a/.sqlx/query-7136097d13f6ab68ba12f15e1f5d5a0c71941ee298264a57a7c944ba63cc3269.json +++ b/.sqlx/query-0db9f5fe7a74e466e9df0c4c9a0ad523ff78c5fe2ceded45599bc3f0a4ee1e79.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n t.id,\n t.short_id,\n t.data as \"data: serde_json::Value\",\n t.status,\n t.description,\n t.priority,\n t.entry_at,\n t.modified_at,\n t.due_at,\n t.scheduled_at,\n t.start_at,\n t.end_at,\n t.wait_at,\n t.parent_id,\n p.name as \"project_name?\",\n t.project_id,\n t.note_id\n FROM tc_tasks t\n LEFT JOIN projects p ON t.project_id = p.id\n ", + "query": "\n SELECT\n t.id,\n t.data as \"data: serde_json::Value\",\n t.status,\n t.description,\n t.priority,\n t.entry_at,\n t.modified_at,\n t.due_at,\n t.scheduled_at,\n t.start_at,\n t.end_at,\n t.wait_at,\n t.parent_id,\n p.name as \"project_name?\",\n t.project_id,\n t.note_id\n FROM tc_tasks t\n LEFT JOIN projects p ON t.project_id = p.id\n ", "describe": { "columns": [ { @@ -10,81 +10,76 @@ }, { "ordinal": 1, - "name": "short_id", - "type_info": "Int4" - }, - { - "ordinal": 2, "name": "data: serde_json::Value", "type_info": "Jsonb" }, { - "ordinal": 3, + "ordinal": 2, "name": "status", "type_info": "Text" }, { - "ordinal": 4, + "ordinal": 3, "name": "description", "type_info": "Text" }, { - "ordinal": 5, + "ordinal": 4, "name": "priority", "type_info": "Text" }, { - "ordinal": 6, + "ordinal": 5, "name": "entry_at", "type_info": "Timestamptz" }, { - "ordinal": 7, + "ordinal": 6, "name": "modified_at", "type_info": "Timestamptz" }, { - "ordinal": 8, + "ordinal": 7, "name": "due_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 8, "name": "scheduled_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 9, "name": "start_at", "type_info": "Timestamptz" }, { - "ordinal": 11, + "ordinal": 10, "name": "end_at", "type_info": "Timestamptz" }, { - "ordinal": 12, + "ordinal": 11, "name": "wait_at", "type_info": "Timestamptz" }, { - "ordinal": 13, + "ordinal": 12, "name": "parent_id", "type_info": "Uuid" }, { - "ordinal": 14, + "ordinal": 13, "name": "project_name?", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 14, "name": "project_id", "type_info": "Uuid" }, { - "ordinal": 16, + "ordinal": 15, "name": "note_id", "type_info": "Uuid" } @@ -93,7 +88,6 @@ "Left": [] }, "nullable": [ - false, false, false, true, @@ -112,5 +106,5 @@ true ] }, - "hash": "7136097d13f6ab68ba12f15e1f5d5a0c71941ee298264a57a7c944ba63cc3269" + "hash": "0db9f5fe7a74e466e9df0c4c9a0ad523ff78c5fe2ceded45599bc3f0a4ee1e79" } diff --git a/.sqlx/query-b07ff48fe7f2449e25a0c802bb8560f2ec2a10089ad84085918a06f717ec6783.json b/.sqlx/query-240f7d7591d413d991976d92a2f369897522d063cacaa6fed530f3064cfe16d4.json similarity index 70% rename from .sqlx/query-b07ff48fe7f2449e25a0c802bb8560f2ec2a10089ad84085918a06f717ec6783.json rename to .sqlx/query-240f7d7591d413d991976d92a2f369897522d063cacaa6fed530f3064cfe16d4.json index 36ef81374..ff893c939 100644 --- a/.sqlx/query-b07ff48fe7f2449e25a0c802bb8560f2ec2a10089ad84085918a06f717ec6783.json +++ b/.sqlx/query-240f7d7591d413d991976d92a2f369897522d063cacaa6fed530f3064cfe16d4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n t.id,\n t.short_id,\n t.data as \"data: serde_json::Value\",\n t.status,\n t.description,\n t.priority,\n t.entry_at,\n t.modified_at,\n t.due_at,\n t.scheduled_at,\n t.start_at,\n t.end_at,\n t.wait_at,\n t.parent_id,\n p.name as \"project_name?\",\n t.project_id,\n t.note_id\n FROM tc_tasks t\n LEFT JOIN projects p ON t.project_id = p.id\n WHERE t.status = 'pending'\n ", + "query": "\n SELECT\n t.id,\n t.data as \"data: serde_json::Value\",\n t.status,\n t.description,\n t.priority,\n t.entry_at,\n t.modified_at,\n t.due_at,\n t.scheduled_at,\n t.start_at,\n t.end_at,\n t.wait_at,\n t.parent_id,\n p.name as \"project_name?\",\n t.project_id,\n t.note_id\n FROM tc_tasks t\n LEFT JOIN projects p ON t.project_id = p.id\n WHERE t.status = 'pending'\n ", "describe": { "columns": [ { @@ -10,81 +10,76 @@ }, { "ordinal": 1, - "name": "short_id", - "type_info": "Int4" - }, - { - "ordinal": 2, "name": "data: serde_json::Value", "type_info": "Jsonb" }, { - "ordinal": 3, + "ordinal": 2, "name": "status", "type_info": "Text" }, { - "ordinal": 4, + "ordinal": 3, "name": "description", "type_info": "Text" }, { - "ordinal": 5, + "ordinal": 4, "name": "priority", "type_info": "Text" }, { - "ordinal": 6, + "ordinal": 5, "name": "entry_at", "type_info": "Timestamptz" }, { - "ordinal": 7, + "ordinal": 6, "name": "modified_at", "type_info": "Timestamptz" }, { - "ordinal": 8, + "ordinal": 7, "name": "due_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 8, "name": "scheduled_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 9, "name": "start_at", "type_info": "Timestamptz" }, { - "ordinal": 11, + "ordinal": 10, "name": "end_at", "type_info": "Timestamptz" }, { - "ordinal": 12, + "ordinal": 11, "name": "wait_at", "type_info": "Timestamptz" }, { - "ordinal": 13, + "ordinal": 12, "name": "parent_id", "type_info": "Uuid" }, { - "ordinal": 14, + "ordinal": 13, "name": "project_name?", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 14, "name": "project_id", "type_info": "Uuid" }, { - "ordinal": 16, + "ordinal": 15, "name": "note_id", "type_info": "Uuid" } @@ -93,7 +88,6 @@ "Left": [] }, "nullable": [ - false, false, false, true, @@ -112,5 +106,5 @@ true ] }, - "hash": "b07ff48fe7f2449e25a0c802bb8560f2ec2a10089ad84085918a06f717ec6783" + "hash": "240f7d7591d413d991976d92a2f369897522d063cacaa6fed530f3064cfe16d4" } diff --git a/.sqlx/query-363e9e0aa3cbfac7e1bf277bfafd93e2a39b116cb12d476893f7a2c92fc59cee.json b/.sqlx/query-6d791f260498f5a6d742ebee8f6e16c82acf52885045a166b8305e123b9bfe9e.json similarity index 70% rename from .sqlx/query-363e9e0aa3cbfac7e1bf277bfafd93e2a39b116cb12d476893f7a2c92fc59cee.json rename to .sqlx/query-6d791f260498f5a6d742ebee8f6e16c82acf52885045a166b8305e123b9bfe9e.json index a0f475205..6446fd433 100644 --- a/.sqlx/query-363e9e0aa3cbfac7e1bf277bfafd93e2a39b116cb12d476893f7a2c92fc59cee.json +++ b/.sqlx/query-6d791f260498f5a6d742ebee8f6e16c82acf52885045a166b8305e123b9bfe9e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n t.id,\n t.short_id,\n t.data as \"data: serde_json::Value\",\n t.status,\n t.description,\n t.priority,\n t.entry_at,\n t.modified_at,\n t.due_at,\n t.scheduled_at,\n t.start_at,\n t.end_at,\n t.wait_at,\n t.parent_id,\n p.name as \"project_name?\",\n t.project_id,\n t.note_id\n FROM tc_tasks t\n LEFT JOIN projects p ON t.project_id = p.id\n WHERE t.id = $1\n LIMIT 1\n ", + "query": "\n SELECT\n t.id,\n t.data as \"data: serde_json::Value\",\n t.status,\n t.description,\n t.priority,\n t.entry_at,\n t.modified_at,\n t.due_at,\n t.scheduled_at,\n t.start_at,\n t.end_at,\n t.wait_at,\n t.parent_id,\n p.name as \"project_name?\",\n t.project_id,\n t.note_id\n FROM tc_tasks t\n LEFT JOIN projects p ON t.project_id = p.id\n WHERE t.id = $1\n LIMIT 1\n ", "describe": { "columns": [ { @@ -10,81 +10,76 @@ }, { "ordinal": 1, - "name": "short_id", - "type_info": "Int4" - }, - { - "ordinal": 2, "name": "data: serde_json::Value", "type_info": "Jsonb" }, { - "ordinal": 3, + "ordinal": 2, "name": "status", "type_info": "Text" }, { - "ordinal": 4, + "ordinal": 3, "name": "description", "type_info": "Text" }, { - "ordinal": 5, + "ordinal": 4, "name": "priority", "type_info": "Text" }, { - "ordinal": 6, + "ordinal": 5, "name": "entry_at", "type_info": "Timestamptz" }, { - "ordinal": 7, + "ordinal": 6, "name": "modified_at", "type_info": "Timestamptz" }, { - "ordinal": 8, + "ordinal": 7, "name": "due_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 8, "name": "scheduled_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 9, "name": "start_at", "type_info": "Timestamptz" }, { - "ordinal": 11, + "ordinal": 10, "name": "end_at", "type_info": "Timestamptz" }, { - "ordinal": 12, + "ordinal": 11, "name": "wait_at", "type_info": "Timestamptz" }, { - "ordinal": 13, + "ordinal": 12, "name": "parent_id", "type_info": "Uuid" }, { - "ordinal": 14, + "ordinal": 13, "name": "project_name?", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 14, "name": "project_id", "type_info": "Uuid" }, { - "ordinal": 16, + "ordinal": 15, "name": "note_id", "type_info": "Uuid" } @@ -95,7 +90,6 @@ ] }, "nullable": [ - false, false, false, true, @@ -114,5 +108,5 @@ true ] }, - "hash": "363e9e0aa3cbfac7e1bf277bfafd93e2a39b116cb12d476893f7a2c92fc59cee" + "hash": "6d791f260498f5a6d742ebee8f6e16c82acf52885045a166b8305e123b9bfe9e" } diff --git a/.sqlx/query-97840db2fb51d1923d7d56d018a4ca7cfe57f8cb48730f477ab564f02844d0b2.json b/.sqlx/query-97840db2fb51d1923d7d56d018a4ca7cfe57f8cb48730f477ab564f02844d0b2.json deleted file mode 100644 index 49be7b5c3..000000000 --- a/.sqlx/query-97840db2fb51d1923d7d56d018a4ca7cfe57f8cb48730f477ab564f02844d0b2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id FROM tc_tasks WHERE short_id = $1 LIMIT 1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [ - false - ] - }, - "hash": "97840db2fb51d1923d7d56d018a4ca7cfe57f8cb48730f477ab564f02844d0b2" -} diff --git a/AGENTS.md b/AGENTS.md index ebf3bc29d..d146098f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ cargo test # run a single test by name # Linting cargo fmt --all -- --check # format check -cargo clippy --features storage-powersync --no-deps -- -D warnings +cargo clippy --features storage-powersync,bundled --no-deps -- -D warnings # Docs cargo doc --release --open -p taskchampion @@ -24,18 +24,6 @@ cargo doc --release --open -p taskchampion MSRV: **1.91.1** (update with `cargo xtask msrv `) -## SQLx Metadata - -The pgwire backend uses SQLx compile-time query macros. After changing any -`sqlx::query!`, `query_as!`, or `query_scalar!` call, run -`./scripts/sqlx-prepare.sh` and commit the `.sqlx/` changes. - -The prepare database must have the matching FlickNote backend migrations -applied first. For short task IDs, this means the backend -`add_user_short_ids` migration must exist locally before regenerating metadata. -Treat missing-column prepare failures as stale schema, not as a reason to -hand-edit `.sqlx/`. - ## Architecture ### Core Abstractions diff --git a/Package.swift b/Package.swift index fdc6fb02b..d07354255 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,6 @@ let package = Package( name: "TaskChampionFFI", platforms: [ .iOS(.v14), - .macCatalyst(.v14), .macOS(.v14), ], products: [ @@ -19,17 +18,18 @@ let package = Package( // Generated Swift bindings that call into the C FFI layer .target( name: "TaskChampionFFI", - dependencies: ["TaskChampionFFIFFI"], + dependencies: ["TaskChampionCore"], path: "Sources/TaskChampionFFI" ), - // Pre-built static library + C headers. + // Pre-built framework + C headers. Tags containing "dynamic" use dynamic linkage; + // other release tags use static linkage. // Downloaded from GitHub Releases. The release workflow auto-updates url + checksum. // NOTE: Placeholder values below — auto-replaced by release workflow on each tag push. // Do NOT use the main branch as an SPM dependency; always pin to a tagged version. .binaryTarget( - name: "TaskChampionFFIFFI", - url: "https://github.com/GuionAI/taskchampion/releases/download/v3.0.2-guion.58/TaskChampionFFIFFI.xcframework.zip", - checksum: "817051ce2cf193a58c7431193f7d8b9a9f077af9f08297903bdc610ab6acb100" + name: "TaskChampionCore", + url: "https://github.com/GuionAI/taskchampion/releases/download/v3.0.2-guion.56-dynamic/TaskChampionCore.xcframework.zip", + checksum: "00ac3dfe70a706d7df44abc249aa08e42e24b11dec630dca54f6581d4a2d3279" ), ] ) diff --git a/README.md b/README.md index 933a08eb2..720e7ea58 100644 --- a/README.md +++ b/README.md @@ -35,25 +35,49 @@ run this against a local backend schema and commit the updated `.sqlx/` files: Set `SQLX_POSTGRES_DATABASE_URL` if your local database is not available at the script default URL. -The prepare database must have the matching FlickNote backend migrations -applied before regenerating metadata. For short task IDs, that includes the -backend `add_user_short_ids` migration; missing-column prepare failures mean -the local schema is stale, not that `.sqlx/` should be edited by hand. - ## iOS & macOS (Swift Package Manager) The `ffi/` crate provides a UniFFI-based FFI layer for iOS and macOS consumption via SPM. +### Migration note + +The public SwiftPM product remains `TaskChampionFFI`, so app code should still +use `import TaskChampionFFI`. The low-level binary target and release asset were +renamed from the legacy doubled-FFI name (`TaskChampionFFIFFI`) to +`TaskChampionCore`. Consumers that only add the `TaskChampionFFI` product do not +need code changes; consumers that referenced the binary target or release zip +directly should update those references to `TaskChampionCore.xcframework.zip`. + ### Building ```bash -# Install Rust iOS targets + build XCFramework + generate Swift bindings -./scripts/build_xcframework.sh +# macOS only: install cargo-swift once +cargo install cargo-swift@0.11.1 --locked + +# Build the default static Swift package and TaskChampionCore XCFramework +./scripts/package_cargo_swift.sh + +# Dynamic tags/releases are built with the same package shape: +TASKCHAMPION_FFI_LINKAGE=dynamic ./scripts/package_cargo_swift.sh target/cargo-swift-dynamic ``` This produces: -- `TaskChampionFFIFFI.xcframework/` — static library for iOS device (arm64), iOS simulator (arm64), and macOS (arm64) -- `Sources/TaskChampionFFI/TaskChampionFFI.swift` — generated Swift bindings +- `target/cargo-swift/TaskChampionFFI/TaskChampionCore.xcframework/` — default static framework for iOS device, iOS simulator, and macOS +- `target/cargo-swift-dynamic/TaskChampionFFI/TaskChampionCore.xcframework/` — dynamic framework when requested +- `target/cargo-swift/TaskChampionFFI/Sources/TaskChampionFFI/taskchampion_ffi.swift` — generated Swift bindings + +For local Xcode testing, point `Package.swift` at the built XCFramework instead +of the release zip: + +```bash +./scripts/use_local_xcframework.sh target/cargo-swift/TaskChampionFFI/TaskChampionCore.xcframework +``` + +Restore the release URL before committing release changes: + +```bash +git restore Package.swift +``` ### Consuming from an iOS or macOS Project @@ -64,7 +88,10 @@ This produces: 2. Run the build script: ```bash - cd vendor/taskchampion && ./scripts/build_xcframework.sh + cd vendor/taskchampion + cargo install cargo-swift@0.11.1 --locked + ./scripts/package_cargo_swift.sh + ./scripts/use_local_xcframework.sh target/cargo-swift/TaskChampionFFI/TaskChampionCore.xcframework ``` 3. In Xcode: **Add Local Package** → select `vendor/taskchampion/` → add `TaskChampionFFI` to your target. diff --git a/Sources/TaskChampionFFI/TaskChampionFFI.swift b/Sources/TaskChampionFFI/TaskChampionFFI.swift index 73f70411b..d0261bc35 100644 --- a/Sources/TaskChampionFFI/TaskChampionFFI.swift +++ b/Sources/TaskChampionFFI/TaskChampionFFI.swift @@ -7,8 +7,8 @@ import Foundation // Depending on the consumer's build setup, the low-level FFI code // might be in a separate module, or it might be compiled inline into // this module. This is a bit of light hackery to work with both. -#if canImport(TaskChampionFFIFFI) -import TaskChampionFFIFFI +#if canImport(TaskChampionCore) +import TaskChampionCore #endif fileprivate extension RustBuffer { @@ -606,7 +606,7 @@ public protocol FfiSessionProtocol: AnyObject, Sendable { func dependencyMap() async throws -> [FfiDependencyEdge] /** - * Fetch a single task by UUID or short ID. + * Fetch a single task by UUID. * * Returns `None` if the task does not exist. */ @@ -1011,7 +1011,7 @@ open func dependencyMap()async throws -> [FfiDependencyEdge] { } /** - * Fetch a single task by UUID or short ID. + * Fetch a single task by UUID. * * Returns `None` if the task does not exist. */ @@ -1676,9 +1676,8 @@ fileprivate struct UniffiCallbackInterfaceFfiSqlExecutor { // Create the VTable using a series of closures. // Swift automatically converts these into C callback functions. // - // This creates 1-element array, since this seems to be the only way to construct a const - // pointer that we can pass to the Rust code. - static let vtable: [UniffiVTableCallbackInterfaceFfiSqlExecutor] = [UniffiVTableCallbackInterfaceFfiSqlExecutor( + // Store the vtable directly. + static let vtable: UniffiVTableCallbackInterfaceFfiSqlExecutor = UniffiVTableCallbackInterfaceFfiSqlExecutor( uniffiFree: { (uniffiHandle: UInt64) -> () in do { try FfiConverterTypeFfiSqlExecutor.handleMap.remove(handle: uniffiHandle) @@ -1824,11 +1823,19 @@ fileprivate struct UniffiCallbackInterfaceFfiSqlExecutor { droppedCallback: uniffiOutDroppedCallback ) } - )] + ) + + // Rust stores this pointer for future callback invocations, so it must live + // for the process lifetime (not just for the init function call). + static let vtablePtr: UnsafePointer = { + let ptr = UnsafeMutablePointer.allocate(capacity: 1) + ptr.initialize(to: vtable) + return UnsafePointer(ptr) + }() } private func uniffiCallbackInitFfiSqlExecutor() { - uniffi_taskchampion_ffi_fn_init_callback_vtable_ffisqlexecutor(UniffiCallbackInterfaceFfiSqlExecutor.vtable) + uniffi_taskchampion_ffi_fn_init_callback_vtable_ffisqlexecutor(UniffiCallbackInterfaceFfiSqlExecutor.vtablePtr) } #if swift(>=5.8) @@ -2489,10 +2496,6 @@ public func FfiConverterTypeFfiSqlStatement_lower(_ value: FfiSqlStatement) -> R */ public struct FfiTask: Equatable, Hashable { public var uuid: String - /** - * Per-user short ID assigned by the backing database. - */ - public var shortId: Int64? public var status: FfiStatus public var description: String /** @@ -2598,13 +2601,10 @@ public struct FfiTask: Equatable, Hashable { // Default memberwise initializers are never public by default, so we // declare one manually. - public init(uuid: String, - /** - * Per-user short ID assigned by the backing database. - */shortId: Int64?, status: FfiStatus, description: String, + public init(uuid: String, status: FfiStatus, description: String, /** * Priority string (e.g. `"H"`, `"M"`, `"L"`), or `None` if unset. - */priority: String?, + */priority: String?, /** * Unix epoch seconds, or `None` if not set. */entry: Int64?, modified: Int64?, due: Int64?, @@ -2677,7 +2677,6 @@ public struct FfiTask: Equatable, Hashable { * Stored as `note_id` column in `tc_tasks`. */noteId: String?) { self.uuid = uuid - self.shortId = shortId self.status = status self.description = description self.priority = priority @@ -2725,7 +2724,6 @@ public struct FfiConverterTypeFfiTask: FfiConverterRustBuffer { return try FfiTask( uuid: FfiConverterString.read(from: &buf), - shortId: FfiConverterOptionInt64.read(from: &buf), status: FfiConverterTypeFfiStatus.read(from: &buf), description: FfiConverterString.read(from: &buf), priority: FfiConverterOptionString.read(from: &buf), @@ -2759,7 +2757,6 @@ public struct FfiConverterTypeFfiTask: FfiConverterRustBuffer { public static func write(_ value: FfiTask, into buf: inout [UInt8]) { FfiConverterString.write(value.uuid, into: &buf) - FfiConverterOptionInt64.write(value.shortId, into: &buf) FfiConverterTypeFfiStatus.write(value.status, into: &buf) FfiConverterString.write(value.description, into: &buf) FfiConverterOptionString.write(value.priority, into: &buf) @@ -3987,11 +3984,6 @@ public enum TaskMutation: Equatable, Hashable { ) case setEntry(epoch: Int64? ) - /** - * Set the parent task by UUID. Short IDs are user-facing handles; callers - * that accept short IDs should resolve them to UUIDs before building this - * mutation. `None` clears the parent. - */ case setParent(uuid: String? ) case setPosition(value: String? @@ -4004,16 +3996,8 @@ public enum TaskMutation: Equatable, Hashable { ) case removeAnnotation(entry: Int64 ) - /** - * Add a dependency by UUID. Resolve short IDs at the UI/input layer before - * constructing this mutation. - */ case addDependency(uuid: String ) - /** - * Remove a dependency by UUID. Resolve short IDs at the UI/input layer - * before constructing this mutation. - */ case removeDependency(uuid: String ) /** @@ -5200,7 +5184,7 @@ private let initializationResult: InitializationResult = { if (uniffi_taskchampion_ffi_checksum_method_ffisession_dependency_map() != 18621) { return InitializationResult.apiChecksumMismatch } - if (uniffi_taskchampion_ffi_checksum_method_ffisession_get_task() != 31606) { + if (uniffi_taskchampion_ffi_checksum_method_ffisession_get_task() != 6917) { return InitializationResult.apiChecksumMismatch } if (uniffi_taskchampion_ffi_checksum_method_ffisession_is_ancestor() != 58227) { @@ -5287,4 +5271,4 @@ public func uniffiEnsureTaskchampionFfiInitialized() { } } -// swiftlint:enable all \ No newline at end of file +// swiftlint:enable all diff --git a/Sources/TaskChampionFFI/TaskChampionFFIFFI.h b/Sources/TaskChampionFFI/TaskChampionFFIFFI.h deleted file mode 100644 index aa6e9e72f..000000000 --- a/Sources/TaskChampionFFI/TaskChampionFFIFFI.h +++ /dev/null @@ -1,755 +0,0 @@ -// This file was autogenerated by some hot garbage in the `uniffi` crate. -// Trust me, you don't want to mess with it! - -#pragma once - -#include -#include -#include - -// The following structs are used to implement the lowest level -// of the FFI, and thus useful to multiple uniffied crates. -// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. -#ifdef UNIFFI_SHARED_H - // We also try to prevent mixing versions of shared uniffi header structs. - // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 - #ifndef UNIFFI_SHARED_HEADER_V4 - #error Combining helper code from multiple versions of uniffi is not supported - #endif // ndef UNIFFI_SHARED_HEADER_V4 -#else -#define UNIFFI_SHARED_H -#define UNIFFI_SHARED_HEADER_V4 -// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ -// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ - -typedef struct RustBuffer -{ - uint64_t capacity; - uint64_t len; - uint8_t *_Nullable data; -} RustBuffer; - -typedef struct ForeignBytes -{ - int32_t len; - const uint8_t *_Nullable data; -} ForeignBytes; - -// Error definitions -typedef struct RustCallStatus { - int8_t code; - RustBuffer errorBuf; -} RustCallStatus; - -// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ -// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ -#endif // def UNIFFI_SHARED_H -#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK -#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK -typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK -typedef void (*UniffiForeignFutureDroppedCallback)(uint64_t - ); - -#endif -#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE -#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE -typedef void (*UniffiCallbackInterfaceFree)(uint64_t - ); - -#endif -#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE -#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_CLONE -typedef uint64_t (*UniffiCallbackInterfaceClone)(uint64_t - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_DROPPED_CALLBACK_STRUCT -typedef struct UniffiForeignFutureDroppedCallbackStruct { - uint64_t handle; - UniffiForeignFutureDroppedCallback _Nonnull free; -} UniffiForeignFutureDroppedCallbackStruct; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U8 -typedef struct UniffiForeignFutureResultU8 { - uint8_t returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultU8; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 -typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureResultU8 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I8 -typedef struct UniffiForeignFutureResultI8 { - int8_t returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultI8; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 -typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureResultI8 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U16 -typedef struct UniffiForeignFutureResultU16 { - uint16_t returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultU16; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 -typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureResultU16 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I16 -typedef struct UniffiForeignFutureResultI16 { - int16_t returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultI16; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 -typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureResultI16 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U32 -typedef struct UniffiForeignFutureResultU32 { - uint32_t returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultU32; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 -typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureResultU32 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I32 -typedef struct UniffiForeignFutureResultI32 { - int32_t returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultI32; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 -typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureResultI32 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_U64 -typedef struct UniffiForeignFutureResultU64 { - uint64_t returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultU64; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 -typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureResultU64 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_I64 -typedef struct UniffiForeignFutureResultI64 { - int64_t returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultI64; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 -typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureResultI64 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F32 -typedef struct UniffiForeignFutureResultF32 { - float returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultF32; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 -typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureResultF32 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_F64 -typedef struct UniffiForeignFutureResultF64 { - double returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultF64; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 -typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureResultF64 - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_RUST_BUFFER -typedef struct UniffiForeignFutureResultRustBuffer { - RustBuffer returnValue; - RustCallStatus callStatus; -} UniffiForeignFutureResultRustBuffer; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER -typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureResultRustBuffer - ); - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_RESULT_VOID -typedef struct UniffiForeignFutureResultVoid { - RustCallStatus callStatus; -} UniffiForeignFutureResultVoid; - -#endif -#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID -#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID -typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureResultVoid - ); - -#endif -#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FFI_SQL_EXECUTOR_METHOD0 -#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FFI_SQL_EXECUTOR_METHOD0 -typedef void (*UniffiCallbackInterfaceFfiSqlExecutorMethod0)(uint64_t, RustBuffer, RustBuffer, UniffiForeignFutureCompleteRustBuffer _Nonnull, uint64_t, UniffiForeignFutureDroppedCallbackStruct* _Nonnull - ); - -#endif -#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FFI_SQL_EXECUTOR_METHOD1 -#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FFI_SQL_EXECUTOR_METHOD1 -typedef void (*UniffiCallbackInterfaceFfiSqlExecutorMethod1)(uint64_t, RustBuffer, RustBuffer, UniffiForeignFutureCompleteRustBuffer _Nonnull, uint64_t, UniffiForeignFutureDroppedCallbackStruct* _Nonnull - ); - -#endif -#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FFI_SQL_EXECUTOR_METHOD2 -#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FFI_SQL_EXECUTOR_METHOD2 -typedef void (*UniffiCallbackInterfaceFfiSqlExecutorMethod2)(uint64_t, RustBuffer, UniffiForeignFutureCompleteVoid _Nonnull, uint64_t, UniffiForeignFutureDroppedCallbackStruct* _Nonnull - ); - -#endif -#ifndef UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_FFI_SQL_EXECUTOR -#define UNIFFI_FFIDEF_V_TABLE_CALLBACK_INTERFACE_FFI_SQL_EXECUTOR -typedef struct UniffiVTableCallbackInterfaceFfiSqlExecutor { - UniffiCallbackInterfaceFree _Nonnull uniffiFree; - UniffiCallbackInterfaceClone _Nonnull uniffiClone; - UniffiCallbackInterfaceFfiSqlExecutorMethod0 _Nonnull queryOne; - UniffiCallbackInterfaceFfiSqlExecutorMethod1 _Nonnull queryAll; - UniffiCallbackInterfaceFfiSqlExecutorMethod2 _Nonnull executeBatch; -} UniffiVTableCallbackInterfaceFfiSqlExecutor; - -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_CLONE_FFISESSION -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_CLONE_FFISESSION -uint64_t uniffi_taskchampion_ffi_fn_clone_ffisession(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_FREE_FFISESSION -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_FREE_FFISESSION -void uniffi_taskchampion_ffi_fn_free_ffisession(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_CONSTRUCTOR_FFISESSION_NEW -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_CONSTRUCTOR_FFISESSION_NEW -uint64_t uniffi_taskchampion_ffi_fn_constructor_ffisession_new(uint64_t executor, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_ALL_TASKS -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_ALL_TASKS -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_all_tasks(uint64_t ptr -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_CREATE_TASK -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_CREATE_TASK -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_create_task(uint64_t ptr, RustBuffer uuid, RustBuffer description -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_DEPENDENCY_MAP -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_DEPENDENCY_MAP -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_dependency_map(uint64_t ptr -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_GET_ALL_TAGS -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_GET_ALL_TAGS -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_get_all_tags(uint64_t ptr -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_GET_TAG_COLOR -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_GET_TAG_COLOR -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_get_tag_color(uint64_t ptr, RustBuffer name -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_GET_TASK -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_GET_TASK -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_get_task(uint64_t ptr, RustBuffer uuid -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_PENDING_TASKS -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_PENDING_TASKS -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_pending_tasks(uint64_t ptr -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_SET_TAG_COLOR -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_SET_TAG_COLOR -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_set_tag_color(uint64_t ptr, RustBuffer name, RustBuffer color -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_TREE_MAP -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_TREE_MAP -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_tree_map(uint64_t ptr -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_UNDO -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_UNDO -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_undo(uint64_t ptr -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_MUTATE_TASK -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISESSION_MUTATE_TASK -uint64_t uniffi_taskchampion_ffi_fn_method_ffisession_mutate_task(uint64_t ptr, RustBuffer uuid, RustBuffer mutations -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_CLONE_FFISQLEXECUTOR -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_CLONE_FFISQLEXECUTOR -uint64_t uniffi_taskchampion_ffi_fn_clone_ffisqlexecutor(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_FREE_FFISQLEXECUTOR -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_FREE_FFISQLEXECUTOR -void uniffi_taskchampion_ffi_fn_free_ffisqlexecutor(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_INIT_CALLBACK_VTABLE_FFISQLEXECUTOR -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_INIT_CALLBACK_VTABLE_FFISQLEXECUTOR -void uniffi_taskchampion_ffi_fn_init_callback_vtable_ffisqlexecutor(const UniffiVTableCallbackInterfaceFfiSqlExecutor* _Nonnull vtable -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISQLEXECUTOR_QUERY_ONE -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISQLEXECUTOR_QUERY_ONE -uint64_t uniffi_taskchampion_ffi_fn_method_ffisqlexecutor_query_one(uint64_t ptr, RustBuffer sql, RustBuffer params -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISQLEXECUTOR_QUERY_ALL -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISQLEXECUTOR_QUERY_ALL -uint64_t uniffi_taskchampion_ffi_fn_method_ffisqlexecutor_query_all(uint64_t ptr, RustBuffer sql, RustBuffer params -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISQLEXECUTOR_EXECUTE_BATCH -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_METHOD_FFISQLEXECUTOR_EXECUTE_BATCH -uint64_t uniffi_taskchampion_ffi_fn_method_ffisqlexecutor_execute_batch(uint64_t ptr, RustBuffer statements -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_FUNC_ALLTASKTABLESSQL -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_FUNC_ALLTASKTABLESSQL -RustBuffer uniffi_taskchampion_ffi_fn_func_alltasktablessql(RustCallStatus *_Nonnull out_status - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_FUNC_TAGCOLORTABLESSQL -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_FN_FUNC_TAGCOLORTABLESSQL -RustBuffer uniffi_taskchampion_ffi_fn_func_tagcolortablessql(RustCallStatus *_Nonnull out_status - -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUSTBUFFER_ALLOC -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUSTBUFFER_ALLOC -RustBuffer ffi_taskchampion_ffi_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUSTBUFFER_FROM_BYTES -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUSTBUFFER_FROM_BYTES -RustBuffer ffi_taskchampion_ffi_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUSTBUFFER_FREE -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUSTBUFFER_FREE -void ffi_taskchampion_ffi_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUSTBUFFER_RESERVE -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUSTBUFFER_RESERVE -RustBuffer ffi_taskchampion_ffi_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_U8 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_U8 -void ffi_taskchampion_ffi_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_U8 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_U8 -void ffi_taskchampion_ffi_rust_future_cancel_u8(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_U8 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_U8 -void ffi_taskchampion_ffi_rust_future_free_u8(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_U8 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_U8 -uint8_t ffi_taskchampion_ffi_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_I8 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_I8 -void ffi_taskchampion_ffi_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_I8 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_I8 -void ffi_taskchampion_ffi_rust_future_cancel_i8(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_I8 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_I8 -void ffi_taskchampion_ffi_rust_future_free_i8(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_I8 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_I8 -int8_t ffi_taskchampion_ffi_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_U16 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_U16 -void ffi_taskchampion_ffi_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_U16 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_U16 -void ffi_taskchampion_ffi_rust_future_cancel_u16(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_U16 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_U16 -void ffi_taskchampion_ffi_rust_future_free_u16(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_U16 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_U16 -uint16_t ffi_taskchampion_ffi_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_I16 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_I16 -void ffi_taskchampion_ffi_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_I16 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_I16 -void ffi_taskchampion_ffi_rust_future_cancel_i16(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_I16 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_I16 -void ffi_taskchampion_ffi_rust_future_free_i16(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_I16 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_I16 -int16_t ffi_taskchampion_ffi_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_U32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_U32 -void ffi_taskchampion_ffi_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_U32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_U32 -void ffi_taskchampion_ffi_rust_future_cancel_u32(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_U32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_U32 -void ffi_taskchampion_ffi_rust_future_free_u32(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_U32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_U32 -uint32_t ffi_taskchampion_ffi_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_I32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_I32 -void ffi_taskchampion_ffi_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_I32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_I32 -void ffi_taskchampion_ffi_rust_future_cancel_i32(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_I32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_I32 -void ffi_taskchampion_ffi_rust_future_free_i32(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_I32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_I32 -int32_t ffi_taskchampion_ffi_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_U64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_U64 -void ffi_taskchampion_ffi_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_U64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_U64 -void ffi_taskchampion_ffi_rust_future_cancel_u64(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_U64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_U64 -void ffi_taskchampion_ffi_rust_future_free_u64(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_U64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_U64 -uint64_t ffi_taskchampion_ffi_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_I64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_I64 -void ffi_taskchampion_ffi_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_I64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_I64 -void ffi_taskchampion_ffi_rust_future_cancel_i64(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_I64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_I64 -void ffi_taskchampion_ffi_rust_future_free_i64(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_I64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_I64 -int64_t ffi_taskchampion_ffi_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_F32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_F32 -void ffi_taskchampion_ffi_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_F32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_F32 -void ffi_taskchampion_ffi_rust_future_cancel_f32(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_F32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_F32 -void ffi_taskchampion_ffi_rust_future_free_f32(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_F32 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_F32 -float ffi_taskchampion_ffi_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_F64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_F64 -void ffi_taskchampion_ffi_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_F64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_F64 -void ffi_taskchampion_ffi_rust_future_cancel_f64(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_F64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_F64 -void ffi_taskchampion_ffi_rust_future_free_f64(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_F64 -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_F64 -double ffi_taskchampion_ffi_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_RUST_BUFFER -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_RUST_BUFFER -void ffi_taskchampion_ffi_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_RUST_BUFFER -void ffi_taskchampion_ffi_rust_future_cancel_rust_buffer(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_RUST_BUFFER -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_RUST_BUFFER -void ffi_taskchampion_ffi_rust_future_free_rust_buffer(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_RUST_BUFFER -RustBuffer ffi_taskchampion_ffi_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_VOID -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_POLL_VOID -void ffi_taskchampion_ffi_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_VOID -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_CANCEL_VOID -void ffi_taskchampion_ffi_rust_future_cancel_void(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_VOID -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_FREE_VOID -void ffi_taskchampion_ffi_rust_future_free_void(uint64_t handle -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_VOID -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_RUST_FUTURE_COMPLETE_VOID -void ffi_taskchampion_ffi_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_FUNC_ALLTASKTABLESSQL -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_FUNC_ALLTASKTABLESSQL -uint16_t uniffi_taskchampion_ffi_checksum_func_alltasktablessql(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_FUNC_TAGCOLORTABLESSQL -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_FUNC_TAGCOLORTABLESSQL -uint16_t uniffi_taskchampion_ffi_checksum_func_tagcolortablessql(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_ALL_TASKS -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_ALL_TASKS -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_all_tasks(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_CREATE_TASK -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_CREATE_TASK -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_create_task(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_DEPENDENCY_MAP -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_DEPENDENCY_MAP -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_dependency_map(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_GET_ALL_TAGS -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_GET_ALL_TAGS -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_get_all_tags(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_GET_TAG_COLOR -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_GET_TAG_COLOR -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_get_tag_color(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_GET_TASK -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_GET_TASK -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_get_task(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_PENDING_TASKS -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_PENDING_TASKS -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_pending_tasks(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_SET_TAG_COLOR -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_SET_TAG_COLOR -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_set_tag_color(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_TREE_MAP -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_TREE_MAP -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_tree_map(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_UNDO -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_UNDO -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_undo(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_MUTATE_TASK -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISESSION_MUTATE_TASK -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisession_mutate_task(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISQLEXECUTOR_QUERY_ONE -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISQLEXECUTOR_QUERY_ONE -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisqlexecutor_query_one(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISQLEXECUTOR_QUERY_ALL -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISQLEXECUTOR_QUERY_ALL -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisqlexecutor_query_all(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISQLEXECUTOR_EXECUTE_BATCH -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_METHOD_FFISQLEXECUTOR_EXECUTE_BATCH -uint16_t uniffi_taskchampion_ffi_checksum_method_ffisqlexecutor_execute_batch(void - -); -#endif -#ifndef UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_CONSTRUCTOR_FFISESSION_NEW -#define UNIFFI_FFIDEF_UNIFFI_TASKCHAMPION_FFI_CHECKSUM_CONSTRUCTOR_FFISESSION_NEW -uint16_t uniffi_taskchampion_ffi_checksum_constructor_ffisession_new(void - -); -#endif -#ifndef UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_UNIFFI_CONTRACT_VERSION -#define UNIFFI_FFIDEF_FFI_TASKCHAMPION_FFI_UNIFFI_CONTRACT_VERSION -uint32_t ffi_taskchampion_ffi_uniffi_contract_version(void - -); -#endif - diff --git a/Sources/TaskChampionFFI/TaskChampionFFIFFI.modulemap b/Sources/TaskChampionFFI/TaskChampionFFIFFI.modulemap deleted file mode 100644 index b4210982b..000000000 --- a/Sources/TaskChampionFFI/TaskChampionFFIFFI.modulemap +++ /dev/null @@ -1,7 +0,0 @@ -module TaskChampionFFIFFI { - header "TaskChampionFFIFFI.h" - export * - use "Darwin" - use "_Builtin_stdbool" - use "_Builtin_stdint" -} \ No newline at end of file diff --git a/ffi/src/convert.rs b/ffi/src/convert.rs index 04e72b982..f92cae243 100644 --- a/ffi/src/convert.rs +++ b/ffi/src/convert.rs @@ -14,7 +14,6 @@ impl From<&Task> for FfiTask { fn from(task: &Task) -> Self { FfiTask { uuid: task.get_uuid().to_string(), - short_id: task.get_short_id(), status: FfiStatus::from(task.get_status()), description: task.get_description().to_string(), priority: Some(task.get_priority()) diff --git a/ffi/src/replica_ops.rs b/ffi/src/replica_ops.rs index 66a265005..ec8155343 100644 --- a/ffi/src/replica_ops.rs +++ b/ffi/src/replica_ops.rs @@ -152,18 +152,12 @@ impl FfiSession { .await } - /// Fetch a single task by UUID or short ID. + /// Fetch a single task by UUID. /// /// Returns `None` if the task does not exist. pub async fn get_task(&self, uuid: String) -> Result, FfiError> { self.with_replica(|mut replica| async move { - let Some(task_uuid) = replica - .resolve_task_ref(&uuid) - .await - .map_err(FfiError::from)? - else { - return Ok(None); - }; + let task_uuid = parse_uuid(&uuid)?; let task = replica.get_task(task_uuid).await.map_err(FfiError::from)?; Ok(task.as_ref().map(FfiTask::from)) }) @@ -414,7 +408,7 @@ impl FfiSession { /// Returns `UnknownXStatus` if `name` is not in tc_config.xstatus definitions. pub async fn set_xstatus(&self, task_uuid: String, name: String) -> Result { self.with_replica(|mut replica| async move { - let uuid = resolve_existing_task_ref(&mut replica, &task_uuid).await?; + let uuid = parse_uuid(&task_uuid)?; let config = load_tc_config(&mut replica).await?; if !config.has_xstatus(&name) { return Err(FfiError::UnknownXStatus { name }); @@ -429,7 +423,7 @@ impl FfiSession { /// Returns the task unchanged (no undo point) if xstatus is already `None`. pub async fn clear_xstatus(&self, task_uuid: String) -> Result { self.with_replica(|mut replica| async move { - let uuid = resolve_existing_task_ref(&mut replica, &task_uuid).await?; + let uuid = parse_uuid(&task_uuid)?; write_xstatus(&mut replica, uuid, &task_uuid, None).await }) .await @@ -549,8 +543,8 @@ impl FfiSession { anchor_uuid: String, ) -> Result { self.with_replica(|mut replica| async move { - let uuid_parsed = resolve_existing_task_ref(&mut replica, &uuid).await?; - let anchor_parsed = resolve_existing_task_ref(&mut replica, &anchor_uuid).await?; + let uuid_parsed = parse_uuid(&uuid)?; + let anchor_parsed = parse_uuid(&anchor_uuid)?; // Load both tasks to verify existence and parent. let task = replica @@ -595,8 +589,8 @@ impl FfiSession { anchor_uuid: String, ) -> Result { self.with_replica(|mut replica| async move { - let uuid_parsed = resolve_existing_task_ref(&mut replica, &uuid).await?; - let anchor_parsed = resolve_existing_task_ref(&mut replica, &anchor_uuid).await?; + let uuid_parsed = parse_uuid(&uuid)?; + let anchor_parsed = parse_uuid(&anchor_uuid)?; // Load both tasks to verify existence and parent. let task = replica @@ -646,7 +640,7 @@ async fn reorder_to_edge( where F: FnOnce(&[(uuid::Uuid, String)]) -> Result, { - let uuid_parsed = resolve_existing_task_ref(replica, uuid_str).await?; + let uuid_parsed = parse_uuid(uuid_str)?; let task = replica .get_task(uuid_parsed) .await @@ -763,8 +757,8 @@ impl FfiSession { anchor_uuid: String, ) -> Result { self.with_replica(|mut replica| async move { - let uuid_parsed = resolve_existing_task_ref(&mut replica, &uuid).await?; - let anchor_parsed = resolve_existing_task_ref(&mut replica, &anchor_uuid).await?; + let uuid_parsed = parse_uuid(&uuid)?; + let anchor_parsed = parse_uuid(&anchor_uuid)?; // Verify both tasks exist. replica @@ -800,8 +794,8 @@ impl FfiSession { anchor_uuid: String, ) -> Result { self.with_replica(|mut replica| async move { - let uuid_parsed = resolve_existing_task_ref(&mut replica, &uuid).await?; - let anchor_parsed = resolve_existing_task_ref(&mut replica, &anchor_uuid).await?; + let uuid_parsed = parse_uuid(&uuid)?; + let anchor_parsed = parse_uuid(&anchor_uuid)?; // Verify both tasks exist. replica @@ -831,7 +825,12 @@ impl FfiSession { /// Returns `TaskNotFound` if the UUID does not exist. pub async fn today_reorder_to_beginning(&self, uuid: String) -> Result { self.with_replica(|mut replica| async move { - let uuid_parsed = resolve_existing_task_ref(&mut replica, &uuid).await?; + let uuid_parsed = parse_uuid(&uuid)?; + replica + .get_task(uuid_parsed) + .await + .map_err(FfiError::from)? + .ok_or_else(|| FfiError::TaskNotFound { uuid: uuid.clone() })?; let all = replica.all_tasks().await.map_err(FfiError::from)?; let today = sorted_today_positions(&all, uuid_parsed); @@ -849,7 +848,12 @@ impl FfiSession { /// Returns `TaskNotFound` if the UUID does not exist. pub async fn today_reorder_to_end(&self, uuid: String) -> Result { self.with_replica(|mut replica| async move { - let uuid_parsed = resolve_existing_task_ref(&mut replica, &uuid).await?; + let uuid_parsed = parse_uuid(&uuid)?; + replica + .get_task(uuid_parsed) + .await + .map_err(FfiError::from)? + .ok_or_else(|| FfiError::TaskNotFound { uuid: uuid.clone() })?; let all = replica.all_tasks().await.map_err(FfiError::from)?; let today = sorted_today_positions(&all, uuid_parsed); @@ -886,11 +890,11 @@ impl FfiSession { position: ReparentPosition, ) -> Result { self.with_replica(|mut replica| async move { - let uuid_parsed = resolve_existing_task_ref(&mut replica, &uuid).await?; - let new_parent_parsed: Option = match new_parent.as_deref() { - Some(parent) => Some(resolve_existing_task_ref(&mut replica, parent).await?), - None => None, - }; + let uuid_parsed = parse_uuid_ctx(&uuid, "uuid")?; + let new_parent_parsed: Option = new_parent + .as_deref() + .map(|s| parse_uuid_ctx(s, "new_parent")) + .transpose()?; // Load uuid task to verify it exists. replica @@ -941,12 +945,12 @@ impl FfiSession { ) } ReparentPosition::After { anchor } => { - let anchor_parsed = resolve_existing_task_ref(&mut replica, anchor).await?; + let anchor_parsed = parse_uuid_ctx(anchor, "anchor")?; let idx = find_anchor_idx(&siblings, anchor_parsed, anchor)?; Some(position_after_anchor(&siblings, idx)?) } ReparentPosition::Before { anchor } => { - let anchor_parsed = resolve_existing_task_ref(&mut replica, anchor).await?; + let anchor_parsed = parse_uuid_ctx(anchor, "anchor")?; let idx = find_anchor_idx(&siblings, anchor_parsed, anchor)?; Some(position_before_anchor(&siblings, idx)?) } @@ -992,22 +996,11 @@ impl FfiSession { /// to call `is_ancestor` for safety. /// /// Returns `false` if either UUID does not exist or is not in the tree. + /// Returns `InvalidInput` if either argument is not a valid UUID string. pub async fn is_ancestor(&self, uuid: String, ancestor_uuid: String) -> Result { self.with_replica(|mut replica| async move { - let Some(uuid_parsed) = replica - .resolve_task_ref(&uuid) - .await - .map_err(FfiError::from)? - else { - return Ok(false); - }; - let Some(ancestor_parsed) = replica - .resolve_task_ref(&ancestor_uuid) - .await - .map_err(FfiError::from)? - else { - return Ok(false); - }; + let uuid_parsed = parse_uuid_ctx(&uuid, "uuid")?; + let ancestor_parsed = parse_uuid_ctx(&ancestor_uuid, "ancestor_uuid")?; let tm = replica.tree_map().await.map_err(FfiError::from)?; Ok(tm.is_ancestor(uuid_parsed, ancestor_parsed)) }) @@ -1019,19 +1012,6 @@ impl FfiSession { // Internal helpers // --------------------------------------------------------------------------- -pub(crate) async fn resolve_existing_task_ref( - replica: &mut Replica, - task_ref: &str, -) -> Result { - replica - .resolve_task_ref(task_ref) - .await - .map_err(FfiError::from)? - .ok_or_else(|| FfiError::TaskNotFound { - uuid: task_ref.to_string(), - }) -} - pub(crate) fn parse_uuid(s: &str) -> Result { Uuid::parse_str(s).map_err(|e| FfiError::InvalidInput { message: format!("Invalid UUID: {e}"), diff --git a/ffi/src/task_ops.rs b/ffi/src/task_ops.rs index 2fb315f3a..d1d2358a4 100644 --- a/ffi/src/task_ops.rs +++ b/ffi/src/task_ops.rs @@ -3,7 +3,7 @@ use chrono::DateTime; use taskchampion::{Annotation, Operation, Operations, Status, Tag}; -use crate::replica_ops::{parse_uuid, resolve_existing_task_ref, FfiSession}; +use crate::replica_ops::{parse_uuid, FfiSession}; use crate::types::{FfiError, FfiTask, TaskMutation}; #[uniffi::export] @@ -21,7 +21,7 @@ impl FfiSession { mutations: Vec, ) -> Result, FfiError> { self.with_replica(|mut replica| async move { - let task_uuid = resolve_existing_task_ref(&mut replica, &uuid).await?; + let task_uuid = parse_uuid(&uuid)?; let mut task = replica .get_task(task_uuid) .await diff --git a/ffi/src/types.rs b/ffi/src/types.rs index 8b45cc3a3..427ff6c3c 100644 --- a/ffi/src/types.rs +++ b/ffi/src/types.rs @@ -30,8 +30,6 @@ pub struct FfiAnnotation { #[derive(uniffi::Record)] pub struct FfiTask { pub uuid: String, - /// Per-user short ID assigned by the backing database. - pub short_id: Option, pub status: FfiStatus, pub description: String, /// Priority string (e.g. `"H"`, `"M"`, `"L"`), or `None` if unset. @@ -145,9 +143,6 @@ pub enum TaskMutation { SetEntry { epoch: Option, }, - /// Set the parent task by UUID. Short IDs are user-facing handles; callers - /// that accept short IDs should resolve them to UUIDs before building this - /// mutation. `None` clears the parent. SetParent { uuid: Option, }, @@ -167,13 +162,9 @@ pub enum TaskMutation { RemoveAnnotation { entry: i64, }, - /// Add a dependency by UUID. Resolve short IDs at the UI/input layer before - /// constructing this mutation. AddDependency { uuid: String, }, - /// Remove a dependency by UUID. Resolve short IDs at the UI/input layer - /// before constructing this mutation. RemoveDependency { uuid: String, }, diff --git a/ffi/tests/round_trip.rs b/ffi/tests/round_trip.rs index 8a214fb7a..e44595f96 100644 --- a/ffi/tests/round_trip.rs +++ b/ffi/tests/round_trip.rs @@ -30,8 +30,7 @@ impl MockFfiSqlExecutor { conn.execute_batch( "CREATE TABLE IF NOT EXISTS tc_tasks ( id TEXT PRIMARY KEY, - -- Populated by the backing sync system; task writes treat it as read-only. - short_id INTEGER, data TEXT NOT NULL DEFAULT '{}', entry_at TEXT, status TEXT, + data TEXT NOT NULL DEFAULT '{}', entry_at TEXT, status TEXT, description TEXT, priority TEXT, modified_at TEXT, due_at TEXT, scheduled_at TEXT, start_at TEXT, end_at TEXT, wait_at TEXT, parent_id TEXT, position TEXT, project_id TEXT, @@ -110,15 +109,6 @@ impl MockFfiSqlExecutor { .expect("read due_at") } - fn assign_short_id(&self, uuid: &str, short_id: i64) { - let conn = self.conn.lock().unwrap(); - conn.execute( - "UPDATE tc_tasks SET short_id = ? WHERE id = ?", - rusqlite::params![short_id, uuid], - ) - .expect("assign short_id"); - } - /// Insert a project into the projects table and return its UUID string. fn inject_project(&self, name: &str) -> String { let conn = self.conn.lock().unwrap(); @@ -253,40 +243,6 @@ async fn test_create_and_read() { assert_eq!(fetched.description, "Hello FFI"); } -#[tokio::test] -async fn test_short_id_read_and_mutate() { - let (session, executor) = make_session_with_executor(); - let uuid = Uuid::new_v4().to_string(); - - session - .create_task(uuid.clone(), "Short ID".into()) - .await - .expect("create"); - executor.assign_short_id(&uuid, 42); - - let fetched = session - .get_task("42".into()) - .await - .expect("get by short id") - .expect("task should exist"); - assert_eq!(fetched.uuid, uuid); - assert_eq!(fetched.short_id, Some(42)); - - let updated = session - .mutate_task( - "42".into(), - vec![TaskMutation::SetDescription { - value: "Updated by short ID".into(), - }], - ) - .await - .expect("mutate by short id") - .expect("task still exists"); - assert_eq!(updated.uuid, uuid); - assert_eq!(updated.short_id, Some(42)); - assert_eq!(updated.description, "Updated by short ID"); -} - #[tokio::test] async fn test_mutate_description() { let session = make_session(); diff --git a/scripts/build_xcframework.sh b/scripts/build_xcframework.sh deleted file mode 100755 index 2bcc4997d..000000000 --- a/scripts/build_xcframework.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/usr/bin/env bash -# -# Build an XCFramework containing the taskchampion-ffi static library -# for iOS device + simulator, Mac Catalyst, and macOS (arm64) targets, plus -# generate Swift bindings. -# -# Prerequisites: -# - Rust toolchain (stable) -# - Xcode command-line tools -# - iOS Rust targets (script installs if missing) -# -# Usage: -# ./scripts/build_xcframework.sh -# -# Outputs: -# TaskChampionFFIFFI.xcframework/ — XCFramework with static libs + headers -# Sources/TaskChampionFFI/ — Generated Swift bindings -# -# Notes: -# - The crate declares crate-type = ["cdylib", "staticlib", "rlib"]. Cargo -# builds all three for each target. The cdylib (.dylib) output is unused — -# only the staticlib (.a) goes into the XCFramework. Linker warnings about -# the cdylib are expected and harmless. -# - The XCFramework and C module are named TaskChampionFFIFFI — derived from -# uniffi.toml module_name = "TaskChampionFFI" plus the "FFI" suffix that -# UniFFI appends to all C-layer artifacts. -# -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" -BUILD_DIR="${PROJECT_ROOT}/build" -XCFRAMEWORK_NAME="TaskChampionFFIFFI" -XCFRAMEWORK_DIR="${PROJECT_ROOT}/${XCFRAMEWORK_NAME}.xcframework" -SWIFT_OUT_DIR="${PROJECT_ROOT}/Sources/TaskChampionFFI" - -TARGETS=( - aarch64-apple-ios - aarch64-apple-ios-sim - aarch64-apple-ios-macabi - aarch64-apple-darwin -) - -# --- Ensure Rust targets are installed --- - -echo "==> Checking Rust targets..." -for target in "${TARGETS[@]}"; do - if ! rustup target list --installed | grep -q "^${target}$"; then - echo " Installing ${target}..." - rustup target add "${target}" - fi -done - -# --- Build static libraries --- - -echo "==> Building static libraries (parallel)..." -pids=() -for target in "${TARGETS[@]}"; do - echo " Spawning build for ${target}..." - # Set iOS deployment target so the static library links against the - # correct SDK version. Without this, cargo and the cc crate use the - # Xcode SDK default (e.g. 18.5), which may be newer than the app's - # deployment target. Per-command env avoids leaking into other targets. - case "$target" in - aarch64-apple-ios | aarch64-apple-ios-sim | aarch64-apple-ios-macabi) - env IPHONEOS_DEPLOYMENT_TARGET=14.0 \ - cargo build \ - -p taskchampion-ffi \ - --lib \ - --release \ - --target "${target}" \ - --manifest-path "${PROJECT_ROOT}/Cargo.toml" & - ;; - *) - # macOS — keep in sync with Package.swift .macOS(.v14) - env MACOSX_DEPLOYMENT_TARGET=14.0 \ - cargo build \ - -p taskchampion-ffi \ - --lib \ - --release \ - --target "${target}" \ - --manifest-path "${PROJECT_ROOT}/Cargo.toml" & - ;; - esac - pids+=($!) -done -for pid in "${pids[@]}"; do - wait "$pid" -done - -# --- Generate Swift bindings --- - -echo "==> Generating Swift bindings..." -# uniffi-bindgen reads type metadata from the compiled library — architecture -# doesn't matter, so we reuse the already-built iOS device lib instead of -# compiling a redundant host-native build. -METADATA_LIB="${PROJECT_ROOT}/target/aarch64-apple-ios/release/libtaskchampion_ffi.a" -if [ ! -f "${METADATA_LIB}" ]; then - echo "ERROR: iOS device lib not found at ${METADATA_LIB} — did the cargo build step fail?" >&2 - exit 1 -fi - -mkdir -p "${SWIFT_OUT_DIR}" -# uniffi-bindgen is compiled in debug mode (no --release) — it only reads -# metadata from the library, not architecture-specific code, so release -# optimisation would add build time with no benefit. -cargo run \ - -p taskchampion-ffi \ - --bin uniffi-bindgen \ - --manifest-path "${PROJECT_ROOT}/Cargo.toml" \ - -- generate \ - --library "${METADATA_LIB}" \ - --language swift \ - --out-dir "${BUILD_DIR}/generated" - -# Move Swift source to Sources/ directory (SPM target) -cp "${BUILD_DIR}/generated/TaskChampionFFI.swift" "${SWIFT_OUT_DIR}/TaskChampionFFI.swift" - -# --- Prepare headers for XCFramework --- - -echo "==> Preparing headers..." -HEADERS_DIR="${BUILD_DIR}/headers" -mkdir -p "${HEADERS_DIR}" -cp "${BUILD_DIR}/generated/${XCFRAMEWORK_NAME}.h" "${HEADERS_DIR}/${XCFRAMEWORK_NAME}.h" - -# UniFFI generates a modulemap, but xcodebuild needs it named module.modulemap -cp "${BUILD_DIR}/generated/${XCFRAMEWORK_NAME}.modulemap" "${HEADERS_DIR}/module.modulemap" - -# --- Prepare simulator library --- - -echo "==> Preparing simulator library..." -mkdir -p "${BUILD_DIR}/ios-simulator" -cp "${PROJECT_ROOT}/target/aarch64-apple-ios-sim/release/libtaskchampion_ffi.a" \ - "${BUILD_DIR}/ios-simulator/libtaskchampion_ffi.a" - -# --- Prepare Mac Catalyst library --- - -echo "==> Preparing Mac Catalyst library..." -mkdir -p "${BUILD_DIR}/mac-catalyst" -cp "${PROJECT_ROOT}/target/aarch64-apple-ios-macabi/release/libtaskchampion_ffi.a" \ - "${BUILD_DIR}/mac-catalyst/libtaskchampion_ffi.a" - -# --- Prepare macOS library --- - -echo "==> Preparing macOS library..." -mkdir -p "${BUILD_DIR}/macos" -cp "${PROJECT_ROOT}/target/aarch64-apple-darwin/release/libtaskchampion_ffi.a" \ - "${BUILD_DIR}/macos/libtaskchampion_ffi.a" - -# --- Create XCFramework --- - -echo "==> Creating XCFramework..." -rm -rf "${XCFRAMEWORK_DIR}" -xcodebuild -create-xcframework \ - -library "${PROJECT_ROOT}/target/aarch64-apple-ios/release/libtaskchampion_ffi.a" \ - -headers "${HEADERS_DIR}" \ - -library "${BUILD_DIR}/ios-simulator/libtaskchampion_ffi.a" \ - -headers "${HEADERS_DIR}" \ - -library "${BUILD_DIR}/mac-catalyst/libtaskchampion_ffi.a" \ - -headers "${HEADERS_DIR}" \ - -library "${BUILD_DIR}/macos/libtaskchampion_ffi.a" \ - -headers "${HEADERS_DIR}" \ - -output "${XCFRAMEWORK_DIR}" - -# --- Cleanup --- - -rm -rf "${BUILD_DIR}" - -echo "" -echo "==> Done!" -echo " XCFramework: ${XCFRAMEWORK_DIR}" -echo " Swift sources: ${SWIFT_OUT_DIR}/TaskChampionFFI.swift" -echo "" -echo " Tag a version and push to create a GitHub Release. SPM consumers add: - https://github.com/GuionAI/taskchampion.git" diff --git a/scripts/package_cargo_swift.sh b/scripts/package_cargo_swift.sh new file mode 100755 index 000000000..18664c8e2 --- /dev/null +++ b/scripts/package_cargo_swift.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# +# Build the Swift package and TaskChampionCore XCFramework with cargo-swift. +# +# This script is intended for macOS. It keeps the checked-in ffi crate unchanged +# and patches a temporary copy so cargo-swift can package this workspace layout. +# +# Usage: +# ./scripts/package_cargo_swift.sh [output-dir] +# +# Environment: +# TASKCHAMPION_FFI_LINKAGE=static|dynamic Defaults to static. +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +OUTPUT_DIR="${1:-${PROJECT_ROOT}/target/cargo-swift}" +LINKAGE="${TASKCHAMPION_FFI_LINKAGE:-static}" + +case "${LINKAGE}" in + dynamic | static) ;; + *) + echo "ERROR: TASKCHAMPION_FFI_LINKAGE must be 'dynamic' or 'static', got '${LINKAGE}'" >&2 + exit 1 + ;; +esac + +command -v cargo >/dev/null || { echo "ERROR: cargo is required" >&2; exit 1; } +command -v cargo-swift >/dev/null || { echo "ERROR: cargo-swift is required; install with: cargo install cargo-swift@0.11.1 --locked" >&2; exit 1; } + +workdir="$(mktemp -d "${TMPDIR:-/tmp}/taskchampion-ffi.XXXXXX")" +trap 'rm -rf "${workdir}"' EXIT + +mkdir -p "${OUTPUT_DIR}" +rm -rf "${OUTPUT_DIR}/TaskChampionFFI" + +rsync -a --exclude target "${PROJECT_ROOT}/ffi/" "${workdir}/" + +python3 - "${PROJECT_ROOT}" "${workdir}/Cargo.toml" "${workdir}/uniffi.toml" <<'PY' +import sys +from pathlib import Path + +repo = Path(sys.argv[1]) +cargo_toml = Path(sys.argv[2]) +uniffi_toml = Path(sys.argv[3]) + +content = cargo_toml.read_text() +content = content.replace('path = ".."', f'path = "{repo}"') +content = content.replace('path = "../praxis"', f'path = "{repo / "praxis"}"') +content = content.replace( + 'crate-type = ["cdylib", "staticlib", "rlib"]', + 'crate-type = ["lib", "cdylib", "staticlib", "rlib"]', +) +cargo_toml.write_text(content) + +content = uniffi_toml.read_text() +content = content.replace( + 'module_name = "TaskChampionFFI"', + 'ffi_module_name = "TaskChampionCore"', + 1, +) +uniffi_toml.write_text(content) +PY + +cd "${workdir}" +cat uniffi.toml + +cargo swift package \ + -p ios@14 macos@14 \ + -n TaskChampionFFI \ + --release \ + --lib-type "${LINKAGE}" \ + --bundle-identifier com.guion.taskchampion \ + --swift-tools-version 5.9 \ + -y --silent + +cp -R TaskChampionFFI "${OUTPUT_DIR}/TaskChampionFFI" +echo "Generated ${OUTPUT_DIR}/TaskChampionFFI" diff --git a/scripts/use_local_xcframework.sh b/scripts/use_local_xcframework.sh new file mode 100755 index 000000000..ee1a17c8a --- /dev/null +++ b/scripts/use_local_xcframework.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# +# Point Package.swift at a locally built XCFramework. +# +# Run this after ./scripts/package_cargo_swift.sh when testing this repo as a +# local Swift package in Xcode: +# +# ./scripts/use_local_xcframework.sh target/cargo-swift/TaskChampionFFI/TaskChampionCore.xcframework +# +# Restore the release URL with: +# +# git restore Package.swift +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +XCFRAMEWORK_PATH="${1:-target/cargo-swift/TaskChampionFFI/TaskChampionCore.xcframework}" +PACKAGE_SWIFT="${PROJECT_ROOT}/Package.swift" + +if [ ! -d "${PROJECT_ROOT}/${XCFRAMEWORK_PATH}" ]; then + echo "ERROR: ${XCFRAMEWORK_PATH} does not exist. Run ./scripts/package_cargo_swift.sh first." >&2 + exit 1 +fi + +python3 - "${PACKAGE_SWIFT}" "${XCFRAMEWORK_PATH}" <<'PY' +import re +import sys +from pathlib import Path + +package_swift = Path(sys.argv[1]) +xcframework_path = sys.argv[2] + +content = package_swift.read_text() +replacement = f'''.binaryTarget( + name: "TaskChampionCore", + path: "{xcframework_path}" + )''' + +pattern = re.compile( + r'''\.binaryTarget\( + name: "TaskChampionCore", + url: "[^"]+", + checksum: "[0-9a-f]{64}" + \)''', + re.MULTILINE, +) + +content, count = pattern.subn(replacement, content) +if count == 0: + path_pattern = re.compile( + r'''\.binaryTarget\( + name: "TaskChampionCore", + path: "[^"]+" + \)''', + re.MULTILINE, + ) + content, count = path_pattern.subn(replacement, content) + +if count != 1: + print(f"ERROR: expected one TaskChampionCore binaryTarget block, found {count}", file=sys.stderr) + sys.exit(1) + +package_swift.write_text(content) +PY + +echo "Package.swift now points at ${XCFRAMEWORK_PATH}" +echo "Restore the release URL with: git restore Package.swift" diff --git a/src/replica.rs b/src/replica.rs index 9ab7e2fe4..c2fd7191f 100644 --- a/src/replica.rs +++ b/src/replica.rs @@ -132,29 +132,6 @@ impl Replica { self.taskdb.all_task_uuids().await } - /// Resolve a task reference to a UUID. - /// - /// Full UUID strings are accepted directly. All-digit strings are treated as - /// per-user short IDs assigned by the backing database. - pub async fn resolve_task_ref(&mut self, task_ref: &str) -> Result> { - if let Ok(uuid) = Uuid::parse_str(task_ref) { - return self - .taskdb - .get_task(uuid) - .await - .map(|task| task.map(|_| uuid)); - } - - if task_ref.chars().all(|c| c.is_ascii_digit()) { - let short_id = task_ref - .parse::() - .map_err(|_| Error::Usage(format!("Invalid task short id: {task_ref}")))?; - return self.taskdb.resolve_task_short_id(short_id).await; - } - - Err(Error::Usage(format!("Invalid task reference: {task_ref}"))) - } - /// Get an array containing all pending tasks pub async fn pending_tasks(&mut self) -> Result> { let depmap = self.dependency_map(false).await?; diff --git a/src/storage/columns.rs b/src/storage/columns.rs index a43f24e3b..baa1e4192 100644 --- a/src/storage/columns.rs +++ b/src/storage/columns.rs @@ -38,7 +38,6 @@ pub(crate) fn extract_timestamp(task_data: &mut TaskMap, key: &str) -> Result, pub(crate) data: String, pub(crate) status: Option, pub(crate) description: Option, @@ -64,10 +63,6 @@ pub(crate) fn raw_to_task(raw: RawTaskRow) -> Result<(Uuid, TaskMap)> { let mut task_map: TaskMap = serde_json::from_str(&raw.data) .map_err(|e| Error::Database(format!("Failed to parse task data for task {uuid}: {e}")))?; - if let Some(v) = raw.short_id { - task_map.insert("short_id".into(), v.to_string()); - } - // Inject string columns back into the task map. if let Some(v) = raw.status { task_map.insert("status".into(), v); @@ -117,8 +112,7 @@ pub(crate) fn raw_to_task(raw: RawTaskRow) -> Result<(Uuid, TaskMap)> { /// Shared column projection for all tc_tasks queries (requires `t` and `p` aliases). #[cfg(any(feature = "storage-external", feature = "storage-powersync"))] -pub(crate) const TASK_SELECT_COLS: &str = - "t.id, t.short_id, t.data, t.status, t.description, t.priority, \ +pub(crate) const TASK_SELECT_COLS: &str = "t.id, t.data, t.status, t.description, t.priority, \ t.entry_at, t.modified_at, t.due_at, t.scheduled_at, \ t.start_at, t.end_at, t.wait_at, t.parent_id, \ p.name as project_name, t.project_id, t.note_id"; @@ -195,7 +189,6 @@ mod tests { fn make_empty_raw(uuid: &Uuid) -> RawTaskRow { RawTaskRow { id: uuid.to_string(), - short_id: None, data: "{}".to_string(), status: None, description: None, diff --git a/src/storage/external.rs b/src/storage/external.rs index 11c3a056a..543ac710d 100644 --- a/src/storage/external.rs +++ b/src/storage/external.rs @@ -26,11 +26,6 @@ use crate::storage::{Storage, StorageTxn, TaskMap}; /// /// Implementors run SQL against their own database connection. Methods are /// async to support non-blocking host-side execution (e.g. Swift async/await). -/// -/// The exposed tables must be scoped to one user, either by using a local -/// single-user PowerSync database or by applying equivalent RLS/filtering in -/// the host. Task short IDs are per-user values; query results must not mix -/// rows from multiple users. #[async_trait] pub trait SqlExecutor: Send + Sync { /// Execute a read query returning at most one row as a JSON object string. @@ -139,7 +134,6 @@ impl ExternalStorageTxn<'_> { Ok(RawTaskRow { id, - short_id: get_opt_i64(obj, "short_id"), data, status: get_opt_str(obj, "status"), description: get_opt_str(obj, "description"), @@ -310,21 +304,6 @@ impl StorageTxn for ExternalStorageTxn<'_> { .collect() } - async fn resolve_task_short_id(&mut self, short_id: i64) -> Result> { - let row = self - .executor - .query_one( - "SELECT id FROM tc_tasks WHERE short_id = ? LIMIT 1", - &[SqlParam::Text(short_id.to_string())], - ) - .await?; - row.map(|json| { - let id = parse_json_string_field(&json, "id")?; - Uuid::parse_str(&id).map_err(|e| Error::Database(format!("Invalid UUID: {e}"))) - }) - .transpose() - } - async fn get_task_operations(&mut self, uuid: Uuid) -> Result> { let rows = self.executor.query_all(ALL_OPERATIONS_SQL, &[]).await?; rows.iter() @@ -440,14 +419,6 @@ fn get_opt_str(obj: &serde_json::Map, key: &str) -> O }) } -fn get_opt_i64(obj: &serde_json::Map, key: &str) -> Option { - obj.get(key).and_then(|v| match v { - serde_json::Value::Number(n) => n.as_i64(), - serde_json::Value::String(s) => s.parse::().ok(), - _ => None, - }) -} - /// Extract a required string field from a JSON object string. /// Returns `Err` if the field is missing, null, or not a string type. fn parse_json_string_field(json: &str, field: &str) -> Result { @@ -516,8 +487,6 @@ mod test { conn.execute_batch( "CREATE TABLE IF NOT EXISTS tc_tasks ( id TEXT PRIMARY KEY, - -- Populated by the backing sync system; task writes treat it as read-only. - short_id INTEGER, data TEXT NOT NULL DEFAULT '{}', entry_at TEXT, status TEXT, description TEXT, priority TEXT, modified_at TEXT, due_at TEXT, scheduled_at TEXT, start_at TEXT, end_at TEXT, diff --git a/src/storage/mod.rs b/src/storage/mod.rs index c812b84b9..ec696a1f5 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -88,24 +88,6 @@ pub trait StorageTxn: Send { /// Get the uuids of all tasks in the storage, in undefined order. async fn all_task_uuids(&mut self) -> Result>; - /// Resolve a per-user short ID to a task UUID. - /// - /// Storage implementations are expected to expose a single-user view of - /// `tc_tasks` through local sync state or database RLS. The short ID is - /// only unique within that user scope. - async fn resolve_task_short_id(&mut self, short_id: i64) -> Result> { - for (uuid, task) in self.all_tasks().await? { - if task - .get("short_id") - .and_then(|v| v.parse::().ok()) - .is_some_and(|v| v == short_id) - { - return Ok(Some(uuid)); - } - } - Ok(None) - } - /// Get the set of operations for the given task. async fn get_task_operations(&mut self, uuid: Uuid) -> Result>; diff --git a/src/storage/pgwire/mod.rs b/src/storage/pgwire/mod.rs index 78569ffd0..761bbe394 100644 --- a/src/storage/pgwire/mod.rs +++ b/src/storage/pgwire/mod.rs @@ -152,7 +152,6 @@ impl<'a> StorageTxn for PgWireTxn<'a> { r#" SELECT t.id, - t.short_id, t.data as "data: serde_json::Value", t.status, t.description, @@ -196,7 +195,6 @@ impl<'a> StorageTxn for PgWireTxn<'a> { r#" SELECT t.id, - t.short_id, t.data as "data: serde_json::Value", t.status, t.description, @@ -354,7 +352,6 @@ impl<'a> StorageTxn for PgWireTxn<'a> { r#" SELECT t.id, - t.short_id, t.data as "data: serde_json::Value", t.status, t.description, @@ -389,20 +386,6 @@ impl<'a> StorageTxn for PgWireTxn<'a> { Ok(ids) } - async fn resolve_task_short_id(&mut self, short_id: i64) -> Result> { - let Ok(short_id) = i32::try_from(short_id) else { - return Ok(None); - }; - let t = self.get_txn()?; - sqlx::query_scalar!( - "SELECT id FROM tc_tasks WHERE short_id = $1 LIMIT 1", - short_id - ) - .fetch_optional(&mut **t) - .await - .map_err(|e| pgwire_context("resolve_task_short_id query", e)) - } - async fn get_task_operations(&mut self, _uuid: Uuid) -> Result> { Ok(vec![]) } diff --git a/src/storage/pgwire/row.rs b/src/storage/pgwire/row.rs index b741d3b1f..31eb4965c 100644 --- a/src/storage/pgwire/row.rs +++ b/src/storage/pgwire/row.rs @@ -15,7 +15,6 @@ use crate::storage::columns::RawTaskRow; /// Intermediate Pg-side struct for a tc_tasks row. pub(super) struct TaskPgRow { pub(super) id: Uuid, - pub(super) short_id: Option, pub(super) data: serde_json::Value, pub(super) status: Option, pub(super) description: Option, @@ -42,7 +41,6 @@ impl From for RawTaskRow { fn from(r: TaskPgRow) -> Self { Self { id: r.id.to_string(), - short_id: r.short_id.map(i64::from), data: serde_json::to_string(&r.data).expect("jsonb Value re-serialize cannot fail"), status: r.status, description: r.description, @@ -78,7 +76,6 @@ mod tests { fn task_pg_row_to_raw_task_row_jsonb_roundtrip() { let row = TaskPgRow { id: Uuid::nil(), - short_id: Some(42), data: json!({"description": "test", "status": "pending"}), status: Some("pending".into()), description: Some("test".into()), @@ -96,7 +93,6 @@ mod tests { note_id: None, }; let raw: RawTaskRow = row.into(); - assert_eq!(raw.short_id, Some(42)); assert!(raw.data.contains("description")); assert!(raw.data.contains("pending")); let _: serde_json::Value = serde_json::from_str(&raw.data).unwrap(); @@ -106,7 +102,6 @@ mod tests { fn task_pg_row_to_raw_task_row_null_json() { let row = TaskPgRow { id: Uuid::nil(), - short_id: None, data: serde_json::Value::Null, status: None, description: None, diff --git a/src/storage/powersync/inner.rs b/src/storage/powersync/inner.rs index 47885fedb..751be8653 100644 --- a/src/storage/powersync/inner.rs +++ b/src/storage/powersync/inner.rs @@ -98,8 +98,6 @@ impl PowerSyncStorageInner { " CREATE TABLE IF NOT EXISTS tc_tasks ( id TEXT PRIMARY KEY, - -- Populated by the backing sync system; task writes treat it as read-only. - short_id INTEGER, data TEXT NOT NULL DEFAULT '{}', entry_at TEXT, status TEXT, @@ -343,18 +341,6 @@ impl crate::storage::StorageTxn for PowerSyncTxn<'_> { .collect() } - async fn resolve_task_short_id(&mut self, short_id: i64) -> Result> { - let t = self.get_txn()?; - let id: Option = - sqlx::query_scalar("SELECT id FROM tc_tasks WHERE short_id = ? LIMIT 1") - .bind(short_id) - .fetch_optional(&mut **t) - .await - .context("resolve_task_short_id query")?; - id.map(|s| Uuid::parse_str(&s).map_err(|e| Error::Database(format!("Invalid UUID: {e}")))) - .transpose() - } - async fn get_task_operations(&mut self, uuid: Uuid) -> Result> { // tc_operations has no UUID column (schema is PowerSync-managed). // Filter in memory after deserializing; acceptable for the expected operation count. diff --git a/src/storage/powersync/mod.rs b/src/storage/powersync/mod.rs index cd8d87308..9219a234a 100644 --- a/src/storage/powersync/mod.rs +++ b/src/storage/powersync/mod.rs @@ -112,69 +112,6 @@ mod test { Ok(()) } - #[tokio::test] - async fn test_short_id_column_round_trip_read_only() -> Result<()> { - let mut storage = storage().await?; - let uuid = Uuid::new_v4(); - - { - let mut txn = storage.txn().await?; - let mut task: TaskMap = TaskMap::new(); - task.insert("status".into(), "pending".into()); - task.insert("description".into(), "short id task".into()); - task.insert("short_id".into(), "999".into()); - txn.set_task(uuid, task).await?; - txn.commit().await?; - } - - sqlx::query("UPDATE tc_tasks SET short_id = ? WHERE id = ?") - .bind(42_i64) - .bind(uuid.to_string()) - .execute(&storage.0.pool) - .await?; - - let data_str: String = sqlx::query_scalar("SELECT data FROM tc_tasks WHERE id = ?") - .bind(uuid.to_string()) - .fetch_one(&storage.0.pool) - .await?; - let data_map: serde_json::Value = serde_json::from_str(&data_str) - .map_err(|e| crate::errors::Error::Database(e.to_string()))?; - assert!( - !data_map.as_object().unwrap().contains_key("short_id"), - "short_id must not be persisted into the editable task data blob" - ); - - let mut txn = storage.txn().await?; - let got = txn.get_task(uuid).await?.expect("task should exist"); - assert_eq!(got.get("short_id").map(String::as_str), Some("42")); - txn.commit().await?; - Ok(()) - } - - #[tokio::test] - async fn test_replica_resolves_uuid_and_short_id_refs() -> Result<()> { - let storage = storage().await?; - let uuid = Uuid::new_v4(); - - sqlx::query( - "INSERT INTO tc_tasks (id, data, status, description, short_id) \ - VALUES (?, '{}', 'pending', 'short id task', ?)", - ) - .bind(uuid.to_string()) - .bind(42_i64) - .execute(&storage.0.pool) - .await?; - - let mut replica = crate::Replica::new(storage); - assert_eq!( - replica.resolve_task_ref(&uuid.to_string()).await?, - Some(uuid) - ); - assert_eq!(replica.resolve_task_ref("42").await?, Some(uuid)); - assert_eq!(replica.resolve_task_ref("404").await?, None); - Ok(()) - } - /// Verify that all seven timestamp fields survive a set_task / get_task round-trip /// through epoch → ISO 8601 → epoch conversion. #[tokio::test] diff --git a/src/storage/sql_ops.rs b/src/storage/sql_ops.rs index c5c1cceae..41204a9ee 100644 --- a/src/storage/sql_ops.rs +++ b/src/storage/sql_ops.rs @@ -61,7 +61,6 @@ pub(crate) fn prepare_task(mut task_data: TaskMap) -> Result { let description = task_data.remove("description"); let priority = task_data.remove("priority"); let parent_id = task_data.remove("parent_id"); - task_data.remove("short_id"); // Extract timestamps (epoch → ISO). let entry_at = extract_timestamp(&mut task_data, "entry")?; diff --git a/src/task/task.rs b/src/task/task.rs index 9df1e26c1..4c32276ae 100644 --- a/src/task/task.rs +++ b/src/task/task.rs @@ -125,12 +125,6 @@ impl Task { self.data.get(Prop::Priority.as_ref()).unwrap_or("") } - pub fn get_short_id(&self) -> Option { - self.data - .get("short_id") - .and_then(|value| value.parse::().ok()) - } - /// Get the wait time. If this value is set, it will be returned, even /// if it is in the past. pub fn get_wait(&self) -> Option { @@ -923,27 +917,6 @@ mod test { assert_eq!(task.get_priority(), ""); } - #[test] - fn test_get_short_id() { - let task = Task::new( - TaskData::new( - Uuid::new_v4(), - TaskMap::from([("short_id".into(), "42".into())]), - ), - dm(), - ); - assert_eq!(task.get_short_id(), Some(42)); - - let task = Task::new( - TaskData::new( - Uuid::new_v4(), - TaskMap::from([("short_id".into(), "not-a-number".into())]), - ), - dm(), - ); - assert_eq!(task.get_short_id(), None); - } - #[test] fn test_get_annotations() { let task = Task::new( diff --git a/src/taskdb/mod.rs b/src/taskdb/mod.rs index 930bb5b33..fe62660e6 100644 --- a/src/taskdb/mod.rs +++ b/src/taskdb/mod.rs @@ -49,11 +49,6 @@ impl TaskDb { txn.all_task_uuids().await } - pub(crate) async fn resolve_task_short_id(&mut self, short_id: i64) -> Result> { - let mut txn = self.storage.txn().await?; - txn.resolve_task_short_id(short_id).await - } - /// Get a single task, by uuid. pub(crate) async fn get_task(&mut self, uuid: Uuid) -> Result> { let mut txn = self.storage.txn().await?; From 85388014a065b9c78503d5cb269f3990d595b6e1 Mon Sep 17 00:00:00 2001 From: neil Date: Fri, 10 Jul 2026 11:49:26 +0800 Subject: [PATCH 2/2] fix(ci): keep snapshot release commits tag-only --- .github/workflows/release-xcframework.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-xcframework.yml b/.github/workflows/release-xcframework.yml index 68f21da2a..325521104 100644 --- a/.github/workflows/release-xcframework.yml +++ b/.github/workflows/release-xcframework.yml @@ -248,7 +248,7 @@ jobs: f.write(content) " - - name: Commit Package.swift and Swift bindings and push + - name: Commit Package.swift and Swift bindings env: VERSION_TAG: ${{ steps.version.outputs.tag }} run: | @@ -260,7 +260,11 @@ jobs: else git commit -m "chore(spm): update Package.swift and Swift bindings for ${VERSION_TAG} release" fi - git push origin "HEAD:${GITHUB_REF_NAME}" + if [[ "$VERSION_TAG" == *snapshot* ]]; then + echo "Snapshot release: keeping the Package.swift/checksum commit tag-only" + else + git push origin "HEAD:${GITHUB_REF_NAME}" + fi # Move the tag after the release commit so SwiftPM sees the matching # Package.swift URL and checksum when pinning this version.