@fluxbase/sdk 0.0.1-rc.31 → 0.0.1-rc.33

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.js CHANGED
@@ -291,6 +291,14 @@ var FluxbaseAuth = class {
291
291
  return { user: session.user, session };
292
292
  });
293
293
  }
294
+ /**
295
+ * Refresh the session (Supabase-compatible alias)
296
+ * Alias for refreshSession() to maintain compatibility with Supabase naming
297
+ * Returns a new session with refreshed tokens
298
+ */
299
+ async refreshToken() {
300
+ return this.refreshSession();
301
+ }
294
302
  /**
295
303
  * Get the current user from the server
296
304
  */
@@ -617,6 +625,136 @@ var FluxbaseAuth = class {
617
625
  return { provider, url };
618
626
  });
619
627
  }
628
+ /**
629
+ * Sign in with OTP (One-Time Password) - Supabase-compatible
630
+ * Sends a one-time password via email or SMS for passwordless authentication
631
+ * @param credentials - Email or phone number and optional configuration
632
+ * @returns Promise with OTP-style response
633
+ */
634
+ async signInWithOtp(credentials) {
635
+ return wrapAsync(async () => {
636
+ await this.fetch.post("/api/v1/auth/otp/signin", credentials);
637
+ return { user: null, session: null };
638
+ });
639
+ }
640
+ /**
641
+ * Verify OTP (One-Time Password) - Supabase-compatible
642
+ * Verify OTP tokens for various authentication flows
643
+ * @param params - OTP verification parameters including token and type
644
+ * @returns Promise with user and session if successful
645
+ */
646
+ async verifyOtp(params) {
647
+ return wrapAsync(async () => {
648
+ const response = await this.fetch.post(
649
+ "/api/v1/auth/otp/verify",
650
+ params
651
+ );
652
+ if (response.access_token && response.refresh_token) {
653
+ const session = {
654
+ ...response,
655
+ expires_at: Date.now() + response.expires_in * 1e3
656
+ };
657
+ this.setSessionInternal(session);
658
+ return { user: response.user, session };
659
+ }
660
+ return { user: response.user, session: null };
661
+ });
662
+ }
663
+ /**
664
+ * Resend OTP (One-Time Password) - Supabase-compatible
665
+ * Resend OTP code when user doesn't receive it
666
+ * @param params - Resend parameters including type and email/phone
667
+ * @returns Promise with OTP-style response
668
+ */
669
+ async resendOtp(params) {
670
+ return wrapAsync(async () => {
671
+ await this.fetch.post("/api/v1/auth/otp/resend", params);
672
+ return { user: null, session: null };
673
+ });
674
+ }
675
+ /**
676
+ * Get user identities (linked OAuth providers) - Supabase-compatible
677
+ * Lists all OAuth identities linked to the current user
678
+ * @returns Promise with list of user identities
679
+ */
680
+ async getUserIdentities() {
681
+ return wrapAsync(async () => {
682
+ if (!this.session) {
683
+ throw new Error("Not authenticated");
684
+ }
685
+ return await this.fetch.get(
686
+ "/api/v1/auth/user/identities"
687
+ );
688
+ });
689
+ }
690
+ /**
691
+ * Link an OAuth identity to current user - Supabase-compatible
692
+ * Links an additional OAuth provider to the existing account
693
+ * @param credentials - Provider to link
694
+ * @returns Promise with OAuth URL to complete linking
695
+ */
696
+ async linkIdentity(credentials) {
697
+ return wrapAsync(async () => {
698
+ if (!this.session) {
699
+ throw new Error("Not authenticated");
700
+ }
701
+ return await this.fetch.post(
702
+ "/api/v1/auth/user/identities",
703
+ credentials
704
+ );
705
+ });
706
+ }
707
+ /**
708
+ * Unlink an OAuth identity from current user - Supabase-compatible
709
+ * Removes a linked OAuth provider from the account
710
+ * @param params - Identity to unlink
711
+ * @returns Promise with void response
712
+ */
713
+ async unlinkIdentity(params) {
714
+ return wrapAsyncVoid(async () => {
715
+ if (!this.session) {
716
+ throw new Error("Not authenticated");
717
+ }
718
+ await this.fetch.delete(
719
+ `/api/v1/auth/user/identities/${params.identity.id}`
720
+ );
721
+ });
722
+ }
723
+ /**
724
+ * Reauthenticate to get security nonce - Supabase-compatible
725
+ * Get a security nonce for sensitive operations (password change, etc.)
726
+ * @returns Promise with nonce for reauthentication
727
+ */
728
+ async reauthenticate() {
729
+ return wrapAsync(async () => {
730
+ if (!this.session) {
731
+ throw new Error("Not authenticated");
732
+ }
733
+ return await this.fetch.post(
734
+ "/api/v1/auth/reauthenticate"
735
+ );
736
+ });
737
+ }
738
+ /**
739
+ * Sign in with ID token (for native mobile apps) - Supabase-compatible
740
+ * Authenticate using native mobile app ID tokens (Google, Apple)
741
+ * @param credentials - Provider, ID token, and optional nonce
742
+ * @returns Promise with user and session
743
+ */
744
+ async signInWithIdToken(credentials) {
745
+ return wrapAsync(async () => {
746
+ const response = await this.fetch.post(
747
+ "/api/v1/auth/signin/idtoken",
748
+ credentials
749
+ );
750
+ const session = {
751
+ ...response,
752
+ expires_at: Date.now() + response.expires_in * 1e3
753
+ };
754
+ this.setSessionInternal(session);
755
+ return { user: session.user, session };
756
+ });
757
+ }
620
758
  /**
621
759
  * Internal: Set the session and persist it
622
760
  */
