@supabase/realtime-js 2.108.3-canary.2 → 2.109.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 (44) 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/RealtimePostgresFilterBuilder.d.ts +132 -0
  7. package/dist/main/RealtimePostgresFilterBuilder.d.ts.map +1 -0
  8. package/dist/main/RealtimePostgresFilterBuilder.js +165 -0
  9. package/dist/main/RealtimePostgresFilterBuilder.js.map +1 -0
  10. package/dist/main/index.d.ts +2 -2
  11. package/dist/main/index.d.ts.map +1 -1
  12. package/dist/main/index.js +3 -1
  13. package/dist/main/index.js.map +1 -1
  14. package/dist/main/lib/constants.d.ts +2 -2
  15. package/dist/main/lib/constants.d.ts.map +1 -1
  16. package/dist/main/lib/version.d.ts +1 -1
  17. package/dist/main/lib/version.d.ts.map +1 -1
  18. package/dist/main/lib/version.js +1 -1
  19. package/dist/main/lib/version.js.map +1 -1
  20. package/dist/module/RealtimeChannel.d.ts +89 -3
  21. package/dist/module/RealtimeChannel.d.ts.map +1 -1
  22. package/dist/module/RealtimeChannel.js +13 -0
  23. package/dist/module/RealtimeChannel.js.map +1 -1
  24. package/dist/module/RealtimePostgresFilterBuilder.d.ts +132 -0
  25. package/dist/module/RealtimePostgresFilterBuilder.d.ts.map +1 -0
  26. package/dist/module/RealtimePostgresFilterBuilder.js +160 -0
  27. package/dist/module/RealtimePostgresFilterBuilder.js.map +1 -0
  28. package/dist/module/index.d.ts +2 -2
  29. package/dist/module/index.d.ts.map +1 -1
  30. package/dist/module/index.js +2 -2
  31. package/dist/module/index.js.map +1 -1
  32. package/dist/module/lib/constants.d.ts +2 -2
  33. package/dist/module/lib/constants.d.ts.map +1 -1
  34. package/dist/module/lib/version.d.ts +1 -1
  35. package/dist/module/lib/version.d.ts.map +1 -1
  36. package/dist/module/lib/version.js +1 -1
  37. package/dist/module/lib/version.js.map +1 -1
  38. package/dist/tsconfig.module.tsbuildinfo +1 -1
  39. package/dist/tsconfig.tsbuildinfo +1 -1
  40. package/package.json +1 -1
  41. package/src/RealtimeChannel.ts +116 -4
  42. package/src/RealtimePostgresFilterBuilder.ts +253 -0
  43. package/src/index.ts +8 -0
  44. package/src/lib/version.ts +1 -1
@@ -13,6 +13,14 @@ import { normalizeChannelError } from './lib/normalizeChannelError'
13
13
  import ChannelAdapter from './phoenix/channelAdapter'
14
14
  import { ChannelBindingCallback, ChannelOnErrorCallback } from './phoenix/types'
15
15
  import type { Timer } from './phoenix/types'
16
+ import { RealtimePostgresFilterBuilder } from './RealtimePostgresFilterBuilder'
17
+ import type { RealtimePostgresChangesFilterOperator } from './RealtimePostgresFilterBuilder'
18
+
19
+ export type { RealtimePostgresChangesFilterOperator } from './RealtimePostgresFilterBuilder'
20
+ export {
21
+ RealtimePostgresFilterBuilder,
22
+ postgresChangesFilter,
23
+ } from './RealtimePostgresFilterBuilder'
16
24
 
