@giaeulate/baas-sdk 1.0.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/js/demo/index.html +133 -0
- package/js/src/index.ts +278 -0
- package/package.json +34 -0
- package/realtime.test.ts +184 -0
- package/scripts/dev.sh +30 -0
- package/src/client.ts +263 -0
- package/src/http-client.ts +144 -0
- package/src/index.ts +43 -0
- package/src/modules/api-keys.ts +36 -0
- package/src/modules/audit.ts +46 -0
- package/src/modules/auth.ts +158 -0
- package/src/modules/backups.ts +68 -0
- package/src/modules/branches.ts +46 -0
- package/src/modules/database.ts +158 -0
- package/src/modules/email.ts +93 -0
- package/src/modules/env-vars.ts +42 -0
- package/src/modules/functions.ts +57 -0
- package/src/modules/graphql.ts +24 -0
- package/src/modules/jobs.ts +78 -0
- package/src/modules/log-drains.ts +76 -0
- package/src/modules/metrics.ts +72 -0
- package/src/modules/migrations.ts +51 -0
- package/src/modules/projects.ts +25 -0
- package/src/modules/realtime.ts +30 -0
- package/src/modules/search.ts +37 -0
- package/src/modules/storage.ts +27 -0
- package/src/modules/users.ts +31 -0
- package/src/modules/webhooks.ts +58 -0
- package/src/query-builder.ts +157 -0
- package/src/realtime.ts +157 -0
- package/src/types.ts +49 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +14 -0
package/src/realtime.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { RealtimeAction, RealtimeCallback, RealtimePayload } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RealtimeService handles WebSocket connections to the BaaS Realtime engine.
|
|
5
|
+
* It allows subscribing to database changes in real-time.
|
|
6
|
+
*/
|
|
7
|
+
export class RealtimeService {
|
|
8
|
+
private socket: WebSocket | null = null;
|
|
9
|
+
private subscribers: Map<string, Set<{ action: RealtimeAction; callback: RealtimeCallback }>> = new Map();
|
|
10
|
+
private reconnectAttempts = 0;
|
|
11
|
+
private maxReconnectAttempts = 5;
|
|
12
|
+
private reconnectInterval = 3000;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
private url: string,
|
|
16
|
+
private projectId: string | null,
|
|
17
|
+
private tokenProvider: () => string | null,
|
|
18
|
+
private wsConstructor: any = typeof WebSocket !== 'undefined' ? WebSocket : null
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialize the WebSocket connection
|
|
23
|
+
*/
|
|
24
|
+
private connect(): Promise<void> {
|
|
25
|
+
if (this.socket?.readyState === WebSocket.OPEN) return Promise.resolve();
|
|
26
|
+
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const token = this.tokenProvider();
|
|
29
|
+
if (!this.projectId || !token) {
|
|
30
|
+
reject(new Error('Missing project ID or authentication token for Realtime connection'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Convert http/https to ws/wss
|
|
35
|
+
const wsUrl = this.url.replace(/^http/, 'ws') + '/ws';
|
|
36
|
+
const fullUrl = `${wsUrl}?project_id=${this.projectId}&token=${token}`;
|
|
37
|
+
|
|
38
|
+
if (!this.wsConstructor) {
|
|
39
|
+
reject(new Error('WebSocket constructor not provided or available in this environment'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const socket = new this.wsConstructor(fullUrl);
|
|
44
|
+
this.socket = socket;
|
|
45
|
+
|
|
46
|
+
socket.onopen = () => {
|
|
47
|
+
console.log('BaaS Realtime: Connected');
|
|
48
|
+
this.reconnectAttempts = 0;
|
|
49
|
+
resolve();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
socket.onmessage = (event: any) => {
|
|
53
|
+
try {
|
|
54
|
+
const payload: RealtimePayload = JSON.parse(event.data);
|
|
55
|
+
this.handleEvent(payload);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error('BaaS Realtime: Failed to parse message', err);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
socket.onerror = (error: any) => {
|
|
62
|
+
console.error('BaaS Realtime: WebSocket Error', error);
|
|
63
|
+
reject(error);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
socket.onclose = () => {
|
|
67
|
+
console.log('BaaS Realtime: Disconnected');
|
|
68
|
+
this.socket = null;
|
|
69
|
+
this.attemptReconnect();
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Subscribe to changes on a specific table
|
|
76
|
+
*/
|
|
77
|
+
public async subscribe<T = any>(
|
|
78
|
+
table: string,
|
|
79
|
+
action: RealtimeAction,
|
|
80
|
+
callback: RealtimeCallback<T>
|
|
81
|
+
): Promise<{ unsubscribe: () => void }> {
|
|
82
|
+
await this.connect();
|
|
83
|
+
|
|
84
|
+
if (!this.subscribers.has(table)) {
|
|
85
|
+
this.subscribers.set(table, new Set());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const sub = { action, callback };
|
|
89
|
+
this.subscribers.get(table)!.add(sub);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
unsubscribe: () => {
|
|
93
|
+
const tableSubs = this.subscribers.get(table);
|
|
94
|
+
if (tableSubs) {
|
|
95
|
+
tableSubs.delete(sub);
|
|
96
|
+
if (tableSubs.size === 0) {
|
|
97
|
+
this.subscribers.delete(table);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Handle incoming CDC events from the server
|
|
106
|
+
*/
|
|
107
|
+
private handleEvent(payload: RealtimePayload) {
|
|
108
|
+
// Notify specific table subscribers
|
|
109
|
+
const tableSubs = this.subscribers.get(payload.table);
|
|
110
|
+
if (tableSubs) {
|
|
111
|
+
for (const sub of tableSubs) {
|
|
112
|
+
if (sub.action === '*' || sub.action.toLowerCase() === payload.action.toLowerCase()) {
|
|
113
|
+
sub.callback(payload);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Notify wildcard ('*') subscribers
|
|
119
|
+
const wildcardSubs = this.subscribers.get('*');
|
|
120
|
+
if (wildcardSubs) {
|
|
121
|
+
for (const sub of wildcardSubs) {
|
|
122
|
+
if (sub.action === '*' || sub.action.toLowerCase() === payload.action.toLowerCase()) {
|
|
123
|
+
sub.callback(payload);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Attempt to reconnect on connection loss
|
|
131
|
+
*/
|
|
132
|
+
private attemptReconnect() {
|
|
133
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
134
|
+
this.reconnectAttempts++;
|
|
135
|
+
console.log(`BaaS Realtime: Attempting reconnect ${this.reconnectAttempts}/${this.maxReconnectAttempts}...`);
|
|
136
|
+
setTimeout(() => this.connect(), this.reconnectInterval);
|
|
137
|
+
} else {
|
|
138
|
+
console.error('BaaS Realtime: Max reconnect attempts reached');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Close the connection
|
|
144
|
+
*/
|
|
145
|
+
public disconnect() {
|
|
146
|
+
if (this.socket) {
|
|
147
|
+
this.socket.onclose = null; // Prevent reconnect
|
|
148
|
+
this.socket.close();
|
|
149
|
+
this.socket = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** @internal - For testing only */
|
|
154
|
+
public get _socket(): WebSocket | null {
|
|
155
|
+
return this.socket;
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Shared Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** HTTP method types */
|
|
6
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
7
|
+
|
|
8
|
+
/** Request options for the HTTP client */
|
|
9
|
+
export interface RequestOptions {
|
|
10
|
+
method?: HttpMethod;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
skipAuth?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Query filter for data operations */
|
|
17
|
+
export type QueryFilter = {
|
|
18
|
+
column: string;
|
|
19
|
+
operator: string;
|
|
20
|
+
value: any;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Pagination options */
|
|
24
|
+
export interface PaginationOptions {
|
|
25
|
+
limit?: number;
|
|
26
|
+
offset?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Generic API response with error */
|
|
30
|
+
export interface ApiResponse<T = any> {
|
|
31
|
+
data?: T;
|
|
32
|
+
error?: string;
|
|
33
|
+
success?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Realtime Event types */
|
|
37
|
+
export type RealtimeAction = 'INSERT' | 'UPDATE' | 'DELETE' | '*';
|
|
38
|
+
|
|
39
|
+
/** Structure of a Realtime event payload */
|
|
40
|
+
export interface RealtimePayload<T = any> {
|
|
41
|
+
table: string;
|
|
42
|
+
action: 'insert' | 'update' | 'delete';
|
|
43
|
+
record: T;
|
|
44
|
+
old_record?: T;
|
|
45
|
+
timestamp: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Callback function for realtime subscriptions */
|
|
49
|
+
export type RealtimeCallback<T = any> = (payload: RealtimePayload<T>) => void;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"outDir": "dist",
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"isolatedModules": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// <reference types="vitest" />
|
|
2
|
+
import { defineConfig } from 'vitest/config';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
environment: 'jsdom',
|
|
7
|
+
globals: true,
|
|
8
|
+
setupFiles: [],
|
|
9
|
+
coverage: {
|
|
10
|
+
provider: 'v8',
|
|
11
|
+
reporter: ['text', 'json', 'html'],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|