@@ -700,6 +838,8 @@ var RealtimeChannel = class {
700
838
  this.maxReconnectAttempts = 10;
701
839
  this.reconnectDelay = 1e3;
702
840
  this.heartbeatInterval = null;
841
+ this.pendingAcks = /* @__PURE__ */ new Map();
842
+ this.messageIdCounter = 0;
703
843
  this.url = url;
704
844
  this.channelName = channelName;
705
845
  this.token = token;
@@ -822,16 +962,40 @@ var RealtimeChannel = class {
822
962
  return "error";
823
963
  }
824
964
  try {
965
+ const messageId = this.config.broadcast?.ack ? `msg_${Date.now()}_${++this.messageIdCounter}` : void 0;
825
966
  this.ws.send(
826
967
  JSON.stringify({
827
968
  type: "broadcast",
828
969
  channel: this.channelName,
829
970
  event: message.event,
830
- payload: message.payload
971
+ payload: message.payload,
972
+ ...messageId && { messageId }
831
973
  })
832
974
  );
833
- if (this.config.broadcast?.ack) {
834
- return "ok";
975
+ if (this.config.broadcast?.ack && messageId) {
976
+ const ackTimeout = this.config.broadcast.ackTimeout || 5e3;
977
+ return await new Promise((resolve, reject) => {
978
+ const timeout = setTimeout(() => {
979
+ this.pendingAcks.delete(messageId);
980
+ reject(new Error("Acknowledgment timeout"));
981
+ }, ackTimeout);
982
+ this.pendingAcks.set(messageId, {
983
+ resolve: (value) => {
984
+ clearTimeout(timeout);
985
+ this.pendingAcks.delete(messageId);
986
+ resolve(value);
987
+ },
988
+ reject: (reason) => {
989
+ clearTimeout(timeout);
990
+ this.pendingAcks.delete(messageId);
991
+ reject(reason);
992
+ },
993
+ timeout
994
+ });
995
+ }).catch((error) => {
996
+ console.error("[Fluxbase Realtime] Acknowledgment error:", error);
997
+ return "error";
998
+ });
835
999
  }
836
1000
  return "ok";
837
1001
  } catch (error) {
@@ -1010,7 +1174,14 @@ var RealtimeChannel = class {
1010
1174
  }
1011
1175
  break;
1012
1176
  case "ack":
1013
- console.log("[Fluxbase Realtime] Subscription acknowledged");
1177
+ if (message.messageId && this.pendingAcks.has(message.messageId)) {
1178
+ const ackHandler = this.pendingAcks.get(message.messageId);
1179
+ if (ackHandler) {
1180
+ ackHandler.resolve(message.status || "ok");
1181
+ }
1182
+ } else {
1183
+ console.log("[Fluxbase Realtime] Acknowledged:", message);
1184
+ }
1014
1185
  break;
1015
1186
  case "error":
1016
1187
  console.error("[Fluxbase Realtime] Error:", message.error);
@@ -3964,6 +4135,7 @@ var QueryBuilder = class {
3964
4135
  this.orderBys = [];
3965
4136
  this.singleRow = false;
3966
4137
  this.maybeSingleRow = false;
4138
+ this.operationType = "select";
3967
4139
  this.fetch = fetch2;
3968
4140
  this.table = table;
3969
4141
  }
@@ -3980,16 +4152,10 @@ var QueryBuilder = class {
3980
4152
  /**
3981
4153
  * Insert a single row or multiple rows
3982
4154
  */
3983
- async insert(data) {
3984
- const body = Array.isArray(data) ? data : data;
3985
- const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body);
3986
- return {
3987
- data: response,
3988
- error: null,
3989
- count: Array.isArray(data) ? data.length : 1,
3990
- status: 201,
3991
- statusText: "Created"
3992
- };
4155
+ insert(data) {
4156
+ this.operationType = "insert";
4157
+ this.insertData = data;
4158
+ return this;
3993
4159
  }
3994
4160
  /**
3995
4161
  * Upsert (insert or update) rows (Supabase-compatible)
@@ -4026,32 +4192,17 @@ var QueryBuilder = class {
4026
4192
  /**
4027
4193
  * Update rows matching the filters
4028
4194
  */
4029
- async update(data) {
4030
- const queryString = this.buildQueryString();
4031
- const path = `/api/v1/tables/${this.table}${queryString}`;
4032
- const response = await this.fetch.patch(path, data);
4033
- return {
4034
- data: response,
4035
- error: null,
4036
- count: null,
4037
- status: 200,
4038
- statusText: "OK"
4039
- };
4195
+ update(data) {
4196
+ this.operationType = "update";
4197
+ this.updateData = data;
4198
+ return this;
4040
4199
  }
4041
4200
  /**
4042
4201
  * Delete rows matching the filters
4043
4202
  */
4044
- async delete() {
4045
- const queryString = this.buildQueryString();
4046
- const path = `/api/v1/tables/${this.table}${queryString}`;
4047
- await this.fetch.delete(path);
4048
- return {
4049
- data: null,
4050
- error: null,
4051
- count: null,
4052
- status: 204,
4053
- statusText: "No Content"
4054
- };
4203
+ delete() {
4204
+ this.operationType = "delete";
4205
+ return this;
4055
4206
  }
4056
4207
  /**
4057
4208
  * Equal to
@@ -4430,13 +4581,13 @@ var QueryBuilder = class {
4430
4581
  * { name: 'Alice', email: 'alice@example.com' },
4431
4582
  * { name: 'Bob', email: 'bob@example.com' },
4432
4583
  * { name: 'Charlie', email: 'charlie@example.com' }
4433
- * ]).execute()
4584
+ * ])
4434
4585
  * ```
4435
4586
  *
4436
4587
  * @category Batch Operations
4437
4588
  */
4438
4589
  async insertMany(rows) {
4439
- return this.insert(rows);
4590
+ return this.insert(rows).execute();
4440
4591
  }
4441
4592
  /**
4442
4593
  * Update multiple rows matching the filters (batch update)
@@ -4453,19 +4604,17 @@ var QueryBuilder = class {
4453
4604
  * const { data } = await client.from('products')
4454
4605
  * .eq('category', 'electronics')
4455
4606
  * .updateMany({ discount: 10, updated_at: new Date() })
4456
- * .execute()
4457
4607
  *
4458
4608
  * // Mark all pending orders as processing
4459
4609
  * const { data } = await client.from('orders')
4460
4610
  * .eq('status', 'pending')
4461
4611
  * .updateMany({ status: 'processing' })
4462
- * .execute()
4463
4612
  * ```
4464
4613
  *
4465
4614
  * @category Batch Operations
4466
4615
  */
4467
4616
  async updateMany(data) {
4468
- return this.update(data);
4617
+ return this.update(data).execute();
4469
4618
  }
4470
4619
  /**
4471
4620
  * Delete multiple rows matching the filters (batch delete)
@@ -4481,27 +4630,66 @@ var QueryBuilder = class {
4481
4630
  * await client.from('users')
4482
4631
  * .eq('active', false)
4483
4632
  * .deleteMany()
4484
- * .execute()
4485
4633
  *
4486
4634
  * // Delete old logs
4487
4635
  * await client.from('logs')
4488
4636
  * .lt('created_at', '2024-01-01')
4489
4637
  * .deleteMany()
4490
- * .execute()
4491
4638
  * ```
4492
4639
  *
4493
4640
  * @category Batch Operations
4494
4641
  */
4495
4642
  async deleteMany() {
4496
- return this.delete();
4643
+ return this.delete().execute();
4497
4644
  }
4498
4645
  /**
4499
4646
  * Execute the query and return results
4500
4647
  */
4501
4648
  async execute() {
4502
- const queryString = this.buildQueryString();
4503
- const path = `/api/v1/tables/${this.table}${queryString}`;
4504
4649
  try {
4650
+ if (this.operationType === "insert") {
4651
+ if (!this.insertData) {
4652
+ throw new Error("Insert data is required for insert operation");
4653
+ }
4654
+ const body = Array.isArray(this.insertData) ? this.insertData : this.insertData;
4655
+ const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body);
4656
+ return {
4657
+ data: response,
4658
+ error: null,
4659
+ count: Array.isArray(this.insertData) ? this.insertData.length : 1,
4660
+ status: 201,
4661
+ statusText: "Created"
4662
+ };
4663
+ }
4664
+ if (this.operationType === "update") {
4665
+ if (!this.updateData) {
4666
+ throw new Error("Update data is required for update operation");
4667
+ }
4668
+ const queryString2 = this.buildQueryString();
4669
+ const path2 = `/api/v1/tables/${this.table}${queryString2}`;
4670
+ const response = await this.fetch.patch(path2, this.updateData);
4671
+ return {
4672
+ data: response,
4673
+ error: null,
4674
+ count: null,
4675
+ status: 200,
4676
+ statusText: "OK"
4677
+ };
4678
+ }
4679
+ if (this.operationType === "delete") {
4680
+ const queryString2 = this.buildQueryString();
4681
+ const path2 = `/api/v1/tables/${this.table}${queryString2}`;
4682
+ await this.fetch.delete(path2);
4683
+ return {
4684
+ data: null,
4685
+ error: null,
4686
+ count: null,
4687
+ status: 204,
4688
+ statusText: "No Content"
4689
+ };
4690
+ }
4691
+ const queryString = this.buildQueryString();
4692
+ const path = `/api/v1/tables/${this.table}${queryString}`;
4505
4693
  const data = await this.fetch.get(path);
4506
4694
  if (this.singleRow) {
4507
4695
  if (Array.isArray(data) && data.length === 0) {