@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.
- package/README.md +114 -0
- package/dist/main/RealtimeChannel.d.ts +89 -3
- package/dist/main/RealtimeChannel.d.ts.map +1 -1
- package/dist/main/RealtimeChannel.js +16 -1
- package/dist/main/RealtimeChannel.js.map +1 -1
- package/dist/main/RealtimeClient.d.ts.map +1 -1
- package/dist/main/RealtimeClient.js +0 -13
- package/dist/main/RealtimeClient.js.map +1 -1
- package/dist/main/RealtimePostgresFilterBuilder.d.ts +132 -0
- package/dist/main/RealtimePostgresFilterBuilder.d.ts.map +1 -0
- package/dist/main/RealtimePostgresFilterBuilder.js +165 -0
- package/dist/main/RealtimePostgresFilterBuilder.js.map +1 -0
- package/dist/main/index.d.ts +2 -2
- package/dist/main/index.d.ts.map +1 -1
- package/dist/main/index.js +3 -1
- package/dist/main/index.js.map +1 -1
- package/dist/main/lib/constants.d.ts +2 -2
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/main/lib/websocket-factory.d.ts +1 -1
- package/dist/main/lib/websocket-factory.d.ts.map +1 -1
- package/dist/main/lib/websocket-factory.js +5 -22
- package/dist/main/lib/websocket-factory.js.map +1 -1
- package/dist/module/RealtimeChannel.d.ts +89 -3
- package/dist/module/RealtimeChannel.d.ts.map +1 -1
- package/dist/module/RealtimeChannel.js +13 -0
- package/dist/module/RealtimeChannel.js.map +1 -1
- package/dist/module/RealtimeClient.d.ts.map +1 -1
- package/dist/module/RealtimeClient.js +0 -13
- package/dist/module/RealtimeClient.js.map +1 -1
- package/dist/module/RealtimePostgresFilterBuilder.d.ts +132 -0
- package/dist/module/RealtimePostgresFilterBuilder.d.ts.map +1 -0
- package/dist/module/RealtimePostgresFilterBuilder.js +160 -0
- package/dist/module/RealtimePostgresFilterBuilder.js.map +1 -0
- package/dist/module/index.d.ts +2 -2
- package/dist/module/index.d.ts.map +1 -1
- package/dist/module/index.js +2 -2
- package/dist/module/index.js.map +1 -1
- package/dist/module/lib/constants.d.ts +2 -2
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/module/lib/websocket-factory.d.ts +1 -1
- package/dist/module/lib/websocket-factory.d.ts.map +1 -1
- package/dist/module/lib/websocket-factory.js +5 -22
- package/dist/module/lib/websocket-factory.js.map +1 -1
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/RealtimeChannel.ts +116 -4
- package/src/RealtimeClient.ts +0 -16
- package/src/RealtimePostgresFilterBuilder.ts +253 -0
- package/src/index.ts +8 -0
- package/src/lib/version.ts +1 -1
- 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,
|
package/src/lib/version.ts
CHANGED
|
@@ -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.
|
|
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' | '
|
|
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
|
-
//
|
|
117
|
-
|
|
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:
|
|
120
|
+
error: 'Node.js detected but native WebSocket not found.',
|
|
138
121
|
workaround:
|
|
139
|
-
'
|
|
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'
|
|
178
|
+
return env.type === 'native'
|
|
198
179
|
} catch {
|
|
199
180
|
return false
|
|
200
181
|
}
|