Skip to content

feat(postgrest): add type-safe PostgREST query layer via Swift Macros - #1036

Draft
grdsdev wants to merge 28 commits into
mainfrom
claude/great-borg-042617
Draft

feat(postgrest): add type-safe PostgREST query layer via Swift Macros#1036
grdsdev wants to merge 28 commits into
mainfrom
claude/great-borg-042617

Conversation

@grdsdev

@grdsdev grdsdev commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a type-safe PostgREST query layer to supabase-swift, allowing users to interact with their database using Swift types instead of raw strings — column names, table names, and query filters are all type-checked at compile time.

What's new

Swift Macros (@Table, @SelectionOf, @Relationship) — Applied to a Swift struct, @Table synthesizes full TableRepresentable conformance including:

  • Insert and Update nested types (with optional fields for @Default/nullable columns; @PrimaryKey fields excluded from inserts)
  • CodingKeys with automatic camelCasesnake_case conversion (overridable via @Column)
  • columnName(for:) using KeyPath identity for type-safe column lookup
  • Read-only tables/views via @Table(readOnly: true)

@SelectionOf(Table.self) declares a partial column projection or join query. Fields annotated with @Relationship(\Table.fkColumn) generate PostgREST disambiguation syntax (alias:tableName!fk_column(subselect)) — the FK column is identified via a Swift KeyPath (type-checked at compile time) and the referenced type is inferred from the field's type annotation.

@Table enforces that @Relationship fields are never declared on it — joins belong exclusively on @SelectionOf structs.

Typed query builders — Four generic wrappers delegate to the existing string-based builders:

  • TypedPostgrestQueryBuilder<Table: TableRepresentable> — insert, upsert, update, delete, select
  • TypedReadOnlyQueryBuilder<Table: ReadOnlyTableRepresentable> — select-only (insert/update/delete are compile errors)
  • TypedPostgrestFilterBuilder<Table, Selection> — KeyPath-based filters (eq, neq, in, like, order, limit, …)
  • TypedPostgrestTransformBuilder / TypedSingleResultBuilder — transform and single-row execution

Entry pointPostgrestClient.from(_ table: T.Type) returns the appropriate typed builder based on whether T conforms to TableRepresentable or ReadOnlyTableRepresentable.

Usage

// Define your table
@Table("messages")
struct Message {
  @PrimaryKey var id: UUID
  var senderId: UUID
  var body: String
}

// Declare a projection with a join
@SelectionOf(Message.self)
struct MessageWithSender {
  var id: UUID
  var body: String
  @Relationship(\Message.senderId) var sender: User
}
// MessageWithSender.selectString == "id,body,sender:users!sender_id(*)"

// Query with full type safety
let rows = try await supabase
  .from(Message.self)
  .select(MessageWithSender.self)
  .execute()
  .value  // [MessageWithSender]

// Filters use KeyPaths
let unread = try await supabase
  .from(Message.self)
  .select()
  .eq(\.senderId, value: userId)
  .execute()
  .value  // [Message]

Architecture

The typed API lives entirely in SupabaseSwiftMacros, a separate opt-in library. PostgREST and Supabase have no dependency on it, so swift-syntax is only compiled for users who explicitly import SupabaseSwiftMacros.

SupabaseMacros (compiler plugin, internal)
    ↑
SupabaseSwiftMacros (opt-in library)
    ├── Protocols.swift         — SelectionRepresentable, ReadOnlyTableRepresentable, TableRepresentable
    ├── Macros.swift            — @Table, @SelectionOf, @PrimaryKey, @Default, @Column, @Relationship
    ├── TypedBuilders/          — TypedPostgrestQueryBuilder, TypedPostgrestFilterBuilder, …
    └── PostgrestClient+Typed.swift — from(_ table: T.Type) entry point

PostgREST  ←──── no swift-syntax dependency
Supabase   ←──── no swift-syntax dependency

Diagnostics

