@supabase/realtime-js 2.108.3-canary.2 → 2.110.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +114 -0
  2. package/dist/main/RealtimeChannel.d.ts +89 -3
  3. package/dist/main/RealtimeChannel.d.ts.map +1 -1
  4. package/dist/main/RealtimeChannel.js +16 -1
  5. package/dist/main/RealtimeChannel.js.map +1 -1
  6. package/dist/main/RealtimeClient.d.ts.map +1 -1
  7. package/dist/main/RealtimeClient.js +0 -13
  8. package/dist/main/RealtimeClient.js.map +1 -1
  9. package/dist/main/RealtimePostgresFilterBuilder.d.ts +132 -0
  10. package/dist/main/RealtimePostgresFilterBuilder.d.ts.map +1 -0
  11. package/dist/main/RealtimePostgresFilterBuilder.js +165 -0
  12. package/dist/main/RealtimePostgresFilterBuilder.js.map +1 -0
  13. package/dist/main/index.d.ts +2 -2
  14. package/dist/main/index.d.ts.map +1 -1
  15. package/dist/main/index.js +3 -1
  16. package/dist/main/index.js.map +1 -1
  17. package/dist/main/lib/constants.d.ts +2 -2
  18. package/dist/main/lib/version.d.ts +1 -1
  19. package/dist/main/lib/version.js +1 -1
  20. package/dist/main/lib/websocket-factory.d.ts +1 -1
  21. package/dist/main/lib/websocket-factory.d.ts.map +1 -1
  22. package/dist/main/lib/websocket-factory.js +5 -22
  23. package/dist/main/lib/websocket-factory.js.map +1 -1
  24. package/dist/module/RealtimeChannel.d.ts +89 -3
  25. package/dist/module/RealtimeChannel.d.ts.map +1 -1
  26. package/dist/module/RealtimeChannel.js +13 -0
  27. package/dist/module/RealtimeChannel.js.map +1 -1
  28. package/dist/module/RealtimeClient.d.ts.map +1 -1
  29. package/dist/module/RealtimeClient.js +0 -13
  30. package/dist/module/RealtimeClient.js.map +1 -1
  31. package/dist/module/RealtimePostgresFilterBuilder.d.ts +132 -0
  32. package/dist/module/RealtimePostgresFilterBuilder.d.ts.map +1 -0
  33. package/dist/module/RealtimePostgresFilterBuilder.js +160 -0
  34. package/dist/module/RealtimePostgresFilterBuilder.js.map +1 -0
  35. package/dist/module/index.d.ts +2 -2
  36. package/dist/module/index.d.ts.map +1 -1
  37. package/dist/module/index.js +2 -2
  38. package/dist/module/index.js.map +1 -1
  39. package/dist/module/lib/constants.d.ts +2 -2
  40. package/dist/module/lib/version.d.ts +1 -1
  41. package/dist/module/lib/version.js +1 -1
  42. package/dist/module/lib/websocket-factory.d.ts +1 -1
  43. package/dist/module/lib/websocket-factory.d.ts.map +1 -1
  44. package/dist/module/lib/websocket-factory.js +5 -22
  45. package/dist/module/lib/websocket-factory.js.map +1 -1
  46. package/dist/tsconfig.module.tsbuildinfo +1 -1
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +2 -2
  49. package/src/RealtimeChannel.ts +116 -4
  50. package/src/RealtimeClient.ts +0 -16
  51. package/src/RealtimePostgresFilterBuilder.ts +253 -0
  52. package/src/index.ts +8 -0
  53. package/src/lib/version.ts +1 -1
  54. package/src/lib/websocket-factory.ts +6 -25
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Comparison operators accepted in a Postgres Changes `filter` string.
3
+ *
4
+ * These mirror the PostgREST operator surface and are evaluated server-side.
5
+ * Any operator can be negated via {@link RealtimePostgresFilterBuilder.not}
6
+ * (the `not.` prefix).
7
+ *
8
+ * This is the subset of PostgREST's `FilterOperator` (see `postgrest-js`) that
9
+ * Realtime Postgres Changes supports. Containment, range and full-text search
10
+ * operators (`cs`, `cd`, `ov`, range ops, `fts`, …) are intentionally not
11
+ * included because the Realtime server does not evaluate them.
12
+ *
13
+ * - `eq`, `neq`, `lt`, `lte`, `gt`, `gte` — comparison
14
+ * - `in` — membership: `status=in.(active,pending)`
15
+ * - `like`, `ilike` — pattern match (case-sensitive / insensitive): `title=like.%foo%`
16
+ * - `is` — `IS` check against `null` / `true` / `false` / `unknown`: `deleted_at=is.null`
17
+ * - `match`, `imatch` — POSIX regex match (`~` / `~*`)
18
+ * - `isdistinct` — NULL-safe inequality (`IS DISTINCT FROM`)
19
+ */
20
+ export type RealtimePostgresChangesFilterOperator =
21
+ | 'eq'
22
+ | 'neq'
23
+ | 'lt'
24
+ | 'lte'
25
+ | 'gt'
26
+ | 'gte'
27
+ | 'in'
28
+ | 'like'
29
+ | 'ilike'
30
+ | 'is'
31
+ | 'match'
32
+ | 'imatch'
33
+ | 'isdistinct'
34
+
35
+ /** Scalar value accepted by a single filter operator. */
36
+ export type RealtimeFilterValue = string | number | boolean | null
37
+
38
+ /** Value accepted by the `is` operator. */
39
+ export type RealtimeIsFilterValue = null | boolean | 'null' | 'true' | 'false' | 'unknown'
40
+
41
+ // Reserved characters that force PostgREST-style quoting: `[,()]` (which the
42
+ // server reads as condition/list delimiters) plus `"`/`\` (escaped inside quotes).
43
+ const PostgrestReservedCharsRegexp = /[,()"\\]/
44
+
45
+ const needsQuoting = (value: string): boolean =>
46
+ PostgrestReservedCharsRegexp.test(value) || value !== value.trim()
47
+
48
+ const quote = (value: string): string => `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
49
+
50
+ const serializeScalar = (value: RealtimeFilterValue): string => {
51
+ const serialized = value === null ? 'null' : String(value)
52
+ return needsQuoting(serialized) ? quote(serialized) : serialized
53
+ }
54
+
55
+ const serializeIsValue = (value: RealtimeIsFilterValue): string =>
56
+ value === null ? 'null' : String(value)
57
+
58
+ // Builds the `operator.value` portion of a filter (everything after `column=`).
59
+ const serialize = (operator: RealtimePostgresChangesFilterOperator, value: unknown): string => {
60
+ if (operator === 'in') {
61
+ const values = Array.isArray(value) ? value : [value]
62
+ if (values.length === 0) {
63
+ throw new Error('Realtime `in` filter requires at least one value.')
64
+ }
65
+ const items = Array.from(new Set(values))
66
+ .map((v) => serializeScalar(v as RealtimeFilterValue))
67
+ .join(',')
68
+ return `in.(${items})`
69
+ }
70
+
71
+ if (operator === 'is') {
72
+ return `is.${serializeIsValue(value as RealtimeIsFilterValue)}`
73
+ }
74
+
75
+ return `${operator}.${serializeScalar(value as RealtimeFilterValue)}`
76
+ }
77
+
78
+ /**
79
+ * Fluent builder for Postgres Changes `filter` strings.
80
+ *
81
+ * Each method appends a single `column=operator.value` condition. Multiple
82
+ * conditions are combined with commas, which the Realtime server applies as an
83
+ * `AND`. Pass an instance straight to `channel.on('postgres_changes', …)` — the
84
+ * SDK serializes it to a string automatically — or call {@link build} to obtain
85
+ * the string yourself.
86
+ *
87
+ * The builder mirrors the `postgrest-js` filter API (`eq`, `neq`, `in`, `like`,
88
+ * `not`, …) for the operators that Realtime supports. Values containing reserved
89
+ * characters (`,`, `(`, `)`, `"`, `\`) — or surrounding whitespace — are
90
+ * automatically double-quoted and escaped the same way PostgREST does, so they
91
+ * survive the server's filter parser; all other values are sent verbatim.
92
+ *
93
+ * The filter is snapshotted when passed to `channel.on(...)`; mutating the
94
+ * builder afterwards does not affect an existing subscription. An empty builder
95
+ * serializes to `''`, which the server treats as "no filter".
96
+ *
97
+ * @example
98
+ * channel.on('postgres_changes', {
99
+ * event: '*',
100
+ * schema: 'public',
101
+ * table: 'users',
102
+ * filter: postgresChangesFilter().eq('id', 1).lt('age', 30), // → 'id=eq.1,age=lt.30'
103
+ * }, (payload) => { ... })
104
+ */
105
+ export class RealtimePostgresFilterBuilder {
106
+ private readonly filters: string[] = []
107
+
108
+ private add(
109
+ column: string,
110
+ operator: RealtimePostgresChangesFilterOperator,
111
+ value: unknown,
112
+ negate = false
113
+ ): this {
114
+ const prefix = negate ? 'not.' : ''
115
+ this.filters.push(`${column}=${prefix}${serialize(operator, value)}`)
116
+ return this
117
+ }
118
+
119
+ /** Match rows where `column` equals `value` (`column=eq.value`). */
120
+ eq(column: string, value: RealtimeFilterValue): this {
121
+ return this.add(column, 'eq', value)
122
+ }
123
+
124
+ /** Match rows where `column` does not equal `value` (`column=neq.value`). */
125
+ neq(column: string, value: RealtimeFilterValue): this {
126
+ return this.add(column, 'neq', value)
127
+ }
128
+
129
+ /** Match rows where `column` is greater than `value` (`column=gt.value`). */
130
+ gt(column: string, value: RealtimeFilterValue): this {
131
+ return this.add(column, 'gt', value)
132
+ }
133
+
134
+ /** Match rows where `column` is greater than or equal to `value` (`column=gte.value`). */
135
+ gte(column: string, value: RealtimeFilterValue): this {
136
+ return this.add(column, 'gte', value)
137
+ }
138
+
139
+ /** Match rows where `column` is less than `value` (`column=lt.value`). */
140
+ lt(column: string, value: RealtimeFilterValue): this {
141
+ return this.add(column, 'lt', value)
142
+ }
143
+
144
+ /** Match rows where `column` is less than or equal to `value` (`column=lte.value`). */
145
+ lte(column: string, value: RealtimeFilterValue): this {
146
+ return this.add(column, 'lte', value)
147
+ }
148
+
149
+ /**
150
+ * Match rows where `column` is one of `values` (`column=in.(a,b,c)`).
151
+ * Requires at least one value; duplicates are removed. An element containing a
152
+ * reserved character is double-quoted (`in.("a,b",c)`), so commas inside an
153
+ * element are preserved. `null` is intentionally not accepted (`IN (null)`
154
+ * never matches in SQL) — use `is`/`not('col','is',null)` for null checks.
155
+ */
156
+ in(column: string, values: ReadonlyArray<string | number | boolean>): this {
157
+ return this.add(column, 'in', values)
158
+ }
159
+
160
+ /** Match rows where `column` matches the case-sensitive `pattern` (`column=like.pattern`). */
161
+ like(column: string, pattern: string): this {
162
+ return this.add(column, 'like', pattern)
163
+ }
164
+
165
+ /** Match rows where `column` matches the case-insensitive `pattern` (`column=ilike.pattern`). */
166
+ ilike(column: string, pattern: string): this {
167
+ return this.add(column, 'ilike', pattern)
168
+ }
169
+
170
+ /** Match rows where `column` matches the POSIX regex `pattern` (`column=match.pattern`). */
171
+ match(column: string, pattern: string): this {
172
+ return this.add(column, 'match', pattern)
173
+ }
174
+
175
+ /** Match rows where `column` matches the case-insensitive POSIX regex `pattern` (`column=imatch.pattern`). */
176
+ imatch(column: string, pattern: string): this {
177
+ return this.add(column, 'imatch', pattern)
178
+ }
179
+
180
+ /**
181
+ * Match rows where `column` `IS` the given value (`column=is.null`).
182
+ * Accepts `null`, a boolean, or the keywords `'null' | 'true' | 'false' | 'unknown'`.
183
+ */
184
+ is(column: string, value: RealtimeIsFilterValue): this {
185
+ return this.add(column, 'is', value)
186
+ }
187
+
188
+ /** Match rows where `column` is distinct from `value` (`column=isdistinct.value`). NULL-safe inequality. */
189
+ isDistinct(column: string, value: RealtimeFilterValue): this {
190
+ return this.add(column, 'isdistinct', value)
191
+ }
192
+
193
+ /**
194
+ * Negate any operator with the `not.` prefix (`column=not.operator.value`).
195
+ * `in` takes an array, `is` takes an `IS` keyword/boolean/null, and every
196
+ * other operator takes a scalar value.
197
+ *
198
+ * @example
199
+ * postgresChangesFilter().not('status', 'in', ['draft', 'archived'])
200
+ * // → status=not.in.(draft,archived)
201
+ * postgresChangesFilter().not('deleted_at', 'is', null)
202
+ * // → deleted_at=not.is.null
203
+ */
204
+ not(column: string, operator: 'in', value: ReadonlyArray<string | number | boolean>): this
205
+ not(column: string, operator: 'is', value: RealtimeIsFilterValue): this
206
+ not(
207
+ column: string,
208
+ operator: Exclude<RealtimePostgresChangesFilterOperator, 'in' | 'is'>,
209
+ value: RealtimeFilterValue
210
+ ): this
211
+ not(
212
+ column: string,
213
+ operator: RealtimePostgresChangesFilterOperator,
214
+ value: RealtimeFilterValue | ReadonlyArray<string | number | boolean>
215
+ ): this {
216
+ return this.add(column, operator, value, true)
217
+ }
218
+
219
+ /**
220
+ * Serialize all conditions into the comma-separated (AND) filter string.
221
+ *
222
+ * Conditions are joined by commas, which the server applies as `AND`. A scalar
223
+ * value (or single `in` element) that contains a reserved character — `,`,
224
+ * `(`, `)`, `"`, `\` — or surrounding whitespace is double-quoted and escaped
225
+ * the way PostgREST does, so commas inside a value are preserved rather than
226
+ * read as a condition boundary.
227
+ */
228
+ build(): string {
229
+ return this.filters.join(',')
230
+ }
231
+
232
+ /** Alias for {@link build}; lets the builder be used wherever a string is expected. */
233
+ toString(): string {
234
+ return this.build()
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Create a {@link RealtimePostgresFilterBuilder} for composing a Postgres
240
+ * Changes `filter`. Conditions are combined with `AND`.
241
+ *
242
+ * @example
243
+ * import { postgresChangesFilter } from '@supabase/realtime-js'
244
+ *
245
+ * channel.on('postgres_changes', {
246
+ * event: 'UPDATE',
247
+ * schema: 'public',
248
+ * table: 'orders',
249
+ * filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'),
250
+ * }, (payload) => { ... })
251
+ */
252
+ export const postgresChangesFilter = (): RealtimePostgresFilterBuilder =>
253
+ new RealtimePostgresFilterBuilder()
package/src/index.ts CHANGED
@@ -8,10 +8,14 @@ import RealtimeChannel, {
8
8
  RealtimeChannelOptions,
9
9
  RealtimeChannelSendResponse,
10
10
  RealtimePostgresChangesFilter,
11
+ RealtimePostgresChangesFilterOperator,
12
+ RealtimePostgresFilterBuilder,
13
+ postgresChangesFilter,
11
14
  RealtimePostgresChangesPayload,
12
15
  RealtimePostgresInsertPayload,
13
16
  RealtimePostgresUpdatePayload,
14
17
  RealtimePostgresDeletePayload,
18
+ RealtimeSystemPayload,
15
19
  REALTIME_LISTEN_TYPES,
16
20
  REALTIME_POSTGRES_CHANGES_LISTEN_EVENT,
17
21
  REALTIME_SUBSCRIBE_STATES,
@@ -34,10 +38,14 @@ export {
34
38
  RealtimeClientOptions,
35
39
  RealtimeMessage,
36
40
  RealtimePostgresChangesFilter,
41
+ RealtimePostgresChangesFilterOperator,
42
+ RealtimePostgresFilterBuilder,
43
+ postgresChangesFilter,
37
44
  RealtimePostgresChangesPayload,
38
45
  RealtimePostgresInsertPayload,
39
46
  RealtimePostgresUpdatePayload,
40
47
  RealtimePostgresDeletePayload,
48
+ RealtimeSystemPayload,
41
49
  RealtimePresenceJoinPayload,
42
50
  RealtimePresenceLeavePayload,
43
51
  RealtimePresenceState,
@@ -4,4 +4,4 @@
4
4
  // - Debugging and support (identifying which version is running)
5
5
  // - Telemetry and logging (version reporting in errors/analytics)
6
6
  // - Ensuring build artifacts match the published package version
7
- export const version = '2.108.3-canary.2'
7
+ export const version = '2.110.0-canary.0'
@@ -38,7 +38,7 @@ export interface WebSocketLike {
38
38
  }
39
39
 
40
40
  export interface WebSocketEnvironment {
41
- type: 'native' | 'ws' | 'cloudflare' | 'unsupported'
41
+ type: 'native' | 'cloudflare' | 'unsupported'
42
42
  /** WebSocket constructor for this environment, if available. */
43
43
  wsConstructor?: typeof WebSocket
44
44
  error?: string
@@ -113,32 +113,13 @@ export class WebSocketFactory {
113
113
  if (_process) {
114
114
  const processVersions = _process['versions']
115
115
  if (processVersions && processVersions['node']) {
116
- // Remove 'v' prefix if present and parse the major version
117
- const versionString = processVersions['node']
118
- const nodeVersion = parseInt(versionString.replace(/^v/, '').split('.')[0])
119
-
120
- // Node.js 22+ should have native WebSocket
121
- if (nodeVersion >= 22) {
122
- // Check if native WebSocket is available (should be in Node.js 22+)
123
- if (typeof globalThis.WebSocket !== 'undefined') {
124
- return { type: 'native', wsConstructor: globalThis.WebSocket }
125
- }
126
- // If not available, user needs to provide it
127
- return {
128
- type: 'unsupported',
129
- error: `Node.js ${nodeVersion} detected but native WebSocket not found.`,
130
- workaround: 'Provide a WebSocket implementation via the transport option.',
131
- }
132
- }
133
-
134
- // Node.js < 22 doesn't have native WebSocket
116
+ // Reaching here means an earlier check did not find a native WebSocket,
117
+ // so this Node.js process is missing the global WebSocket (Node.js 22+).
135
118
  return {
136
119
  type: 'unsupported',
137
- error: `Node.js ${nodeVersion} detected without native WebSocket support.`,
120
+ error: 'Node.js detected but native WebSocket not found.',
138
121
  workaround:
139
- 'For Node.js < 22, install "ws" package and provide it via the transport option:\n' +
140
- 'import ws from "ws"\n' +
141
- 'new RealtimeClient(url, { transport: ws })',
122
+ 'Ensure you are running Node.js 22+ or provide a WebSocket implementation via the transport option.',
142
123
  }
143
124
  }
144
125
  }
@@ -194,7 +175,7 @@ export class WebSocketFactory {
194
175
  public static isWebSocketSupported(): boolean {
195
176
  try {
196
177
  const env = this.detectEnvironment()
197
- return env.type === 'native' || env.type === 'ws'
178
+ return env.type === 'native'
198
179
  } catch {
199
180
  return false
200
181
  }