@urbackend/sdk 0.1.0 → 0.1.1

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.
@@ -0,0 +1,108 @@
1
+ import { expect, test, vi } from 'vitest';
2
+ import urBackend from '../src/index';
3
+
4
+ const mockApiKey = 'test-api-key';
5
+ const client = urBackend({ apiKey: mockApiKey });
6
+
7
+ test('signUp returns user object on success', async () => {
8
+ const mockUser = { _id: '123', email: 'test@example.com' };
9
+ vi.stubGlobal(
10
+ 'fetch',
11
+ vi.fn().mockResolvedValue({
12
+ ok: true,
13
+ headers: new Headers({ 'content-type': 'application/json' }),
14
+ json: () => Promise.resolve({ success: true, data: mockUser }),
15
+ }),
16
+ );
17
+
18
+ const user = await client.auth.signUp({ email: 'test@example.com', password: 'password' });
19
+ expect(user).toEqual(mockUser);
20
+ });
21
+
22
+ test('login stores session token', async () => {
23
+ const mockToken = 'mock-token';
24
+ vi.stubGlobal(
25
+ 'fetch',
26
+ vi.fn().mockResolvedValue({
27
+ ok: true,
28
+ headers: new Headers({ 'content-type': 'application/json' }),
29
+ json: () =>
30
+ Promise.resolve({
31
+ success: true,
32
+ data: { token: mockToken, user: { _id: '123', email: 'test@example.com' } },
33
+ }),
34
+ }),
35
+ );
36
+
37
+ const response = await client.auth.login({ email: 'test@example.com', password: 'password' });
38
+ expect(response.token).toBe(mockToken);
39
+ });
40
+
41
+ test('me() uses stored token from login', async () => {
42
+ const mockToken = 'mock-token';
43
+ const mockUser = { _id: '123', email: 'test@example.com' };
44
+
45
+ // First mock login
46
+ vi.stubGlobal(
47
+ 'fetch',
48
+ vi.fn().mockResolvedValue({
49
+ ok: true,
50
+ headers: new Headers({ 'content-type': 'application/json' }),
51
+ json: () =>
52
+ Promise.resolve({
53
+ success: true,
54
+ data: { token: mockToken, user: mockUser },
55
+ }),
56
+ }),
57
+ );
58
+ await client.auth.login({ email: 'test@example.com', password: 'password' });
59
+
60
+ // Then mock me call
61
+ const meFetchMock = vi.fn().mockResolvedValue({
62
+ ok: true,
63
+ headers: new Headers({ 'content-type': 'application/json' }),
64
+ json: () => Promise.resolve({ success: true, data: mockUser }),
65
+ });
66
+ vi.stubGlobal('fetch', meFetchMock);
67
+
68
+ await client.auth.me();
69
+
70
+ expect(meFetchMock).toHaveBeenCalledWith(
71
+ expect.stringContaining('/api/userAuth/me'),
72
+ expect.objectContaining({
73
+ headers: expect.objectContaining({
74
+ Authorization: `Bearer ${mockToken}`,
75
+ }),
76
+ }),
77
+ );
78
+ });
79
+
80
+ test('me() throws AuthError when no token present', async () => {
81
+ client.auth.logout();
82
+ await expect(client.auth.me()).rejects.toThrow('Authentication token is required');
83
+ });
84
+
85
+ test('login throws AuthError on 401', async () => {
86
+ vi.stubGlobal(
87
+ 'fetch',
88
+ vi.fn().mockResolvedValue({
89
+ ok: false,
90
+ status: 401,
91
+ url: 'https://api.urbackend.bitbros.in/api/userAuth/login',
92
+ statusText: 'Unauthorized',
93
+ json: () => Promise.resolve({ success: false, message: 'Invalid credentials' }),
94
+ }),
95
+ );
96
+
97
+ await expect(client.auth.login({ email: 'a', password: 'b' })).rejects.toThrow(
98
+ 'Invalid credentials',
99
+ );
100
+ });
101
+
102
+ test('handles network failure', async () => {
103
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('DNS failure')));
104
+
105
+ await expect(client.auth.login({ email: 'a', password: 'b' })).rejects.toThrow(
106
+ 'DNS failure',
107
+ );
108
+ });
@@ -0,0 +1,118 @@
1
+ import { expect, test, vi } from 'vitest';
2
+ import urBackend from '../src/index';
3
+
4
+ const mockApiKey = 'test-api-key';
5
+ const client = urBackend({ apiKey: mockApiKey });
6
+
7
+ test('getAll returns array of typed documents', async () => {
8
+ const mockData = [
9
+ { _id: '1', name: 'Product 1' },
10
+ { _id: '2', name: 'Product 2' },
11
+ ];
12
+ vi.stubGlobal(
13
+ 'fetch',
14
+ vi.fn().mockResolvedValue({
15
+ ok: true,
16
+ headers: new Headers({ 'content-type': 'application/json' }),
17
+ json: () => Promise.resolve({ success: true, data: mockData }),
18
+ }),
19
+ );
20
+
21
+ const items = await client.db.getAll<{ _id: string; name: string }>('products');
22
+ expect(items).toEqual(mockData);
23
+ expect(items[0].name).toBe('Product 1');
24
+ });
25
+
26
+ test('getOne throws NotFoundError on 404', async () => {
27
+ vi.stubGlobal(
28
+ 'fetch',
29
+ vi.fn().mockResolvedValue({
30
+ ok: false,
31
+ status: 404,
32
+ url: 'https://api.urbackend.bitbros.in/api/data/products/999',
33
+ json: () => Promise.resolve({ success: false, message: 'Not Found' }),
34
+ }),
35
+ );
36
+
37
+ await expect(client.db.getOne('products', '999')).rejects.toThrow('Not Found');
38
+ });
39
+
40
+ test('insert returns created document with _id', async () => {
41
+ const payload = { name: 'New Item' };
42
+ const mockCreated = { _id: 'new-id', ...payload };
43
+ vi.stubGlobal(
44
+ 'fetch',
45
+ vi.fn().mockResolvedValue({
46
+ ok: true,
47
+ headers: new Headers({ 'content-type': 'application/json' }),
48
+ json: () => Promise.resolve({ success: true, data: mockCreated }),
49
+ }),
50
+ );
51
+
52
+ const result = await client.db.insert<{ _id: string; name: string }>('products', payload);
53
+ expect(result._id).toBe('new-id');
54
+ });
55
+
56
+ test('update returns updated document', async () => {
57
+ const payload = { name: 'Updated' };
58
+ const mockUpdated = { _id: 'id-1', ...payload };
59
+ vi.stubGlobal(
60
+ 'fetch',
61
+ vi.fn().mockResolvedValue({
62
+ ok: true,
63
+ headers: new Headers({ 'content-type': 'application/json' }),
64
+ json: () => Promise.resolve({ success: true, data: mockUpdated }),
65
+ }),
66
+ );
67
+
68
+ const result = await client.db.update<{ _id: string; name: string }>('products', 'id-1', payload);
69
+ expect(result.name).toBe('Updated');
70
+ });
71
+
72
+ test('delete returns { deleted: true }', async () => {
73
+ vi.stubGlobal(
74
+ 'fetch',
75
+ vi.fn().mockResolvedValue({
76
+ ok: true,
77
+ headers: new Headers({ 'content-type': 'application/json' }),
78
+ json: () => Promise.resolve({ success: true, data: { deleted: true } }),
79
+ }),
80
+ );
81
+
82
+ const result = await client.db.delete('products', 'id-1');
83
+ expect(result.deleted).toBe(true);
84
+ });
85
+
86
+ test('RateLimitError thrown on 429', async () => {
87
+ vi.stubGlobal(
88
+ 'fetch',
89
+ vi.fn().mockResolvedValue({
90
+ ok: false,
91
+ status: 429,
92
+ url: 'https://api.urbackend.bitbros.in/api/data/products',
93
+ headers: new Headers({ 'Retry-After': '60' }),
94
+ json: () => Promise.resolve({ success: false, message: 'Too Many Requests' }),
95
+ }),
96
+ );
97
+
98
+ try {
99
+ await client.db.getAll('products');
100
+ } catch (error: any) {
101
+ expect(error.name).toBe('RateLimitError');
102
+ expect(error.retryAfter).toBe(60);
103
+ }
104
+ });
105
+
106
+ test('ValidationError thrown on 400', async () => {
107
+ vi.stubGlobal(
108
+ 'fetch',
109
+ vi.fn().mockResolvedValue({
110
+ ok: false,
111
+ status: 400,
112
+ url: 'https://api.urbackend.bitbros.in/api/data/products',
113
+ json: () => Promise.resolve({ success: false, message: 'Invalid data format' }),
114
+ }),
115
+ );
116
+
117
+ await expect(client.db.insert('products', {})).rejects.toThrow('Invalid data format');
118
+ });
@@ -0,0 +1,62 @@
1
+ /// <reference types="node" />
2
+ import { expect, test, vi } from 'vitest';
3
+ import urBackend from '../src/index';
4
+
5
+ const mockApiKey = 'test-api-key';
6
+ const client = urBackend({ apiKey: mockApiKey });
7
+
8
+ test('upload sends FormData and returns { url, path }', async () => {
9
+ const mockResponse = { url: 'http://cdn.com/file.jpg', path: '/uploads/file.jpg' };
10
+ const fetchMock = vi.fn().mockResolvedValue({
11
+ ok: true,
12
+ headers: new Headers({ 'content-type': 'application/json' }),
13
+ json: () => Promise.resolve({ success: true, data: mockResponse }),
14
+ });
15
+ vi.stubGlobal('fetch', fetchMock);
16
+
17
+ // In Node.js testing environment, use a Buffer as mock file
18
+ const result = await client.storage.upload(Buffer.from('mock binary data'), 'test.jpg');
19
+
20
+ expect(result).toEqual(mockResponse);
21
+ expect(fetchMock).toHaveBeenCalledWith(
22
+ expect.stringContaining('/api/storage/upload'),
23
+ expect.objectContaining({
24
+ method: 'POST',
25
+ body: expect.any(Object), // FormData
26
+ }),
27
+ );
28
+ });
29
+
30
+ test('deleteFile sends path in body', async () => {
31
+ const fetchMock = vi.fn().mockResolvedValue({
32
+ ok: true,
33
+ headers: new Headers({ 'content-type': 'application/json' }),
34
+ json: () => Promise.resolve({ success: true, data: { deleted: true } }),
35
+ });
36
+ vi.stubGlobal('fetch', fetchMock);
37
+
38
+ const result = await client.storage.deleteFile('/uploads/file.jpg');
39
+
40
+ expect(result.deleted).toBe(true);
41
+ expect(fetchMock).toHaveBeenCalledWith(
42
+ expect.stringContaining('/api/storage/file'),
43
+ expect.objectContaining({
44
+ method: 'DELETE',
45
+ body: JSON.stringify({ path: '/uploads/file.jpg' }),
46
+ }),
47
+ );
48
+ });
49
+
50
+ test('StorageError thrown on failure', async () => {
51
+ vi.stubGlobal(
52
+ 'fetch',
53
+ vi.fn().mockResolvedValue({
54
+ ok: false,
55
+ status: 500,
56
+ url: 'https://api.urbackend.bitbros.in/api/storage/upload',
57
+ json: () => Promise.resolve({ success: false, message: 'Upload failed' }),
58
+ }),
59
+ );
60
+
61
+ await expect(client.storage.upload(Buffer.from('data'))).rejects.toThrow('Upload failed');
62
+ });
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "..",
5
+ "outDir": "../dist-test",
6
+ "types": ["node", "vitest/globals"]
7
+ },
8
+ "include": ["./**/*", "../src/**/*"]
9
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
7
+ "types": ["node"],
8
+ "strict": true,
9
+ "declaration": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src",
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist", "tests"]
18
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ outExtension({ format }) {
7
+ return {
8
+ js: format === 'cjs' ? '.cjs' : '.mjs',
9
+ };
10
+ },
11
+ dts: true,
12
+ clean: true,
13
+ minify: false,
14
+ sourcemap: true,
15
+ target: 'es2020',
16
+ });
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ coverage: {
8
+ provider: 'v8',
9
+ reporter: ['text', 'json', 'html'],
10
+ },
11
+ },
12
+ });
package/dist/index.cjs DELETED
@@ -1,315 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- AuthError: () => AuthError,
24
- NotFoundError: () => NotFoundError,
25
- RateLimitError: () => RateLimitError,
26
- StorageError: () => StorageError,
27
- UrBackendClient: () => UrBackendClient,
28
- UrBackendError: () => UrBackendError,
29
- ValidationError: () => ValidationError,
30
- default: () => urBackend,
31
- parseApiError: () => parseApiError
32
- });
33
- module.exports = __toCommonJS(index_exports);
34
-
35
- // src/errors.ts
36
- var UrBackendError = class extends Error {
37
- constructor(message, statusCode, endpoint) {
38
- super(message);
39
- this.message = message;
40
- this.statusCode = statusCode;
41
- this.endpoint = endpoint;
42
- this.name = "UrBackendError";
43
- }
44
- };
45
- var AuthError = class extends UrBackendError {
46
- constructor(message, statusCode, endpoint) {
47
- super(message, statusCode, endpoint);
48
- this.name = "AuthError";
49
- }
50
- };
51
- var NotFoundError = class extends UrBackendError {
52
- constructor(message, endpoint) {
53
- super(message, 404, endpoint);
54
- this.name = "NotFoundError";
55
- }
56
- };
57
- var RateLimitError = class extends UrBackendError {
58
- constructor(message, endpoint, retryAfter) {
59
- super(message, 429, endpoint);
60
- this.name = "RateLimitError";
61
- this.retryAfter = retryAfter;
62
- }
63
- };
64
- var StorageError = class extends UrBackendError {
65
- constructor(message, statusCode, endpoint) {
66
- super(message, statusCode, endpoint);
67
- this.name = "StorageError";
68
- }
69
- };
70
- var ValidationError = class extends UrBackendError {
71
- constructor(message, endpoint) {
72
- super(message, 400, endpoint);
73
- this.name = "ValidationError";
74
- }
75
- };
76
- async function parseApiError(response) {
77
- const endpoint = new URL(response.url).pathname;
78
- let message = "An unexpected error occurred";
79
- let data;
80
- try {
81
- data = await response.json();
82
- if (typeof data === "object" && data !== null && "message" in data) {
83
- message = data.message || message;
84
- }
85
- } catch {
86
- message = response.statusText || message;
87
- }
88
- const status = response.status;
89
- if (status === 401 || status === 403) {
90
- return new AuthError(message, status, endpoint);
91
- }
92
- if (status === 404) {
93
- return new NotFoundError(message, endpoint);
94
- }
95
- if (status === 429) {
96
- const retryAfter = response.headers.get("Retry-After");
97
- return new RateLimitError(message, endpoint, retryAfter ? parseInt(retryAfter, 10) : void 0);
98
- }
99
- if (status === 400) {
100
- return new ValidationError(message, endpoint);
101
- }
102
- if (endpoint.includes("/api/storage")) {
103
- return new StorageError(message, status, endpoint);
104
- }
105
- return new UrBackendError(message, status, endpoint);
106
- }
107
-
108
- // src/modules/auth.ts
109
- var AuthModule = class {
110
- constructor(client) {
111
- this.client = client;
112
- }
113
- /**
114
- * Create a new user account
115
- */
116
- async signUp(payload) {
117
- return this.client.request("POST", "/api/userAuth/signup", { body: payload });
118
- }
119
- /**
120
- * Log in an existing user and store the session token
121
- */
122
- async login(payload) {
123
- const response = await this.client.request("POST", "/api/userAuth/login", {
124
- body: payload
125
- });
126
- this.sessionToken = response.token;
127
- return response;
128
- }
129
- /**
130
- * Get the current authenticated user's profile
131
- */
132
- async me(token) {
133
- const activeToken = token || this.sessionToken;
134
- if (!activeToken) {
135
- throw new AuthError("Authentication token is required for /me endpoint", 401, "/api/userAuth/me");
136
- }
137
- return this.client.request("GET", "/api/userAuth/me", { token: activeToken });
138
- }
139
- /**
140
- * Clear the local session token
141
- */
142
- logout() {
143
- this.sessionToken = void 0;
144
- }
145
- };
146
-
147
- // src/modules/database.ts
148
- var DatabaseModule = class {
149
- constructor(client) {
150
- this.client = client;
151
- }
152
- /**
153
- * Fetch all documents from a collection
154
- */
155
- async getAll(collection) {
156
- try {
157
- return await this.client.request("GET", `/api/data/${collection}`);
158
- } catch (e) {
159
- if (e instanceof NotFoundError) {
160
- return [];
161
- }
162
- throw e;
163
- }
164
- }
165
- /**
166
- * Fetch a single document by its ID
167
- */
168
- async getOne(collection, id) {
169
- return this.client.request("GET", `/api/data/${collection}/${id}`);
170
- }
171
- /**
172
- * Insert a new document into a collection
173
- */
174
- async insert(collection, data) {
175
- return this.client.request("POST", `/api/data/${collection}`, { body: data });
176
- }
177
- /**
178
- * Update an existing document by its ID
179
- */
180
- async update(collection, id, data) {
181
- return this.client.request("PUT", `/api/data/${collection}/${id}`, { body: data });
182
- }
183
- /**
184
- * Delete a document by its ID
185
- */
186
- async delete(collection, id) {
187
- return this.client.request("DELETE", `/api/data/${collection}/${id}`);
188
- }
189
- };
190
-
191
- // src/modules/storage.ts
192
- var StorageModule = class {
193
- constructor(client) {
194
- this.client = client;
195
- }
196
- /**
197
- * Upload a file to storage
198
- */
199
- async upload(file, filename) {
200
- const formData = new FormData();
201
- if (typeof window === "undefined" && typeof Buffer !== "undefined" && Buffer.isBuffer(file)) {
202
- const blob = new Blob([file]);
203
- formData.append("file", blob, filename || "file");
204
- } else {
205
- formData.append("file", file, filename);
206
- }
207
- return this.client.request("POST", "/api/storage/upload", {
208
- body: formData,
209
- isMultipart: true
210
- });
211
- }
212
- /**
213
- * Delete a file from storage by its path
214
- */
215
- async deleteFile(path) {
216
- return this.client.request("DELETE", "/api/storage/file", {
217
- body: { path }
218
- });
219
- }
220
- };
221
-
222
- // src/client.ts
223
- var UrBackendClient = class {
224
- constructor(config) {
225
- this.apiKey = config.apiKey;
226
- this.baseUrl = config.baseUrl || "https://api.urbackend.bitbros.in";
227
- if (typeof window !== "undefined") {
228
- console.warn(
229
- "\u26A0\uFE0F urbackend-sdk: Avoid exposing your API key in client-side code. This can lead to unauthorized access to your account and data."
230
- );
231
- }
232
- }
233
- get auth() {
234
- if (!this._auth) {
235
- this._auth = new AuthModule(this);
236
- }
237
- return this._auth;
238
- }
239
- get db() {
240
- if (!this._db) {
241
- this._db = new DatabaseModule(this);
242
- }
243
- return this._db;
244
- }
245
- get storage() {
246
- if (!this._storage) {
247
- this._storage = new StorageModule(this);
248
- }
249
- return this._storage;
250
- }
251
- /**
252
- * Internal request handler
253
- */
254
- async request(method, path, options = {}) {
255
- const url = `${this.baseUrl}${path}`;
256
- const headers = {
257
- "x-api-key": this.apiKey,
258
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
259
- "Origin": "https://urbackend.bitbros.in",
260
- "Referer": "https://urbackend.bitbros.in/"
261
- };
262
- if (options.token) {
263
- headers["Authorization"] = `Bearer ${options.token}`;
264
- }
265
- let requestBody;
266
- if (options.isMultipart) {
267
- requestBody = options.body;
268
- } else if (options.body) {
269
- headers["Content-Type"] = "application/json";
270
- requestBody = JSON.stringify(options.body);
271
- }
272
- try {
273
- const response = await fetch(url, {
274
- method,
275
- headers,
276
- body: requestBody
277
- });
278
- if (!response.ok) {
279
- throw await parseApiError(response);
280
- }
281
- const contentType = response.headers.get("content-type");
282
- if (contentType && contentType.includes("application/json")) {
283
- const json = await response.json();
284
- return json.data !== void 0 ? json.data : json;
285
- }
286
- return await response.text();
287
- } catch (error) {
288
- if (error instanceof UrBackendError) {
289
- throw error;
290
- }
291
- throw new UrBackendError(
292
- error instanceof Error ? error.message : "Network request failed",
293
- 0,
294
- path
295
- );
296
- }
297
- }
298
- };
299
-
300
- // src/index.ts
301
- function urBackend(config) {
302
- return new UrBackendClient(config);
303
- }
304
- // Annotate the CommonJS export names for ESM import in node:
305
- 0 && (module.exports = {
306
- AuthError,
307
- NotFoundError,
308
- RateLimitError,
309
- StorageError,
310
- UrBackendClient,
311
- UrBackendError,
312
- ValidationError,
313
- parseApiError
314
- });
315
- //# sourceMappingURL=index.cjs.map