Non-primitive @SelectionOf fields without @Relationship, @Relationship on a @Table field, and let bindings on @Table are all compile-time errors with descriptive messages.

Testing

  • 17 macro expansion tests (exact synthesized source assertions via swift-macro-testing)
  • 8 Swift Testing tests verifying KeyPath → snake_case column name translation via inline-snapshot of actual HTTP request URLs

Test plan

  • swift test --filter SupabaseMacrosTests — all 17 expansion/diagnostic tests pass
  • swift test --filter PostgRESTTests — all typed builder tests pass
  • import PostgREST alone (without import SupabaseSwiftMacros) does not pull in swift-syntax
  • @Table struct with a @Relationship field is a compile error
  • Non-primitive @SelectionOf field without @Relationship is a compile error
  • client.from(ReadOnlyView.self).insert(...) is a compile error

@coveralls

coveralls commented Jun 25, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 30086116632

Coverage remained the same at 83.648%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 9785
Covered Lines: 8185
Line Coverage: 83.65%
Coverage Strength: 39.08 hits per line

💛 - Coveralls

grdsdev added 25 commits July 24, 2026 06:59
Design for type-safe PostgREST queries via CLI codegen + Swift Macros
(@table, @SelectionOf) + a typed wrapper layer over existing string API.
7-task plan covering package infra, protocol layer, marker macros,
@table macro, @SelectionOf macro, typed builders, and client extension.
Also fixes ReadOnlyTableRepresentable protocol hierarchy in the spec.
Defines SelectionRepresentable, ReadOnlyTableRepresentable, and
TableRepresentable protocol hierarchy, plus all six macro stubs
(@table, @SelectionOf, @PrimaryKey, @default, @column, @relationship)
and an empty CompilerPlugin entry point for SupabaseMacros.
…relationship)

These four marker macros are PeerMacros that produce no expansion.
They exist solely as source annotations that the @table and @SelectionOf
macros read during compilation.
Synthesizes TableRepresentable/ReadOnlyTableRepresentable conformance,
Insert, Update, CodingKeys, and columnName for @Table-annotated structs.
Introduces TypedPostgrestQueryBuilder, TypedPostgrestFilterBuilder,
TypedPostgrestTransformBuilder, TypedSingleResultBuilder, and a
PostgrestClient extension so callers can use .from(Table.Type) with
KeyPath-based filter methods that translate to snake_case column names.
Both typed `from()` overloads were discarding the schema variable — both branches of `schema.map` returned `configuration.url`, so non-public schemas were silently ignored. Fix by mutating a copy of the configuration's schema field (mirroring the existing `schema()` method pattern), which causes `PostgrestBuilder.execute()` to emit the correct `Accept-Profile`/`Content-Profile` headers. Also removes the redundant `where Table: ReadOnlyTableRepresentable` constraint on the read-only overload.
…y point

Move PostgrestClient+Typed.swift from Sources/PostgREST/TypedBuilders/ to
the canonical location Sources/PostgREST/PostgrestClient+Typed.swift.
No code changes — the implementation is identical; only the file path is corrected.
…ix swift-syntax constraint

- Add `@_exported import SupabaseSwiftMacros` to Sources/Supabase/Exports.swift so
  consumers who write `import Supabase` gain access to @table, TableRepresentable,
  and related types without an additional import.

- Raise the swift-syntax dependency from `from: "510.0.0"` to `"600.0.0"..<"605.0.0"`.
  swift-macro-testing 0.6.5 accepts swift-syntax "509.0.0"..<"605.0.0", so the 600.x
  range is fully compatible. This prevents SPM from resolving to the old 510.x series
  (510.0.3 was previously pinned) and instead lands on 603.0.2, matching what
  Xcode 16+ ships. Also tightens swift-macro-testing minimum to 0.6.0 to align with
  the actual resolved version (0.6.5).
…swift-syntax range

