Skip to content

Commit 3dc9b82

Browse files
authored
feat(realtime): add predefined filters instead of regular String (#669)
* Add RealTme filter * Fix typo * RealtimeFilterValue & RealtimeFilter * Create proper files for FilerValue & Filter * Add tests * Clean up * Add tests * Renaming * Update Tests
1 parent a1b5271 commit 3dc9b82

5 files changed

+236
-0
lines changed

Sources/Realtime/RealtimeChannel+AsyncAwait.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ extension RealtimeChannelV2 {
2424
}
2525

2626
/// Listen for postgres changes in a channel.
27+
public func postgresChange(
28+
_: InsertAction.Type,
29+
schema: String = "public",
30+
table: String? = nil,
31+
filter: RealtimePostgresFilter? = nil
32+
) -> AsyncStream<InsertAction> {
33+
postgresChange(event: .insert, schema: schema, table: table, filter: filter?.value)
34+
.compactErase()
35+
}
36+
37+
/// Listen for postgres changes in a channel.
38+
@available(
39+
*,
40+
deprecated,
41+
message: "Use the new filter syntax instead."
42+
)
43+
@_disfavoredOverload
2744
public func postgresChange(
2845
_: InsertAction.Type,
2946
schema: String = "public",
@@ -35,6 +52,23 @@ extension RealtimeChannelV2 {
3552
}
3653

3754
/// Listen for postgres changes in a channel.
55+
public func postgresChange(
56+
_: UpdateAction.Type,
57+
schema: String = "public",
58+
table: String? = nil,
59+
filter: RealtimePostgresFilter? = nil
60+
) -> AsyncStream<UpdateAction> {
61+
postgresChange(event: .update, schema: schema, table: table, filter: filter?.value)
62+
.compactErase()
63+
}
64+
65+
/// Listen for postgres changes in a channel.
66+
@available(
67+
*,
68+
deprecated,
69+
message: "Use the new filter syntax instead."
70+
)
71+
@_disfavoredOverload
3872
public func postgresChange(
3973
_: UpdateAction.Type,
4074
schema: String = "public",
@@ -46,6 +80,23 @@ extension RealtimeChannelV2 {
4680
}
4781

4882
/// Listen for postgres changes in a channel.
83+
public func postgresChange(
84+
_: DeleteAction.Type,
85+
schema: String = "public",
86+
table: String? = nil,
87+
filter: RealtimePostgresFilter? = nil
88+
) -> AsyncStream<DeleteAction> {
89+
postgresChange(event: .delete, schema: schema, table: table, filter: filter?.value)
90+
.compactErase()
91+
}
92+
93+
/// Listen for postgres changes in a channel.
94+
@available(
95+
*,
96+
deprecated,
97+
message: "Use the new filter syntax instead."
98+
)
99+
@_disfavoredOverload
49100
public func postgresChange(
50101
_: DeleteAction.Type,
51102
schema: String = "public",
@@ -57,6 +108,22 @@ extension RealtimeChannelV2 {
57108
}
58109

59110
/// Listen for postgres changes in a channel.
111+
public func postgresChange(
112+
_: AnyAction.Type,
113+
schema: String = "public",
114+
table: String? = nil,
115+
filter: RealtimePostgresFilter? = nil
116+
) -> AsyncStream<AnyAction> {
117+
postgresChange(event: .all, schema: schema, table: table, filter: filter?.value)
118+
}
119+
120+
/// Listen for postgres changes in a channel.
121+
@available(
122+
*,
123+
deprecated,
124+
message: "Use the new filter syntax instead."
125+
)
126+
@_disfavoredOverload
60127
public func postgresChange(
61128
_: AnyAction.Type,
62129
schema: String = "public",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//
2+
// RealtimePostgresFilter.swift
3+
// Supabase
4+
//
5+
// Created by Lucas Abijmil on 19/02/2025.
6+
//
7+
8+
/// A filter that can be used in Realtime.
9+
public enum RealtimePostgresFilter {
10+
case eq(_ column: String, value: any RealtimePostgresFilterValue)
11+
case neq(_ column: String, value: any RealtimePostgresFilterValue)
12+
case gt(_ column: String, value: any RealtimePostgresFilterValue)
13+
case gte(_ column: String, value: any RealtimePostgresFilterValue)
14+
case lt(_ column: String, value: any RealtimePostgresFilterValue)
15+
case lte(_ column: String, value: any RealtimePostgresFilterValue)
16+
case `in`(_ column: String, values: [any RealtimePostgresFilterValue])
17+
18+
var value: String {
19+
switch self {
20+
case let .eq(column, value):
21+
return "\(column)=eq.\(value.rawValue)"
22+
case let .neq(column, value):
23+
return "\(column)=neq.\(value.rawValue)"
24+
case let .gt(column, value):
25+
return "\(column)=gt.\(value.rawValue)"
26+
case let .gte(column, value):
27+
return "\(column)=gte.\(value.rawValue)"
28+
case let .lt(column, value):
29+
return "\(column)=lt.\(value.rawValue)"
30+
case let .lte(column, value):
31+
return "\(column)=lte.\(value.rawValue)"
32+
case let .in(column, values):
33+
return "\(column)=in.(\(values.map(\.rawValue).joined(separator: ",")))"
34+
}
35+
}
36+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// RealtimePostgresFilterValue.swift
3+
// Supabase
4+
//
5+
// Created by Lucas Abijmil on 19/02/2025.
6+
//
7+
8+
import Foundation
9+
10+
/// A value that can be used to filter Realtime changes in a channel.
11+
public protocol RealtimePostgresFilterValue {
12+
var rawValue: String { get }
13+
}
14+
15+
extension String: RealtimePostgresFilterValue {
16+
public var rawValue: String { self }
17+
}
18+
19+
extension Int: RealtimePostgresFilterValue {
20+
public var rawValue: String { "\(self)" }
21+
}
22+
23+
extension Double: RealtimePostgresFilterValue {
24+
public var rawValue: String { "\(self)" }
25+
}
26+
27+
extension Bool: RealtimePostgresFilterValue {
28+
public var rawValue: String { "\(self)" }
29+
}
30+
31+
extension UUID: RealtimePostgresFilterValue {
32+
public var rawValue: String { uuidString }
33+
}
34+
35+
extension Date: RealtimePostgresFilterValue {
36+
public var rawValue: String {
37+
let formatter = ISO8601DateFormatter()
38+
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
39+
return formatter.string(from: self)
40+
}
41+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//
2+
// RealtimePostgresFilterTests.swift
3+
// Supabase
4+
//
5+
// Created by Lucas Abijmil on 20/02/2025.
6+
//
7+
8+
import XCTest
9+
@testable import Realtime
10+
11+
final class RealtimePostgresFilterTests: XCTestCase {
12+
13+
func testEq() {
14+
let value = "value"
15+
let column = "column"
16+
let filter: RealtimePostgresFilter = .eq(column, value: value)
17+
18+
XCTAssertEqual(filter.value, "column=eq.value")
19+
}
20+
21+
func testNeq() {
22+
let value = "value"
23+
let column = "column"
24+
let filter: RealtimePostgresFilter = .neq(column, value: value)
25+
26+
XCTAssertEqual(filter.value, "column=neq.value")
27+
}
28+
29+
func testGt() {
30+
let value = "value"
31+
let column = "column"
32+
let filter: RealtimePostgresFilter = .gt(column, value: value)
33+
34+
XCTAssertEqual(filter.value, "column=gt.value")
35+
}
36+
37+
func testGte() {
38+
let value = "value"
39+
let column = "column"
40+
let filter: RealtimePostgresFilter = .gte(column, value: value)
41+
42+
XCTAssertEqual(filter.value, "column=gte.value")
43+
}
44+
45+
func testLt() {
46+
let value = "value"
47+
let column = "column"
48+
let filter: RealtimePostgresFilter = .lt(column, value: value)
49+
50+
XCTAssertEqual(filter.value, "column=lt.value")
51+
}
52+
53+
func testLte() {
54+
let value = "value"
55+
let column = "column"
56+
let filter: RealtimePostgresFilter = .lte(column, value: value)
57+
58+
XCTAssertEqual(filter.value, "column=lte.value")
59+
}
60+
61+
func testIn() {
62+
let values = ["value1", "value2"]
63+
let column = "column"
64+
let filter: RealtimePostgresFilter = .in(column, values: values)
65+
66+
XCTAssertEqual(filter.value, "column=in.(value1,value2)")
67+
}
68+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// RealtimePostgresFilterValueTests.swift
3+
// Supabase
4+
//
5+
// Created by Lucas Abijmil on 19/02/2025.
6+
//
7+
8+
import XCTest
9+
@testable import Realtime
10+
11+
final class RealtimePostgresFilterValueTests: XCTestCase {
12+
func testUUID() {
13+
XCTAssertEqual(
14+
UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!.rawValue,
15+
"E621E1F8-C36C-495A-93FC-0C247A3E6E5F")
16+
}
17+
18+
func testDate() {
19+
XCTAssertEqual(
20+
Date(timeIntervalSince1970: 1_737_465_985).rawValue,
21+
"2025-01-21T13:26:25.000Z"
22+
)
23+
}
24+
}

0 commit comments

Comments
 (0)