Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2dea4a8
docs: add generated database types design spec
grdsdev Jun 25, 2026
046611a
docs: add implementation plan for generated database types
grdsdev Jun 25, 2026
7b6681e
feat(postgrest): add SupabaseMacros and SupabaseSwiftMacros SPM targets
grdsdev Jun 25, 2026
f9848ea
fix(postgrest): add CompilerPluginSupport import and use .macro() tar…
grdsdev Jun 25, 2026
797b11a
feat(postgrest): add protocol layer and macro declarations
grdsdev Jun 25, 2026
fa77af6
feat(postgrest): add marker macros (@PrimaryKey, @Default, @Column, @…
grdsdev Jun 25, 2026
7af7adc
feat(postgrest): implement @Table macro
grdsdev Jun 25, 2026
1ef0122
feat(postgrest): implement @SelectionOf macro
grdsdev Jun 25, 2026
0fc61f0
feat(postgrest): add typed query builder wrappers
grdsdev Jun 25, 2026
7f18629
fix(postgrest): fix schema handling in typed from() methods
grdsdev Jun 25, 2026
c7c2168
feat(postgrest): add PostgrestClient.from(_ table: T.Type) typed entr…
grdsdev Jun 25, 2026
3fe4e19
fix(postgrest): re-export SupabaseSwiftMacros from umbrella module; f…
grdsdev Jun 25, 2026
aa8fa53
refactor(postgrest): move typed builders to SupabaseSwiftMacros; fix …
grdsdev Jun 25, 2026
5e67508
docs: add @Relationship macro design spec
grdsdev Jun 25, 2026
6fbfaa2
docs: add @Relationship macro implementation plan
grdsdev Jun 25, 2026
f80d9e2
feat(macros): enforce @Relationship only in @SelectionOf; update macr…
grdsdev Jun 25, 2026
8ca9825
feat(macros): implement @Relationship in @SelectionOf with KeyPath FK…
grdsdev Jun 25, 2026
6f533b7
fix(macros): use fixed diagnostic IDs in SelectionOfDiagnostic to avo…
grdsdev Jun 25, 2026
3ecabf2
fix(macros): diagnose let bindings in @Table; update stale @Relations…
grdsdev Jun 25, 2026
475bc8c
fix(macros): update stale comment — let properties are diagnosed, not…
grdsdev Jun 25, 2026
36cce24
feat(examples): update SlackClone to use typed PostgREST API
grdsdev Jun 25, 2026
ab17ffd
feat(macros): add SupabaseClient typed from() overloads
grdsdev Jun 25, 2026
21037fd
fix(macros): remove @attached(extension) from @Table; require explici…
grdsdev Jun 25, 2026
097339e
chore: update workspace Package.resolved with swift-macro-testing pin
grdsdev Jun 25, 2026
9013a4f
fix(build): reconcile new macro targets/tests with main's upcoming fe…
grdsdev Jul 24, 2026
58d7666
chore: remove superpowers planning docs from repo
grdsdev Jul 24, 2026
2073201
revert: move SlackClone typed-postgrest adoption to a stacked PR
grdsdev Jul 24, 2026
6c22425
refactor(postgrest): rename SupabaseMacros/SupabaseSwiftMacros to Pos…
grdsdev Jul 24, 2026
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
33 changes: 12 additions & 21 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 40 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// swift-tools-version:6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import CompilerPluginSupport
import Foundation
import PackageDescription

Expand All @@ -20,6 +21,7 @@ let package = Package(
.library(name: "Realtime", targets: ["Realtime"]),
.library(name: "Storage", targets: ["Storage"]),
.library(name: "Supabase", targets: ["Supabase"]),
.library(name: "PostgrestMacros", targets: ["PostgrestMacros"]),
],
traits: [
// Enables W3C traceparent header propagation using opentelemetry-swift's active span.
Expand All @@ -37,6 +39,10 @@ let package = Package(
.package(url: "https://github.com/WeTransfer/Mocker", from: "3.0.0"),
.package(url: "https://github.com/21-DOT-DEV/swift-secp256k1.git", from: "0.23.2"),
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.10.0"),
// Lower bound matches the package's minimum Swift version (5.10 → 510.x).
// Upper bound matches swift-macro-testing 0.6.x compatibility ceiling.
.package(url: "https://github.com/swiftlang/swift-syntax", "510.0.0"..<"605.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.0"),
],
targets: [
.target(
Expand Down Expand Up @@ -136,6 +142,7 @@ let package = Package(
"Helpers",
"Mocker",
"PostgREST",
"PostgrestMacros",
"TestHelpers",
],
exclude: [
Expand Down Expand Up @@ -239,6 +246,28 @@ let package = Package(
"Mocker",
]
),
.macro(
name: "PostgrestMacrosPlugin",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
]
),
.target(
name: "PostgrestMacros",
dependencies: [
.target(name: "PostgrestMacrosPlugin"),
"PostgREST",
]
),
.testTarget(
name: "PostgrestMacrosTests",
dependencies: [
.target(name: "PostgrestMacrosPlugin"),
.target(name: "PostgrestMacros"),
.product(name: "MacroTesting", package: "swift-macro-testing"),
]
),
]
)

Expand All @@ -263,10 +292,19 @@ for target in package.targets {
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("ImmutableWeakCaptures"),
.enableUpcomingFeature("InferIsolatedConformances"),
.enableUpcomingFeature("InternalImportsByDefault"),
.enableUpcomingFeature("MemberImportVisibility"),
]

// The `PostgrestMacrosPlugin` compiler-plugin target must declare its macro types
// `public` to satisfy SwiftSyntax's `CompilerPlugin`/`PeerMacro` protocols,
// which is incompatible with `InternalImportsByDefault` (a public conformance
// to a protocol from an internally-imported module is rejected). Compiler
// plugins run at build time and ship no distributable API, so the
// import-visibility features gain nothing there — enable them everywhere else.
if target.name != "PostgrestMacrosPlugin" {
swiftSettings.append(.enableUpcomingFeature("InternalImportsByDefault"))
swiftSettings.append(.enableUpcomingFeature("MemberImportVisibility"))
}

// The `Realtime` target hosts the legacy pre-async/await Phoenix client under
// `Deprecated/`, which predates Swift concurrency and isn't safe under Swift 6's
// strict checking. Keep it on the Swift 5 language mode so it keeps compiling
Expand Down
Empty file.
78 changes: 78 additions & 0 deletions Sources/PostgrestMacros/Macros.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/// Marks a struct as a PostgREST table.
///
/// Synthesizes:
/// - `static let tableName`, `schema`, `selectString = "*"`
/// - `static func columnName<V>(for:) -> String`
/// - Nested `Insert` struct (excluded when readOnly: true)
/// - Nested `Update` struct (excluded when readOnly: true)
/// - `CodingKeys` enum with snake_case mapping
///
/// **Important:** Due to a Swift compiler limitation, the protocol conformance
/// (`TableRepresentable` or `ReadOnlyTableRepresentable`) must be declared explicitly
/// on the struct — the macro provides the required implementations.
///
/// - Parameters:
/// - tableName: The PostgREST table or view name.
/// - schema: The PostgreSQL schema (default: "public").
/// - readOnly: Pass true for views — omits Insert/Update; conform to ReadOnlyTableRepresentable.
@attached(
member,
names: named(CodingKeys), named(tableName), named(schema), named(selectString),
named(columnName), named(Insert), named(Update))
public macro Table(
_ tableName: String,
schema: String = "public",
readOnly: Bool = false
) = #externalMacro(module: "PostgrestMacrosPlugin", type: "TableMacro")

/// Marks a struct as a partial column projection of table T.
///
/// Synthesizes:
/// - `SelectionRepresentable` conformance
/// - `static var selectString: String` — computed from field names, resolved at runtime
/// for nested SelectionRepresentable fields (e.g. `"id,title,profile(\(Profile.selectString))"`)
/// - `CodingKeys` enum with snake_case mapping
///
/// Field names must match column or relationship names on T. Type mismatches are caught
/// at decode time via Decodable. Compile-time cross-type validation is a future enhancement.
///
/// - Parameter table: The parent TableRepresentable type, e.g. `Todo.self`.
@attached(member, names: named(CodingKeys))
@attached(
extension, conformances: SelectionRepresentable, names: named(selectString))
public macro SelectionOf(_ table: Any.Type) =
#externalMacro(
module: "PostgrestMacrosPlugin", type: "SelectionOfMacro")

