feat(postgrest): add type-safe PostgREST query layer via Swift Macros#1036
Draft
grdsdev wants to merge 24 commits into
Draft
feat(postgrest): add type-safe PostgREST query layer via Swift Macros#1036grdsdev wants to merge 24 commits into
grdsdev wants to merge 24 commits into
Conversation
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)
…o signature to AnyKeyPath
… and disambiguation select strings
…id MessageID collision
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
… silently dropped
- 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.
Coverage Report for CI Build 28173234122Coverage increased (+0.2%) to 81.177%Details
Uncovered ChangesNo uncovered changes found. Coverage Regressions6 previously-covered lines in 2 files lost coverage.
Coverage Stats
💛 - Coveralls |
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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,@Tablesynthesizes fullTableRepresentableconformance including:InsertandUpdatenested types (with optional fields for@Default/nullable columns;@PrimaryKeyfields excluded from inserts)CodingKeyswith automaticcamelCase→snake_caseconversion (overridable via@Column)columnName(for:)using KeyPath identity for type-safe column lookup@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.@Tableenforces that@Relationshipfields are never declared on it — joins belong exclusively on@SelectionOfstructs.Typed query builders — Four generic wrappers delegate to the existing string-based builders:
TypedPostgrestQueryBuilder<Table: TableRepresentable>— insert, upsert, update, delete, selectTypedReadOnlyQueryBuilder<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 executionEntry point —
PostgrestClient.from(_ table: T.Type)returns the appropriate typed builder based on whetherTconforms toTableRepresentableorReadOnlyTableRepresentable.Usage
Architecture
The typed API lives entirely in
SupabaseSwiftMacros, a separate opt-in library.PostgRESTandSupabasehave no dependency on it, so swift-syntax is only compiled for users who explicitly importSupabaseSwiftMacros.Diagnostics
Non-primitive
@SelectionOffields without@Relationship,@Relationshipon a@Tablefield, andletbindings on@Tableare all compile-time errors with descriptive messages.Testing
swift-macro-testing)snake_casecolumn name translation via inline-snapshot of actual HTTP request URLsTest plan
swift test --filter SupabaseMacrosTests— all 17 expansion/diagnostic tests passswift test --filter PostgRESTTests— all typed builder tests passimport PostgRESTalone (withoutimport SupabaseSwiftMacros) does not pull in swift-syntax@Tablestruct with a@Relationshipfield is a compile error@SelectionOffield without@Relationshipis a compile errorclient.from(ReadOnlyView.self).insert(...)is a compile error