@depup/supabase__realtime-js 2.99.3-depup.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 +32 -0
- package/changes.json +14 -0
- package/dist/main/RealtimeChannel.d.ts +370 -0
- package/dist/main/RealtimeChannel.d.ts.map +1 -0
- package/dist/main/RealtimeChannel.js +644 -0
- package/dist/main/RealtimeChannel.js.map +1 -0
- package/dist/main/RealtimeClient.d.ts +238 -0
- package/dist/main/RealtimeClient.d.ts.map +1 -0
- package/dist/main/RealtimeClient.js +870 -0
- package/dist/main/RealtimeClient.js.map +1 -0
- package/dist/main/RealtimePresence.d.ts +76 -0
- package/dist/main/RealtimePresence.d.ts.map +1 -0
- package/dist/main/RealtimePresence.js +237 -0
- package/dist/main/RealtimePresence.js.map +1 -0
- package/dist/main/index.d.ts +6 -0
- package/dist/main/index.d.ts.map +1 -0
- package/dist/main/index.js +18 -0
- package/dist/main/index.js.map +1 -0
- package/dist/main/lib/constants.d.ts +39 -0
- package/dist/main/lib/constants.d.ts.map +1 -0
- package/dist/main/lib/constants.js +48 -0
- package/dist/main/lib/constants.js.map +1 -0
- package/dist/main/lib/push.d.ts +48 -0
- package/dist/main/lib/push.d.ts.map +1 -0
- package/dist/main/lib/push.js +102 -0
- package/dist/main/lib/push.js.map +1 -0
- package/dist/main/lib/serializer.d.ts +33 -0
- package/dist/main/lib/serializer.d.ts.map +1 -0
- package/dist/main/lib/serializer.js +155 -0
- package/dist/main/lib/serializer.js.map +1 -0
- package/dist/main/lib/timer.d.ts +22 -0
- package/dist/main/lib/timer.d.ts.map +1 -0
- package/dist/main/lib/timer.js +39 -0
- package/dist/main/lib/timer.js.map +1 -0
- package/dist/main/lib/transformers.d.ts +109 -0
- package/dist/main/lib/transformers.d.ts.map +1 -0
- package/dist/main/lib/transformers.js +241 -0
- package/dist/main/lib/transformers.js.map +1 -0
- package/dist/main/lib/version.d.ts +2 -0
- package/dist/main/lib/version.d.ts.map +1 -0
- package/dist/main/lib/version.js +11 -0
- package/dist/main/lib/version.js.map +1 -0
- package/dist/main/lib/websocket-factory.d.ts +81 -0
- package/dist/main/lib/websocket-factory.d.ts.map +1 -0
- package/dist/main/lib/websocket-factory.js +131 -0
- package/dist/main/lib/websocket-factory.js.map +1 -0
- package/dist/module/RealtimeChannel.d.ts +370 -0
- package/dist/module/RealtimeChannel.d.ts.map +1 -0
- package/dist/module/RealtimeChannel.js +639 -0
- package/dist/module/RealtimeChannel.js.map +1 -0
- package/dist/module/RealtimeClient.d.ts +238 -0
- package/dist/module/RealtimeClient.d.ts.map +1 -0
- package/dist/module/RealtimeClient.js +866 -0
- package/dist/module/RealtimeClient.js.map +1 -0
- package/dist/module/RealtimePresence.d.ts +76 -0
- package/dist/module/RealtimePresence.d.ts.map +1 -0
- package/dist/module/RealtimePresence.js +233 -0
- package/dist/module/RealtimePresence.js.map +1 -0
- package/dist/module/index.d.ts +6 -0
- package/dist/module/index.d.ts.map +1 -0
- package/dist/module/index.js +6 -0
- package/dist/module/index.js.map +1 -0
- package/dist/module/lib/constants.d.ts +39 -0
- package/dist/module/lib/constants.d.ts.map +1 -0
- package/dist/module/lib/constants.js +45 -0
- package/dist/module/lib/constants.js.map +1 -0
- package/dist/module/lib/push.d.ts +48 -0
- package/dist/module/lib/push.d.ts.map +1 -0
- package/dist/module/lib/push.js +99 -0
- package/dist/module/lib/push.js.map +1 -0
- package/dist/module/lib/serializer.d.ts +33 -0
- package/dist/module/lib/serializer.d.ts.map +1 -0
- package/dist/module/lib/serializer.js +152 -0
- package/dist/module/lib/serializer.js.map +1 -0
- package/dist/module/lib/timer.d.ts +22 -0
- package/dist/module/lib/timer.d.ts.map +1 -0
- package/dist/module/lib/timer.js +36 -0
- package/dist/module/lib/timer.js.map +1 -0
- package/dist/module/lib/transformers.d.ts +109 -0
- package/dist/module/lib/transformers.d.ts.map +1 -0
- package/dist/module/lib/transformers.js +229 -0
- package/dist/module/lib/transformers.js.map +1 -0
- package/dist/module/lib/version.d.ts +2 -0
- package/dist/module/lib/version.d.ts.map +1 -0
- package/dist/module/lib/version.js +8 -0
- package/dist/module/lib/version.js.map +1 -0
- package/dist/module/lib/websocket-factory.d.ts +81 -0
- package/dist/module/lib/websocket-factory.d.ts.map +1 -0
- package/dist/module/lib/websocket-factory.js +127 -0
- package/dist/module/lib/websocket-factory.js.map +1 -0
- package/dist/tsconfig.module.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +85 -0
- package/src/RealtimeChannel.ts +1039 -0
- package/src/RealtimeClient.ts +1023 -0
- package/src/RealtimePresence.ts +346 -0
- package/src/index.ts +53 -0
- package/src/lib/constants.ts +49 -0
- package/src/lib/push.ts +121 -0
- package/src/lib/serializer.ts +203 -0
- package/src/lib/timer.ts +43 -0
- package/src/lib/transformers.ts +270 -0
- package/src/lib/version.ts +7 -0
- package/src/lib/websocket-factory.ts +192 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// This file draws heavily from https://github.com/phoenixframework/phoenix/commit/cf098e9cf7a44ee6479d31d911a97d3c7430c6fe
|
|
2
|
+
// License: https://github.com/phoenixframework/phoenix/blob/master/LICENSE.md
|
|
3
|
+
export type Msg<T> = {
|
|
4
|
+
join_ref?: string | null
|
|
5
|
+
ref?: string | null
|
|
6
|
+
topic: string
|
|
7
|
+
event: string
|
|
8
|
+
payload: T
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default class Serializer {
|
|
12
|
+
HEADER_LENGTH = 1
|
|
13
|
+
USER_BROADCAST_PUSH_META_LENGTH = 6
|
|
14
|
+
KINDS = { userBroadcastPush: 3, userBroadcast: 4 }
|
|
15
|
+
BINARY_ENCODING = 0
|
|
16
|
+
JSON_ENCODING = 1
|
|
17
|
+
BROADCAST_EVENT = 'broadcast'
|
|
18
|
+
|
|
19
|
+
allowedMetadataKeys: string[] = []
|
|
20
|
+
|
|
21
|
+
constructor(allowedMetadataKeys?: string[] | null) {
|
|
22
|
+
this.allowedMetadataKeys = allowedMetadataKeys ?? []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
encode(msg: Msg<{ [key: string]: any }>, callback: (result: ArrayBuffer | string) => any) {
|
|
26
|
+
if (
|
|
27
|
+
msg.event === this.BROADCAST_EVENT &&
|
|
28
|
+
!(msg.payload instanceof ArrayBuffer) &&
|
|
29
|
+
typeof msg.payload.event === 'string'
|
|
30
|
+
) {
|
|
31
|
+
return callback(
|
|
32
|
+
this._binaryEncodeUserBroadcastPush(msg as Msg<{ event: string } & { [key: string]: any }>)
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]
|
|
37
|
+
return callback(JSON.stringify(payload))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private _binaryEncodeUserBroadcastPush(message: Msg<{ event: string } & { [key: string]: any }>) {
|
|
41
|
+
if (this._isArrayBuffer(message.payload?.payload)) {
|
|
42
|
+
return this._encodeBinaryUserBroadcastPush(message)
|
|
43
|
+
} else {
|
|
44
|
+
return this._encodeJsonUserBroadcastPush(message)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private _encodeBinaryUserBroadcastPush(message: Msg<{ event: string } & { [key: string]: any }>) {
|
|
49
|
+
const userPayload = message.payload?.payload ?? new ArrayBuffer(0)
|
|
50
|
+
return this._encodeUserBroadcastPush(message, this.BINARY_ENCODING, userPayload)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private _encodeJsonUserBroadcastPush(message: Msg<{ event: string } & { [key: string]: any }>) {
|
|
54
|
+
const userPayload = message.payload?.payload ?? {}
|
|
55
|
+
const encoder = new TextEncoder()
|
|
56
|
+
const encodedUserPayload = encoder.encode(JSON.stringify(userPayload)).buffer
|
|
57
|
+
return this._encodeUserBroadcastPush(message, this.JSON_ENCODING, encodedUserPayload)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private _encodeUserBroadcastPush(
|
|
61
|
+
message: Msg<{ event: string } & { [key: string]: any }>,
|
|
62
|
+
encodingType: number,
|
|
63
|
+
encodedPayload: ArrayBuffer
|
|
64
|
+
) {
|
|
65
|
+
const topic = message.topic
|
|
66
|
+
const ref = message.ref ?? ''
|
|
67
|
+
const joinRef = message.join_ref ?? ''
|
|
68
|
+
const userEvent = message.payload.event
|
|
69
|
+
|
|
70
|
+
// Filter metadata based on allowed keys
|
|
71
|
+
const rest = this.allowedMetadataKeys
|
|
72
|
+
? this._pick(message.payload, this.allowedMetadataKeys)
|
|
73
|
+
: {}
|
|
74
|
+
|
|
75
|
+
const metadata = Object.keys(rest).length === 0 ? '' : JSON.stringify(rest)
|
|
76
|
+
|
|
77
|
+
// Validate lengths don't exceed uint8 max value (255)
|
|
78
|
+
if (joinRef.length > 255) {
|
|
79
|
+
throw new Error(`joinRef length ${joinRef.length} exceeds maximum of 255`)
|
|
80
|
+
}
|
|
81
|
+
if (ref.length > 255) {
|
|
82
|
+
throw new Error(`ref length ${ref.length} exceeds maximum of 255`)
|
|
83
|
+
}
|
|
84
|
+
if (topic.length > 255) {
|
|
85
|
+
throw new Error(`topic length ${topic.length} exceeds maximum of 255`)
|
|
86
|
+
}
|
|
87
|
+
if (userEvent.length > 255) {
|
|
88
|
+
throw new Error(`userEvent length ${userEvent.length} exceeds maximum of 255`)
|
|
89
|
+
}
|
|
90
|
+
if (metadata.length > 255) {
|
|
91
|
+
throw new Error(`metadata length ${metadata.length} exceeds maximum of 255`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const metaLength =
|
|
95
|
+
this.USER_BROADCAST_PUSH_META_LENGTH +
|
|
96
|
+
joinRef.length +
|
|
97
|
+
ref.length +
|
|
98
|
+
topic.length +
|
|
99
|
+
userEvent.length +
|
|
100
|
+
metadata.length
|
|
101
|
+
|
|
102
|
+
const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength)
|
|
103
|
+
let view = new DataView(header)
|
|
104
|
+
let offset = 0
|
|
105
|
+
|
|
106
|
+
view.setUint8(offset++, this.KINDS.userBroadcastPush) // kind
|
|
107
|
+
view.setUint8(offset++, joinRef.length)
|
|
108
|
+
view.setUint8(offset++, ref.length)
|
|
109
|
+
view.setUint8(offset++, topic.length)
|
|
110
|
+
view.setUint8(offset++, userEvent.length)
|
|
111
|
+
view.setUint8(offset++, metadata.length)
|
|
112
|
+
view.setUint8(offset++, encodingType)
|
|
113
|
+
Array.from(joinRef, (char) => view.setUint8(offset++, char.charCodeAt(0)))
|
|
114
|
+
Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0)))
|
|
115
|
+
Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0)))
|
|
116
|
+
Array.from(userEvent, (char) => view.setUint8(offset++, char.charCodeAt(0)))
|
|
117
|
+
Array.from(metadata, (char) => view.setUint8(offset++, char.charCodeAt(0)))
|
|
118
|
+
|
|
119
|
+
var combined = new Uint8Array(header.byteLength + encodedPayload.byteLength)
|
|
120
|
+
combined.set(new Uint8Array(header), 0)
|
|
121
|
+
combined.set(new Uint8Array(encodedPayload), header.byteLength)
|
|
122
|
+
|
|
123
|
+
return combined.buffer
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
decode(rawPayload: ArrayBuffer | string, callback: Function) {
|
|
127
|
+
if (this._isArrayBuffer(rawPayload)) {
|
|
128
|
+
let result = this._binaryDecode(rawPayload as ArrayBuffer)
|
|
129
|
+
return callback(result)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (typeof rawPayload === 'string') {
|
|
133
|
+
const jsonPayload = JSON.parse(rawPayload)
|
|
134
|
+
const [join_ref, ref, topic, event, payload] = jsonPayload
|
|
135
|
+
return callback({ join_ref, ref, topic, event, payload })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return callback({})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private _binaryDecode(buffer: ArrayBuffer) {
|
|
142
|
+
const view = new DataView(buffer)
|
|
143
|
+
const kind = view.getUint8(0)
|
|
144
|
+
const decoder = new TextDecoder()
|
|
145
|
+
switch (kind) {
|
|
146
|
+
case this.KINDS.userBroadcast:
|
|
147
|
+
return this._decodeUserBroadcast(buffer, view, decoder)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private _decodeUserBroadcast(
|
|
152
|
+
buffer: ArrayBuffer,
|
|
153
|
+
view: DataView,
|
|
154
|
+
decoder: TextDecoder
|
|
155
|
+
): {
|
|
156
|
+
join_ref: null
|
|
157
|
+
ref: null
|
|
158
|
+
topic: string
|
|
159
|
+
event: string
|
|
160
|
+
payload: { [key: string]: any }
|
|
161
|
+
} {
|
|
162
|
+
const topicSize = view.getUint8(1)
|
|
163
|
+
const userEventSize = view.getUint8(2)
|
|
164
|
+
const metadataSize = view.getUint8(3)
|
|
165
|
+
const payloadEncoding = view.getUint8(4)
|
|
166
|
+
|
|
167
|
+
let offset = this.HEADER_LENGTH + 4
|
|
168
|
+
const topic = decoder.decode(buffer.slice(offset, offset + topicSize))
|
|
169
|
+
offset = offset + topicSize
|
|
170
|
+
const userEvent = decoder.decode(buffer.slice(offset, offset + userEventSize))
|
|
171
|
+
offset = offset + userEventSize
|
|
172
|
+
const metadata = decoder.decode(buffer.slice(offset, offset + metadataSize))
|
|
173
|
+
offset = offset + metadataSize
|
|
174
|
+
|
|
175
|
+
const payload = buffer.slice(offset, buffer.byteLength)
|
|
176
|
+
const parsedPayload =
|
|
177
|
+
payloadEncoding === this.JSON_ENCODING ? JSON.parse(decoder.decode(payload)) : payload
|
|
178
|
+
|
|
179
|
+
const data: { [key: string]: any } = {
|
|
180
|
+
type: this.BROADCAST_EVENT,
|
|
181
|
+
event: userEvent,
|
|
182
|
+
payload: parsedPayload,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Metadata is optional and always JSON encoded
|
|
186
|
+
if (metadataSize > 0) {
|
|
187
|
+
data['meta'] = JSON.parse(metadata)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { join_ref: null, ref: null, topic: topic, event: this.BROADCAST_EVENT, payload: data }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private _isArrayBuffer(buffer: any): boolean {
|
|
194
|
+
return buffer instanceof ArrayBuffer || buffer?.constructor?.name === 'ArrayBuffer'
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private _pick(obj: Record<string, any> | null | undefined, keys: string[]): Record<string, any> {
|
|
198
|
+
if (!obj || typeof obj !== 'object') {
|
|
199
|
+
return {}
|
|
200
|
+
}
|
|
201
|
+
return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)))
|
|
202
|
+
}
|
|
203
|
+
}
|
package/src/lib/timer.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a timer that accepts a `timerCalc` function to perform calculated timeout retries, such as exponential backoff.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* let reconnectTimer = new Timer(() => this.connect(), function(tries){
|
|
6
|
+
* return [1000, 5000, 10000][tries - 1] || 10000
|
|
7
|
+
* })
|
|
8
|
+
* reconnectTimer.scheduleTimeout() // fires after 1000
|
|
9
|
+
* reconnectTimer.scheduleTimeout() // fires after 5000
|
|
10
|
+
* reconnectTimer.reset()
|
|
11
|
+
* reconnectTimer.scheduleTimeout() // fires after 1000
|
|
12
|
+
*/
|
|
13
|
+
export default class Timer {
|
|
14
|
+
timer: number | undefined = undefined
|
|
15
|
+
tries: number = 0
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
public callback: Function,
|
|
19
|
+
public timerCalc: Function
|
|
20
|
+
) {
|
|
21
|
+
this.callback = callback
|
|
22
|
+
this.timerCalc = timerCalc
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
reset() {
|
|
26
|
+
this.tries = 0
|
|
27
|
+
clearTimeout(this.timer)
|
|
28
|
+
this.timer = undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Cancels any previous scheduleTimeout and schedules callback
|
|
32
|
+
scheduleTimeout() {
|
|
33
|
+
clearTimeout(this.timer)
|
|
34
|
+
|
|
35
|
+
this.timer = <any>setTimeout(
|
|
36
|
+
() => {
|
|
37
|
+
this.tries = this.tries + 1
|
|
38
|
+
this.callback()
|
|
39
|
+
},
|
|
40
|
+
this.timerCalc(this.tries + 1)
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers to convert the change Payload into native JS types.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Adapted from epgsql (src/epgsql_binary.erl), this module licensed under
|
|
6
|
+
// 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE
|
|
7
|
+
|
|
8
|
+
export enum PostgresTypes {
|
|
9
|
+
abstime = 'abstime',
|
|
10
|
+
bool = 'bool',
|
|
11
|
+
date = 'date',
|
|
12
|
+
daterange = 'daterange',
|
|
13
|
+
float4 = 'float4',
|
|
14
|
+
float8 = 'float8',
|
|
15
|
+
int2 = 'int2',
|
|
16
|
+
int4 = 'int4',
|
|
17
|
+
int4range = 'int4range',
|
|
18
|
+
int8 = 'int8',
|
|
19
|
+
int8range = 'int8range',
|
|
20
|
+
json = 'json',
|
|
21
|
+
jsonb = 'jsonb',
|
|
22
|
+
money = 'money',
|
|
23
|
+
numeric = 'numeric',
|
|
24
|
+
oid = 'oid',
|
|
25
|
+
reltime = 'reltime',
|
|
26
|
+
text = 'text',
|
|
27
|
+
time = 'time',
|
|
28
|
+
timestamp = 'timestamp',
|
|
29
|
+
timestamptz = 'timestamptz',
|
|
30
|
+
timetz = 'timetz',
|
|
31
|
+
tsrange = 'tsrange',
|
|
32
|
+
tstzrange = 'tstzrange',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type Columns = {
|
|
36
|
+
name: string // the column name. eg: "user_id"
|
|
37
|
+
type: string // the column type. eg: "uuid"
|
|
38
|
+
flags?: string[] // any special flags for the column. eg: ["key"]
|
|
39
|
+
type_modifier?: number // the type modifier. eg: 4294967295
|
|
40
|
+
}[]
|
|
41
|
+
|
|
42
|
+
type BaseValue = null | string | number | boolean
|
|
43
|
+
type RecordValue = BaseValue | BaseValue[]
|
|
44
|
+
|
|
45
|
+
type Record = {
|
|
46
|
+
[key: string]: RecordValue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Takes an array of columns and an object of string values then converts each string value
|
|
51
|
+
* to its mapped type.
|
|
52
|
+
*
|
|
53
|
+
* @param {{name: String, type: String}[]} columns
|
|
54
|
+
* @param {Object} record
|
|
55
|
+
* @param {Object} options The map of various options that can be applied to the mapper
|
|
56
|
+
* @param {Array} options.skipTypes The array of types that should not be converted
|
|
57
|
+
*
|
|
58
|
+
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
|
|
59
|
+
* //=>{ first_name: 'Paul', age: 33 }
|
|
60
|
+
*/
|
|
61
|
+
export const convertChangeData = (
|
|
62
|
+
columns: Columns,
|
|
63
|
+
record: Record | null,
|
|
64
|
+
options: { skipTypes?: string[] } = {}
|
|
65
|
+
): Record => {
|
|
66
|
+
const skipTypes = options.skipTypes ?? []
|
|
67
|
+
|
|
68
|
+
if (!record) {
|
|
69
|
+
return {}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Object.keys(record).reduce((acc, rec_key) => {
|
|
73
|
+
acc[rec_key] = convertColumn(rec_key, columns, record, skipTypes)
|
|
74
|
+
return acc
|
|
75
|
+
}, {} as Record)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Converts the value of an individual column.
|
|
80
|
+
*
|
|
81
|
+
* @param {String} columnName The column that you want to convert
|
|
82
|
+
* @param {{name: String, type: String}[]} columns All of the columns
|
|
83
|
+
* @param {Object} record The map of string values
|
|
84
|
+
* @param {Array} skipTypes An array of types that should not be converted
|
|
85
|
+
* @return {object} Useless information
|
|
86
|
+
*
|
|
87
|
+
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, [])
|
|
88
|
+
* //=> 33
|
|
89
|
+
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, ['int4'])
|
|
90
|
+
* //=> "33"
|
|
91
|
+
*/
|
|
92
|
+
export const convertColumn = (
|
|
93
|
+
columnName: string,
|
|
94
|
+
columns: Columns,
|
|
95
|
+
record: Record,
|
|
96
|
+
skipTypes: string[]
|
|
97
|
+
): RecordValue => {
|
|
98
|
+
const column = columns.find((x) => x.name === columnName)
|
|
99
|
+
const colType = column?.type
|
|
100
|
+
const value = record[columnName]
|
|
101
|
+
|
|
102
|
+
if (colType && !skipTypes.includes(colType)) {
|
|
103
|
+
return convertCell(colType, value)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return noop(value)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* If the value of the cell is `null`, returns null.
|
|
111
|
+
* Otherwise converts the string value to the correct type.
|
|
112
|
+
* @param {String} type A postgres column type
|
|
113
|
+
* @param {String} value The cell value
|
|
114
|
+
*
|
|
115
|
+
* @example convertCell('bool', 't')
|
|
116
|
+
* //=> true
|
|
117
|
+
* @example convertCell('int8', '10')
|
|
118
|
+
* //=> 10
|
|
119
|
+
* @example convertCell('_int4', '{1,2,3,4}')
|
|
120
|
+
* //=> [1,2,3,4]
|
|
121
|
+
*/
|
|
122
|
+
export const convertCell = (type: string, value: RecordValue): RecordValue => {
|
|
123
|
+
// if data type is an array
|
|
124
|
+
if (type.charAt(0) === '_') {
|
|
125
|
+
const dataType = type.slice(1, type.length)
|
|
126
|
+
return toArray(value, dataType)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If not null, convert to correct type.
|
|
130
|
+
switch (type) {
|
|
131
|
+
case PostgresTypes.bool:
|
|
132
|
+
return toBoolean(value)
|
|
133
|
+
case PostgresTypes.float4:
|
|
134
|
+
case PostgresTypes.float8:
|
|
135
|
+
case PostgresTypes.int2:
|
|
136
|
+
case PostgresTypes.int4:
|
|
137
|
+
case PostgresTypes.int8:
|
|
138
|
+
case PostgresTypes.numeric:
|
|
139
|
+
case PostgresTypes.oid:
|
|
140
|
+
return toNumber(value)
|
|
141
|
+
case PostgresTypes.json:
|
|
142
|
+
case PostgresTypes.jsonb:
|
|
143
|
+
return toJson(value)
|
|
144
|
+
case PostgresTypes.timestamp:
|
|
145
|
+
return toTimestampString(value) // Format to be consistent with PostgREST
|
|
146
|
+
case PostgresTypes.abstime: // To allow users to cast it based on Timezone
|
|
147
|
+
case PostgresTypes.date: // To allow users to cast it based on Timezone
|
|
148
|
+
case PostgresTypes.daterange:
|
|
149
|
+
case PostgresTypes.int4range:
|
|
150
|
+
case PostgresTypes.int8range:
|
|
151
|
+
case PostgresTypes.money:
|
|
152
|
+
case PostgresTypes.reltime: // To allow users to cast it based on Timezone
|
|
153
|
+
case PostgresTypes.text:
|
|
154
|
+
case PostgresTypes.time: // To allow users to cast it based on Timezone
|
|
155
|
+
case PostgresTypes.timestamptz: // To allow users to cast it based on Timezone
|
|
156
|
+
case PostgresTypes.timetz: // To allow users to cast it based on Timezone
|
|
157
|
+
case PostgresTypes.tsrange:
|
|
158
|
+
case PostgresTypes.tstzrange:
|
|
159
|
+
return noop(value)
|
|
160
|
+
default:
|
|
161
|
+
// Return the value for remaining types
|
|
162
|
+
return noop(value)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const noop = (value: RecordValue): RecordValue => {
|
|
167
|
+
return value
|
|
168
|
+
}
|
|
169
|
+
export const toBoolean = (value: RecordValue): RecordValue => {
|
|
170
|
+
switch (value) {
|
|
171
|
+
case 't':
|
|
172
|
+
return true
|
|
173
|
+
case 'f':
|
|
174
|
+
return false
|
|
175
|
+
default:
|
|
176
|
+
return value
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export const toNumber = (value: RecordValue): RecordValue => {
|
|
180
|
+
if (typeof value === 'string') {
|
|
181
|
+
const parsedValue = parseFloat(value)
|
|
182
|
+
if (!Number.isNaN(parsedValue)) {
|
|
183
|
+
return parsedValue
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return value
|
|
187
|
+
}
|
|
188
|
+
export const toJson = (value: RecordValue): RecordValue => {
|
|
189
|
+
if (typeof value === 'string') {
|
|
190
|
+
try {
|
|
191
|
+
return JSON.parse(value)
|
|
192
|
+
} catch {
|
|
193
|
+
return value
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return value
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Converts a Postgres Array into a native JS array
|
|
201
|
+
*
|
|
202
|
+
* @example toArray('{}', 'int4')
|
|
203
|
+
* //=> []
|
|
204
|
+
* @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', 'daterange')
|
|
205
|
+
* //=> ['[2021-01-01,2021-12-31)', '(2021-01-01,2021-12-32]']
|
|
206
|
+
* @example toArray([1,2,3,4], 'int4')
|
|
207
|
+
* //=> [1,2,3,4]
|
|
208
|
+
*/
|
|
209
|
+
export const toArray = (value: RecordValue, type: string): RecordValue => {
|
|
210
|
+
if (typeof value !== 'string') {
|
|
211
|
+
return value
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const lastIdx = value.length - 1
|
|
215
|
+
const closeBrace = value[lastIdx]
|
|
216
|
+
const openBrace = value[0]
|
|
217
|
+
|
|
218
|
+
// Confirm value is a Postgres array by checking curly brackets
|
|
219
|
+
if (openBrace === '{' && closeBrace === '}') {
|
|
220
|
+
let arr
|
|
221
|
+
const valTrim = value.slice(1, lastIdx)
|
|
222
|
+
|
|
223
|
+
// TODO: find a better solution to separate Postgres array data
|
|
224
|
+
try {
|
|
225
|
+
arr = JSON.parse('[' + valTrim + ']')
|
|
226
|
+
} catch (_) {
|
|
227
|
+
// WARNING: splitting on comma does not cover all edge cases
|
|
228
|
+
arr = valTrim ? valTrim.split(',') : []
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return arr.map((val: BaseValue) => convertCell(type, val))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return value
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T'
|
|
239
|
+
* See https://github.com/supabase/supabase/issues/18
|
|
240
|
+
*
|
|
241
|
+
* @example toTimestampString('2019-09-10 00:00:00')
|
|
242
|
+
* //=> '2019-09-10T00:00:00'
|
|
243
|
+
*/
|
|
244
|
+
export const toTimestampString = (value: RecordValue): RecordValue => {
|
|
245
|
+
if (typeof value === 'string') {
|
|
246
|
+
return value.replace(' ', 'T')
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return value
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const httpEndpointURL = (socketUrl: string): string => {
|
|
253
|
+
const wsUrl = new URL(socketUrl)
|
|
254
|
+
|
|
255
|
+
wsUrl.protocol = wsUrl.protocol.replace(/^ws/i, 'http')
|
|
256
|
+
|
|
257
|
+
wsUrl.pathname = wsUrl.pathname
|
|
258
|
+
.replace(/\/+$/, '') // remove all trailing slashes
|
|
259
|
+
.replace(/\/socket\/websocket$/i, '') // remove the socket/websocket path
|
|
260
|
+
.replace(/\/socket$/i, '') // remove the socket path
|
|
261
|
+
.replace(/\/websocket$/i, '') // remove the websocket path
|
|
262
|
+
|
|
263
|
+
if (wsUrl.pathname === '' || wsUrl.pathname === '/') {
|
|
264
|
+
wsUrl.pathname = '/api/broadcast'
|
|
265
|
+
} else {
|
|
266
|
+
wsUrl.pathname = wsUrl.pathname + '/api/broadcast'
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return wsUrl.href
|
|
270
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Generated automatically during releases by scripts/update-version-files.ts
|
|
2
|
+
// This file provides runtime access to the package version for:
|
|
3
|
+
// - HTTP request headers (e.g., X-Client-Info header for API requests)
|
|
4
|
+
// - Debugging and support (identifying which version is running)
|
|
5
|
+
// - Telemetry and logging (version reporting in errors/analytics)
|
|
6
|
+
// - Ensuring build artifacts match the published package version
|
|
7
|
+
export const version = '2.99.3'
|