/// Marks a stored property as the table primary key.
/// Excluded from the synthesized Insert and Update types.
@attached(peer)
public macro PrimaryKey() =
#externalMacro(
module: "PostgrestMacrosPlugin", type: "PrimaryKeyMacro")

/// Marks a stored property as having a database-side default value.
/// The property becomes Optional with `= nil` in the synthesized Insert type.
@attached(peer)
public macro Default() =
#externalMacro(
module: "PostgrestMacrosPlugin", type: "DefaultMacro")

/// Overrides the snake_case-derived column name for a stored property.
/// - Parameter name: The exact PostgREST column name.
@attached(peer)
public macro Column(_ name: String) =
#externalMacro(
module: "PostgrestMacrosPlugin", type: "ColumnMacro")

/// Declares a foreign-key join in a `@SelectionOf` struct.
/// Not allowed in `@Table` structs — the table row type has no embedded relationships.
///
/// The referenced table type is inferred from the field's type annotation
/// (Optional and Array wrappers are unwrapped automatically).
///
/// - Parameter keyPath: Key path to the FK column on the owning table.
/// Use the explicit root form `\Message.senderId` to identify which table owns the FK.
@attached(peer)
public macro Relationship(_ keyPath: AnyKeyPath) =
#externalMacro(module: "PostgrestMacrosPlugin", type: "RelationshipMacro")
27 changes: 27 additions & 0 deletions Sources/PostgrestMacros/PostgrestClient+Typed.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// PostgrestClient+Typed.swift
// PostgrestMacros
//
// Created by Guilherme Souza on 24/06/25.
//

public import PostgREST

extension PostgrestClient {
/// Returns a typed query builder for the given table.
/// The table name and schema are taken from the type's TableRepresentable conformance.
public func from<Table: TableRepresentable>(
_ table: Table.Type
) -> TypedPostgrestQueryBuilder<Table> {
let client = Table.schema == "public" ? self : self.schema(Table.schema)
return TypedPostgrestQueryBuilder(underlying: client.from(Table.tableName))
}

/// Returns a read-only typed query builder for a view.
public func from<Table: ReadOnlyTableRepresentable>(
_ table: Table.Type
) -> TypedReadOnlyQueryBuilder<Table> {
let client = Table.schema == "public" ? self : self.schema(Table.schema)
return TypedReadOnlyQueryBuilder(underlying: client.from(Table.tableName))
}
}
24 changes: 24 additions & 0 deletions Sources/PostgrestMacros/Protocols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Foundation

/// Conformance synthesized by @SelectionOf or @Table.
/// Carries the PostgREST column select expression and is Decodable.
public protocol SelectionRepresentable: Decodable {
static var selectString: String { get }
}

/// Conformance synthesized by @Table(readOnly: true) — for views.
/// Shared base for both read-only and read-write tables so that
/// TypedPostgrestFilterBuilder can be constrained to this single protocol.
public protocol ReadOnlyTableRepresentable: SelectionRepresentable {
static var tableName: String { get }
static var schema: String { get }
static func columnName<V>(for keyPath: KeyPath<Self, V>) -> String
}

/// Conformance synthesized by @Table on a read-write table.
/// Refines ReadOnlyTableRepresentable by adding Insert and Update associated types.
/// TypedPostgrestQueryBuilder<Table: TableRepresentable> exposes insert/update/delete.
public protocol TableRepresentable: ReadOnlyTableRepresentable {
associatedtype Insert: Encodable
associatedtype Update: Encodable
}
Loading
Loading