Skip to content

Commit

Permalink
feat(realtime): add predefined filters instead of regular String (#669
Browse files Browse the repository at this point in the history
)

* Add RealTme filter

* Fix typo

* RealtimeFilterValue & RealtimeFilter

* Create proper files for FilerValue & Filter

* Add tests

* Clean up

* Add tests

* Renaming

* Update Tests
  • Loading branch information
LucasAbijmil authored Feb 20, 2025
1 parent a1b5271 commit 3dc9b82
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 0 deletions.
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"
)
}
}

0 comments on commit 3dc9b82

Please sign in to comment.