@quatrain/cloudwrapper-supabase 1.1.18 → 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 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 Error(`Passed script value is not a function`);
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
- this._supabaseClient = (0, supabase_js_1.createClient)(this._params.url, this._params.key);
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.18",
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.21",
23
+ "@quatrain/backend": "^1.1.22",
24
24
  "@quatrain/cloudwrapper": "^1.1.14",
25
- "@supabase/supabase-js": "^2.53.0"
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 Error(`Passed script value is not a function`)
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
- this._supabaseClient = createClient(this._params.url, this._params.key)
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
- public poll() {
185
- CloudWrapper.info(
186
- `Supabase client status: ${this._supabaseClient?.channel}`
187
- )
188
- this._channels.storage.forEach((channel) => {
189
- CloudWrapper.info(`${channel.name}: ${channel.state}`)
190
- console.log(channel.channel?.params)
191
- })
192
- setTimeout(() => this.poll(), 10000)
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: {