- Move TypedBuilders/ and PostgrestClient+Typed.swift from PostgREST to
  SupabaseSwiftMacros so swift-syntax is only compiled when the typed API
  is explicitly imported — PostgREST and Supabase no longer depend on it
- SupabaseSwiftMacros gains PostgREST as a dependency
- PostgrestClient+Typed now uses public schema()/from() API instead of
  constructing PostgrestQueryBuilder internally
- Swift-syntax range widened to 510.0.0..<605.0.0 to match the package's
  minimum Swift 5.10 support (was 600.0.0..<605.0.0)
 test signature

- Add letBindingNotAllowed diagnostic to TableMacroDiagnostic and enforce
  it in expansion(of:providingMembersOf:in:) so that let-bound stored
  properties in a @table struct emit a clear error instead of being
  silently dropped and causing cryptic compile errors or runtime panics
- Add testLetBindingDiagnostic snapshot test covering both @PrimaryKey let
  and plain let properties
- Update testRelationshipProducesNoPeers to use the current KeyPath-form
  @relationship(\Todo.userId) instead of the obsolete two-argument string form
- Annotate User, Channel, Message with @table macros
- Add MessageWithDetails @SelectionOf for the join query with @relationship
- Replace raw string queries with type-safe from(T.self), eq(\.keyPath), order(\.keyPath)
- Replace AddChannel/NewMessage with generated Insert nested types
- Remove MessagePayload in favour of Message (the @table type)
- Remove convertToSnakeCase/convertFromSnakeCase encoder/decoder strategies
  (@table CodingKeys carry explicit snake_case strings; no strategy needed)
- Add SupabaseSwiftMacros framework to SlackClone target in Xcode project
Extends SupabaseClient with the same typed from(_ table: T.Type) entry
points available on PostgrestClient, so callers can use supabase.from(T.self)
directly without going through supabase.database.from(T.self).

Adds Supabase as a dependency of SupabaseSwiftMacros to make this possible.
…t conformance

Swift typechecks extension macro expansion files in isolation and cannot
see member macro additions, causing conformance failures in Xcode builds.
The fix moves all protocol implementations into the MemberMacro and
requires users to declare TableRepresentable/ReadOnlyTableRepresentable
explicitly on their struct.

Also fixes SWIFT_DEFAULT_ACTOR_ISOLATION crash in SlackClone target and
updates macro test snapshots to match the new member-only output.
…atures

Rebasing onto main swept the PR's new SupabaseMacros/SupabaseSwiftMacros
targets and PostgRESTTests additions into main's InternalImportsByDefault
+ MemberImportVisibility upcoming features:

- SupabaseMacros is a compiler-plugin target whose macro types must be
  public to satisfy CompilerPlugin/PeerMacro; that conflicts with
  InternalImportsByDefault. Skip the import-visibility features for it
  (plugins run at build time and ship no API).
- SupabaseSwiftMacros is a shipping library exposing PostgREST and
  Supabase types in its public API, so those imports become public.
- TypedBuildersTests.swift now imports TestHelpers explicitly, required
  by MemberImportVisibility (matches sibling PostgRESTTests files).
@grdsdev
grdsdev force-pushed the claude/great-borg-042617 branch from b18aeb5 to 9013a4f Compare July 24, 2026 10:12
@grdsdev
grdsdev marked this pull request as ready for review July 24, 2026 10:13
@grdsdev
grdsdev requested a review from a team as a code owner July 24, 2026 10:13
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17972db6-159c-4765-a9aa-5d44c854b588

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@grdsdev
grdsdev marked this pull request as draft July 24, 2026 10:13
grdsdev added 2 commits July 24, 2026 07:14
Design specs and implementation plans under docs/superpowers/ were
working notes from the brainstorming/planning process, not intended
to ship as repo documentation.
The typed PostgREST API itself (macros, protocols, typed builders) stays
here; the SlackClone example's adoption of it moves to a follow-up PR
stacked on top of this one, so this PR only touches the library surface.
@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

