feat(postgrest): add type-safe PostgREST query layer via Swift Macros - #1036
feat(postgrest): add type-safe PostgREST query layer via Swift Macros#1036grdsdev wants to merge 28 commits into
Conversation
Coverage Report for CI Build 30086116632Coverage remained the same at 83.648%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
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.
…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).
b18aeb5 to
9013a4f
Compare
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
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.
|
…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).
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