@fluxbase/sdk 0.0.1-rc.30 → 0.0.1-rc.32

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.cjs CHANGED
@@ -3961,8 +3961,12 @@ var QueryBuilder = class {
3961
3961
  constructor(fetch2, table) {
3962
3962
  this.selectQuery = "*";
3963
3963
  this.filters = [];
3964
+ this.orFilters = [];
3965
+ this.andFilters = [];
3964
3966
  this.orderBys = [];
3965
3967
  this.singleRow = false;
3968
+ this.maybeSingleRow = false;
3969
+ this.operationType = "select";
3966
3970
  this.fetch = fetch2;
3967
3971
  this.table = table;
3968
3972
  }
@@ -3979,27 +3983,35 @@ var QueryBuilder = class {
3979
3983
  /**
3980
3984
  * Insert a single row or multiple rows
3981
3985
  */
3982
- async insert(data) {
3983
- const body = Array.isArray(data) ? data : data;
3984
- const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body);
3985
- return {
3986
- data: response,
3987
- error: null,
3988
- count: Array.isArray(data) ? data.length : 1,
3989
- status: 201,
3990
- statusText: "Created"
3991
- };
3986
+ insert(data) {
3987
+ this.operationType = "insert";
3988
+ this.insertData = data;
3989
+ return this;
3992
3990
  }
3993
3991
  /**
3994
- * Upsert (insert or update) rows
3992
+ * Upsert (insert or update) rows (Supabase-compatible)
3993
+ * @param data - Row(s) to upsert
3994
+ * @param options - Upsert options (onConflict, ignoreDuplicates, defaultToNull)
3995
3995
  */
3996
- async upsert(data) {
3996
+ async upsert(data, options) {
3997
3997
  const body = Array.isArray(data) ? data : data;
3998
- const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body, {
3999
- headers: {
4000
- Prefer: "resolution=merge-duplicates"
4001
- }
4002
- });
3998
+ const preferValues = [];
3999
+ if (options?.ignoreDuplicates) {
4000
+ preferValues.push("resolution=ignore-duplicates");
4001
+ } else {
4002
+ preferValues.push("resolution=merge-duplicates");
4003
+ }
4004
+ if (options?.defaultToNull) {
4005
+ preferValues.push("missing=default");
4006
+ }
4007
+ const headers = {
4008
+ Prefer: preferValues.join(",")
4009
+ };
4010
+ let path = `/api/v1/tables/${this.table}`;
4011
+ if (options?.onConflict) {
4012
+ path += `?on_conflict=${encodeURIComponent(options.onConflict)}`;
4013
+ }
4014
+ const response = await this.fetch.post(path, body, { headers });
4003
4015
  return {
4004
4016
  data: response,
4005
4017
  error: null,
@@ -4011,32 +4023,17 @@ var QueryBuilder = class {
4011
4023
  /**
4012
4024
  * Update rows matching the filters
4013
4025
  */
4014
- async update(data) {
4015
- const queryString = this.buildQueryString();
4016
- const path = `/api/v1/tables/${this.table}${queryString}`;
4017
- const response = await this.fetch.patch(path, data);
4018
- return {
4019
- data: response,
4020
- error: null,
4021
- count: null,
4022
- status: 200,
4023
- statusText: "OK"
4024
- };
4026
+ update(data) {
4027
+ this.operationType = "update";
4028
+ this.updateData = data;
4029
+ return this;
4025
4030
  }
4026
4031
  /**
4027
4032
  * Delete rows matching the filters
4028
4033
  */
4029
- async delete() {
4030
- const queryString = this.buildQueryString();
4031
- const path = `/api/v1/tables/${this.table}${queryString}`;
4032
- await this.fetch.delete(path);
4033
- return {
4034
- data: null,
4035
- error: null,
4036
- count: null,
4037
- status: 204,
4038
- statusText: "No Content"
4039
- };
4034
+ delete() {
4035
+ this.operationType = "delete";
4036
+ return this;
4040
4037
  }
4041
4038
  /**
4042
4039
  * Equal to
@@ -4122,6 +4119,71 @@ var QueryBuilder = class {
4122
4119
  this.filters.push({ column, operator: "fts", value: query });
4123
4120
  return this;
4124
4121
  }
4122
+ /**
4123
+ * Negate a filter condition (Supabase-compatible)
4124
+ * @example not('status', 'eq', 'deleted')
4125
+ * @example not('completed_at', 'is', null)
4126
+ */
4127
+ not(column, operator, value) {
4128
+ this.filters.push({ column, operator: "not", value: `${operator}.${this.formatValue(value)}` });
4129
+ return this;
4130
+ }
4131
+ /**
4132
+ * Apply OR logic to filters (Supabase-compatible)
4133
+ * @example or('status.eq.active,status.eq.pending')
4134
+ * @example or('id.eq.2,name.eq.Han')
4135
+ */
4136
+ or(filters) {
4137
+ this.orFilters.push(filters);
4138
+ return this;
4139
+ }
4140
+ /**
4141
+ * Apply AND logic to filters (Supabase-compatible)
4142
+ * Groups multiple conditions that must all be true
4143
+ * @example and('status.eq.active,verified.eq.true')
4144
+ * @example and('age.gte.18,age.lte.65')
4145
+ */
4146
+ and(filters) {
4147
+ this.andFilters.push(filters);
4148
+ return this;
4149
+ }
4150
+ /**
4151
+ * Match multiple columns with exact values (Supabase-compatible)
4152
+ * Shorthand for multiple .eq() calls
4153
+ * @example match({ id: 1, status: 'active', role: 'admin' })
4154
+ */
4155
+ match(conditions) {
4156
+ for (const [column, value] of Object.entries(conditions)) {
4157
+ this.eq(column, value);
4158
+ }
4159
+ return this;
4160
+ }
4161
+ /**
4162
+ * Generic filter method using PostgREST syntax (Supabase-compatible)
4163
+ * @example filter('name', 'in', '("Han","Yoda")')
4164
+ * @example filter('age', 'gte', '18')
4165
+ */
4166
+ filter(column, operator, value) {
4167
+ this.filters.push({ column, operator, value });
4168
+ return this;
4169
+ }
4170
+ /**
4171
+ * Check if column is contained by value (Supabase-compatible)
4172
+ * For arrays and JSONB
4173
+ * @example containedBy('tags', '["news","update"]')
4174
+ */
4175
+ containedBy(column, value) {
4176
+ this.filters.push({ column, operator: "cd", value });
4177
+ return this;
4178
+ }
4179
+ /**
4180
+ * Check if arrays have common elements (Supabase-compatible)
4181
+ * @example overlaps('tags', '["news","sports"]')
4182
+ */
4183
+ overlaps(column, value) {
4184
+ this.filters.push({ column, operator: "ov", value });
4185
+ return this;
4186
+ }
4125
4187
  /**
4126
4188
  * Order results
4127
4189
  */
@@ -4149,12 +4211,32 @@ var QueryBuilder = class {
4149
4211
  }
4150
4212
  /**
4151
4213
  * Return a single row (adds limit(1))
4214
+ * Errors if no rows found
4152
4215
  */
4153
4216
  single() {
4154
4217
  this.singleRow = true;
4155
4218
  this.limitValue = 1;
4156
4219
  return this;
4157
4220
  }
4221
+ /**
4222
+ * Return a single row or null (adds limit(1))
4223
+ * Does not error if no rows found (Supabase-compatible)
4224
+ * @example
4225
+ * ```typescript
4226
+ * // Returns null instead of erroring when no row exists
4227
+ * const { data, error } = await client
4228
+ * .from('users')
4229
+ * .select('*')
4230
+ * .eq('id', 999)
4231
+ * .maybeSingle()
4232
+ * // data will be null if no row found
4233
+ * ```
4234
+ */
4235
+ maybeSingle() {
4236
+ this.maybeSingleRow = true;
4237
+ this.limitValue = 1;
4238
+ return this;
4239
+ }
4158
4240
  /**
4159
4241
  * Range selection (pagination)
4160
4242
  */
@@ -4330,13 +4412,13 @@ var QueryBuilder = class {
4330
4412
  * { name: 'Alice', email: 'alice@example.com' },
4331
4413
  * { name: 'Bob', email: 'bob@example.com' },
4332
4414
  * { name: 'Charlie', email: 'charlie@example.com' }
4333
- * ]).execute()
4415
+ * ])
4334
4416
  * ```
4335
4417
  *
4336
4418
  * @category Batch Operations
4337
4419
  */
4338
4420
  async insertMany(rows) {
4339
- return this.insert(rows);
4421
+ return this.insert(rows).execute();
4340
4422
  }
4341
4423
  /**
4342
4424
  * Update multiple rows matching the filters (batch update)
@@ -4353,19 +4435,17 @@ var QueryBuilder = class {
4353
4435
  * const { data } = await client.from('products')
4354
4436
  * .eq('category', 'electronics')
4355
4437
  * .updateMany({ discount: 10, updated_at: new Date() })
4356
- * .execute()
4357
4438
  *
4358
4439
  * // Mark all pending orders as processing
4359
4440
  * const { data } = await client.from('orders')
4360
4441
  * .eq('status', 'pending')
4361
4442
  * .updateMany({ status: 'processing' })
4362
- * .execute()
4363
4443
  * ```
4364
4444
  *
4365
4445
  * @category Batch Operations
4366
4446
  */
4367
4447
  async updateMany(data) {
4368
- return this.update(data);
4448
+ return this.update(data).execute();
4369
4449
  }
4370
4450
  /**
4371
4451
  * Delete multiple rows matching the filters (batch delete)
@@ -4381,27 +4461,66 @@ var QueryBuilder = class {
4381
4461
  * await client.from('users')
4382
4462
  * .eq('active', false)
4383
4463
  * .deleteMany()
4384
- * .execute()
4385
4464
  *
4386
4465
  * // Delete old logs
4387
4466
  * await client.from('logs')
4388
4467
  * .lt('created_at', '2024-01-01')
4389
4468
  * .deleteMany()
4390
- * .execute()
4391
4469
  * ```
4392
4470
  *
4393
4471
  * @category Batch Operations
4394
4472
  */
4395
4473
  async deleteMany() {
4396
- return this.delete();
4474
+ return this.delete().execute();
4397
4475
  }
4398
4476
  /**
4399
4477
  * Execute the query and return results
4400
4478
  */
4401
4479
  async execute() {
4402
- const queryString = this.buildQueryString();
4403
- const path = `/api/v1/tables/${this.table}${queryString}`;
4404
4480
  try {
4481
+ if (this.operationType === "insert") {
4482
+ if (!this.insertData) {
4483
+ throw new Error("Insert data is required for insert operation");
4484
+ }
4485
+ const body = Array.isArray(this.insertData) ? this.insertData : this.insertData;
4486
+ const response = await this.fetch.post(`/api/v1/tables/${this.table}`, body);
4487
+ return {
4488
+ data: response,
4489
+ error: null,
4490
+ count: Array.isArray(this.insertData) ? this.insertData.length : 1,
4491
+ status: 201,
4492
+ statusText: "Created"
4493
+ };
4494
+ }
4495
+ if (this.operationType === "update") {
4496
+ if (!this.updateData) {
4497
+ throw new Error("Update data is required for update operation");
4498
+ }
4499
+ const queryString2 = this.buildQueryString();
4500
+ const path2 = `/api/v1/tables/${this.table}${queryString2}`;
4501
+ const response = await this.fetch.patch(path2, this.updateData);
4502
+ return {
4503
+ data: response,
4504
+ error: null,
4505
+ count: null,
4506
+ status: 200,
4507
+ statusText: "OK"
4508
+ };
4509
+ }
4510
+ if (this.operationType === "delete") {
4511
+ const queryString2 = this.buildQueryString();
4512
+ const path2 = `/api/v1/tables/${this.table}${queryString2}`;
4513
+ await this.fetch.delete(path2);
4514
+ return {
4515
+ data: null,
4516
+ error: null,
4517
+ count: null,
4518
+ status: 204,
4519
+ statusText: "No Content"
4520
+ };
4521
+ }
4522
+ const queryString = this.buildQueryString();
4523
+ const path = `/api/v1/tables/${this.table}${queryString}`;
4405
4524
  const data = await this.fetch.get(path);
4406
4525
  if (this.singleRow) {
4407
4526
  if (Array.isArray(data) && data.length === 0) {
@@ -4422,6 +4541,25 @@ var QueryBuilder = class {
4422
4541
  statusText: "OK"
4423
4542
  };
4424
4543
  }
4544
+ if (this.maybeSingleRow) {
4545
+ if (Array.isArray(data) && data.length === 0) {
4546
+ return {
4547
+ data: null,
4548
+ error: null,
4549
+ count: 0,
4550
+ status: 200,
4551
+ statusText: "OK"
4552
+ };
4553
+ }
4554
+ const singleData = Array.isArray(data) ? data[0] : data;
4555
+ return {
4556
+ data: singleData,
4557
+ error: null,
4558
+ count: 1,
4559
+ status: 200,
4560
+ statusText: "OK"
4561
+ };
4562
+ }
4425
4563
  return {
4426
4564
  data,
4427
4565
  error: null,
@@ -4443,6 +4581,37 @@ var QueryBuilder = class {
4443
4581
  };
4444
4582
  }
4445
4583
  }
4584
+ /**
4585
+ * Execute the query and throw an error if one occurs (Supabase-compatible)
4586
+ * Returns the data directly instead of { data, error } wrapper
4587
+ *
4588
+ * @throws {Error} If the query fails or returns an error
4589
+ * @example
4590
+ * ```typescript
4591
+ * // Throws error instead of returning { data, error }
4592
+ * try {
4593
+ * const user = await client
4594
+ * .from('users')
4595
+ * .select('*')
4596
+ * .eq('id', 1)
4597
+ * .single()
4598
+ * .throwOnError()
4599
+ * } catch (error) {
4600
+ * console.error('Query failed:', error)
4601
+ * }
4602
+ * ```
4603
+ */
4604
+ async throwOnError() {
4605
+ const response = await this.execute();
4606
+ if (response.error) {
4607
+ const error = new Error(response.error.message);
4608
+ if (response.error.code) {
4609
+ error.code = response.error.code;
4610
+ }
4611
+ throw error;
4612
+ }
4613
+ return response.data;
4614
+ }
4446
4615
  /**
4447
4616
  * Make QueryBuilder awaitable (implements PromiseLike)
4448
4617
  * This allows using `await client.from('table').select()` without calling `.execute()`
@@ -4470,6 +4639,12 @@ var QueryBuilder = class {
4470
4639
  for (const filter of this.filters) {
4471
4640
  params.append(filter.column, `${filter.operator}.${this.formatValue(filter.value)}`);
4472
4641
  }
4642
+ for (const orFilter of this.orFilters) {
4643
+ params.append("or", `(${orFilter})`);
4644
+ }
4645
+ for (const andFilter of this.andFilters) {
4646
+ params.append("and", `(${andFilter})`);
4647
+ }
4473
4648
  if (this.groupByColumns && this.groupByColumns.length > 0) {
4474
4649
  params.append("group_by", this.groupByColumns.join(","));
4475
4650
  }