⚠️ Potential Breaking API Changes Detected

This PR appears to contain breaking API changes. Please review the changes below:

API Check Output
  💔 API breakage: class FunctionsClient has been removed
  💔 API breakage: enum FunctionsError has been removed
  💔 API breakage: struct FunctionInvokeOptions has been removed
  💔 API breakage: struct FunctionRegion has been removed
  💔 API breakage: struct AuthAdmin has been removed
  💔 API breakage: struct AuthAdminOAuth has been removed
  💔 API breakage: class AuthClient has been removed
  💔 API breakage: struct ErrorCode has been removed
  💔 API breakage: enum AuthError has been removed
  💔 API breakage: struct AuthMFA has been removed
  💔 API breakage: struct AuthOAuthServer has been removed
  💔 API breakage: protocol AuthStateChangeListenerRegistration has been removed
  💔 API breakage: typealias AuthStateChangeListener has been removed
  💔 API breakage: typealias GoTrueClient has been removed
  💔 API breakage: typealias GoTrueMFA has been removed
  💔 API breakage: typealias GoTrueLocalStorage has been removed
  💔 API breakage: typealias GoTrueMetaSecurity has been removed
  💔 API breakage: typealias GoTrueError has been removed
  💔 API breakage: typealias MFAEnrollParams has been removed
  💔 API breakage: protocol AuthLocalStorage has been removed
  💔 API breakage: struct KeychainLocalStorage has been removed
  💔 API breakage: enum AuthChangeEvent has been removed
  💔 API breakage: struct UserCredentials has been removed
  💔 API breakage: struct Session has been removed
  💔 API breakage: struct User has been removed
  💔 API breakage: struct UserIdentity has been removed
  💔 API breakage: enum Provider has been removed
  💔 API breakage: struct OpenIDConnectCredentials has been removed
  💔 API breakage: struct AuthMetaSecurity has been removed
  💔 API breakage: struct Web3Chain has been removed
  💔 API breakage: struct Web3Credentials has been removed
  💔 API breakage: enum MobileOTPType has been removed
  💔 API breakage: enum EmailOTPType has been removed
  💔 API breakage: enum AuthResponse has been removed
  💔 API breakage: struct UserAttributes has been removed
  💔 API breakage: struct AdminUserAttributes has been removed
  💔 API breakage: enum AuthFlowType has been removed
  💔 API breakage: typealias FactorType has been removed
  💔 API breakage: enum FactorStatus has been removed
  💔 API breakage: struct Factor has been removed
  💔 API breakage: protocol MFAEnrollParamsType has been removed
  💔 API breakage: struct MFATotpEnrollParams has been removed
  💔 API breakage: struct MFAPhoneEnrollParams has been removed
  💔 API breakage: struct AuthMFAEnrollResponse has been removed
  💔 API breakage: struct MFAChallengeParams has been removed
  💔 API breakage: struct MFAVerifyParams has been removed
  💔 API breakage: struct MFAUnenrollParams has been removed
  💔 API breakage: struct MFAChallengeAndVerifyParams has been removed
  💔 API breakage: struct AuthMFAChallengeResponse has been removed
  💔 API breakage: typealias AuthMFAVerifyResponse has been removed
  💔 API breakage: struct AuthMFAUnenrollResponse has been removed
  💔 API breakage: struct AuthMFAListFactorsResponse has been removed
  💔 API breakage: typealias AuthenticatorAssuranceLevels has been removed
  💔 API breakage: struct AMREntry has been removed
  💔 API breakage: struct AuthMFAGetAuthenticatorAssuranceLevelResponse has been removed
  💔 API breakage: enum SignOutScope has been removed
  💔 API breakage: enum ResendEmailType has been removed
  💔 API breakage: enum ResendMobileType has been removed
  💔 API breakage: struct ResendMobileResponse has been removed
  💔 API breakage: struct WeakPassword has been removed
  💔 API breakage: enum MessagingChannel has been removed
  💔 API breakage: struct SSOResponse has been removed
  💔 API breakage: struct OAuthResponse has been removed
  💔 API breakage: struct PageParams has been removed
  💔 API breakage: struct ListUsersPaginatedResponse has been removed
  💔 API breakage: struct OAuthClientGrantType has been removed
  💔 API breakage: struct OAuthClientResponseType has been removed
  💔 API breakage: struct OAuthClientType has been removed
  💔 API breakage: struct OAuthClientRegistrationType has been removed
  💔 API breakage: struct OAuthClient has been removed
  💔 API breakage: struct CreateOAuthClientParams has been removed
  💔 API breakage: struct UpdateOAuthClientParams has been removed
  💔 API breakage: struct ListOAuthClientsPaginatedResponse has been removed
  💔 API breakage: struct OAuthAuthorizationClient has been removed
  💔 API breakage: struct OAuthAuthorizationUser has been removed
  💔 API breakage: struct OAuthAuthorizationDetails has been removed
  💔 API breakage: struct OAuthRedirect has been removed
  💔 API breakage: enum OAuthAuthorizationDetailsResponse has been removed
  💔 API breakage: struct OAuthGrant has been removed
  💔 API breakage: struct JWK has been removed
  💔 API breakage: struct JWKS has been removed
  💔 API breakage: struct JWTHeader has been removed
  💔 API breakage: struct JWTClaims has been removed
  💔 API breakage: enum AudienceClaim has been removed
  💔 API breakage: struct JWTClaimsResponse has been removed
  💔 API breakage: struct GetClaimsOptions has been removed
  💔 API breakage: struct MFAWebAuthnEnrollParams has been removed
  💔 API breakage: struct WebAuthnChallengeOptions has been removed
  💔 API breakage: enum WebAuthnChallengeType has been removed
  💔 API breakage: struct WebAuthnChallengeResponseData has been removed
  💔 API breakage: struct PasskeyListItem has been removed
  💔 API breakage: struct PasskeyRegistrationOptions has been removed
  💔 API breakage: struct PasskeyAuthenticationOptions has been removed
  💔 API breakage: var JSONEncoder.goTrue has been removed
  💔 API breakage: var JSONDecoder.goTrue has been removed
  💔 API breakage: struct File has been removed
  💔 API breakage: class FormData has been removed
  💔 API breakage: class StorageApi has been removed
  💔 API breakage: class StorageBucketApi has been removed
  💔 API breakage: struct StorageError has been removed
  💔 API breakage: class StorageFileApi has been removed
  💔 API breakage: struct StorageHTTPSession has been removed
  💔 API breakage: struct StorageClientConfiguration has been removed
  💔 API breakage: class SupabaseStorageClient has been removed
  💔 API breakage: struct SearchOptions has been removed
  💔 API breakage: struct SortBy has been removed
  💔 API breakage: struct FileOptions has been removed
  💔 API breakage: struct SignedURL has been removed
  💔 API breakage: enum SignedURLResult has been removed
  💔 API breakage: struct SignedUploadURL has been removed
  💔 API breakage: struct FileUploadResponse has been removed
  💔 API breakage: struct SignedURLUploadResponse has been removed
  💔 API breakage: struct CreateSignedUploadURLOptions has been removed
  💔 API breakage: struct DestinationOptions has been removed
  💔 API breakage: struct FileObject has been removed
  💔 API breakage: struct FileObjectV2 has been removed
  💔 API breakage: struct Bucket has been removed
  💔 API breakage: struct StorageByteCount has been removed
  💔 API breakage: struct ResizeMode has been removed
  💔 API breakage: struct ImageFormat has been removed
  💔 API breakage: struct SortOrder has been removed
  💔 API breakage: enum DownloadBehavior has been removed
  💔 API breakage: struct BucketOptions has been removed
  💔 API breakage: struct TransformOptions has been removed
  💔 API breakage: var JSONEncoder.defaultStorageEncoder has been removed
  💔 API breakage: var JSONDecoder.defaultStorageDecoder has been removed
  💔 API breakage: enum Defaults has been removed
  💔 API breakage: enum ChannelState has been removed
  💔 API breakage: struct Delegated has been removed
  💔 API breakage: typealias Message has been removed
  💔 API breakage: protocol PhoenixTransport has been removed
  💔 API breakage: protocol PhoenixTransportDelegate has been removed
  💔 API breakage: enum PhoenixTransportReadyState has been removed
  💔 API breakage: class URLSessionTransport has been removed
  💔 API breakage: class Presence has been removed
  💔 API breakage: class Push has been removed
  💔 API breakage: struct ChannelFilter has been removed
  💔 API breakage: enum ChannelResponse has been removed
  💔 API breakage: enum RealtimeListenTypes has been removed
  💔 API breakage: struct RealtimeChannelOptions has been removed
  💔 API breakage: enum RealtimeSubscribeStates has been removed
  💔 API breakage: class RealtimeChannel has been removed
  💔 API breakage: enum SocketError has been removed
  💔 API breakage: typealias Payload has been removed
  💔 API breakage: typealias PayloadClosure has been removed
  💔 API breakage: class RealtimeClient has been removed
  💔 API breakage: struct RealtimeMessage has been removed
  💔 API breakage: var RealtimeClientV2.subscriptions has been removed
  💔 API breakage: struct RealtimeClientV2.Configuration has been removed
  💔 API breakage: typealias RealtimeClientV2.Status has been removed
  💔 API breakage: constructor RealtimeClientV2.init(config:) has been removed
  💔 API breakage: typealias RealtimeChannelV2.Subscription has been removed
  💔 API breakage: typealias RealtimeChannelV2.Status has been removed
  💔 API breakage: class SupabaseClient has been removed
  💔 API breakage: struct SupabaseClientOptions has been removed
  💔 API breakage: typealias URLQueryRepresentable has been removed
  💔 API breakage: class PostgrestBuilder has been removed
  💔 API breakage: class PostgrestClient has been removed
  💔 API breakage: class PostgrestFilterBuilder has been removed
  💔 API breakage: protocol PostgrestFilterValue has been removed
  💔 API breakage: class PostgrestQueryBuilder has been removed
  💔 API breakage: class PostgrestTransformBuilder has been removed
  💔 API breakage: struct PostgrestResponse has been removed
  💔 API breakage: enum CountOption has been removed
  💔 API breakage: enum PostgrestReturningOptions has been removed
  💔 API breakage: enum TextSearchType has been removed
  💔 API breakage: struct ExplainFormat has been removed
  💔 API breakage: struct FetchOptions has been removed
  💔 API breakage: var String.rawValue has been removed
  💔 API breakage: var Int.rawValue has been removed
  💔 API breakage: var Double.rawValue has been removed
  💔 API breakage: var Bool.rawValue has been removed
  💔 API breakage: var UUID.rawValue has been removed
  💔 API breakage: var Date.rawValue has been removed
  💔 API breakage: var Array.rawValue has been removed
  💔 API breakage: var AnyJSON.rawValue has been removed
  💔 API breakage: var Optional.rawValue has been removed
  💔 API breakage: var Dictionary.rawValue has been removed

If this is intentional, please update your PR title or commit message to include:

  • ! after the type (e.g., feat!: remove deprecated method)
  • Or include BREAKING CHANGE: in the commit body

If this is a false positive, you can safely ignore this warning.

…tgrestMacrosPlugin/PostgrestMacros

Rename the compiler-plugin target SupabaseMacros to PostgrestMacrosPlugin and
the public library SupabaseSwiftMacros to PostgrestMacros, and drop the library's
dependency on the Supabase target so it works standalone with bare PostgrestClient.
Removes the SupabaseClient+Typed convenience extension (the only piece needing Supabase).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants