Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(realtime): add predefined filters instead of regular String #669

Merged
merged 9 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
67 changes: 67 additions & 0 deletions Sources/Realtime/RealtimeChannel+AsyncAwait.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ extension RealtimeChannelV2 {
}

/// Listen for postgres changes in a channel.
public func postgresChange(
_: InsertAction.Type,
schema: String = "public",
table: String? = nil,
filter: RealtimePostgresFilter? = nil
) -> AsyncStream<InsertAction> {
postgresChange(event: .insert, schema: schema, table: table, filter: filter?.value)
.compactErase()
}

/// Listen for postgres changes in a channel.
@available(
*,
deprecated,
message: "Use the new filter syntax instead."
)
@_disfavoredOverload
public func postgresChange(
_: InsertAction.Type,
schema: String = "public",
Expand All @@ -35,6 +52,23 @@ extension RealtimeChannelV2 {
}

/// Listen for postgres changes in a channel.
public func postgresChange(
_: UpdateAction.Type,
schema: String = "public",
table: String? = nil,
filter: RealtimePostgresFilter? = nil
) -> AsyncStream<UpdateAction> {
postgresChange(event: .update, schema: schema, table: table, filter: filter?.value)
.compactErase()
}

/// Listen for postgres changes in a channel.
@available(
*,
deprecated,
message: "Use the new filter syntax instead."
)
@_disfavoredOverload
public func postgresChange(
_: UpdateAction.Type,
schema: String = "public",
Expand All @@ -46,6 +80,23 @@ extension RealtimeChannelV2 {
}

/// Listen for postgres changes in a channel.
public func postgresChange(
_: DeleteAction.Type,
schema: String = "public",
table: String? = nil,
filter: RealtimePostgresFilter? = nil
) -> AsyncStream<DeleteAction> {
postgresChange(event: .delete, schema: schema, table: table, filter: filter?.value)
.compactErase()
}

/// Listen for postgres changes in a channel.
@available(
*,
deprecated,
message: "Use the new filter syntax instead."
)
@_disfavoredOverload
public func postgresChange(
_: DeleteAction.Type,
schema: String = "public",
Expand All @@ -57,6 +108,22 @@ extension RealtimeChannelV2 {
}

/// Listen for postgres changes in a channel.
public func postgresChange(
_: AnyAction.Type,
schema: String = "public",
table: String? = nil,
filter: RealtimePostgresFilter? = nil
) -> AsyncStream<AnyAction> {
postgresChange(event: .all, schema: schema, table: table, filter: filter?.value)
}

/// Listen for postgres changes in a channel.
@available(
*,
deprecated,
message: "Use the new filter syntax instead."
)
@_disfavoredOverload
public func postgresChange(
_: AnyAction.Type,
schema: String = "public",
Expand Down
36 changes: 36 additions & 0 deletions Sources/Realtime/RealtimePostgresFilter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// RealtimePostgresFilter.swift
// Supabase
//
// Created by Lucas Abijmil on 19/02/2025.
//

/// A filter that can be used in Realtime.
public enum RealtimePostgresFilter {
case eq(_ column: String, value: any RealtimePostgresFilterValue)
case neq(_ column: String, value: any RealtimePostgresFilterValue)
case gt(_ column: String, value: any RealtimePostgresFilterValue)
case gte(_ column: String, value: any RealtimePostgresFilterValue)
case lt(_ column: String, value: any RealtimePostgresFilterValue)
case lte(_ column: String, value: any RealtimePostgresFilterValue)
case `in`(_ column: String, values: [any RealtimePostgresFilterValue])

var value: String {
switch self {
case let .eq(column, value):
return "\(column)=eq.\(value.rawValue)"
case let .neq(column, value):
return "\(column)=neq.\(value.rawValue)"
case let .gt(column, value):
return "\(column)=gt.\(value.rawValue)"
case let .gte(column, value):
return "\(column)=gte.\(value.rawValue)"
case let .lt(column, value):
return "\(column)=lt.\(value.rawValue)"
case let .lte(column, value):
return "\(column)=lte.\(value.rawValue)"
case let .in(column, values):
return "\(column)=in.(\(values.map(\.rawValue).joined(separator: ",")))"
}
}
}
41 changes: 41 additions & 0 deletions Sources/Realtime/RealtimePostgresFilterValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// RealtimePostgresFilterValue.swift
// Supabase
//
// Created by Lucas Abijmil on 19/02/2025.
//

import Foundation

/// A value that can be used to filter Realtime changes in a channel.
public protocol RealtimePostgresFilterValue {
var rawValue: String { get }
}

extension String: RealtimePostgresFilterValue {
public var rawValue: String { self }
}

extension Int: RealtimePostgresFilterValue {
public var rawValue: String { "\(self)" }
}

extension Double: RealtimePostgresFilterValue {
public var rawValue: String { "\(self)" }
}

extension Bool: RealtimePostgresFilterValue {
public var rawValue: String { "\(self)" }
}

extension UUID: RealtimePostgresFilterValue {
public var rawValue: String { uuidString }
}

extension Date: RealtimePostgresFilterValue {
public var rawValue: String {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter.string(from: self)
}
}
68 changes: 68 additions & 0 deletions Tests/RealtimeTests/RealtimePostgresFilterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// RealtimePostgresFilterTests.swift
// Supabase
//
// Created by Lucas Abijmil on 20/02/2025.
//

import XCTest
@testable import Realtime

final class RealtimePostgresFilterTests: XCTestCase {

func testEq() {
let value = "value"
let column = "column"
let filter: RealtimePostgresFilter = .eq(column, value: value)

XCTAssertEqual(filter.value, "column=eq.value")
}

func testNeq() {
let value = "value"
let column = "column"
let filter: RealtimePostgresFilter = .neq(column, value: value)

XCTAssertEqual(filter.value, "column=neq.value")
}

func testGt() {
let value = "value"
let column = "column"
let filter: RealtimePostgresFilter = .gt(column, value: value)

XCTAssertEqual(filter.value, "column=gt.value")
}

func testGte() {
let value = "value"
let column = "column"
let filter: RealtimePostgresFilter = .gte(column, value: value)

XCTAssertEqual(filter.value, "column=gte.value")
}

func testLt() {
let value = "value"
let column = "column"
let filter: RealtimePostgresFilter = .lt(column, value: value)

XCTAssertEqual(filter.value, "column=lt.value")
}

func testLte() {
let value = "value"
let column = "column"
let filter: RealtimePostgresFilter = .lte(column, value: value)

XCTAssertEqual(filter.value, "column=lte.value")
}

func testIn() {
let values = ["value1", "value2"]
let column = "column"
let filter: RealtimePostgresFilter = .in(column, values: values)

XCTAssertEqual(filter.value, "column=in.(value1,value2)")
}
}
24 changes: 24 additions & 0 deletions Tests/RealtimeTests/RealtimePostgresFilterValueTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// RealtimePostgresFilterValueTests.swift
// Supabase
//
// Created by Lucas Abijmil on 19/02/2025.
//

import XCTest
@testable import Realtime

final class RealtimePostgresFilterValueTests: XCTestCase {
func testUUID() {
XCTAssertEqual(
UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!.rawValue,
"E621E1F8-C36C-495A-93FC-0C247A3E6E5F")
}

func testDate() {
XCTAssertEqual(
Date(timeIntervalSince1970: 1_737_465_985).rawValue,
"2025-01-21T13:26:25.000Z"
)
}
}
Loading