Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/duplicate-contract-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,3 @@
# databasetype:<file defining a production DatabaseType other than the TableProCore source>

databasetype:TablePro/Models/Connection/DatabaseConnection.swift

pluginkit:Info.plist
33 changes: 33 additions & 0 deletions .github/workflows/pluginkit-abi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: PluginKit ABI Gate

on:
pull_request:
paths:
- "Plugins/TableProPluginKit/**"
- "scripts/check-pluginkit-abi.sh"
- ".github/workflows/pluginkit-abi.yml"
Comment on lines +5 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run the ABI gate when the Xcode project changes

The new gate only runs for PluginKit sources, the script, or this workflow, but the script builds TablePro.xcodeproj and the library-evolution setting that makes the ABI resilient lives in TablePro.xcodeproj/project.pbxproj. A PR that only changes that project file (for example disabling BUILD_LIBRARY_FOR_DISTRIBUTION, changing the PluginKit target settings, or otherwise affecting the generated .swiftinterface) would bypass this required ABI check entirely, leaving the baseline stale despite an ABI-affecting change.

Useful? React with 👍 / 👎.


jobs:
abi-gate:
name: PluginKit ABI Gate
runs-on: macos-26
timeout-minutes: 20

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "26.4.1"

- name: Create Secrets.xcconfig
run: touch Secrets.xcconfig

