@withvlibe/base-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/dist/index.mjs ADDED
@@ -0,0 +1,673 @@
1
+ // src/VlibeBaseDatabase.ts
2
+ import { createClient } from "@supabase/supabase-js";
3
+ var DEFAULT_BASE_URL = "https://vlibe.app";
4
+ var DEFAULT_SUPABASE_URL = "https://qoblysxhxtifxhgdlzgl.supabase.co";
5
+ function resolveBaseUrl(configBaseUrl) {
6
+ if (configBaseUrl) return configBaseUrl;
7
+ if (typeof process !== "undefined" && process.env) {
8
+ if (process.env.VLIBE_BASE_URL) return process.env.VLIBE_BASE_URL;
9
+ if (process.env.NEXT_PUBLIC_VLIBE_BASE_URL) return process.env.NEXT_PUBLIC_VLIBE_BASE_URL;
10
+ }
11
+ return DEFAULT_BASE_URL;
12
+ }
13
+ var VlibeBaseDatabase = class {
14
+ /**
15
+ * Create a new VlibeBaseDatabase instance
16
+ *
17
+ * @param config - Database configuration
18
+ * @throws Error if projectId or databaseToken is missing
19
+ */
20
+ constructor(config) {
21
+ this.supabase = null;
22
+ this.subscriptions = /* @__PURE__ */ new Map();
23
+ if (!config.projectId) {
24
+ throw new Error("VlibeBaseDatabase: projectId is required");
25
+ }
26
+ if (!config.databaseToken) {
27
+ throw new Error("VlibeBaseDatabase: databaseToken is required");
28
+ }
29
+ this.projectId = config.projectId;
30
+ this.databaseToken = config.databaseToken;
31
+ this.baseUrl = resolveBaseUrl(config.baseUrl);
32
+ this.supabaseUrl = config.supabaseUrl || DEFAULT_SUPABASE_URL;
33
+ }
34
+ /**
35
+ * Initialize Supabase client for real-time subscriptions
36
+ */
37
+ initSupabase() {
38
+ if (this.supabase) return;
39
+ this.supabase = createClient(this.supabaseUrl, this.databaseToken, {
40
+ realtime: {
41
+ params: {
42
+ eventsPerSecond: 10
43
+ }
44
+ }
45
+ });
46
+ }
47
+ /**
48
+ * Make an authenticated API request
49
+ */
50
+ async apiRequest(endpoint, options = {}) {
51
+ const url = `${this.baseUrl}/api/database/${this.projectId}${endpoint}`;
52
+ const response = await fetch(url, {
53
+ ...options,
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ Authorization: `Bearer ${this.databaseToken}`,
57
+ ...options.headers
58
+ }
59
+ });
60
+ const data = await response.json();
61
+ if (!response.ok) {
62
+ throw new Error(data.error || data.message || "API request failed");
63
+ }
64
+ return data;
65
+ }
66
+ // ============================================================================
67
+ // Table Operations
68
+ // ============================================================================
69
+ /**
70
+ * Create a new table
71
+ */
72
+ async createTable(name, schema) {
73
+ const response = await this.apiRequest(
74
+ "/tables",
75
+ {
76
+ method: "POST",
77
+ body: JSON.stringify({ name, schema })
78
+ }
79
+ );
80
+ return response.data;
81
+ }
82
+ /**
83
+ * Get table information
84
+ */
85
+ async getTable(name) {
86
+ try {
87
+ const response = await this.apiRequest(
88
+ `/tables/${name}`
89
+ );
90
+ return response.data;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+ /**
96
+ * List all tables
97
+ */
98
+ async listTables() {
99
+ const response = await this.apiRequest(
100
+ "/tables"
101
+ );
102
+ return response.data;
103
+ }
104
+ /**
105
+ * Delete a table
106
+ */
107
+ async deleteTable(name) {
108
+ await this.apiRequest(`/tables/${name}`, { method: "DELETE" });
109
+ return true;
110
+ }
111
+ // ============================================================================
112
+ // CRUD Operations
113
+ // ============================================================================
114
+ /**
115
+ * Insert a document into a collection
116
+ */
117
+ async insert(collection, data) {
118
+ const response = await this.apiRequest(
119
+ `/collections/${collection}`,
120
+ {
121
+ method: "POST",
122
+ body: JSON.stringify(data)
123
+ }
124
+ );
125
+ return response.data;
126
+ }
127
+ /**
128
+ * Query documents from a collection
129
+ */
130
+ async query(collection, options = {}) {
131
+ const params = new URLSearchParams();
132
+ if (options.limit) params.set("limit", String(options.limit));
133
+ if (options.offset) params.set("offset", String(options.offset));
134
+ if (options.orderBy) params.set("orderBy", options.orderBy);
135
+ if (options.orderDirection) params.set("orderDirection", options.orderDirection);
136
+ if (options.where) params.set("where", JSON.stringify(options.where));
137
+ const queryString = params.toString();
138
+ const endpoint = `/collections/${collection}${queryString ? `?${queryString}` : ""}`;
139
+ const response = await this.apiRequest(endpoint);
140
+ return response.data;
141
+ }
142
+ /**
143
+ * Get a single document by ID
144
+ */
145
+ async get(collection, id) {
146
+ try {
147
+ const response = await this.apiRequest(
148
+ `/collections/${collection}/${id}`
149
+ );
150
+ return response.data;
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+ /**
156
+ * Update a document
157
+ */
158
+ async update(collection, id, data) {
159
+ const response = await this.apiRequest(
160
+ `/collections/${collection}/${id}`,
161
+ {
162
+ method: "PATCH",
163
+ body: JSON.stringify(data)
164
+ }
165
+ );
166
+ return response.data;
167
+ }
168
+ /**
169
+ * Delete a document
170
+ */
171
+ async delete(collection, id) {
172
+ await this.apiRequest(`/collections/${collection}/${id}`, {
173
+ method: "DELETE"
174
+ });
175
+ return true;
176
+ }
177
+ /**
178
+ * Count documents in a collection
179
+ */
180
+ async count(collection, where) {
181
+ const params = new URLSearchParams();
182
+ if (where) params.set("where", JSON.stringify(where));
183
+ const queryString = params.toString();
184
+ const endpoint = `/collections/${collection}/count${queryString ? `?${queryString}` : ""}`;
185
+ const response = await this.apiRequest(
186
+ endpoint
187
+ );
188
+ return response.data.count;
189
+ }
190
+ // ============================================================================
191
+ // Key-Value Store
192
+ // ============================================================================
193
+ /**
194
+ * Set a key-value pair
195
+ */
196
+ async setKV(key, value) {
197
+ await this.apiRequest("/kv", {
198
+ method: "POST",
199
+ body: JSON.stringify({ key, value })
200
+ });
201
+ }
202
+ /**
203
+ * Get a value by key
204
+ */
205
+ async getKV(key) {
206
+ try {
207
+ const response = await this.apiRequest(
208
+ `/kv/${key}`
209
+ );
210
+ return response.data.value;
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+ /**
216
+ * Delete a key-value pair
217
+ */
218
+ async deleteKV(key) {
219
+ await this.apiRequest(`/kv/${key}`, { method: "DELETE" });
220
+ return true;
221
+ }
222
+ // ============================================================================
223
+ // Real-time Subscriptions
224
+ // ============================================================================
225
+ /**
226
+ * Subscribe to real-time changes on a collection
227
+ */
228
+ subscribe(collection, callback) {
229
+ this.initSupabase();
230
+ const channelName = `base:${this.projectId}:${collection}`;
231
+ const tableName = `project_${this.projectId.replace(/-/g, "_")}_${collection}`;
232
+ const channel = this.supabase.channel(channelName).on(
233
+ "postgres_changes",
234
+ {
235
+ event: "*",
236
+ schema: "public",
237
+ table: tableName
238
+ },
239
+ (payload) => {
240
+ callback({
241
+ eventType: payload.eventType,
242
+ new: payload.new,
243
+ old: payload.old,
244
+ table: collection
245
+ });
246
+ }
247
+ ).subscribe();
248
+ this.subscriptions.set(channelName, channel);
249
+ return {
250
+ unsubscribe: () => {
251
+ channel.unsubscribe();
252
+ this.subscriptions.delete(channelName);
253
+ }
254
+ };
255
+ }
256
+ /**
257
+ * Unsubscribe from all subscriptions
258
+ */
259
+ unsubscribeAll() {
260
+ for (const channel of this.subscriptions.values()) {
261
+ channel.unsubscribe();
262
+ }
263
+ this.subscriptions.clear();
264
+ }
265
+ // ============================================================================
266
+ // Utilities
267
+ // ============================================================================
268
+ /**
269
+ * Get the project ID
270
+ */
271
+ getProjectId() {
272
+ return this.projectId;
273
+ }
274
+ /**
275
+ * Get the base URL
276
+ */
277
+ getBaseUrl() {
278
+ return this.baseUrl;
279
+ }
280
+ };
281
+
282
+ // src/VlibeBaseAuth.ts
283
+ var DEFAULT_BASE_URL2 = "https://vlibe.app";
284
+ function resolveBaseUrl2(configBaseUrl) {
285
+ if (configBaseUrl) return configBaseUrl;
286
+ if (typeof process !== "undefined" && process.env) {
287
+ if (process.env.VLIBE_BASE_URL) return process.env.VLIBE_BASE_URL;
288
+ if (process.env.NEXT_PUBLIC_VLIBE_BASE_URL) return process.env.NEXT_PUBLIC_VLIBE_BASE_URL;
289
+ }
290
+ return DEFAULT_BASE_URL2;
291
+ }
292
+ var VlibeBaseAuth = class {
293
+ /**
294
+ * Create a new VlibeBaseAuth instance
295
+ *
296
+ * @param config - Authentication configuration
297
+ * @throws Error if appId or appSecret is missing
298
+ */
299
+ constructor(config) {
300
+ if (!config.appId) {
301
+ throw new Error("VlibeBaseAuth: appId is required");
302
+ }
303
+ if (!config.appSecret) {
304
+ throw new Error("VlibeBaseAuth: appSecret is required");
305
+ }
306
+ this.appId = config.appId;
307
+ this.appSecret = config.appSecret;
308
+ this.baseUrl = resolveBaseUrl2(config.baseUrl);
309
+ }
310
+ /**
311
+ * Verify a session token and get user information
312
+ *
313
+ * @param token - The session token from the SSO callback
314
+ * @returns The user object if valid, null otherwise
315
+ */
316
+ async verifySession(token) {
317
+ if (!token) {
318
+ return null;
319
+ }
320
+ try {
321
+ const response = await fetch(`${this.baseUrl}/api/auth/sso/verify`, {
322
+ method: "POST",
323
+ headers: {
324
+ "Content-Type": "application/json"
325
+ },
326
+ body: JSON.stringify({
327
+ token,
328
+ appId: this.appId,
329
+ appSecret: this.appSecret,
330
+ appType: "base"
331
+ // Indicate this is a Base app
332
+ })
333
+ });
334
+ if (!response.ok) {
335
+ return null;
336
+ }
337
+ const data = await response.json();
338
+ if (data.valid && data.user) {
339
+ return data.user;
340
+ }
341
+ return null;
342
+ } catch (error) {
343
+ console.error("VlibeBaseAuth: Failed to verify session:", error);
344
+ return null;
345
+ }
346
+ }
347
+ /**
348
+ * Generate the SSO login URL to redirect unauthenticated users
349
+ *
350
+ * @param redirectPath - The path to redirect to after login
351
+ * @returns The full Vlibe SSO URL
352
+ */
353
+ getLoginUrl(redirectPath = "/") {
354
+ const params = new URLSearchParams({
355
+ app_id: this.appId,
356
+ app_type: "base",
357
+ redirect: redirectPath
358
+ });
359
+ return `${this.baseUrl}/api/auth/sso?${params.toString()}`;
360
+ }
361
+ /**
362
+ * Generate the logout URL
363
+ *
364
+ * @param redirectPath - The path to redirect to after logout
365
+ * @returns The logout URL
366
+ */
367
+ getLogoutUrl(redirectPath = "/") {
368
+ const params = new URLSearchParams({
369
+ app_id: this.appId,
370
+ redirect: redirectPath
371
+ });
372
+ return `${this.baseUrl}/api/auth/sso/logout?${params.toString()}`;
373
+ }
374
+ /**
375
+ * Check if user has access to a specific feature
376
+ *
377
+ * @param user - The Vlibe user object
378
+ * @param feature - The feature name to check
379
+ * @returns True if user has access to the feature
380
+ */
381
+ hasFeature(user, feature) {
382
+ if (!user) return false;
383
+ if (user.subscriptionType === "platform") {
384
+ return true;
385
+ }
386
+ if (user.appAccess?.features) {
387
+ return user.appAccess.features.includes("*") || user.appAccess.features.includes(feature);
388
+ }
389
+ return false;
390
+ }
391
+ /**
392
+ * Check if user has an active subscription
393
+ *
394
+ * @param user - The Vlibe user object
395
+ * @returns True if user has any active subscription
396
+ */
397
+ hasSubscription(user) {
398
+ return user?.subscriptionType !== null;
399
+ }
400
+ /**
401
+ * Get the subscription tier for a user
402
+ *
403
+ * @param user - The Vlibe user object
404
+ * @returns The tier name or null
405
+ */
406
+ getTier(user) {
407
+ if (!user) return null;
408
+ if (user.subscriptionType === "platform") return "platform";
409
+ return user.appAccess?.tier || null;
410
+ }
411
+ /**
412
+ * Get the app ID
413
+ */
414
+ getAppId() {
415
+ return this.appId;
416
+ }
417
+ /**
418
+ * Get the base URL
419
+ */
420
+ getBaseUrl() {
421
+ return this.baseUrl;
422
+ }
423
+ };
424
+
425
+ // src/VlibeBasePayments.ts
426
+ var DEFAULT_BASE_URL3 = "https://vlibe.app";
427
+ function resolveBaseUrl3(configBaseUrl) {
428
+ if (configBaseUrl) return configBaseUrl;
429
+ if (typeof process !== "undefined" && process.env) {
430
+ if (process.env.VLIBE_BASE_URL) return process.env.VLIBE_BASE_URL;
431
+ if (process.env.NEXT_PUBLIC_VLIBE_BASE_URL) return process.env.NEXT_PUBLIC_VLIBE_BASE_URL;
432
+ }
433
+ return DEFAULT_BASE_URL3;
434
+ }
435
+ var VlibeBasePayments = class {
436
+ /**
437
+ * Create a new VlibeBasePayments instance
438
+ *
439
+ * @param config - Payments configuration
440
+ * @throws Error if appId or appSecret is missing
441
+ *
442
+ * @remarks
443
+ * This class should only be used server-side. Never expose your
444
+ * appSecret to the client.
445
+ */
446
+ constructor(config) {
447
+ if (!config.appId) {
448
+ throw new Error("VlibeBasePayments: appId is required");
449
+ }
450
+ if (!config.appSecret) {
451
+ throw new Error("VlibeBasePayments: appSecret is required");
452
+ }
453
+ if (typeof window !== "undefined") {
454
+ console.warn(
455
+ "VlibeBasePayments: This class should only be used server-side. Never expose your appSecret to the client."
456
+ );
457
+ }
458
+ this.appId = config.appId;
459
+ this.appSecret = config.appSecret;
460
+ this.baseUrl = resolveBaseUrl3(config.baseUrl);
461
+ }
462
+ /**
463
+ * Make an authenticated API request
464
+ */
465
+ async apiRequest(endpoint, options = {}) {
466
+ const url = `${this.baseUrl}/api/base/payments${endpoint}`;
467
+ const response = await fetch(url, {
468
+ ...options,
469
+ headers: {
470
+ "Content-Type": "application/json",
471
+ "X-App-Id": this.appId,
472
+ "X-App-Secret": this.appSecret,
473
+ ...options.headers
474
+ }
475
+ });
476
+ const data = await response.json();
477
+ if (!response.ok) {
478
+ throw new Error(data.error || data.message || "Payment API request failed");
479
+ }
480
+ return data;
481
+ }
482
+ // ============================================================================
483
+ // Stripe Connect
484
+ // ============================================================================
485
+ /**
486
+ * Get Stripe Connect onboarding URL
487
+ *
488
+ * Redirect users to this URL to connect their Stripe account.
489
+ *
490
+ * @param returnUrl - URL to return to after onboarding
491
+ * @returns The Stripe Connect onboarding URL
492
+ */
493
+ async getConnectOnboardingUrl(returnUrl) {
494
+ const response = await this.apiRequest(
495
+ "/connect/onboard",
496
+ {
497
+ method: "POST",
498
+ body: JSON.stringify({ returnUrl })
499
+ }
500
+ );
501
+ return response.data.url;
502
+ }
503
+ /**
504
+ * Check Stripe Connect status
505
+ *
506
+ * @returns The current Stripe Connect status
507
+ */
508
+ async getConnectStatus() {
509
+ const response = await this.apiRequest(
510
+ "/connect/status"
511
+ );
512
+ return response.data;
513
+ }
514
+ // ============================================================================
515
+ // Checkout
516
+ // ============================================================================
517
+ /**
518
+ * Create a checkout session for one-time payments
519
+ *
520
+ * @param options - Checkout options
521
+ * @returns The checkout session with URL to redirect user
522
+ *
523
+ * @remarks
524
+ * Transaction fees are automatically calculated based on your plan:
525
+ * - Free plan: 2% of transaction
526
+ * - Premium plan: 0.5% of transaction
527
+ */
528
+ async createCheckout(options) {
529
+ const response = await this.apiRequest(
530
+ "/checkout",
531
+ {
532
+ method: "POST",
533
+ body: JSON.stringify({
534
+ amount: options.amount,
535
+ currency: options.currency || "usd",
536
+ userId: options.userId,
537
+ userEmail: options.userEmail,
538
+ description: options.description,
539
+ metadata: options.metadata,
540
+ successUrl: options.successUrl,
541
+ cancelUrl: options.cancelUrl
542
+ })
543
+ }
544
+ );
545
+ return response.data;
546
+ }
547
+ /**
548
+ * Get checkout session by ID
549
+ *
550
+ * @param sessionId - The checkout session ID
551
+ * @returns The checkout session details
552
+ */
553
+ async getCheckoutSession(sessionId) {
554
+ try {
555
+ const response = await this.apiRequest(
556
+ `/checkout/${sessionId}`
557
+ );
558
+ return response.data;
559
+ } catch {
560
+ return null;
561
+ }
562
+ }
563
+ // ============================================================================
564
+ // Transactions
565
+ // ============================================================================
566
+ /**
567
+ * Get all transactions
568
+ *
569
+ * @param options - Query options
570
+ * @returns List of transactions
571
+ */
572
+ async getTransactions(options = {}) {
573
+ const params = new URLSearchParams();
574
+ if (options.limit) params.set("limit", String(options.limit));
575
+ if (options.offset) params.set("offset", String(options.offset));
576
+ if (options.status) params.set("status", options.status);
577
+ const queryString = params.toString();
578
+ const endpoint = `/transactions${queryString ? `?${queryString}` : ""}`;
579
+ const response = await this.apiRequest(
580
+ endpoint
581
+ );
582
+ return response.data;
583
+ }
584
+ /**
585
+ * Get a single transaction
586
+ *
587
+ * @param transactionId - The transaction ID
588
+ * @returns The transaction details
589
+ */
590
+ async getTransaction(transactionId) {
591
+ try {
592
+ const response = await this.apiRequest(
593
+ `/transactions/${transactionId}`
594
+ );
595
+ return response.data;
596
+ } catch {
597
+ return null;
598
+ }
599
+ }
600
+ /**
601
+ * Get transaction summary/stats
602
+ *
603
+ * @returns Transaction statistics
604
+ */
605
+ async getTransactionStats() {
606
+ const response = await this.apiRequest("/transactions/stats");
607
+ return response.data;
608
+ }
609
+ // ============================================================================
610
+ // Refunds
611
+ // ============================================================================
612
+ /**
613
+ * Create a refund
614
+ *
615
+ * @param options - Refund options
616
+ * @returns The updated transaction
617
+ *
618
+ * @remarks
619
+ * Note: Transaction fees are not refunded when you issue a refund.
620
+ */
621
+ async createRefund(options) {
622
+ const response = await this.apiRequest(
623
+ "/refunds",
624
+ {
625
+ method: "POST",
626
+ body: JSON.stringify(options)
627
+ }
628
+ );
629
+ return response.data;
630
+ }
631
+ // ============================================================================
632
+ // Utilities
633
+ // ============================================================================
634
+ /**
635
+ * Calculate the Vlibe fee for an amount
636
+ *
637
+ * @param amount - Amount in cents
638
+ * @param plan - The Base plan ('free' or 'premium')
639
+ * @returns The fee amount in cents
640
+ */
641
+ calculateFee(amount, plan = "free") {
642
+ const feeRate = plan === "premium" ? 5e-3 : 0.02;
643
+ return Math.round(amount * feeRate);
644
+ }
645
+ /**
646
+ * Calculate net amount after fees
647
+ *
648
+ * @param amount - Gross amount in cents
649
+ * @param plan - The Base plan ('free' or 'premium')
650
+ * @returns The net amount in cents
651
+ */
652
+ calculateNetAmount(amount, plan = "free") {
653
+ const fee = this.calculateFee(amount, plan);
654
+ return amount - fee;
655
+ }
656
+ /**
657
+ * Get the app ID
658
+ */
659
+ getAppId() {
660
+ return this.appId;
661
+ }
662
+ /**
663
+ * Get the base URL
664
+ */
665
+ getBaseUrl() {
666
+ return this.baseUrl;
667
+ }
668
+ };
669
+ export {
670
+ VlibeBaseAuth,
671
+ VlibeBaseDatabase,
672
+ VlibeBasePayments
673
+ };