17
25
  type ReplayOption = {
18
26
  since: number
@@ -25,8 +33,18 @@ export type RealtimeChannelOptions = {
25
33
  * self option enables client to receive message it broadcast
26
34
  * ack option instructs server to acknowledge that broadcast message was received
27
35
  * replay option instructs server to replay broadcast messages
36
+ * replication_ready option instructs the server to emit a `system` event once the
37
+ * Postgres replication connection backing this channel is established and ready to
38
+ * stream changes. Listen for it with `channel.on('system', {}, (payload) => ...)`;
39
+ * the payload's `status` is `'ok'` (`message: 'Replication connection established'`)
40
+ * on success or `'error'` if the connection is not ready in time.
28
41
  */
29
- broadcast?: { self?: boolean; ack?: boolean; replay?: ReplayOption }
42
+ broadcast?: {
43
+ self?: boolean
44
+ ack?: boolean
45
+ replay?: ReplayOption
46
+ replication_ready?: boolean
47
+ }
30
48
  /**
31
49
  * key option is used to track presence payload across clients
32
50
  */
@@ -120,13 +138,67 @@ export type RealtimePostgresChangesFilter<T extends `${REALTIME_POSTGRES_CHANGES
120
138
  */
121
139
  table?: string
122
140
  /**
123
- * Receive database changes when filter is matched.
141
+ * Receive database changes only when the filter is matched.
142
+ *
143
+ * A filter is a `column=operator.value` expression, e.g. `id=eq.1` or
144
+ * `title=like.%foo%`. See {@link RealtimePostgresChangesFilterOperator} for
145
+ * the available operators.
146
+ *
147
+ * Multiple filters can be combined with commas; they are applied as an `AND`
148
+ * condition: `filter: 'id=gt.0,id=lt.100'`.
149
+ *
150
+ * Any operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.
151
+ *
152
+ * The server splits conditions on commas outside quotes/parentheses. To
153
+ * include a reserved character (`,`, `(`, `)`) in a value, wrap it in double
154
+ * quotes PostgREST-style: `name=eq."a,b"`. The {@link RealtimePostgresFilterBuilder}
155
+ * does this quoting for you.
156
+ *
157
+ * Instead of a raw string you can pass a {@link RealtimePostgresFilterBuilder}
158
+ * (via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the
159
+ * SDK serializes it to a string automatically.
160
+ */
161
+ filter?: string | RealtimePostgresFilterBuilder
162
+ /**
163
+ * Restrict the change payload to a subset of columns instead of receiving the
164
+ * full row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)
165
+ * and the data transferred per event.
166
+ *
167
+ * The listed columns must be selectable by the subscribing role.
168
+ *
169
+ * @example
170
+ * channel.on('postgres_changes', {
171
+ * event: '*',
172
+ * schema: 'public',
173
+ * table: 'users',
174
+ * select: ['id', 'first_name'],
175
+ * }, (payload) => {
176
+ * // payload.new only contains { id, first_name }
177
+ * })
124
178
  */
125
- filter?: string
179
+ select?: string[]
126
180
  }
127
181
 
128
182
  export type RealtimeChannelSendResponse = 'ok' | 'timed out' | 'error' | (string & {})
129
183
 
184
+ /**
185
+ * Payload of a `system` event emitted by the server.
186
+ *
187
+ * Most notably, when a channel is created with `config.broadcast.replication_ready: true`,
188
+ * the server sends one of these once the Postgres replication connection is ready
189
+ * (`status: 'ok'`) or fails to become ready in time (`status: 'error'`).
190
+ */
191
+ export type RealtimeSystemPayload = {
192
+ /** The extension that produced the message, e.g. `'system'` or `'postgres_changes'`. */
193
+ extension: 'system' | 'postgres_changes' | (string & {})
194
+ /** `'ok'` on success, `'error'` on failure. */
195
+ status: 'ok' | 'error' | (string & {})
196
+ /** Human-readable description, e.g. `'Replication connection established'`. */
197
+ message: string
198
+ /** The channel (sub)topic the message refers to. */
199
+ channel: string
200
+ }
201
+
130
202
  export enum REALTIME_POSTGRES_CHANGES_LISTEN_EVENT {
131
203
  ALL = '*',
132
204
  INSERT = 'INSERT',
@@ -157,6 +229,7 @@ type PostgresChangesFilters = {
157
229
  schema?: string
158
230
  table?: string
159
231
  filter?: string
232
+ select?: string[]
160
233
  }[]
161
234
  }
162
235
 
@@ -569,6 +642,31 @@ export default class RealtimeChannel {
569
642
  payload: RealtimeBroadcastDeletePayload<T>
570
643
  }) => void
571
644
  ): RealtimeChannel
645
+ /**
646
+ * Listen for `system` events on this channel.
647
+ *
648
+ * The payload follows the {@link RealtimeSystemPayload} shape. Opt in to the replication-ready
649
+ * notification with `config.broadcast.replication_ready: true` when creating the channel, then
650
+ * watch for `payload.status === 'ok'` to know the Postgres replication connection is ready.
651
+ *
652
+ * @example Know when the replication connection is ready
653
+ * ```js
654
+ * const channel = supabase.channel('room1', {
655
+ * config: { broadcast: { replication_ready: true } },
656
+ * })
657
+ *
658
+ * channel
659
+ * .on('postgres_changes', { event: '*', schema: 'public', table: 'messages' }, (payload) => {
660
+ * console.log('Change received!', payload)
661
+ * })
662
+ * .on('system', {}, (payload) => {
663
+ * if (payload.extension === 'system' && payload.status === 'ok') {
664
+ * console.log('Replication connection is ready:', payload.message)
665
+ * }
666
+ * })
667
+ * .subscribe()
668
+ * ```
669
+ */
572
670
  on<T extends { [key: string]: any }>(
573
671
  type: `${REALTIME_LISTEN_TYPES.SYSTEM}`,
574
672
  filter: {},
@@ -735,7 +833,7 @@ export default class RealtimeChannel {
735
833
  */
736
834
  on(
737
835
  type: `${REALTIME_LISTEN_TYPES}`,
738
- filter: { event: string; [key: string]: string },
836
+ filter: { event: string; [key: string]: any },
739
837
  callback: (payload: any) => void
740
838
  ): RealtimeChannel {
741
839
  const stateCheck = this.channelAdapter.isJoined() || this.channelAdapter.isJoining()
@@ -1007,6 +1105,20 @@ export default class RealtimeChannel {
1007
1105
  _on(type: string, filter: { [key: string]: any }, callback: ChannelBindingCallback) {
1008
1106
  const typeLower = type.toLocaleLowerCase()
1009
1107
 
1108
+ // Serialize a postgres_changes filter builder into its string form so the
1109
+ // rest of the pipeline (join payload, server binding match) sees a string.
1110
+ // Duck-type `build()` in addition to `instanceof` so a builder constructed
1111
+ // against a duplicate copy of the package (separate module realm) still works.
1112
+ const filterValue = filter?.filter
1113
+ if (
1114
+ filterValue instanceof RealtimePostgresFilterBuilder ||
1115
+ (typeof filterValue === 'object' &&
1116
+ filterValue !== null &&
1117
+ typeof (filterValue as { build?: unknown }).build === 'function')
1118
+ ) {
1119
+ filter = { ...filter, filter: (filterValue as RealtimePostgresFilterBuilder).build() }
1120
+ }
1121
+
1010
1122
  const ref = this.channelAdapter.on(type, callback)
1011
1123
 
1012
1124
  const binding: Binding = {
@@ -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.109.0'