- name: Check PluginKit ABI vs base
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: scripts/check-pluginkit-abi.sh "$BASE_SHA"
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- The database plugin interface is now binary-stable. Adding plugin capabilities in a later release no longer forces installed plugins to be rebuilt; only a breaking interface change does.
- Connection list rows show the database name after the host, so connections that share a name and host are easier to tell apart. (#1535)
- Save as Favorite uses Cmd+D again. The Cmd+Control+D set in 0.47.0 is reserved by macOS for Look Up, so it never fired.
- Editor toolbar buttons show their keyboard shortcut in the tooltip, and it updates if you rebind the shortcut.
Expand Down
14 changes: 10 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,19 +86,25 @@ gh release upload libs-v1 /tmp/tablepro-libs-ios-v1.tar.gz --clobber --repo Tabl

All database drivers are `.tableplugin` bundles loaded at runtime by `PluginManager` (`Core/Plugins/`):

- **TableProPluginKit** (`Plugins/TableProPluginKit/`) — shared framework with `PluginDatabaseDriver`, `DriverPlugin`, `TableProPlugin` protocols and transfer types (`PluginQueryResult`, `PluginColumnInfo`, etc.)
- **TableProPluginKit** (`Plugins/TableProPluginKit/`) — shared framework with `PluginDatabaseDriver`, `DriverPlugin`, `TableProPlugin` protocols and transfer types (`PluginQueryResult`, `PluginColumnInfo`, etc.). This is the single source of truth; the SwiftPM target at `Packages/TableProCore/Sources/TableProPluginKit` is a symlink to it, so edit the files under `Plugins/TableProPluginKit/` only.
- **PluginDriverAdapter** (`Core/Plugins/PluginDriverAdapter.swift`) — bridges `PluginDatabaseDriver` → `DatabaseDriver` protocol
- **DatabaseDriverFactory** (`Core/Database/DatabaseDriver.swift`) — looks up plugins via `DatabaseType.pluginTypeId`
- **DatabaseManager** (`Core/Database/DatabaseManager.swift`) — connection pool, lifecycle, primary interface for views/coordinators
- **ConnectionHealthMonitor** — 30s ping, auto-reconnect with exponential backoff

When adding a new driver: create a new plugin bundle under `Plugins/`, implement `DriverPlugin` + `PluginDatabaseDriver`, add target to pbxproj, add `DatabaseType` static constant, add case to `resolve_plugin_info()` in `.github/workflows/build-plugin.yml`, add row to `docs/index.mdx` supported databases table, and add CHANGELOG entry. See `docs/development/plugin-system/` for details.

When adding a new method to the driver protocol: add to `PluginDatabaseDriver` (with default implementation), then update `PluginDriverAdapter` to bridge it to `DatabaseDriver`.
When adding a new method to the driver protocol: add to `PluginDatabaseDriver` (with default implementation), then update `PluginDriverAdapter` to bridge it to `DatabaseDriver`. This is an additive, ABI-safe change (see below) and needs no version bump.

**PluginKit ABI versioning**: When `DriverPlugin` or `PluginDatabaseDriver` protocol changes (new methods, changed signatures), bump `currentPluginKitVersion` in `PluginManager.swift` AND `TableProPluginKitVersion` in every plugin's `Info.plist`. Stale user-installed plugins with mismatched versions crash on load with `EXC_BAD_INSTRUCTION` (not catchable in Swift). Removing protocol methods that have default `nil` implementations does NOT require a version bump. Adding new `static var` or `func` requirements to `DriverPlugin` DOES require a version bump even with default implementations via protocol extension — Swift protocol witness tables are compiled statically.
**PluginKit ABI (resilient)**: TableProPluginKit is built with `BUILD_LIBRARY_FOR_DISTRIBUTION = YES` (Swift Library Evolution), so its public ABI is resilient. The Swift runtime instantiates witness tables for already-built plugins and fills any requirement the plugin did not implement from the protocol's default, so a plugin built against an older PluginKit keeps loading under a newer app.

**Post-ABI-bump checklist (mandatory)**: After bumping `currentPluginKitVersion`, every registry-published plugin must be rebuilt against the new ABI. App auto-update reconciliation handles the user-facing recovery, but the registry has to carry binaries for the new PluginKit version first.
**Additive changes are binary-compatible and need NO version bump**: adding a requirement to `DriverPlugin` / `PluginDatabaseDriver` that has a default implementation, reordering requirements, adding a field to a non-`@frozen` transfer struct, or removing a requirement that defaulted to `nil`.

**Bump `currentPluginKitVersion` (in `PluginManager.swift`) and `TableProPluginKitVersion` in every plugin `Info.plist` ONLY for a breaking change**: changing or removing an existing requirement's signature, adding a requirement without a default, adding a case to a `@frozen` enum, or changing a frozen type's layout. Mark a public enum `@frozen` only when an exhaustive switch over it forces it (the compiler flags the switch) and its case set is genuinely closed; leave the rest non-frozen so they can gain cases. `PluginCapability` stays non-frozen with `@unknown default` because it is a growing capability set, not a closed vocabulary. The driver protocols and transfer structs stay non-frozen so they can grow. The strict version gate in `validateBundleVersions` still rejects a stale plugin cleanly after a breaking bump (no `EXC_BAD_INSTRUCTION`).

**ABI gate**: `scripts/check-pluginkit-abi.sh [base-ref]` builds TableProPluginKit at the current tree and at the base ref with the same toolchain, then diffs their public interfaces. There is no committed baseline, so a Swift version difference between a dev machine and CI never produces a false diff. CI (`.github/workflows/pluginkit-abi.yml`) runs it on every PR that touches `Plugins/TableProPluginKit/**`, comparing against the PR base. A reported diff is a real ABI change: additive needs no bump; breaking needs the version bump above plus `release-all-plugins.sh`. (Until Library Evolution is on the base too, the base emits no interface and the gate passes as a bootstrap.)

**Post-ABI-bump checklist (mandatory, breaking bumps only)**: Bumps are now rare (only the breaking changes listed above). After one, every registry-published plugin must be rebuilt against the new ABI. Run `release-all-plugins.sh` for the new version BEFORE or WITH the app release, never after, or users on the new app hit `noCompatibleBinary` until the registry catches up. App auto-update reconciliation handles the user-facing recovery, but the registry has to carry binaries for the new PluginKit version first.

1. Commit the bump (updates `PluginManager.swift` and every bundled plugin's `Info.plist`). Bundled plugins ship with the next app release. Do not tag them.
2. Trigger the bulk re-release:
Expand Down
3 changes: 2 additions & 1 deletion Packages/TableProCore/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ let package = Package(
.target(
name: "TableProPluginKit",
dependencies: [],
path: "Sources/TableProPluginKit"
path: "Sources/TableProPluginKit",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include PluginKit source changes in iOS CI

This target now resolves through the new Sources/TableProPluginKit symlink to Plugins/TableProPluginKit, so future edits under Plugins/TableProPluginKit/** change the TableProPluginKit product that the mobile project consumes. I checked .github/workflows/ios-tests.yml, and its PR/push filters only include TableProMobile/** and Packages/TableProCore/**; a PluginKit change can therefore skip the iOS test workflow even though TableProMobile.xcodeproj depends on the TableProPluginKit product from ../Packages/TableProCore and mobile code imports it. Please add the PluginKit source path to that workflow's filters when consolidating the source tree.

Useful? React with 👍 / 👎.

exclude: ["Info.plist"]
),
.target(
name: "TableProModels",
Expand Down
1 change: 1 addition & 0 deletions Packages/TableProCore/Sources/TableProPluginKit

This file was deleted.

139 changes: 0 additions & 139 deletions Packages/TableProCore/Sources/TableProPluginKit/ConnectionField.swift

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading