@tspvivek/baasix-sdk 0.1.0-alpha.9 → 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.
package/README.md CHANGED
@@ -558,6 +558,7 @@ await baasix.schemas.create({
558
558
 
559
559
  ```typescript
560
560
  // Many-to-One (BelongsTo)
561
+ // Auto-creates index on foreign key column
561
562
  await baasix.schemas.createRelationship('products', {
562
563
  type: 'M2O',
563
564
  target: 'categories',
@@ -566,14 +567,40 @@ await baasix.schemas.createRelationship('products', {
566
567
  });
567
568
 
568
569
  // Many-to-Many
570
+ // Auto-generates junction table: products_tags_tags_junction
569
571
  await baasix.schemas.createRelationship('products', {
570
572
  type: 'M2M',
571
573
  target: 'tags',
572
574
  name: 'tags',
573
575
  alias: 'products',
574
576
  });
577
+
578
+ // Many-to-Many with custom junction table name
579
+ // Useful when auto-generated name exceeds PostgreSQL's 63 char limit
580
+ await baasix.schemas.createRelationship('products', {
581
+ type: 'M2M',
582
+ target: 'tags',
583
+ name: 'tags',
584
+ alias: 'products',
585
+ through: 'product_tags', // Custom junction table name (max 63 chars)
586
+ });
587
+
588
+ // Many-to-Any (Polymorphic)
589
+ await baasix.schemas.createRelationship('comments', {
590
+ type: 'M2A',
591
+ name: 'commentable',
592
+ tables: ['posts', 'products'],
593
+ alias: 'comments',
594
+ through: 'comment_refs', // Optional custom junction table name
595
+ });
575
596
  ```
576
597
 
598
+ #### Junction Tables (M2M/M2A)
599
+ - **Auto-generated name**: `{source}_{target}_{name}_junction`
600
+ - **Custom name**: Use `through` property (max 63 characters for PostgreSQL)
601
+ - **Schema property**: Junction tables have `isJunction: true` in their schema
602
+ - **Auto-indexed**: Foreign key columns are automatically indexed
603
+
577
604
  ### Indexes
578
605
 
579
606
  ```typescript
@@ -374,6 +374,11 @@ interface SchemaDefinition {
374
374
  name: string;
375
375
  timestamps?: boolean;
376
376
  paranoid?: boolean;
377
+ /**
378
+ * True for M2M/M2A junction tables (system-generated)
379
+ * Junction tables are auto-created when defining M2M or M2A relationships
380
+ */
381
+ isJunction?: boolean;
377
382
  fields: Record<string, FieldDefinition>;
378
383
  indexes?: IndexDefinition[];
379
384
  }
@@ -390,8 +395,15 @@ interface RelationshipDefinition {
390
395
  target: string;
391
396
  name: string;
392
397
  alias?: string;
398
+ /**
399
+ * Custom junction table name for M2M/M2A relationships (max 63 chars for PostgreSQL)
400
+ * If not provided, auto-generated as: {source}_{target}_{name}_junction
401
+ * @example "product_tags" or "comment_refs"
402
+ */
403
+ through?: string;
393
404
  onDelete?: "CASCADE" | "RESTRICT" | "SET NULL";
394
405
  onUpdate?: "CASCADE" | "RESTRICT" | "SET NULL";
406
+ /** Target tables for M2A (polymorphic) relationships */
395
407
  tables?: string[];
396
408
  }
397
409
  interface SchemaInfo {
@@ -425,6 +437,8 @@ interface UploadOptions {
425
437
  isPublic?: boolean;
426
438
  metadata?: Record<string, unknown>;
427
439
  onProgress?: (progress: number) => void;
440
+ /** Request timeout in milliseconds (default: 30000). Set to 0 for no timeout. */
441
+ timeout?: number;
428
442
  }
429
443
  interface AssetTransformOptions {
430
444
  width?: number;
@@ -374,6 +374,11 @@ interface SchemaDefinition {
374
374
  name: string;
375
375
  timestamps?: boolean;
376
376
  paranoid?: boolean;
377
+ /**
378
+ * True for M2M/M2A junction tables (system-generated)
379
+ * Junction tables are auto-created when defining M2M or M2A relationships
380
+ */
381
+ isJunction?: boolean;
377
382
  fields: Record<string, FieldDefinition>;
378
383
  indexes?: IndexDefinition[];
379
384
  }
@@ -390,8 +395,15 @@ interface RelationshipDefinition {
390
395
  target: string;
391
396
  name: string;
392
397
  alias?: string;
398
+ /**
399
+ * Custom junction table name for M2M/M2A relationships (max 63 chars for PostgreSQL)
400
+ * If not provided, auto-generated as: {source}_{target}_{name}_junction
401
+ * @example "product_tags" or "comment_refs"
402
+ */
403
+ through?: string;
393
404
  onDelete?: "CASCADE" | "RESTRICT" | "SET NULL";
394
405
  onUpdate?: "CASCADE" | "RESTRICT" | "SET NULL";
406
+ /** Target tables for M2A (polymorphic) relationships */
395
407
  tables?: string[];
396
408
  }
397
409
  interface SchemaInfo {
@@ -425,6 +437,8 @@ interface UploadOptions {
425
437
  isPublic?: boolean;
426
438
  metadata?: Record<string, unknown>;
427
439
  onProgress?: (progress: number) => void;
440
+ /** Request timeout in milliseconds (default: 30000). Set to 0 for no timeout. */
441
+ timeout?: number;
428
442
  }
429
443
  interface AssetTransformOptions {
430
444
  width?: number;
package/dist/index.cjs CHANGED
@@ -1770,7 +1770,10 @@ var FilesModule = class {
1770
1770
  const response = await this.client.upload(
1771
1771
  "/files",
1772
1772
  formData,
1773
- { onProgress: options?.onProgress }
1773
+ {
1774
+ onProgress: options?.onProgress,
1775
+ timeout: options?.timeout
1776
+ }
1774
1777
  );
1775
1778
  return response.data;
1776
1779
  }
@@ -3030,6 +3033,9 @@ var RealtimeModule = class {
3030
3033
  socketPath;
3031
3034
  subscriptions = /* @__PURE__ */ new Map();
3032
3035
  workflowCallbacks = /* @__PURE__ */ new Map();
3036
+ roomCallbacks = /* @__PURE__ */ new Map();
3037
+ // room -> event -> callbacks
3038
+ roomUserCallbacks = /* @__PURE__ */ new Map();
3033
3039
  connectionCallbacks = /* @__PURE__ */ new Set();
3034
3040
  reconnecting = false;
3035
3041
  connectionPromise = null;
@@ -3037,7 +3043,7 @@ var RealtimeModule = class {
3037
3043
  this.client = config.client;
3038
3044
  this.storage = config.storage;
3039
3045
  this.socketUrl = config.socketUrl || "";
3040
- this.socketPath = config.socketPath || "/socket";
3046
+ this.socketPath = config.socketPath || "/realtime";
3041
3047
  }
3042
3048
  /**
3043
3049
  * Set the socket.io client instance
@@ -3139,6 +3145,26 @@ var RealtimeModule = class {
3139
3145
  this.socket.on("workflow:execution:complete", (data) => {
3140
3146
  this.handleWorkflowUpdate({ ...data, status: "complete" });
3141
3147
  });
3148
+ this.socket.on("room:user:joined", (data) => {
3149
+ const callbacks = this.roomUserCallbacks.get(data.room);
3150
+ callbacks?.joined.forEach((cb) => {
3151
+ try {
3152
+ cb(data);
3153
+ } catch (e) {
3154
+ console.error("[Baasix Realtime] Error in room user joined callback:", e);
3155
+ }
3156
+ });
3157
+ });
3158
+ this.socket.on("room:user:left", (data) => {
3159
+ const callbacks = this.roomUserCallbacks.get(data.room);
3160
+ callbacks?.left.forEach((cb) => {
3161
+ try {
3162
+ cb(data);
3163
+ } catch (e) {
3164
+ console.error("[Baasix Realtime] Error in room user left callback:", e);
3165
+ }
3166
+ });
3167
+ });
3142
3168
  this.socket.connect();
3143
3169
  } catch (error) {
3144
3170
  this.connectionPromise = null;
@@ -3166,6 +3192,8 @@ var RealtimeModule = class {
3166
3192
  }
3167
3193
  this.subscriptions.clear();
3168
3194
  this.workflowCallbacks.clear();
3195
+ this.roomCallbacks.clear();
3196
+ this.roomUserCallbacks.clear();
3169
3197
  }
3170
3198
  /**
3171
3199
  * Check if connected to the realtime server
@@ -3382,6 +3410,214 @@ var RealtimeModule = class {
3382
3410
  }
3383
3411
  }
3384
3412
  // ===================
3413
+ // Custom Rooms API
3414
+ // ===================
3415
+ /**
3416
+ * Join a custom room for real-time communication
3417
+ *
3418
+ * @example
3419
+ * ```typescript
3420
+ * // Join a room
3421
+ * await baasix.realtime.joinRoom('game:lobby');
3422
+ *
3423
+ * // Listen for messages
3424
+ * baasix.realtime.onRoomMessage('game:lobby', 'chat', (data) => {
3425
+ * console.log(`${data.sender.userId}: ${data.payload.text}`);
3426
+ * });
3427
+ * ```
3428
+ */
3429
+ async joinRoom(roomName) {
3430
+ if (!this.socket?.connected) {
3431
+ throw new Error("Not connected. Call connect() first.");
3432
+ }
3433
+ return new Promise((resolve, reject) => {
3434
+ this.socket.emit("room:join", { room: roomName }, (response) => {
3435
+ if (response.status === "success") {
3436
+ this.setupRoomListeners(roomName);
3437
+ resolve();
3438
+ } else {
3439
+ reject(new Error(response.message || "Failed to join room"));
3440
+ }
3441
+ });
3442
+ });
3443
+ }
3444
+ /**
3445
+ * Leave a custom room
3446
+ *
3447
+ * @example
3448
+ * ```typescript
3449
+ * await baasix.realtime.leaveRoom('game:lobby');
3450
+ * ```
3451
+ */
3452
+ async leaveRoom(roomName) {
3453
+ if (!this.socket?.connected) {
3454
+ return;
3455
+ }
3456
+ return new Promise((resolve, reject) => {
3457
+ this.socket.emit("room:leave", { room: roomName }, (response) => {
3458
+ if (response.status === "success") {
3459
+ this.cleanupRoomListeners(roomName);
3460
+ resolve();
3461
+ } else {
3462
+ reject(new Error(response.message || "Failed to leave room"));
3463
+ }
3464
+ });
3465
+ });
3466
+ }
3467
+ /**
3468
+ * Send a message to a room
3469
+ *
3470
+ * @example
3471
+ * ```typescript
3472
+ * // Send a chat message
3473
+ * await baasix.realtime.sendToRoom('game:lobby', 'chat', { text: 'Hello!' });
3474
+ *
3475
+ * // Send a game event
3476
+ * await baasix.realtime.sendToRoom('game:123', 'move', { x: 10, y: 20 });
3477
+ * ```
3478
+ */
3479
+ async sendToRoom(roomName, event, payload) {
3480
+ if (!this.socket?.connected) {
3481
+ throw new Error("Not connected. Call connect() first.");
3482
+ }
3483
+ return new Promise((resolve, reject) => {
3484
+ this.socket.emit(
3485
+ "room:message",
3486
+ { room: roomName, event, payload },
3487
+ (response) => {
3488
+ if (response.status === "success") {
3489
+ resolve();
3490
+ } else {
3491
+ reject(new Error(response.message || "Failed to send message"));
3492
+ }
3493
+ }
3494
+ );
3495
+ });
3496
+ }
3497
+ /**
3498
+ * Listen for messages in a room with a specific event type
3499
+ *
3500
+ * @example
3501
+ * ```typescript
3502
+ * const unsubscribe = baasix.realtime.onRoomMessage('game:lobby', 'chat', (data) => {
3503
+ * console.log(`${data.sender.userId}: ${data.payload.text}`);
3504
+ * });
3505
+ *
3506
+ * // Later
3507
+ * unsubscribe();
3508
+ * ```
3509
+ */
3510
+ onRoomMessage(roomName, event, callback) {
3511
+ if (!this.roomCallbacks.has(roomName)) {
3512
+ this.roomCallbacks.set(roomName, /* @__PURE__ */ new Map());
3513
+ }
3514
+ const roomEvents = this.roomCallbacks.get(roomName);
3515
+ if (!roomEvents.has(event)) {
3516
+ roomEvents.set(event, /* @__PURE__ */ new Set());
3517
+ }
3518
+ roomEvents.get(event).add(callback);
3519
+ return () => {
3520
+ const events = this.roomCallbacks.get(roomName);
3521
+ if (events) {
3522
+ const callbacks = events.get(event);
3523
+ if (callbacks) {
3524
+ callbacks.delete(callback);
3525
+ if (callbacks.size === 0) {
3526
+ events.delete(event);
3527
+ }
3528
+ }
3529
+ if (events.size === 0) {
3530
+ this.roomCallbacks.delete(roomName);
3531
+ }
3532
+ }
3533
+ };
3534
+ }
3535
+ /**
3536
+ * Listen for users joining a room
3537
+ *
3538
+ * @example
3539
+ * ```typescript
3540
+ * const unsubscribe = baasix.realtime.onRoomUserJoined('game:lobby', (data) => {
3541
+ * console.log(`User ${data.userId} joined the room`);
3542
+ * });
3543
+ * ```
3544
+ */
3545
+ onRoomUserJoined(roomName, callback) {
3546
+ if (!this.roomUserCallbacks.has(roomName)) {
3547
+ this.roomUserCallbacks.set(roomName, { joined: /* @__PURE__ */ new Set(), left: /* @__PURE__ */ new Set() });
3548
+ }
3549
+ this.roomUserCallbacks.get(roomName).joined.add(callback);
3550
+ return () => {
3551
+ const callbacks = this.roomUserCallbacks.get(roomName);
3552
+ if (callbacks) {
3553
+ callbacks.joined.delete(callback);
3554
+ if (callbacks.joined.size === 0 && callbacks.left.size === 0) {
3555
+ this.roomUserCallbacks.delete(roomName);
3556
+ }
3557
+ }
3558
+ };
3559
+ }
3560
+ /**
3561
+ * Listen for users leaving a room
3562
+ *
3563
+ * @example
3564
+ * ```typescript
3565
+ * const unsubscribe = baasix.realtime.onRoomUserLeft('game:lobby', (data) => {
3566
+ * console.log(`User ${data.userId} left the room`);
3567
+ * });
3568
+ * ```
3569
+ */
3570
+ onRoomUserLeft(roomName, callback) {
3571
+ if (!this.roomUserCallbacks.has(roomName)) {
3572
+ this.roomUserCallbacks.set(roomName, { joined: /* @__PURE__ */ new Set(), left: /* @__PURE__ */ new Set() });
3573
+ }
3574
+ this.roomUserCallbacks.get(roomName).left.add(callback);
3575
+ return () => {
3576
+ const callbacks = this.roomUserCallbacks.get(roomName);
3577
+ if (callbacks) {
3578
+ callbacks.left.delete(callback);
3579
+ if (callbacks.joined.size === 0 && callbacks.left.size === 0) {
3580
+ this.roomUserCallbacks.delete(roomName);
3581
+ }
3582
+ }
3583
+ };
3584
+ }
3585
+ /**
3586
+ * Invoke a custom server-side handler
3587
+ *
3588
+ * @example
3589
+ * ```typescript
3590
+ * const result = await baasix.realtime.invoke('game:roll-dice', { sides: 6 });
3591
+ * console.log('Dice result:', result);
3592
+ * ```
3593
+ */
3594
+ async invoke(event, payload) {
3595
+ if (!this.socket?.connected) {
3596
+ throw new Error("Not connected. Call connect() first.");
3597
+ }
3598
+ return new Promise((resolve, reject) => {
3599
+ this.socket.emit("custom", { event, payload }, (response) => {
3600
+ if (response.status === "success") {
3601
+ resolve(response);
3602
+ } else {
3603
+ reject(new Error(response.message || "Custom event failed"));
3604
+ }
3605
+ });
3606
+ });
3607
+ }
3608
+ setupRoomListeners(roomName) {
3609
+ if (!this.roomCallbacks.has(roomName)) {
3610
+ this.roomCallbacks.set(roomName, /* @__PURE__ */ new Map());
3611
+ }
3612
+ if (!this.roomUserCallbacks.has(roomName)) {
3613
+ this.roomUserCallbacks.set(roomName, { joined: /* @__PURE__ */ new Set(), left: /* @__PURE__ */ new Set() });
3614
+ }
3615
+ }
3616
+ cleanupRoomListeners(roomName) {
3617
+ this.roomCallbacks.delete(roomName);
3618
+ this.roomUserCallbacks.delete(roomName);
3619
+ }
3620
+ // ===================
3385
3621
  // Channel (Room) API - Supabase-style
3386
3622
  // ===================
3387
3623
  /**
@@ -4097,7 +4333,8 @@ var Baasix = class {
4097
4333
  ...config,
4098
4334
  url: normalizedUrl,
4099
4335
  authMode: config.authMode || "jwt",
4100
- timeout: config.timeout || 3e4,
4336
+ timeout: config.timeout || 6e5,
4337
+ // 10 minutes default
4101
4338
  autoRefresh: config.autoRefresh !== false
4102
4339
  };
4103
4340
  this.storage = config.storage || getDefaultStorage();