Skip to content

feat: add Cloudflare D1 database driver plugin#376

Merged
datlechin merged 6 commits intomainfrom
feat/cloudflare-d1-driver
Mar 19, 2026
Merged

feat: add Cloudflare D1 database driver plugin#376
datlechin merged 6 commits intomainfrom
feat/cloudflare-d1-driver

Conversation

@datlechin
Copy link
Owner

@datlechin datlechin commented Mar 19, 2026

Summary

Closes #206

  • Add Cloudflare D1 driver plugin — HTTP-based (URLSession), SQLite-compatible SQL dialect, connects via Cloudflare REST API with Bearer token auth
  • Add ConnectionMode.apiOnly — new connection form mode that shows only Database, Account ID, and API Token (no Host/Port/Username/SSH/SSL)
  • Add 76 unit tests covering JSON decoding, API response parsing, driver helpers, and plugin metadata

Plugin Features

  • Full schema introspection: tables, columns, indexes, foreign keys, DDL, views
  • Query execution via D1 /raw endpoint with stable column ordering
  • Parameterized queries with native ? placeholder support (nil → JSON null)
  • Database listing, switching, and creation via Cloudflare API
  • EXPLAIN QUERY PLAN support
  • SQLite-compatible identifier quoting (") and string escaping
  • Cloudflare internal table filtering (_cf_*)
  • Rate limit (429) and auth error (401/403) handling

Files Changed

  • New plugin: Plugins/CloudflareD1DriverPlugin/ (4 files)
  • New tests: TableProTests/Core/CloudflareD1/ (4 files, 76 tests)
  • New asset: cloudflare-d1-icon.imageset/
  • Modified: ConnectionMode.swift (added .apiOnly), ConnectionFormView.swift (apiOnly form layout), PluginMetadataRegistry+RegistryDefaults.swift (D1 entry), DatabaseConnection.swift (.cloudflareD1 constant), project.pbxproj (D1 target)

Test plan

  • Build succeeds: xcodebuild -scheme TablePro build -skipPackagePluginValidation
  • 76 D1 unit tests pass (D1ValueDecoding, D1ResponseParsing, CloudflareD1DriverHelper, CloudflareD1PluginMetadata)
  • Connection form shows Database + Account ID + API Token (no Host/Port/Username)
  • Other connection types (MySQL, PostgreSQL, SQLite) unaffected by apiOnly changes
  • Connect to real D1 database with valid credentials
  • Tables list, column structure, indexes, foreign keys display correctly
  • SELECT/INSERT/UPDATE/DELETE queries work
  • Database switching lists all account databases
  • Create Database works via Cloudflare API

Summary by CodeRabbit

  • New Features

    • Built-in Cloudflare D1 support: connect via Account ID + API Token, browse databases/tables/views, run queries (including EXPLAIN QUERY PLAN), edit data, and switch/manage databases in the UI.
    • Connection form updated for API-only flows with a Database field and API Token input; Cloudflare D1 offered as an installable plugin; new Cloudflare D1 icon asset.
  • Documentation

    • Added Cloudflare D1 docs (EN/VI/ZH) and changelog entry with setup, credentials, limitations, and troubleshooting.
  • Tests

    • New test suites covering D1 API parsing, value decoding, and driver helper utilities.

@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds first-class Cloudflare D1 support: a new DriverPlugin and async HTTP driver/client, registry/dialect defaults, API-only connection mode and UI updates, plugin packaging and assets, docs (EN/VI/ZH), changelog/CI updates, and comprehensive unit tests for parsing and helper utilities.

Changes

Cohort / File(s) Summary
Plugin Core
Plugins/CloudflareD1DriverPlugin/CloudflareD1Plugin.swift, Plugins/CloudflareD1DriverPlugin/CloudflareD1PluginDriver.swift
New plugin and driver implementing TableProPlugin/PluginDatabaseDriver: metadata, SQL dialect, connection lifecycle, query execution/pagination, result mapping, schema introspection, DB management, quoting/DDL helpers, and transaction-not-supported behavior.
HTTP Client
Plugins/CloudflareD1DriverPlugin/D1HttpClient.swift
Async Cloudflare D1 HTTP client with request/session/task lifecycle, JSON response models, endpoints for /raw, /query and DB listing/creation/details, and structured error handling (including 401/403/429 mapping).
Plugin Packaging & Xcode Assets
Plugins/CloudflareD1DriverPlugin/Info.plist, TablePro.xcodeproj/project.pbxproj, TablePro/Assets.xcassets/cloudflare-d1-icon.imageset/Contents.json
Adds plugin plist and asset, creates new Xcode target/product, build configurations, and wires TableProPluginKit framework into the plugin target.
Registry & Dialect Defaults
TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift
Registers "Cloudflare D1" defaults: SQL dialect descriptor, column type mappings, explain variant, connectionMode .apiOnly, UI/auth fields (cfAccountId), capabilities and structure column fields.
Connection Model & UI
Plugins/TableProPluginKit/ConnectionMode.swift, TablePro/Models/Connection/DatabaseConnection.swift, TablePro/Views/Connection/ConnectionFormView.swift, TablePro/Resources/Localizable.xcstrings
Adds apiOnly ConnectionMode and cloudflareD1 DatabaseType; connection form renders Database/Account ID/API Token for API-only mode, adjusts auth field visibility and validation, and adds localization keys.
Documentation
docs/.../cloudflare-d1.mdx, docs/vi/.../cloudflare-d1.mdx, docs/zh/.../cloudflare-d1.mdx
Adds multi-language documentation describing connection/setup, required credentials, limitations, troubleshooting, examples, and supported features.
Tests
TableProTests/Core/CloudflareD1/*
Adds test suites for D1 response parsing, D1 value decoding, driver helper utilities (quoting/escaping/UUID/LIMIT stripping/DDL formatting), and plugin metadata registry assertions.
Changelog & CI
CHANGELOG.md, .github/workflows/build-plugin.yml
Changelog entry for Cloudflare D1 and CI workflow updates to support plugin name/version parsing.

Sequence Diagram(s)

sequenceDiagram
    participant Client as TablePro Client
    participant Driver as CloudflareD1PluginDriver
    participant HttpClient as D1HttpClient
    participant D1API as Cloudflare D1 API

    Client->>Driver: connect()
    activate Driver
    Driver->>HttpClient: listDatabases() / getDatabaseDetails()
    activate HttpClient
    HttpClient->>D1API: GET /accounts/:id/databases or /databases/:uuid
    D1API-->>HttpClient: database list / details
    HttpClient-->>Driver: parsed DatabaseInfo
    deactivate HttpClient
    Driver->>Driver: resolve/cache database UUID, set serverVersion
    Driver-->>Client: connected
    deactivate Driver

    Client->>Driver: executeParameterized(sql, params)
    activate Driver
    Driver->>HttpClient: executeQuery(sql, params) (POST /query)
    activate HttpClient
    HttpClient->>D1API: POST /query { sql, params }
    D1API-->>HttpClient: query result envelope (results, meta)
    HttpClient-->>Driver: payload
    deactivate HttpClient
    Driver->>Driver: mapRawResult() -> PluginQueryResult
    Driver-->>Client: query result (rows, columns, meta)
    deactivate Driver
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through endpoints, tokens in tow,
Queried and parsed each row by row.
With UUID maps and SQL delight,
D1 and TablePro now leap at night. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add Cloudflare D1 database driver plugin' is clear, concise, and directly reflects the main change: adding a new database driver plugin for Cloudflare D1.
Linked Issues check ✅ Passed The PR implements all required coding objectives from #206: D1 HTTP driver, schema introspection, parameterized queries, EXPLAIN support, database operations, apiOnly connection UI, internal table filtering, error handling, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to D1 support: new plugin files, UI updates for apiOnly mode, plugin registry/metadata entries, ConnectionMode extension, tests, documentation, and build configuration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cloudflare-d1-driver
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (8)
TableProTests/Core/CloudflareD1/CloudflareD1PluginMetadataTests.swift (1)

33-172: Exercise the live registry lookup instead of the defaults factory.

These tests call registryPluginDefaults() directly, but the app reads metadata through the registry lookup path. A broken bootstrap/registration step could still pass this suite. Prefer asserting via snapshot(forTypeId:).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TableProTests/Core/CloudflareD1/CloudflareD1PluginMetadataTests.swift` around
lines 33 - 172, Tests are using registryPluginDefaults() directly which bypasses
the registry lookup path; change each test to query
PluginMetadataRegistry.shared.snapshot(forTypeId: "Cloudflare D1") (or
equivalent lookup API) instead of calling registryPluginDefaults(), and update
assertions to unwrap the returned snapshot (guard let snapshot =
registry.snapshot(forTypeId: "Cloudflare D1") else { Issue.record(...) ; return
}) then assert against snapshot properties (displayName, brandColorHex,
isDownloadable, capabilities, supportsDatabaseSwitching,
schema.databaseGroupingStrategy, editor.sqlDialect, explainVariants,
connection.additionalConnectionFields, parameterStyle) so the live
registration/bootstrap is exercised. Ensure you remove uses of defaults/d1Entry
and reference snapshot consistently across tests.
TableProTests/Core/CloudflareD1/D1ValueDecodingTests.swift (1)

14-60: Avoid a second hand-rolled D1Value decoder in the test target.

This suite only proves the local copy can decode the fixtures. It will stay green even if the real D1 value decoder regresses, and it can also drift from the other local D1Value copy in D1ResponseParsingTests.swift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TableProTests/Core/CloudflareD1/D1ValueDecodingTests.swift` around lines 14 -
60, Tests define a duplicate D1Value enum (with stringValue and init(from:))
which duplicates production/other-test decoding logic; remove this local enum
and instead reuse the canonical D1Value implementation by importing or
referencing the shared type used in D1ResponseParsingTests.swift (or the
production module) so the tests exercise the real decoder; update usages in
D1ValueDecodingTests.swift to use the shared D1Value type and delete the enum
and its stringValue/init(from:) implementation from this file.
TablePro.xcodeproj/project.pbxproj (1)

781-782: Add an explicit TablePro → CloudflareD1DriverPlugin target dependency.

The app target's "Copy D1 Plugin" build phase relies on implicit dependency resolution. All other bundled plugins (OracleDriver, SQLiteDriver, ClickHouseDriver, MySQLDriver, PostgreSQLDriver, DuckDBDriver, CSVExport, JSONExport, SQLExport) have explicit PBXTargetDependency + PBXContainerItemProxy entries. CloudflareD1DriverPlugin should follow the same pattern for consistency and build reliability.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TablePro.xcodeproj/project.pbxproj` around lines 781 - 782, Add an explicit
PBXTargetDependency + PBXContainerItemProxy for CloudflareD1DriverPlugin so the
TablePro target's "Copy D1 Plugin" build phase no longer relies on implicit
resolution: create a PBXContainerItemProxy entry that points to the
CloudflareD1DriverPlugin target and then add a corresponding PBXTargetDependency
referencing that container item, and include the new PBXTargetDependency
identifier in the TablePro target's dependencies (alongside the existing
OracleDriver/SQLiteDriver/etc. entries) so CloudflareD1DriverPlugin is treated
the same as the other bundled plugins.
Plugins/CloudflareD1DriverPlugin/CloudflareD1PluginDriver.swift (3)

219-225: Inconsistent filtering pattern for Cloudflare internal tables.

Line 181 (fetchTables) uses GLOB '_cf_*' while line 223 uses LIKE '_cf_%'. GLOB is case-sensitive in SQLite whereas LIKE is case-insensitive by default. For consistency, consider using the same pattern throughout:

♻️ Suggested fix
             WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' AND m.name NOT LIKE '_cf_%'
+            -- Or alternatively, use GLOB for consistency with fetchTables:
+            -- WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' AND m.name NOT GLOB '_cf_*'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/CloudflareD1DriverPlugin/CloudflareD1PluginDriver.swift` around lines
219 - 225, fetchAllColumns uses "LIKE '_cf_%'" which is inconsistent with
fetchTables that uses "GLOB '_cf_*'"; update the SQL in fetchAllColumns (the
multiline query in function fetchAllColumns) to use the same GLOB '_cf_*'
pattern to match Cloudflare internal table names the same way as fetchTables
(ensuring consistent case-sensitive filtering across both methods).

572-574: Consider documenting or throwing for unsupported transaction methods.

Since supportsTransactions returns false, these no-op implementations are technically correct. However, if a caller ignores the capability flag and calls these methods, the silent no-op could lead to data integrity issues (caller believes a transaction is active when it isn't).

Consider either:

  1. Adding a comment explaining the intentional no-op behavior, or
  2. Throwing an error to fail fast if called incorrectly
💡 Alternative: fail-fast approach
-    func beginTransaction() async throws {}
-    func commitTransaction() async throws {}
-    func rollbackTransaction() async throws {}
+    func beginTransaction() async throws {
+        throw CloudflareD1Error(message: String(localized: "Transactions are not supported by Cloudflare D1"))
+    }
+    func commitTransaction() async throws {
+        throw CloudflareD1Error(message: String(localized: "Transactions are not supported by Cloudflare D1"))
+    }
+    func rollbackTransaction() async throws {
+        throw CloudflareD1Error(message: String(localized: "Transactions are not supported by Cloudflare D1"))
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/CloudflareD1DriverPlugin/CloudflareD1PluginDriver.swift` around lines
572 - 574, The no-op transaction methods (beginTransaction, commitTransaction,
rollbackTransaction) in CloudflareD1PluginDriver silently do nothing despite
supportsTransactions returning false; update these methods to fail fast by
throwing a descriptive error (e.g., “Transactions not supported by
CloudflareD1PluginDriver”) when called, or at minimum add a clear comment above
each method noting the intentional no-op and referencing supportsTransactions;
ensure the thrown error type/message is consistent across beginTransaction,
commitTransaction, and rollbackTransaction so callers immediately know
transactions are unsupported.

142-145: Parameter mapping can be simplified.

The explicit mapping from [String?] to [Any?] is correct but verbose. Swift handles this coercion automatically.

♻️ Suggested simplification
-        let anyParams: [Any?] = parameters.map { param -> Any? in
-            guard let value = param else { return nil }
-            return value
-        }
+        let anyParams: [Any?] = parameters
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/CloudflareD1DriverPlugin/CloudflareD1PluginDriver.swift` around lines
142 - 145, The local mapping that constructs anyParams from parameters is
verbose; replace the explicit map (the block creating anyParams) with a direct
type coercion by assigning parameters to anyParams (use parameters as [Any?] or
pass parameters directly where [Any?] is expected), removing the unnecessary
map; update references to anyParams in the surrounding code (in
CloudflareD1PluginDriver.swift where anyParams and parameters are used) so the
simpler cast is used consistently.
Plugins/CloudflareD1DriverPlugin/D1HttpClient.swift (2)

365-370: Minor: Rate limit message could be clearer when Retry-After is missing.

When Retry-After header is absent, the message becomes "Rate limited by Cloudflare. Retry after unknown seconds." which reads awkwardly.

💡 Suggested improvement
         case 429:
-            let retryAfter = response.value(forHTTPHeaderField: "Retry-After") ?? "unknown"
-            Self.logger.warning("D1 rate limited. Retry-After: \(retryAfter)")
-            throw D1HttpError(
-                message: String(localized: "Rate limited by Cloudflare. Retry after \(retryAfter) seconds.")
-            )
+            let retryAfter = response.value(forHTTPHeaderField: "Retry-After")
+            Self.logger.warning("D1 rate limited. Retry-After: \(retryAfter ?? "not specified")")
+            if let seconds = retryAfter {
+                throw D1HttpError(
+                    message: String(localized: "Rate limited by Cloudflare. Retry after \(seconds) seconds.")
+                )
+            } else {
+                throw D1HttpError(
+                    message: String(localized: "Rate limited by Cloudflare. Please try again later.")
+                )
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/CloudflareD1DriverPlugin/D1HttpClient.swift` around lines 365 - 370,
The rate-limit error message becomes awkward when the Retry-After header is
missing; update the handling in the case 429 block (the retryAfter variable and
thrown D1HttpError) to produce a clearer message when retryAfter == "unknown"
(e.g., log "D1 rate limited. Retry-After: unknown" and throw D1HttpError with
text like "Rate limited by Cloudflare. Retry time unknown." or "Rate limited by
Cloudflare. Retry after an unknown time."), so the logger and D1HttpError
messages read naturally whether the header is present or not.

311-343: Concurrent requests could lose cancellation tracking.

If multiple requests execute concurrently, storing the task in currentTask (line 329) will overwrite any previously running task reference. This means cancelCurrentTask() would only cancel the most recent request, not all in-flight requests.

If concurrent requests are expected, consider using a collection to track all active tasks:

♻️ Suggested approach
-    private var currentTask: URLSessionDataTask?
+    private var currentTasks: Set<URLSessionDataTask> = []

Then add/remove tasks from the set instead of replacing a single reference.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/CloudflareD1DriverPlugin/D1HttpClient.swift` around lines 311 - 343,
The code overwrites a single currentTask for concurrent requests causing lost
cancellation; replace currentTask with a thread-safe collection (e.g.,
Set<URLSessionTask> or [URLSessionTask]) named currentTasks, and update the
logic in the dataTask creation and cancellation handler to add the task to
currentTasks under lock before task.resume(), remove it from currentTasks under
lock in the task completion closure (and in the continuation error/return
paths), and in the onCancel block iterate over currentTasks to cancel each task
and then clear the set; also update the final cleanup (where currentTask is set
to nil) to remove the finished task from currentTasks under the same lock.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@TablePro/Views/Connection/ConnectionFormView.swift`:
- Around line 321-324: The SecureField is still bound to password even for
API-only connections, so testConnection() persists that value under the
temporary testConn.id and leaves the API token orphaned; change the UI and
persistence logic so API tokens are not stored to the regular password field or
left in Keychain after a test: introduce a separate state (e.g., apiToken) and
bind the SecureField to apiToken when PluginManager.shared.connectionMode(for:
type) == .apiOnly (instead of password), and update testConnection() to use that
apiToken and either avoid writing it to Keychain for unsaved connections (or
delete the temporary testConn.id entry after testing) so tokens are never
orphaned.
- Around line 875-877: The current basicValid check only enforces database for
.fileBased/.apiOnly modes but ignores the new cfAccountId and api token
requirements, so update the validation in ConnectionFormView.swift (around
PluginManager.shared.connectionMode(for: type), mode, requiresDatabase,
basicValid) to also require non-empty cfAccountId and apiToken when mode ==
.apiOnly (or when PluginManager indicates API-only). Concretely, add checks for
!cfAccountId.isEmpty and !apiToken.isEmpty into the boolean used for enabling
Save/Test so API-only connections are rejected until those fields are filled.

In `@TableProTests/Core/CloudflareD1/CloudflareD1DriverHelperTests.swift`:
- Around line 14-116: The test file currently reimplements helper functions
(quoteIdentifier, escapeStringLiteral, castColumnToText, isUuid,
stripLimitOffset, formatDDL) and SQL snippets; instead, update the tests to call
the real implementations on CloudflareD1PluginDriver (e.g.,
CloudflareD1PluginDriver.quoteIdentifier(...), .stripLimitOffset(...),
.formatDDL(...)) and remove the duplicated local helper functions so the
assertions validate the actual driver behavior; ensure any test data or calls
that used the local helpers now invoke the corresponding
CloudflareD1PluginDriver methods and adjust imports/visibility if needed so the
driver helpers are accessible to the test.

In `@TableProTests/Core/CloudflareD1/D1ResponseParsingTests.swift`:
- Around line 14-92: Tests are using locally redefined mirror models
(D1ApiResponse, D1Value, D1RawResultPayload, D1RawResults, D1QueryMeta,
D1DatabaseInfo, D1ListResponse) instead of the real decoder types used by
D1HttpClient, so they only validate the fixtures against the copies; change the
tests to import and decode into the actual parser types used by D1HttpClient (or
remove the duplicated structs and reference the shared types), ensuring the test
uses the real D1Value decoding/coercion and the real API response structs (e.g.,
D1ApiResponse and D1Value from the production module) so any future changes to
coding keys or optional handling in D1HttpClient will be caught by the tests.

---

Nitpick comments:
In `@Plugins/CloudflareD1DriverPlugin/CloudflareD1PluginDriver.swift`:
- Around line 219-225: fetchAllColumns uses "LIKE '_cf_%'" which is inconsistent
with fetchTables that uses "GLOB '_cf_*'"; update the SQL in fetchAllColumns
(the multiline query in function fetchAllColumns) to use the same GLOB '_cf_*'
pattern to match Cloudflare internal table names the same way as fetchTables
(ensuring consistent case-sensitive filtering across both methods).
- Around line 572-574: The no-op transaction methods (beginTransaction,
commitTransaction, rollbackTransaction) in CloudflareD1PluginDriver silently do
nothing despite supportsTransactions returning false; update these methods to
fail fast by throwing a descriptive error (e.g., “Transactions not supported by
CloudflareD1PluginDriver”) when called, or at minimum add a clear comment above
each method noting the intentional no-op and referencing supportsTransactions;
ensure the thrown error type/message is consistent across beginTransaction,
commitTransaction, and rollbackTransaction so callers immediately know
transactions are unsupported.
- Around line 142-145: The local mapping that constructs anyParams from
parameters is verbose; replace the explicit map (the block creating anyParams)
with a direct type coercion by assigning parameters to anyParams (use parameters
as [Any?] or pass parameters directly where [Any?] is expected), removing the
unnecessary map; update references to anyParams in the surrounding code (in
CloudflareD1PluginDriver.swift where anyParams and parameters are used) so the
simpler cast is used consistently.

In `@Plugins/CloudflareD1DriverPlugin/D1HttpClient.swift`:
- Around line 365-370: The rate-limit error message becomes awkward when the
Retry-After header is missing; update the handling in the case 429 block (the
retryAfter variable and thrown D1HttpError) to produce a clearer message when
retryAfter == "unknown" (e.g., log "D1 rate limited. Retry-After: unknown" and
throw D1HttpError with text like "Rate limited by Cloudflare. Retry time
unknown." or "Rate limited by Cloudflare. Retry after an unknown time."), so the
logger and D1HttpError messages read naturally whether the header is present or
not.
- Around line 311-343: The code overwrites a single currentTask for concurrent
requests causing lost cancellation; replace currentTask with a thread-safe
collection (e.g., Set<URLSessionTask> or [URLSessionTask]) named currentTasks,
and update the logic in the dataTask creation and cancellation handler to add
the task to currentTasks under lock before task.resume(), remove it from
currentTasks under lock in the task completion closure (and in the continuation
error/return paths), and in the onCancel block iterate over currentTasks to
cancel each task and then clear the set; also update the final cleanup (where
currentTask is set to nil) to remove the finished task from currentTasks under
the same lock.

In `@TablePro.xcodeproj/project.pbxproj`:
- Around line 781-782: Add an explicit PBXTargetDependency +
PBXContainerItemProxy for CloudflareD1DriverPlugin so the TablePro target's
"Copy D1 Plugin" build phase no longer relies on implicit resolution: create a
PBXContainerItemProxy entry that points to the CloudflareD1DriverPlugin target
and then add a corresponding PBXTargetDependency referencing that container
item, and include the new PBXTargetDependency identifier in the TablePro
target's dependencies (alongside the existing OracleDriver/SQLiteDriver/etc.
entries) so CloudflareD1DriverPlugin is treated the same as the other bundled
plugins.

In `@TableProTests/Core/CloudflareD1/CloudflareD1PluginMetadataTests.swift`:
- Around line 33-172: Tests are using registryPluginDefaults() directly which
bypasses the registry lookup path; change each test to query
PluginMetadataRegistry.shared.snapshot(forTypeId: "Cloudflare D1") (or
equivalent lookup API) instead of calling registryPluginDefaults(), and update
assertions to unwrap the returned snapshot (guard let snapshot =
registry.snapshot(forTypeId: "Cloudflare D1") else { Issue.record(...) ; return
}) then assert against snapshot properties (displayName, brandColorHex,
isDownloadable, capabilities, supportsDatabaseSwitching,
schema.databaseGroupingStrategy, editor.sqlDialect, explainVariants,
connection.additionalConnectionFields, parameterStyle) so the live
registration/bootstrap is exercised. Ensure you remove uses of defaults/d1Entry
and reference snapshot consistently across tests.

In `@TableProTests/Core/CloudflareD1/D1ValueDecodingTests.swift`:
- Around line 14-60: Tests define a duplicate D1Value enum (with stringValue and
init(from:)) which duplicates production/other-test decoding logic; remove this
local enum and instead reuse the canonical D1Value implementation by importing
or referencing the shared type used in D1ResponseParsingTests.swift (or the
production module) so the tests exercise the real decoder; update usages in
D1ValueDecodingTests.swift to use the shared D1Value type and delete the enum
and its stringValue/init(from:) implementation from this file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 87ec9257-bc6c-4b5b-9559-f62aa6a7d191

📥 Commits

Reviewing files that changed from the base of the PR and between 9c08063 and 093935b.

⛔ Files ignored due to path filters (1)
  • TablePro/Assets.xcassets/cloudflare-d1-icon.imageset/cloudflare-d1.svg is excluded by !**/*.svg
📒 Files selected for processing (16)
  • CHANGELOG.md
  • Plugins/CloudflareD1DriverPlugin/CloudflareD1Plugin.swift
  • Plugins/CloudflareD1DriverPlugin/CloudflareD1PluginDriver.swift
  • Plugins/CloudflareD1DriverPlugin/D1HttpClient.swift
  • Plugins/CloudflareD1DriverPlugin/Info.plist
  • Plugins/TableProPluginKit/ConnectionMode.swift
  • TablePro.xcodeproj/project.pbxproj
  • TablePro/Assets.xcassets/cloudflare-d1-icon.imageset/Contents.json
  • TablePro/Core/Plugins/PluginMetadataRegistry+RegistryDefaults.swift
  • TablePro/Models/Connection/DatabaseConnection.swift
  • TablePro/Resources/Localizable.xcstrings
  • TablePro/Views/Connection/ConnectionFormView.swift
  • TableProTests/Core/CloudflareD1/CloudflareD1DriverHelperTests.swift
  • TableProTests/Core/CloudflareD1/CloudflareD1PluginMetadataTests.swift
  • TableProTests/Core/CloudflareD1/D1ResponseParsingTests.swift
  • TableProTests/Core/CloudflareD1/D1ValueDecodingTests.swift

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
.github/workflows/build-plugin.yml (1)

182-183: Fail fast on malformed plugin tags before extraction.

If the tag does not match, sed returns the original string and the failure surfaces later as “Unknown plugin”. An explicit format guard here makes failures clearer.

♻️ Proposed change
-          PLUGIN_NAME=$(echo "$TAG" | sed -E 's/^plugin-([a-z0-9-]+)-v([0-9].*)$/\1/')
-          VERSION=$(echo "$TAG" | sed -E 's/^plugin-([a-z0-9-]+)-v([0-9].*)$/\2/')
+          if [[ ! "$TAG" =~ ^plugin-([a-z0-9-]+)-v([0-9].*)$ ]]; then
+            echo "::error::Invalid tag format: $TAG (expected plugin-<name>-v<version>)"
+            exit 1
+          fi
+          PLUGIN_NAME="${BASH_REMATCH[1]}"
+          VERSION="${BASH_REMATCH[2]}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build-plugin.yml around lines 182 - 183, Add an explicit
validation step that checks TAG against the expected pattern before extracting
PLUGIN_NAME and VERSION: test TAG with the regex used by the sed commands and if
it does not match, print a clear error including the malformed TAG and exit
non-zero. Keep using the same identifiers (TAG, PLUGIN_NAME, VERSION) so the
guard runs just prior to the existing sed extractions and prevents later
“Unknown plugin” failures by failing fast with a descriptive message.
docs/databases/cloudflare-d1.mdx (1)

97-102: Consider adding a language identifier to the code block.

The fenced code block lacks a language identifier. While this is an example configuration (not executable code), the coding guidelines recommend including language identifiers in all fenced code blocks for consistency.

📝 Suggested addition
-```
+```text
 Name:       My D1 Database
 Database:   my-app-db
 Account ID: abc123def456
 API Token:  (your Cloudflare API token)

</details>

As per coding guidelines: "Include language identifiers in fenced code blocks."

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/databases/cloudflare-d1.mdx around lines 97 - 102, The fenced code
block containing the Cloudflare D1 example lacks a language identifier; update
that block (the triple-backtick block showing "Name: My D1 Database / Database:
my-app-db / Account ID: abc123def456 / API Token: (your Cloudflare API token)")
to include a language tag such as "text" (e.g., ```text) so it follows the
repository guideline to always include language identifiers in fenced code
blocks.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/build-plugin.yml:

  • Around line 182-183: Add an explicit validation step that checks TAG against
    the expected pattern before extracting PLUGIN_NAME and VERSION: test TAG with
    the regex used by the sed commands and if it does not match, print a clear error
    including the malformed TAG and exit non-zero. Keep using the same identifiers
    (TAG, PLUGIN_NAME, VERSION) so the guard runs just prior to the existing sed
    extractions and prevents later “Unknown plugin” failures by failing fast with a
    descriptive message.

In @docs/databases/cloudflare-d1.mdx:

  • Around line 97-102: The fenced code block containing the Cloudflare D1 example
    lacks a language identifier; update that block (the triple-backtick block
    showing "Name: My D1 Database / Database: my-app-db / Account ID: abc123def456 /
    API Token: (your Cloudflare API token)") to include a language tag such as
    "text" (e.g., ```text) so it follows the repository guideline to always include
    language identifiers in fenced code blocks.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `14a597aa-0633-4ba8-8ffd-c870f46da0f4`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 093935bbd936885695ac6257a8750f4c54dc3d01 and 535185a647ce4aff6c13cd881478b3e843e6e5c6.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `.github/workflows/build-plugin.yml`
* `TablePro.xcodeproj/project.pbxproj`
* `docs/databases/cloudflare-d1.mdx`
* `docs/vi/databases/cloudflare-d1.mdx`
* `docs/zh/databases/cloudflare-d1.mdx`

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (2)</summary>

* docs/zh/databases/cloudflare-d1.mdx
* docs/vi/databases/cloudflare-d1.mdx

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@datlechin datlechin merged commit 2229188 into main Mar 19, 2026
2 checks passed
@datlechin datlechin deleted the feat/cloudflare-d1-driver branch March 19, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Cloudflare D1 support

1 participant