corebase-js 0.1.1 → 0.2.3

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 CHANGED
@@ -6,6 +6,9 @@ A robust JavaScript/TypeScript client for communicating with your CoreBase backe
6
6
 
7
7
  ```bash
8
8
  npm install corebase-js
9
+
10
+ # If using React/React Native hooks (Optioanal peer dependency)
11
+ npm install --save-dev react @types/react
9
12
  ```
10
13
 
11
14
  ## Initialization
@@ -171,6 +174,67 @@ const { data, error } = await corebase
171
174
  .eq('id', 123);
172
175
  ```
173
176
 
177
+ ## File Storage
178
+
179
+ Upload files to your project buckets.
180
+
181
+ ### Upload File
182
+
183
+ Upload a file directly from the browser (e.g., from an `<input type="file" />`).
184
+
185
+ ```typescript
186
+ // Assuming you have an input element: <input type="file" id="fileInput" />
187
+ const fileInput = document.getElementById('fileInput') as HTMLInputElement;
188
+ const file = fileInput.files?.[0];
189
+
190
+ if (file) {
191
+ const { data, error } = await corebase.storage.upload(file);
192
+
193
+ if (error) {
194
+ console.error('Upload failed:', error);
195
+ } else {
196
+ console.log('File uploaded successfully!');
197
+ console.log('File Key:', data.key);
198
+ console.log('Upload URL:', data.url);
199
+ }
200
+ }
201
+ ```
202
+
203
+ ## Realtime Data (React / React Native)
204
+
205
+ Subscribe to live data changes using the `useQuery` hook.
206
+
207
+ ```typescript
208
+ import { useQuery } from 'corebase-js/react';
209
+
210
+ // Define your query
211
+ const query = {
212
+ from: 'posts',
213
+ select: ['id', 'title', 'likes'],
214
+ where: {
215
+ status: 'published'
216
+ }
217
+ };
218
+
219
+ const MyComponent = () => {
220
+ // Subscribe to realtime updates
221
+ const { data, loading, error } = useQuery(corebase, query);
222
+
223
+ if (loading) return <div>Loading...</div>;
224
+ if (error) return <div>Error: {error.message}</div>;
225
+
226
+ return (
227
+ <ul>
228
+ {data.map(post => (
229
+ <li key={post.id}>
230
+ {post.title} ({post.likes} likes)
231
+ </li>
232
+ ))}
233
+ </ul>
234
+ );
235
+ };
236
+ ```
237
+
174
238
  ## TypeScript Support
175
239
 
176
240
  This library is written in TypeScript and exports types for all responses.
package/dist/client.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import { AuthClient } from './auth';
2
2
  import { QueryBuilder } from './query';
3
+ import { StorageClient } from './storage';
4
+ import { RealtimeClient } from './realtime';
3
5
  import { ClientConfig } from './types';
4
6
  export declare class CoreBaseClient {
5
7
  auth: AuthClient;
8
+ storage: StorageClient;
9
+ realtime: RealtimeClient;
6
10
  private config;
7
11
  constructor(config: ClientConfig);
8
12
  from<T = any>(table: string): QueryBuilder<T>;
package/dist/client.js CHANGED
@@ -4,6 +4,8 @@ exports.CoreBaseClient = void 0;
4
4
  exports.createClient = createClient;
5
5
  const auth_1 = require("./auth");
6
6
  const query_1 = require("./query");
7
+ const storage_1 = require("./storage");
8
+ const realtime_1 = require("./realtime");
7
9
  class CoreBaseClient {
8
10
  constructor(config) {
9
11
  if (!config.apiKey) {
@@ -14,6 +16,8 @@ class CoreBaseClient {
14
16
  }
15
17
  this.config = config;
16
18
  this.auth = new auth_1.AuthClient(config);
19
+ this.storage = new storage_1.StorageClient(this.auth);
20
+ this.realtime = new realtime_1.RealtimeClient(config, this.auth);
17
21
  }
18
22
  from(table) {
19
23
  return new query_1.QueryBuilder((path, options) => this.auth.request(path, options), table);
package/dist/index.d.ts CHANGED
@@ -2,3 +2,5 @@ export * from './types';
2
2
  export * from './client';
3
3
  export * from './auth';
4
4
  export * from './query';
5
+ export * from './storage';
6
+ export * from './realtime';
package/dist/index.js CHANGED
@@ -18,3 +18,5 @@ __exportStar(require("./types"), exports);
18
18
  __exportStar(require("./client"), exports);
19
19
  __exportStar(require("./auth"), exports);
20
20
  __exportStar(require("./query"), exports);
21
+ __exportStar(require("./storage"), exports);
22
+ __exportStar(require("./realtime"), exports);
@@ -0,0 +1,21 @@
1
+ import { CoreBaseClient } from './client';
2
+ export interface UseQueryOptions {
3
+ enabled?: boolean;
4
+ onData?: (data: any) => void;
5
+ onError?: (error: any) => void;
6
+ }
7
+ export interface UseQueryResult<T> {
8
+ data: T | null;
9
+ loading: boolean;
10
+ error: any;
11
+ }
12
+ /**
13
+ * Hook to subscribe to realtime data queries.
14
+ * Compatible with React and React Native.
15
+ *
16
+ * @param client - The CoreBaseClient instance
17
+ * @param query - The query object definition
18
+ * @param options - Options for the hook
19
+ * @returns The current data, loading state, and error
20
+ */
21
+ export declare function useQuery<T = any>(client: CoreBaseClient, query: any, options?: UseQueryOptions): UseQueryResult<T>;
package/dist/react.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useQuery = useQuery;
4
+ const react_1 = require("react");
5
+ /**
6
+ * Hook to subscribe to realtime data queries.
7
+ * Compatible with React and React Native.
8
+ *
9
+ * @param client - The CoreBaseClient instance
10
+ * @param query - The query object definition
11
+ * @param options - Options for the hook
12
+ * @returns The current data, loading state, and error
13
+ */
14
+ function useQuery(client, query, options = {}) {
15
+ const [data, setData] = (0, react_1.useState)(null);
16
+ const [loading, setLoading] = (0, react_1.useState)(true);
17
+ const [error, setError] = (0, react_1.useState)(null);
18
+ const { enabled = true, onData, onError } = options;
19
+ // Use stringified query to detect changes deeply
20
+ const queryJson = JSON.stringify(query);
21
+ (0, react_1.useEffect)(() => {
22
+ if (!enabled) {
23
+ setLoading(false);
24
+ return;
25
+ }
26
+ let subId;
27
+ const subscribe = () => {
28
+ setLoading(true);
29
+ setError(null);
30
+ try {
31
+ if (!client.realtime) {
32
+ throw new Error("RealtimeClient is not initialized on the CoreBaseClient instance.");
33
+ }
34
+ subId = client.realtime.subscribe(query, (newData) => {
35
+ setData(newData);
36
+ setLoading(false);
37
+ if (onData)
38
+ onData(newData);
39
+ });
40
+ }
41
+ catch (err) {
42
+ console.error("CoreBase useQuery error:", err);
43
+ setError(err);
44
+ setLoading(false);
45
+ if (onError)
46
+ onError(err);
47
+ }
48
+ };
49
+ subscribe();
50
+ return () => {
51
+ if (subId && client.realtime) {
52
+ client.realtime.unsubscribe(subId);
53
+ }
54
+ };
55
+ }, [client, queryJson, enabled]); // Re-run if client, query or enabled status changes
56
+ return { data, loading, error };
57
+ }
@@ -0,0 +1,28 @@
1
+ import { AuthClient } from './auth';
2
+ import { ClientConfig } from './types';
3
+ export interface RealtimeMessage {
4
+ type: 'subscribe' | 'unsubscribe' | 'data' | 'error';
5
+ id?: string;
6
+ query?: any;
7
+ data?: any;
8
+ error?: any;
9
+ }
10
+ export type RealtimeCallback = (data: any) => void;
11
+ export declare class RealtimeClient {
12
+ private ws;
13
+ private subscriptions;
14
+ private reconnectTimer;
15
+ private config;
16
+ private auth;
17
+ private isConnected;
18
+ constructor(config: ClientConfig, auth: AuthClient);
19
+ private getUrl;
20
+ connect(): void;
21
+ disconnect(): void;
22
+ subscribe(query: any, callback: RealtimeCallback): string;
23
+ unsubscribe(id: string): void;
24
+ private send;
25
+ private handleMessage;
26
+ private resubscribeAll;
27
+ private scheduleReconnect;
28
+ }
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RealtimeClient = void 0;
4
+ class RealtimeClient {
5
+ constructor(config, auth) {
6
+ this.ws = null;
7
+ this.subscriptions = new Map();
8
+ this.reconnectTimer = null;
9
+ this.isConnected = false;
10
+ this.config = config;
11
+ this.auth = auth;
12
+ }
13
+ getUrl() {
14
+ var _a;
15
+ const baseUrl = this.config.baseUrl || 'http://localhost:3000';
16
+ let url = baseUrl.replace(/^http/, 'ws') + '/v1/realtime';
17
+ // Append Auth params for standard browser support (which doesn't support headers)
18
+ const params = new URLSearchParams();
19
+ if (this.config.apiKey) {
20
+ params.append('x-api-key', this.config.apiKey);
21
+ }
22
+ const token = (_a = this.auth.getSession()) === null || _a === void 0 ? void 0 : _a.access_token;
23
+ if (token) {
24
+ params.append('token', token);
25
+ }
26
+ const queryString = params.toString();
27
+ if (queryString) {
28
+ url += `?${queryString}`;
29
+ }
30
+ return url;
31
+ }
32
+ connect() {
33
+ var _a;
34
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING))
35
+ return;
36
+ const url = this.getUrl();
37
+ const headers = {
38
+ 'x-api-key': this.config.apiKey
39
+ };
40
+ const token = (_a = this.auth.getSession()) === null || _a === void 0 ? void 0 : _a.access_token;
41
+ if (token) {
42
+ headers['Authorization'] = `Bearer ${token}`;
43
+ }
44
+ try {
45
+ // Try to pass headers (Node.js / React Native)
46
+ // @ts-ignore
47
+ this.ws = new WebSocket(url, undefined, { headers });
48
+ }
49
+ catch (e) {
50
+ // Fallback for standard browsers
51
+ this.ws = new WebSocket(url);
52
+ }
53
+ if (!this.ws)
54
+ return;
55
+ this.ws.onopen = () => {
56
+ this.isConnected = true;
57
+ this.resubscribeAll();
58
+ };
59
+ this.ws.onmessage = (event) => {
60
+ try {
61
+ const msg = JSON.parse(event.data);
62
+ this.handleMessage(msg);
63
+ }
64
+ catch (e) {
65
+ console.error('CoreBase Realtime parse error', e);
66
+ }
67
+ };
68
+ this.ws.onclose = () => {
69
+ this.isConnected = false;
70
+ this.scheduleReconnect();
71
+ };
72
+ this.ws.onerror = (e) => {
73
+ console.error('CoreBase Realtime error', e);
74
+ };
75
+ }
76
+ disconnect() {
77
+ if (this.ws) {
78
+ this.ws.close();
79
+ this.ws = null;
80
+ }
81
+ if (this.reconnectTimer) {
82
+ clearTimeout(this.reconnectTimer);
83
+ this.reconnectTimer = null;
84
+ }
85
+ this.isConnected = false;
86
+ }
87
+ subscribe(query, callback) {
88
+ const id = Math.random().toString(36).substring(7);
89
+ this.subscriptions.set(id, { query, callback });
90
+ if (this.isConnected) {
91
+ this.send({ type: 'subscribe', id, query });
92
+ }
93
+ else {
94
+ this.connect();
95
+ }
96
+ return id;
97
+ }
98
+ unsubscribe(id) {
99
+ if (this.subscriptions.has(id)) {
100
+ this.subscriptions.delete(id);
101
+ if (this.isConnected) {
102
+ this.send({ type: 'unsubscribe', id });
103
+ }
104
+ }
105
+ }
106
+ send(msg) {
107
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
108
+ this.ws.send(JSON.stringify(msg));
109
+ }
110
+ }
111
+ handleMessage(msg) {
112
+ if (msg.type === 'data' && msg.id && msg.data) {
113
+ const sub = this.subscriptions.get(msg.id);
114
+ if (sub) {
115
+ sub.callback(msg.data);
116
+ }
117
+ }
118
+ else if (msg.type === 'error') {
119
+ console.error('CoreBase Realtime message error:', msg.error);
120
+ }
121
+ }
122
+ resubscribeAll() {
123
+ this.subscriptions.forEach((sub, id) => {
124
+ this.send({ type: 'subscribe', id, query: sub.query });
125
+ });
126
+ }
127
+ scheduleReconnect() {
128
+ if (!this.reconnectTimer) {
129
+ this.reconnectTimer = setTimeout(() => {
130
+ this.reconnectTimer = null;
131
+ this.connect();
132
+ }, 3000);
133
+ }
134
+ }
135
+ }
136
+ exports.RealtimeClient = RealtimeClient;
@@ -0,0 +1,20 @@
1
+ import { AuthClient } from './auth';
2
+ import { ClientResponse } from './types';
3
+ export declare class StorageClient {
4
+ private auth;
5
+ constructor(auth: AuthClient);
6
+ /**
7
+ * Uploads a file to the storage bucket.
8
+ * This involves two steps:
9
+ * 1. Getting a signed upload URL from the API.
10
+ * 2. Uploading the file content directly to the cloud storage provider via the signed URL.
11
+ *
12
+ * @param file The File object to upload.
13
+ * @param bucketName The name of the bucket to upload the file to.
14
+ * @returns The key of the uploaded file.
15
+ */
16
+ upload(file: File, bucketName: string): Promise<ClientResponse<{
17
+ key: string;
18
+ url: string;
19
+ }>>;
20
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.StorageClient = void 0;
13
+ class StorageClient {
14
+ constructor(auth) {
15
+ this.auth = auth;
16
+ }
17
+ /**
18
+ * Uploads a file to the storage bucket.
19
+ * This involves two steps:
20
+ * 1. Getting a signed upload URL from the API.
21
+ * 2. Uploading the file content directly to the cloud storage provider via the signed URL.
22
+ *
23
+ * @param file The File object to upload.
24
+ * @param bucketName The name of the bucket to upload the file to.
25
+ * @returns The key of the uploaded file.
26
+ */
27
+ upload(file, bucketName) {
28
+ return __awaiter(this, void 0, void 0, function* () {
29
+ // 1. Get Signed URL
30
+ const { data: signData, error: signError } = yield this.auth.request('/storage/upload/sign', {
31
+ method: 'POST',
32
+ body: JSON.stringify({
33
+ filename: file.name,
34
+ contentType: file.type,
35
+ size: file.size,
36
+ bucketName
37
+ }),
38
+ });
39
+ if (signError) {
40
+ return { data: null, error: signError };
41
+ }
42
+ if (!signData) {
43
+ return { data: null, error: { message: 'Failed to retrieve signed upload URL.' } };
44
+ }
45
+ try {
46
+ // 2. Upload File to Signed URL
47
+ const uploadResponse = yield fetch(signData.uploadUrl, {
48
+ method: 'PUT',
49
+ headers: {
50
+ 'Content-Type': file.type,
51
+ },
52
+ body: file,
53
+ });
54
+ if (!uploadResponse.ok) {
55
+ return {
56
+ data: null,
57
+ error: {
58
+ message: `File upload failed with status ${uploadResponse.status}: ${uploadResponse.statusText}`
59
+ }
60
+ };
61
+ }
62
+ // Return success with the key.
63
+ // The URL returned by sign is the upload URL (often with SAS/Signature params).
64
+ // Users typically store the 'key' to reference the file later.
65
+ return {
66
+ data: {
67
+ key: signData.key,
68
+ url: signData.uploadUrl // Returning the upload URL might be useful for immediate access if it's still valid/public-read
69
+ },
70
+ error: null
71
+ };
72
+ }
73
+ catch (err) {
74
+ return { data: null, error: { message: err.message || 'Network error during file upload.' } };
75
+ }
76
+ });
77
+ }
78
+ }
79
+ exports.StorageClient = StorageClient;
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "corebase-js",
3
- "version": "0.1.1",
3
+ "version": "0.2.3",
4
4
  "description": "JavaScript client for CoreBase",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./react": "./dist/react.js"
10
+ },
7
11
  "scripts": {
8
12
  "build": "tsc",
9
13
  "prepublishOnly": "npm run build"
@@ -20,6 +24,16 @@
20
24
  "license": "ISC",
21
25
  "type": "commonjs",
22
26
  "devDependencies": {
27
+ "@types/react": "^19.2.10",
28
+ "react": "^19.2.4",
23
29
  "typescript": "^5.0.0"
30
+ },
31
+ "peerDependencies": {
32
+ "react": ">=16.8.0"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "react": {
36
+ "optional": true
37
+ }
24
38
  }
25
39
  }