@quatrain/cloudwrapper-supabase 1.1.19 → 1.1.20
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 +24 -0
- package/lib/SupabaseCloudWrapper.d.ts +4 -1
- package/lib/SupabaseCloudWrapper.js +51 -12
- package/package.json +3 -3
- package/src/SupabaseCloudWrapper.ts +72 -13
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @quatrain/cloudwrapper-supabase
|
|
2
|
+
|
|
3
|
+
This package provides a Cloud Wrapper adapter for Supabase Edge Functions. It allows you to invoke serverless functions in a consistent way across different BaaS providers.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Invokes Supabase Edge Functions.
|
|
8
|
+
- Consistent interface provided by `@quatrain/cloudwrapper`.
|
|
9
|
+
- Works with both SaaS and self-hosted Supabase instances.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @quatrain/cloudwrapper-supabase @supabase/supabase-js
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { CloudWrapper } from '@quatrain/cloudwrapper'
|
|
21
|
+
import { SupabaseCloudWrapperAdapter } from '@quatrain/cloudwrapper-supabase'
|
|
22
|
+
|
|
23
|
+
// Setup and use the adapter with the CloudWrapper singleton.
|
|
24
|
+
```
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import { AbstractCloudWrapper, DatabaseTriggerType, StorageTriggerType } from '@quatrain/cloudwrapper';
|
|
2
3
|
import { SupabaseClient, RealtimeChannel } from '@supabase/supabase-js';
|
|
3
4
|
import { FileType } from '@quatrain/storage';
|
|
@@ -5,6 +6,7 @@ export type SupabaseParams = {
|
|
|
5
6
|
useEmulator?: boolean;
|
|
6
7
|
url?: string;
|
|
7
8
|
key?: string;
|
|
9
|
+
exitOnDisconnect?: boolean;
|
|
8
10
|
};
|
|
9
11
|
export type Channel = {
|
|
10
12
|
name: string;
|
|
@@ -24,6 +26,8 @@ export declare class SupabaseCloudWrapper extends AbstractCloudWrapper {
|
|
|
24
26
|
protected _supabaseClient: SupabaseClient | undefined;
|
|
25
27
|
protected _realtimeClient: RealtimeChannel | undefined;
|
|
26
28
|
protected _isInitialized: boolean;
|
|
29
|
+
protected _heartbeatOkReceived: boolean;
|
|
30
|
+
protected _connectionTimeout: NodeJS.Timeout | undefined;
|
|
27
31
|
protected _channels: Channels;
|
|
28
32
|
constructor(params: SupabaseParams);
|
|
29
33
|
databaseTrigger(trigger: DatabaseTriggerType): void | {
|
|
@@ -37,7 +41,6 @@ export declare class SupabaseCloudWrapper extends AbstractCloudWrapper {
|
|
|
37
41
|
table: string;
|
|
38
42
|
};
|
|
39
43
|
protected _initialize(): void;
|
|
40
|
-
poll(): void;
|
|
41
44
|
protected _payload2File(payload: {
|
|
42
45
|
[x: string]: any;
|
|
43
46
|
}): FileType | undefined;
|
|
@@ -33,6 +33,7 @@ class SupabaseCloudWrapper extends cloudwrapper_1.AbstractCloudWrapper {
|
|
|
33
33
|
constructor(params) {
|
|
34
34
|
super(params);
|
|
35
35
|
this._isInitialized = false;
|
|
36
|
+
this._heartbeatOkReceived = false;
|
|
36
37
|
this._channels = { database: [], storage: [] };
|
|
37
38
|
this._initialize();
|
|
38
39
|
}
|
|
@@ -86,7 +87,7 @@ class SupabaseCloudWrapper extends cloudwrapper_1.AbstractCloudWrapper {
|
|
|
86
87
|
var _a;
|
|
87
88
|
this._initialize();
|
|
88
89
|
if (typeof trigger.script !== 'function') {
|
|
89
|
-
throw new
|
|
90
|
+
throw new TypeError(`Passed script value is not a function`);
|
|
90
91
|
}
|
|
91
92
|
if (Array.isArray(trigger.event)) {
|
|
92
93
|
const params = trigger.event.forEach((event) => {
|
|
@@ -131,21 +132,59 @@ class SupabaseCloudWrapper extends cloudwrapper_1.AbstractCloudWrapper {
|
|
|
131
132
|
}
|
|
132
133
|
_initialize() {
|
|
133
134
|
if (this._isInitialized === false) {
|
|
134
|
-
|
|
135
|
+
cloudwrapper_1.CloudWrapper.info(`Starting Supabase client with heartbeat monitoring`);
|
|
136
|
+
if (this._connectionTimeout) {
|
|
137
|
+
clearTimeout(this._connectionTimeout);
|
|
138
|
+
}
|
|
139
|
+
this._supabaseClient = (0, supabase_js_1.createClient)(this._params.url, this._params.key, {
|
|
140
|
+
realtime: {
|
|
141
|
+
heartbeatIntervalMs: 5000,
|
|
142
|
+
heartbeatCallback: (status) => {
|
|
143
|
+
switch (status) {
|
|
144
|
+
case 'ok':
|
|
145
|
+
if (!this._heartbeatOkReceived) {
|
|
146
|
+
cloudwrapper_1.CloudWrapper.info(`🛜 Supabase Realtime connection established`);
|
|
147
|
+
if (this._connectionTimeout) {
|
|
148
|
+
clearTimeout(this._connectionTimeout);
|
|
149
|
+
}
|
|
150
|
+
this._heartbeatOkReceived = true;
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
case 'timeout':
|
|
154
|
+
cloudwrapper_1.CloudWrapper.warn(`⚠️ Supabase Realtime connection timed out`);
|
|
155
|
+
break;
|
|
156
|
+
case 'disconnected':
|
|
157
|
+
// Reconnect only if exitOnDisconnect is explicitly set to false.
|
|
158
|
+
if (this._params.exitOnDisconnect === false) {
|
|
159
|
+
cloudwrapper_1.CloudWrapper.warn(`❌ Supabase connection lost. Attempting to reconnect...`);
|
|
160
|
+
this._isInitialized = false;
|
|
161
|
+
this._heartbeatOkReceived = false;
|
|
162
|
+
this._initialize();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Default behavior: exit to allow for a clean restart by the orchestrator.
|
|
166
|
+
cloudwrapper_1.CloudWrapper.error(`❌ Supabase connection lost. Exiting to allow for a clean restart.`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
// do nothing
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
});
|
|
135
177
|
this._isInitialized = true;
|
|
136
178
|
cloudwrapper_1.CloudWrapper.info(`Supabase App initialized`);
|
|
179
|
+
cloudwrapper_1.CloudWrapper.info(`🕘 Supabase Realtime connection is being established`);
|
|
180
|
+
this._connectionTimeout = setTimeout(() => {
|
|
181
|
+
if (!this._heartbeatOkReceived) {
|
|
182
|
+
cloudwrapper_1.CloudWrapper.error(`❌ Supabase Realtime connection failed to establish after 30 seconds. Exiting.`);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}, 30000);
|
|
137
186
|
}
|
|
138
187
|
}
|
|
139
|
-
poll() {
|
|
140
|
-
var _a;
|
|
141
|
-
cloudwrapper_1.CloudWrapper.info(`Supabase client status: ${(_a = this._supabaseClient) === null || _a === void 0 ? void 0 : _a.channel}`);
|
|
142
|
-
this._channels.storage.forEach((channel) => {
|
|
143
|
-
var _a;
|
|
144
|
-
cloudwrapper_1.CloudWrapper.info(`${channel.name}: ${channel.state}`);
|
|
145
|
-
console.log((_a = channel.channel) === null || _a === void 0 ? void 0 : _a.params);
|
|
146
|
-
});
|
|
147
|
-
setTimeout(() => this.poll(), 10000);
|
|
148
|
-
}
|
|
149
188
|
_payload2File(payload) {
|
|
150
189
|
if (!payload.metadata) {
|
|
151
190
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quatrain/cloudwrapper-supabase",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.20",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cloud Wrapper adapter for Supabase",
|
|
6
6
|
"repository": {
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"@quatrain/core": "^1.1.22"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@quatrain/backend": "^1.1.
|
|
23
|
+
"@quatrain/backend": "^1.1.22",
|
|
24
24
|
"@quatrain/cloudwrapper": "^1.1.14",
|
|
25
|
-
"@supabase/supabase-js": "2.
|
|
25
|
+
"@supabase/supabase-js": "^2.82.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@tsconfig/recommended": "^1.0.1",
|
|
@@ -11,12 +11,14 @@ import {
|
|
|
11
11
|
SupabaseClient,
|
|
12
12
|
RealtimeChannel,
|
|
13
13
|
} from '@supabase/supabase-js'
|
|
14
|
+
import { HeartbeatStatus } from '@supabase/realtime-js/dist/module/RealtimeClient'
|
|
14
15
|
import { FileType } from '@quatrain/storage'
|
|
15
16
|
|
|
16
17
|
export type SupabaseParams = {
|
|
17
18
|
useEmulator?: boolean
|
|
18
19
|
url?: string
|
|
19
20
|
key?: string
|
|
21
|
+
exitOnDisconnect?: boolean
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export type Channel = {
|
|
@@ -40,6 +42,8 @@ export class SupabaseCloudWrapper extends AbstractCloudWrapper {
|
|
|
40
42
|
protected _supabaseClient: SupabaseClient | undefined
|
|
41
43
|
protected _realtimeClient: RealtimeChannel | undefined
|
|
42
44
|
protected _isInitialized = false
|
|
45
|
+
protected _heartbeatOkReceived = false
|
|
46
|
+
protected _connectionTimeout: NodeJS.Timeout | undefined
|
|
43
47
|
protected _channels: Channels = { database: [], storage: [] }
|
|
44
48
|
|
|
45
49
|
constructor(params: SupabaseParams) {
|
|
@@ -114,7 +118,7 @@ export class SupabaseCloudWrapper extends AbstractCloudWrapper {
|
|
|
114
118
|
this._initialize()
|
|
115
119
|
|
|
116
120
|
if (typeof trigger.script !== 'function') {
|
|
117
|
-
throw new
|
|
121
|
+
throw new TypeError(`Passed script value is not a function`)
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
if (Array.isArray(trigger.event)) {
|
|
@@ -175,21 +179,76 @@ export class SupabaseCloudWrapper extends AbstractCloudWrapper {
|
|
|
175
179
|
|
|
176
180
|
protected _initialize() {
|
|
177
181
|
if (this._isInitialized === false) {
|
|
178
|
-
|
|
182
|
+
CloudWrapper.info(`Starting Supabase client with heartbeat monitoring`)
|
|
183
|
+
|
|
184
|
+
if (this._connectionTimeout) {
|
|
185
|
+
clearTimeout(this._connectionTimeout)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this._supabaseClient = createClient(
|
|
189
|
+
this._params.url,
|
|
190
|
+
this._params.key,
|
|
191
|
+
{
|
|
192
|
+
realtime: {
|
|
193
|
+
heartbeatIntervalMs: 5000,
|
|
194
|
+
heartbeatCallback: (status: HeartbeatStatus) => {
|
|
195
|
+
switch (status) {
|
|
196
|
+
case 'ok':
|
|
197
|
+
if (!this._heartbeatOkReceived) {
|
|
198
|
+
CloudWrapper.info(
|
|
199
|
+
`🛜 Supabase Realtime connection established`
|
|
200
|
+
)
|
|
201
|
+
if (this._connectionTimeout) {
|
|
202
|
+
clearTimeout(this._connectionTimeout)
|
|
203
|
+
}
|
|
204
|
+
this._heartbeatOkReceived = true
|
|
205
|
+
}
|
|
206
|
+
break
|
|
207
|
+
case 'timeout':
|
|
208
|
+
CloudWrapper.warn(
|
|
209
|
+
`⚠️ Supabase Realtime connection timed out`
|
|
210
|
+
)
|
|
211
|
+
break
|
|
212
|
+
case 'disconnected':
|
|
213
|
+
// Reconnect only if exitOnDisconnect is explicitly set to false.
|
|
214
|
+
if (this._params.exitOnDisconnect === false) {
|
|
215
|
+
CloudWrapper.warn(
|
|
216
|
+
`❌ Supabase connection lost. Attempting to reconnect...`
|
|
217
|
+
)
|
|
218
|
+
this._isInitialized = false
|
|
219
|
+
this._heartbeatOkReceived = false
|
|
220
|
+
this._initialize()
|
|
221
|
+
} else {
|
|
222
|
+
// Default behavior: exit to allow for a clean restart by the orchestrator.
|
|
223
|
+
CloudWrapper.error(
|
|
224
|
+
`❌ Supabase connection lost. Exiting to allow for a clean restart.`
|
|
225
|
+
)
|
|
226
|
+
process.exit(1)
|
|
227
|
+
}
|
|
228
|
+
break
|
|
229
|
+
default:
|
|
230
|
+
// do nothing
|
|
231
|
+
break
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
}
|
|
236
|
+
)
|
|
179
237
|
this._isInitialized = true
|
|
180
238
|
CloudWrapper.info(`Supabase App initialized`)
|
|
181
|
-
|
|
182
|
-
|
|
239
|
+
CloudWrapper.info(
|
|
240
|
+
`🕘 Supabase Realtime connection is being established`
|
|
241
|
+
)
|
|
183
242
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
243
|
+
this._connectionTimeout = setTimeout(() => {
|
|
244
|
+
if (!this._heartbeatOkReceived) {
|
|
245
|
+
CloudWrapper.error(
|
|
246
|
+
`❌ Supabase Realtime connection failed to establish after 30 seconds. Exiting.`
|
|
247
|
+
)
|
|
248
|
+
process.exit(1)
|
|
249
|
+
}
|
|
250
|
+
}, 30000)
|
|
251
|
+
}
|
|
193
252
|
}
|
|
194
253
|
|
|
195
254
|
protected _payload2File(payload: {
|