@urbackend/sdk 0.1.0 → 0.2.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/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "all",
4
+ "singleQuote": true,
5
+ "printWidth": 100,
6
+ "tabWidth": 2
7
+ }
package/README.md CHANGED
@@ -1,82 +1,85 @@
1
- # urbackend-sdk
2
-
3
- Official TypeScript SDK for [urBackend](https://urbackend.bitbros.in) — the instant Backend-as-a-Service for frontend developers.
4
-
5
- ## Installation
6
- npm install @urbackend/sdk
7
-
8
- ## Quick Start
9
- ```javascript
10
- import urBackend from '@urbackend/sdk';
11
-
12
- const client = urBackend({ apiKey: 'YOUR_API_KEY' });
13
-
14
- // Auth
15
- await client.auth.signUp({ email, password, name });
16
- const { token } = await client.auth.login({ email, password });
17
-
18
- // Database (collections auto-created on first insert)
19
- await client.db.insert('products', { name: 'Chair', price: 99 });
20
- await client.db.getAll('products');
21
- await client.db.update('products', id, { price: 79 });
22
- await client.db.delete('products', id);
23
-
24
- // Storage
25
- const { url } = await client.storage.upload(file);
26
- ```
27
-
28
- ## API Reference
29
-
30
- ### Client initialization
31
- `urBackend({ apiKey: string, baseUrl?: string })`
32
-
33
- ### Auth
34
- | Method | Params | Returns |
35
- |--------|--------|---------|
36
- | signUp | { email, password, name? } | AuthUser |
37
- | login | { email, password } | { token, user } |
38
- | me | token? | AuthUser |
39
- | logout | | void |
40
-
41
- ### Database
42
- | Method | Params | Returns |
43
- |--------|--------|---------|
44
- | getAll<T> | collection | T[] |
45
- | getOne<T> | collection, id | T |
46
- | insert<T> | collection, data | T |
47
- | update<T> | collection, id, data | T |
48
- | delete | collection, id | { deleted: boolean } |
49
-
50
- ### Storage
51
- | Method | Params | Returns |
52
- |--------|--------|---------|
53
- | upload | file, filename? | { url, path } |
54
- | deleteFile | path | { deleted: boolean } |
55
-
56
- ## Error Handling
57
- ```javascript
58
- import { AuthError, NotFoundError, RateLimitError } from '@urbackend/sdk';
59
-
60
- try {
61
- await client.db.getOne('products', id);
62
- } catch (e) {
63
- if (e instanceof NotFoundError) console.log('Not found');
64
- if (e instanceof RateLimitError) console.log('Slow down');
65
- }
66
- ```
67
-
68
- ## TypeScript Support
69
- ```typescript
70
- interface Product { _id: string; name: string; price: number; }
71
- const products = await client.db.getAll<Product>('products');
72
- ```
73
-
74
- ## Limits
75
- - Rate limit: 100 requests / 15 mins per IP
76
- - Database: 50 MB per project
77
- - Storage: 100 MB per project
78
- - File upload: 5 MB per file
79
-
80
- ## Security
81
- ⚠️ Never expose your API key in client-side/browser code.
82
- Use environment variables: `process.env.URBACKEND_API_KEY`
1
+ # urbackend-sdk
2
+
3
+ Official TypeScript SDK for [urBackend](https://urbackend.bitbros.in) — the instant Backend-as-a-Service for frontend developers.
4
+
5
+ ## Installation
6
+ npm install @urbackend/sdk
7
+
8
+ ## Quick Start
9
+ ```javascript
10
+ import urBackend from '@urbackend/sdk';
11
+
12
+ const client = urBackend({ apiKey: 'YOUR_API_KEY' });
13
+
14
+ // Auth
15
+ await client.auth.signUp({ email, password, name });
16
+ await client.auth.login({ email, password });
17
+ await client.auth.me();
18
+
19
+ // Database collections are auto-created on first insert
20
+ await client.db.insert('products', { name: 'Chair', price: 99 });
21
+ await client.db.getAll('products');
22
+ await client.db.getOne('products', id);
23
+ await client.db.update('products', id, { price: 79 });
24
+ await client.db.delete('products', id);
25
+
26
+ // Storage
27
+ await client.storage.upload(file);
28
+ await client.storage.deleteFile(path);
29
+ ```
30
+
31
+ ## API Reference
32
+
33
+ ### Client initialization
34
+ `urBackend({ apiKey: string, baseUrl?: string })`
35
+
36
+ ### Auth
37
+ | Method | Params | Returns |
38
+ |--------|--------|---------|
39
+ | signUp | { email, password, name? } | AuthUser |
40
+ | login | { email, password } | { token, user } |
41
+ | me | token? | AuthUser |
42
+ | logout | | void |
43
+
44
+ ### Database
45
+ | Method | Params | Returns |
46
+ |--------|--------|---------|
47
+ | getAll<T> | collection | T[] |
48
+ | getOne<T> | collection, id | T |
49
+ | insert<T> | collection, data | T |
50
+ | update<T> | collection, id, data | T |
51
+ | delete | collection, id | { deleted: boolean } |
52
+
53
+ ### Storage
54
+ | Method | Params | Returns |
55
+ |--------|--------|---------|
56
+ | upload | file, filename? | { url, path } |
57
+ | deleteFile | path | { deleted: boolean } |
58
+
59
+ ## Error Handling
60
+ ```javascript
61
+ import { AuthError, NotFoundError, RateLimitError } from '@urbackend/sdk';
62
+
63
+ try {
64
+ await client.db.getOne('products', id);
65
+ } catch (e) {
66
+ if (e instanceof NotFoundError) console.log('Not found');
67
+ if (e instanceof RateLimitError) console.log('Slow down');
68
+ }
69
+ ```
70
+
71
+ ## TypeScript Support
72
+ ```typescript
73
+ interface Product { _id: string; name: string; price: number; }
74
+ const products = await client.db.getAll<Product>('products');
75
+ ```
76
+
77
+ ## Limits
78
+ - Rate limit: 100 requests / 15 mins per IP
79
+ - Database: 50 MB per project
80
+ - Storage: 100 MB per project
81
+ - File upload: 5 MB per file
82
+
83
+ ## Security
84
+ ⚠️ Never expose your API key in client-side/browser code.
85
+ Use environment variables: `process.env.URBACKEND_API_KEY`
@@ -0,0 +1,15 @@
1
+ import eslint from '@eslint/js';
2
+ import tseslint from 'typescript-eslint';
3
+
4
+ export default tseslint.config(
5
+ eslint.configs.recommended,
6
+ ...tseslint.configs.recommended,
7
+ {
8
+ rules: {
9
+ '@typescript-eslint/no-explicit-any': 'error',
10
+ },
11
+ },
12
+ {
13
+ ignores: ['dist/**', 'node_modules/**', 'tests/**'],
14
+ },
15
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@urbackend/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Official TypeScript SDK for urBackend BaaS",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -12,16 +12,11 @@
12
12
  "require": "./dist/index.cjs"
13
13
  }
14
14
  },
15
- "files": [
16
- "dist",
17
- "README.md"
18
- ],
19
15
  "scripts": {
20
16
  "build": "tsup",
21
17
  "test": "vitest run --coverage",
22
18
  "lint": "eslint src",
23
- "format": "prettier --write \"src/**/*.ts\"",
24
- "prepublishOnly": "npm run build && npm test"
19
+ "format": "prettier --write \"src/**/*.ts\""
25
20
  },
26
21
  "keywords": [
27
22
  "urbackend",
@@ -53,8 +48,5 @@
53
48
  "typescript": "^5.9.3",
54
49
  "typescript-eslint": "^8.57.2",
55
50
  "vitest": "^4.1.2"
56
- },
57
- "dependencies": {
58
- "@urbackend/sdk": "^1.0.0"
59
51
  }
60
- }
52
+ }
package/src/client.ts ADDED
@@ -0,0 +1,143 @@
1
+ import { UrBackendConfig, RequestOptions } from './types';
2
+ import { UrBackendError, parseApiError } from './errors';
3
+ import { AuthModule } from './modules/auth';
4
+ import { DatabaseModule } from './modules/database';
5
+ import { StorageModule } from './modules/storage';
6
+ import { SchemaModule } from './modules/schema';
7
+ import { MailModule } from './modules/mail';
8
+
9
+ export class UrBackendClient {
10
+ private apiKey: string;
11
+ private baseUrl: string;
12
+ private _auth?: AuthModule;
13
+ private _db?: DatabaseModule;
14
+ private _storage?: StorageModule;
15
+ private _schema?: SchemaModule;
16
+ private _mail?: MailModule;
17
+ private headers: Record<string, string>;
18
+
19
+ constructor(config: UrBackendConfig) {
20
+ this.apiKey = config.apiKey;
21
+ this.baseUrl = config.baseUrl || 'https://api.ub.bitbros.in';
22
+ this.headers = config.headers || {};
23
+
24
+ if (typeof window !== 'undefined' && this.apiKey.startsWith('sk_live_')) {
25
+ console.warn(
26
+ '⚠️ urbackend-sdk: Avoid exposing your Secret Key (sk_live_...) in client-side code. This can lead to unauthorized access to your account and data. Use your Publishable Key (pk_live_...) instead.',
27
+ );
28
+ }
29
+ }
30
+
31
+ get auth(): AuthModule {
32
+ if (!this._auth) {
33
+ this._auth = new AuthModule(this);
34
+ }
35
+ return this._auth;
36
+ }
37
+
38
+ get db(): DatabaseModule {
39
+ if (!this._db) {
40
+ this._db = new DatabaseModule(this);
41
+ }
42
+ return this._db;
43
+ }
44
+
45
+ get storage(): StorageModule {
46
+ if (!this._storage) {
47
+ this._storage = new StorageModule(this);
48
+ }
49
+ return this._storage;
50
+ }
51
+
52
+ get schema(): SchemaModule {
53
+ if (!this._schema) {
54
+ this._schema = new SchemaModule(this);
55
+ }
56
+ return this._schema;
57
+ }
58
+
59
+ get mail(): MailModule {
60
+ if (!this._mail) {
61
+ this._mail = new MailModule(this);
62
+ }
63
+ return this._mail;
64
+ }
65
+
66
+ public getBaseUrl(): string {
67
+ return this.baseUrl;
68
+ }
69
+
70
+ public getApiKey(): string {
71
+ return this.apiKey;
72
+ }
73
+
74
+ /**
75
+ * Internal request handler
76
+ */
77
+ public async request<T>(
78
+ method: string,
79
+ path: string,
80
+ options: RequestOptions = {},
81
+ ): Promise<T> {
82
+ const url = `${this.baseUrl}${path}`;
83
+ const headers: Record<string, string> = {
84
+ 'x-api-key': this.apiKey,
85
+ 'User-Agent': `urbackend-sdk-js/0.2.0`,
86
+ ...this.headers,
87
+ };
88
+
89
+ if (options.token) {
90
+ headers['Authorization'] = `Bearer ${options.token}`;
91
+ }
92
+
93
+ // Merge custom headers from options if provided
94
+ if (options.headers) {
95
+ Object.assign(headers, options.headers);
96
+ }
97
+
98
+ let requestBody: BodyInit | undefined;
99
+
100
+ if (options.isMultipart) {
101
+ // Fetch handles FormData content type and boundary
102
+ requestBody = options.body as FormData;
103
+ } else if (options.body) {
104
+ headers['Content-Type'] = 'application/json';
105
+ requestBody = JSON.stringify(options.body);
106
+ }
107
+
108
+ try {
109
+ const response = await fetch(url, {
110
+ method,
111
+ headers,
112
+ body: requestBody,
113
+ credentials: options.credentials,
114
+ });
115
+
116
+ if (!response.ok) {
117
+ throw await parseApiError(response);
118
+ }
119
+
120
+ const contentType = response.headers.get('content-type');
121
+ if (contentType && contentType.includes('application/json')) {
122
+ const json = await response.json();
123
+ // The API returns { data, success, message }
124
+ // If data is present, return it. If success/message are present but no data, return the whole object (for exchange/logout etc)
125
+ if (json.data !== undefined) {
126
+ return json.data;
127
+ }
128
+ return json;
129
+ }
130
+
131
+ return (await response.text()) as unknown as T;
132
+ } catch (error) {
133
+ if (error instanceof UrBackendError) {
134
+ throw error;
135
+ }
136
+ throw new UrBackendError(
137
+ error instanceof Error ? error.message : 'Network request failed',
138
+ 0,
139
+ path,
140
+ );
141
+ }
142
+ }
143
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,90 @@
1
+ export class UrBackendError extends Error {
2
+ constructor(
3
+ public message: string,
4
+ public statusCode: number,
5
+ public endpoint: string,
6
+ ) {
7
+ super(message);
8
+ this.name = 'UrBackendError';
9
+ }
10
+ }
11
+
12
+ export class AuthError extends UrBackendError {
13
+ constructor(message: string, statusCode: number, endpoint: string) {
14
+ super(message, statusCode, endpoint);
15
+ this.name = 'AuthError';
16
+ }
17
+ }
18
+
19
+ export class NotFoundError extends UrBackendError {
20
+ constructor(message: string, endpoint: string) {
21
+ super(message, 404, endpoint);
22
+ this.name = 'NotFoundError';
23
+ }
24
+ }
25
+
26
+ export class RateLimitError extends UrBackendError {
27
+ public retryAfter?: number;
28
+
29
+ constructor(message: string, endpoint: string, retryAfter?: number) {
30
+ super(message, 429, endpoint);
31
+ this.name = 'RateLimitError';
32
+ this.retryAfter = retryAfter;
33
+ }
34
+ }
35
+
36
+ export class StorageError extends UrBackendError {
37
+ constructor(message: string, statusCode: number, endpoint: string) {
38
+ super(message, statusCode, endpoint);
39
+ this.name = 'StorageError';
40
+ }
41
+ }
42
+
43
+ export class ValidationError extends UrBackendError {
44
+ constructor(message: string, endpoint: string) {
45
+ super(message, 400, endpoint);
46
+ this.name = 'ValidationError';
47
+ }
48
+ }
49
+
50
+ export async function parseApiError(response: Response): Promise<UrBackendError> {
51
+ const endpoint = new URL(response.url).pathname;
52
+ let message = 'An unexpected error occurred';
53
+ let data: unknown;
54
+
55
+ try {
56
+ data = await response.json();
57
+ if (typeof data === 'object' && data !== null && 'message' in data) {
58
+ message = (data as { message: string }).message || message;
59
+ }
60
+ } catch {
61
+ // If not JSON, use status text
62
+ message = response.statusText || message;
63
+ }
64
+
65
+ const status = response.status;
66
+
67
+ if (status === 401 || status === 403) {
68
+ return new AuthError(message, status, endpoint);
69
+ }
70
+
71
+ if (status === 404) {
72
+ return new NotFoundError(message, endpoint);
73
+ }
74
+
75
+ if (status === 429) {
76
+ const retryAfter = response.headers.get('Retry-After');
77
+ return new RateLimitError(message, endpoint, retryAfter ? parseInt(retryAfter, 10) : undefined);
78
+ }
79
+
80
+ if (status === 400) {
81
+ return new ValidationError(message, endpoint);
82
+ }
83
+
84
+ // Default for 5xx or other 4xx
85
+ if (endpoint.includes('/api/storage')) {
86
+ return new StorageError(message, status, endpoint);
87
+ }
88
+
89
+ return new UrBackendError(message, status, endpoint);
90
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { UrBackendClient } from './client';
2
+ import { UrBackendConfig } from './types';
3
+ import { AuthModule } from './modules/auth';
4
+ import { DatabaseModule } from './modules/database';
5
+ import { StorageModule } from './modules/storage';
6
+ import { SchemaModule } from './modules/schema';
7
+ import { MailModule } from './modules/mail';
8
+
9
+ export * from './types';
10
+ export * from './errors';
11
+ export { UrBackendClient, AuthModule, DatabaseModule, StorageModule, SchemaModule, MailModule };
12
+
13
+ /**
14
+ * Factory function to create a new urBackend client
15
+ */
16
+ export default function urBackend(config: UrBackendConfig): UrBackendClient {
17
+ return new UrBackendClient(config);
18
+ }
@@ -0,0 +1,209 @@
1
+ import { UrBackendClient } from '../client';
2
+ import {
3
+ AuthUser,
4
+ AuthResponse,
5
+ SignUpPayload,
6
+ LoginPayload,
7
+ UpdateProfilePayload,
8
+ ChangePasswordPayload,
9
+ VerifyEmailPayload,
10
+ ResendOtpPayload,
11
+ RequestPasswordResetPayload,
12
+ ResetPasswordPayload,
13
+ SocialExchangePayload,
14
+ SocialExchangeResponse,
15
+ RequestOptions,
16
+ } from '../types';
17
+ import { AuthError } from '../errors';
18
+
19
+ export class AuthModule {
20
+ private sessionToken?: string;
21
+
22
+ constructor(private client: UrBackendClient) {}
23
+
24
+ /**
25
+ * Create a new user account
26
+ */
27
+ public async signUp(payload: SignUpPayload): Promise<AuthUser> {
28
+ return this.client.request<AuthUser>('POST', '/api/userAuth/signup', { body: payload });
29
+ }
30
+
31
+ /**
32
+ * Log in an existing user and store the session token
33
+ */
34
+ public async login(payload: LoginPayload): Promise<AuthResponse> {
35
+ const response = await this.client.request<AuthResponse>('POST', '/api/userAuth/login', {
36
+ body: payload,
37
+ });
38
+
39
+ this.sessionToken = response.accessToken || response.token;
40
+
41
+ if (!response.accessToken && response.token) {
42
+ console.warn(
43
+ 'urbackend-sdk: The server returned "token" which is deprecated. Please update your backend to return "accessToken".',
44
+ );
45
+ }
46
+
47
+ return response;
48
+ }
49
+
50
+ /**
51
+ * Get the current authenticated user's profile
52
+ */
53
+ public async me(token?: string): Promise<AuthUser> {
54
+ const activeToken = token || this.sessionToken;
55
+
56
+ if (!activeToken) {
57
+ throw new AuthError(
58
+ 'Authentication token is required for /me endpoint',
59
+ 401,
60
+ '/api/userAuth/me',
61
+ );
62
+ }
63
+
64
+ return this.client.request<AuthUser>('GET', '/api/userAuth/me', { token: activeToken });
65
+ }
66
+
67
+ /**
68
+ * Update the current authenticated user's profile
69
+ */
70
+ public async updateProfile(payload: UpdateProfilePayload, token?: string): Promise<{ message: string }> {
71
+ const activeToken = token || this.sessionToken;
72
+ if (!activeToken) {
73
+ throw new AuthError('Authentication token is required to update profile', 401, '/api/userAuth/update-profile');
74
+ }
75
+ return this.client.request<{ message: string }>('PUT', '/api/userAuth/update-profile', {
76
+ body: payload,
77
+ token: activeToken,
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Change the current authenticated user's password
83
+ */
84
+ public async changePassword(payload: ChangePasswordPayload, token?: string): Promise<{ message: string }> {
85
+ const activeToken = token || this.sessionToken;
86
+ if (!activeToken) {
87
+ throw new AuthError('Authentication token is required to change password', 401, '/api/userAuth/change-password');
88
+ }
89
+ return this.client.request<{ message: string }>('PUT', '/api/userAuth/change-password', {
90
+ body: payload,
91
+ token: activeToken,
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Verify user email with OTP
97
+ */
98
+ public async verifyEmail(payload: VerifyEmailPayload): Promise<{ message: string }> {
99
+ return this.client.request<{ message: string }>('POST', '/api/userAuth/verify-email', {
100
+ body: payload,
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Resend verification OTP
106
+ */
107
+ public async resendVerificationOtp(payload: ResendOtpPayload): Promise<{ message: string }> {
108
+ return this.client.request<{ message: string }>('POST', '/api/userAuth/resend-verification-otp', {
109
+ body: payload,
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Request password reset OTP
115
+ */
116
+ public async requestPasswordReset(payload: RequestPasswordResetPayload): Promise<{ message: string }> {
117
+ return this.client.request<{ message: string }>('POST', '/api/userAuth/request-password-reset', {
118
+ body: payload,
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Reset user password with OTP
124
+ */
125
+ public async resetPassword(payload: ResetPasswordPayload): Promise<{ message: string }> {
126
+ return this.client.request<{ message: string }>('POST', '/api/userAuth/reset-password', {
127
+ body: payload,
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Get public-safe profile by username
133
+ */
134
+ public async publicProfile(username: string): Promise<AuthUser> {
135
+ return this.client.request<AuthUser>('GET', `/api/userAuth/public/${username}`);
136
+ }
137
+
138
+ /**
139
+ * Refresh the access token
140
+ * @param refreshToken Optional refresh token for header mode. If omitted, uses cookie mode.
141
+ */
142
+ public async refreshToken(refreshToken?: string): Promise<AuthResponse> {
143
+ const options: RequestOptions = {};
144
+ if (refreshToken) {
145
+ options.headers = { 'x-refresh-token': refreshToken, 'x-refresh-token-mode': 'header' };
146
+ } else {
147
+ options.credentials = 'include';
148
+ }
149
+
150
+ const response = await this.client.request<AuthResponse>('POST', '/api/userAuth/refresh-token', options);
151
+ this.sessionToken = response.accessToken || response.token;
152
+ return response;
153
+ }
154
+
155
+ /**
156
+ * Returns the start URL for social authentication.
157
+ * Redirect the user's browser to this URL to begin the flow.
158
+ */
159
+ public socialStart(provider: 'github' | 'google'): string {
160
+ return `${this.client.getBaseUrl()}/api/userAuth/social/${provider}/start?key=${this.client.getApiKey()}`;
161
+ }
162
+
163
+ /**
164
+ * Exchange social auth rtCode for a refresh token
165
+ */
166
+ public async socialExchange(payload: SocialExchangePayload): Promise<SocialExchangeResponse> {
167
+ return this.client.request<SocialExchangeResponse>('POST', '/api/userAuth/social/exchange', {
168
+ body: payload,
169
+ });
170
+ }
171
+
172
+ /**
173
+ * Revoke the current session and clear local state
174
+ */
175
+ public async logout(token?: string): Promise<{ success: boolean; message: string }> {
176
+ const activeToken = token || this.sessionToken;
177
+ let result = { success: true, message: 'Logged out locally' };
178
+
179
+ if (activeToken) {
180
+ try {
181
+ result = await this.client.request<{ success: boolean; message: string }>(
182
+ 'POST',
183
+ '/api/userAuth/logout',
184
+ { token: activeToken, credentials: 'include' },
185
+ );
186
+ } catch (e) {
187
+ // Silently fail if server logout fails, we still want to clear local state
188
+ console.warn('urbackend-sdk: Server logout failed', e);
189
+ }
190
+ }
191
+
192
+ this.sessionToken = undefined;
193
+ return result;
194
+ }
195
+
196
+ /**
197
+ * Manually set the session token (e.g. after social auth exchange)
198
+ */
199
+ public setToken(token: string): void {
200
+ this.sessionToken = token;
201
+ }
202
+
203
+ /**
204
+ * Get the current stored session token
205
+ */
206
+ public getToken(): string | undefined {
207
+ return this.sessionToken;
208
+ }
209
+ }